--- a/pvl/dns/zone.py Fri Feb 27 14:06:03 2015 +0200
+++ b/pvl/dns/zone.py Fri Feb 27 16:45:53 2015 +0200
@@ -54,7 +54,11 @@
"""
for line in cls.parse(file) :
- if line.parts[0].startswith('$'):
+ if not line.parts:
+ log.debug("%s: Skip empty line: %r", line, line)
+ continue
+
+ elif line.parts[0].startswith('$'):
# control directive
line_type = ZoneDirective
@@ -213,6 +217,19 @@
return cls(directive, arguments, **opts)
+ @classmethod
+ def INCLUDE (cls, path, origin=None, **opts):
+ """
+ Build $INCLUDE "path" [origin]
+
+ Note that origin should be a FQDN, or it will be interpreted relative to the current origin!
+ """
+
+ if origin:
+ return cls.build('INCLUDE', zone_quote(path), origin, **opts)
+ else:
+ return cls.build('INCLUDE', zone_quote(path), **opts)
+
def __init__ (self, directive, arguments, comment=None, line=None, origin=None):
"""
directive - uppercase directive name, withtout leading $
@@ -314,7 +331,11 @@
name = None
for line in ZoneLine.parse(file):
- if line.parts[0].startswith('$'):
+ if not line.parts:
+ log.debug("%s: Skip empty line: %r", line, line)
+ continue
+
+ elif line.parts[0].startswith('$'):
directive = ZoneDirective.parse(line.parts,
origin = origin,
line = line,
@@ -326,14 +347,14 @@
if directive.directive == 'ORIGIN':
directive_origin, = directive.arguments
- log.info("%s: $ORIGIN %s -> %s", file, origin, directive_origin)
+ log.info("%s: $ORIGIN %s <- %s", line, directive_origin, origin)
origin = pvl.dns.labels.join(origin, directive_origin)
elif directive.directive == 'TTL' :
directive_ttl, = directive.arguments
- log.info("%s: $TTL %d -> %s", file, ttl, directive_ttl)
+ log.info("%s: $TTL %s <- %s", line, directive_ttl, ttl)
ttl = int(directive_ttl)
@@ -398,20 +419,23 @@
_cls = None
if parts and parts[0][0].isdigit() :
- ttl = int(parts.pop(0))
+ ttl = parts.pop(0)
if parts and parts[0].upper() in ('IN', 'CH') :
- _cls = parts.pop(0).upper()
+ _cls = parts.pop(0)
# always have type
- type = parts.pop(0).upper()
+ type = parts.pop(0)
# remaining parts are data
data = parts
log.debug(" ttl=%r, cls=%r, type=%r, data=%r", ttl, _cls, type, data)
- return cls(name, ttl, _cls, type, data, line=line, **opts)
+ # optional subclass for build()
+ cls = ZONE_RECORD_TYPES.get(type.upper(), cls)
+
+ return cls.build(name, type, *data, ttl=ttl, cls=_cls, line=line, **opts)
@classmethod
def build (_cls, name, type, *data, **opts):
@@ -536,29 +560,32 @@
self.name, self.ttl, self.cls, self.type, self.data
)))
-class SOA (ZoneRecord) :
+class ZoneRecordSOA (ZoneRecord):
+ """
+ Specialized SOA record.
+ """
+
@classmethod
- def build (cls, line, name, ttl, _cls, type, data, **opts) :
- assert name == '@'
+ def build (_cls, name, type,
+ master, contact, serial, refresh, retry, expire, nxttl,
+ line=None,
+ **opts
+ ):
+ assert type == 'SOA'
- return cls(*data,
- ttl = ttl,
- cls = cls,
- line = line,
+ if name != '@':
+ raise ZoneLineError(line, "SOA should be @: {name}", name=name)
+
+ return super(ZoneRecordSOA, _cls).build('@', 'SOA',
+ master, contact, serial, refresh, retry, expire, nxttl,
**opts
)
- def __init__ (self, master, contact, serial, refresh, retry, expire, nxttl, **opts) :
- super(SOA, self).__init__('@', 'SOA',
- [master, contact, serial, refresh, retry, expire, nxttl],
- **opts
- )
+ def __init__ (self, name, ttl, cls, type, data, **opts):
+ super(ZoneRecordSOA, self).__init__(name, ttl, cls, type, data, **opts)
- self.master = master
- self.contact = contact
- self.serial = serial
- self.refresh = refresh
- self.retry = retry
- self.expire = expire
- self.nxttl = nxttl
+ self.master, self.contact, self.serial, self.refresh, self.retry, self.expire, self.nxttl = data
+ZONE_RECORD_TYPES = {
+ 'SOA': ZoneRecordSOA,
+}