]> SALOME platform Git repositories - tools/sat.git/blob - src/pyconf.py
Salome HOME
Modifying source command behavior as requested by CEA in 18/03/2016 meeting. The...
[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
351         makePath('', 'suffix') -> 'suffix'
352         makePath('prefix', 'suffix') -> 'prefix.suffix'
353         makePath('prefix', '[1]') -> 'prefix[1]'
354
355     @param prefix:  The prefix to use. If it evaluates as false, the suffix
356                     is returned.
357     @type prefix:   str
358     @param suffix:  The suffix to use. It is either an identifier or an
359                     index in brackets.
360     @type suffix:   str
361     @return:        The path concatenation of prefix and suffix, with a
362                     dot if the suffix is not a bracketed index.
363     @rtype:         str
364
365     """
366     if not prefix:
367         rv = suffix
368     elif suffix[0] == '[':
369         rv = prefix + suffix
370     else:
371         rv = prefix + '.' + suffix
372     return rv
373
374
375 class Container(object):
376     """
377     This internal class is the base class for mappings and sequences.
378
379     @ivar path: A string which describes how to get
380     to this instance from the root of the hierarchy.
381
382     Example::
383
384         a.list.of[1].or['more'].elements
385     """
386     def __init__(self, parent):
387         """
388         Initialize an instance.
389
390         @param parent: The parent of this instance in the hierarchy.
391         @type parent: A L{Container} instance.
392         """
393         object.__setattr__(self, 'parent', parent)
394
395     def setPath(self, path):
396         """
397         Set the path for this instance.
398         @param path: The path - a string which describes how to get
399         to this instance from the root of the hierarchy.
400         @type path: str
401         """
402         object.__setattr__(self, 'path', path)
403
404     def evaluate(self, item):
405         """
406         Evaluate items which are instances of L{Reference} or L{Expression}.
407
408         L{Reference} instances are evaluated using L{Reference.resolve},
409         and L{Expression} instances are evaluated using
410         L{Expression.evaluate}.
411
412         @param item: The item to be evaluated.
413         @type item: any
414         @return: If the item is an instance of L{Reference} or L{Expression},
415         the evaluated value is returned, otherwise the item is returned
416         unchanged.
417         """
418         if isinstance(item, Reference):
419             item = item.resolve(self)
420         elif isinstance(item, Expression):
421             item = item.evaluate(self)
422         return item
423
424     def writeToStream(self, stream, indent, container):
425         """
426         Write this instance to a stream at the specified indentation level.
427
428         Should be redefined in subclasses.
429
430         @param stream: The stream to write to
431         @type stream: A writable stream (file-like object)
432         @param indent: The indentation level
433         @type indent: int
434         @param container: The container of this instance
435         @type container: L{Container}
436         @raise NotImplementedError: If a subclass does not override this
437         """
438         raise NotImplementedError
439
440     def writeValue(self, value, stream, indent):
441         if isinstance(self, Mapping):
442             indstr = ' '
443         else:
444             indstr = indent * '  '
445         if isinstance(value, Reference) or isinstance(value, Expression):
446             stream.write('%s%r%s' % (indstr, value, NEWLINE))
447         else:
448             if isinstance(value, str): # and not isWord(value):
449                 value = repr(value)
450             stream.write('%s%s%s' % (indstr, value, NEWLINE))
451
452 class Mapping(Container):
453     """
454     This internal class implements key-value mappings in configurations.
455     """
456
457     def __init__(self, parent=None):
458         """
459         Initialize an instance.
460
461         @param parent: The parent of this instance in the hierarchy.
462         @type parent: A L{Container} instance.
463         """
464         Container.__init__(self, parent)
465         object.__setattr__(self, 'path', '')
466         object.__setattr__(self, 'data', {})
467         object.__setattr__(self, 'order', [])   # to preserve ordering
468         object.__setattr__(self, 'comments', {})
469
470     def __delitem__(self, key):
471         """
472         Remove an item
473         """
474         data = object.__getattribute__(self, 'data')
475         if key not in data:
476             raise AttributeError(key)
477         order = object.__getattribute__(self, 'order')
478         comments = object.__getattribute__(self, 'comments')
479         del data[key]
480         order.remove(key)
481         del comments[key]
482
483     def __getitem__(self, key):
484         data = object.__getattribute__(self, 'data')
485         if key not in data:
486             raise AttributeError(key)
487         rv = data[key]
488         return self.evaluate(rv)
489
490     __getattr__ = __getitem__
491     
492     '''
493     def __getattribute__(self, name):
494         if name == "__dict__":
495             return {}
496         if name in ["__methods__", "__members__"]:
497             return []
498         #if name == "__class__":
499         #    return ''
500         data = object.__getattribute__(self, "data")
501         useData = data.has_key(name)
502         if useData:
503             rv = getattr(data, name)
504         else:
505             rv = object.__getattribute__(self, name)
506             if rv is None:
507                 raise AttributeError(name)
508         return rv
509     '''
510
511     def iteritems(self):
512         for key in self.keys():
513             yield(key, self[key])
514         raise StopIteration
515
516     def __contains__(self, item):
517         order = object.__getattribute__(self, 'order')
518         return item in order
519
520     def addMapping(self, key, value, comment, setting=False):
521         """
522         Add a key-value mapping with a comment.
523
524         @param key: The key for the mapping.
525         @type key: str
526         @param value: The value for the mapping.
527         @type value: any
528         @param comment: The comment for the key (can be None).
529         @type comment: str
530         @param setting: If True, ignore clashes. This is set
531         to true when called from L{__setattr__}.
532         @raise ConfigFormatError: If an existing key is seen
533         again and setting is False.
534         """
535         data = object.__getattribute__(self, 'data')
536         order = object.__getattribute__(self, 'order')
537         comments = object.__getattribute__(self, 'comments')
538
539         data[key] = value
540         if key not in order:
541             order.append(key)
542         elif not setting:
543             raise ConfigFormatError("repeated key: %s" % key)
544         comments[key] = comment
545
546     def __setattr__(self, name, value):
547         self.addMapping(name, value, None, True)
548
549     __setitem__ = __setattr__
550
551     def keys(self):
552         """
553         Return the keys in a similar way to a dictionary.
554         """
555         return object.__getattribute__(self, 'order')
556
557     def get(self, key, default=None):
558         """
559         Allows a dictionary-style get operation.
560         """
561         if key in self:
562             return self[key]
563         return default
564
565     def __str__(self):
566         return str(object.__getattribute__(self, 'data'))
567
568     def __repr__(self):
569         return repr(object.__getattribute__(self, 'data'))
570
571     def __len__(self):
572         return len(object.__getattribute__(self, 'order'))
573
574     def __iter__(self):
575         return self.iterkeys()
576
577     def iterkeys(self):
578         order = object.__getattribute__(self, 'order')
579         return order.__iter__()
580
581     def writeToStream(self, stream, indent, container):
582         """
583         Write this instance to a stream at the specified indentation level.
584
585         Should be redefined in subclasses.
586
587         @param stream: The stream to write to
588         @type stream: A writable stream (file-like object)
589         @param indent: The indentation level
590         @type indent: int
591         @param container: The container of this instance
592         @type container: L{Container}
593         """
594         indstr = indent * '  '
595         if len(self) == 0:
596             stream.write(' { }%s' % NEWLINE)
597         else:
598             if isinstance(container, Mapping):
599                 stream.write(NEWLINE)
600             stream.write('%s{%s' % (indstr, NEWLINE))
601             self.__save__(stream, indent + 1)
602             stream.write('%s}%s' % (indstr, NEWLINE))
603
604     def __save__(self, stream, indent=0):
605         """
606         Save this configuration to the specified stream.
607         @param stream: A stream to which the configuration is written.
608         @type stream: A write-only stream (file-like object).
609         @param indent: The indentation level for the output.
610         @type indent: int
611         """
612         indstr = indent * '  '
613         order = object.__getattribute__(self, 'order')
614         data = object.__getattribute__(self, 'data')
615         maxlen = 0 # max(map(lambda x: len(x), order))
616         for key in order:
617             comment = self.comments[key]
618             if isWord(key):
619                 skey = key
620             else:
621                 skey = repr(key)
622             if comment:
623                 stream.write('%s#%s' % (indstr, comment))
624             stream.write('%s%-*s :' % (indstr, maxlen, skey))
625             value = data[key]
626             if isinstance(value, Container):
627                 value.writeToStream(stream, indent, self)
628             else:
629                 self.writeValue(value, stream, indent)
630
631 class Config(Mapping):
632     """
633     This class represents a configuration, and is the only one which clients
634     need to interface to, under normal circumstances.
635     """
636
637     class Namespace(object):
638         """
639         This internal class is used for implementing default namespaces.
640
641         An instance acts as a namespace.
642         """
643         def __init__(self):
644             self.sys = sys
645             self.os = os
646
647     def __init__(self, streamOrFile=None, parent=None):
648         """
649         Initializes an instance.
650
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.
659         """
660         Mapping.__init__(self, parent)
661         object.__setattr__(self, 'reader', ConfigReader(self))
662         object.__setattr__(self, 'namespaces', [Config.Namespace()])
663         if streamOrFile is not None:
664             if isinstance(streamOrFile, str) or isinstance(streamOrFile, bytes):
665                 global streamOpener
666                 if streamOpener is None:
667                     streamOpener = defaultStreamOpener
668                 streamOrFile = streamOpener(streamOrFile)
669             load = object.__getattribute__(self, "load")
670             load(streamOrFile)
671
672     def load(self, stream):
673         """
674         Load the configuration from the specified stream. Multiple streams can
675         be used to populate the same instance, as long as there are no
676         clashing keys. The stream is closed.
677         @param stream: A stream from which the configuration is read.
678         @type stream: A read-only stream (file-like object).
679         @raise ConfigError: if keys in the loaded configuration clash with
680         existing keys.
681         @raise ConfigFormatError: if there is a syntax error in the stream.
682         """
683         reader = object.__getattribute__(self, 'reader')
684         reader.load(stream)
685         stream.close()
686
687     def addNamespace(self, ns, name=None):
688         """
689         Add a namespace to this configuration which can be used to evaluate
690         (resolve) dotted-identifier expressions.
691         @param ns: The namespace to be added.
692         @type ns: A module or other namespace suitable for passing as an
693         argument to vars().
694         @param name: A name for the namespace, which, if specified, provides
695         an additional level of indirection.
696         @type name: str
697         """
698         namespaces = object.__getattribute__(self, 'namespaces')
699         if name is None:
700             namespaces.append(ns)
701         else:
702             setattr(namespaces[0], name, ns)
703
704     def removeNamespace(self, ns, name=None):
705         """
706         Remove a namespace added with L{addNamespace}.
707         @param ns: The namespace to be removed.
708         @param name: The name which was specified when L{addNamespace} was
709         called.
710         @type name: str
711         """
712         namespaces = object.__getattribute__(self, 'namespaces')
713         if name is None:
714             namespaces.remove(ns)
715         else:
716             delattr(namespaces[0], name)
717
718     def __save__(self, stream, indent=0, no_close=False):
719         """
720         Save this configuration to the specified stream. The stream is
721         closed if this is the top-level configuration in the hierarchy.
722         L{Mapping.__save__} is called to do all the work.
723         @param stream: A stream to which the configuration is written.
724         @type stream: A write-only stream (file-like object).
725         @param indent: The indentation level for the output.
726         @type indent: int
727         """
728         Mapping.__save__(self, stream, indent)
729         if indent == 0 and not no_close:
730             stream.close()
731
732     def getByPath(self, path):
733         """
734         Obtain a value in the configuration via its path.
735         @param path: The path of the required value
736         @type path: str
737         @return the value at the specified path.
738         @rtype: any
739         @raise ConfigError: If the path is invalid
740         """
741         s = 'self.' + path
742         try:
743             return eval(s)
744         except Exception as e:
745             raise ConfigError(str(e))
746
747 class Sequence(Container):
748     """
749     This internal class implements a value which is a sequence of other values.
750     """
751     class SeqIter(object):
752         """
753         This internal class implements an iterator for a L{Sequence} instance.
754         """
755         def __init__(self, seq):
756             self.seq = seq
757             self.limit = len(object.__getattribute__(seq, 'data'))
758             self.index = 0
759
760         def __iter__(self):
761             return self
762
763         def next(self):
764             if self.index >= self.limit:
765                 raise StopIteration
766             rv = self.seq[self.index]
767             self.index += 1
768             return rv
769         
770         # This method is for python3 compatibility
771         def __next__(self): 
772             if self.index >= self.limit:
773                 raise StopIteration
774             rv = self.seq[self.index]
775             self.index += 1
776             return rv
777
778     def __init__(self, parent=None):
779         """
780         Initialize an instance.
781
782         @param parent: The parent of this instance in the hierarchy.
783         @type parent: A L{Container} instance.
784         """
785         Container.__init__(self, parent)
786         object.__setattr__(self, 'data', [])
787         object.__setattr__(self, 'comments', [])
788
789     def append(self, item, comment):
790         """
791         Add an item to the sequence.
792
793         @param item: The item to add.
794         @type item: any
795         @param comment: A comment for the item.
796         @type comment: str
797         """
798         data = object.__getattribute__(self, 'data')
799         comments = object.__getattribute__(self, 'comments')
800         data.append(item)
801         comments.append(comment)
802
803     def __getitem__(self, index):
804         data = object.__getattribute__(self, 'data')
805         try:
806             rv = data[index]
807         except (IndexError, KeyError, TypeError):
808             raise ConfigResolutionError('%r is not a valid index for %r' % (index, object.__getattribute__(self, 'path')))
809         if not isinstance(rv, list):
810             rv = self.evaluate(rv)
811         else:
812             # deal with a slice
813             result = []
814             for a in rv:
815                 result.append(self.evaluate(a))
816             rv = result
817         return rv
818
819     def __iter__(self):
820         return Sequence.SeqIter(self)
821
822     def __repr__(self):
823         return repr(object.__getattribute__(self, 'data'))
824
825     def __str__(self):
826         return str(self[:]) # using the slice evaluates the contents
827
828     def __len__(self):
829         return len(object.__getattribute__(self, 'data'))
830
831     def writeToStream(self, stream, indent, container):
832         """
833         Write this instance to a stream at the specified indentation level.
834
835         Should be redefined in subclasses.
836
837         @param stream: The stream to write to
838         @type stream: A writable stream (file-like object)
839         @param indent: The indentation level
840         @type indent: int
841         @param container: The container of this instance
842         @type container: L{Container}
843         """
844         indstr = indent * '  '
845         if len(self) == 0:
846             stream.write(' [ ]%s' % NEWLINE)
847         else:
848             if isinstance(container, Mapping):
849                 stream.write(NEWLINE)
850             stream.write('%s[%s' % (indstr, NEWLINE))
851             self.__save__(stream, indent + 1)
852             stream.write('%s]%s' % (indstr, NEWLINE))
853
854     def __save__(self, stream, indent):
855         """
856         Save this instance to the specified stream.
857         @param stream: A stream to which the configuration is written.
858         @type stream: A write-only stream (file-like object).
859         @param indent: The indentation level for the output, > 0
860         @type indent: int
861         """
862         if indent == 0:
863             raise ConfigError("sequence cannot be saved as a top-level item")
864         data = object.__getattribute__(self, 'data')
865         comments = object.__getattribute__(self, 'comments')
866         indstr = indent * '  '
867         for i in range(0, len(data)):
868             value = data[i]
869             comment = comments[i]
870             if comment:
871                 stream.write('%s#%s' % (indstr, comment))
872             if isinstance(value, Container):
873                 value.writeToStream(stream, indent, self)
874             else:
875                 self.writeValue(value, stream, indent)
876
877 class Reference(object):
878     """
879     This internal class implements a value which is a reference to another value.
880     """
881     def __init__(self, config, type, ident):
882         """
883         Initialize an instance.
884
885         @param config: The configuration which contains this reference.
886         @type config: A L{Config} instance.
887         @param type: The type of reference.
888         @type type: BACKTICK or DOLLAR
889         @param ident: The identifier which starts the reference.
890         @type ident: str
891         """
892         self.config = config
893         self.type = type
894         self.elements = [ident]
895
896     def addElement(self, type, ident):
897         """
898         Add an element to the reference.
899
900         @param type: The type of reference.
901         @type type: BACKTICK or DOLLAR
902         @param ident: The identifier which continues the reference.
903         @type ident: str
904         """
905         self.elements.append((type, ident))
906
907     def findConfig(self, container):
908         """
909         Find the closest enclosing configuration to the specified container.
910
911         @param container: The container to start from.
912         @type container: L{Container}
913         @return: The closest enclosing configuration, or None.
914         @rtype: L{Config}
915         """
916         while (container is not None) and not isinstance(container, Config):
917             container = object.__getattribute__(container, 'parent')
918         return container
919
920     def resolve(self, container):
921         """
922         Resolve this instance in the context of a container.
923
924         @param container: The container to resolve from.
925         @type container: L{Container}
926         @return: The resolved value.
927         @rtype: any
928         @raise ConfigResolutionError: If resolution fails.
929         """
930         rv = None
931         path = object.__getattribute__(container, 'path')
932         current = container
933         while current is not None:
934             if self.type == BACKTICK:
935                 namespaces = object.__getattribute__(current, 'namespaces')
936                 found = False
937                 for ns in namespaces:
938                     try:
939                         rv = eval(str(self)[1:-1], vars(ns))
940                         found = True
941                         break
942                     except:
943                         pass
944                 if found:
945                     break
946             else:
947                 key = self.elements[0]
948                 try:
949                     rv = current[key]
950                     for item in self.elements[1:]:
951                         key = item[1]
952                         rv = rv[key]
953                     break
954                 except:
955                     rv = None
956                     pass
957             current = object.__getattribute__(current, 'parent')
958         if current is None:
959             raise ConfigResolutionError("unable to evaluate %r in the configuration %s" % (self, path))
960         return rv
961
962     def __str__(self):
963         s = self.elements[0]
964         for tt, tv in self.elements[1:]:
965             if tt == DOT:
966                 s += '.%s' % tv
967             else:
968                 s += '[%r]' % tv
969         if self.type == BACKTICK:
970             return BACKTICK + s + BACKTICK
971         else:
972             return DOLLAR + s
973
974     def __repr__(self):
975         return self.__str__()
976
977 class Expression(object):
978     """
979     This internal class implements a value which is obtained by evaluating an expression.
980     """
981     def __init__(self, op, lhs, rhs):
982         """
983         Initialize an instance.
984
985         @param op: the operation expressed in the expression.
986         @type op: PLUS, MINUS, STAR, SLASH, MOD
987         @param lhs: the left-hand-side operand of the expression.
988         @type lhs: any Expression or primary value.
989         @param rhs: the right-hand-side operand of the expression.
990         @type rhs: any Expression or primary value.
991         """
992         self.op = op
993         self.lhs = lhs
994         self.rhs = rhs
995
996     def __str__(self):
997         return '%r %s %r' % (self.lhs, self.op, self.rhs)
998
999     def __repr__(self):
1000         return self.__str__()
1001
1002     def evaluate(self, container):
1003         """
1004         Evaluate this instance in the context of a container.
1005
1006         @param container: The container to evaluate in from.
1007         @type container: L{Container}
1008         @return: The evaluated value.
1009         @rtype: any
1010         @raise ConfigResolutionError: If evaluation fails.
1011         @raise ZeroDivideError: If division by zero occurs.
1012         @raise TypeError: If the operation is invalid, e.g.
1013         subtracting one string from another.
1014         """
1015         lhs = self.lhs
1016         if isinstance(lhs, Reference):
1017             lhs = lhs.resolve(container)
1018         elif isinstance(lhs, Expression):
1019             lhs = lhs.evaluate(container)
1020         rhs = self.rhs
1021         if isinstance(rhs, Reference):
1022             rhs = rhs.resolve(container)
1023         elif isinstance(rhs, Expression):
1024             rhs = rhs.evaluate(container)
1025         op = self.op
1026         if op == PLUS:
1027             rv = lhs + rhs
1028         elif op == MINUS:
1029             rv = lhs - rhs
1030         elif op == STAR:
1031             rv = lhs * rhs
1032         elif op == SLASH:
1033             rv = lhs / rhs
1034         else:
1035             rv = lhs % rhs
1036         return rv
1037
1038 class ConfigReader(object):
1039     """
1040     This internal class implements a parser for configurations.
1041     """
1042
1043     def __init__(self, config):
1044         self.filename = None
1045         self.config = config
1046         self.lineno = 0
1047         self.colno = 0
1048         self.lastc = None
1049         self.last_token = None
1050         self.commentchars = '#'
1051         self.whitespace = ' \t\r\n'
1052         self.quotes = '\'"'
1053         self.punct = ':-+*/%,.{}[]()@`$'
1054         self.digits = '0123456789'
1055         self.wordchars = '%s' % WORDCHARS # make a copy
1056         self.identchars = self.wordchars + self.digits
1057         self.pbchars = []
1058         self.pbtokens = []
1059         self.comment = None
1060
1061     def location(self):
1062         """
1063         Return the current location (filename, line, column) in the stream
1064         as a string.
1065
1066         Used when printing error messages,
1067
1068         @return: A string representing a location in the stream being read.
1069         @rtype: str
1070         """
1071         return "%s(%d,%d)" % (self.filename, self.lineno, self.colno)
1072
1073     def getChar(self):
1074         """
1075         Get the next char from the stream. Update line and column numbers
1076         appropriately.
1077
1078         @return: The next character from the stream.
1079         @rtype: str
1080         """
1081         if self.pbchars:
1082             c = self.pbchars.pop()
1083             if isinstance(c,bytes):
1084                 c = c.decode()
1085         else:
1086             c = self.stream.read(1)
1087             if isinstance(c,bytes):
1088                 try:
1089                     c = c.decode()
1090                 except:
1091                     import pdb;pdb.set_trace()
1092             self.colno += 1
1093             if c == '\n':
1094                 self.lineno += 1
1095                 self.colno = 1
1096         return c
1097
1098     def __repr__(self):
1099         return "<ConfigReader at 0x%08x>" % id(self)
1100
1101     __str__ = __repr__
1102
1103     def getToken(self):
1104         """
1105         Get a token from the stream. String values are returned in a form
1106         where you need to eval() the returned value to get the actual
1107         string. The return value is (token_type, token_value).
1108
1109         Multiline string tokenizing is thanks to David Janes (BlogMatrix)
1110
1111         @return: The next token.
1112         @rtype: A token tuple.
1113         """
1114         if self.pbtokens:
1115             return self.pbtokens.pop()
1116         stream = self.stream
1117         self.comment = None
1118         token = ''
1119         tt = EOF
1120         while True:
1121             c = self.getChar()
1122             if not c:
1123                 break
1124             elif c == '#':
1125                 if self.comment :
1126                     self.comment += '#' + stream.readline()
1127                 else :
1128                     self.comment = stream.readline()
1129                 self.lineno += 1
1130                 continue
1131             if c in self.quotes:
1132                 token = c
1133                 quote = c
1134                 tt = STRING
1135                 escaped = False
1136                 multiline = False
1137                 c1 = self.getChar()
1138                 if c1 == quote:
1139                     c2 = self.getChar()
1140                     if c2 == quote:
1141                         multiline = True
1142                         token += quote
1143                         token += quote
1144                     else:
1145                         self.pbchars.append(c2)
1146                         self.pbchars.append(c1)
1147                 else:
1148                     self.pbchars.append(c1)
1149                 while True:
1150                     c = self.getChar()
1151                     if not c:
1152                         break
1153                     token += c
1154                     if (c == quote) and not escaped:
1155                         if not multiline or (len(token) >= 6 and token.endswith(token[:3]) and token[-4] != '\\'):
1156                             break
1157                     if c == '\\':
1158                         escaped = not escaped
1159                     else:
1160                         escaped = False
1161                 if not c:
1162                     raise ConfigFormatError('%s: Unterminated quoted string: %r, %r' % (self.location(), token, c))
1163                 break
1164             if c in self.whitespace:
1165                 self.lastc = c
1166                 continue
1167             elif c in self.punct:
1168                 token = c
1169                 tt = c
1170                 if (self.lastc == ']') or (self.lastc in self.identchars):
1171                     if c == '[':
1172                         tt = LBRACK2
1173                     elif c == '(':
1174                         tt = LPAREN2
1175                 break
1176             elif c in self.digits:
1177                 token = c
1178                 tt = NUMBER
1179                 while True:
1180                     c = self.getChar()
1181                     if not c:
1182                         break
1183                     if c in self.digits:
1184                         token += c
1185                     elif (c == '.') and token.find('.') < 0:
1186                         token += c
1187                     else:
1188                         if c and (c not in self.whitespace):
1189                             self.pbchars.append(c)
1190                         break
1191                 break
1192             elif c in self.wordchars:
1193                 token = c
1194                 tt = WORD
1195                 c = self.getChar()
1196                 while c and (c in self.identchars):
1197                     token += c
1198                     c = self.getChar()
1199                 if c: # and c not in self.whitespace:
1200                     self.pbchars.append(c)
1201                 if token == "True":
1202                     tt = TRUE
1203                 elif token == "False":
1204                     tt = FALSE
1205                 elif token == "None":
1206                     tt = NONE
1207                 break
1208             else:
1209                 raise ConfigFormatError('%s: Unexpected character: %r' % (self.location(), c))
1210         if token:
1211             self.lastc = token[-1]
1212         else:
1213             self.lastc = None
1214         self.last_token = tt
1215         return (tt, token)
1216
1217     def load(self, stream, parent=None, suffix=None):
1218         """
1219         Load the configuration from the specified stream.
1220
1221         @param stream: A stream from which to load the configuration.
1222         @type stream: A stream (file-like object).
1223         @param parent: The parent of the configuration (to which this reader
1224         belongs) in the hierarchy. Specified when the configuration is
1225         included in another one.
1226         @type parent: A L{Container} instance.
1227         @param suffix: The suffix of this configuration in the parent
1228         configuration. Should be specified whenever the parent is not None.
1229         @raise ConfigError: If parent is specified but suffix is not.
1230         @raise ConfigFormatError: If there are syntax errors in the stream.
1231         """
1232         if parent is not None:
1233             if suffix is None:
1234                 raise ConfigError("internal error: load called with parent but no suffix")
1235             self.config.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1236         self.setStream(stream)
1237         self.token = self.getToken()
1238         self.parseMappingBody(self.config)
1239         if self.token[0] != EOF:
1240             raise ConfigFormatError('%s: expecting EOF, found %r' % (self.location(), self.token[1]))
1241
1242     def setStream(self, stream):
1243         """
1244         Set the stream to the specified value, and prepare to read from it.
1245
1246         @param stream: A stream from which to load the configuration.
1247         @type stream: A stream (file-like object).
1248         """
1249         self.stream = stream
1250         if hasattr(stream, 'name'):
1251             filename = stream.name
1252         else:
1253             filename = '?'
1254         self.filename = filename
1255         self.lineno = 1
1256         self.colno = 1
1257
1258     def match(self, t):
1259         """
1260         Ensure that the current token type matches the specified value, and
1261         advance to the next token.
1262
1263         @param t: The token type to match.
1264         @type t: A valid token type.
1265         @return: The token which was last read from the stream before this
1266         function is called.
1267         @rtype: a token tuple - see L{getToken}.
1268         @raise ConfigFormatError: If the token does not match what's expected.
1269         """
1270         if self.token[0] != t:
1271             raise ConfigFormatError("%s: expecting %s, found %r" % (self.location(), t, self.token[1]))
1272         rv = self.token
1273         self.token = self.getToken()
1274         return rv
1275
1276     def parseMappingBody(self, parent):
1277         """
1278         Parse the internals of a mapping, and add entries to the provided
1279         L{Mapping}.
1280
1281         @param parent: The mapping to add entries to.
1282         @type parent: A L{Mapping} instance.
1283         """
1284         while self.token[0] in [WORD, STRING]:
1285             self.parseKeyValuePair(parent)
1286
1287     def parseKeyValuePair(self, parent):
1288         """
1289         Parse a key-value pair, and add it to the provided L{Mapping}.
1290
1291         @param parent: The mapping to add entries to.
1292         @type parent: A L{Mapping} instance.
1293         @raise ConfigFormatError: if a syntax error is found.
1294         """
1295         comment = self.comment
1296         tt, tv = self.token
1297         if tt == WORD:
1298             key = tv
1299             suffix = tv
1300         elif tt == STRING:
1301             key = eval(tv)
1302             suffix = '[%s]' % tv
1303         else:
1304             msg = "%s: expecting word or string, found %r"
1305             raise ConfigFormatError(msg % (self.location(), tv))
1306         self.token = self.getToken()
1307         # for now, we allow key on its own as a short form of key : True
1308         if self.token[0] == COLON:
1309             self.token = self.getToken()
1310             value = self.parseValue(parent, suffix)
1311         else:
1312             value = True
1313         try:
1314             parent.addMapping(key, value, comment)
1315         except Exception as e:
1316             raise ConfigFormatError("%s: %s, %r" % (self.location(), e,
1317                                     self.token[1]))
1318         tt = self.token[0]
1319         if tt not in [EOF, WORD, STRING, RCURLY, COMMA]:
1320             msg = "%s: expecting one of EOF, WORD, STRING, \
1321 RCURLY, COMMA, found %r"
1322             raise ConfigFormatError(msg  % (self.location(), self.token[1]))
1323         if tt == COMMA:
1324             self.token = self.getToken()
1325
1326     def parseValue(self, parent, suffix):
1327         """
1328         Parse a value.
1329
1330         @param parent: The container to which the value will be added.
1331         @type parent: A L{Container} instance.
1332         @param suffix: The suffix for the value.
1333         @type suffix: str
1334         @return: The value
1335         @rtype: any
1336         @raise ConfigFormatError: if a syntax error is found.
1337         """
1338         tt = self.token[0]
1339         if tt in [STRING, WORD, NUMBER, LPAREN, DOLLAR,
1340                   TRUE, FALSE, NONE, BACKTICK, MINUS]:
1341             rv = self.parseScalar()
1342         elif tt == LBRACK:
1343             rv = self.parseSequence(parent, suffix)
1344         elif tt in [LCURLY, AT]:
1345             rv = self.parseMapping(parent, suffix)
1346         else:
1347             raise ConfigFormatError("%s: unexpected input: %r" %
1348                (self.location(), self.token[1]))
1349         return rv
1350
1351     def parseSequence(self, parent, suffix):
1352         """
1353         Parse a sequence.
1354
1355         @param parent: The container to which the sequence will be added.
1356         @type parent: A L{Container} instance.
1357         @param suffix: The suffix for the value.
1358         @type suffix: str
1359         @return: a L{Sequence} instance representing the sequence.
1360         @rtype: L{Sequence}
1361         @raise ConfigFormatError: if a syntax error is found.
1362         """
1363         rv = Sequence(parent)
1364         rv.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
1365         self.match(LBRACK)
1366         comment = self.comment
1367         tt = self.token[0]
1368         while tt in [STRING, WORD, NUMBER, LCURLY, LBRACK, LPAREN, DOLLAR,
1369                      TRUE, FALSE, NONE, BACKTICK]:
1370             suffix = '[%d]' % len(rv)
1371             value = self.parseValue(parent, suffix)
1372             rv.append(value, comment)
1373             tt = self.token[0]
1374             comment = self.comment
1375             if tt == COMMA:
1376                 self.match(COMMA)
1377                 tt = self.token[0]
1378                 comment = self.comment
1379                 continue
1380         self.match(RBRACK)
1381         return rv
1382
1383     def parseMapping(self, parent, suffix):
1384         """
1385         Parse a mapping.
1386
1387         @param parent: The container to which the mapping will be added.
1388         @type parent: A L{Container} instance.
1389         @param suffix: The suffix for the value.
1390         @type suffix: str
1391         @return: a L{Mapping} instance representing the mapping.
1392         @rtype: L{Mapping}
1393         @raise ConfigFormatError: if a syntax error is found.
1394         """
1395         if self.token[0] == LCURLY:
1396             self.match(LCURLY)
1397             rv = Mapping(parent)
1398             rv.setPath(
1399                makePath(object.__getattribute__(parent, 'path'), suffix))
1400             self.parseMappingBody(rv)
1401             self.match(RCURLY)
1402         else:
1403             self.match(AT)
1404             _, fn = self.match(STRING)
1405             rv = Config(eval(fn), parent)
1406         return rv
1407
1408     def parseScalar(self):
1409         """
1410         Parse a scalar - a terminal value such as a string or number, or
1411         an L{Expression} or L{Reference}.
1412
1413         @return: the parsed scalar
1414         @rtype: any scalar
1415         @raise ConfigFormatError: if a syntax error is found.
1416         """
1417         lhs = self.parseTerm()
1418         tt = self.token[0]
1419         while tt in [PLUS, MINUS]:
1420             self.match(tt)
1421             rhs = self.parseTerm()
1422             lhs = Expression(tt, lhs, rhs)
1423             tt = self.token[0]
1424         return lhs
1425
1426     def parseTerm(self):
1427         """
1428         Parse a term in an additive expression (a + b, a - b)
1429
1430         @return: the parsed term
1431         @rtype: any scalar
1432         @raise ConfigFormatError: if a syntax error is found.
1433         """
1434         lhs = self.parseFactor()
1435         tt = self.token[0]
1436         while tt in [STAR, SLASH, MOD]:
1437             self.match(tt)
1438             rhs = self.parseFactor()
1439             lhs = Expression(tt, lhs, rhs)
1440             tt = self.token[0]
1441         return lhs
1442
1443     def parseFactor(self):
1444         """
1445         Parse a factor in an multiplicative expression (a * b, a / b, a % b)
1446
1447         @return: the parsed factor
1448         @rtype: any scalar
1449         @raise ConfigFormatError: if a syntax error is found.
1450         """
1451         tt = self.token[0]
1452         if tt in [NUMBER, WORD, STRING, TRUE, FALSE, NONE]:
1453             rv = self.token[1]
1454             if tt != WORD:
1455                 rv = eval(rv)
1456             self.match(tt)
1457         elif tt == LPAREN:
1458             self.match(LPAREN)
1459             rv = self.parseScalar()
1460             self.match(RPAREN)
1461         elif tt == DOLLAR:
1462             self.match(DOLLAR)
1463             rv = self.parseReference(DOLLAR)
1464         elif tt == BACKTICK:
1465             self.match(BACKTICK)
1466             rv = self.parseReference(BACKTICK)
1467             self.match(BACKTICK)
1468         elif tt == MINUS:
1469             self.match(MINUS)
1470             rv = -self.parseScalar()
1471         else:
1472             raise ConfigFormatError("%s: unexpected input: %r" %
1473                (self.location(), self.token[1]))
1474         return rv
1475
1476     def parseReference(self, type):
1477         """
1478         Parse a reference.
1479
1480         @return: the parsed reference
1481         @rtype: L{Reference}
1482         @raise ConfigFormatError: if a syntax error is found.
1483         """
1484         word = self.match(WORD)
1485         rv = Reference(self.config, type, word[1])
1486         while self.token[0] in [DOT, LBRACK2]:
1487             self.parseSuffix(rv)
1488         return rv
1489
1490     def parseSuffix(self, ref):
1491         """
1492         Parse a reference suffix.
1493
1494         @param ref: The reference of which this suffix is a part.
1495         @type ref: L{Reference}.
1496         @raise ConfigFormatError: if a syntax error is found.
1497         """
1498         tt = self.token[0]
1499         if tt == DOT:
1500             self.match(DOT)
1501             word = self.match(WORD)
1502             ref.addElement(DOT, word[1])
1503         else:
1504             self.match(LBRACK2)
1505             tt, tv = self.token
1506             if tt not in [NUMBER, STRING]:
1507                 raise ConfigFormatError("%s: expected number or string, found %r" % (self.location(), tv))
1508             self.token = self.getToken()
1509             tv = eval(tv)
1510             self.match(RBRACK)
1511             ref.addElement(LBRACK, tv)
1512
1513 def defaultMergeResolve(map1, map2, key):
1514     """
1515     A default resolver for merge conflicts. Returns a string
1516     indicating what action to take to resolve the conflict.
1517
1518     @param map1: The map being merged into.
1519     @type map1: L{Mapping}.
1520     @param map2: The map being used as the merge operand.
1521     @type map2: L{Mapping}.
1522     @param key: The key in map2 (which also exists in map1).
1523     @type key: str
1524     @return: One of "merge", "append", "mismatch" or "overwrite"
1525              indicating what action should be taken. This should
1526              be appropriate to the objects being merged - e.g.
1527              there is no point returning "merge" if the two objects
1528              are instances of L{Sequence}.
1529     @rtype: str
1530     """
1531     obj1 = map1[key]
1532     obj2 = map2[key]
1533     if isinstance(obj1, Mapping) and isinstance(obj2, Mapping):
1534         rv = "merge"
1535     elif isinstance(obj1, Sequence) and isinstance(obj2, Sequence):
1536         rv = "append"
1537     else:
1538         rv = "mismatch"
1539     return rv
1540
1541 def overwriteMergeResolve(map1, map2, key):
1542     """
1543     An overwriting resolver for merge conflicts. Calls L{defaultMergeResolve},
1544     but where a "mismatch" is detected, returns "overwrite" instead.
1545
1546     @param map1: The map being merged into.
1547     @type map1: L{Mapping}.
1548     @param map2: The map being used as the merge operand.
1549     @type map2: L{Mapping}.
1550     @param key: The key in map2 (which also exists in map1).
1551     @type key: str
1552     """
1553     rv = defaultMergeResolve(map1, map2, key)
1554     if rv == "mismatch":
1555         rv = "overwrite"
1556     return rv
1557
1558 class ConfigMerger(object):
1559     """
1560     This class is used for merging two configurations. If a key exists in the
1561     merge operand but not the merge target, then the entry is copied from the
1562     merge operand to the merge target. If a key exists in both configurations,
1563     then a resolver (a callable) is called to decide how to handle the
1564     conflict.
1565     """
1566
1567     def __init__(self, resolver=defaultMergeResolve):
1568         """
1569         Initialise an instance.
1570
1571         @param resolver:
1572         @type resolver: A callable which takes the argument list
1573         (map1, map2, key) where map1 is the mapping being merged into,
1574         map2 is the merge operand and key is the clashing key. The callable
1575         should return a string indicating how the conflict should be resolved.
1576         For possible return values, see L{defaultMergeResolve}. The default
1577         value preserves the old behaviour
1578         """
1579         self.resolver = resolver
1580
1581     def merge(self, merged, mergee):
1582         """
1583         Merge two configurations. The second configuration is unchanged,
1584         and the first is changed to reflect the results of the merge.
1585
1586         @param merged: The configuration to merge into.
1587         @type merged: L{Config}.
1588         @param mergee: The configuration to merge.
1589         @type mergee: L{Config}.
1590         """
1591         self.mergeMapping(merged, mergee)
1592
1593     def overwriteKeys(self, map1, seq2):
1594         """
1595         Renint variables. The second mapping is unchanged,
1596         and the first is changed depending the keys of the second mapping.
1597         @param map1: The mapping to reinit keys into.
1598         @type map1: L{Mapping}.
1599         @param map2: The mapping container reinit information.
1600         @type map2: L{Mapping}.
1601         """
1602
1603         overwrite_list = object.__getattribute__(seq2, 'data')
1604         for overwrite_instruction in overwrite_list:
1605             object.__setattr__(overwrite_instruction, 'parent', map1)
1606             if "__condition__" in overwrite_instruction.keys():
1607                 overwrite_condition = overwrite_instruction["__condition__"]
1608                 if eval(overwrite_condition, globals(), map1):
1609                     for key in overwrite_instruction.keys():
1610                         if key == "__condition__":
1611                             continue
1612                         try:
1613                             exec( 'map1.' + key + " = " + repr(overwrite_instruction[key]))
1614                         except:
1615                             exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1616             else:
1617                 for key in overwrite_instruction.keys():
1618                     try:
1619                         exec('map1.' + key + " = " + repr(overwrite_instruction[key]))
1620                     except:
1621                         exec('map1.' + key + " = " + str(overwrite_instruction[key]))
1622
1623     def mergeMapping(self, map1, map2):
1624         """
1625         Merge two mappings recursively. The second mapping is unchanged,
1626         and the first is changed to reflect the results of the merge.
1627
1628         @param map1: The mapping to merge into.
1629         @type map1: L{Mapping}.
1630         @param map2: The mapping to merge.
1631         @type map2: L{Mapping}.
1632         """
1633         keys = map1.keys()
1634         global __resolveOverwrite__
1635         for key in map2.keys():
1636             if __resolveOverwrite__ and key == "__overwrite__":
1637                 self.overwriteKeys(map1,map2[key])
1638
1639             elif key not in keys:
1640                 map1[key] = map2[key]
1641                 if isinstance(map1[key], Container) :
1642                     object.__setattr__(map1[key], 'parent', map1)
1643             else:
1644                 obj1 = map1[key]
1645                 obj2 = map2[key]
1646                 decision = self.resolver(map1, map2, key)
1647                 if decision == "merge":
1648                     self.mergeMapping(obj1, obj2)
1649                 elif decision == "append":
1650                     self.mergeSequence(obj1, obj2)
1651                 elif decision == "overwrite":
1652                     map1[key] = obj2
1653                     if isinstance(map1[key], Container):
1654                         object.__setattr__(map1[key], 'parent', map1)
1655                 elif decision == "mismatch":
1656                     self.handleMismatch(obj1, obj2)
1657                 else:
1658                     msg = "unable to merge: don't know how to implement %r"
1659                     raise ValueError(msg % decision)
1660
1661     def mergeSequence(self, seq1, seq2):
1662         """
1663         Merge two sequences. The second sequence is unchanged,
1664         and the first is changed to have the elements of the second
1665         appended to it.
1666
1667         @param seq1: The sequence to merge into.
1668         @type seq1: L{Sequence}.
1669         @param seq2: The sequence to merge.
1670         @type seq2: L{Sequence}.
1671         """
1672         data1 = object.__getattribute__(seq1, 'data')
1673         data2 = object.__getattribute__(seq2, 'data')
1674         for obj in data2:
1675             data1.append(obj)
1676         comment1 = object.__getattribute__(seq1, 'comments')
1677         comment2 = object.__getattribute__(seq2, 'comments')
1678         for obj in comment2:
1679             comment1.append(obj)
1680
1681     def handleMismatch(self, obj1, obj2):
1682         """
1683         Handle a mismatch between two objects.
1684
1685         @param obj1: The object to merge into.
1686         @type obj1: any
1687         @param obj2: The object to merge.
1688         @type obj2: any
1689         """
1690         raise ConfigError("unable to merge %r with %r" % (obj1, obj2))
1691
1692 class ConfigList(list):
1693     """
1694     This class implements an ordered list of configurations and allows you
1695     to try getting the configuration from each entry in turn, returning
1696     the first successfully obtained value.
1697     """
1698
1699     def getByPath(self, path):
1700         """
1701         Obtain a value from the first configuration in the list which defines
1702         it.
1703
1704         @param path: The path of the value to retrieve.
1705         @type path: str
1706         @return: The value from the earliest configuration in the list which
1707         defines it.
1708         @rtype: any
1709         @raise ConfigError: If no configuration in the list has an entry with
1710         the specified path.
1711         """
1712         found = False
1713         rv = None
1714         for entry in self:
1715             try:
1716                 rv = entry.getByPath(path)
1717                 found = True
1718                 break
1719             except ConfigError:
1720                 pass
1721         if not found:
1722             raise ConfigError("unable to resolve %r" % path)
1723         return rv