#!/usr/bin/env python

example = '''
# All times in this file are in UTC (GMT), not your local timezone.   This is
# not a bug, so please don't ask about it.   There is no portable way to
# store leases in the local timezone, so please don't request this as a
# feature.   If this is inconvenient or confusing to you, we sincerely
# apologize.   Seriously, though - don't ask.
# The format of this file is documented in the dhcpd.leases(5) manual page.
# This lease file was written by isc-dhcp-V3.0.5

lease 136.168.227.186 {
  starts 5 2008/10/24 16:59:31;
  ends 6 2008/10/25 16:59:31;
  cltt 1 2010/05/03 16:15:53;
  tstp 6 2008/10/25 16:59:31;
  binding state free;
  hardware ethernet 00:08:74:48:52:81;
  uid "\001\000\010tHR\201";
}
lease 136.168.227.187 {
  starts 5 2009/03/06 17:50:20;
  ends 6 2009/03/07 17:50:20;
  tstp 6 2009/03/07 17:50:20;
  cltt 1 2010/05/03 16:15:53;
  binding state active;
  next binding state free;
  hardware ethernet 00:19:d1:6e:52:d6;
  uid "\001\000\031\321nR\326";
  set ddns-fwd-name = "17091ACS-0-19-d1-6e-52-d6.dyn.csub.edu";
  set ddns-txt = "31faf27d99a0eee0772af8c4d110776bd7";
  set ddns-rev-name = "187.227.168.136.in-addr.arpa.";
  option agent.circuit-id 0:4:0:e3:2:2a;
  option agent.remote-id 0:6:0:1f:27:db:c1:80;
  client-hostname "17091ACS";
}
lease 136.168.144.189 {
  starts 1 2010/05/03 16:35:53;
  ends 1 2010/05/03 16:36:11;
  tstp 1 2010/05/03 16:36:11;
  cltt 1 2010/05/03 16:35:53;
  binding state abandoned;
  next binding state free;
}
server-duid "\000\001\000\001\023n)\302\000PV\257lA";
'''

from pyparsing import *

HASH      = Literal('#').suppress()
SEMICOLON = Literal(';').suppress()
LBRACE    = Literal('{').suppress()
RBRACE    = Literal('}').suppress()
EQUALS    = Literal('=').suppress()

SET    = Keyword('set').suppress()
OPTION = Keyword('option').suppress()

hexbyte      = Word(nums + 'AaBbCcDdEeFf', max = 2)
integer      = Word(nums)
varname      = Word(alphas, alphanums + '.' + '-')
quotedstring = QuotedString('"', escChar = '\\')
hwtype       = Word(alphas, alphanums)

comment    = LineStart() + HASH + SkipTo('\n', include = True)
bytestring = Combine(hexbyte + ZeroOrMore(':' + hexbyte))
datespec   = integer + Combine(integer + '/' + integer + '/' + integer)
timespec   = Combine(integer + ':' + integer + ':' + integer)
ip_addr    = Combine(integer + ('.' + integer) * 3)
mac_addr   = Combine(hexbyte + (':' + hexbyte) * 5)

starts          = 'starts' + datespec + timespec + SEMICOLON
ends            = 'ends' + ((datespec + timespec) | 'never') + SEMICOLON
tstp            = 'tstp' + datespec + timespec + SEMICOLON
tsfp            = 'tsfp' + datespec + timespec + SEMICOLON
cltt            = 'cltt' + datespec + timespec + SEMICOLON
bindstate       = Optional('next') + 'binding' + 'state' + oneOf('free active abandoned')('state') + SEMICOLON
hw_addr         = 'hardware' + hwtype + mac_addr('mac_addr') + SEMICOLON
uid             = 'uid' + quotedstring('uid') + SEMICOLON
set             = SET + varname + EQUALS + quotedstring + SEMICOLON
option          = OPTION + varname + bytestring + SEMICOLON
client_hostname = 'client-hostname' + quotedstring + SEMICOLON

server_duid = Group('server-duid' + quotedstring + SEMICOLON)

decl = starts | ends | tstp | tsfp | cltt | bindstate | hw_addr | uid | set | option | client_hostname
decl_list = Dict(ZeroOrMore(Group(decl)))

lease = Group('lease' + ip_addr('ip_addr') + LBRACE + decl_list + RBRACE)

leaseFile = StringStart() + ZeroOrMore(lease | server_duid | comment) + StringEnd()

if __name__ == '__main__':
	tok = leaseFile.parseString(example, parseAll=True)
	for t in tok:
		print ''
		print t.dump()
