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.
350 makePath('', 'suffix') -> 'suffix'
351 makePath('prefix', 'suffix') -> 'prefix.suffix'
352 makePath('prefix', '[1]') -> 'prefix[1]'
354 @param prefix: The prefix to use. If it evaluates as false, the suffix is returned.
356 @param suffix: The suffix to use. It is either an identifier or an index in brackets.
358 @return: The path concatenation of prefix and suffix, with adot if the suffix is not a bracketed index.
363 elif suffix[0] == '[':
366 rv = prefix + '.' + suffix
370 class Container(object):
372 This internal class is the base class for mappings and sequences.
374 @ivar path: A string which describes how to get
375 to this instance from the root of the hierarchy.
379 a.list.of[1].or['more'].elements
381 def __init__(self, parent):
383 Initialize an instance.
385 @param parent: The parent of this instance in the hierarchy.
386 @type parent: A L{Container} instance.
388 object.__setattr__(self, 'parent', parent)
390 def setPath(self, path):
392 Set the path for this instance.
393 @param path: The path - a string which describes how to get
394 to this instance from the root of the hierarchy.
397 object.__setattr__(self, 'path', path)
399 def evaluate(self, item):
401 Evaluate items which are instances of L{Reference} or L{Expression}.
403 L{Reference} instances are evaluated using L{Reference.resolve},
404 and L{Expression} instances are evaluated using
405 L{Expression.evaluate}.
407 @param item: The item to be evaluated.
409 @return: If the item is an instance of L{Reference} or L{Expression},
410 the evaluated value is returned, otherwise the item is returned
413 if isinstance(item, Reference):
414 item = item.resolve(self)
415 elif isinstance(item, Expression):
416 item = item.evaluate(self)
419 def writeToStream(self, stream, indent, container):
421 Write this instance to a stream at the specified indentation level.
423 Should be redefined in subclasses.
425 @param stream: The stream to write to
426 @type stream: A writable stream (file-like object)
427 @param indent: The indentation level
429 @param container: The container of this instance
430 @type container: L{Container}
431 @raise NotImplementedError: If a subclass does not override this
433 raise NotImplementedError
435 def writeValue(self, value, stream, indent):
436 if isinstance(self, Mapping):
439 indstr = indent * ' '
440 if isinstance(value, Reference) or isinstance(value, Expression):
441 stream.write('%s%r%s' % (indstr, value, NEWLINE))
443 if isinstance(value, str): # and not isWord(value):
445 stream.write('%s%s%s' % (indstr, value, NEWLINE))
447 class Mapping(Container):
449 This internal class implements key-value mappings in configurations.
452 def __init__(self, parent=None):
454 Initialize an instance.
456 @param parent: The parent of this instance in the hierarchy.
457 @type parent: A L{Container} instance.
459 Container.__init__(self, parent)
460 object.__setattr__(self, 'path', '')
461 object.__setattr__(self, 'data', {})
462 object.__setattr__(self, 'order', []) # to preserve ordering
463 object.__setattr__(self, 'comments', {})
465 def __delitem__(self, key):
469 data = object.__getattribute__(self, 'data')
471 raise AttributeError(key)
472 order = object.__getattribute__(self, 'order')
473 comments = object.__getattribute__(self, 'comments')
478 def __getitem__(self, key):
479 data = object.__getattribute__(self, 'data')
481 raise AttributeError(key)
483 return self.evaluate(rv)
485 __getattr__ = __getitem__
488 def __getattribute__(self, name):
489 if name == "__dict__":
491 if name in ["__methods__", "__members__"]:
493 #if name == "__class__":
495 data = object.__getattribute__(self, "data")
496 useData = data.has_key(name)
498 rv = getattr(data, name)
500 rv = object.__getattribute__(self, name)
502 raise AttributeError(name)
507 for key in self.keys():
508 yield(key, self[key])
511 def __contains__(self, item):
512 order = object.__getattribute__(self, 'order')
515 def addMapping(self, key, value, comment, setting=False):
517 Add a key-value mapping with a comment.
519 @param key: The key for the mapping.
521 @param value: The value for the mapping.
523 @param comment: The comment for the key (can be None).
525 @param setting: If True, ignore clashes. This is set
526 to true when called from L{__setattr__}.
527 @raise ConfigFormatError: If an existing key is seen
528 again and setting is False.
530 data = object.__getattribute__(self, 'data')
531 order = object.__getattribute__(self, 'order')
532 comments = object.__getattribute__(self, 'comments')
538 raise ConfigFormatError("repeated key: %s" % key)
539 comments[key] = comment
541 def __setattr__(self, name, value):
542 self.addMapping(name, value, None, True)
544 __setitem__ = __setattr__
548 Return the keys in a similar way to a dictionary.
550 return object.__getattribute__(self, 'order')
552 def get(self, key, default=None):
554 Allows a dictionary-style get operation.
561 return str(object.__getattribute__(self, 'data'))
564 return repr(object.__getattribute__(self, 'data'))
567 return len(object.__getattribute__(self, 'order'))
570 return self.iterkeys()
573 order = object.__getattribute__(self, 'order')
574 return order.__iter__()
576 def writeToStream(self, stream, indent, container):
578 Write this instance to a stream at the specified indentation level.
580 Should be redefined in subclasses.
582 @param stream: The stream to write to
583 @type stream: A writable stream (file-like object)
584 @param indent: The indentation level
586 @param container: The container of this instance
587 @type container: L{Container}
589 indstr = indent * ' '
591 stream.write(' { }%s' % NEWLINE)
593 if isinstance(container, Mapping):
594 stream.write(NEWLINE)
595 stream.write('%s{%s' % (indstr, NEWLINE))
596 self.__save__(stream, indent + 1)
597 stream.write('%s}%s' % (indstr, NEWLINE))
599 def __save__(self, stream, indent=0):
601 Save this configuration to the specified stream.
602 @param stream: A stream to which the configuration is written.
603 @type stream: A write-only stream (file-like object).
604 @param indent: The indentation level for the output.
607 indstr = indent * ' '
608 order = object.__getattribute__(self, 'order')
609 data = object.__getattribute__(self, 'data')
610 maxlen = 0 # max(map(lambda x: len(x), order))
612 comment = self.comments[key]
618 stream.write('%s#%s' % (indstr, comment))
619 if skey.startswith("u'"):
621 stream.write('%s%-*s :' % (indstr, maxlen, skey))
623 if isinstance(value, Container):
624 value.writeToStream(stream, indent, self)
626 self.writeValue(value, stream, indent)
628 class Config(Mapping):
630 This class represents a configuration, and is the only one which clients
631 need to interface to, under normal circumstances.
634 class Namespace(object):
636 This internal class is used for implementing default namespaces.
638 An instance acts as a namespace.
644 def __init__(self, streamOrFile=None, parent=None, PWD = None):
646 Initializes an instance.
648 @param streamOrFile: If specified, causes this instance to be loaded
649 from the stream (by calling L{load}). If a string is provided, it is
650 passed to L{streamOpener} to open a stream. Otherwise, the passed
651 value is assumed to be a stream and used as is.
652 @type streamOrFile: A readable stream (file-like object) or a name.
653 @param parent: If specified, this becomes the parent of this instance
654 in the configuration hierarchy.
655 @type parent: a L{Container} instance.
657 try: # Python 3 compatibility
658 if isinstance(streamOrFile, unicode):
659 streamOrFile = streamOrFile.encode()
662 Mapping.__init__(self, parent)
663 object.__setattr__(self, 'reader', ConfigReader(self))
664 object.__setattr__(self, 'namespaces', [Config.Namespace()])
665 if streamOrFile is not None:
666 if isinstance(streamOrFile, str) or isinstance(streamOrFile, bytes):
668 if streamOpener is None:
669 streamOpener = defaultStreamOpener
670 streamOrFile = streamOpener(streamOrFile)
671 load = object.__getattribute__(self, "load")
673 # Specific add for salomeTools : PWD
681 def load(self, stream):
683 Load the configuration from the specified stream. Multiple streams can
684 be used to populate the same instance, as long as there are no
685 clashing keys. The stream is closed.
686 @param stream: A stream from which the configuration is read.
687 @type stream: A read-only stream (file-like object).
688 @raise ConfigError: if keys in the loaded configuration clash with
690 @raise ConfigFormatError: if there is a syntax error in the stream.
692 reader = object.__getattribute__(self, 'reader')
696 def addNamespace(self, ns, name=None):
698 Add a namespace to this configuration which can be used to evaluate
699 (resolve) dotted-identifier expressions.
700 @param ns: The namespace to be added.
701 @type ns: A module or other namespace suitable for passing as an
703 @param name: A name for the namespace, which, if specified, provides
704 an additional level of indirection.
707 namespaces = object.__getattribute__(self, 'namespaces')
709 namespaces.append(ns)
711 setattr(namespaces[0], name, ns)
713 def removeNamespace(self, ns, name=None):
715 Remove a namespace added with L{addNamespace}.
716 @param ns: The namespace to be removed.
717 @param name: The name which was specified when L{addNamespace} was
721 namespaces = object.__getattribute__(self, 'namespaces')
723 namespaces.remove(ns)
725 delattr(namespaces[0], name)
727 def __save__(self, stream, indent=0, no_close=False):
729 Save this configuration to the specified stream. The stream is
730 closed if this is the top-level configuration in the hierarchy.
731 L{Mapping.__save__} is called to do all the work.
732 @param stream: A stream to which the configuration is written.
733 @type stream: A write-only stream (file-like object).
734 @param indent: The indentation level for the output.
737 Mapping.__save__(self, stream, indent)
738 if indent == 0 and not no_close:
741 def getByPath(self, path):
743 Obtain a value in the configuration via its path.
744 @param path: The path of the required value
746 @return the value at the specified path.
748 @raise ConfigError: If the path is invalid
753 except Exception as e:
754 raise ConfigError(str(e))
756 class Sequence(Container):
758 This internal class implements a value which is a sequence of other values.
760 class SeqIter(object):
762 This internal class implements an iterator for a L{Sequence} instance.
764 def __init__(self, seq):
766 self.limit = len(object.__getattribute__(seq, 'data'))
773 if self.index >= self.limit:
775 rv = self.seq[self.index]
779 # This method is for python3 compatibility
781 if self.index >= self.limit:
783 rv = self.seq[self.index]
787 def __init__(self, parent=None):
789 Initialize an instance.
791 @param parent: The parent of this instance in the hierarchy.
792 @type parent: A L{Container} instance.
794 Container.__init__(self, parent)
795 object.__setattr__(self, 'data', [])
796 object.__setattr__(self, 'comments', [])
798 def append(self, item, comment):
800 Add an item to the sequence.
802 @param item: The item to add.
804 @param comment: A comment for the item.
807 data = object.__getattribute__(self, 'data')
808 comments = object.__getattribute__(self, 'comments')
810 comments.append(comment)
812 def __getitem__(self, index):
813 data = object.__getattribute__(self, 'data')
816 except (IndexError, KeyError, TypeError):
817 raise ConfigResolutionError('%r is not a valid index for %r' % (index, object.__getattribute__(self, 'path')))
818 if not isinstance(rv, list):
819 rv = self.evaluate(rv)
824 result.append(self.evaluate(a))
829 return Sequence.SeqIter(self)
832 return repr(object.__getattribute__(self, 'data'))
835 return str(self[:]) # using the slice evaluates the contents
838 return len(object.__getattribute__(self, 'data'))
840 def writeToStream(self, stream, indent, container):
842 Write this instance to a stream at the specified indentation level.
844 Should be redefined in subclasses.
846 @param stream: The stream to write to
847 @type stream: A writable stream (file-like object)
848 @param indent: The indentation level
850 @param container: The container of this instance
851 @type container: L{Container}
853 indstr = indent * ' '
855 stream.write(' [ ]%s' % NEWLINE)
857 if isinstance(container, Mapping):
858 stream.write(NEWLINE)
859 stream.write('%s[%s' % (indstr, NEWLINE))
860 self.__save__(stream, indent + 1)
861 stream.write('%s]%s' % (indstr, NEWLINE))
863 def __save__(self, stream, indent):
865 Save this instance to the specified stream.
866 @param stream: A stream to which the configuration is written.
867 @type stream: A write-only stream (file-like object).
868 @param indent: The indentation level for the output, > 0
872 raise ConfigError("sequence cannot be saved as a top-level item")
873 data = object.__getattribute__(self, 'data')
874 comments = object.__getattribute__(self, 'comments')
875 indstr = indent * ' '
876 for i in range(0, len(data)):
878 comment = comments[i]
880 stream.write('%s#%s' % (indstr, comment))
881 if isinstance(value, Container):
882 value.writeToStream(stream, indent, self)
884 self.writeValue(value, stream, indent)
886 class Reference(object):
888 This internal class implements a value which is a reference to another value.
890 def __init__(self, config, type, ident):
892 Initialize an instance.
894 @param config: The configuration which contains this reference.
895 @type config: A L{Config} instance.
896 @param type: The type of reference.
897 @type type: BACKTICK or DOLLAR
898 @param ident: The identifier which starts the reference.
903 self.elements = [ident]
905 def addElement(self, type, ident):
907 Add an element to the reference.
909 @param type: The type of reference.
910 @type type: BACKTICK or DOLLAR
911 @param ident: The identifier which continues the reference.
914 self.elements.append((type, ident))
916 def findConfig(self, container):
918 Find the closest enclosing configuration to the specified container.
920 @param container: The container to start from.
921 @type container: L{Container}
922 @return: The closest enclosing configuration, or None.
925 while (container is not None) and not isinstance(container, Config):
926 container = object.__getattribute__(container, 'parent')
929 def resolve(self, container):
931 Resolve this instance in the context of a container.
933 @param container: The container to resolve from.
934 @type container: L{Container}
935 @return: The resolved value.
937 @raise ConfigResolutionError: If resolution fails.
940 path = object.__getattribute__(container, 'path')
942 while current is not None:
943 if self.type == BACKTICK:
944 namespaces = object.__getattribute__(current, 'namespaces')
946 for ns in namespaces:
948 rv = eval(str(self)[1:-1], vars(ns))
956 key = self.elements[0]
959 for item in self.elements[1:]:
966 current = object.__getattribute__(current, 'parent')
968 raise ConfigResolutionError("unable to evaluate %r in the configuration %s" % (self, path))
973 for tt, tv in self.elements[1:]:
978 if self.type == BACKTICK:
979 return BACKTICK + s + BACKTICK
984 return self.__str__()
986 class Expression(object):
988 This internal class implements a value which is obtained by evaluating an expression.
990 def __init__(self, op, lhs, rhs):
992 Initialize an instance.
994 @param op: the operation expressed in the expression.
995 @type op: PLUS, MINUS, STAR, SLASH, MOD
996 @param lhs: the left-hand-side operand of the expression.
997 @type lhs: any Expression or primary value.
998 @param rhs: the right-hand-side operand of the expression.
999 @type rhs: any Expression or primary value.
1006 return '%r %s %r' % (self.lhs, self.op, self.rhs)
1009 return self.__str__()
1011 def evaluate(self, container):
1013 Evaluate this instance in the context of a container.
1015 @param container: The container to evaluate in from.
1016 @type container: L{Container}
1017 @return: The evaluated value.
1019 @raise ConfigResolutionError: If evaluation fails.
1020 @raise ZeroDivideError: If division by zero occurs.
1021 @raise TypeError: If the operation is invalid, e.g.
1022 subtracting one string from another.
1025 if isinstance(lhs, Reference):
1026 lhs = lhs.resolve(container)
1027 elif isinstance(lhs, Expression):
1028 lhs = lhs.evaluate(container)
1030 if isinstance(rhs, Reference):
1031 rhs = rhs.resolve(container)
1032 elif isinstance(rhs, Expression):
1033 rhs = rhs.evaluate(container)
1047 class ConfigReader(object):
1049 This internal class implements a parser for configurations.
1052 def __init__(self, config):
1053 self.filename = None
1054 self.config = config
1058 self.last_token = None
1059 self.commentchars = '#'
1060 self.whitespace = ' \t\r\n'
1062 self.punct = ':-+*/%,.{}[]()@`$'
1063 self.digits = '0123456789'
1064 self.wordchars = '%s' % WORDCHARS # make a copy
1065 self.identchars = self.wordchars + self.digits
1072 Return the current location (filename, line, column) in the stream
1075 Used when printing error messages,
1077 @return: A string representing a location in the stream being read.
1080 return "%s(%d,%d)" % (self.filename, self.lineno, self.colno)
1084 Get the next char from the stream. Update line and column numbers
1087 @return: The next character from the stream.
1091 c = self.pbchars.pop()
1092 if isinstance(c,bytes):
1095 c = self.stream.read(1)
1096 if isinstance(c,bytes):
1105 return "<ConfigReader at 0x%08x>" % id(self)
1111 Get a token from the stream. String values are returned in a form
1112 where you need to eval() the returned value to get the actual
1113 string. The return value is (token_type, token_value).
1115 Multiline string tokenizing is thanks to David Janes (BlogMatrix)
1117 @return: The next token.
1118 @rtype: A token tuple.
1121 return self.pbtokens.pop()
1122 stream = self.stream
1132 self.comment += '#' + stream.readline()
1134 self.comment = stream.readline()
1137 if c in self.quotes:
1151 self.pbchars.append(c2)
1152 self.pbchars.append(c1)
1154 self.pbchars.append(c1)
1160 if (c == quote) and not escaped:
1161 if not multiline or (len(token) >= 6 and token.endswith(token[:3]) and token[-4] != '\\'):
1164 escaped = not escaped
1168 raise ConfigFormatError('%s: Unterminated quoted string: %r, %r' % (self.location(), token, c))
1170 if c in self.whitespace:
1173 elif c in self.punct:
1176 if (self.lastc == ']') or (self.lastc in self.identchars):
1182 elif c in self.digits:
1189 if c in self.digits:
1191 elif (c == '.') and token.find('.') < 0:
1194 if c and (c not in self.whitespace):
1195 self.pbchars.append(c)
1198 elif c in self.wordchars:
1202 while c and (c in self.identchars):
1205 if c: # and c not in self.whitespace:
1206 self.pbchars.append(c)
1209 elif token == "False":
1211 elif token == "None":
1215 raise ConfigFormatError('%s: Unexpected character: %r' % (self.location(), c))
1217 self.lastc = token[-1]
1220 self.last_token = tt
1222 # Python 2.x specific unicode conversion
1223 if sys.version_info[0] == 2 and tt == WORD and isinstance(token, unicode):
1224 token = token.encode('ascii')
1227 def load(self, stream, parent=None, suffix=None):
1229 Load the configuration from the specified stream.
1231 @param stream: A stream from which to load the configuration.
1232 @type stream: A stream (file-like object).
1233 @param parent: The parent of the configuration (to which this reader
1234 belongs) in the hierarchy. Specified when the configuration is
1235 included in another one.
1236 @type parent: A L{Container} instance.
1237 @param suffix: The suffix of this configuration in the parent
1238 configuration. Should be specified whenever the parent is not None.
1239 @raise ConfigError: If parent is specified but suffix is not.
1240 @raise ConfigFormatError: If there are syntax errors in the stream.
1242 if parent is not None:
1244 raise ConfigError("internal error: load called with parent but no suffix")
1245 self.config.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1246 self.setStream(stream)
1247 self.token = self.getToken()
1248 self.parseMappingBody(self.config)
1249 if self.token[0] != EOF:
1250 raise ConfigFormatError('%s: expecting EOF, found %r' % (self.location(), self.token[1]))
1252 def setStream(self, stream):
1254 Set the stream to the specified value, and prepare to read from it.
1256 @param stream: A stream from which to load the configuration.
1257 @type stream: A stream (file-like object).
1259 self.stream = stream
1260 if hasattr(stream, 'name'):
1261 filename = stream.name
1264 self.filename = filename
1270 Ensure that the current token type matches the specified value, and
1271 advance to the next token.
1273 @param t: The token type to match.
1274 @type t: A valid token type.
1275 @return: The token which was last read from the stream before this
1277 @rtype: a token tuple - see L{getToken}.
1278 @raise ConfigFormatError: If the token does not match what's expected.
1280 if self.token[0] != t:
1281 raise ConfigFormatError("%s: expecting %s, found %r" % (self.location(), t, self.token[1]))
1283 self.token = self.getToken()
1286 def parseMappingBody(self, parent):
1288 Parse the internals of a mapping, and add entries to the provided
1291 @param parent: The mapping to add entries to.
1292 @type parent: A L{Mapping} instance.
1294 while self.token[0] in [WORD, STRING]:
1295 self.parseKeyValuePair(parent)
1297 def parseKeyValuePair(self, parent):
1299 Parse a key-value pair, and add it to the provided L{Mapping}.
1301 @param parent: The mapping to add entries to.
1302 @type parent: A L{Mapping} instance.
1303 @raise ConfigFormatError: if a syntax error is found.
1305 comment = self.comment
1312 suffix = '[%s]' % tv
1314 msg = "%s: expecting word or string, found %r"
1315 raise ConfigFormatError(msg % (self.location(), tv))
1316 self.token = self.getToken()
1317 # for now, we allow key on its own as a short form of key : True
1318 if self.token[0] == COLON:
1319 self.token = self.getToken()
1320 value = self.parseValue(parent, suffix)
1324 parent.addMapping(key, value, comment)
1325 except Exception as e:
1326 raise ConfigFormatError("%s: %s, %r" % (self.location(), e,
1329 if tt not in [EOF, WORD, STRING, RCURLY, COMMA]:
1330 msg = "%s: expecting one of EOF, WORD, STRING, \
1331 RCURLY, COMMA, found %r"
1332 raise ConfigFormatError(msg % (self.location(), self.token[1]))
1334 self.token = self.getToken()
1336 def parseValue(self, parent, suffix):
1340 @param parent: The container to which the value will be added.
1341 @type parent: A L{Container} instance.
1342 @param suffix: The suffix for the value.
1346 @raise ConfigFormatError: if a syntax error is found.
1349 if tt in [STRING, WORD, NUMBER, LPAREN, DOLLAR,
1350 TRUE, FALSE, NONE, BACKTICK, MINUS]:
1351 rv = self.parseScalar()
1353 rv = self.parseSequence(parent, suffix)
1354 elif tt in [LCURLY, AT]:
1355 rv = self.parseMapping(parent, suffix)
1357 raise ConfigFormatError("%s: unexpected input: %r" %
1358 (self.location(), self.token[1]))
1361 def parseSequence(self, parent, suffix):
1365 @param parent: The container to which the sequence will be added.
1366 @type parent: A L{Container} instance.
1367 @param suffix: The suffix for the value.
1369 @return: a L{Sequence} instance representing the sequence.
1371 @raise ConfigFormatError: if a syntax error is found.
1373 rv = Sequence(parent)
1374 rv.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1376 comment = self.comment
1378 while tt in [STRING, WORD, NUMBER, LCURLY, LBRACK, LPAREN, DOLLAR,
1379 TRUE, FALSE, NONE, BACKTICK]:
1380 suffix = '[%d]' % len(rv)
1381 value = self.parseValue(parent, suffix)
1382 rv.append(value, comment)
1384 comment = self.comment
1388 comment = self.comment
1393 def parseMapping(self, parent, suffix):
1397 @param parent: The container to which the mapping will be added.
1398 @type parent: A L{Container} instance.
1399 @param suffix: The suffix for the value.
1401 @return: a L{Mapping} instance representing the mapping.
1403 @raise ConfigFormatError: if a syntax error is found.
1405 if self.token[0] == LCURLY:
1407 rv = Mapping(parent)
1409 makePath(object.__getattribute__(parent, 'path'), suffix))
1410 self.parseMappingBody(rv)
1414 _, fn = self.match(STRING)
1415 rv = Config(eval(fn), parent)
1418 def parseScalar(self):
1420 Parse a scalar - a terminal value such as a string or number, or
1421 an L{Expression} or L{Reference}.
1423 @return: the parsed scalar
1425 @raise ConfigFormatError: if a syntax error is found.
1427 lhs = self.parseTerm()
1429 while tt in [PLUS, MINUS]:
1431 rhs = self.parseTerm()
1432 lhs = Expression(tt, lhs, rhs)
1436 def parseTerm(self):
1438 Parse a term in an additive expression (a + b, a - b)
1440 @return: the parsed term
1442 @raise ConfigFormatError: if a syntax error is found.
1444 lhs = self.parseFactor()
1446 while tt in [STAR, SLASH, MOD]:
1448 rhs = self.parseFactor()
1449 lhs = Expression(tt, lhs, rhs)
1453 def parseFactor(self):
1455 Parse a factor in an multiplicative expression (a * b, a / b, a % b)
1457 @return: the parsed factor
1459 @raise ConfigFormatError: if a syntax error is found.
1462 if tt in [NUMBER, WORD, STRING, TRUE, FALSE, NONE]:
1469 rv = self.parseScalar()
1473 rv = self.parseReference(DOLLAR)
1474 elif tt == BACKTICK:
1475 self.match(BACKTICK)
1476 rv = self.parseReference(BACKTICK)
1477 self.match(BACKTICK)
1480 rv = -self.parseScalar()
1482 raise ConfigFormatError("%s: unexpected input: %r" %
1483 (self.location(), self.token[1]))
1486 def parseReference(self, type):
1490 @return: the parsed reference
1491 @rtype: L{Reference}
1492 @raise ConfigFormatError: if a syntax error is found.
1494 word = self.match(WORD)
1495 rv = Reference(self.config, type, word[1])
1496 while self.token[0] in [DOT, LBRACK2]:
1497 self.parseSuffix(rv)
1500 def parseSuffix(self, ref):
1502 Parse a reference suffix.
1504 @param ref: The reference of which this suffix is a part.
1505 @type ref: L{Reference}.
1506 @raise ConfigFormatError: if a syntax error is found.
1511 word = self.match(WORD)
1512 ref.addElement(DOT, word[1])
1516 if tt not in [NUMBER, STRING]:
1517 raise ConfigFormatError("%s: expected number or string, found %r" % (self.location(), tv))
1518 self.token = self.getToken()
1521 ref.addElement(LBRACK, tv)
1523 def defaultMergeResolve(map1, map2, key):
1525 A default resolver for merge conflicts.
1526 Returns a string indicating what action to take to resolve the conflict.
1528 @param map1: The map being merged into.
1529 @type map1: L{Mapping}.
1530 @param map2: The map being used as the merge operand.
1531 @type map2: L{Mapping}.
1532 @param key: The key in map2 (which also exists in map1).
1535 @return: One of "merge", "append", "mismatch" or "overwrite"
1536 indicating what action should be taken. This should
1537 be appropriate to the objects being merged - e.g.
1538 there is no point returning "merge" if the two objects
1539 are instances of L{Sequence}.
1545 if isinstance(obj1, Mapping) and isinstance(obj2, Mapping):
1547 elif isinstance(obj1, Sequence) and isinstance(obj2, Sequence):
1553 def overwriteMergeResolve(map1, map2, key):
1555 An overwriting resolver for merge conflicts. Calls L{defaultMergeResolve},
1556 but where a "mismatch" is detected, returns "overwrite" instead.
1558 @param map1: The map being merged into.
1559 @type map1: L{Mapping}.
1560 @param map2: The map being used as the merge operand.
1561 @type map2: L{Mapping}.
1562 @param key: The key in map2 (which also exists in map1).
1565 rv = defaultMergeResolve(map1, map2, key)
1566 if rv == "mismatch":
1570 def deepCopyMapping(inMapping):
1572 for element in inMapping:
1573 res[element] = inMapping[element]
1576 class ConfigMerger(object):
1578 This class is used for merging two configurations. If a key exists in the
1579 merge operand but not the merge target, then the entry is copied from the
1580 merge operand to the merge target. If a key exists in both configurations,
1581 then a resolver (a callable) is called to decide how to handle the
1585 def __init__(self, resolver=defaultMergeResolve):
1587 Initialise an instance.
1590 @type resolver: A callable which takes the argument list
1591 (map1, map2, key) where map1 is the mapping being merged into,
1592 map2 is the merge operand and key is the clashing key. The callable
1593 should return a string indicating how the conflict should be resolved.
1594 For possible return values, see L{defaultMergeResolve}. The default
1595 value preserves the old behaviour
1597 self.resolver = resolver
1599 def merge(self, merged, mergee):
1601 Merge two configurations. The second configuration is unchanged,
1602 and the first is changed to reflect the results of the merge.
1604 @param merged: The configuration to merge into.
1605 @type merged: L{Config}.
1606 @param mergee: The configuration to merge.
1607 @type mergee: L{Config}.
1609 self.mergeMapping(merged, mergee)
1611 def overwriteKeys(self, map1, seq2):
1613 Renint variables. The second mapping is unchanged,
1614 and the first is changed depending the keys of the second mapping.
1615 @param map1: The mapping to reinit keys into.
1616 @type map1: L{Mapping}.
1617 @param map2: The mapping container reinit information.
1618 @type map2: L{Mapping}.
1621 overwrite_list = object.__getattribute__(seq2, 'data')
1622 for overwrite_instruction in overwrite_list:
1623 object.__setattr__(overwrite_instruction, 'parent', map1)
1624 if "__condition__" in overwrite_instruction.keys():
1625 overwrite_condition = overwrite_instruction["__condition__"]
1626 if eval(overwrite_condition, globals(), map1):
1627 for key in overwrite_instruction.keys():
1628 if key == "__condition__":
1631 exec( 'map1.' + key + " = " + repr(overwrite_instruction[key]))
1633 exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1635 for key in overwrite_instruction.keys():
1637 exec('map1.' + key + " = " + repr(overwrite_instruction[key]))
1639 exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1641 def mergeMapping(self, map1, map2):
1643 Merge two mappings recursively. The second mapping is unchanged,
1644 and the first is changed to reflect the results of the merge.
1646 @param map1: The mapping to merge into.
1647 @type map1: L{Mapping}.
1648 @param map2: The mapping to merge.
1649 @type map2: L{Mapping}.
1652 global __resolveOverwrite__
1653 for key in map2.keys():
1654 if __resolveOverwrite__ and key == "__overwrite__":
1655 self.overwriteKeys(map1,map2[key])
1657 elif key not in keys:
1658 map1[key] = map2[key]
1659 if isinstance(map1[key], Container) :
1660 object.__setattr__(map1[key], 'parent', map1)
1664 decision = self.resolver(map1, map2, key)
1665 if decision == "merge":
1666 self.mergeMapping(obj1, obj2)
1667 elif decision == "append":
1668 self.mergeSequence(obj1, obj2)
1669 elif decision == "overwrite":
1671 if isinstance(map1[key], Container):
1672 object.__setattr__(map1[key], 'parent', map1)
1673 elif decision == "mismatch":
1674 self.handleMismatch(obj1, obj2)
1676 msg = "unable to merge: don't know how to implement %r"
1677 raise ValueError(msg % decision)
1679 def mergeSequence(self, seq1, seq2):
1681 Merge two sequences. The second sequence is unchanged,
1682 and the first is changed to have the elements of the second
1685 @param seq1: The sequence to merge into.
1686 @type seq1: L{Sequence}.
1687 @param seq2: The sequence to merge.
1688 @type seq2: L{Sequence}.
1690 data1 = object.__getattribute__(seq1, 'data')
1691 data2 = object.__getattribute__(seq2, 'data')
1694 comment1 = object.__getattribute__(seq1, 'comments')
1695 comment2 = object.__getattribute__(seq2, 'comments')
1696 for obj in comment2:
1697 comment1.append(obj)
1699 def handleMismatch(self, obj1, obj2):
1701 Handle a mismatch between two objects.
1703 @param obj1: The object to merge into.
1705 @param obj2: The object to merge.
1708 raise ConfigError("unable to merge %r with %r" % (obj1, obj2))
1710 class ConfigList(list):
1712 This class implements an ordered list of configurations and allows you
1713 to try getting the configuration from each entry in turn, returning
1714 the first successfully obtained value.
1717 def getByPath(self, path):
1719 Obtain a value from the first configuration in the list which defines
1722 @param path: The path of the value to retrieve.
1724 @return: The value from the earliest configuration in the list which
1727 @raise ConfigError: If no configuration in the list has an entry with
1734 rv = entry.getByPath(path)
1740 raise ConfigError("unable to resolve %r" % path)