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