pvl/dns/generate.py
author Tero Marttila <terom@paivola.fi>
Tue, 10 Mar 2015 00:11:43 +0200
changeset 739 5149c39f3dfc
parent 459 927b9caf3be6
permissions -rw-r--r--
pvl.hosts: improve HostExtension support enough to move boot= into pvl.hosts.dhcp
import logging

log = logging.getLogger('pvl.dns.generate')

class OffsetValue (object) :
    """
        Magic for $GENERATE offsets.

        >>> OffsetValue(0)[1]
        1
        >>> OffsetValue(10)[5]
        15
    """

    def __init__ (self, base) :
        self.base = base

    def __getitem__ (self, offset) :
        return self.base + offset

def parse_generate_field (field) :
    """
        Parse a $GENERATE lhs/rhs field:
            $
            ${<offset>[,<width>[,<base>]]}
            \$
            $$

        Returns a wrapper that builds the field-value when called with the index.
        
        >>> parse_generate_field("foo")(1)
        'foo'
        >>> parse_generate_field("foo-$")(1)
        'foo-1'
        >>> parse_generate_field("foo-$$")(1)
        'foo-$'
        >>> parse_generate_field("\$")(1)
        '$'
        >>> parse_generate_field("10.0.0.${100}")(1)
        '10.0.0.101'
        >>> parse_generate_field("foo-${0,2,d}")(1)
        'foo-01'

    """

    input = field
    expr = []

    while '$' in field :
        # defaults
        offset = 0
        width = 0
        base = 'd'
        escape = False

        # different forms
        if '${' in field :
            pre, body = field.split('${', 1)
            body, post = body.split('}', 1)

            # parse body
            parts = body.split(',')

            # offset
            offset = int(parts.pop(0))

            # width
            if parts :
                width = int(parts.pop(0))

            # base
            if parts :
                base = parts.pop(0)
            
            if parts:
                # fail
                raise ValueError("extra data in ${...} body: {0!r}", parts)

        elif '$$' in field :
            pre, post = field.split('$$', 1)
            escape = True

        elif '\\$' in field :
            pre, post = field.split('\\$', 1)
            escape = True

        else :
            pre, post = field.split('$', 1)
        
        expr.append(pre)

        if escape :
            expr.append('$')

        else :
            # meta-format
            fmt = '{value[%d]:0%d%s}' % (offset, width, base)

            log.debug("field=%r -> pre=%r, fmt=%r, post=%r", field, expr, fmt, post)

            expr.append(fmt)

        field = post

    # final
    if field :
        expr.append(field)
    
    # combine
    expr = ''.join(expr)

    log.debug("%s: %s", input, expr)

    # processed
    def value_func (value) :
        # magic wrapper to implement offsets
        return expr.format(value=OffsetValue(value))
    
    return value_func

def parse_generate_range (field) :
    """
        Parse a <start>-<stop>[/<step>] field
    """

    if '/' in field :
        field, step = field.split('/')
        step = int(step)
    else :
        step = 1

    start, stop = field.split('-')
    start = int(start)
    stop = int(stop)

    log.debug("  range: start=%r, stop=%r, step=%r", start, stop, step)

    # inclusive
    return range(start, stop + 1, step)