pvl.hosts.Host.fqnd(): assume any host with a . is a fqdn
"""
Simple parser for ISC dhcpd config files.
"""
import logging; log = logging.getLogger('pvl.dhcp.config')
class DHCPConfigParser (object) :
"""
Simplistic parser for a dhcpd.leases file.
Doesn't implement the full spec, but a useful approximation.
"""
@classmethod
def load (cls, file) :
return cls().parse_file(file)
def __init__ (self) :
self.stack = []
self.block = None
self.items = []
self.blocks = []
@classmethod
def split (cls, line) :
"""
Split given line-data.
>>> split = DHCPConfigParser.split
>>> split('foo bar')
['foo', 'bar']
>>> split('"foo"')
['foo']
>>> split('foo "asdf quux" bar')
['foo', 'asdf quux', 'bar']
>>> split('foo "asdf quux"')
['foo', 'asdf quux']
"""
# parse out one str
if '"' in line :
log.debug("%s", line)
# crude
pre, line = line.split('"', 1)
data, post = line.rsplit('"', 1)
return pre.split() + [data] + post.split()
else :
return line.split()
@classmethod
def lex (self, line) :
"""
Yield tokens from the given lines.
>>> lex = DHCPConfigParser.lex
>>> list(lex('foo;'))
[('item', ['foo'])]
>>> list(item for line in ['foo {', ' bar;', '}'] for item in lex(line))
[('open', ['foo']), ('item', ['bar']), ('close', None)]
"""
log.debug("%s", line)
# comments?
if '#' in line :
line, comment = line.split('#', 1)
else :
comment = None
# clean?
line = line.strip()
# parse
if not line :
# ignore, empty/comment
return
elif line.startswith('uid') :
# XXX: too hard to parse properly
return
elif '{' in line :
decl, line = line.split('{', 1)
# we are in a new decl
yield 'open', self.split(decl)
elif ';' in line :
param, line = line.split(';', 1)
# a stanza
yield 'item', self.split(param)
elif '}' in line :
close, line = line.split('}', 1)
if close.strip() :
log.warn("Predata on close: %s", close)
# end
yield 'close', None
else :
log.warn("Unknown line: %s", line)
return
# got the whole line?
if line.strip() :
log.warn("Data remains: %s", line)
def push_block (self, block) :
"""
Open new block.
"""
self.stack.append((self.block, self.items, self.blocks))
self.block = block
self.items = []
self.blocks = []
def feed_item (self, item) :
"""
Add item to block
"""
self.items.append(item)
def pop_block (self) :
"""
Close block. Returns
(block, [items])
"""
assert self.block
block = (self.block, self.items, self.blocks)
self.block, self.items, self.blocks = self.stack.pop(-1)
self.blocks.append(block)
return block
def parse_line (self, line) :
"""
Parse given line, yielding any complete blocks that come out.
Yields (block, [ lines ]) tuples.
>>> parser = DHCPConfigParser()
>>> list(parser.parse_lines(['foo {', ' bar;', ' quux asdf;', '}']))
[(['foo'], [['bar'], ['quux', 'asdf']], [])]
>>> parser = DHCPConfigParser()
>>> list(parser.parse_line('foo {'))
[]
>>> list(parser.parse_lines([' bar;', ' quux asdf;']))
[]
>>> list(parser.parse_line('}'))
[(['foo'], [['bar'], ['quux', 'asdf']], [])]
"""
for token, args in self.lex(line) :
#log.debug("%s: %s [block=%s]", token, args, self.block)
if token == 'open' :
# open new block
block = args
if self.block :
log.debug("nested block: %s > %s", self.block, block)
else :
log.debug("open block: %s", block)
self.push_block(block)
elif token == 'close' :
log.debug("close block: %s", self.block)
# collected block items
yield self.pop_block()
# must be within block!
elif token == 'item' :
item = args
log.debug("block %s item: %s", self.block, item)
self.feed_item(item)
else :
# ???
raise KeyError("Unknown token: {0}: {1}".format(token, args))
def parse_lines (self, lines) :
"""
Trivial wrapper around parse to parse multiple lines.
"""
for line in lines :
for item in self.parse_line(line) :
yield item
def parse_file (self, file) :
"""
Parse an entire file, returning (items, blocks) lists.
>>> DHCPConfigParser().parse_file(['foo;', 'barfoo {', 'bar;', '}'])
([['foo']], [(['barfoo'], [['bar']], [])])
"""
for line in file :
for item in self.parse_line(line) :
log.debug("%s", item)
assert not self.block
return self.items, self.blocks