Salome HOME
fix apidoc sphinx ERROR: Unexpected indentation
[tools/sat.git] / src / pyconf.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3
4 # Copyright 2004-2007 by Vinay Sajip. All Rights Reserved.
5 #
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.
19
20 #  Copyright (C) 2010-2013  CEA/DEN
21 #
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.
26 #
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.
31 #
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
35
36 # CEA adds : 
37 # Possibility to overwrites value in a pyconf file
38 # Python 3 porting
39
40
41 """
42 This is a configuration module for Python.
43
44 This module should work under Python versions >= 2.2, and cannot be used with
45 earlier versions since it uses new-style classes.
46
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
50 download link).
51
52 A simple example - with the example configuration file::
53
54     messages:
55     [
56       {
57         stream : `sys.stderr`
58         message: 'Welcome'
59         name: 'Harry'
60       }
61       {
62         stream : `sys.stdout`
63         message: 'Welkom'
64         name: 'Ruud'
65       }
66       {
67         stream : $messages[0].stream
68         message: 'Bienvenue'
69         name: Yves
70       }
71     ]
72
73 a program to read the configuration would be::
74
75     from config import Config
76
77     f = file('simple.cfg')
78     cfg = Config(f)
79     for m in cfg.messages:
80         s = '%s, %s' % (m.message, m.name)
81         try:
82             print >> m.stream, s
83         except IOError, e:
84             print e
85
86 which, when run, would yield the console output::
87
88     Welcome, Harry
89     Welkom, Ruud
90     Bienvenue, Yves
91
92 See U{this tutorial<http://www.red-dove.com/python_config.html|_blank>} for more
93 information.
94
95 #modified for salomeTools
96 @version: 0.3.7.1
97
98 @author: Vinay Sajip
99
100 @copyright: Copyright (C) 2004-2007 Vinay Sajip. All Rights Reserved.
101
102
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.
106
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).
109 """
110
111 __author__  = "Vinay Sajip <vinay_sajip@red-dove.com>"
112 __status__  = "alpha"
113 __version__ = "0.3.7.1" #modified for salomeTools
114 __date__    = "05 October 2007"
115
116 import codecs
117 import os
118 import sys
119
120 WORD = 'a'
121 NUMBER = '9'
122 STRING = '"'
123 EOF = ''
124 LCURLY = '{'
125 RCURLY = '}'
126 LBRACK = '['
127 LBRACK2 = 'a['
128 RBRACK = ']'
129 LPAREN = '('
130 LPAREN2 = '(('
131 RPAREN = ')'
132 DOT = '.'
133 COMMA = ','
134 COLON = ':'
135 AT = '@'
136 PLUS = '+'
137 MINUS = '-'
138 STAR = '*'
139 SLASH = '/'
140 MOD = '%'
141 BACKTICK = '`'
142 DOLLAR = '$'
143 TRUE = 'True'
144 FALSE = 'False'
145 NONE = 'None'
146
147 WORDCHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
148
149 if sys.platform == 'win32':
150     NEWLINE = '\r\n'
151 elif os.name == 'mac':
152     NEWLINE = '\r'
153 else:
154     NEWLINE = '\n'
155
156 try:
157     has_utf32 = True
158 except:
159     has_utf32 = False
160
161 class ConfigInputStream(object):
162     """
163     An input stream which can read either ANSI files with default encoding
164     or Unicode files with BOMs.
165
166     Handles UTF-8, UTF-16LE, UTF-16BE. Could handle UTF-32 if Python had
167     built-in support.
168     """
169     def __init__(self, stream):
170         """
171         Initialize an instance.
172
173         @param stream: The underlying stream to be read. Should be seekable.
174         @type stream: A stream (file-like object).
175         """
176         encoding = None
177         signature = stream.read(4)
178         used = -1
179         if has_utf32:
180             if signature == codecs.BOM_UTF32_LE:
181                 encoding = 'utf-32le'
182             elif signature == codecs.BOM_UTF32_BE:
183                 encoding = 'utf-32be'
184         if encoding is None:
185             if signature[:3] == codecs.BOM_UTF8:
186                 used = 3
187                 encoding = 'utf-8'
188             elif signature[:2] == codecs.BOM_UTF16_LE:
189                 used = 2
190                 encoding = 'utf-16le'
191             elif signature[:2] == codecs.BOM_UTF16_BE:
192                 used = 2
193                 encoding = 'utf-16be'
194             else:
195                 used = 0
196         if used >= 0:
197             stream.seek(used)
198         if encoding:
199             reader = codecs.getreader(encoding)
200             stream = reader(stream)
201         self.stream = stream
202         self.encoding = encoding
203
204     def read(self, size):
205         if (size == 0) or (self.encoding is None):
206             rv = self.stream.read(size)
207         else:
208             rv = u''
209             while size > 0:
210                 rv += self.stream.read(1)
211                 size -= 1
212         return rv
213
214     def close(self):
215         self.stream.close()
216
217     def readline(self):
218         if self.encoding is None:
219             line = ''
220         else:
221             line = u''
222         while True:
223             c = self.stream.read(1)
224             if isinstance(c, bytes):
225                 c = c.decode()
226             if c:
227                 line += c
228             if c == '\n':
229                 break
230         return line
231
232 class ConfigOutputStream(object):
233     """
234     An output stream which can write either ANSI files with default encoding
235     or Unicode files with BOMs.
236
237     Handles UTF-8, UTF-16LE, UTF-16BE. Could handle UTF-32 if Python had
238     built-in support.
239     """
240
241     def __init__(self, stream, encoding=None):
242         """
243         Initialize an instance.
244
245         @param stream: The underlying stream to be written.
246         @type stream: A stream (file-like object).
247         @param encoding: The desired encoding.
248         @type encoding: str
249         """
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)
263
264         if encoding is not None:
265             writer = codecs.getwriter(encoding)
266             stream = writer(stream)
267         self.stream = stream
268
269     def write(self, data):
270         self.stream.write(data)
271
272     def flush(self):
273         self.stream.flush()
274
275     def close(self):
276         self.stream.close()
277
278 def defaultStreamOpener(name):
279     """\
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
282     raised.
283
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
286     urllib2.urlopen().
287
288     @param name: The name of a stream, most commonly a file name.
289     @type name: str
290     @return: A stream with the specified name.
291     @rtype: A read-only stream (file-like object)
292     """
293     return ConfigInputStream(open(name, 'rb'))
294
295 streamOpener = None
296
297 __resolveOverwrite__ = True
298
299 class ConfigError(Exception):
300     """
301     This is the base class of exceptions raised by this module.
302     """
303     pass
304
305 class ConfigFormatError(ConfigError):
306     """
307     This is the base class of exceptions raised due to syntax errors in
308     configurations.
309     """
310     pass
311
312 class ConfigResolutionError(ConfigError):
313     """
314     This is the base class of exceptions raised due to semantic errors in
315     configurations.
316     """
317     pass
318
319 def isWord(s):
320     """
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.
324
325     Examples::
326
327         isWord('a word') ->False
328         isWord('award') -> True
329         isWord(9) -> False
330         isWord('a_b_c_') ->True
331
332     @note: isWord('9abc') will return True - not exactly correct, but adequate
333     for the way it's used here.
334
335     @param s: The name to be tested
336     @type s: any
337     @return: True if a word, else False
338     @rtype: bool
339     """
340     if type(s) != type(''):
341         return False
342     s = s.replace('_', '')
343     return s.isalnum()
344
345 def makePath(prefix, suffix):
346     """\
347     Make a path from a prefix and suffix.
348
349     Examples:
350     makePath('', 'suffix') -> 'suffix'
351     makePath('prefix', 'suffix') -> 'prefix.suffix'
352     makePath('prefix', '[1]') -> 'prefix[1]'
353
354     @param prefix: The prefix to use. If it evaluates as false, the suffix is returned.
355     @type prefix: str
356     @param suffix: The suffix to use. It is either an identifier or an index in brackets.
357     @type suffix: str
358     @return: The path concatenation of prefix and suffix, with adot if the suffix is not a bracketed index.
359     @rtype: str
360     """
361     if not prefix:
362         rv = suffix
363     elif suffix[0] == '[':
364         rv = prefix + suffix
365     else:
366         rv = prefix + '.' + suffix
367     return rv
368
369
370 class Container(object):
371     """
372     This internal class is the base class for mappings and sequences.
373
374     @ivar path: A string which describes how to get
375     to this instance from the root of the hierarchy.
376
377     Example::
378
379         a.list.of[1].or['more'].elements
380     """
381     def __init__(self, parent):
382         """
383         Initialize an instance.
384
385         @param parent: The parent of this instance in the hierarchy.
386         @type parent: A L{Container} instance.
387         """
388         object.__setattr__(self, 'parent', parent)
389
390     def setPath(self, path):
391         """
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.
395         @type path: str
396         """
397         object.__setattr__(self, 'path', path)
398
399     def evaluate(self, item):
400         """
401         Evaluate items which are instances of L{Reference} or L{Expression}.
402
403         L{Reference} instances are evaluated using L{Reference.resolve},
404         and L{Expression} instances are evaluated using
405         L{Expression.evaluate}.
406
407         @param item: The item to be evaluated.
408         @type item: any
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
411         unchanged.
412         """
413         if isinstance(item, Reference):
414             item = item.resolve(self)
415         elif isinstance(item, Expression):
416             item = item.evaluate(self)
417         return item
418
419     def writeToStream(self, stream, indent, container):
420         """
421         Write this instance to a stream at the specified indentation level.
422
423         Should be redefined in subclasses.
424
425         @param stream: The stream to write to
426         @type stream: A writable stream (file-like object)
427         @param indent: The indentation level
428         @type indent: int
429         @param container: The container of this instance
430         @type container: L{Container}
431         @raise NotImplementedError: If a subclass does not override this
432         """
433         raise NotImplementedError
434
435     def writeValue(self, value, stream, indent):
436         if isinstance(self, Mapping):
437             indstr = ' '
438         else:
439             indstr = indent * '  '
440         if isinstance(value, Reference) or isinstance(value, Expression):
441             stream.write('%s%r%s' % (indstr, value, NEWLINE))
442         else:
443             if isinstance(value, str): # and not isWord(value):
444                 value = repr(value)
445             stream.write('%s%s%s' % (indstr, value, NEWLINE))
446
447 class Mapping(Container):
448     """
449     This internal class implements key-value mappings in configurations.
450     """
451
452     def __init__(self, parent=None):
453         """
454         Initialize an instance.
455
456         @param parent: The parent of this instance in the hierarchy.
457         @type parent: A L{Container} instance.
458         """
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', {})
464
465     def __delitem__(self, key):
466         """
467         Remove an item
468         """
469         data = object.__getattribute__(self, 'data')
470         if key not in data:
471             raise AttributeError(key)
472         order = object.__getattribute__(self, 'order')
473         comments = object.__getattribute__(self, 'comments')
474         del data[key]
475         order.remove(key)
476         del comments[key]
477
478     def __getitem__(self, key):
479         data = object.__getattribute__(self, 'data')
480         if key not in data:
481             raise AttributeError(key)
482         rv = data[key]
483         return self.evaluate(rv)
484
485     __getattr__ = __getitem__
486     
487     '''
488     def __getattribute__(self, name):
489         if name == "__dict__":
490             return {}
491         if name in ["__methods__", "__members__"]:
492             return []
493         #if name == "__class__":
494         #    return ''
495         data = object.__getattribute__(self, "data")
496         useData = data.has_key(name)
497         if useData:
498             rv = getattr(data, name)
499         else:
500             rv = object.__getattribute__(self, name)
501             if rv is None:
502                 raise AttributeError(name)
503         return rv
504     '''
505
506     def iteritems(self):
507         for key in self.keys():
508             yield(key, self[key])
509         raise StopIteration
510
511     def __contains__(self, item):
512         order = object.__getattribute__(self, 'order')
513         return item in order
514
515     def addMapping(self, key, value, comment, setting=False):
516         """
517         Add a key-value mapping with a comment.
518
519         @param key: The key for the mapping.
520         @type key: str
521         @param value: The value for the mapping.
522         @type value: any
523         @param comment: The comment for the key (can be None).
524         @type comment: str
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.
529         """
530         data = object.__getattribute__(self, 'data')
531         order = object.__getattribute__(self, 'order')
532         comments = object.__getattribute__(self, 'comments')
533
534         data[key] = value
535         if key not in order:
536             order.append(key)
537         elif not setting:
538             raise ConfigFormatError("repeated key: %s" % key)
539         comments[key] = comment
540
541     def __setattr__(self, name, value):
542         self.addMapping(name, value, None, True)
543
544     __setitem__ = __setattr__
545
546     def keys(self):
547         """
548         Return the keys in a similar way to a dictionary.
549         """
550         return object.__getattribute__(self, 'order')
551
552     def get(self, key, default=None):
553         """
554         Allows a dictionary-style get operation.
555         """
556         if key in self:
557             return self[key]
558         return default
559
560     def __str__(self):
561         return str(object.__getattribute__(self, 'data'))
562
563     def __repr__(self):
564         return repr(object.__getattribute__(self, 'data'))
565
566     def __len__(self):
567         return len(object.__getattribute__(self, 'order'))
568
569     def __iter__(self):
570         return self.iterkeys()
571
572     def iterkeys(self):
573         order = object.__getattribute__(self, 'order')
574         return order.__iter__()
575
576     def writeToStream(self, stream, indent, container):
577         """
578         Write this instance to a stream at the specified indentation level.
579
580         Should be redefined in subclasses.
581
582         @param stream: The stream to write to
583         @type stream: A writable stream (file-like object)
584         @param indent: The indentation level
585         @type indent: int
586         @param container: The container of this instance
587         @type container: L{Container}
588         """
589         indstr = indent * '  '
590         if len(self) == 0:
591             stream.write(' { }%s' % NEWLINE)
592         else:
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))
598
599     def __save__(self, stream, indent=0):
600         """
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.
605         @type indent: int
606         """
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))
611         for key in order:
612             comment = self.comments[key]
613             if isWord(key):
614                 skey = key
615             else:
616                 skey = repr(key)
617             if comment:
618                 stream.write('%s#%s' % (indstr, comment))
619             if skey.startswith("u'"):
620                 skey = skey[1:]
621             stream.write('%s%-*s :' % (indstr, maxlen, skey))
622             value = data[key]
623             if isinstance(value, Container):
624                 value.writeToStream(stream, indent, self)
625             else:
626                 self.writeValue(value, stream, indent)
627
628 class Config(Mapping):
629     """
630     This class represents a configuration, and is the only one which clients
631     need to interface to, under normal circumstances.
632     """
633
634     class Namespace(object):
635         """
636         This internal class is used for implementing default namespaces.
637
638         An instance acts as a namespace.
639         """
640         def __init__(self):
641             self.sys = sys
642             self.os = os
643
644     def __init__(self, streamOrFile=None, parent=None, PWD = None):
645         """
646         Initializes an instance.
647
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.
656         """
657         try: # Python 3 compatibility
658             if isinstance(streamOrFile, unicode):
659                 streamOrFile = streamOrFile.encode()
660         except NameError:
661             pass
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):
667                 global streamOpener
668                 if streamOpener is None:
669                     streamOpener = defaultStreamOpener
670                 streamOrFile = streamOpener(streamOrFile)
671             load = object.__getattribute__(self, "load")
672             load(streamOrFile)
673             # Specific add for salomeTools : PWD
674             if PWD:
675                 key, pwd = PWD
676                 if key == "":
677                     self.PWD = pwd
678                 else:
679                     self[key].PWD = pwd
680
681     def load(self, stream):
682         """
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
689         existing keys.
690         @raise ConfigFormatError: if there is a syntax error in the stream.
691         """
692         reader = object.__getattribute__(self, 'reader')
693         reader.load(stream)
694         stream.close()
695
696     def addNamespace(self, ns, name=None):
697         """
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
702         argument to vars().
703         @param name: A name for the namespace, which, if specified, provides
704         an additional level of indirection.
705         @type name: str
706         """
707         namespaces = object.__getattribute__(self, 'namespaces')
708         if name is None:
709             namespaces.append(ns)
710         else:
711             setattr(namespaces[0], name, ns)
712
713     def removeNamespace(self, ns, name=None):
714         """
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
718         called.
719         @type name: str
720         """
721         namespaces = object.__getattribute__(self, 'namespaces')
722         if name is None:
723             namespaces.remove(ns)
724         else:
725             delattr(namespaces[0], name)
726
727     def __save__(self, stream, indent=0, no_close=False):
728         """
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.
735         @type indent: int
736         """
737         Mapping.__save__(self, stream, indent)
738         if indent == 0 and not no_close:
739             stream.close()
740
741     def getByPath(self, path):
742         """
743         Obtain a value in the configuration via its path.
744         @param path: The path of the required value
745         @type path: str
746         @return the value at the specified path.
747         @rtype: any
748         @raise ConfigError: If the path is invalid
749         """
750         s = 'self.' + path
751         try:
752             return eval(s)
753         except Exception as e:
754             raise ConfigError(str(e))
755
756 class Sequence(Container):
757     """
758     This internal class implements a value which is a sequence of other values.
759     """
760     class SeqIter(object):
761         """
762         This internal class implements an iterator for a L{Sequence} instance.
763         """
764         def __init__(self, seq):
765             self.seq = seq
766             self.limit = len(object.__getattribute__(seq, 'data'))
767             self.index = 0
768
769         def __iter__(self):
770             return self
771
772         def next(self):
773             if self.index >= self.limit:
774                 raise StopIteration
775             rv = self.seq[self.index]
776             self.index += 1
777             return rv
778         
779         # This method is for python3 compatibility
780         def __next__(self): 
781             if self.index >= self.limit:
782                 raise StopIteration
783             rv = self.seq[self.index]
784             self.index += 1
785             return rv
786
787     def __init__(self, parent=None):
788         """
789         Initialize an instance.
790
791         @param parent: The parent of this instance in the hierarchy.
792         @type parent: A L{Container} instance.
793         """
794         Container.__init__(self, parent)
795         object.__setattr__(self, 'data', [])
796         object.__setattr__(self, 'comments', [])
797
798     def append(self, item, comment):
799         """
800         Add an item to the sequence.
801
802         @param item: The item to add.
803         @type item: any
804         @param comment: A comment for the item.
805         @type comment: str
806         """
807         data = object.__getattribute__(self, 'data')
808         comments = object.__getattribute__(self, 'comments')
809         data.append(item)
810         comments.append(comment)
811
812     def __getitem__(self, index):
813         data = object.__getattribute__(self, 'data')
814         try:
815             rv = data[index]
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)
820         else:
821             # deal with a slice
822             result = []
823             for a in rv:
824                 result.append(self.evaluate(a))
825             rv = result
826         return rv
827
828     def __iter__(self):
829         return Sequence.SeqIter(self)
830
831     def __repr__(self):
832         return repr(object.__getattribute__(self, 'data'))
833
834     def __str__(self):
835         return str(self[:]) # using the slice evaluates the contents
836
837     def __len__(self):
838         return len(object.__getattribute__(self, 'data'))
839
840     def writeToStream(self, stream, indent, container):
841         """
842         Write this instance to a stream at the specified indentation level.
843
844         Should be redefined in subclasses.
845
846         @param stream: The stream to write to
847         @type stream: A writable stream (file-like object)
848         @param indent: The indentation level
849         @type indent: int
850         @param container: The container of this instance
851         @type container: L{Container}
852         """
853         indstr = indent * '  '
854         if len(self) == 0:
855             stream.write(' [ ]%s' % NEWLINE)
856         else:
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))
862
863     def __save__(self, stream, indent):
864         """
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
869         @type indent: int
870         """
871         if indent == 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)):
877             value = data[i]
878             comment = comments[i]
879             if comment:
880                 stream.write('%s#%s' % (indstr, comment))
881             if isinstance(value, Container):
882                 value.writeToStream(stream, indent, self)
883             else:
884                 self.writeValue(value, stream, indent)
885
886 class Reference(object):
887     """
888     This internal class implements a value which is a reference to another value.
889     """
890     def __init__(self, config, type, ident):
891         """
892         Initialize an instance.
893
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.
899         @type ident: str
900         """
901         self.config = config
902         self.type = type
903         self.elements = [ident]
904
905     def addElement(self, type, ident):
906         """
907         Add an element to the reference.
908
909         @param type: The type of reference.
910         @type type: BACKTICK or DOLLAR
911         @param ident: The identifier which continues the reference.
912         @type ident: str
913         """
914         self.elements.append((type, ident))
915
916     def findConfig(self, container):
917         """
918         Find the closest enclosing configuration to the specified container.
919
920         @param container: The container to start from.
921         @type container: L{Container}
922         @return: The closest enclosing configuration, or None.
923         @rtype: L{Config}
924         """
925         while (container is not None) and not isinstance(container, Config):
926             container = object.__getattribute__(container, 'parent')
927         return container
928
929     def resolve(self, container):
930         """
931         Resolve this instance in the context of a container.
932
933         @param container: The container to resolve from.
934         @type container: L{Container}
935         @return: The resolved value.
936         @rtype: any
937         @raise ConfigResolutionError: If resolution fails.
938         """
939         rv = None
940         path = object.__getattribute__(container, 'path')
941         current = container
942         while current is not None:
943             if self.type == BACKTICK:
944                 namespaces = object.__getattribute__(current, 'namespaces')
945                 found = False
946                 for ns in namespaces:
947                     try:
948                         rv = eval(str(self)[1:-1], vars(ns))
949                         found = True
950                         break
951                     except:
952                         pass
953                 if found:
954                     break
955             else:
956                 key = self.elements[0]
957                 try:
958                     rv = current[key]
959                     for item in self.elements[1:]:
960                         key = item[1]
961                         rv = rv[key]
962                     break
963                 except:
964                     rv = None
965                     pass
966             current = object.__getattribute__(current, 'parent')
967         if current is None:
968             raise ConfigResolutionError("unable to evaluate %r in the configuration %s" % (self, path))
969         return rv
970
971     def __str__(self):
972         s = self.elements[0]
973         for tt, tv in self.elements[1:]:
974             if tt == DOT:
975                 s += '.%s' % tv
976             else:
977                 s += '[%r]' % tv
978         if self.type == BACKTICK:
979             return BACKTICK + s + BACKTICK
980         else:
981             return DOLLAR + s
982
983     def __repr__(self):
984         return self.__str__()
985
986 class Expression(object):
987     """
988     This internal class implements a value which is obtained by evaluating an expression.
989     """
990     def __init__(self, op, lhs, rhs):
991         """
992         Initialize an instance.
993
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.
1000         """
1001         self.op = op
1002         self.lhs = lhs
1003         self.rhs = rhs
1004
1005     def __str__(self):
1006         return '%r %s %r' % (self.lhs, self.op, self.rhs)
1007
1008     def __repr__(self):
1009         return self.__str__()
1010
1011     def evaluate(self, container):
1012         """
1013         Evaluate this instance in the context of a container.
1014
1015         @param container: The container to evaluate in from.
1016         @type container: L{Container}
1017         @return: The evaluated value.
1018         @rtype: any
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.
1023         """
1024         lhs = self.lhs
1025         if isinstance(lhs, Reference):
1026             lhs = lhs.resolve(container)
1027         elif isinstance(lhs, Expression):
1028             lhs = lhs.evaluate(container)
1029         rhs = self.rhs
1030         if isinstance(rhs, Reference):
1031             rhs = rhs.resolve(container)
1032         elif isinstance(rhs, Expression):
1033             rhs = rhs.evaluate(container)
1034         op = self.op
1035         if op == PLUS:
1036             rv = lhs + rhs
1037         elif op == MINUS:
1038             rv = lhs - rhs
1039         elif op == STAR:
1040             rv = lhs * rhs
1041         elif op == SLASH:
1042             rv = lhs / rhs
1043         else:
1044             rv = lhs % rhs
1045         return rv
1046
1047 class ConfigReader(object):
1048     """
1049     This internal class implements a parser for configurations.
1050     """
1051
1052     def __init__(self, config):
1053         self.filename = None
1054         self.config = config
1055         self.lineno = 0
1056         self.colno = 0
1057         self.lastc = None
1058         self.last_token = None
1059         self.commentchars = '#'
1060         self.whitespace = ' \t\r\n'
1061         self.quotes = '\'"'
1062         self.punct = ':-+*/%,.{}[]()@`$'
1063         self.digits = '0123456789'
1064         self.wordchars = '%s' % WORDCHARS # make a copy
1065         self.identchars = self.wordchars + self.digits
1066         self.pbchars = []
1067         self.pbtokens = []
1068         self.comment = None
1069
1070     def location(self):
1071         """
1072         Return the current location (filename, line, column) in the stream
1073         as a string.
1074
1075         Used when printing error messages,
1076
1077         @return: A string representing a location in the stream being read.
1078         @rtype: str
1079         """
1080         return "%s(%d,%d)" % (self.filename, self.lineno, self.colno)
1081
1082     def getChar(self):
1083         """
1084         Get the next char from the stream. Update line and column numbers
1085         appropriately.
1086
1087         @return: The next character from the stream.
1088         @rtype: str
1089         """
1090         if self.pbchars:
1091             c = self.pbchars.pop()
1092             if isinstance(c,bytes):
1093                 c = c.decode()
1094         else:
1095             c = self.stream.read(1)
1096             if isinstance(c,bytes):
1097                 c = c.decode()
1098             self.colno += 1
1099             if c == '\n':
1100                 self.lineno += 1
1101                 self.colno = 1
1102         return c
1103
1104     def __repr__(self):
1105         return "<ConfigReader at 0x%08x>" % id(self)
1106
1107     __str__ = __repr__
1108
1109     def getToken(self):
1110         """
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).
1114
1115         Multiline string tokenizing is thanks to David Janes (BlogMatrix)
1116
1117         @return: The next token.
1118         @rtype: A token tuple.
1119         """
1120         if self.pbtokens:
1121             return self.pbtokens.pop()
1122         stream = self.stream
1123         self.comment = None
1124         token = ''
1125         tt = EOF
1126         while True:
1127             c = self.getChar()
1128             if not c:
1129                 break
1130             elif c == '#':
1131                 if self.comment :
1132                     self.comment += '#' + stream.readline()
1133                 else :
1134                     self.comment = stream.readline()
1135                 self.lineno += 1
1136                 continue
1137             if c in self.quotes:
1138                 token = c
1139                 quote = c
1140                 tt = STRING
1141                 escaped = False
1142                 multiline = False
1143                 c1 = self.getChar()
1144                 if c1 == quote:
1145                     c2 = self.getChar()
1146                     if c2 == quote:
1147                         multiline = True
1148                         token += quote
1149                         token += quote
1150                     else:
1151                         self.pbchars.append(c2)
1152                         self.pbchars.append(c1)
1153                 else:
1154                     self.pbchars.append(c1)
1155                 while True:
1156                     c = self.getChar()
1157                     if not c:
1158                         break
1159                     token += c
1160                     if (c == quote) and not escaped:
1161                         if not multiline or (len(token) >= 6 and token.endswith(token[:3]) and token[-4] != '\\'):
1162                             break
1163                     if c == '\\':
1164                         escaped = not escaped
1165                     else:
1166                         escaped = False
1167                 if not c:
1168                     raise ConfigFormatError('%s: Unterminated quoted string: %r, %r' % (self.location(), token, c))
1169                 break
1170             if c in self.whitespace:
1171                 self.lastc = c
1172                 continue
1173             elif c in self.punct:
1174                 token = c
1175                 tt = c
1176                 if (self.lastc == ']') or (self.lastc in self.identchars):
1177                     if c == '[':
1178                         tt = LBRACK2
1179                     elif c == '(':
1180                         tt = LPAREN2
1181                 break
1182             elif c in self.digits:
1183                 token = c
1184                 tt = NUMBER
1185                 while True:
1186                     c = self.getChar()
1187                     if not c:
1188                         break
1189                     if c in self.digits:
1190                         token += c
1191                     elif (c == '.') and token.find('.') < 0:
1192                         token += c
1193                     else:
1194                         if c and (c not in self.whitespace):
1195                             self.pbchars.append(c)
1196                         break
1197                 break
1198             elif c in self.wordchars:
1199                 token = c
1200                 tt = WORD
1201                 c = self.getChar()
1202                 while c and (c in self.identchars):
1203                     token += c
1204                     c = self.getChar()
1205                 if c: # and c not in self.whitespace:
1206                     self.pbchars.append(c)
1207                 if token == "True":
1208                     tt = TRUE
1209                 elif token == "False":
1210                     tt = FALSE
1211                 elif token == "None":
1212                     tt = NONE
1213                 break
1214             else:
1215                 raise ConfigFormatError('%s: Unexpected character: %r' % (self.location(), c))
1216         if token:
1217             self.lastc = token[-1]
1218         else:
1219             self.lastc = None
1220         self.last_token = tt
1221         
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')
1225         return (tt, token)
1226
1227     def load(self, stream, parent=None, suffix=None):
1228         """
1229         Load the configuration from the specified stream.
1230
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.
1241         """
1242         if parent is not None:
1243             if suffix is 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]))
1251
1252     def setStream(self, stream):
1253         """
1254         Set the stream to the specified value, and prepare to read from it.
1255
1256         @param stream: A stream from which to load the configuration.
1257         @type stream: A stream (file-like object).
1258         """
1259         self.stream = stream
1260         if hasattr(stream, 'name'):
1261             filename = stream.name
1262         else:
1263             filename = '?'
1264         self.filename = filename
1265         self.lineno = 1
1266         self.colno = 1
1267
1268     def match(self, t):
1269         """
1270         Ensure that the current token type matches the specified value, and
1271         advance to the next token.
1272
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
1276         function is called.
1277         @rtype: a token tuple - see L{getToken}.
1278         @raise ConfigFormatError: If the token does not match what's expected.
1279         """
1280         if self.token[0] != t:
1281             raise ConfigFormatError("%s: expecting %s, found %r" % (self.location(), t, self.token[1]))
1282         rv = self.token
1283         self.token = self.getToken()
1284         return rv
1285
1286     def parseMappingBody(self, parent):
1287         """
1288         Parse the internals of a mapping, and add entries to the provided
1289         L{Mapping}.
1290
1291         @param parent: The mapping to add entries to.
1292         @type parent: A L{Mapping} instance.
1293         """
1294         while self.token[0] in [WORD, STRING]:
1295             self.parseKeyValuePair(parent)
1296
1297     def parseKeyValuePair(self, parent):
1298         """
1299         Parse a key-value pair, and add it to the provided L{Mapping}.
1300
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.
1304         """
1305         comment = self.comment
1306         tt, tv = self.token
1307         if tt == WORD:
1308             key = tv
1309             suffix = tv
1310         elif tt == STRING:
1311             key = eval(tv)
1312             suffix = '[%s]' % tv
1313         else:
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)
1321         else:
1322             value = True
1323         try:
1324             parent.addMapping(key, value, comment)
1325         except Exception as e:
1326             raise ConfigFormatError("%s: %s, %r" % (self.location(), e,
1327                                     self.token[1]))
1328         tt = self.token[0]
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]))
1333         if tt == COMMA:
1334             self.token = self.getToken()
1335
1336     def parseValue(self, parent, suffix):
1337         """
1338         Parse a value.
1339
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.
1343         @type suffix: str
1344         @return: The value
1345         @rtype: any
1346         @raise ConfigFormatError: if a syntax error is found.
1347         """
1348         tt = self.token[0]
1349         if tt in [STRING, WORD, NUMBER, LPAREN, DOLLAR,
1350                   TRUE, FALSE, NONE, BACKTICK, MINUS]:
1351             rv = self.parseScalar()
1352         elif tt == LBRACK:
1353             rv = self.parseSequence(parent, suffix)
1354         elif tt in [LCURLY, AT]:
1355             rv = self.parseMapping(parent, suffix)
1356         else:
1357             raise ConfigFormatError("%s: unexpected input: %r" %
1358                (self.location(), self.token[1]))
1359         return rv
1360
1361     def parseSequence(self, parent, suffix):
1362         """
1363         Parse a sequence.
1364
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.
1368         @type suffix: str
1369         @return: a L{Sequence} instance representing the sequence.
1370         @rtype: L{Sequence}
1371         @raise ConfigFormatError: if a syntax error is found.
1372         """
1373         rv = Sequence(parent)
1374         rv.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1375         self.match(LBRACK)
1376         comment = self.comment
1377         tt = self.token[0]
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)
1383             tt = self.token[0]
1384             comment = self.comment
1385             if tt == COMMA:
1386                 self.match(COMMA)
1387                 tt = self.token[0]
1388                 comment = self.comment
1389                 continue
1390         self.match(RBRACK)
1391         return rv
1392
1393     def parseMapping(self, parent, suffix):
1394         """
1395         Parse a mapping.
1396
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.
1400         @type suffix: str
1401         @return: a L{Mapping} instance representing the mapping.
1402         @rtype: L{Mapping}
1403         @raise ConfigFormatError: if a syntax error is found.
1404         """
1405         if self.token[0] == LCURLY:
1406             self.match(LCURLY)
1407             rv = Mapping(parent)
1408             rv.setPath(
1409                makePath(object.__getattribute__(parent, 'path'), suffix))
1410             self.parseMappingBody(rv)
1411             self.match(RCURLY)
1412         else:
1413             self.match(AT)
1414             _, fn = self.match(STRING)
1415             rv = Config(eval(fn), parent)
1416         return rv
1417
1418     def parseScalar(self):
1419         """
1420         Parse a scalar - a terminal value such as a string or number, or
1421         an L{Expression} or L{Reference}.
1422
1423         @return: the parsed scalar
1424         @rtype: any scalar
1425         @raise ConfigFormatError: if a syntax error is found.
1426         """
1427         lhs = self.parseTerm()
1428         tt = self.token[0]
1429         while tt in [PLUS, MINUS]:
1430             self.match(tt)
1431             rhs = self.parseTerm()
1432             lhs = Expression(tt, lhs, rhs)
1433             tt = self.token[0]
1434         return lhs
1435
1436     def parseTerm(self):
1437         """
1438         Parse a term in an additive expression (a + b, a - b)
1439
1440         @return: the parsed term
1441         @rtype: any scalar
1442         @raise ConfigFormatError: if a syntax error is found.
1443         """
1444         lhs = self.parseFactor()
1445         tt = self.token[0]
1446         while tt in [STAR, SLASH, MOD]:
1447             self.match(tt)
1448             rhs = self.parseFactor()
1449             lhs = Expression(tt, lhs, rhs)
1450             tt = self.token[0]
1451         return lhs
1452
1453     def parseFactor(self):
1454         """
1455         Parse a factor in an multiplicative expression (a * b, a / b, a % b)
1456
1457         @return: the parsed factor
1458         @rtype: any scalar
1459         @raise ConfigFormatError: if a syntax error is found.
1460         """
1461         tt = self.token[0]
1462         if tt in [NUMBER, WORD, STRING, TRUE, FALSE, NONE]:
1463             rv = self.token[1]
1464             if tt != WORD:
1465                 rv = eval(rv)
1466             self.match(tt)
1467         elif tt == LPAREN:
1468             self.match(LPAREN)
1469             rv = self.parseScalar()
1470             self.match(RPAREN)
1471         elif tt == DOLLAR:
1472             self.match(DOLLAR)
1473             rv = self.parseReference(DOLLAR)
1474         elif tt == BACKTICK:
1475             self.match(BACKTICK)
1476             rv = self.parseReference(BACKTICK)
1477             self.match(BACKTICK)
1478         elif tt == MINUS:
1479             self.match(MINUS)
1480             rv = -self.parseScalar()
1481         else:
1482             raise ConfigFormatError("%s: unexpected input: %r" %
1483                (self.location(), self.token[1]))
1484         return rv
1485
1486     def parseReference(self, type):
1487         """
1488         Parse a reference.
1489
1490         @return: the parsed reference
1491         @rtype: L{Reference}
1492         @raise ConfigFormatError: if a syntax error is found.
1493         """
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)
1498         return rv
1499
1500     def parseSuffix(self, ref):
1501         """
1502         Parse a reference suffix.
1503
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.
1507         """
1508         tt = self.token[0]
1509         if tt == DOT:
1510             self.match(DOT)
1511             word = self.match(WORD)
1512             ref.addElement(DOT, word[1])
1513         else:
1514             self.match(LBRACK2)
1515             tt, tv = self.token
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()
1519             tv = eval(tv)
1520             self.match(RBRACK)
1521             ref.addElement(LBRACK, tv)
1522
1523 def defaultMergeResolve(map1, map2, key):
1524     """\
1525     A default resolver for merge conflicts. 
1526     Returns a string indicating what action to take to resolve the conflict.
1527
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).
1533     @type key: str
1534
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}.
1540
1541     @rtype: str
1542     """
1543     obj1 = map1[key]
1544     obj2 = map2[key]
1545     if isinstance(obj1, Mapping) and isinstance(obj2, Mapping):
1546         rv = "merge"
1547     elif isinstance(obj1, Sequence) and isinstance(obj2, Sequence):
1548         rv = "append"
1549     else:
1550         rv = "mismatch"
1551     return rv
1552
1553 def overwriteMergeResolve(map1, map2, key):
1554     """
1555     An overwriting resolver for merge conflicts. Calls L{defaultMergeResolve},
1556     but where a "mismatch" is detected, returns "overwrite" instead.
1557
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).
1563     @type key: str
1564     """
1565     rv = defaultMergeResolve(map1, map2, key)
1566     if rv == "mismatch":
1567         rv = "overwrite"
1568     return rv
1569
1570 def deepCopyMapping(inMapping):
1571     res = Mapping()
1572     for element in inMapping:
1573         res[element] = inMapping[element]
1574     return res
1575
1576 class ConfigMerger(object):
1577     """
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
1582     conflict.
1583     """
1584
1585     def __init__(self, resolver=defaultMergeResolve):
1586         """
1587         Initialise an instance.
1588
1589         @param resolver:
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
1596         """
1597         self.resolver = resolver
1598
1599     def merge(self, merged, mergee):
1600         """
1601         Merge two configurations. The second configuration is unchanged,
1602         and the first is changed to reflect the results of the merge.
1603
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}.
1608         """
1609         self.mergeMapping(merged, mergee)
1610
1611     def overwriteKeys(self, map1, seq2):
1612         """
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}.
1619         """
1620
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__":
1629                             continue
1630                         try:
1631                             exec( 'map1.' + key + " = " + repr(overwrite_instruction[key]))
1632                         except:
1633                             exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1634             else:
1635                 for key in overwrite_instruction.keys():
1636                     try:
1637                         exec('map1.' + key + " = " + repr(overwrite_instruction[key]))
1638                     except:
1639                         exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1640
1641     def mergeMapping(self, map1, map2):
1642         """
1643         Merge two mappings recursively. The second mapping is unchanged,
1644         and the first is changed to reflect the results of the merge.
1645
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}.
1650         """
1651         keys = map1.keys()
1652         global __resolveOverwrite__
1653         for key in map2.keys():
1654             if __resolveOverwrite__ and key == "__overwrite__":
1655                 self.overwriteKeys(map1,map2[key])
1656
1657             elif key not in keys:
1658                 map1[key] = map2[key]
1659                 if isinstance(map1[key], Container) :
1660                     object.__setattr__(map1[key], 'parent', map1)
1661             else:
1662                 obj1 = map1[key]
1663                 obj2 = map2[key]
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":
1670                     map1[key] = obj2
1671                     if isinstance(map1[key], Container):
1672                         object.__setattr__(map1[key], 'parent', map1)
1673                 elif decision == "mismatch":
1674                     self.handleMismatch(obj1, obj2)
1675                 else:
1676                     msg = "unable to merge: don't know how to implement %r"
1677                     raise ValueError(msg % decision)
1678
1679     def mergeSequence(self, seq1, seq2):
1680         """
1681         Merge two sequences. The second sequence is unchanged,
1682         and the first is changed to have the elements of the second
1683         appended to it.
1684
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}.
1689         """
1690         data1 = object.__getattribute__(seq1, 'data')
1691         data2 = object.__getattribute__(seq2, 'data')
1692         for obj in data2:
1693             data1.append(obj)
1694         comment1 = object.__getattribute__(seq1, 'comments')
1695         comment2 = object.__getattribute__(seq2, 'comments')
1696         for obj in comment2:
1697             comment1.append(obj)
1698
1699     def handleMismatch(self, obj1, obj2):
1700         """
1701         Handle a mismatch between two objects.
1702
1703         @param obj1: The object to merge into.
1704         @type obj1: any
1705         @param obj2: The object to merge.
1706         @type obj2: any
1707         """
1708         raise ConfigError("unable to merge %r with %r" % (obj1, obj2))
1709
1710 class ConfigList(list):
1711     """
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.
1715     """
1716
1717     def getByPath(self, path):
1718         """
1719         Obtain a value from the first configuration in the list which defines
1720         it.
1721
1722         @param path: The path of the value to retrieve.
1723         @type path: str
1724         @return: The value from the earliest configuration in the list which
1725         defines it.
1726         @rtype: any
1727         @raise ConfigError: If no configuration in the list has an entry with
1728         the specified path.
1729         """
1730         found = False
1731         rv = None
1732         for entry in self:
1733             try:
1734                 rv = entry.getByPath(path)
1735                 found = True
1736                 break
1737             except ConfigError:
1738                 pass
1739         if not found:
1740             raise ConfigError("unable to resolve %r" % path)
1741         return rv