4 # Copyright 2004-2007 by Vinay Sajip. All Rights Reserved.
6 # Permission to use, copy, modify, and distribute this software and its
7 # documentation for any purpose and without fee is hereby granted,
8 # provided that the above copyright notice appear in all copies and that
9 # both that copyright notice and this permission notice appear in
10 # supporting documentation, and that the name of Vinay Sajip
11 # not be used in advertising or publicity pertaining to distribution
12 # of the software without specific, written prior permission.
13 # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
14 # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
15 # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
16 # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
17 # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
18 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 # Copyright (C) 2010-2013 CEA/DEN
22 # This library is free software; you can redistribute it and/or
23 # modify it under the terms of the GNU Lesser General Public
24 # License as published by the Free Software Foundation; either
25 # version 2.1 of the License.
27 # This library is distributed in the hope that it will be useful,
28 # but WITHOUT ANY WARRANTY; without even the implied warranty of
29 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30 # Lesser General Public License for more details.
32 # You should have received a copy of the GNU Lesser General Public
33 # License along with this library; if not, write to the Free Software
34 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
37 # Possibility to overwrites value in a pyconf file
42 This is a configuration module for Python.
44 This module should work under Python versions >= 2.2, and cannot be used with
45 earlier versions since it uses new-style classes.
47 Development and testing has only been carried out (so far) on Python 2.3.4 and
48 Python 2.4.2. See the test module (test_config.py) included in the
49 U{distribution<http://www.red-dove.com/python_config.html|_blank>} (follow the
52 A simple example - with the example configuration file::
67 stream : $messages[0].stream
73 a program to read the configuration would be::
75 from config import Config
77 f = file('simple.cfg')
79 for m in cfg.messages:
80 s = '%s, %s' % (m.message, m.name)
86 which, when run, would yield the console output::
92 See U{this tutorial<http://www.red-dove.com/python_config.html|_blank>} for more
95 #modified for salomeTools
100 @copyright: Copyright (C) 2004-2007 Vinay Sajip. All Rights Reserved.
103 @var streamOpener: The default stream opener. This is a factory function which
104 takes a string (e.g. filename) and returns a stream suitable for reading. If
105 unable to open the stream, an IOError exception should be thrown.
107 The default value of this variable is L{defaultStreamOpener}. For an example
108 of how it's used, see test_config.py (search for streamOpener).
111 __author__ = "Vinay Sajip <vinay_sajip@red-dove.com>"
113 __version__ = "0.3.7.1" #modified for salomeTools
114 __date__ = "05 October 2007"
147 WORDCHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
149 if sys.platform == 'win32':
151 elif os.name == 'mac':
161 class ConfigInputStream(object):
163 An input stream which can read either ANSI files with default encoding
164 or Unicode files with BOMs.
166 Handles UTF-8, UTF-16LE, UTF-16BE. Could handle UTF-32 if Python had
169 def __init__(self, stream):
171 Initialize an instance.
173 @param stream: The underlying stream to be read. Should be seekable.
174 @type stream: A stream (file-like object).
177 signature = stream.read(4)
180 if signature == codecs.BOM_UTF32_LE:
181 encoding = 'utf-32le'
182 elif signature == codecs.BOM_UTF32_BE:
183 encoding = 'utf-32be'
185 if signature[:3] == codecs.BOM_UTF8:
188 elif signature[:2] == codecs.BOM_UTF16_LE:
190 encoding = 'utf-16le'
191 elif signature[:2] == codecs.BOM_UTF16_BE:
193 encoding = 'utf-16be'
199 reader = codecs.getreader(encoding)
200 stream = reader(stream)
202 self.encoding = encoding
204 def read(self, size):
205 if (size == 0) or (self.encoding is None):
206 rv = self.stream.read(size)
210 rv += self.stream.read(1)
218 if self.encoding is None:
223 c = self.stream.read(1)
224 if isinstance(c, bytes):
232 class ConfigOutputStream(object):
234 An output stream which can write either ANSI files with default encoding
235 or Unicode files with BOMs.
237 Handles UTF-8, UTF-16LE, UTF-16BE. Could handle UTF-32 if Python had
241 def __init__(self, stream, encoding=None):
243 Initialize an instance.
245 @param stream: The underlying stream to be written.
246 @type stream: A stream (file-like object).
247 @param encoding: The desired encoding.
250 if encoding is not None:
251 encoding = str(encoding).lower()
252 self.encoding = encoding
253 if encoding == "utf-8":
254 stream.write(codecs.BOM_UTF8)
255 elif encoding == "utf-16be":
256 stream.write(codecs.BOM_UTF16_BE)
257 elif encoding == "utf-16le":
258 stream.write(codecs.BOM_UTF16_LE)
259 elif encoding == "utf-32be":
260 stream.write(codecs.BOM_UTF32_BE)
261 elif encoding == "utf-32le":
262 stream.write(codecs.BOM_UTF32_LE)
264 if encoding is not None:
265 writer = codecs.getwriter(encoding)
266 stream = writer(stream)
269 def write(self, data):
270 self.stream.write(data)
278 def defaultStreamOpener(name):
280 This function returns a read-only stream, given its name. The name passed
281 in should correspond to an existing stream, otherwise an exception will be
284 This is the default value of L{streamOpener}; assign your own callable to
285 streamOpener to return streams based on names. For example, you could use
288 @param name: The name of a stream, most commonly a file name.
290 @return: A stream with the specified name.
291 @rtype: A read-only stream (file-like object)
293 return ConfigInputStream(open(name, 'rb'))
297 __resolveOverwrite__ = True
299 class ConfigError(Exception):
301 This is the base class of exceptions raised by this module.
305 class ConfigFormatError(ConfigError):
307 This is the base class of exceptions raised due to syntax errors in
312 class ConfigResolutionError(ConfigError):
314 This is the base class of exceptions raised due to semantic errors in
321 See if a passed-in value is an identifier. If the value passed in is not a
322 string, False is returned. An identifier consists of alphanumerics or
323 underscore characters.
327 isWord('a word') ->False
328 isWord('award') -> True
330 isWord('a_b_c_') ->True
332 @note: isWord('9abc') will return True - not exactly correct, but adequate
333 for the way it's used here.
335 @param s: The name to be tested
337 @return: True if a word, else False
340 if type(s) != type(''):
342 s = s.replace('_', '')
345 def makePath(prefix, suffix):
347 Make a path from a prefix and suffix.
351 makePath('', 'suffix') -> 'suffix'
352 makePath('prefix', 'suffix') -> 'prefix.suffix'
353 makePath('prefix', '[1]') -> 'prefix[1]'
355 @param prefix: The prefix to use. If it evaluates as false, the suffix
358 @param suffix: The suffix to use. It is either an identifier or an
361 @return: The path concatenation of prefix and suffix, with a
362 dot if the suffix is not a bracketed index.
368 elif suffix[0] == '[':
371 rv = prefix + '.' + suffix
375 class Container(object):
377 This internal class is the base class for mappings and sequences.
379 @ivar path: A string which describes how to get
380 to this instance from the root of the hierarchy.
384 a.list.of[1].or['more'].elements
386 def __init__(self, parent):
388 Initialize an instance.
390 @param parent: The parent of this instance in the hierarchy.
391 @type parent: A L{Container} instance.
393 object.__setattr__(self, 'parent', parent)
395 def setPath(self, path):
397 Set the path for this instance.
398 @param path: The path - a string which describes how to get
399 to this instance from the root of the hierarchy.
402 object.__setattr__(self, 'path', path)
404 def evaluate(self, item):
406 Evaluate items which are instances of L{Reference} or L{Expression}.
408 L{Reference} instances are evaluated using L{Reference.resolve},
409 and L{Expression} instances are evaluated using
410 L{Expression.evaluate}.
412 @param item: The item to be evaluated.
414 @return: If the item is an instance of L{Reference} or L{Expression},
415 the evaluated value is returned, otherwise the item is returned
418 if isinstance(item, Reference):
419 item = item.resolve(self)
420 elif isinstance(item, Expression):
421 item = item.evaluate(self)
424 def writeToStream(self, stream, indent, container):
426 Write this instance to a stream at the specified indentation level.
428 Should be redefined in subclasses.
430 @param stream: The stream to write to
431 @type stream: A writable stream (file-like object)
432 @param indent: The indentation level
434 @param container: The container of this instance
435 @type container: L{Container}
436 @raise NotImplementedError: If a subclass does not override this
438 raise NotImplementedError
440 def writeValue(self, value, stream, indent):
441 if isinstance(self, Mapping):
444 indstr = indent * ' '
445 if isinstance(value, Reference) or isinstance(value, Expression):
446 stream.write('%s%r%s' % (indstr, value, NEWLINE))
448 if isinstance(value, str): # and not isWord(value):
450 stream.write('%s%s%s' % (indstr, value, NEWLINE))
452 class Mapping(Container):
454 This internal class implements key-value mappings in configurations.
457 def __init__(self, parent=None):
459 Initialize an instance.
461 @param parent: The parent of this instance in the hierarchy.
462 @type parent: A L{Container} instance.
464 Container.__init__(self, parent)
465 object.__setattr__(self, 'path', '')
466 object.__setattr__(self, 'data', {})
467 object.__setattr__(self, 'order', []) # to preserve ordering
468 object.__setattr__(self, 'comments', {})
470 def __delitem__(self, key):
474 data = object.__getattribute__(self, 'data')
476 raise AttributeError(key)
477 order = object.__getattribute__(self, 'order')
478 comments = object.__getattribute__(self, 'comments')
483 def __getitem__(self, key):
484 data = object.__getattribute__(self, 'data')
486 raise AttributeError(key)
488 return self.evaluate(rv)
490 __getattr__ = __getitem__
493 def __getattribute__(self, name):
494 if name == "__dict__":
496 if name in ["__methods__", "__members__"]:
498 #if name == "__class__":
500 data = object.__getattribute__(self, "data")
501 useData = data.has_key(name)
503 rv = getattr(data, name)
505 rv = object.__getattribute__(self, name)
507 raise AttributeError(name)
512 for key in self.keys():
513 yield(key, self[key])
516 def __contains__(self, item):
517 order = object.__getattribute__(self, 'order')
520 def addMapping(self, key, value, comment, setting=False):
522 Add a key-value mapping with a comment.
524 @param key: The key for the mapping.
526 @param value: The value for the mapping.
528 @param comment: The comment for the key (can be None).
530 @param setting: If True, ignore clashes. This is set
531 to true when called from L{__setattr__}.
532 @raise ConfigFormatError: If an existing key is seen
533 again and setting is False.
535 data = object.__getattribute__(self, 'data')
536 order = object.__getattribute__(self, 'order')
537 comments = object.__getattribute__(self, 'comments')
543 raise ConfigFormatError("repeated key: %s" % key)
544 comments[key] = comment
546 def __setattr__(self, name, value):
547 self.addMapping(name, value, None, True)
549 __setitem__ = __setattr__
553 Return the keys in a similar way to a dictionary.
555 return object.__getattribute__(self, 'order')
557 def get(self, key, default=None):
559 Allows a dictionary-style get operation.
566 return str(object.__getattribute__(self, 'data'))
569 return repr(object.__getattribute__(self, 'data'))
572 return len(object.__getattribute__(self, 'order'))
575 return self.iterkeys()
578 order = object.__getattribute__(self, 'order')
579 return order.__iter__()
581 def writeToStream(self, stream, indent, container):
583 Write this instance to a stream at the specified indentation level.
585 Should be redefined in subclasses.
587 @param stream: The stream to write to
588 @type stream: A writable stream (file-like object)
589 @param indent: The indentation level
591 @param container: The container of this instance
592 @type container: L{Container}
594 indstr = indent * ' '
596 stream.write(' { }%s' % NEWLINE)
598 if isinstance(container, Mapping):
599 stream.write(NEWLINE)
600 stream.write('%s{%s' % (indstr, NEWLINE))
601 self.__save__(stream, indent + 1)
602 stream.write('%s}%s' % (indstr, NEWLINE))
604 def __save__(self, stream, indent=0):
606 Save this configuration to the specified stream.
607 @param stream: A stream to which the configuration is written.
608 @type stream: A write-only stream (file-like object).
609 @param indent: The indentation level for the output.
612 indstr = indent * ' '
613 order = object.__getattribute__(self, 'order')
614 data = object.__getattribute__(self, 'data')
615 maxlen = 0 # max(map(lambda x: len(x), order))
617 comment = self.comments[key]
623 stream.write('%s#%s' % (indstr, comment))
624 stream.write('%s%-*s :' % (indstr, maxlen, skey))
626 if isinstance(value, Container):
627 value.writeToStream(stream, indent, self)
629 self.writeValue(value, stream, indent)
631 class Config(Mapping):
633 This class represents a configuration, and is the only one which clients
634 need to interface to, under normal circumstances.
637 class Namespace(object):
639 This internal class is used for implementing default namespaces.
641 An instance acts as a namespace.
647 def __init__(self, streamOrFile=None, parent=None):
649 Initializes an instance.
651 @param streamOrFile: If specified, causes this instance to be loaded
652 from the stream (by calling L{load}). If a string is provided, it is
653 passed to L{streamOpener} to open a stream. Otherwise, the passed
654 value is assumed to be a stream and used as is.
655 @type streamOrFile: A readable stream (file-like object) or a name.
656 @param parent: If specified, this becomes the parent of this instance
657 in the configuration hierarchy.
658 @type parent: a L{Container} instance.
660 Mapping.__init__(self, parent)
661 object.__setattr__(self, 'reader', ConfigReader(self))
662 object.__setattr__(self, 'namespaces', [Config.Namespace()])
663 if streamOrFile is not None:
664 if isinstance(streamOrFile, str) or isinstance(streamOrFile, bytes):
666 if streamOpener is None:
667 streamOpener = defaultStreamOpener
668 streamOrFile = streamOpener(streamOrFile)
669 load = object.__getattribute__(self, "load")
672 def load(self, stream):
674 Load the configuration from the specified stream. Multiple streams can
675 be used to populate the same instance, as long as there are no
676 clashing keys. The stream is closed.
677 @param stream: A stream from which the configuration is read.
678 @type stream: A read-only stream (file-like object).
679 @raise ConfigError: if keys in the loaded configuration clash with
681 @raise ConfigFormatError: if there is a syntax error in the stream.
683 reader = object.__getattribute__(self, 'reader')
687 def addNamespace(self, ns, name=None):
689 Add a namespace to this configuration which can be used to evaluate
690 (resolve) dotted-identifier expressions.
691 @param ns: The namespace to be added.
692 @type ns: A module or other namespace suitable for passing as an
694 @param name: A name for the namespace, which, if specified, provides
695 an additional level of indirection.
698 namespaces = object.__getattribute__(self, 'namespaces')
700 namespaces.append(ns)
702 setattr(namespaces[0], name, ns)
704 def removeNamespace(self, ns, name=None):
706 Remove a namespace added with L{addNamespace}.
707 @param ns: The namespace to be removed.
708 @param name: The name which was specified when L{addNamespace} was
712 namespaces = object.__getattribute__(self, 'namespaces')
714 namespaces.remove(ns)
716 delattr(namespaces[0], name)
718 def __save__(self, stream, indent=0, no_close=False):
720 Save this configuration to the specified stream. The stream is
721 closed if this is the top-level configuration in the hierarchy.
722 L{Mapping.__save__} is called to do all the work.
723 @param stream: A stream to which the configuration is written.
724 @type stream: A write-only stream (file-like object).
725 @param indent: The indentation level for the output.
728 Mapping.__save__(self, stream, indent)
729 if indent == 0 and not no_close:
732 def getByPath(self, path):
734 Obtain a value in the configuration via its path.
735 @param path: The path of the required value
737 @return the value at the specified path.
739 @raise ConfigError: If the path is invalid
744 except Exception as e:
745 raise ConfigError(str(e))
747 class Sequence(Container):
749 This internal class implements a value which is a sequence of other values.
751 class SeqIter(object):
753 This internal class implements an iterator for a L{Sequence} instance.
755 def __init__(self, seq):
757 self.limit = len(object.__getattribute__(seq, 'data'))
764 if self.index >= self.limit:
766 rv = self.seq[self.index]
770 # This method is for python3 compatibility
772 if self.index >= self.limit:
774 rv = self.seq[self.index]
778 def __init__(self, parent=None):
780 Initialize an instance.
782 @param parent: The parent of this instance in the hierarchy.
783 @type parent: A L{Container} instance.
785 Container.__init__(self, parent)
786 object.__setattr__(self, 'data', [])
787 object.__setattr__(self, 'comments', [])
789 def append(self, item, comment):
791 Add an item to the sequence.
793 @param item: The item to add.
795 @param comment: A comment for the item.
798 data = object.__getattribute__(self, 'data')
799 comments = object.__getattribute__(self, 'comments')
801 comments.append(comment)
803 def __getitem__(self, index):
804 data = object.__getattribute__(self, 'data')
807 except (IndexError, KeyError, TypeError):
808 raise ConfigResolutionError('%r is not a valid index for %r' % (index, object.__getattribute__(self, 'path')))
809 if not isinstance(rv, list):
810 rv = self.evaluate(rv)
815 result.append(self.evaluate(a))
820 return Sequence.SeqIter(self)
823 return repr(object.__getattribute__(self, 'data'))
826 return str(self[:]) # using the slice evaluates the contents
829 return len(object.__getattribute__(self, 'data'))
831 def writeToStream(self, stream, indent, container):
833 Write this instance to a stream at the specified indentation level.
835 Should be redefined in subclasses.
837 @param stream: The stream to write to
838 @type stream: A writable stream (file-like object)
839 @param indent: The indentation level
841 @param container: The container of this instance
842 @type container: L{Container}
844 indstr = indent * ' '
846 stream.write(' [ ]%s' % NEWLINE)
848 if isinstance(container, Mapping):
849 stream.write(NEWLINE)
850 stream.write('%s[%s' % (indstr, NEWLINE))
851 self.__save__(stream, indent + 1)
852 stream.write('%s]%s' % (indstr, NEWLINE))
854 def __save__(self, stream, indent):
856 Save this instance to the specified stream.
857 @param stream: A stream to which the configuration is written.
858 @type stream: A write-only stream (file-like object).
859 @param indent: The indentation level for the output, > 0
863 raise ConfigError("sequence cannot be saved as a top-level item")
864 data = object.__getattribute__(self, 'data')
865 comments = object.__getattribute__(self, 'comments')
866 indstr = indent * ' '
867 for i in range(0, len(data)):
869 comment = comments[i]
871 stream.write('%s#%s' % (indstr, comment))
872 if isinstance(value, Container):
873 value.writeToStream(stream, indent, self)
875 self.writeValue(value, stream, indent)
877 class Reference(object):
879 This internal class implements a value which is a reference to another value.
881 def __init__(self, config, type, ident):
883 Initialize an instance.
885 @param config: The configuration which contains this reference.
886 @type config: A L{Config} instance.
887 @param type: The type of reference.
888 @type type: BACKTICK or DOLLAR
889 @param ident: The identifier which starts the reference.
894 self.elements = [ident]
896 def addElement(self, type, ident):
898 Add an element to the reference.
900 @param type: The type of reference.
901 @type type: BACKTICK or DOLLAR
902 @param ident: The identifier which continues the reference.
905 self.elements.append((type, ident))
907 def findConfig(self, container):
909 Find the closest enclosing configuration to the specified container.
911 @param container: The container to start from.
912 @type container: L{Container}
913 @return: The closest enclosing configuration, or None.
916 while (container is not None) and not isinstance(container, Config):
917 container = object.__getattribute__(container, 'parent')
920 def resolve(self, container):
922 Resolve this instance in the context of a container.
924 @param container: The container to resolve from.
925 @type container: L{Container}
926 @return: The resolved value.
928 @raise ConfigResolutionError: If resolution fails.
931 path = object.__getattribute__(container, 'path')
933 while current is not None:
934 if self.type == BACKTICK:
935 namespaces = object.__getattribute__(current, 'namespaces')
937 for ns in namespaces:
939 rv = eval(str(self)[1:-1], vars(ns))
947 key = self.elements[0]
950 for item in self.elements[1:]:
957 current = object.__getattribute__(current, 'parent')
959 raise ConfigResolutionError("unable to evaluate %r in the configuration %s" % (self, path))
964 for tt, tv in self.elements[1:]:
969 if self.type == BACKTICK:
970 return BACKTICK + s + BACKTICK
975 return self.__str__()
977 class Expression(object):
979 This internal class implements a value which is obtained by evaluating an expression.
981 def __init__(self, op, lhs, rhs):
983 Initialize an instance.
985 @param op: the operation expressed in the expression.
986 @type op: PLUS, MINUS, STAR, SLASH, MOD
987 @param lhs: the left-hand-side operand of the expression.
988 @type lhs: any Expression or primary value.
989 @param rhs: the right-hand-side operand of the expression.
990 @type rhs: any Expression or primary value.
997 return '%r %s %r' % (self.lhs, self.op, self.rhs)
1000 return self.__str__()
1002 def evaluate(self, container):
1004 Evaluate this instance in the context of a container.
1006 @param container: The container to evaluate in from.
1007 @type container: L{Container}
1008 @return: The evaluated value.
1010 @raise ConfigResolutionError: If evaluation fails.
1011 @raise ZeroDivideError: If division by zero occurs.
1012 @raise TypeError: If the operation is invalid, e.g.
1013 subtracting one string from another.
1016 if isinstance(lhs, Reference):
1017 lhs = lhs.resolve(container)
1018 elif isinstance(lhs, Expression):
1019 lhs = lhs.evaluate(container)
1021 if isinstance(rhs, Reference):
1022 rhs = rhs.resolve(container)
1023 elif isinstance(rhs, Expression):
1024 rhs = rhs.evaluate(container)
1038 class ConfigReader(object):
1040 This internal class implements a parser for configurations.
1043 def __init__(self, config):
1044 self.filename = None
1045 self.config = config
1049 self.last_token = None
1050 self.commentchars = '#'
1051 self.whitespace = ' \t\r\n'
1053 self.punct = ':-+*/%,.{}[]()@`$'
1054 self.digits = '0123456789'
1055 self.wordchars = '%s' % WORDCHARS # make a copy
1056 self.identchars = self.wordchars + self.digits
1063 Return the current location (filename, line, column) in the stream
1066 Used when printing error messages,
1068 @return: A string representing a location in the stream being read.
1071 return "%s(%d,%d)" % (self.filename, self.lineno, self.colno)
1075 Get the next char from the stream. Update line and column numbers
1078 @return: The next character from the stream.
1082 c = self.pbchars.pop()
1083 if isinstance(c,bytes):
1086 c = self.stream.read(1)
1087 if isinstance(c,bytes):
1091 import pdb;pdb.set_trace()
1099 return "<ConfigReader at 0x%08x>" % id(self)
1105 Get a token from the stream. String values are returned in a form
1106 where you need to eval() the returned value to get the actual
1107 string. The return value is (token_type, token_value).
1109 Multiline string tokenizing is thanks to David Janes (BlogMatrix)
1111 @return: The next token.
1112 @rtype: A token tuple.
1115 return self.pbtokens.pop()
1116 stream = self.stream
1126 self.comment += '#' + stream.readline()
1128 self.comment = stream.readline()
1131 if c in self.quotes:
1145 self.pbchars.append(c2)
1146 self.pbchars.append(c1)
1148 self.pbchars.append(c1)
1154 if (c == quote) and not escaped:
1155 if not multiline or (len(token) >= 6 and token.endswith(token[:3]) and token[-4] != '\\'):
1158 escaped = not escaped
1162 raise ConfigFormatError('%s: Unterminated quoted string: %r, %r' % (self.location(), token, c))
1164 if c in self.whitespace:
1167 elif c in self.punct:
1170 if (self.lastc == ']') or (self.lastc in self.identchars):
1176 elif c in self.digits:
1183 if c in self.digits:
1185 elif (c == '.') and token.find('.') < 0:
1188 if c and (c not in self.whitespace):
1189 self.pbchars.append(c)
1192 elif c in self.wordchars:
1196 while c and (c in self.identchars):
1199 if c: # and c not in self.whitespace:
1200 self.pbchars.append(c)
1203 elif token == "False":
1205 elif token == "None":
1209 raise ConfigFormatError('%s: Unexpected character: %r' % (self.location(), c))
1211 self.lastc = token[-1]
1214 self.last_token = tt
1217 def load(self, stream, parent=None, suffix=None):
1219 Load the configuration from the specified stream.
1221 @param stream: A stream from which to load the configuration.
1222 @type stream: A stream (file-like object).
1223 @param parent: The parent of the configuration (to which this reader
1224 belongs) in the hierarchy. Specified when the configuration is
1225 included in another one.
1226 @type parent: A L{Container} instance.
1227 @param suffix: The suffix of this configuration in the parent
1228 configuration. Should be specified whenever the parent is not None.
1229 @raise ConfigError: If parent is specified but suffix is not.
1230 @raise ConfigFormatError: If there are syntax errors in the stream.
1232 if parent is not None:
1234 raise ConfigError("internal error: load called with parent but no suffix")
1235 self.config.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1236 self.setStream(stream)
1237 self.token = self.getToken()
1238 self.parseMappingBody(self.config)
1239 if self.token[0] != EOF:
1240 raise ConfigFormatError('%s: expecting EOF, found %r' % (self.location(), self.token[1]))
1242 def setStream(self, stream):
1244 Set the stream to the specified value, and prepare to read from it.
1246 @param stream: A stream from which to load the configuration.
1247 @type stream: A stream (file-like object).
1249 self.stream = stream
1250 if hasattr(stream, 'name'):
1251 filename = stream.name
1254 self.filename = filename
1260 Ensure that the current token type matches the specified value, and
1261 advance to the next token.
1263 @param t: The token type to match.
1264 @type t: A valid token type.
1265 @return: The token which was last read from the stream before this
1267 @rtype: a token tuple - see L{getToken}.
1268 @raise ConfigFormatError: If the token does not match what's expected.
1270 if self.token[0] != t:
1271 raise ConfigFormatError("%s: expecting %s, found %r" % (self.location(), t, self.token[1]))
1273 self.token = self.getToken()
1276 def parseMappingBody(self, parent):
1278 Parse the internals of a mapping, and add entries to the provided
1281 @param parent: The mapping to add entries to.
1282 @type parent: A L{Mapping} instance.
1284 while self.token[0] in [WORD, STRING]:
1285 self.parseKeyValuePair(parent)
1287 def parseKeyValuePair(self, parent):
1289 Parse a key-value pair, and add it to the provided L{Mapping}.
1291 @param parent: The mapping to add entries to.
1292 @type parent: A L{Mapping} instance.
1293 @raise ConfigFormatError: if a syntax error is found.
1295 comment = self.comment
1302 suffix = '[%s]' % tv
1304 msg = "%s: expecting word or string, found %r"
1305 raise ConfigFormatError(msg % (self.location(), tv))
1306 self.token = self.getToken()
1307 # for now, we allow key on its own as a short form of key : True
1308 if self.token[0] == COLON:
1309 self.token = self.getToken()
1310 value = self.parseValue(parent, suffix)
1314 parent.addMapping(key, value, comment)
1315 except Exception as e:
1316 raise ConfigFormatError("%s: %s, %r" % (self.location(), e,
1319 if tt not in [EOF, WORD, STRING, RCURLY, COMMA]:
1320 msg = "%s: expecting one of EOF, WORD, STRING, \
1321 RCURLY, COMMA, found %r"
1322 raise ConfigFormatError(msg % (self.location(), self.token[1]))
1324 self.token = self.getToken()
1326 def parseValue(self, parent, suffix):
1330 @param parent: The container to which the value will be added.
1331 @type parent: A L{Container} instance.
1332 @param suffix: The suffix for the value.
1336 @raise ConfigFormatError: if a syntax error is found.
1339 if tt in [STRING, WORD, NUMBER, LPAREN, DOLLAR,
1340 TRUE, FALSE, NONE, BACKTICK, MINUS]:
1341 rv = self.parseScalar()
1343 rv = self.parseSequence(parent, suffix)
1344 elif tt in [LCURLY, AT]:
1345 rv = self.parseMapping(parent, suffix)
1347 raise ConfigFormatError("%s: unexpected input: %r" %
1348 (self.location(), self.token[1]))
1351 def parseSequence(self, parent, suffix):
1355 @param parent: The container to which the sequence will be added.
1356 @type parent: A L{Container} instance.
1357 @param suffix: The suffix for the value.
1359 @return: a L{Sequence} instance representing the sequence.
1361 @raise ConfigFormatError: if a syntax error is found.
1363 rv = Sequence(parent)
1364 rv.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1366 comment = self.comment
1368 while tt in [STRING, WORD, NUMBER, LCURLY, LBRACK, LPAREN, DOLLAR,
1369 TRUE, FALSE, NONE, BACKTICK]:
1370 suffix = '[%d]' % len(rv)
1371 value = self.parseValue(parent, suffix)
1372 rv.append(value, comment)
1374 comment = self.comment
1378 comment = self.comment
1383 def parseMapping(self, parent, suffix):
1387 @param parent: The container to which the mapping will be added.
1388 @type parent: A L{Container} instance.
1389 @param suffix: The suffix for the value.
1391 @return: a L{Mapping} instance representing the mapping.
1393 @raise ConfigFormatError: if a syntax error is found.
1395 if self.token[0] == LCURLY:
1397 rv = Mapping(parent)
1399 makePath(object.__getattribute__(parent, 'path'), suffix))
1400 self.parseMappingBody(rv)
1404 _, fn = self.match(STRING)
1405 rv = Config(eval(fn), parent)
1408 def parseScalar(self):
1410 Parse a scalar - a terminal value such as a string or number, or
1411 an L{Expression} or L{Reference}.
1413 @return: the parsed scalar
1415 @raise ConfigFormatError: if a syntax error is found.
1417 lhs = self.parseTerm()
1419 while tt in [PLUS, MINUS]:
1421 rhs = self.parseTerm()
1422 lhs = Expression(tt, lhs, rhs)
1426 def parseTerm(self):
1428 Parse a term in an additive expression (a + b, a - b)
1430 @return: the parsed term
1432 @raise ConfigFormatError: if a syntax error is found.
1434 lhs = self.parseFactor()
1436 while tt in [STAR, SLASH, MOD]:
1438 rhs = self.parseFactor()
1439 lhs = Expression(tt, lhs, rhs)
1443 def parseFactor(self):
1445 Parse a factor in an multiplicative expression (a * b, a / b, a % b)
1447 @return: the parsed factor
1449 @raise ConfigFormatError: if a syntax error is found.
1452 if tt in [NUMBER, WORD, STRING, TRUE, FALSE, NONE]:
1459 rv = self.parseScalar()
1463 rv = self.parseReference(DOLLAR)
1464 elif tt == BACKTICK:
1465 self.match(BACKTICK)
1466 rv = self.parseReference(BACKTICK)
1467 self.match(BACKTICK)
1470 rv = -self.parseScalar()
1472 raise ConfigFormatError("%s: unexpected input: %r" %
1473 (self.location(), self.token[1]))
1476 def parseReference(self, type):
1480 @return: the parsed reference
1481 @rtype: L{Reference}
1482 @raise ConfigFormatError: if a syntax error is found.
1484 word = self.match(WORD)
1485 rv = Reference(self.config, type, word[1])
1486 while self.token[0] in [DOT, LBRACK2]:
1487 self.parseSuffix(rv)
1490 def parseSuffix(self, ref):
1492 Parse a reference suffix.
1494 @param ref: The reference of which this suffix is a part.
1495 @type ref: L{Reference}.
1496 @raise ConfigFormatError: if a syntax error is found.
1501 word = self.match(WORD)
1502 ref.addElement(DOT, word[1])
1506 if tt not in [NUMBER, STRING]:
1507 raise ConfigFormatError("%s: expected number or string, found %r" % (self.location(), tv))
1508 self.token = self.getToken()
1511 ref.addElement(LBRACK, tv)
1513 def defaultMergeResolve(map1, map2, key):
1515 A default resolver for merge conflicts. Returns a string
1516 indicating what action to take to resolve the conflict.
1518 @param map1: The map being merged into.
1519 @type map1: L{Mapping}.
1520 @param map2: The map being used as the merge operand.
1521 @type map2: L{Mapping}.
1522 @param key: The key in map2 (which also exists in map1).
1524 @return: One of "merge", "append", "mismatch" or "overwrite"
1525 indicating what action should be taken. This should
1526 be appropriate to the objects being merged - e.g.
1527 there is no point returning "merge" if the two objects
1528 are instances of L{Sequence}.
1533 if isinstance(obj1, Mapping) and isinstance(obj2, Mapping):
1535 elif isinstance(obj1, Sequence) and isinstance(obj2, Sequence):
1541 def overwriteMergeResolve(map1, map2, key):
1543 An overwriting resolver for merge conflicts. Calls L{defaultMergeResolve},
1544 but where a "mismatch" is detected, returns "overwrite" instead.
1546 @param map1: The map being merged into.
1547 @type map1: L{Mapping}.
1548 @param map2: The map being used as the merge operand.
1549 @type map2: L{Mapping}.
1550 @param key: The key in map2 (which also exists in map1).
1553 rv = defaultMergeResolve(map1, map2, key)
1554 if rv == "mismatch":
1558 class ConfigMerger(object):
1560 This class is used for merging two configurations. If a key exists in the
1561 merge operand but not the merge target, then the entry is copied from the
1562 merge operand to the merge target. If a key exists in both configurations,
1563 then a resolver (a callable) is called to decide how to handle the
1567 def __init__(self, resolver=defaultMergeResolve):
1569 Initialise an instance.
1572 @type resolver: A callable which takes the argument list
1573 (map1, map2, key) where map1 is the mapping being merged into,
1574 map2 is the merge operand and key is the clashing key. The callable
1575 should return a string indicating how the conflict should be resolved.
1576 For possible return values, see L{defaultMergeResolve}. The default
1577 value preserves the old behaviour
1579 self.resolver = resolver
1581 def merge(self, merged, mergee):
1583 Merge two configurations. The second configuration is unchanged,
1584 and the first is changed to reflect the results of the merge.
1586 @param merged: The configuration to merge into.
1587 @type merged: L{Config}.
1588 @param mergee: The configuration to merge.
1589 @type mergee: L{Config}.
1591 self.mergeMapping(merged, mergee)
1593 def overwriteKeys(self, map1, seq2):
1595 Renint variables. The second mapping is unchanged,
1596 and the first is changed depending the keys of the second mapping.
1597 @param map1: The mapping to reinit keys into.
1598 @type map1: L{Mapping}.
1599 @param map2: The mapping container reinit information.
1600 @type map2: L{Mapping}.
1603 overwrite_list = object.__getattribute__(seq2, 'data')
1604 for overwrite_instruction in overwrite_list:
1605 object.__setattr__(overwrite_instruction, 'parent', map1)
1606 if "__condition__" in overwrite_instruction.keys():
1607 overwrite_condition = overwrite_instruction["__condition__"]
1608 if eval(overwrite_condition, globals(), map1):
1609 for key in overwrite_instruction.keys():
1610 if key == "__condition__":
1613 exec( 'map1.' + key + " = " + repr(overwrite_instruction[key]))
1615 exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1617 for key in overwrite_instruction.keys():
1619 exec('map1.' + key + " = " + repr(overwrite_instruction[key]))
1621 exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1623 def mergeMapping(self, map1, map2):
1625 Merge two mappings recursively. The second mapping is unchanged,
1626 and the first is changed to reflect the results of the merge.
1628 @param map1: The mapping to merge into.
1629 @type map1: L{Mapping}.
1630 @param map2: The mapping to merge.
1631 @type map2: L{Mapping}.
1634 global __resolveOverwrite__
1635 for key in map2.keys():
1636 if __resolveOverwrite__ and key == "__overwrite__":
1637 self.overwriteKeys(map1,map2[key])
1639 elif key not in keys:
1640 map1[key] = map2[key]
1641 if isinstance(map1[key], Container) :
1642 object.__setattr__(map1[key], 'parent', map1)
1646 decision = self.resolver(map1, map2, key)
1647 if decision == "merge":
1648 self.mergeMapping(obj1, obj2)
1649 elif decision == "append":
1650 self.mergeSequence(obj1, obj2)
1651 elif decision == "overwrite":
1653 if isinstance(map1[key], Container):
1654 object.__setattr__(map1[key], 'parent', map1)
1655 elif decision == "mismatch":
1656 self.handleMismatch(obj1, obj2)
1658 msg = "unable to merge: don't know how to implement %r"
1659 raise ValueError(msg % decision)
1661 def mergeSequence(self, seq1, seq2):
1663 Merge two sequences. The second sequence is unchanged,
1664 and the first is changed to have the elements of the second
1667 @param seq1: The sequence to merge into.
1668 @type seq1: L{Sequence}.
1669 @param seq2: The sequence to merge.
1670 @type seq2: L{Sequence}.
1672 data1 = object.__getattribute__(seq1, 'data')
1673 data2 = object.__getattribute__(seq2, 'data')
1676 comment1 = object.__getattribute__(seq1, 'comments')
1677 comment2 = object.__getattribute__(seq2, 'comments')
1678 for obj in comment2:
1679 comment1.append(obj)
1681 def handleMismatch(self, obj1, obj2):
1683 Handle a mismatch between two objects.
1685 @param obj1: The object to merge into.
1687 @param obj2: The object to merge.
1690 raise ConfigError("unable to merge %r with %r" % (obj1, obj2))
1692 class ConfigList(list):
1694 This class implements an ordered list of configurations and allows you
1695 to try getting the configuration from each entry in turn, returning
1696 the first successfully obtained value.
1699 def getByPath(self, path):
1701 Obtain a value from the first configuration in the list which defines
1704 @param path: The path of the value to retrieve.
1706 @return: The value from the earliest configuration in the list which
1709 @raise ConfigError: If no configuration in the list has an entry with
1716 rv = entry.getByPath(path)
1722 raise ConfigError("unable to resolve %r" % path)