# config.py --- Python interface to libbeep --- configuration management. # Copyright (c) 2005 Scott Grayban # # This file is part of PyBMP. # # PyBMP is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 dated June, 1991. # # PyBMP is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Boston, MA 02110-1301 USA. # # $Id: config.py 3 2006-03-28 11:52:53Z sgrayban $ # """Python interface to BMP --- configuration management module. This module provides a Python interface to manage the main configuration file for BMP (the X MultiMedia System), an audio and video player for Unix-like platforms. This module is a wrapper around the functions for configuration file management in libbeep. It allows reading and writing BMP configuration files through structured Python objects. Overview -------- An BMP configuration file is composed of zero ore more sections, each of which contains zero or more configuration lines. A configuration line is of the form "key=value". Here is an example of a configuration file containing two sections: [CDDA] device=/dev/cdrom directory=/cdrom/ [ESD] use_remote=FALSE remote_host=localhost remote_port=16001 Notes: 1. Spaces were added on the left to make the example stand out. 2. This example is not suitable as the main configuration file for BMP because it is very incomplete. It is only a small excerpt of a complete one. The two sections are named "CDDA" and "ESD". There are two configuration lines in the "CDDA" section and three in the "ESD" section. This module provides three classes (ConfigLine, ConfigSection and Config) to manage the three levels used to store the data in a configuration file. Of course, after a simple "import bmp.config", the classes are accessible as bmp.config.ConfigLine, bmp.config.ConfigSection and bmp.config.Config but I will just write ConfigLine, ConfigSection and Config in the rest of this documentation to preserve my (and your) sanity. Since a configuration can be seen as a list of configuration sections and a configuration section as a list of configuration lines, Config and ConfigSection instances behave as lists. You can append, insert, remove, etc. elements to/into/from them just as you would do with lists. In fact, they have an attribute that holds the underlying list but I encourage you not to access it unless really necessary. Since Config and ConfigSection support all standard list operations, you will probably never need it. Config and ConfigSection also behave a bit like dictionaries in that they allow you to retrieve the "CDDA" section from a Config instance 'c' with 'c["CDDA"]' and the value associated with the "remote_host" key from a section 's' with 's["remote_host"]. In the end, these behaviours of Config and ConfigSection allow for a nice syntax for simple operations: dir = c["CDDA"]["directory"] c["ESD"]["remote_port"] = "16002" (or bmp.config.int_to_str(16002)) As I wanted to be *really* nice with you, I also allowed simple syntax where a ConfigLine, ConfigSection or Config instance is required. For instance, when creating a ConfigSection instance, you are supposed to provide a sequence of ConfigLine instances to specify the contents of the section, as in: s = bmp.config.ConfigSection( name="CDDA", lines=(bmp.config.ConfigLine(key="device", value="/dev/cdrom"), bmp.config.ConfigLine(key="directory", value="/cdrom/"))) but the following shorter version is allowed: s = bmp.config.ConfigSection( name="CDDA", lines=(("device", "/dev/cdrom"), ("directory", "/cdrom/"))) So, here, a ConfigLine instance was built automatically from the object '("device", "/dev/cdrom")' and another one from '("directory", "/cdrom/")'. I will say in the rest of this documentation that each of these objects is *coercible* to a ConfigLine instance. As advertised, coercion is also possible with ConfigSection and Config instances. The details telling exactly which objects are coercible to ConfigLine, ConfigSection and Config instances are explained in the documentations for ConfigLine.AsInstance, ConfigSection.AsInstance and Config.AsInstance (which are class methods). The important question is when you should use these facilities. Personally, I prefer clear and strict rules, so I would encourage you not to use them too much. Although they appear to make things more readable sometimes, they can bite you if you forget a parenthesis or bracket for instance: bmp.config.Config([["CDDA", ("device", "/dev/cdrom"), ("directory", "/cdrom/")]]) This assignment looks harmless and won't raise any exception but will probably not create the object you had in mind, which was presumably: bmp.config.Config([["CDDA", [("device", "/dev/cdrom"), ("directory", "/cdrom/")]]]) Of course, the configuration lines must be given as a sequence (here, a list)! With the strict syntax, using bmp.config.ConfigLine and bmp.config.ConfigSection wherever you want to create a configuration line or a configuration section, you cannot make this error. So, both syntax styles have their pros and cons. I'll let you choose. OK. I hear you crying: "But I want a strict _and_ readable syntax!..." ;-) Here is a hint, then: there is a nice feature in Python called List Comprehension. Look: s = bmp.config.ConfigSection( name="foo section", lines=[bmp.config.ConfigLine(key=k, value=v) for (k,v) in (("foo", "foovalue"), ("bar", "barvalue"), ("baz", "bazvalue"))]) This is readable, uses no magic and follows the strict syntax. About Typing ------------ The data described in a configuration file is all read as strings in the first place (not very surprising for a file). To avoid black magic that does not always work as expected and to keep things simple, all keys and values are handled as just these strings in every Config, ConfigSection and ConfigLine method (however, note that the underlying libxmms function xmms_cfg_write_string strips leading and trailing whitespace from every key or value you write to a configuration file). So, all values are strings, but these strings, as used in the main configuration file for BMP, can be classified into three categories. Indeed, libxmms provides specific functions to read a value string (the value in a configuration line) as: - the string itself - an integer - a floating point number - a boolean value. To allow you to convert easily between these "BMP types" for value strings and the obvious corresponding Python types, six functions are provided: - str_to_int, str_to_float, str_to_bool - int_to_str, float_to_str, bool_to_str. These functions follow the same rules as BMP when converting between strings and other types except in some cases where BMP is waaaaay too lax. For instance, libxmms will accept the value "1234tyd" as an integer (1234). bmp.config.str_to_int will not. You'd better fix your program if it spits out such integers! These were the basic principles you have to keep in mind to make good use of this module. I will now give you a list of the objects it exports and let you find the remaining details in their own documentation, which is embedded in their docstrings. Functions exported by this module --------------------------------- str_to_int str_to_bool str_to_float int_to_str bool_to_str float_to_str Classes exported by this module ------------------------------- ConfigLine ConfigSection Config Exceptions specific to this module ---------------------------------- UsageError WriteToFileError Both exceptions are subclasses of xmms.error. """ import sys # Python 2.2 minimum is required because of the use of the "new-style # classes". if sys.hexversion < 0x02020000: raise ImportError("module requires a Python version greater than or " "equal to 2.2, but this version of Python is :\n\n %s" % sys.version) import re import common, _bmpconfig # Python < 2.3 compatibility if sys.hexversion < 0x02030000: # The assignments would work with Python >= 2.3 but then, pydoc # shows them in the DATA section of the module... True = 0 == 0 False = 0 == 1 class UsageError(common.error): """Exception raised when the API is not used as documented.""" ExceptionShortDescription = "Usage error" class WriteToFileError(common.error): """Exception raised when writing a Config contents to a file fails.""" ExceptionShortDescription = "Configuration file write error" _int_rec = re.compile(r"^[ \t]*(?P\d+)[ \t]*$") _bool_rec = re.compile(r"^(?PTRUE|FALSE)$", re.IGNORECASE) def str_to_int(s): """Convert a string to an integer. s -- a string representing an integer in an BMP configuration file """ if not isinstance(s, str): raise TypeError("%s is not a string" % repr(s)) mo = _int_rec.match(s) if mo is None: raise ValueError("%s cannot be converted to an integer" % repr(s)) return int(mo.group("int")) def str_to_bool(s): """Convert a string to a boolean. s -- a string representing a boolean in an BMP configuration file """ if not isinstance(s, str): raise TypeError("%s is not a string" % repr(s)) mo = _bool_rec.match(s) if mo is None: raise ValueError("%s cannot be converted to a boolean" % repr(s)) bool_string = mo.group("bool") if bool_string.upper() == "TRUE": return True else: return False def str_to_float(s): """Convert a string to a floating point number. s -- a string representing a floating point number in an BMP configuration file """ if not isinstance(s, str): raise TypeError("%s is not a string" % repr(s)) return float(s) def int_to_str(i): """Convert an integer to a string. i -- an integer Return a string suitable for use as the "value" string of a parameter holding an integer in an BMP configuration file. """ if not isinstance(i, int): raise TypeError("%s is not an integer" % repr(i)) # BMP 1.2.8 uses g_strdup_printf("%d", value) in xmms_cfg_write_int return "%d" % i def bool_to_str(b): """Convert a boolean to a string. b -- an object that can be evaluated in boolean expressions Return a string suitable for use as the "value" string of a parameter holding a boolean in an BMP configuration file. The results depends on whether 'b' is considered True or False in a boolean context. """ if b: return "TRUE" else: return "FALSE" def float_to_str(f): """Convert a floating point number to a string. f -- a float Return a string suitable for use as the "value" string of a parameter holding a floating point number in an BMP configuration file. """ # BMP 1.2.8 uses g_strdup_printf("%g", value) in xmms_cfg_write_float # and xmms_cfg_write_double if not isinstance(f, float): raise TypeError("%s is not a float" % repr(f)) return "%g" % f class ConfigLine(object): """Class for configuration line objects. This class implements objects that represent configuration lines in the main configuration file for BMP. Each instance of this class represents a line of the form "key=value", with the key and value stored in attributes with the same names (namely, "key" and "value"). Public Methods -------------- The ConfigLine class has the following methods: __init__ __repr__ __str__ __lt__ __le__ __eq__ __ne__ __gt__ __ge__ __nonzero__ AsInstance Public Instance Variables (attributes) -------------------------------------- The ConfigLine class has the following attributes: key -- the "key" component of the configuration line value -- the "value" component of the configuration line Example to illustrate what "key" and "value" represent: In a configuration line such as "device=/dev/cdrom", "device" is said to be the key and "/dev/cdrom" the value. Both key and value are strings. No magic is attempted to convert strings that look like numbers to Python integers or floats, or whatever other conversion (this avoids the problem that would arise when reading floats from the configuration file and writing the back, where the conversions string -> float -> string could change the string from 3.2 to 3.2000000000001 for instance). """ def __init__(self, key, value): """Constructor for ConfigLine instances. key -- the "key" component of the configuration line value -- the "value" component of the configuration line Example: In a configuration line such as "device=/dev/cdrom", "device" is said to be the key and "/dev/cdrom" the value. """ if not isinstance(key, str): raise TypeError('expected a string as the "key" parameter of ' 'bmp.config.ConfigLine.__init__, but received %s' % repr(key)) if not isinstance(value, str): raise TypeError('expected a string as the "value" parameter of ' 'bmp.config.ConfigLine.__init__, but received %s' % repr(value)) self.key = key self.value = value def __repr__(self): """Return a string accurately representing the object.""" return "bmp.config.ConfigLine(key=%s, value=%s)" % (repr(self.key), repr(self.value)) def __str__(self): """Return a string representing the object.""" # I prefer using repr for the strings to avoid amiguous # situations with strings containing quotes for instance... return "bmp.config.ConfigLine(key=%s, value=%s)" % (repr(self.key), repr(self.value)) def AsInstance(cls, obj): """Return an object as an instance of this method's class. Note: this is class method. obj -- an instance of the class 'cls' or any other object 'o' that can be used to build a 'cls' instance with 'cls(key=o[0], value=o[1])' Return 'obj' if already a 'cls' instance (or an instance of a direct or indirect subclass of 'cls'). Otherwise, create and return a new 'cls' instance built from 'obj[0]' and 'obj[1]' as described above. """ if isinstance(obj, cls): return obj else: try: return cls(key=obj[0], value=obj[1]) except KeyError, exc: raise TypeError("%s cannot be interpreted as a %s object " "because it is not a %s instance and has no " "key (or index) %s" % (repr(obj), cls.__name__, cls.__name__, str(exc))) AsInstance = classmethod(AsInstance) def __AsInstanceForComparison(cls, obj, method_name): # cf. comment for the same method for ConfigSection return cls.AsInstance(obj) __AsInstanceForComparison = classmethod(__AsInstanceForComparison) # Could be used to sort lines lexicographically within a section... def __lt__(self, other): """Tell whether 'self' is smaller than another object. The comparison is performed first on the keys of the objects to compare, then on their values. TypeError is raised if 'other' cannot be interpreted as a ConfigLine instance. """ o = self.__AsInstanceForComparison(other, "__lt__") return (self.key < o.key) or (self.key == o.key and self.value < o.value) def __le__(self, other): """Tell whether 'self' is smaller than or equal to another object. The comparison is performed first on the keys of the objects to compare, then on their values. TypeError is raised if 'other' cannot be interpreted as a ConfigLine instance. """ o = self.__AsInstanceForComparison(other, "__le__") return (self.key < o.key) or (self.key == o.key and self.value <= o.value) def __eq__(self, other): """Tell whether 'self' is equal to another object. The comparison is performed first on the keys of the objects to compare, then on their values. TypeError is raised if 'other' cannot be interpreted as a ConfigLine instance. """ o = self.__AsInstanceForComparison(other, "__eq__") return (self.key == o.key) and (self.value == o.value) def __ne__(self, other): """Tell whether 'self' is not equal to another object. The comparison is performed first on the keys of the objects to compare, then on their values. TypeError is raised if 'other' cannot be interpreted as a ConfigLine instance. """ o = self.__AsInstanceForComparison(other, "__ne__") return (self.key != o.key) or (self.value != o.value) def __gt__(self, other): """Tell whether 'self' is greater than another object. The comparison is performed first on the keys of the objects to compare, then on their values. TypeError is raised if 'other' cannot be interpreted as a ConfigLine instance. """ o = self.__AsInstanceForComparison(other, "__gt__") return (self.key > o.key) or (self.key == o.key and self.value > o.value) def __ge__(self, other): """Tell whether 'self' is greater than or equal to another object. The comparison is performed first on the keys of the objects to compare, then on their values. TypeError is raised if 'other' cannot be interpreted as a ConfigLine instance. """ o = self.__AsInstanceForComparison(other, "__ge__") return (self.key > o.key) or (self.key == o.key and self.value >= o.value) def __nonzero__(self): """Tell whether 'self' is considered True in a boolean context. The instance is considered True if its key or its value is considered True (which includes the case where both are considered True). """ return bool(self.key or self.value) class ConfigSection(object): """Class for configuration section objects. This class implements objects that represent configuration sections in the main configuration file for BMP. Each instance of this class represents a configuration section such as: [ESD] use_remote=FALSE remote_host=localhost remote_port=16001 buffer_size=3000 prebuffer=25 Public Methods -------------- The ConfigSection class has the following methods: __init__ __repr__ __str__ __lt__ __le__ __eq__ __ne__ __gt__ __ge__ __nonzero__ __len__ __getitem__ __setitem__ __delitem__ __iter__ __contains__ append extend insert index count remove pop reverse sort AsInstance Public Instance Variables (attributes) -------------------------------------- The ConfigSection class has the following attributes: name -- the name of the section lines -- a list of ConfigLine instances representing the configuration lines in the section In the previous example, the "name" attribute would be "ESD" and the "lines" attribute would be the following list: [bmp.config.ConfigLine(key="use_remote", value="FALSE"), bmp.config.ConfigLine(key="remote_host", value="localhost"), bmp.config.ConfigLine(key="remote_port", value="16001"), bmp.config.ConfigLine(key="buffer_size", value="3000"), bmp.config.ConfigLine(key="prebuffer", value="25")] As you can guess from the method names listed above, ConfigSection instances behave like lists and also support some typical dictionary operations, so in most cases, you should not have to fiddle directly with the "lines" attribute. """ def __init__(self, name, lines=None): """Constructor for ConfigSection instances. name -- the section name lines -- an iterable yielding the lines for the section If lines is None, the section will have no lines. Otherwise, if an object 'obj' obtained while iterating over 'lines' is not a ConfigLine instance, then 'obj[0]' is considered to be the key and 'obj[1]' the associated value ('obj' must support this kind of subscripting if it is not a ConfigLine). Example: bmp.config.ConfigSection("CDDA", (("device", "/dev/cdrom"), ("directory", "/cdrom/"))) would create the same object as: bmp.config.ConfigSection("CDDA", (bmp.config.ConfigLine(key="device", value="/dev/cdrom"), bmp.config.ConfigLine(key="directory", value="/cdrom/"))) """ if not isinstance(name, str): raise TypeError('expected a string as the "name" parameter of ' 'bmp.config.ConfigLine.__init__, but received %s' % repr(name)) self.name = name self.lines = [] if lines is not None: for line in lines: self.lines.append(ConfigLine.AsInstance(line)) def __repr__(self): """Return a string accurately representing the object.""" return "bmp.config.ConfigSection(name=%s, lines=%s)" \ % (repr(self.name), repr(self.lines)) def __str__(self): """Return a string representing the object.""" # I prefer using repr for the string to avoid ambiguous # situations if it contains quotes for instance... return "bmp.config.ConfigSection(name=%s, lines=%s)" \ % (repr(self.name), str(self.lines)) def AsInstance(cls, obj): """Return an object as an instance of this method's class. obj -- an instance of the class 'cls' or any other object 'o' that can be used to build a 'cls' instance with 'cls(name=o[0], lines=o[1])' Return 'obj' if already a 'cls' instance (or an instance of a direct or indirect subclass of 'cls'). Otherwise, create and return a new 'cls' instance built from 'obj[0]' and 'obj[1]' as described above. """ if isinstance(obj, cls): return obj else: try: return cls(name=obj[0], lines=obj[1]) except KeyError, exc: raise TypeError(("%s cannot be interpreted as a %s object " "because it is not a %s instance and has no " "key (or index) %s") % (repr(obj), cls.__name__, cls.__name__, str(exc))) AsInstance = classmethod(AsInstance) def __AsInstanceForComparison(cls, obj, method_name): # The first time I wrote this function, I trapped KeyError # and translated it into a NotImplementedError based on some # vague suggestion in the Python Reference Manual, at the end of # the description for __ge__ in "Special Method Names, Basic # Customization", but this seems to be a bad idea in this # case since depending on how AsInstance was called, you # could get various exceptions... With the current situation, # whether you try to compare an apple to an orange (__eq__), # or to remove a duck from a salad (salad.remove(duck)), # you'll get a TypeError. return cls.AsInstance(obj) __AsInstanceForComparison = classmethod(__AsInstanceForComparison) # Could be used to sort sections lexicographically within a config... def __lt__(self, other): """Tell whether 'self' is smaller than another object. The comparison is performed first on the names of the objects to compare, then on their lines. The comparisons on the names and lines are the standard comparisons for the objects stored in the "name" and "lines" attributes of ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a ConfigSection instance. """ o = self.__AsInstanceForComparison(other, "__lt__") return (self.name < o.name) or (self.name == o.name and self.lines < o.lines) def __le__(self, other): """Tell whether 'self' is smaller than or equal to another object. The comparison is performed first on the names of the objects to compare, then on their lines. The comparisons on the names and lines are the standard comparisons for the objects stored in the "name" and "lines" attributes of bmp.config.ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a ConfigSection instance. """ o = self.__AsInstanceForComparison(other, "__le__") return (self.name < o.name) or (self.name == o.name and self.lines <= o.lines) def __eq__(self, other): """Tell whether 'self' is equal to another object. The comparison is performed first on the names of the objects to compare, then on their lines. The comparisons on the names and lines are the standard comparisons for the objects stored in the "name" and "lines" attributes of bmp.config.ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a ConfigSection instance. """ o = self.__AsInstanceForComparison(other, "__eq__") return (self.name == o.name) and (self.lines == o.lines) def __ne__(self, other): """Tell whether 'self' is not equal to another object. The comparison is performed first on the names of the objects to compare, then on their lines. The comparisons on the names and lines are the standard comparisons for the objects stored in the "name" and "lines" attributes of bmp.config.ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a ConfigSection instance. """ o = self.__AsInstanceForComparison(other, "__ne__") return (self.name != o.name) or (self.lines != o.lines) def __gt__(self, other): """Tell whether 'self' is greater than another object. The comparison is performed first on the names of the objects to compare, then on their lines. The comparisons on the names and lines are the standard comparisons for the objects stored in the "name" and "lines" attributes of bmp.config.ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a ConfigSection instance. """ o = self.__AsInstanceForComparison(other, "__gt__") return (self.name > o.name) or (self.name == o.name and self.lines > o.lines) def __ge__(self, other): """Tell whether 'self' is greater than or equal to another object. The comparison is performed first on the names of the objects to compare, then on their lines. The comparisons on the names and lines are the standard comparisons for the objects stored in the "name" and "lines" attributes of bmp.config.ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a ConfigSection instance. """ o = self.__AsInstanceForComparison(other, "__ge__") return (self.name > o.name) or (self.name == o.name and self.lines >= o.lines) def __nonzero__(self): """Tell whether 'self' is considered True in a boolean context. 'self' is considered True if at least one of the objects referred to by its "name" and its "lines" attributes is considered True, i.e. if the configuration section it represents has at least a non-empty name or a configuration line. """ return bool(self.name or self.lines) def __len__(self): """Return the length of 'self'. The length of a ConfigSection instance is defined as the number of configuration lines in the configuration section it represents. """ return self.lines.__len__() def __getitem__(self, key): """Retrieve a configuration line or value from a configuration section. key -- an integer or a string specifying the line (or value) to retrieve If 'key' is an integer, it is interpreted as the number of the line to retrieve (starting from 0, as with all Python standard sequences). Otherwise, 'key' must be a string and the value (not the whole line) from the first line in 'self' whose key is 'key' will be returned. Note: there are usually no duplicate keys within a configuration section). When 'key' is an integer, the configuration line is returned in the form of the ConfigLine instance embedded in 'self' that describes the line. Therefore, any in-place modification of the return value will affect 'self'. If 'key' is an integer that corresponds to no configuration line in 'self', IndexError is raised. If 'key' is a string and no line in 'self' has such a key, KeyError is raised. """ if isinstance(key, str): for line in self.lines: if line.key == key: return line.value raise KeyError(key) else: try: return self.lines.__getitem__(key) except TypeError: raise TypeError( "__getitem__ key for bmp.config.ConfigSection " "instances must be either a string or a valid key for " "list.__getitem__") def __setitem__(self, key, value): """Set an item in a ConfigSection instance. key -- line number or key that indicates which line to operate on value -- value or configuration line to store instead of the item referred to by 'key' This method is fairly flexible, as it can accept various types for "key" and "value". If 'key' is a string, the operation is performed on the first line (ConfigLine instance) whose key is 'key'. Notes: 1. There are usually no duplicate keys within a configuration section. 2. If no configuration line has 'key' as its key, KeyError is raised. We don't even try to add a new configuration line to the section built from 'key' and 'value' because we wouldn't know where to store it within the section (first line, last line, random?). You can use the usual sequence operations (insert, append, extend...) on 'self' for this purpose. In this case, if 'value' is also a string, the configuration line's "value" attribute is simply set to 'value'. Otherwise, 'value' must be coercible to a ConfigLine instance (i.e., either a ConfigLine instance or an instance of a direct or indirect subclass of ConfigLine or an object 'o' for which 'o[0]' and 'o[1]' are strings). Then, the whole line which is operated on will be replaced by a ConfigLine instance built from 'value' (which *is* 'value' if it is an instance of ConfigLine or an instance of a direct or indirect subclass of ConfigLine). If 'key' is an integer, 'value' must be coercible to a ConfigLine instance, as defined in the previous paragraph, and the line whose number is 'key' will be replaced with a ConfigLine instance built from 'value' (which *is* 'value' if it is an instance of ConfigLine or an instance of a direct or indirect subclass of ConfigLine, as in the previous paragraph). In other words, if 's' is a ConfigSection instance and 'l' an object such that isinstance(l, bmp.config.ConfigLine) returns True, the following operations (implemented by this method) can be performed: s["existing_key"] = "value" s["existing_key"] = l s["existing_key"] = ("new_key", "new_value") s["existing_key"] = ["new_key", "new_value"] etc. s[index] = l (where 'index' is an integer such that 0 <= index < len(s)) s[index] = ("new_key", "new_value") s[index] = ["new_key", "new_value"] etc. Note: s[index] = "newvalue" is *NOT* possible because I find it non-intuitive (when I write s[index], it find it intuitive to have it refer to the whole configuration line). Exceptions: - TypeError is raised if 'key' and 'value' do not conform to the preceding rules. - KeyError is raised if 'key' is a string and there is no line with that key in the configuration section. - IndexError is raised if 'key' is an integer and there is no line whose index is 'key' in the configuration section. """ if isinstance(key, str): i = 0 for line in self.lines: if line.key == key: if isinstance(value, str): line.value = value else: try: val = ConfigLine.AsInstance(value) except TypeError: raise TypeError( "bmp.config.ConfigSection.__setitem__ " "called with a string parameter as key and a " "value that is neither a string, nor an " "bmp.config.ConfigLine, nor an object that " "can be coerced to an bmp.config.ConfigLine") self.lines[i] = val return i += 1 # There is no such key in self.lines. raise KeyError(key) else: # Don't pollute our nice ConfigSection with elements that # are not ConfigLine instances! val = ConfigLine.AsInstance(value) try: return self.lines.__setitem__(key, val) except TypeError: raise TypeError( "__setitem__ key for bmp.config.ConfigSection " "instances must be either a string or a valid key " "for list.__setitem__") def __delitem__(self, key): """Delete a configuration line from a configuration section. If 'key' is a string, the first line in 'self' whose key is 'key' is deleted. If there is no such line, KeyError is raised. If 'key' is an integer, the line in 'self' whose index is 'key' (starting from 0) is deleted. If there is no such line, IndexError is raised. """ if isinstance(key, str): i = 0 for line in self.lines: if line.key == key: del self.lines[i] return i += 1 raise KeyError(key) else: try: self.lines.__delitem__(key) except TypeError: raise TypeError( "__delitem__ key for bmp.config.ConfigSection " "instances must be either a string or a valid key for " "list.__delitem__") def __iter__(self): """Return an iterator over the configuration lines of a section. This method returns an iterator over the configuration lines contained in 'self', preserving order. """ # In Python 2.2.1, lists don't have the __iter__ attribute... return iter(self.lines) def __contains__(self, item): """Membership test for a configuration line or key within a section. If 'item' is a string, this methods tests whether there is a configuration line in 'self' whose key is 'item'. If there is such a line, the return value is True, otherwise it is False. If 'item' is not a string, it is coerced to a ConfigLine and this method returns True if 'self' contains a line with the same key and value, otherwise False. If 'item' is not a string and cannot be coerced to a ConfigLine, TypeError is raised. Examples: If 's' is a ConfigSection instance, the following tests are allowed using this method: "device" in s bmp.config.ConfigLine(key="device", value="/dev/cdrom") in s ("device", "/dev/cdrom") in s ["device", "/dev/cdrom"] in s etc. """ if isinstance(item, str): for line in self.lines: if line.key == item: return True return False else: # Thanks to ConfigLine.__eq__, the following line allows tests # like '("device", "/dev/cdrom") in ConfigSection_instance'. return self.lines.__contains__(item) def append(self, line, *args, **kwargs): """Append a configuration line to a configuration section. line -- an object that can be coerced to a ConfigLine instance This method works as list.append and operates on the list of configuration lines contained in 'self'. """ # Could also be done as self[len(self):len(self)] = [...], # but this way even works in case append() would grow more # arguments... self.lines.append(ConfigLine.AsInstance(line), *args, **kwargs) def count(self, *args, **kwargs): """Return the number of lines that are equal to the parameter given. This method works like list.count and operates on the list of configuration lines contained in 'self'. It returns the number of lines in 'self' that are equal (according to bmp.config.ConfigLine.__eq__) to the parameter it is given. It does *NOT* count, say, the number of different keys for a given value, or the number of different values for a given key. """ return self.lines.count(*args, **kwargs) def index(self, *args, **kwargs): """Return the index of a configuration line in a configuration section. This method works like list.index and operates on the list of configuration lines contained in 'self'. """ return self.lines.index(*args, **kwargs) def extend(self, lineseq, *args, **kwargs): """Extend a configuration section using a sequence of configuration lines. lines -- a sequence of objects that can be coerced to ConfigLine instances This method works like list.extend and operates on the list of configuration lines contained in 'self'. If an element of 'lines' is not coercible to a ConfigLine instance, TypeError is raised. """ # Could also be done as self[len(self):len(self)] = map(...), # but this way even works in case extend() would grow more # arguments... return self.lines.extend( map(ConfigLine.AsInstance, lines), *args, **kwargs) def insert(self, i, line, *args, **kwargs): """Insert a configuration line into a configuration section. i -- index indicating where the insertion should take place line -- an object that can be coerced to a ConfigLine instance This method works like list.insert and operates on the list of configuration lines contained in 'self'. """ # Could also be done as self[i:i] = [...], # but this way even works in case insert() would grow more # arguments... return self.lines.insert(i, ConfigLine.AsInstance(line), *args, **kwargs) def pop(self, key=-1): """Pop a configuration line or value from a configuration section. key -- integer index or string indicating what to pop from 'self' This method works like a hybrid of list.pop and dict.pop and operates on the list of configuration lines contained in 'self'. If the parameter "key" is an integer, or if it is not provided (in which case it defaults to the integer -1), this method acts similarly to list.pop: it returns the line whose index is 'key' (the last line if "key" is -1) in the list of lines contained in 'self' and deletes it from this list. If "key" is a string, this method acts somewhat similarly to dict.pop: it returns the value from the first line whose key is 'key' in the list of lines contained in 'self' and deletes this line from the list. Examples: If 's' is a ConfigSection instance and 'index' an integer such that 0 <= index < len(s), the following operations are allowed using this method: ConfigLine_instance = s.pop(index) value = s.pop("device") """ # Here, something like self.lines.pop(*args, **kwargs) would # work with integer parameters but would not allow to pop a # line given its key, like in # ConfigSection_instance.pop("key"), which is nice and # somewhat consistent with the ability to retreive a value # with ConfigSection_instance["key"]. x = self[i] del self[i] return x def remove(self, line, *args, **kwargs): """Remove a configuration line from a configuration section. line -- an object that can be coerced to a ConfigLine instance This method works like list.remove and operates on the list of configuration lines contained in 'self'. """ # Could also be done as del self[self.index(...)], # but this way even works in case remove() would grow more # arguments... return self.lines.remove(ConfigLine.AsInstance(line), *args, **kwargs) def reverse(self, *args, **kwargs): """Reverse the order of configuration lines in a configuration section. This method works like list.reverse and operates on the list of configuration lines contained in 'self'. """ return self.lines.reverse(*args, **kwargs) def sort(self, *args, **kwargs): """Sort the configuration lines contained in a configuration section. This method works like list.sort and operates on the list of configuration lines contained in 'self'. """ return self.lines.sort(*args, **kwargs) # _add__(), __radd__(), __iadd__(), __mul__(), __rmul__() and __imul__() # are not implemented because they don't really make sense for # ConfigSection instances. # # Ex. : let a and b be two ConfigSections instances. # # 1. What would be the name of a+b? # 2. Let c = b * n, n being a positive integer. # # With a __mul__ implementation analog to list.__mul__, several # ConfigLine instances in c.lines would be the same objects (same # as in 'id(x) == id(y)'). Hence, modifying a key or a value in # one of these objects would modify it in the other ones, leading # to several lines having the same key (or value) in a given # section, which IMHO is not the effect the user intended to # achieve (in an assignment affecting a single ConfigLine). class Config(object): """Class for configuration objects. This class implements objects that represent the contents of files similar to the main configuration file for BMP (usually, ~/.xmms/config). Each instance of this class represents a configuration such as: [CDDA] device=/dev/cdrom directory=/cdrom/ [ESD] use_remote=FALSE remote_host=localhost remote_port=16001 Note: this is an incomplete configuration for BMP; it is only intended as a short example using a correct syntax. Public Methods -------------- The Config class has the following methods: __init__ __repr__ __str__ __lt__ __le__ __eq__ __ne__ __gt__ __ge__ __nonzero__ __len__ __getitem__ __setitem__ __delitem__ __iter__ __contains__ append extend insert index count remove pop reverse sort AsInstance write_to_file Public Instance Variables (attributes) -------------------------------------- The Config class has the following attribute: sections -- a list of ConfigSection instances In the previous example, the "sections" attribute would be the following list: [bmp.config.ConfigSection(name='CDDA', lines=[ bmp.config.ConfigLine(key='device', value='/dev/cdrom'), bmp.config.ConfigLine(key='directory', value='/cdrom/')]), bmp.config.ConfigSection(name='ESD', lines=[ bmp.config.ConfigLine(key='use_remote', value='FALSE'), bmp.config.ConfigLine(key='remote_host', value='localhost'), bmp.config.ConfigLine(key='remote_port', value='16001')])] As you can guess from the method names listed above, Config instances behave like lists and also support some typical dictionary operations, so in most cases, you should not have to fiddle directly with the "sections" attribute. """ def __init__(self, sections=None, fromfile=None): """Constructor for Config instances. sections -- an iterable yielding configuration sections fromfile -- a file name There are three ways to invoke this constructor: 1. If both 'sections' and 'fromfile' are None, an empty Config instance (i.e. having no sections) is created. 2. If 'sections' is not None but 'fromfile' is, 'sections' must be an iterable from which the sections to reference in the Config instance being created shall be retrieved (their order is preserved). 3. If 'sections' is None but 'fromfile' is not, the sections to reference in the Config instance being created will be fetched from a file with the BMP configuration file syntax. If 'fromfile' is the empty string, the default configuration file for BMP is used (usually, ~/.xmms/config). Otherwise, 'fromfile' must be a relative or absolute path to the file that shall be used to retrieve the sections. If both 'sections' and 'fromfile' are not None, UsageError is raised. In the second case above, where the sections are provided through an iterable object, the objects returned by that iterable need not be strictly ConfigSection instances. They just need to be coercible to ConfigSection instances, as defined by the AsInstance class method of ConfigSection. Examples: 1. c = bmp.config.Config(fromfile=os.path.join( os.getenv("HOME"), ".xmms", "config")) or (better, since it allows BMP itself to decide what its defaut configuration file is): c = bmp.config.Config(fromfile="") 2. c = bmp.config.Config( sections=[ bmp.config.ConfigSection(name='CDDA', lines=[ bmp.config.ConfigLine(key='device', value='/dev/cdrom'), bmp.config.ConfigLine(key='directory', value='/cdrom/')]), bmp.config.ConfigSection(name='ESD', lines=[ bmp.config.ConfigLine(key='use_remote', value='FALSE'), bmp.config.ConfigLine(key='remote_host', value='localhost'), bmp.config.ConfigLine(key='remote_port', value='16001')])]) or, equivalently: c2 = bmp.config.Config( [ ('CDDA', [('device', '/dev/cdrom'), ('directory', '/cdrom/')]), ('ESD', [('use_remote', 'FALSE'), ('remote_host', 'localhost'), ('remote_port', '16001')])]) Notable exception: bmp.config.UsageError is raised if both 'sections' and 'fromfile' are not None. """ if (sections is None) and (fromfile is None): self.sections = [] elif fromfile is None: self.sections = [] # Raises a TypeError if we cannot iterate over 'sections' for section in sections: self.sections.append(ConfigSection.AsInstance(section)) elif sections is None: self.sections = _bmpconfig.Config(fromfile=fromfile).dump() else: raise UsageError('bmp.config.Config.__init__ does not accept ' 'the non-None "sections" and "fromfile" ' 'parameters simultaneously') def __repr__(self): """Return a string accurately representing the object.""" return "bmp.config.Config(sections=%s)" \ % (repr(self.sections),) def __str__(self): """Return a string representing the object.""" return "bmp.config.Config(sections=%s)" \ % (str(self.sections),) def AsInstance(cls, obj): """Return an object as an instance of this method's class. Note: this is class method. obj -- an instance of the class 'cls' or any other object 'o' that can be used to build a 'cls' instance with 'cls(sections=o)' Return 'obj' if already a 'cls' instance (or an instance of a direct or indirect subclass of 'cls'). Otherwise, create and return a new 'cls' instance built from 'obj' as described above. """ if isinstance(obj, cls): return obj else: return cls(sections=obj) AsInstance = classmethod(AsInstance) def __AsInstanceForComparison(cls, obj, method_name): # cf. comment for the same method for ConfigSection return cls.AsInstance(obj) __AsInstanceForComparison = classmethod(__AsInstanceForComparison) def __lt__(self, other): """Tell whether 'self' is smaller than another object. The comparison is performed on the lists of configuration sections stored in 'self' and 'other'. Therefore, it relies on the comparison operators for lists and ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a Config instance. """ o = self.__AsInstanceForComparison(other, "__lt__") return (self.sections < o.sections) def __le__(self, other): """Tell whether 'self' is smaller than or equal to another object. The comparison is performed on the lists of configuration sections stored in 'self' and 'other'. Therefore, it relies on the comparison operators for lists and ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a Config instance. """ o = self.__AsInstanceForComparison(other, "__le__") return (self.sections <= o.sections) def __eq__(self, other): """Tell whether 'self' is equal to another object. The comparison is performed on the lists of configuration sections stored in 'self' and 'other'. Therefore, it relies on the comparison operators for lists and ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a Config instance. """ o = self.__AsInstanceForComparison(other, "__eq__") return (self.sections == o.sections) def __ne__(self, other): """Tell whether 'self' is not equal to another object. The comparison is performed on the lists of configuration sections stored in 'self' and 'other'. Therefore, it relies on the comparison operators for lists and ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a Config instance. """ o = self.__AsInstanceForComparison(other, "__ne__") return (self.sections != o.sections) def __gt__(self, other): """Tell whether 'self' is greater than another object. The comparison is performed on the lists of configuration sections stored in 'self' and 'other'. Therefore, it relies on the comparison operators for lists and ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a Config instance. """ o = self.__AsInstanceForComparison(other, "__gt__") return (self.sections > o.sections) def __ge__(self, other): """Tell whether 'self' is greater than or equal to another object. The comparison is performed on the lists of configuration sections stored in 'self' and 'other'. Therefore, it relies on the comparison operators for lists and ConfigSection instances. TypeError is raised if 'other' cannot be interpreted as a Config instance. """ o = self.__AsInstanceForComparison(other, "__ge__") return (self.sections >= o.sections) def __nonzero__(self): """Tell whether 'self' is considered True in a boolean context. 'self' is considered True if it contains at least one configuration section. """ # Lists don't have __nonzero__ (at least in Python 2.3) return bool(self.sections) def __len__(self): """Return the length of 'self'. The length of a Config instance is defined as the number of configuration sections it contains. """ return self.sections.__len__() def __getitem__(self, key): """Retrieve a configuration section from a configuration. key -- an integer or a string specifying the section to retrieve If 'key' is an integer, it is interpreted as the number of the section to retrieve (starting from 0, as with all Python standard sequences). Otherwise, 'key' must be a string and the first section whose name is 'key' will be returned. Note: usually, all sections within a Config instance have different names. The configuration section is returned in the form of the ConfigSection instance embedded in 'self' that describes the section. Therefore, any in-place modification of the return value will affect 'self'. If 'key' is an integer that corresponds to no configuration section in 'self', IndexError is raised. If 'key' is a string and no section in 'self' has such a name, KeyError is raised. """ if isinstance(key, str): for section in self.sections: if section.name == key: return section raise KeyError(key) else: try: return self.sections.__getitem__(key) except TypeError: raise TypeError( "__getitem__ key for bmp.config.Config " "instances must be either a string or a valid key for " "list.__getitem__") def __setitem__(self, key, value): """Set an item in a Config instance. key -- section number or name that indicates which section to operate on value -- configuration section to substitute for that referred to by 'key' This method is fairly flexible, as it can accept various types for "key" and "value". If 'key' is a string, the operation is performed on the first section (ConfigSection instance) whose name is 'key'. Notes: 1. Usually, all sections within a Config instance have different names. 2. If no configuration section has 'key' as its name, KeyError is raised. We don't even try to add a new configuration section to 'self' built from 'key' and/or 'value' because we wouldn't know where to store it within the whole configuration (first, last, random?). You can use the usual sequence operations (insert, append, extend...) on 'self' for this purpose. In this case, the configuration section to substitute for that referred to by 'key' is: - 'ConfigSection.AsInstance(value)' if this expression does not raise TypeError; - 'ConfigSection.AsInstance((key, value))' otherwise. If both attempts fail, TypeError is raised. If 'key' is an integer, 'value' must be coercible to a ConfigSection instance: the expression 'ConfigSection.AsInstance(value)' must not raise a TypeError and the resulting ConfigSection instance will replace the section referred to by 'key'. In other words, if 'c' is a Config instance and 's' an object such that isinstance(s, bmp.config.ConfigSection) returns True, the following operations (implemented by this method) can be performed: 1. c["existing_section"] = ( bmp.config.ConfigLine(key='foo', value='foo_value'), bmp.config.ConfigLine(key='bar', value='bar_value')) equivalent to: c["existing_section"] = (('foo', 'foo_value'), ('bar', 'bar_value')) (and variants involving other types of sequences...) 2. c["existing_section"] = s c["existing_section"] = ("new_section_name", (bmp.config.ConfigLine(key='foo', value='foo_value'), bmp.config.ConfigLine(key='bar', value='bar_value'))) equivalent to: c["existing_section"] = ("new_section_name", (('foo', 'foo_value'), ('bar', 'bar_value'))) (and variants involving other types of sequences...) 3. c[index] = s (where 'index' is an integer such that 0 <= index < len(c)) c[index] = ("new_section_name", (bmp.config.ConfigLine(key='foo', value='foo_value'), bmp.config.ConfigLine(key='bar', value='bar_value'))) c[index] = ("new_section_name", (('foo', 'foo_value'), ('bar', 'bar_value'))) etc. Note: c[index] = (('foo', 'foo_value'), ('bar', 'bar_value')) and similar constructs are *NOT* possible because I find them non-intuitive (when I write c[index], it find it intuitive to have it refer to the whole configuration section, including its name). Exceptions: - TypeError is raised if 'key' and 'value' do not conform to the preceding rules. - KeyError is raised if 'key' is a string and there is no section with that name in the configuration represented by 'self'. - IndexError is raised if 'key' is an integer and there is no section whose index is 'key' in the configuration represented by 'self'. """ if isinstance(key, str): i = 0 for section in self.sections: if section.name == key: try: val = ConfigSection.AsInstance(value) except TypeError: try: val = ConfigSection.AsInstance((key, value)) except TypeError: raise TypeError( "bmp.config.Config.__setitem__ " "called with a string parameter as key and a " "value that can neither be interpreted as an" "bmp.config.ConfigSection, nor as a " "sequence of objects that can be interpreted " "as bmp.config.ConfigLine instances") self.sections[i] = val return i += 1 # There is no section named 'key' in self.sections. raise KeyError(key) else: # Don't pollute our nice Config with elements that are # not ConfigSection instances! val = ConfigSection.AsInstance(value) try: return self.sections.__setitem__(key, val) except TypeError: raise TypeError( "__setitem__ key for bmp.config.Config " "instances must be either a string or a valid key " "for list.__setitem__") def __delitem__(self, key): """Delete a configuration section from a configuration. If 'key' is a string, the first section in 'self' whose name is 'key' is deleted. If there is no such section, KeyError is raised. If 'key' is an integer, the section in 'self' whose index is 'key' (starting from 0) is deleted. If there is no such section, IndexError is raised. """ if isinstance(key, str): i = 0 for section in self.sections: if section.name == key: del self.sections[i] return i += 1 raise KeyError(key) else: try: self.sections.__delitem__(key) except TypeError: raise TypeError( "__delitem__ key for bmp.config.Config " "instances must be either a string or a valid key for " "list.__delitem__") def __iter__(self): """Return an iterator over the sections in a configuration. This method returns an iterator over the configuration sections contained in 'self', preserving order. """ # In Python 2.2.1, lists don't have the __iter__ attribute... return iter(self.sections) def __contains__(self, item): """Membership test for a section or name within a configuration. If 'item' is a string, this methods tests whether there is a configuration section in 'self' whose name is 'item'. If there is such a section, the return value is True, otherwise it is False. If 'item' is not a string, it is coerced to a ConfigSection and this method returns True if 'self' contains an identical section (name and lines have to be identical for that), otherwise False. If 'item' is not a string and cannot be coerced to a ConfigSection, TypeError is raised. Examples: If 'c' is a Config instance, the following tests are allowed using this method: "CDDA" in c bmp.config.ConfigSection(name='CDDA', lines=[ bmp.config.ConfigLine(key='device', value='/dev/cdrom'), bmp.config.ConfigLine(key='directory', value='/cdrom/')]) in c ('CDDA', [bmp.config.ConfigLine(key='device', value='/dev/cdrom'), bmp.config.ConfigLine(key='directory', value='/cdrom/')]) in c ('CDDA', [('device', '/dev/cdrom'), ('directory', '/cdrom/')]) in c etc. """ if isinstance(item, str): for section in self.sections: if section.name == item: return True return False else: # Thanks to ConfigSection.__eq__, the following line allows tests # like: # # ("CDDA", ("device", "/dev/cdrom"), # ("directory", "/cdrom/")) in Config_instance return self.sections.__contains__(item) def append(self, section, *args, **kwargs): """Append a configuration section to a configuration. section -- an object that can be coerced to a ConfigSection instance This method works as list.append and operates on the list of configuration sections contained in 'self'. """ # Could also be done as self[len(self):len(self)] = [...], # but this way even works in case append() would grow more # arguments... self.sections.append(ConfigSection.AsInstance(section), *args, **kwargs) def count(self, *args, **kwargs): """Return the number of sections that are equal to the parameter given. This method returns the number of sections in a Config instance that are equal to the parameter it is given. It does *NOT* count, say, the number of different sections that have a given name, or whatever else you may wish it would do for you. """ return self.sections.count(*args, **kwargs) def index(self, *args, **kwargs): """Return the index of a configuration section in a configuration. This method works like list.index and operates on the list of configuration sections contained in 'self'. """ return self.sections.index(*args, **kwargs) def extend(self, sections, *args, **kwargs): """Extend a configuration using a sequence of configuration sections. sections -- a sequence of objects that can be coerced to ConfigSection instances This method works like list.extend and operates on the list of configuration sections contained in 'self'. If an element of 'sections' is not coercible to a ConfigSection instance, TypeError is raised. """ # Could also be done as self[len(self):len(self)] = map(...), # but this way even works in case extend() would grow more # arguments... return self.sections.extend( map(ConfigSection.AsInstance, sections), *args, **kwargs) def insert(self, i, section, *args, **kwargs): """Insert a configuration section into a configuration. i -- index indicating where the insertion should take place section -- an object that can be coerced to a ConfigSection instance This method works like list.insert and operates on the list of configuration sections contained in 'self'. """ # Could also be done as self[i:i] = [...], # but this way even works in case insert() would grow more # arguments... return self.sections.insert(i, ConfigSection.AsInstance(section), *args, **kwargs) def pop(self, key=-1): """Pop a configuration section from a configuration. key -- integer index or string indicating the section to pop from 'self' This method works like a hybrid of list.pop and dict.pop and operates on the list of configuration sections contained in 'self'. If the parameter "key" is an integer, or if it is not provided (in which case it defaults to the integer -1), this method acts similarly to list.pop: it returns the section whose index is 'key' (the last section if "key" is -1) in the list of sections contained in 'self' and deletes it from this list. If "key" is a string, this method acts somewhat similarly to dict.pop: it returns the first section whose name is 'key' in the list of sections contained in 'self' and deletes this section from the list. Examples: If 'c' is a Config instance and 'index' an integer such that 0 <= index < len(c), the following operations are allowed using this method: ConfigSection_instance = c.pop(index) ConfigSection_instance = c.pop("CDDA") """ # Here, something like self.sections.pop(*args, **kwargs) would # work with integer parameters but would not allow to pop a # section given its name, like in # Config_instance.pop("section_name"), which is nice and # somewhat consistent with the ability to retreive a section # with Config_instance["section_name"]. x = self[key] del self[key] return x def remove(self, section, *args, **kwargs): """Remove a configuration section from a configuration. section -- an object that can be coerced to a ConfigSection instance This method works like list.remove and operates on the list of configuration sections contained in 'self'. """ # Could also be done as del self[self.index(...)], # but this way even works in case remove() would grow more # arguments... # Could also be done as del self[self.index(...)], # but this way even works in case remove() would grow more # arguments... return self.sections.remove(ConfigSection.AsInstance(section), *args, **kwargs) def reverse(self, *args, **kwargs): """Reverse the order of configuration sections in a configuration. This method works like list.reverse and operates on the list of configuration sections contained in 'self'. """ return self.sections.reverse(*args, **kwargs) def sort(self, *args, **kwargs): """Sort the configuration sections contained in a configuration. This method works like list.sort and operates on the list of configuration sections contained in 'self'. """ return self.sections.sort(*args, **kwargs) def write_to_file(self, filename=""): """Write the contents of a Config instance to a file. Write the contents of 'self' to the file indicated by the "filename" parameter. If "filename" is None or is the empty string, the configuration is written to the default BMP configuration file. Otherwise, "filename" must indicate an absolute or relative file name. ************************************************************* * I shall repeat it. If you don't provide a specific * * filename, the default configuration file for BMP is * * replaced by the contents of 'self'. YOU HAVE BEEN WARNED. * ************************************************************* """ # Create an empty _bmpconfig.Config object cfg = _bmpconfig.Config() for section in self: section_name = section.name cfg.create_section(section_name) for line in section: cfg.write_string(section_name, line.key, line.value) try: cfg.write_to_file(filename) except _bmpconfig.WriteToFileError: raise WriteToFileError("error while writing the contents of an " "bmp.config.Config instance to the file " "indicated with the \"filename\" " "parameter \"%s\"" % filename) # _add__(), __radd__(), __iadd__(), __mul__(), __rmul__() and # __imul__() are not implemented because the problem highlighted # by the second example detailed above in the explanation of why # these methods are not implemented for ConfigSection objects # also applies to Config objects.