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