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 if skey.startswith("u'"):
626 stream.write('%s%-*s :' % (indstr, maxlen, skey))
628 if isinstance(value, Container):
629 value.writeToStream(stream, indent, self)
631 self.writeValue(value, stream, indent)
633 class Config(Mapping):
635 This class represents a configuration, and is the only one which clients
636 need to interface to, under normal circumstances.
639 class Namespace(object):
641 This internal class is used for implementing default namespaces.
643 An instance acts as a namespace.
649 def __init__(self, streamOrFile=None, parent=None, PWD = None):
651 Initializes an instance.
653 @param streamOrFile: If specified, causes this instance to be loaded
654 from the stream (by calling L{load}). If a string is provided, it is
655 passed to L{streamOpener} to open a stream. Otherwise, the passed
656 value is assumed to be a stream and used as is.
657 @type streamOrFile: A readable stream (file-like object) or a name.
658 @param parent: If specified, this becomes the parent of this instance
659 in the configuration hierarchy.
660 @type parent: a L{Container} instance.
662 try: # Python 3 compatibility
663 if isinstance(streamOrFile, unicode):
664 streamOrFile = streamOrFile.encode()
667 Mapping.__init__(self, parent)
668 object.__setattr__(self, 'reader', ConfigReader(self))
669 object.__setattr__(self, 'namespaces', [Config.Namespace()])
670 if streamOrFile is not None:
671 if isinstance(streamOrFile, str) or isinstance(streamOrFile, bytes):
673 if streamOpener is None:
674 streamOpener = defaultStreamOpener
675 streamOrFile = streamOpener(streamOrFile)
676 load = object.__getattribute__(self, "load")
678 # Specific add for salomeTools : PWD
686 def load(self, stream):
688 Load the configuration from the specified stream. Multiple streams can
689 be used to populate the same instance, as long as there are no
690 clashing keys. The stream is closed.
691 @param stream: A stream from which the configuration is read.
692 @type stream: A read-only stream (file-like object).
693 @raise ConfigError: if keys in the loaded configuration clash with
695 @raise ConfigFormatError: if there is a syntax error in the stream.
697 reader = object.__getattribute__(self, 'reader')
701 def addNamespace(self, ns, name=None):
703 Add a namespace to this configuration which can be used to evaluate
704 (resolve) dotted-identifier expressions.
705 @param ns: The namespace to be added.
706 @type ns: A module or other namespace suitable for passing as an
708 @param name: A name for the namespace, which, if specified, provides
709 an additional level of indirection.
712 namespaces = object.__getattribute__(self, 'namespaces')
714 namespaces.append(ns)
716 setattr(namespaces[0], name, ns)
718 def removeNamespace(self, ns, name=None):
720 Remove a namespace added with L{addNamespace}.
721 @param ns: The namespace to be removed.
722 @param name: The name which was specified when L{addNamespace} was
726 namespaces = object.__getattribute__(self, 'namespaces')
728 namespaces.remove(ns)
730 delattr(namespaces[0], name)
732 def __save__(self, stream, indent=0, no_close=False):
734 Save this configuration to the specified stream. The stream is
735 closed if this is the top-level configuration in the hierarchy.
736 L{Mapping.__save__} is called to do all the work.
737 @param stream: A stream to which the configuration is written.
738 @type stream: A write-only stream (file-like object).
739 @param indent: The indentation level for the output.
742 Mapping.__save__(self, stream, indent)
743 if indent == 0 and not no_close:
746 def getByPath(self, path):
748 Obtain a value in the configuration via its path.
749 @param path: The path of the required value
751 @return the value at the specified path.
753 @raise ConfigError: If the path is invalid
758 except Exception as e:
759 raise ConfigError(str(e))
761 class Sequence(Container):
763 This internal class implements a value which is a sequence of other values.
765 class SeqIter(object):
767 This internal class implements an iterator for a L{Sequence} instance.
769 def __init__(self, seq):
771 self.limit = len(object.__getattribute__(seq, 'data'))
778 if self.index >= self.limit:
780 rv = self.seq[self.index]
784 # This method is for python3 compatibility
786 if self.index >= self.limit:
788 rv = self.seq[self.index]
792 def __init__(self, parent=None):
794 Initialize an instance.
796 @param parent: The parent of this instance in the hierarchy.
797 @type parent: A L{Container} instance.
799 Container.__init__(self, parent)
800 object.__setattr__(self, 'data', [])
801 object.__setattr__(self, 'comments', [])
803 def append(self, item, comment):
805 Add an item to the sequence.
807 @param item: The item to add.
809 @param comment: A comment for the item.
812 data = object.__getattribute__(self, 'data')
813 comments = object.__getattribute__(self, 'comments')
815 comments.append(comment)
817 def __getitem__(self, index):
818 data = object.__getattribute__(self, 'data')
821 except (IndexError, KeyError, TypeError):
822 raise ConfigResolutionError('%r is not a valid index for %r' % (index, object.__getattribute__(self, 'path')))
823 if not isinstance(rv, list):
824 rv = self.evaluate(rv)
829 result.append(self.evaluate(a))
834 return Sequence.SeqIter(self)
837 return repr(object.__getattribute__(self, 'data'))
840 return str(self[:]) # using the slice evaluates the contents
843 return len(object.__getattribute__(self, 'data'))
845 def writeToStream(self, stream, indent, container):
847 Write this instance to a stream at the specified indentation level.
849 Should be redefined in subclasses.
851 @param stream: The stream to write to
852 @type stream: A writable stream (file-like object)
853 @param indent: The indentation level
855 @param container: The container of this instance
856 @type container: L{Container}
858 indstr = indent * ' '
860 stream.write(' [ ]%s' % NEWLINE)
862 if isinstance(container, Mapping):
863 stream.write(NEWLINE)
864 stream.write('%s[%s' % (indstr, NEWLINE))
865 self.__save__(stream, indent + 1)
866 stream.write('%s]%s' % (indstr, NEWLINE))
868 def __save__(self, stream, indent):
870 Save this instance to the specified stream.
871 @param stream: A stream to which the configuration is written.
872 @type stream: A write-only stream (file-like object).
873 @param indent: The indentation level for the output, > 0
877 raise ConfigError("sequence cannot be saved as a top-level item")
878 data = object.__getattribute__(self, 'data')
879 comments = object.__getattribute__(self, 'comments')
880 indstr = indent * ' '
881 for i in range(0, len(data)):
883 comment = comments[i]
885 stream.write('%s#%s' % (indstr, comment))
886 if isinstance(value, Container):
887 value.writeToStream(stream, indent, self)
889 self.writeValue(value, stream, indent)
891 class Reference(object):
893 This internal class implements a value which is a reference to another value.
895 def __init__(self, config, type, ident):
897 Initialize an instance.
899 @param config: The configuration which contains this reference.
900 @type config: A L{Config} instance.
901 @param type: The type of reference.
902 @type type: BACKTICK or DOLLAR
903 @param ident: The identifier which starts the reference.
908 self.elements = [ident]
910 def addElement(self, type, ident):
912 Add an element to the reference.
914 @param type: The type of reference.
915 @type type: BACKTICK or DOLLAR
916 @param ident: The identifier which continues the reference.
919 self.elements.append((type, ident))
921 def findConfig(self, container):
923 Find the closest enclosing configuration to the specified container.
925 @param container: The container to start from.
926 @type container: L{Container}
927 @return: The closest enclosing configuration, or None.
930 while (container is not None) and not isinstance(container, Config):
931 container = object.__getattribute__(container, 'parent')
934 def resolve(self, container):
936 Resolve this instance in the context of a container.
938 @param container: The container to resolve from.
939 @type container: L{Container}
940 @return: The resolved value.
942 @raise ConfigResolutionError: If resolution fails.
945 path = object.__getattribute__(container, 'path')
947 while current is not None:
948 if self.type == BACKTICK:
949 namespaces = object.__getattribute__(current, 'namespaces')
951 for ns in namespaces:
953 rv = eval(str(self)[1:-1], vars(ns))
961 key = self.elements[0]
964 for item in self.elements[1:]:
971 current = object.__getattribute__(current, 'parent')
973 raise ConfigResolutionError("unable to evaluate %r in the configuration %s" % (self, path))
978 for tt, tv in self.elements[1:]:
983 if self.type == BACKTICK:
984 return BACKTICK + s + BACKTICK
989 return self.__str__()
991 class Expression(object):
993 This internal class implements a value which is obtained by evaluating an expression.
995 def __init__(self, op, lhs, rhs):
997 Initialize an instance.
999 @param op: the operation expressed in the expression.
1000 @type op: PLUS, MINUS, STAR, SLASH, MOD
1001 @param lhs: the left-hand-side operand of the expression.
1002 @type lhs: any Expression or primary value.
1003 @param rhs: the right-hand-side operand of the expression.
1004 @type rhs: any Expression or primary value.
1011 return '%r %s %r' % (self.lhs, self.op, self.rhs)
1014 return self.__str__()
1016 def evaluate(self, container):
1018 Evaluate this instance in the context of a container.
1020 @param container: The container to evaluate in from.
1021 @type container: L{Container}
1022 @return: The evaluated value.
1024 @raise ConfigResolutionError: If evaluation fails.
1025 @raise ZeroDivideError: If division by zero occurs.
1026 @raise TypeError: If the operation is invalid, e.g.
1027 subtracting one string from another.
1030 if isinstance(lhs, Reference):
1031 lhs = lhs.resolve(container)
1032 elif isinstance(lhs, Expression):
1033 lhs = lhs.evaluate(container)
1035 if isinstance(rhs, Reference):
1036 rhs = rhs.resolve(container)
1037 elif isinstance(rhs, Expression):
1038 rhs = rhs.evaluate(container)
1052 class ConfigReader(object):
1054 This internal class implements a parser for configurations.
1057 def __init__(self, config):
1058 self.filename = None
1059 self.config = config
1063 self.last_token = None
1064 self.commentchars = '#'
1065 self.whitespace = ' \t\r\n'
1067 self.punct = ':-+*/%,.{}[]()@`$'
1068 self.digits = '0123456789'
1069 self.wordchars = '%s' % WORDCHARS # make a copy
1070 self.identchars = self.wordchars + self.digits
1077 Return the current location (filename, line, column) in the stream
1080 Used when printing error messages,
1082 @return: A string representing a location in the stream being read.
1085 return "%s(%d,%d)" % (self.filename, self.lineno, self.colno)
1089 Get the next char from the stream. Update line and column numbers
1092 @return: The next character from the stream.
1096 c = self.pbchars.pop()
1097 if isinstance(c,bytes):
1100 c = self.stream.read(1)
1101 if isinstance(c,bytes):
1110 return "<ConfigReader at 0x%08x>" % id(self)
1116 Get a token from the stream. String values are returned in a form
1117 where you need to eval() the returned value to get the actual
1118 string. The return value is (token_type, token_value).
1120 Multiline string tokenizing is thanks to David Janes (BlogMatrix)
1122 @return: The next token.
1123 @rtype: A token tuple.
1126 return self.pbtokens.pop()
1127 stream = self.stream
1137 self.comment += '#' + stream.readline()
1139 self.comment = stream.readline()
1142 if c in self.quotes:
1156 self.pbchars.append(c2)
1157 self.pbchars.append(c1)
1159 self.pbchars.append(c1)
1165 if (c == quote) and not escaped:
1166 if not multiline or (len(token) >= 6 and token.endswith(token[:3]) and token[-4] != '\\'):
1169 escaped = not escaped
1173 raise ConfigFormatError('%s: Unterminated quoted string: %r, %r' % (self.location(), token, c))
1175 if c in self.whitespace:
1178 elif c in self.punct:
1181 if (self.lastc == ']') or (self.lastc in self.identchars):
1187 elif c in self.digits:
1194 if c in self.digits:
1196 elif (c == '.') and token.find('.') < 0:
1199 if c and (c not in self.whitespace):
1200 self.pbchars.append(c)
1203 elif c in self.wordchars:
1207 while c and (c in self.identchars):
1210 if c: # and c not in self.whitespace:
1211 self.pbchars.append(c)
1214 elif token == "False":
1216 elif token == "None":
1220 raise ConfigFormatError('%s: Unexpected character: %r' % (self.location(), c))
1222 self.lastc = token[-1]
1225 self.last_token = tt
1227 # Python 2.x specific unicode conversion
1228 if sys.version_info[0] == 2 and tt == WORD and isinstance(token, unicode):
1229 token = token.encode('ascii')
1232 def load(self, stream, parent=None, suffix=None):
1234 Load the configuration from the specified stream.
1236 @param stream: A stream from which to load the configuration.
1237 @type stream: A stream (file-like object).
1238 @param parent: The parent of the configuration (to which this reader
1239 belongs) in the hierarchy. Specified when the configuration is
1240 included in another one.
1241 @type parent: A L{Container} instance.
1242 @param suffix: The suffix of this configuration in the parent
1243 configuration. Should be specified whenever the parent is not None.
1244 @raise ConfigError: If parent is specified but suffix is not.
1245 @raise ConfigFormatError: If there are syntax errors in the stream.
1247 if parent is not None:
1249 raise ConfigError("internal error: load called with parent but no suffix")
1250 self.config.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1251 self.setStream(stream)
1252 self.token = self.getToken()
1253 self.parseMappingBody(self.config)
1254 if self.token[0] != EOF:
1255 raise ConfigFormatError('%s: expecting EOF, found %r' % (self.location(), self.token[1]))
1257 def setStream(self, stream):
1259 Set the stream to the specified value, and prepare to read from it.
1261 @param stream: A stream from which to load the configuration.
1262 @type stream: A stream (file-like object).
1264 self.stream = stream
1265 if hasattr(stream, 'name'):
1266 filename = stream.name
1269 self.filename = filename
1275 Ensure that the current token type matches the specified value, and
1276 advance to the next token.
1278 @param t: The token type to match.
1279 @type t: A valid token type.
1280 @return: The token which was last read from the stream before this
1282 @rtype: a token tuple - see L{getToken}.
1283 @raise ConfigFormatError: If the token does not match what's expected.
1285 if self.token[0] != t:
1286 raise ConfigFormatError("%s: expecting %s, found %r" % (self.location(), t, self.token[1]))
1288 self.token = self.getToken()
1291 def parseMappingBody(self, parent):
1293 Parse the internals of a mapping, and add entries to the provided
1296 @param parent: The mapping to add entries to.
1297 @type parent: A L{Mapping} instance.
1299 while self.token[0] in [WORD, STRING]:
1300 self.parseKeyValuePair(parent)
1302 def parseKeyValuePair(self, parent):
1304 Parse a key-value pair, and add it to the provided L{Mapping}.
1306 @param parent: The mapping to add entries to.
1307 @type parent: A L{Mapping} instance.
1308 @raise ConfigFormatError: if a syntax error is found.
1310 comment = self.comment
1317 suffix = '[%s]' % tv
1319 msg = "%s: expecting word or string, found %r"
1320 raise ConfigFormatError(msg % (self.location(), tv))
1321 self.token = self.getToken()
1322 # for now, we allow key on its own as a short form of key : True
1323 if self.token[0] == COLON:
1324 self.token = self.getToken()
1325 value = self.parseValue(parent, suffix)
1329 parent.addMapping(key, value, comment)
1330 except Exception as e:
1331 raise ConfigFormatError("%s: %s, %r" % (self.location(), e,
1334 if tt not in [EOF, WORD, STRING, RCURLY, COMMA]:
1335 msg = "%s: expecting one of EOF, WORD, STRING, \
1336 RCURLY, COMMA, found %r"
1337 raise ConfigFormatError(msg % (self.location(), self.token[1]))
1339 self.token = self.getToken()
1341 def parseValue(self, parent, suffix):
1345 @param parent: The container to which the value will be added.
1346 @type parent: A L{Container} instance.
1347 @param suffix: The suffix for the value.
1351 @raise ConfigFormatError: if a syntax error is found.
1354 if tt in [STRING, WORD, NUMBER, LPAREN, DOLLAR,
1355 TRUE, FALSE, NONE, BACKTICK, MINUS]:
1356 rv = self.parseScalar()
1358 rv = self.parseSequence(parent, suffix)
1359 elif tt in [LCURLY, AT]:
1360 rv = self.parseMapping(parent, suffix)
1362 raise ConfigFormatError("%s: unexpected input: %r" %
1363 (self.location(), self.token[1]))
1366 def parseSequence(self, parent, suffix):
1370 @param parent: The container to which the sequence will be added.
1371 @type parent: A L{Container} instance.
1372 @param suffix: The suffix for the value.
1374 @return: a L{Sequence} instance representing the sequence.
1376 @raise ConfigFormatError: if a syntax error is found.
1378 rv = Sequence(parent)
1379 rv.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1381 comment = self.comment
1383 while tt in [STRING, WORD, NUMBER, LCURLY, LBRACK, LPAREN, DOLLAR,
1384 TRUE, FALSE, NONE, BACKTICK]:
1385 suffix = '[%d]' % len(rv)
1386 value = self.parseValue(parent, suffix)
1387 rv.append(value, comment)
1389 comment = self.comment
1393 comment = self.comment
1398 def parseMapping(self, parent, suffix):
1402 @param parent: The container to which the mapping will be added.
1403 @type parent: A L{Container} instance.
1404 @param suffix: The suffix for the value.
1406 @return: a L{Mapping} instance representing the mapping.
1408 @raise ConfigFormatError: if a syntax error is found.
1410 if self.token[0] == LCURLY:
1412 rv = Mapping(parent)
1414 makePath(object.__getattribute__(parent, 'path'), suffix))
1415 self.parseMappingBody(rv)
1419 _, fn = self.match(STRING)
1420 rv = Config(eval(fn), parent)
1423 def parseScalar(self):
1425 Parse a scalar - a terminal value such as a string or number, or
1426 an L{Expression} or L{Reference}.
1428 @return: the parsed scalar
1430 @raise ConfigFormatError: if a syntax error is found.
1432 lhs = self.parseTerm()
1434 while tt in [PLUS, MINUS]:
1436 rhs = self.parseTerm()
1437 lhs = Expression(tt, lhs, rhs)
1441 def parseTerm(self):
1443 Parse a term in an additive expression (a + b, a - b)
1445 @return: the parsed term
1447 @raise ConfigFormatError: if a syntax error is found.
1449 lhs = self.parseFactor()
1451 while tt in [STAR, SLASH, MOD]:
1453 rhs = self.parseFactor()
1454 lhs = Expression(tt, lhs, rhs)
1458 def parseFactor(self):
1460 Parse a factor in an multiplicative expression (a * b, a / b, a % b)
1462 @return: the parsed factor
1464 @raise ConfigFormatError: if a syntax error is found.
1467 if tt in [NUMBER, WORD, STRING, TRUE, FALSE, NONE]:
1474 rv = self.parseScalar()
1478 rv = self.parseReference(DOLLAR)
1479 elif tt == BACKTICK:
1480 self.match(BACKTICK)
1481 rv = self.parseReference(BACKTICK)
1482 self.match(BACKTICK)
1485 rv = -self.parseScalar()
1487 raise ConfigFormatError("%s: unexpected input: %r" %
1488 (self.location(), self.token[1]))
1491 def parseReference(self, type):
1495 @return: the parsed reference
1496 @rtype: L{Reference}
1497 @raise ConfigFormatError: if a syntax error is found.
1499 word = self.match(WORD)
1500 rv = Reference(self.config, type, word[1])
1501 while self.token[0] in [DOT, LBRACK2]:
1502 self.parseSuffix(rv)
1505 def parseSuffix(self, ref):
1507 Parse a reference suffix.
1509 @param ref: The reference of which this suffix is a part.
1510 @type ref: L{Reference}.
1511 @raise ConfigFormatError: if a syntax error is found.
1516 word = self.match(WORD)
1517 ref.addElement(DOT, word[1])
1521 if tt not in [NUMBER, STRING]:
1522 raise ConfigFormatError("%s: expected number or string, found %r" % (self.location(), tv))
1523 self.token = self.getToken()
1526 ref.addElement(LBRACK, tv)
1528 def defaultMergeResolve(map1, map2, key):
1530 A default resolver for merge conflicts. Returns a string
1531 indicating what action to take to resolve the conflict.
1533 @param map1: The map being merged into.
1534 @type map1: L{Mapping}.
1535 @param map2: The map being used as the merge operand.
1536 @type map2: L{Mapping}.
1537 @param key: The key in map2 (which also exists in map1).
1539 @return: One of "merge", "append", "mismatch" or "overwrite"
1540 indicating what action should be taken. This should
1541 be appropriate to the objects being merged - e.g.
1542 there is no point returning "merge" if the two objects
1543 are instances of L{Sequence}.
1548 if isinstance(obj1, Mapping) and isinstance(obj2, Mapping):
1550 elif isinstance(obj1, Sequence) and isinstance(obj2, Sequence):
1556 def overwriteMergeResolve(map1, map2, key):
1558 An overwriting resolver for merge conflicts. Calls L{defaultMergeResolve},
1559 but where a "mismatch" is detected, returns "overwrite" instead.
1561 @param map1: The map being merged into.
1562 @type map1: L{Mapping}.
1563 @param map2: The map being used as the merge operand.
1564 @type map2: L{Mapping}.
1565 @param key: The key in map2 (which also exists in map1).
1568 rv = defaultMergeResolve(map1, map2, key)
1569 if rv == "mismatch":
1573 def deepCopyMapping(inMapping):
1575 for element in inMapping:
1576 res[element] = inMapping[element]
1579 class ConfigMerger(object):
1581 This class is used for merging two configurations. If a key exists in the
1582 merge operand but not the merge target, then the entry is copied from the
1583 merge operand to the merge target. If a key exists in both configurations,
1584 then a resolver (a callable) is called to decide how to handle the
1588 def __init__(self, resolver=defaultMergeResolve):
1590 Initialise an instance.
1593 @type resolver: A callable which takes the argument list
1594 (map1, map2, key) where map1 is the mapping being merged into,
1595 map2 is the merge operand and key is the clashing key. The callable
1596 should return a string indicating how the conflict should be resolved.
1597 For possible return values, see L{defaultMergeResolve}. The default
1598 value preserves the old behaviour
1600 self.resolver = resolver
1602 def merge(self, merged, mergee):
1604 Merge two configurations. The second configuration is unchanged,
1605 and the first is changed to reflect the results of the merge.
1607 @param merged: The configuration to merge into.
1608 @type merged: L{Config}.
1609 @param mergee: The configuration to merge.
1610 @type mergee: L{Config}.
1612 self.mergeMapping(merged, mergee)
1614 def overwriteKeys(self, map1, seq2):
1616 Renint variables. The second mapping is unchanged,
1617 and the first is changed depending the keys of the second mapping.
1618 @param map1: The mapping to reinit keys into.
1619 @type map1: L{Mapping}.
1620 @param map2: The mapping container reinit information.
1621 @type map2: L{Mapping}.
1624 overwrite_list = object.__getattribute__(seq2, 'data')
1625 for overwrite_instruction in overwrite_list:
1626 object.__setattr__(overwrite_instruction, 'parent', map1)
1627 if "__condition__" in overwrite_instruction.keys():
1628 overwrite_condition = overwrite_instruction["__condition__"]
1629 if eval(overwrite_condition, globals(), map1):
1630 for key in overwrite_instruction.keys():
1631 if key == "__condition__":
1634 exec( 'map1.' + key + " = " + repr(overwrite_instruction[key]))
1636 exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1638 for key in overwrite_instruction.keys():
1640 exec('map1.' + key + " = " + repr(overwrite_instruction[key]))
1642 exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1644 def mergeMapping(self, map1, map2):
1646 Merge two mappings recursively. The second mapping is unchanged,
1647 and the first is changed to reflect the results of the merge.
1649 @param map1: The mapping to merge into.
1650 @type map1: L{Mapping}.
1651 @param map2: The mapping to merge.
1652 @type map2: L{Mapping}.
1655 global __resolveOverwrite__
1656 for key in map2.keys():
1657 if __resolveOverwrite__ and key == "__overwrite__":
1658 self.overwriteKeys(map1,map2[key])
1660 elif key not in keys:
1661 map1[key] = map2[key]
1662 if isinstance(map1[key], Container) :
1663 object.__setattr__(map1[key], 'parent', map1)
1667 decision = self.resolver(map1, map2, key)
1668 if decision == "merge":
1669 self.mergeMapping(obj1, obj2)
1670 elif decision == "append":
1671 self.mergeSequence(obj1, obj2)
1672 elif decision == "overwrite":
1674 if isinstance(map1[key], Container):
1675 object.__setattr__(map1[key], 'parent', map1)
1676 elif decision == "mismatch":
1677 self.handleMismatch(obj1, obj2)
1679 msg = "unable to merge: don't know how to implement %r"
1680 raise ValueError(msg % decision)
1682 def mergeSequence(self, seq1, seq2):
1684 Merge two sequences. The second sequence is unchanged,
1685 and the first is changed to have the elements of the second
1688 @param seq1: The sequence to merge into.
1689 @type seq1: L{Sequence}.
1690 @param seq2: The sequence to merge.
1691 @type seq2: L{Sequence}.
1693 data1 = object.__getattribute__(seq1, 'data')
1694 data2 = object.__getattribute__(seq2, 'data')
1697 comment1 = object.__getattribute__(seq1, 'comments')
1698 comment2 = object.__getattribute__(seq2, 'comments')
1699 for obj in comment2:
1700 comment1.append(obj)
1702 def handleMismatch(self, obj1, obj2):
1704 Handle a mismatch between two objects.
1706 @param obj1: The object to merge into.
1708 @param obj2: The object to merge.
1711 raise ConfigError("unable to merge %r with %r" % (obj1, obj2))
1713 class ConfigList(list):
1715 This class implements an ordered list of configurations and allows you
1716 to try getting the configuration from each entry in turn, returning
1717 the first successfully obtained value.
1720 def getByPath(self, path):
1722 Obtain a value from the first configuration in the list which defines
1725 @param path: The path of the value to retrieve.
1727 @return: The value from the earliest configuration in the list which
1730 @raise ConfigError: If no configuration in the list has an entry with
1737 rv = entry.getByPath(path)
1743 raise ConfigError("unable to resolve %r" % path)