pvl.dns.zone: move to ZoneLine/Record.load(file)
authorTero Marttila <terom@paivola.fi>
Wed, 11 Sep 2013 14:02:22 +0300
changeset 250 65f0272ce458
parent 249 8dfe61659b18
child 251 d250f200dd7e
pvl.dns.zone: move to ZoneLine/Record.load(file)
bin/pvl.dns-zone
pvl/dns/zone.py
--- a/bin/pvl.dns-zone	Wed Sep 11 14:01:57 2013 +0300
+++ b/bin/pvl.dns-zone	Wed Sep 11 14:02:22 2013 +0300
@@ -261,7 +261,7 @@
     for file in input_files :
         log.info("Reading zone: %s", file)
 
-        zone += list(pvl.dns.zone.parse_zone_records(file, 
+        zone += list(pvl.dns.zone.ZoneRecord.load(file, 
             line_timestamp_prefix   = options.input_line_date,
         ))
 
--- 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.