continue the overengineering effort, we are now able to generate dhcpd.conf files
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore Thu Apr 02 20:19:18 2009 +0300
@@ -0,0 +1,4 @@
+syntax: regexp
+
+\.pyc$
+\.[^/]+\.sw[op]$
--- a/addr.py Thu Apr 02 17:47:43 2009 +0300
+++ b/addr.py Thu Apr 02 20:19:18 2009 +0300
@@ -28,3 +28,19 @@
super(Network, self).__init__(prefix)
+class MAC (object) :
+ """
+ A mac address
+ """
+
+ def __init__ (self, mac) :
+ """
+ Parse the given MAC address in "aa:bb:cc:dd:ee:ff" form
+ """
+
+ # XXX: validate
+ self.mac = mac
+
+ def __str__ (self) :
+ return self.mac
+
--- a/conf.py Thu Apr 02 17:47:43 2009 +0300
+++ b/conf.py Thu Apr 02 20:19:18 2009 +0300
@@ -2,9 +2,9 @@
Generic configuration file output
"""
-import os, tempfile, shutil
+import os, os.path, tempfile, shutil
-class Object (object) :
+class ConfObject (object) :
"""
An object that can be written to a ConfFile, as multiple lines of text.
"""
@@ -16,11 +16,11 @@
abstract
-class File (object) :
+class File (ConfObject) :
"""
A single configuration file on the filesystem.
- Configuration files are
+ Configuration files are themselves ConfObject's, although this must be implemented in the inheriting class.
"""
def __init__ (self, name, path, backup_suffix='.bak', mode=0644) :
@@ -38,17 +38,17 @@
self.backup_suffix = backup_suffix
self.mode = mode
- def write_file (self, file, objects) :
+ def write_file (self, file) :
"""
- Write out the given config objects into the given file
+ Write out this config's stuff into the given file
"""
writer = Writer(file)
- writer.write_objs(objects)
+ writer.write_obj(self)
- def write (self, objects) :
+ def write (self) :
"""
- Write out a new config file with the given series of objects using the following procedure:
+ Write out a new config file with this config's stuff using the following procedure:
* lock the real file
* open a new temporary file, and write out the objects into that, closing it
* move the real file out of the way
@@ -70,15 +70,14 @@
os.chmod(tmp_path, self.mode)
# write it
- self.write_file(tmp_file, objects)
+ self.write_file(tmp_file)
# close it
tmp_file.close()
- del writer
- del tmp_file
# move the old file out of the way
- os.rename(self.path, self.path + self.backup_suffix)
+ if os.path.exists(self.path) :
+ os.rename(self.path, self.path + self.backup_suffix)
# move the new file in
shutil.move(tmp_path, self.path)
@@ -95,20 +94,26 @@
self.file = file
+ def write_line (self, line) :
+ """
+ Write a single line to the file
+ """
+
+ self.file.write("%s\n" % (line, ))
+
+ def write_lines (self, lines) :
+ """
+ Write a series of lines into the file
+ """
+
+ for line in lines :
+ self.write_line(line)
+
def write_obj (self, obj) :
"""
Write a single object to the file
"""
# just write out all the lines
- self.file.writelines(obj.fmt_lines())
+ self.write_lines(obj.fmt_lines())
- def write_objs (self, objs) :
- """
- Write a series of objects to the file
- """
-
- # just write each in turn
- for obj in objs :
- self.write_obj(obj)
-
--- a/conf_dhcp.py Thu Apr 02 17:47:43 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,227 +0,0 @@
-"""
- Configuration file output for the ISC DHCP server
-"""
-
-import conf
-
-import itertools
-
-class ConfDHCP (conf.File) :
- def __init__ (self, name="dhcpd.conf", path="/etc/dhcp3/dhcpd.conf") :
- """
- Initialize the dhcpd config file, but don't open it yet
-
- @see conf.ConfFile.__init__
- """
-
- super(ConfDHCP, self).__init__(name, path)
-
-class Statement (conf.Object) :
- """
- A statement is a single line in the config file
- """
-
- def __init__ (self, name, *args) :
- """
- The statement will be formatted like this:
- <name> [ <arg> [ ... ] ] ";"
- """
-
- self.name = name
- self.args = args
-
- def _fmt_arg (self, arg) :
- """
- Formats a arg for use in output, the following types are supported:
-
- list/tuple/iter: results in a comma-and-space separated list of formatted values
- unicode: results in an encoded str
- str: results in the string itself, quoted if needed
- other: attempt to convert to a str, and then format that
- """
-
- # format lists specially
- # XXX: iterators?
- if isinstance(arg, (list, tuple)) :
- # recurse as a comma-and-space separated list
- return ', '.join(self._fmt_arg(a) for a in arg)
-
- elif isinstance(arg, Literal) :
- # use what it specifies
- return arg.fmt_arg()
-
- elif isinstance(arg, unicode) :
- # recurse with the str version
- # XXX: what encoding to use?
- return self._fmt_arg(arg.encode('utf8'))
-
- elif isinstance(arg, str) :
- # XXX: quoting
- return arg
-
- else :
- # try and use it as a string
- return self._fmt_arg(str(arg))
-
- def _fmt_data (self) :
- """
- Formats the statement name/params as a single line
- """
-
- return "%s%s" % (self.name, (' ' + ' '.join(self._fmt_arg(a) for a in self.args)) if self.args else '')
-
-class Literal (Statement) :
- """
- A literal is something that goes into the config file as-is, with no formatting or escaping applied.
- """
-
- def __init__ (self, literal) :
- self.literal = literal
-
- def fmt_arg (self) :
- return self.literal
-
- def fmt_lines (self) :
- yield self.literal
-
-class Parameter (Statement) :
- """
- A parameter is a single statement that configures the behaviour of something.
-
- Parameters have a name, and optionally, a number of arguments, and are formatted as statements terminated with
- a semicolon.
- """
-
- def fmt_lines (self) :
- """
- Yields a single ;-terminated line
- """
-
- yield "%s;" % self._fmt_data()
-
-class Declaration (Statement) :
- """
- A declaration begins like a statement (with name and args), but then contains a block of any number of
- parameters followed by any number of nested declarations.
-
- <name> [ <args> [ ... ] ] {
- [ <parameters> ]
- [ <declarations> ]
- }
-
- """
-
- def __init__ (self, name, args=[], params=[], decls=[]) :
- """
- The name/args will be formatted as in Statement, but params should be an iterable of Parameters, and decls
- an iterable of Declarations.
- """
-
- # init the statement bit
- Statement.__init__(self, name, *args)
-
- # store the iterables
- self.params = params
- self.decls = decls
-
- def fmt_lines (self) :
- """
- Yields a header line, a series of indented body lines, and the footer line
- """
-
- # the header to open the block
- yield "%s {" % self._fmt_data()
-
- # then output each content line
- for stmt in itertools.chain(self.params, self.decls) :
- # ..indented
- for line in stmt.fmt_lines() :
- yield "\t%s" % line
-
- # and then close the block
- yield "}"
-
-class SharedNetwork (Declaration) :
- """
- A shared-network declaration is used to define a set of subnets that share the same physical network,
- optionally with some shared params.
-
- shared-network <name> {
- [ parameters ]
- [ declarations ]
- }
- """
-
- def __init__ (self, name, params=[], decls=[]) :
- """
- @param name the name of the shared-subnet
- @param params optional parameters
- @param decls the iterable of subnets or other declarations in the shared network
- """
-
- super(SharedNetwork, self).__init__("shared-network", [name], params, decls)
-
-class Subnet (Declaration) :
- """
- A subnet is used to provide the information about a subnet required to identify whether or not an IP address is
- on that subnet, and may also be used to specify parameters/declarations for that subnet.
-
- subnet <subnet-number> netmask <netmask> {
- [ parameters ]
- [ declarations ]
- }
- """
-
- def __init__ (self, network, params=[], decls=[]) :
- """
- @param network the addr.Network for the subnet
- @param params optional parameters
- @param decls optional decls, e.g. subnets
- """
-
- super(Subnet, self).__init__("subnet", [network.net(), "netmask", network.netmask()], params, decls)
-
-class Group (Declaration) :
- """
- A group is simply used to apply a set of parameters to a set of declarations.
-
- group {
- [ parameters ]
- [ declarations ]
- }
- """
-
- def __init__ (self, params=[], decls=[]) :
- super(Group, self).__init__("group", [], params, decls)
-
-
-class Host (Declaration) :
- """
- A host is used to match a request against specific host, and then apply settings for that host.
-
- The "hostname" is the DHCP name to identify the host.
-
- If no dhcp-client-identifier option is specified in the parameters, then the host is matched using the
- "hardware" parameter.
-
- host <hostname> {
- [ parameters ]
- [ declarations ]
- }
- """
-
- def __init__ (self, hostname, params=[], decls=[]) :
- super(Host, self).__init__("host", [hostname], params, decls)
-
-class Option (Parameter) :
- """
- A generic 'option' parameter for a dhcpd.conf file
- """
-
- def __init__ (self, name, *args) :
- """
- Formatted as a Satement with a name of "option <name>".
- """
-
- super(Option, self).__init__("option %s" % name, *args)
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/data.py Thu Apr 02 20:19:18 2009 +0300
@@ -0,0 +1,23 @@
+"""
+ Functions to load data from various sources
+"""
+
+import imp
+
+def load_py (path) :
+ """
+ Load a python file from the given filesystem path, returning the module itself
+ """
+
+ # XXX: what name to use?
+ name = "hosts"
+
+ # find the module
+ file, pathname, info = imp.find_module(name, [path])
+
+ # load it
+ module = imp.load_module(name, file, pathname, info)
+
+ # ok
+ return module
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dhcp.py Thu Apr 02 20:19:18 2009 +0300
@@ -0,0 +1,81 @@
+"""
+ Higher-level DHCP config structure model
+"""
+
+import dhcp_conf as dhcpc
+
+class Config (dhcpc.ConfFile) :
+ """
+ A full configuration file
+ """
+
+ def __init__ (self, name=dhcpc.ConfFile.DEFAULT_NAME, path=dhcpc.ConfFile.DEFAULT_PATH,
+ settings=None, options=None, shared_network=False, subnets=None, hosts=None
+ ) :
+ """
+ Create a full configuration file for the given settings:
+
+ settings: a { name: value } mappping of general settings to set
+ options: a { opt_name: opt_value } mapping of options to set
+ shared_network: define the subnets as a shared network of the given name
+ subnets: an iterable of Subnet's to define
+ hosts: an iterable of Host's to define
+
+ """
+
+ dhcpc.ConfFile.__init__(self, name, path)
+
+ # define global settings
+ if settings :
+ self.add_params(dhcpc.Parameter(setting, value) for setting, value in settings.iteritems())
+
+ # define global options
+ if options :
+ self.add_params(dhcpc.Option(option, value) for option, value in options.iteritems())
+
+ # the shared-network section, or a series of subnets
+ if shared_network :
+ self.add_decl(dhcpc.SharedNetwork(shared_network, decls=subnets))
+
+ elif subnets :
+ self.add_decls(subnets)
+
+ # hosts section
+ if hosts :
+ self.add_decls(hosts)
+
+class Subnet (dhcpc.Subnet) :
+ """
+ A subnet declaration with a router, and optionally a dynamic address pool, and allow/deny unknown clients
+ """
+
+ def __init__ (self, subnet, router_idx=1, range=None, unknown_clients=None) :
+ """
+ @param subnet the addr.IP representing the subnet
+ @param router_idx the subnet[index] of the default gateway
+ @param range optional (from_idx, to_idx) to define a dhcp pool
+ @param unknown_clients optional 'allow'/'deny' to set a policy for unknown clients
+ """
+
+ # validate
+ if unknown_clients :
+ assert unknown_clients in ('allow', 'deny')
+
+ super(Subnet, self).__init__(subnet, params=[
+ dhcpc.Option("routers", subnet[router_idx]),
+ dhcpc.Parameter("range", subnet[range[0]], subnet[range[1]]) if range else None,
+ dhcpc.Parameter(unknown_clients, "unknown-clients") if unknown_clients else None,
+ ])
+
+
+class Host (dhcpc.Host) :
+ """
+ A host declaration with a hardware address and a IP address
+ """
+
+ def __init__ (self, hostname, mac_addr, ip_addr) :
+ super(Host, self).__init__(hostname, params=[
+ dhcpc.Parameter("hardware ethernet", mac_addr),
+ dhcpc.Parameter("fixed-address", ip_addr)
+ ])
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dhcp_conf.py Thu Apr 02 20:19:18 2009 +0300
@@ -0,0 +1,274 @@
+"""
+ Configuration file output for the ISC DHCP server
+"""
+
+import conf
+
+import itertools
+
+class Section (conf.ConfObject) :
+ """
+ A section holds a list of params and a list of decls
+ """
+
+ def __init__ (self, params=None, decls=None) :
+ """
+ If params/decls are given, those are the used as the initial contents of this section
+ """
+
+ self.params = params or []
+ self.decls = decls or []
+
+ def add_param (self, param) :
+ """
+ Add the given Parameter to the end of this section's params
+ """
+
+ self.params.append(param)
+
+ def add_params (self, params) :
+ for param in params :
+ self.add_param(param)
+
+ def add_decl (self, decl) :
+ """
+ Add the given Declaration to the end of this section's decls
+ """
+
+ self.decls.append(decl)
+
+ def add_decls (self, decls) :
+ for decl in decls :
+ self.add_decl(decl)
+
+ def fmt_lines (self) :
+ """
+ Format all of our params and decls, in that order
+ """
+
+ # then output each content line
+ for stmt in itertools.chain(self.params, self.decls) :
+ # skip Nones
+ if stmt is None :
+ continue
+
+ for line in stmt.fmt_lines() :
+ yield line
+
+class ConfFile (Section, conf.File) :
+ DEFAULT_NAME = "dhcpd.conf"
+ DEFAULT_PATH = "/etc/dhcp3/dhcpd.conf"
+
+ def __init__ (self, name=DEFAULT_NAME, path=DEFAULT_PATH, params=None, decls=None) :
+ """
+ Initialize the dhcpd config file, but don't open it yet.
+ """
+
+ conf.File.__init__(self, name, path)
+ Section.__init__(self, params, decls)
+
+class Statement (conf.ConfObject) :
+ """
+ A statement is a single line in the config file
+ """
+
+ def __init__ (self, name, *args) :
+ """
+ The statement will be formatted like this:
+ <name> [ <arg> [ ... ] ] ";"
+
+ Arguments given as None will be ignored.
+ """
+
+ self.name = name
+ self.args = args
+
+ def _fmt_arg (self, arg) :
+ """
+ Formats a arg for use in output, the following types are supported:
+
+ list/tuple/iter: results in a comma-and-space separated list of formatted values
+ unicode: results in an encoded str
+ str: results in the string itself, quoted if needed
+ other: attempt to convert to a str, and then format that
+ """
+
+ # format lists specially
+ # XXX: iterators?
+ if isinstance(arg, (list, tuple)) :
+ # recurse as a comma-and-space separated list
+ return ', '.join(self._fmt_arg(a) for a in arg)
+
+ elif isinstance(arg, Literal) :
+ # use what it specifies
+ return arg.fmt_arg()
+
+ elif isinstance(arg, unicode) :
+ # recurse with the str version
+ # XXX: what encoding to use?
+ return self._fmt_arg(arg.encode('utf8'))
+
+ elif isinstance(arg, str) :
+ # XXX: quoting
+ return arg
+
+ else :
+ # try and use it as a string
+ return self._fmt_arg(str(arg))
+
+ def _fmt_data (self) :
+ """
+ Formats the statement name/params as a single line, ignoring None
+ """
+
+ return "%s%s" % (self.name, (' ' + ' '.join(self._fmt_arg(a) for a in self.args if a is not None)) if self.args else '')
+
+class Literal (Statement) :
+ """
+ A literal is something that goes into the config file as-is, with no formatting or escaping applied.
+ """
+
+ def __init__ (self, literal) :
+ self.literal = literal
+
+ def fmt_arg (self) :
+ return self.literal
+
+ def fmt_lines (self) :
+ yield self.literal
+
+class Parameter (Statement) :
+ """
+ A parameter is a single statement that configures the behaviour of something.
+
+ Parameters have a name, and optionally, a number of arguments, and are formatted as statements terminated with
+ a semicolon. For convenience, params/decls that are None are ignored.
+ """
+
+ def fmt_lines (self) :
+ """
+ Yields a single ;-terminated line
+ """
+
+ yield "%s;" % self._fmt_data()
+
+class Declaration (Section, Statement) :
+ """
+ A declaration begins like a statement (with name and args), but then contains a curly-braces-delimited block
+ that acts like a Section.
+
+ <name> [ <args> [ ... ] ] {
+ [ <Section> ]
+ }
+
+ """
+
+ def __init__ (self, name, args=[], params=None, decls=None) :
+ """
+ The name/args will be formatted as in Statement, but params should be an iterable of Parameters, and decls
+ an iterable of Declarations.
+ """
+
+ # init the statement bit
+ Section.__init__(self, params, decls)
+ Statement.__init__(self, name, *args)
+
+ def fmt_lines (self) :
+ """
+ Yields a header line, a series of indented body lines, and the footer line
+ """
+
+ # the header to open the block
+ yield "%s {" % self._fmt_data()
+
+ # then output the section stuff, indented
+ for line in Section.fmt_lines(self) :
+ yield "\t%s" % line
+
+ # and then close the block
+ yield "}"
+
+class SharedNetwork (Declaration) :
+ """
+ A shared-network declaration is used to define a set of subnets that share the same physical network,
+ optionally with some shared params.
+
+ shared-network <name> {
+ [ parameters ]
+ [ declarations ]
+ }
+ """
+
+ def __init__ (self, name, params=[], decls=[]) :
+ """
+ @param name the name of the shared-subnet
+ @param params optional parameters
+ @param decls the iterable of subnets or other declarations in the shared network
+ """
+
+ super(SharedNetwork, self).__init__("shared-network", [name], params, decls)
+
+class Subnet (Declaration) :
+ """
+ A subnet is used to provide the information about a subnet required to identify whether or not an IP address is
+ on that subnet, and may also be used to specify parameters/declarations for that subnet.
+
+ subnet <subnet-number> netmask <netmask> {
+ [ parameters ]
+ [ declarations ]
+ }
+ """
+
+ def __init__ (self, network, params=None, decls=None) :
+ """
+ @param network the addr.Network for the subnet
+ @param params optional parameters
+ @param decls optional decls, e.g. subnets
+ """
+
+ super(Subnet, self).__init__("subnet", [network.net(), "netmask", network.netmask()], params, decls)
+
+class Group (Declaration) :
+ """
+ A group is simply used to apply a set of parameters to a set of declarations.
+
+ group {
+ [ parameters ]
+ [ declarations ]
+ }
+ """
+
+ def __init__ (self, params=None, decls=None) :
+ super(Group, self).__init__("group", [], params, decls)
+
+
+class Host (Declaration) :
+ """
+ A host is used to match a request against specific host, and then apply settings for that host.
+
+ The "hostname" is the DHCP name to identify the host.
+
+ If no dhcp-client-identifier option is specified in the parameters, then the host is matched using the
+ "hardware" parameter.
+
+ host <hostname> {
+ [ parameters ]
+ [ declarations ]
+ }
+ """
+
+ def __init__ (self, hostname, params=None, decls=None) :
+ super(Host, self).__init__("host", [hostname], params, decls)
+
+class Option (Parameter) :
+ """
+ A generic 'option' parameter for a dhcpd.conf file
+ """
+
+ def __init__ (self, name, *args) :
+ """
+ Formatted as a Satement with a name of "option <name>".
+ """
+
+ super(Option, self).__init__("option %s" % name, *args)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/host.py Thu Apr 02 20:19:18 2009 +0300
@@ -0,0 +1,52 @@
+"""
+ Information about one physica host
+"""
+
+import dhcp
+
+class Interface (object) :
+ """
+ A physical interface for a host
+ """
+
+ def __init__ (self, mac_addr, name=None) :
+ """
+ @param name the short name of the interface (e.g. 'lan' or 'wlan'), or None for no suffix
+ @param mac the physical-layer addr.MAC address
+ """
+
+ self.addr = mac_addr
+ self.name = name
+
+class Host (object) :
+ """
+ A host has a single address/name, an owner, and multiple interfaces
+ """
+
+ def __init__ (self, hostname, address, interfaces) :
+ """
+ @param hostname the short hostname, without the domain name component
+ @param address the addr.IP address
+ @param interfaces a list of zero or more Interface objects
+ """
+
+ self.hostname = hostname
+ self.address = address
+ self.interfaces = interfaces
+
+ def build_dhcp_hosts (self) :
+ """
+ Build and yield a series of dhcp_conf.Host objects for this host.
+
+ If the host does not have any interfaces defined, this doesn't yield anything
+ """
+
+ # XXX: do we want to ensure that the host names are unique?
+
+ for iface in self.interfaces :
+ # the DHCP hostname
+ name = "%s%s" % (self.hostname, ('-%s' % (iface.name)) if iface.name else '')
+
+ # build it
+ yield dhcp.Host(name, iface.addr, self.address)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/main.py Thu Apr 02 20:19:18 2009 +0300
@@ -0,0 +1,63 @@
+#!/usr/bin/env python2.5
+
+import data, dhcp_conf, dhcp
+
+import optparse, itertools
+
+def parse_args (argv) :
+ """
+ Parse the command-line arguments from the given argv list, returning a (options_struct, args_list) tuple,
+ as per optparse.
+ """
+
+ usage = "Usage: %prog [options] data-file"
+
+ # define our options
+ parser = optparse.OptionParser(usage=usage)
+ parser.add_option('--dhcpd-conf', dest='dhcpd_conf', metavar='PATH', help="path to dhcpd.conf", default='/etc/dhcp3/dhcpd.conf')
+
+ # parse them
+ options, args = parser.parse_args(args=argv[1:])
+
+ # parse the positional arguments
+ data_file, = args
+
+ # ok
+ return options, (data_file, )
+
+def write_dhcp (options, settings) :
+ """
+ Write the DHCP config module using the data loaded from the given module
+ """
+
+ # build the config f file
+ config = dhcp.Config(path=options.dhcpd_conf,
+ settings = settings.dhcp_settings,
+ options = settings.dhcp_options,
+ shared_network = settings.shared_network,
+ subnets = settings.subnets,
+ hosts = itertools.chain(*(host.build_dhcp_hosts() for host in settings.hosts)),
+ )
+
+ # write it out
+ config.write()
+
+def main (argv) :
+ """
+ Our app entry point, parse args, load data, write out the config files
+ """
+
+ # parse args
+ options, (data_file, ) = parse_args(argv)
+
+ # load the data
+ data_module = data.load_py(data_file)
+
+ # write out the config files
+ write_dhcp(options, data_module)
+
+if __name__ == '__main__' :
+ from sys import argv
+
+ main(argv)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/settings/hosts.py Thu Apr 02 20:19:18 2009 +0300
@@ -0,0 +1,28 @@
+from addr import IP, Network
+from host import Interface, Host
+from dhcp import Subnet
+
+dhcp_settings = {
+ 'default-lease-time': 43200,
+ 'max-lease-time': 86400,
+ 'authorative': None,
+}
+
+dhcp_options = {
+ 'domain-name-servers': IP('194.197.235.145'),
+}
+
+shared_network = 'PVL'
+subnets = [
+ Subnet(Network('194.197.235.0/24'), router_idx=1, range=(26, 70), unknown_clients='allow'),
+ Subnet(Network('192.168.0.0/23'), router_idx=1, unknown_clients='deny'),
+]
+
+hosts = [
+ Host('jumpgate', IP('194.197.235.1'), [ ]),
+ Host('mikk4', IP('194.197.235.72'), [
+ Interface('00:16:01:37:D1:D2'),
+ Interface('00:0F:B0:0A:EF:58'),
+ ]),
+]
+
--- a/test_dhcp.py Thu Apr 02 17:47:43 2009 +0300
+++ b/test_dhcp.py Thu Apr 02 20:19:18 2009 +0300
@@ -1,8 +1,9 @@
+#!/usr/bin/env python2.5
"""
Test conf_dhcp
"""
-import conf_dhcp as dhcpc, conf, addr
+import dhcp_conf as dhcpc, conf, addr
import unittest
from cStringIO import StringIO
@@ -17,11 +18,12 @@
def test_statement (self) :
self.assert_stmt(dhcpc.Statement("stmt0"), "stmt0")
- self.assert_stmt(dhcpc.Statement("stmt3", [ "this", "that" ]), "stmt3 this, that")
- self.assert_stmt(dhcpc.Statement("stmt4", dhcpc.Literal("...")), "stmt4 ...")
- self.assert_stmt(dhcpc.Statement("stmt1", u"quux"), "stmt1 quux")
- self.assert_stmt(dhcpc.Statement("stmt1", "bar"), "stmt1 bar")
- self.assert_stmt(dhcpc.Statement("stmt2", 1), "stmt2 1")
+ self.assert_stmt(dhcpc.Statement("stmt1", [ "this", "that" ]), "stmt1 this, that")
+ self.assert_stmt(dhcpc.Statement("stmt2", dhcpc.Literal("...")), "stmt2 ...")
+ self.assert_stmt(dhcpc.Statement("stmt3", u"quux"), "stmt3 quux")
+ self.assert_stmt(dhcpc.Statement("stmt4", "bar"), "stmt4 bar")
+ self.assert_stmt(dhcpc.Statement("stmt5", 1), "stmt5 1")
+ self.assert_stmt(dhcpc.Statement("stmt6", 1, None, 2), "stmt6 1 2")
def assert_obj (self, obj, lines) :
"""
@@ -38,7 +40,8 @@
def test_declaration (self) :
self.assert_obj(dhcpc.Declaration("decl0", ["arg0", "arg1"], [
- dhcpc.Parameter("param0")
+ dhcpc.Parameter("param0"),
+ None
], [
dhcpc.Declaration("decl0.0", params=[
dhcpc.Parameter("param0.0.1", "value")