Add support for expansion of the $GENERATE directive to all RRs in the
given range with given name and content formatting strings.

Add an additional parser for parsing $GENERATE format strings:
Tokenizer.get_generate_string(self)
---
 dns/tokenizer.py |  111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 dns/zone.py      |   94 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 205 insertions(+), 0 deletions(-)

diff --git a/dns/tokenizer.py b/dns/tokenizer.py
index 4f68a2a..b637d59 100644
--- a/dns/tokenizer.py
+++ b/dns/tokenizer.py
@@ -527,6 +527,117 @@ class Tokenizer(object):
             raise dns.exception.SyntaxError('expecting an identifier')
         return dns.name.from_text(token.value, origin)
 
+    def get_generate_string(self):
+        """Read the next identifier token and interpret it as a $GENERATE 
format string.
+
+        @raises dns.exception.UnexpectedEnd: input ended prematurely
+        @raises dns.exception.SyntaxError: input was badly formed
+        @rtype: function that takes an integer as parameter and returns a 
string"""
+
+        (ttype, t) = self.get()
+        if ttype != IDENTIFIER:
+            raise dns.exception.SyntaxError, 'expecting an identifier'
+
+        format = ''
+        offsets = set()
+        escape = False
+        iterator_start = False
+        (ITFORMAT_NONE, ITFORMAT_OFFSET, ITFORMAT_WIDTH, ITFORMAT_RADIX) = 
range(4)
+        iterator_format = ITFORMAT_NONE
+        format_offset = ''
+        format_width = ''
+        format_radix = ''
+        while True:
+            try:
+                c = t[0]
+            except IndexError:
+                if   escape:
+                    format += '\\'
+                elif iterator_start:
+                    format += '%(offset0)d'
+                    offsets.add(0)
+                elif iterator_format:
+                    raise dns.exception.UnexpectedEnd
+                break
+
+            t = t[1:]
+
+            if   escape:
+                if   c in ('\\', '$'):
+                    format += c
+                elif c == '{' and iterator_start:
+                    format += '%(offset0)d' + c
+                    offsets.add(0)
+                    iterator_start = False
+                else:
+                    format += '\\' + c
+                escape = False
+                continue
+            elif iterator_format:
+                if   c == '}':
+                    format += '%'
+
+                    format_offset = int(format_offset)
+                    format += '(offset%d)' % format_offset
+                    offsets.add(format_offset)
+
+                    if format_width:
+                        format += '0%d' % int(format_width)
+
+                    format += format_radix or 'd'
+
+                    iterator_format = ITFORMAT_NONE
+                    continue
+                elif c in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'):
+                    if   iterator_format == ITFORMAT_OFFSET:
+                        format_offset += c
+                    elif iterator_format == ITFORMAT_WIDTH:
+                        format_width += c
+                    elif iterator_format == ITFORMAT_RADIX:
+                        raise dns.exception.SyntaxError, \
+                              "bad radix in $GENERATE record name"
+                    continue
+                elif c in ('d', 'o', 'x', 'X'):
+                    if iterator_format != ITFORMAT_RADIX or format_radix:
+                        raise dns.exception.SyntaxError, \
+                              "bad radix in $GENERATE record name"
+                    format_radix += c
+                    continue
+                elif c == ',':
+                    if   iterator_format == ITFORMAT_OFFSET and not 
format_offset:
+                        raise dns.exception.SyntaxError, \
+                              "empty offset in $GENERATE record name"
+                    elif iterator_format == ITFORMAT_WIDTH and not 
format_width:
+                        raise dns.exception.SyntaxError, \
+                              "empty width in $GENERATE record name"
+                    elif iterator_format == ITFORMAT_RADIX:
+                        raise dns.exception.SyntaxError, \
+                              "superfluos ',' in $GENERATE record name"
+                    iterator_format += 1
+                    continue
+                raise dns.exception.SyntaxError, \
+                      "unknown '%s' in format specifier in $GENERATE record 
name" % c
+            elif c == '\\':
+                escape = True
+                continue
+            elif c == '$':
+                iterator_start = True
+                continue
+            elif c == '{' and iterator_start:
+                iterator_start = False
+                iterator_format = ITFORMAT_OFFSET
+                format_offset = ''
+                format_width = ''
+                format_radix = ''
+                continue
+            format += c
+
+        def func(num):
+            numbers = dict(('offset%d' % offset, num + offset) for offset in 
offsets)
+            return format % numbers
+
+        return func
+
     def get_eol(self):
         """Read the next token and raise an exception if it isn't EOL or
         EOF.
diff --git a/dns/zone.py b/dns/zone.py
index 93c157d..a91dfc8 100644
--- a/dns/zone.py
+++ b/dns/zone.py
@@ -17,6 +17,7 @@
 
 from __future__ import generators
 
+import re
 import sys
 
 import dns.exception
@@ -703,6 +704,99 @@ class _MasterReader(object):
                         self.tok = dns.tokenizer.Tokenizer(self.current_file,
                                                            filename)
                         self.current_origin = new_origin
+                    elif u == '$GENERATE':
+                        if self.current_origin is None:
+                            raise UnknownOrigin
+
+                        # Generator range
+                        token = self.tok.get()
+                        m = re.match(r'(\d+)-(\d+)(?:/(\d+))?$', token[1])
+                        if token[0] != dns.tokenizer.IDENTIFIER or not m:
+                            raise dns.exception.SyntaxError, \
+                                  "bad range '%s' in $GENERATE" % token[1]
+                        (start, last, step) = m.groups()
+                        (start, stop) = (int(start), int(last) + 1)
+                        if step is None:
+                            step = 1
+                        else:
+                            step = int(step)
+                        range = xrange(int(start), stop, int(step))
+
+                        # Name
+                        lhs = self.tok.get_generate_string()
+
+                        token = self.tok.get()
+                        if token[0] != dns.tokenizer.IDENTIFIER:
+                            raise dns.exception.SyntaxError
+                        # TTL
+                        try:
+                            ttl = dns.ttl.from_text(token[1])
+                            token = self.tok.get()
+                            if token[0] != dns.tokenizer.IDENTIFIER:
+                                raise dns.exception.SyntaxError
+                        except dns.ttl.BadTTL:
+                            ttl = self.ttl
+                        # Class
+                        try:
+                            rdclass = dns.rdataclass.from_text(token[1])
+                            token = self.tok.get()
+                            if token[0] != dns.tokenizer.IDENTIFIER:
+                                raise dns.exception.SyntaxError
+                        except dns.exception.SyntaxError:
+                            raise dns.exception.SyntaxError
+                        except:
+                            rdclass = self.zone.rdclass
+                        if rdclass != self.zone.rdclass:
+                            raise dns.exception.SyntaxError, "RR class is not 
zone's class"
+                        # Type
+                        try:
+                            rdtype = dns.rdatatype.from_text(token[1])
+                        except:
+                            raise dns.exception.SyntaxError, \
+                                  "unknown rdatatype '%s'" % token[1]
+                        if not rdtype in (dns.rdatatype.A, dns.rdatatype.AAAA, 
dns.rdatatype.PTR, dns.rdatatype.CNAME, dns.rdatatype.NS):
+                            raise dns.exception.SyntaxError, \
+                                  "unsupported rdatatype '%s' in $GENERATE" % 
token[1]
+
+                        # Content
+                        rhs = self.tok.get_generate_string()
+
+                        # Now produce the RRs for the given range with lhs and 
rhs
+                        def RRs(lhs, rhs):
+                            for i in range:
+                                name = dns.name.from_text(lhs(i), 
self.current_origin)
+                                content = rhs(i)
+                                yield name, content
+
+                        for name, content in RRs(lhs, rhs):
+                            if self.relativize:
+                                name = name.relativize(self.zone.origin)
+                            n = self.zone.nodes.get(name)
+                            if n is None:
+                                n = self.zone.node_factory()
+                                self.zone.nodes[name] = n
+                            try:
+                                rd = dns.rdata.from_text(rdclass, rdtype, 
content,
+                                                         self.current_origin, 
False)
+                            except dns.exception.SyntaxError:
+                                # Catch and reraise.
+                                (ty, va) = sys.exc_info()[:2]
+                                raise ty, va
+                            except:
+                                # All exceptions that occur in the processing 
of rdata
+                                # are treated as syntax errors.  This is not 
strictly
+                                # correct, but it is correct almost all of the 
time.
+                                # We convert them to syntax errors so that we 
can emit
+                                # helpful filename:line info.
+
+                                (ty, va) = sys.exc_info()[:2]
+                                raise dns.exception.SyntaxError, \
+                                      "caught exception %s: %s" % (str(ty), 
str(va))
+
+                            rd.choose_relativity(self.zone.origin, 
self.relativize)
+                            covers = rd.covers()
+                            rds = n.find_rdataset(rdclass, rdtype, covers, 
True)
+                            rds.add(rd, ttl)
                     else:
                         raise dns.exception.SyntaxError("Unknown master file 
directive '" + u + "'")
                     continue
-- 
1.6.6

_______________________________________________
dnspython-dev mailing list
[email protected]
http://howl.play-bow.org/mailman/listinfo.cgi/dnspython-dev

Reply via email to