--- a/pvl/dns/zone.py Wed Sep 11 14:01:57 2013 +0300
+++ b/pvl/dns/zone.py Wed Sep 11 14:02:22 2013 +0300
@@ -23,9 +23,96 @@
class ZoneLine (object) :
"""
- A line in a zonefile.
+ A line parsed from a zonefile.
"""
+ @classmethod
+ def load (cls, file, filename=None, line_timestamp_prefix=None) :
+ """
+ Yield ZoneLines lexed from a file.
+ """
+
+ if not filename :
+ filename = file.name
+
+ multiline_start = None
+ multiline_parts = None
+
+ for lineno, raw_line in enumerate(file) :
+ # possible mtime prefix for line
+ timestamp = None
+
+ if line_timestamp_prefix :
+ if ': ' not in raw_line :
+ raise ZoneError("%s:%d: Missing timestamp prefix: %s" % (filename, lineno, raw_line))
+
+ # split prefix
+ prefix, raw_line = raw_line.split(': ', 1)
+
+ # parse it out
+ timestamp = datetime.strptime(prefix, cls.PARSE_DATETIME_FORMAT)
+
+ log.debug("%s:%d: ts=%r", filename, lineno, ts)
+
+ log.debug("%s:%d: %s", filename, lineno, raw_line)
+
+ # capture indent from raw line
+ indent = raw_line.startswith(' ') or raw_line.startswith('\t')
+ line = raw_line.strip()
+
+ # parse comment
+ if ';' in line:
+ line, comment = line.split(';', 1)
+
+ line = line.strip()
+ comment = comment.strip()
+ else :
+ comment = None
+
+ log.debug("%s:%d: indent=%r, line=%r, comment=%r", filename, lineno, indent, line, comment)
+
+ # split (quoted) fields
+ if '"' in line :
+ pre, data, post = line.split('"', 2)
+ parts = pre.split() + [data] + post.split()
+
+ else :
+ parts = line.split()
+
+ # handle multi-line statements...
+ if '(' in parts :
+ assert not multiline_start
+
+ log.warn("%s:%d: Start of multi-line statement: %s", filename, lineno, line)
+
+ multiline_start = (lineno, timestamp, indent, comment)
+ multiline_line = raw_line
+ multiline_parts = []
+
+ if multiline_start:
+ log.warn("%s:%d: Multi-line statement: %s", filename, lineno, line)
+
+ # XXX: some better way to do this
+ multiline_parts.extend([part for part in parts if part not in set('()')])
+ multiline_line += raw_line
+
+ if ')' in parts :
+ assert multiline_start
+
+ log.warn("%s:%d: End of multi-line statement: %s", filename, lineno, line)
+
+ lineno, timestamp, indent, comment = multiline_start
+ raw_line = multiline_line
+ parts = multiline_parts
+
+ multiline_start = multiline_line = multiline_parts = None
+
+ # parse
+ if multiline_start:
+ pass
+ else:
+ yield ZoneLine(filename, lineno, raw_line, indent, parts, comment, timestamp=timestamp)
+
file = None
lineno = None
@@ -38,7 +125,7 @@
comment = None
PARSE_DATETIME_FORMAT = '%Y-%m-%d'
-
+
def __init__ (self, file, lineno, line, indent, parts, comment=None, timestamp=None) :
# source
self.file = file
@@ -60,28 +147,63 @@
"""
A record from a zonefile.
"""
-
- # the underlying line
- line = None
-
- # possible $ORIGIN context
- origin = None
+
+ # context
+ line = None # the underlying line
+ origin = None # possible $ORIGIN context
# record fields
name = None
+ ttl = None # optional
+ cls = None # optional
type = None
-
- # list of data fields
- data = None
+ data = None # list of data fields
+
+ @classmethod
+ def load (cls, file, origin=None, **opts) :
+ """
+ Parse ZoneRecord items from the given zonefile, ignoring non-record lines.
+ """
- # optional
- ttl = None
- cls = None
+ for line in ZoneLine.load(file, **opts):
+ if not line.parts :
+ log.debug("%s: skip empty line", line)
+ elif line.line.startswith('$') :
+ # control record
+ type = line.parts[0]
+
+ if type == '$ORIGIN':
+ # update
+ origin = line.parts[1]
+
+ log.info("%s: origin: %s", line, origin)
+
+ elif type == '$GENERATE':
+ # process...
+ log.info("%s: generate: %s", line, line.parts)
+
+ for record in process_generate(line, origin, line.parts[1:]) :
+ yield record
+
+ else :
+ log.warning("%s: skip control record: %s", line, line.line)
+
+ else :
+ # normal record?
+ record = ZoneRecord.build(line, origin=origin)
+
+ if record :
+ yield record
+
+ else :
+ # unknown
+ log.warning("%s: skip unknown line: %s", line, line.line)
+
@classmethod
- def parse (cls, line, parts=None, origin=None) :
+ def build (cls, line, parts=None, origin=None) :
"""
- Parse from ZoneLine. Returns None if there is no record on the line..
+ Build a ZoneRecord from a ZoneLine.
"""
if parts is None :
@@ -333,130 +455,7 @@
# parse
yield ZoneRecord.parse(line, parts=parts, origin=origin)
-def parse_zone_lines (file, line_timestamp_prefix=None) :
- """
- Parse ZoneLines from a file.
- """
-
- multiline_start = None
- multiline_parts = None
-
- for lineno, raw_line in enumerate(file) :
- # possible mtime prefix for line
- timestamp = None
-
- if line_timestamp_prefix :
- if ': ' not in raw_line :
- raise ZoneError("%s:%d: Missing timestamp prefix: %s" % (file.name, lineno, raw_line))
-
- # split prefix
- prefix, raw_line = raw_line.split(': ', 1)
-
- # parse it out
- timestamp = datetime.strptime(prefix, cls.PARSE_DATETIME_FORMAT)
-
- log.debug("%s:%d: ts=%r", file.name, lineno, ts)
-
- log.debug("%s:%d: %s", file.name, lineno, raw_line)
-
- # capture indent from raw line
- indent = raw_line.startswith(' ') or raw_line.startswith('\t')
- line = raw_line.strip()
-
- # parse comment
- if ';' in line:
- line, comment = line.split(';', 1)
-
- line = line.strip()
- comment = comment.strip()
- else :
- comment = None
-
- log.debug("%s:%d: indent=%r, line=%r, comment=%r", file.name, lineno, indent, line, comment)
-
- # parse fields
- if '"' in line :
- pre, data, post = line.split('"', 2)
- parts = pre.split() + [data] + post.split()
-
- else :
- parts = line.split()
-
- # handle multi-line statements...
- if '(' in parts :
- assert not multiline_start
-
- log.warn("%s:%d: Start of multi-line statement: %s", file.name, lineno, line)
-
- multiline_start = (lineno, timestamp, indent, comment)
- multiline_line = raw_line
- multiline_parts = []
-
- if multiline_start:
- log.warn("%s:%d: Multi-line statement: %s", file.name, lineno, line)
-
- multiline_parts.extend([part for part in parts if part not in set('()')])
- multiline_line += raw_line
-
- if ')' in parts :
- assert multiline_start
-
- log.warn("%s:%d: End of multi-line statement: %s", file.name, lineno, line)
-
- lineno, timestamp, indent, comment = multiline_start
- raw_line = multiline_line
- parts = multiline_parts
-
- multiline_start = multiline_line = multiline_parts = None
-
- # parse
- if multiline_start:
- pass
- else:
- yield ZoneLine(file.name, lineno, raw_line, indent, parts, comment, timestamp=timestamp)
-
-def parse_zone_records (file, origin=None, **opts) :
- """
- Parse ZoneRecord items from the given zonefile, ignoring non-record lines.
- """
-
- ttl = None
-
- for line in parse_zone_lines(file, **opts):
- if not line.parts :
- log.debug("%s: skip empty line", line)
-
- elif line.line.startswith('$') :
- # control record
- type = line.parts[0]
-
- if type == '$ORIGIN':
- # update
- origin = line.parts[1]
-
- log.info("%s: origin: %s", line, origin)
-
- elif type == '$GENERATE':
- # process...
- log.info("%s: generate: %s", line, line.parts)
-
- for record in process_generate(line, origin, line.parts[1:]) :
- yield record
-
- else :
- log.warning("%s: skip control record: %s", line, line.line)
-
- else :
- # normal record?
- record = ZoneRecord.parse(line, origin=origin)
-
- if record :
- yield record
-
- else :
- # unknown
- log.warning("%s: skip unknown line: %s", line, line.line)
-
+
def reverse_ipv4 (ip) :
"""
Return in-addr.arpa reverse for given IPv4 prefix.