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, evaluated=False):
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, evaluated=False):
436 if isinstance(self, Mapping):
439 indstr = indent * ' '
440 if isinstance(value, Reference) or isinstance(value, Expression):
442 stream.write('%s%r%s' % (indstr, value, NEWLINE))
444 stream.write('%s%r%s' % (indstr, self.evaluate(value), NEWLINE))
446 if isinstance(value, str): # and not isWord(value):
448 stream.write('%s%s%s' % (indstr, value, NEWLINE))
450 class Mapping(Container):
452 This internal class implements key-value mappings in configurations.
455 def __init__(self, parent=None):
457 Initialize an instance.
459 @param parent: The parent of this instance in the hierarchy.
460 @type parent: A L{Container} instance.
462 Container.__init__(self, parent)
463 object.__setattr__(self, 'path', '')
464 object.__setattr__(self, 'data', {})
465 object.__setattr__(self, 'order', []) # to preserve ordering
466 object.__setattr__(self, 'comments', {})
468 def __delitem__(self, key):
472 data = object.__getattribute__(self, 'data')
474 raise AttributeError(key)
475 order = object.__getattribute__(self, 'order')
476 comments = object.__getattribute__(self, 'comments')
481 def __getitem__(self, key):
482 data = object.__getattribute__(self, 'data')
484 raise AttributeError("Unknown pyconf key: '%s'" % key)
486 return self.evaluate(rv)
488 __getattr__ = __getitem__
491 def __getattribute__(self, name):
492 if name == "__dict__":
494 if name in ["__methods__", "__members__"]:
496 #if name == "__class__":
498 data = object.__getattribute__(self, "data")
499 useData = name in data
501 rv = getattr(data, name)
503 rv = object.__getattribute__(self, name)
505 raise AttributeError(name)
510 for key in self.keys():
511 yield(key, self[key])
514 def __contains__(self, item):
515 order = object.__getattribute__(self, 'order')
518 def addMapping(self, key, value, comment, setting=False):
520 Add a key-value mapping with a comment.
522 @param key: The key for the mapping.
524 @param value: The value for the mapping.
526 @param comment: The comment for the key (can be None).
528 @param setting: If True, ignore clashes. This is set
529 to true when called from L{__setattr__}.
530 @raise ConfigFormatError: If an existing key is seen
531 again and setting is False.
533 data = object.__getattribute__(self, 'data')
534 order = object.__getattribute__(self, 'order')
535 comments = object.__getattribute__(self, 'comments')
541 raise ConfigFormatError("repeated key: %s" % key)
542 comments[key] = comment
544 def __setattr__(self, name, value):
545 self.addMapping(name, value, None, True)
547 __setitem__ = __setattr__
551 Return the keys in a similar way to a dictionary.
553 return object.__getattribute__(self, 'order')
555 def get(self, key, default=None):
557 Allows a dictionary-style get operation.
564 return str(object.__getattribute__(self, 'data'))
567 return repr(object.__getattribute__(self, 'data'))
570 return len(object.__getattribute__(self, 'order'))
573 return self.iterkeys()
576 order = object.__getattribute__(self, 'order')
577 return order.__iter__()
579 def writeToStream(self, stream, indent, container, evaluated=False):
581 Write this instance to a stream at the specified indentation level.
583 Should be redefined in subclasses.
585 @param stream: The stream to write to
586 @type stream: A writable stream (file-like object)
587 @param indent: The indentation level
589 @param container: The container of this instance
590 @type container: L{Container}
592 indstr = indent * ' '
594 stream.write(' { }%s' % NEWLINE)
596 if isinstance(container, Mapping):
597 stream.write(NEWLINE)
598 stream.write('%s{%s' % (indstr, NEWLINE))
599 self.__save__(stream, indent + 1, evaluated=evaluated)
600 stream.write('%s}%s' % (indstr, NEWLINE))
602 def __save__(self, stream, indent=0, evaluated=False):
604 Save this configuration to the specified stream.
605 @param stream: A stream to which the configuration is written.
606 @type stream: A write-only stream (file-like object).
607 @param indent: The indentation level for the output.
610 indstr = indent * ' '
611 order = object.__getattribute__(self, 'order')
612 data = object.__getattribute__(self, 'data')
613 maxlen = 0 # max(map(lambda x: len(x), order))
615 comment = self.comments[key]
621 stream.write('%s#%s' % (indstr, comment))
622 if skey.startswith("u'"):
624 stream.write('%s%-*s :' % (indstr, maxlen, skey))
626 if isinstance(value, Container):
627 value.writeToStream(stream, indent, self, evaluated=evaluated)
629 self.writeValue(value, stream, indent, evaluated=evaluated)
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, PWD = 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 try: # Python 3 compatibility
661 if isinstance(streamOrFile, unicode):
662 streamOrFile = streamOrFile.encode()
665 Mapping.__init__(self, parent)
666 object.__setattr__(self, 'reader', ConfigReader(self))
667 object.__setattr__(self, 'namespaces', [Config.Namespace()])
668 if streamOrFile is not None:
669 if isinstance(streamOrFile, str) or isinstance(streamOrFile, bytes):
671 if streamOpener is None:
672 streamOpener = defaultStreamOpener
673 streamOrFile = streamOpener(streamOrFile)
674 load = object.__getattribute__(self, "load")
676 # Specific add for salomeTools : PWD
684 def load(self, stream):
686 Load the configuration from the specified stream. Multiple streams can
687 be used to populate the same instance, as long as there are no
688 clashing keys. The stream is closed.
689 @param stream: A stream from which the configuration is read.
690 @type stream: A read-only stream (file-like object).
691 @raise ConfigError: if keys in the loaded configuration clash with
693 @raise ConfigFormatError: if there is a syntax error in the stream.
695 reader = object.__getattribute__(self, 'reader')
699 def addNamespace(self, ns, name=None):
701 Add a namespace to this configuration which can be used to evaluate
702 (resolve) dotted-identifier expressions.
703 @param ns: The namespace to be added.
704 @type ns: A module or other namespace suitable for passing as an
706 @param name: A name for the namespace, which, if specified, provides
707 an additional level of indirection.
710 namespaces = object.__getattribute__(self, 'namespaces')
712 namespaces.append(ns)
714 setattr(namespaces[0], name, ns)
716 def removeNamespace(self, ns, name=None):
718 Remove a namespace added with L{addNamespace}.
719 @param ns: The namespace to be removed.
720 @param name: The name which was specified when L{addNamespace} was
724 namespaces = object.__getattribute__(self, 'namespaces')
726 namespaces.remove(ns)
728 delattr(namespaces[0], name)
730 def __save__(self, stream, indent=0, no_close=False, evaluated=False):
732 Save this configuration to the specified stream. The stream is
733 closed if this is the top-level configuration in the hierarchy.
734 L{Mapping.__save__} is called to do all the work.
735 @param stream: A stream to which the configuration is written.
736 @type stream: A write-only stream (file-like object).
737 @param indent: The indentation level for the output.
740 Mapping.__save__(self, stream, indent, evaluated=evaluated)
741 if indent == 0 and not no_close:
744 def getByPath(self, path):
746 Obtain a value in the configuration via its path.
747 @param path: The path of the required value
749 @return the value at the specified path.
751 @raise ConfigError: If the path is invalid
756 except Exception as e:
757 raise ConfigError(str(e))
759 class Sequence(Container):
761 This internal class implements a value which is a sequence of other values.
763 class SeqIter(object):
765 This internal class implements an iterator for a L{Sequence} instance.
767 def __init__(self, seq):
769 self.limit = len(object.__getattribute__(seq, 'data'))
776 if self.index >= self.limit:
778 rv = self.seq[self.index]
782 # This method is for python3 compatibility
784 if self.index >= self.limit:
786 rv = self.seq[self.index]
790 def __init__(self, parent=None):
792 Initialize an instance.
794 @param parent: The parent of this instance in the hierarchy.
795 @type parent: A L{Container} instance.
797 Container.__init__(self, parent)
798 object.__setattr__(self, 'data', [])
799 object.__setattr__(self, 'comments', [])
801 def append(self, item, comment):
803 Add an item to the sequence.
805 @param item: The item to add.
807 @param comment: A comment for the item.
810 data = object.__getattribute__(self, 'data')
811 comments = object.__getattribute__(self, 'comments')
813 comments.append(comment)
815 def __getitem__(self, index):
816 data = object.__getattribute__(self, 'data')
819 except (IndexError, KeyError, TypeError):
820 raise ConfigResolutionError('Invalid pyconf index %r for %r' % (index, object.__getattribute__(self, 'path')))
821 if not isinstance(rv, list):
822 rv = self.evaluate(rv)
827 result.append(self.evaluate(a))
832 return Sequence.SeqIter(self)
835 return repr(object.__getattribute__(self, 'data'))
838 return str(self[:]) # using the slice evaluates the contents
841 return len(object.__getattribute__(self, 'data'))
843 def writeToStream(self, stream, indent, container, evaluated=False):
845 Write this instance to a stream at the specified indentation level.
847 Should be redefined in subclasses.
849 @param stream: The stream to write to
850 @type stream: A writable stream (file-like object)
851 @param indent: The indentation level
853 @param container: The container of this instance
854 @type container: L{Container}
856 indstr = indent * ' '
858 stream.write(' [ ]%s' % NEWLINE)
860 if isinstance(container, Mapping):
861 stream.write(NEWLINE)
862 stream.write('%s[%s' % (indstr, NEWLINE))
863 self.__save__(stream, indent + 1, evaluated=evaluated)
864 stream.write('%s]%s' % (indstr, NEWLINE))
866 def __save__(self, stream, indent, evaluated=False):
868 Save this instance to the specified stream.
869 @param stream: A stream to which the configuration is written.
870 @type stream: A write-only stream (file-like object).
871 @param indent: The indentation level for the output, > 0
875 raise ConfigError("sequence cannot be saved as a top-level item")
876 data = object.__getattribute__(self, 'data')
877 comments = object.__getattribute__(self, 'comments')
878 indstr = indent * ' '
879 for i in range(0, len(data)):
881 comment = comments[i]
883 stream.write('%s#%s' % (indstr, comment))
884 if isinstance(value, Container):
885 value.writeToStream(stream, indent, self, evaluated=evaluated)
887 self.writeValue(value, stream, indent, evaluated=evaluated)
889 class Reference(object):
891 This internal class implements a value which is a reference to another value.
893 def __init__(self, config, type, ident):
895 Initialize an instance.
897 @param config: The configuration which contains this reference.
898 @type config: A L{Config} instance.
899 @param type: The type of reference.
900 @type type: BACKTICK or DOLLAR
901 @param ident: The identifier which starts the reference.
906 self.elements = [ident]
908 def addElement(self, type, ident):
910 Add an element to the reference.
912 @param type: The type of reference.
913 @type type: BACKTICK or DOLLAR
914 @param ident: The identifier which continues the reference.
917 self.elements.append((type, ident))
919 def findConfig(self, container):
921 Find the closest enclosing configuration to the specified container.
923 @param container: The container to start from.
924 @type container: L{Container}
925 @return: The closest enclosing configuration, or None.
928 while (container is not None) and not isinstance(container, Config):
929 container = object.__getattribute__(container, 'parent')
932 def resolve(self, container):
934 Resolve this instance in the context of a container.
936 @param container: The container to resolve from.
937 @type container: L{Container}
938 @return: The resolved value.
940 @raise ConfigResolutionError: If resolution fails.
943 path = object.__getattribute__(container, 'path')
945 while current is not None:
946 if self.type == BACKTICK:
947 namespaces = object.__getattribute__(current, 'namespaces')
949 for ns in namespaces:
951 rv = eval(str(self)[1:-1], vars(ns))
959 key = self.elements[0]
962 for item in self.elements[1:]:
969 current = object.__getattribute__(current, 'parent')
971 raise ConfigResolutionError("unable to evaluate %r in the configuration %s" % (self, path))
976 for tt, tv in self.elements[1:]:
981 if self.type == BACKTICK:
982 return BACKTICK + s + BACKTICK
987 return self.__str__()
989 class Expression(object):
991 This internal class implements a value which is obtained by evaluating an expression.
993 def __init__(self, op, lhs, rhs):
995 Initialize an instance.
997 @param op: the operation expressed in the expression.
998 @type op: PLUS, MINUS, STAR, SLASH, MOD
999 @param lhs: the left-hand-side operand of the expression.
1000 @type lhs: any Expression or primary value.
1001 @param rhs: the right-hand-side operand of the expression.
1002 @type rhs: any Expression or primary value.
1009 return '%r %s %r' % (self.lhs, self.op, self.rhs)
1012 return self.__str__()
1014 def evaluate(self, container):
1016 Evaluate this instance in the context of a container.
1018 @param container: The container to evaluate in from.
1019 @type container: L{Container}
1020 @return: The evaluated value.
1022 @raise ConfigResolutionError: If evaluation fails.
1023 @raise ZeroDivideError: If division by zero occurs.
1024 @raise TypeError: If the operation is invalid, e.g.
1025 subtracting one string from another.
1028 if isinstance(lhs, Reference):
1029 lhs = lhs.resolve(container)
1030 elif isinstance(lhs, Expression):
1031 lhs = lhs.evaluate(container)
1033 if isinstance(rhs, Reference):
1034 rhs = rhs.resolve(container)
1035 elif isinstance(rhs, Expression):
1036 rhs = rhs.evaluate(container)
1050 class ConfigReader(object):
1052 This internal class implements a parser for configurations.
1055 def __init__(self, config):
1056 self.filename = None
1057 self.config = config
1061 self.last_token = None
1062 self.commentchars = '#'
1063 self.whitespace = ' \t\r\n'
1065 self.punct = ':-+*/%,.{}[]()@`$'
1066 self.digits = '0123456789'
1067 self.wordchars = '%s' % WORDCHARS # make a copy
1068 self.identchars = self.wordchars + self.digits
1075 Return the current location (filename, line, column) in the stream
1078 Used when printing error messages,
1080 @return: A string representing a location in the stream being read.
1083 return "%s(%d,%d)" % (self.filename, self.lineno, self.colno)
1087 Get the next char from the stream. Update line and column numbers
1090 @return: The next character from the stream.
1094 c = self.pbchars.pop()
1095 if isinstance(c,bytes):
1098 c = self.stream.read(1)
1099 if isinstance(c,bytes):
1108 return "<ConfigReader at 0x%08x>" % id(self)
1114 Get a token from the stream. String values are returned in a form
1115 where you need to eval() the returned value to get the actual
1116 string. The return value is (token_type, token_value).
1118 Multiline string tokenizing is thanks to David Janes (BlogMatrix)
1120 @return: The next token.
1121 @rtype: A token tuple.
1124 return self.pbtokens.pop()
1125 stream = self.stream
1135 self.comment += '#' + stream.readline()
1137 self.comment = stream.readline()
1140 if c in self.quotes:
1154 self.pbchars.append(c2)
1155 self.pbchars.append(c1)
1157 self.pbchars.append(c1)
1163 if (c == quote) and not escaped:
1164 if not multiline or (len(token) >= 6 and token.endswith(token[:3]) and token[-4] != '\\'):
1167 escaped = not escaped
1171 raise ConfigFormatError('%s: Unterminated quoted string: %r, %r' % (self.location(), token, c))
1173 if c in self.whitespace:
1176 elif c in self.punct:
1179 if (self.lastc == ']') or (self.lastc in self.identchars):
1185 elif c in self.digits:
1192 if c in self.digits:
1194 elif (c == '.') and token.find('.') < 0:
1197 if c and (c not in self.whitespace):
1198 self.pbchars.append(c)
1201 elif c in self.wordchars:
1205 while c and (c in self.identchars):
1208 if c: # and c not in self.whitespace:
1209 self.pbchars.append(c)
1212 elif token == "False":
1214 elif token == "None":
1218 raise ConfigFormatError('%s: Unexpected character: %r' % (self.location(), c))
1220 self.lastc = token[-1]
1223 self.last_token = tt
1225 # Python 2.x specific unicode conversion
1226 if sys.version_info[0] == 2 and tt == WORD and isinstance(token, unicode):
1227 token = token.encode('ascii')
1230 def load(self, stream, parent=None, suffix=None):
1232 Load the configuration from the specified stream.
1234 @param stream: A stream from which to load the configuration.
1235 @type stream: A stream (file-like object).
1236 @param parent: The parent of the configuration (to which this reader
1237 belongs) in the hierarchy. Specified when the configuration is
1238 included in another one.
1239 @type parent: A L{Container} instance.
1240 @param suffix: The suffix of this configuration in the parent
1241 configuration. Should be specified whenever the parent is not None.
1242 @raise ConfigError: If parent is specified but suffix is not.
1243 @raise ConfigFormatError: If there are syntax errors in the stream.
1245 if parent is not None:
1247 raise ConfigError("internal error: load called with parent but no suffix")
1248 self.config.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1249 self.setStream(stream)
1250 self.token = self.getToken()
1251 self.parseMappingBody(self.config)
1252 if self.token[0] != EOF:
1253 raise ConfigFormatError('%s: expecting EOF, found %r' % (self.location(), self.token[1]))
1255 def setStream(self, stream):
1257 Set the stream to the specified value, and prepare to read from it.
1259 @param stream: A stream from which to load the configuration.
1260 @type stream: A stream (file-like object).
1262 self.stream = stream
1263 if hasattr(stream, 'name'):
1264 filename = stream.name
1267 self.filename = filename
1273 Ensure that the current token type matches the specified value, and
1274 advance to the next token.
1276 @param t: The token type to match.
1277 @type t: A valid token type.
1278 @return: The token which was last read from the stream before this
1280 @rtype: a token tuple - see L{getToken}.
1281 @raise ConfigFormatError: If the token does not match what's expected.
1283 if self.token[0] != t:
1284 raise ConfigFormatError("%s: expecting %s, found %r" % (self.location(), t, self.token[1]))
1286 self.token = self.getToken()
1289 def parseMappingBody(self, parent):
1291 Parse the internals of a mapping, and add entries to the provided
1294 @param parent: The mapping to add entries to.
1295 @type parent: A L{Mapping} instance.
1297 while self.token[0] in [WORD, STRING]:
1298 self.parseKeyValuePair(parent)
1300 def parseKeyValuePair(self, parent):
1302 Parse a key-value pair, and add it to the provided L{Mapping}.
1304 @param parent: The mapping to add entries to.
1305 @type parent: A L{Mapping} instance.
1306 @raise ConfigFormatError: if a syntax error is found.
1308 comment = self.comment
1315 suffix = '[%s]' % tv
1317 msg = "%s: expecting word or string, found %r"
1318 raise ConfigFormatError(msg % (self.location(), tv))
1319 self.token = self.getToken()
1320 # for now, we allow key on its own as a short form of key : True
1321 if self.token[0] == COLON:
1322 self.token = self.getToken()
1323 value = self.parseValue(parent, suffix)
1327 parent.addMapping(key, value, comment)
1328 except Exception as e:
1329 raise ConfigFormatError("%s: %s, %r" % (self.location(), e,
1332 if tt not in [EOF, WORD, STRING, RCURLY, COMMA]:
1333 msg = "%s: expecting one of EOF, WORD, STRING, \
1334 RCURLY, COMMA, found %r"
1335 raise ConfigFormatError(msg % (self.location(), self.token[1]))
1337 self.token = self.getToken()
1339 def parseValue(self, parent, suffix):
1343 @param parent: The container to which the value will be added.
1344 @type parent: A L{Container} instance.
1345 @param suffix: The suffix for the value.
1349 @raise ConfigFormatError: if a syntax error is found.
1352 if tt in [STRING, WORD, NUMBER, LPAREN, DOLLAR,
1353 TRUE, FALSE, NONE, BACKTICK, MINUS]:
1354 rv = self.parseScalar()
1356 rv = self.parseSequence(parent, suffix)
1357 elif tt in [LCURLY, AT]:
1358 rv = self.parseMapping(parent, suffix)
1360 raise ConfigFormatError("%s: unexpected input: %r" %
1361 (self.location(), self.token[1]))
1364 def parseSequence(self, parent, suffix):
1368 @param parent: The container to which the sequence will be added.
1369 @type parent: A L{Container} instance.
1370 @param suffix: The suffix for the value.
1372 @return: a L{Sequence} instance representing the sequence.
1374 @raise ConfigFormatError: if a syntax error is found.
1376 rv = Sequence(parent)
1377 rv.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1379 comment = self.comment
1381 while tt in [STRING, WORD, NUMBER, LCURLY, LBRACK, LPAREN, DOLLAR,
1382 TRUE, FALSE, NONE, BACKTICK]:
1383 suffix = '[%d]' % len(rv)
1384 value = self.parseValue(parent, suffix)
1385 rv.append(value, comment)
1387 comment = self.comment
1391 comment = self.comment
1396 def parseMapping(self, parent, suffix):
1400 @param parent: The container to which the mapping will be added.
1401 @type parent: A L{Container} instance.
1402 @param suffix: The suffix for the value.
1404 @return: a L{Mapping} instance representing the mapping.
1406 @raise ConfigFormatError: if a syntax error is found.
1408 if self.token[0] == LCURLY:
1410 rv = Mapping(parent)
1412 makePath(object.__getattribute__(parent, 'path'), suffix))
1413 self.parseMappingBody(rv)
1417 _, fn = self.match(STRING)
1418 rv = Config(eval(fn), parent)
1421 def parseScalar(self):
1423 Parse a scalar - a terminal value such as a string or number, or
1424 an L{Expression} or L{Reference}.
1426 @return: the parsed scalar
1428 @raise ConfigFormatError: if a syntax error is found.
1430 lhs = self.parseTerm()
1432 while tt in [PLUS, MINUS]:
1434 rhs = self.parseTerm()
1435 lhs = Expression(tt, lhs, rhs)
1439 def parseTerm(self):
1441 Parse a term in an additive expression (a + b, a - b)
1443 @return: the parsed term
1445 @raise ConfigFormatError: if a syntax error is found.
1447 lhs = self.parseFactor()
1449 while tt in [STAR, SLASH, MOD]:
1451 rhs = self.parseFactor()
1452 lhs = Expression(tt, lhs, rhs)
1456 def parseFactor(self):
1458 Parse a factor in an multiplicative expression (a * b, a / b, a % b)
1460 @return: the parsed factor
1462 @raise ConfigFormatError: if a syntax error is found.
1465 if tt in [NUMBER, WORD, STRING, TRUE, FALSE, NONE]:
1472 rv = self.parseScalar()
1476 rv = self.parseReference(DOLLAR)
1477 elif tt == BACKTICK:
1478 self.match(BACKTICK)
1479 rv = self.parseReference(BACKTICK)
1480 self.match(BACKTICK)
1483 rv = -self.parseScalar()
1485 raise ConfigFormatError("%s: unexpected input: %r" %
1486 (self.location(), self.token[1]))
1489 def parseReference(self, type):
1493 @return: the parsed reference
1494 @rtype: L{Reference}
1495 @raise ConfigFormatError: if a syntax error is found.
1497 word = self.match(WORD)
1498 rv = Reference(self.config, type, word[1])
1499 while self.token[0] in [DOT, LBRACK2]:
1500 self.parseSuffix(rv)
1503 def parseSuffix(self, ref):
1505 Parse a reference suffix.
1507 @param ref: The reference of which this suffix is a part.
1508 @type ref: L{Reference}.
1509 @raise ConfigFormatError: if a syntax error is found.
1514 word = self.match(WORD)
1515 ref.addElement(DOT, word[1])
1519 if tt not in [NUMBER, STRING]:
1520 raise ConfigFormatError("%s: expected number or string, found %r" % (self.location(), tv))
1521 self.token = self.getToken()
1524 ref.addElement(LBRACK, tv)
1526 def defaultMergeResolve(map1, map2, key):
1528 A default resolver for merge conflicts.
1529 Returns a string indicating what action to take to resolve the conflict.
1531 @param map1: The map being merged into.
1532 @type map1: L{Mapping}.
1533 @param map2: The map being used as the merge operand.
1534 @type map2: L{Mapping}.
1535 @param key: The key in map2 (which also exists in map1).
1538 @return: One of "merge", "append", "mismatch" or "overwrite"
1539 indicating what action should be taken. This should
1540 be appropriate to the objects being merged - e.g.
1541 there is no point returning "merge" if the two objects
1542 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)