]> SALOME platform Git repositories - tools/sat.git/commitdiff
Salome HOME
fix isatty andtry src/pyconf_0_3_9_1.py TODO to fix 'Circular reference'
authorChristian Van Wambeke <chvw@orange.fr>
Wed, 9 May 2018 11:16:14 +0000 (13:16 +0200)
committerChristian Van Wambeke <chvw@orange.fr>
Wed, 9 May 2018 11:16:14 +0000 (13:16 +0200)
19 files changed:
commands/application.py
commands/config.py
commands/find_duplicates.py
commands/generate.py
commands/jobs.py
commands/test.py
src/colorama/ansitowin32.py
src/coloringSat.py
src/configManager.py
src/debug.py
src/loggingSat.py
src/pyconf.py
src/pyconf_0_3_7_1.py [new file with mode: 0644]
src/pyconf_0_3_9_1.py [new file with mode: 0644]
src/xmlManager.py
test/config_0_3_9/test_config.py
test/test_020_debug.py
test/test_025_pyconf.py
test/test_030_pyconf_0_3_9.py

index a53b7305cf27bbae4d72c1c0e3550621deaad221..d94271b39e0950ed3a12a393e2ef36231c00a1aa 100644 (file)
@@ -26,7 +26,7 @@ import os
 import getpass
 import subprocess
 
-import src.ElementTree as ET
+import src.ElementTree as ETREE
 import src.debug as DBG
 import src.returnCode as RCO
 import src.utilsSat as UTS
@@ -276,7 +276,7 @@ def customize_app(config, appli_dir, logger):
 
     def add_simple_node(parent, node_name, text=None):
         """shortcut method to create a node"""
-        n = etree.Element(node_name)
+        n = ETREE.Element(node_name)
         if text is not None:
             try:
                 n.text = text.strip("\n\t").decode("UTF-8")
@@ -289,7 +289,7 @@ def customize_app(config, appli_dir, logger):
 
     # read the app file
     app_file = os.path.join(appli_dir, "SalomeApp.xml")
-    tree = etree.parse(app_file)
+    tree = ETREE.parse(app_file)
     document = tree.getroot()
     assert document is not None, "document tag not found"
 
@@ -307,7 +307,7 @@ def customize_app(config, appli_dir, logger):
     # write the file
     f = open(app_file, "w")
     f.write("<?xml version='1.0' encoding='utf-8'?>\n")
-    f.write(etree.tostring(document, encoding='utf-8'))
+    f.write(ETREE.tostring(document, encoding='utf-8'))
     f.close()
 
 def generate_application(config, appli_dir, config_file, logger):
index 2d29ff7820069a299bbb1872b744b510187420c5..d4e8e1296a2bc1d7764ee1458a55f844323b2a1d 100644 (file)
@@ -108,15 +108,18 @@ If a name is given the new config file takes the given name."""))
     if options.value:
         if options.value == ".":
             # if argument is ".", print all the config
+            msg = ""
             for val in sorted(config.keys()):
-                CFGMGR.print_value(config, val, logger, not options.no_label)
+                msg += CFGMGR.getStrConfigValue(config, val, not options.no_label)
         else:
-            CFGMGR.print_value(config, options.value, logger, not options.no_label, 
-                        level=0, show_full_path=False)
+            msg = CFGMGR.getStrConfigValue(config, options.value, not options.no_label, 
+                                           level=0, show_full_path=False)
+        logger.info(msg)
 
     if options.debug:
-        CFGMGR.print_debug(config, str(options.debug), logger, not options.no_label, 
-                    level=0, show_full_path=False)
+        msg = CFGMGR.getStrConfigDebug(config, str(options.debug), not options.no_label, 
+                                       level=0, show_full_path=False)
+        logger.info(msg)
     
     # case : edit user pyconf file or application file
     elif options.edit:
index 71d8fa3d11091010a45a9105a04c6df53dc3a6b8..cb237a93e4670ef6e45915a48d05f27e0cbd914f 100644 (file)
@@ -282,7 +282,7 @@ class Progress_bar:
         self.length = length
         self.logger = logger
         if (self.valMax - self.valMin) <= 0 or length <= 0:
-            out_err = _('ERROR: Wrong init values for the progress bar\n')
+            out_err = _('Wrong init values for the progress bar\n')
             raise Exception(out_err)
         
     def display_value_progression(self,val):
index 9a4e855a9c0f0c258bbe31bd72051d507d17b57a..6a35475b4d86d693374dee37f987df82cb511e22 100644 (file)
@@ -125,7 +125,6 @@ class Command(_BaseCommand):
             result = str(exc)
 
         if result != RCO._OK_STATUS:
-            result = _("ERROR: %s") % result
             details.append([product, result])
 
     if len(details) != 0:
index 29a09e0b20ae0c6f2cc924e4904e2abb79f6c138..7fdfbc158df5405e7fc45095c2ff29f7b25a00c0 100644 (file)
@@ -28,7 +28,7 @@ import re
 
 # import paramiko later
   
-import src.ElementTree as etree
+import src.ElementTree as ETREE
 import src.debug as DBG
 import src.returnCode as RCO
 import src.utilsSat as UTS
@@ -1510,7 +1510,7 @@ class Gui(object):
         Get all the first information needed for each file and write the 
         first version of the files   
 
-        :param xml_node_jobs: (etree.Element) 
+        :param xml_node_jobs: (ETREE.Element) 
           the node corresponding to a job
         :param l_jobs_not_today: (list) 
           the list of jobs that do not run today
@@ -1712,7 +1712,7 @@ class Gui(object):
             oExpr = re.compile(regex)
             if dirname == "TEST" and oExpr.search(file_name):
                 # find the res of the command
-                prod_node = etree.parse(file_path).getroot().find("product")
+                prod_node = ETREE.parse(file_path).getroot().find("product")
                 res_test = prod_node.attrib["global_res"]
                 # find the number of fails
                 testbase_node = prod_node.find("tests").find("testbase")
index e614f64fd59aa6095f7a3072e0481afbe178750b..e20a8f1e1e9733bf253ea61b10e5d2d803af7b24 100644 (file)
@@ -27,7 +27,7 @@ import src.debug as DBG
 import src.returnCode as RCO
 import src.utilsSat as UTS
 from src.salomeTools import _BaseCommand
-import src.ElementTree as etree
+import src.ElementTree as ETREE
 import src.xmlManager as XMLMGR
 import src.architecture as ARCH
 import src.test_module as TMOD
@@ -437,7 +437,7 @@ def create_test_report(config,
     Creates the XML report for a product.
     """
     ASNODE = XMLMGR.add_simple_node # shortcut
-    ETELEM = etree.Element # shortcut
+    ETELEM = ETREE.Element # shortcut
 
     # get the date and hour of the launching of the command, in order to keep
     # history
@@ -454,7 +454,7 @@ def create_test_report(config,
         prod_node = ETELEM("product", name=application_name, build=xmlname)
         root.append(prod_node)
     else:
-        root = etree.parse(xml_history_path).getroot()
+        root = ETREE.parse(xml_history_path).getroot()
         prod_node = root.find("product")
     
     prod_node.attrib["history_file"] = os.path.basename(xml_history_path)
index b7ff6f2136eec3530563c0fedf226b4b00c52d9d..ca3c0db951f22c8e4340db5601f83fa86a7d07a4 100644 (file)
@@ -12,13 +12,15 @@ winterm = None
 if windll is not None:
     winterm = WinTerm()
 
-
 def is_stream_closed(stream):
     return not hasattr(stream, 'closed') or stream.closed
 
 
 def is_a_tty(stream):
-    return hasattr(stream, 'isatty') and stream.isatty()
+    res = hasattr(stream, 'isatty') and stream.isatty()
+    # import src.debug as DBG # avoid cross import
+    # DBG.write("is_a_tty %s" % type(stream), res, True)
+    return res
 
 
 class StreamWrapper(object):
index 721adc9072e37887bd777017746402badc8fe0ba..5bc87c6995417311a59ecb46231ec9c21bd680b0 100755 (executable)
@@ -55,6 +55,8 @@ from colorama import Style as ST
 #from colorama import AnsiToWin32
 from colorama import AnsiToWin32 # debug is os.name == 'nt' ?
 
+verbose = False
+
 CLRM.init(wrap=False) # choose NO wrapping
 
 """
@@ -93,30 +95,10 @@ _tags = (
   ("<KO>", FG.RED + ST.BRIGHT + "KO" + ST.RESET_ALL),
 )
 
-# _tagsNone = ((i, "") for i,j in _tags) # to clean tags when log not tty
+# _tagsNone = ( (i, "") for i,j in _tags ) # to clean tags when log not tty
 # reversed order matters for item replaces backward to no color
-_tagsNone = reversed( (
-  ("<black>", ""),
-  ("<red>", ""),
-  ("<green>", ""),
-  ("<yellow>", ""),
-  ("<blue>", ""),
-  ("<magenta>", ""),
-  ("<cyan>", ""),
-  ("<white>", ""),
-  ("<bright>", ""),
-  ("<normal>", ""),
-  ("<reset>", ""),
-  ("<info>", ""),
-  ("<header>", ""),
-  ("<label>", ""),
-  ("<success>", ""),
-  ("<warning>", ""),
-  ("<error>", ""),
-  ("<critical>", ""),
-  ("<OK>", "OK"),
-  ("<KO>", "KO"),
-) )
+_tagsNone = tuple( reversed( [(i, "") for i, j in _tags] ) )
+
 
 def indent(msg, nb, car=" "):
   """indent nb car (spaces) multi lines message except first one"""
@@ -164,6 +146,9 @@ def toColor(msg):
   example:
   >> sat compile SALOME > log.txt
   """
+  if verbose:
+    import src.debug as DBG # avoid cross import
+    DBG.write("toColor isatty %s %s" % ('isatty' in dir(sys.stdout), sys.stdout.isatty()), msg[0:40])
   if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
     # clean the message color (if the terminal is redirected by user)
     return replace(msg, _tagsNone)
index a58167386e84389a8fe8c6e6a5e1c67f65abc15f..44d36cb984268f670094a7159f5d30423f0efc9f 100644 (file)
@@ -270,7 +270,7 @@ class ConfigManager:
         
         for project_pyconf_path in cfg.PROJECTS.project_file_paths:
             if not os.path.exists(project_pyconf_path):
-                msg = _("Cannot find project file %s, Ignored.") % UTS.red(project_pyconf_path)
+                msg = _("Cannot find project file %s, Ignored.") % UTS.header(project_pyconf_path)
                 self.logger.warning(msg)
                 continue
             project_name = os.path.basename(project_pyconf_path)[:-len(".pyconf")]
@@ -655,10 +655,13 @@ def getConfigColored(config, path, stream, show_label=False, level=0, show_full_
     tab_level = "  " * level
     
     # call to the function that gets the value of the path.
-    try:
+    if level == 0:
+      val = config.getByPath(path) # could raise error first level
+    else: # better stream trace error and continue
+      try:
         val = config.getByPath(path)
-    except Exception as e:
-        stream.write(tab_level + "<header>%s: <red>ERROR %s<reset>\n" % (vname, str(e)))
+      except Exception as e:
+        stream.write(tab_level + "<header>%s: <red>!!! ERROR: %s !!!<reset>\n" % (vname, str(e)))
         return
 
     # in this case, display only the value
@@ -682,7 +685,7 @@ def getConfigColored(config, path, stream, show_label=False, level=0, show_full_
     else: # case where val is just a str
         stream.write("%s\n" % val)
         
-def print_value(config, path, logger, show_label=False, level=0, show_full_path=False):
+def getStrConfigValue(config, path, show_label=False, level=0, show_full_path=False):
     """
     print a colored representation value from a config pyconf instance.
     used recursively from the initial path.
@@ -692,11 +695,10 @@ def print_value(config, path, logger, show_label=False, level=0, show_full_path=
     outStream = DBG.OutStream()
     getConfigColored(config, path, outStream, show_label, level, show_full_path)
     res = outStream.getvalue() # stream not closed
-    logger.info(res)
-    return
+    return res
 
      
-def print_debug(config, aPath, logger, show_label=False, level=0, show_full_path=False):
+def getStrConfigDebug(config, aPath, show_label=False, level=0, show_full_path=False):
     """
     logger output for debugging a config/pyconf
     lines contains: path : expression --> 'evaluation'
@@ -716,8 +718,7 @@ def print_debug(config, aPath, logger, show_label=False, level=0, show_full_path
     outStream = DBG.OutStream()
     DBG.saveConfigDbg(val, outStream, path=path)
     res = outStream.value
-    logger.info(res)
-    return
+    return res
 
 
 def get_config_children(config, args):
index d194b2408d491b1349c7b3aa607d96d81924a815..2231321448ebcd5eadd13184e1c2bad83e7333a5 100644 (file)
@@ -190,9 +190,9 @@ def _saveConfigRecursiveDbg(config, aStream, indent, path):
     if "Reference" in strType:
       try:
         #evaluate = value.resolve(config)
-        aStream.write("<blue>%s%s<reset> : %s <yellow>--> '%s'<reset>\n" % (indstr, path, config, str(config)))
+        aStream.write("<header>%s%s<reset> : %s <yellow>--> '%s'<reset>\n" % (indstr, path, config, str(config)))
       except Exception as e:  
-        aStream.write("<blue>%s%s<reset> : <red>!!! ERROR: %s !!!<reset>\n" % (indstr, path, e.message))     
+        aStream.write("<header>%s%s<reset> : <red>!!! ERROR: %s !!!<reset>\n" % (indstr, path, e.message))     
       return
     '''
     
@@ -200,7 +200,7 @@ def _saveConfigRecursiveDbg(config, aStream, indent, path):
       order = object.__getattribute__(config, 'order')
       data = object.__getattribute__(config, 'data')
     except:
-      aStream.write("<blue>%s%s<reset> : '%s'\n" % (indstr, path, str(config)))
+      aStream.write("<header>%s%s<reset> : '%s'\n" % (indstr, path, str(config)))
       return     
     for key in sorted(data): #order): # data as sort alphabetical, order as initial order
       value = data[key]
@@ -219,21 +219,21 @@ def _saveConfigRecursiveDbg(config, aStream, indent, path):
       if "Expression" in strType:
         try:
           evaluate = value.evaluate(config)
-          aStream.write("<blue>%s%s.%s<reset> : %s <yellow>--> '%s'<reset>\n" % (indstr, path, key, str(value), evaluate))
+          aStream.write("<header>%s%s.%s<reset> : %s <yellow>--> '%s'<reset>\n" % (indstr, path, key, str(value), evaluate))
         except Exception as e:      
-          aStream.write("<blue>%s%s.%s<reset> : <red>!!! ERROR: %s !!!<reset>\n" % (indstr, path, key, e.message))     
+          aStream.write("<header>%s%s.%s<reset> : <red>!!! ERROR: %s !!!<reset>\n" % (indstr, path, key, e.message))     
         continue
       if "Reference" in strType:
         try:
           evaluate = value.resolve(config)
-          aStream.write("<blue>%s%s.%s<reset> : %s <yellow>--> '%s'<reset>\n" % (indstr, path, key, str(value), evaluate))
+          aStream.write("<header>%s%s.%s<reset> : %s <yellow>--> '%s'<reset>\n" % (indstr, path, key, str(value), evaluate))
         except Exception as e:  
-          aStream.write("<blue>%s%s.%s<reset> : <red>!!! ERROR: %s !!!<reset>\n" % (indstr, path, key, e.message))     
+          aStream.write("<header>%s%s.%s<reset> : <red>!!! ERROR: %s !!!<reset>\n" % (indstr, path, key, e.message))     
         continue
       if type(value) in [str, bool, int, type(None), unicode]:
-        aStream.write("<blue>%s%s.%s<reset> : '%s'\n" % (indstr, path, key, str(value)))
+        aStream.write("<header>%s%s.%s<reset> : '%s'\n" % (indstr, path, key, str(value)))
         continue
       try:
         aStream.write("<red>!!! TODO fix that<reset> %s %s%s.%s : %s\n" % (type(value), indstr, path, key, str(value)))
       except Exception as e:      
-        aStream.write("<blue>%s%s.%s<reset> : <red>!!! %s<reset>\n" % (indstr, path, key, e.message))
+        aStream.write("<header>%s%s.%s<reset> : <red>!!! %s<reset>\n" % (indstr, path, key, e.message))
index 6242bfe262a9d5dd9d50a1bbb88334dcccdf7624..53d80e598950d8467dea00c5e8093c94dc448bf9 100755 (executable)
@@ -68,7 +68,7 @@ class DefaultFormatter(logging.Formatter):
   
   # to set color prefix, problem with indent format
   _ColorLevelname = {
-    "DEBUG": "<blue>",
+    "DEBUG": "<green>",
     "INFO": "<green>",
     "WARNING": "<red>",
     "ERROR": "<yellow>",
@@ -76,10 +76,13 @@ class DefaultFormatter(logging.Formatter):
   }
   
   def format(self, record):
-    if record.levelname == "INFO":
+    if _verbose:
+      import src.debug as DBG # avoid cross import
+      DBG.write("DefaultFormatter.format", "%s: %s..." % (record.levelname, record.msg[0:20]), True)
+    record.levelname = self.setColorLevelname(record.levelname)
+    if "INFO" in record.levelname:
       res = str(record.msg)
     else:
-      #record.levelname = self.setColorLevelname(record.levelname)
       res = indent(super(DefaultFormatter, self).format(record), 12)
     return COLS.toColor(res)
   
index db4d51ec874eef5bb268923b8fe4624dceb58e1d..dacd921b3a75a11d073eb6db6b8556a5b7a54bbc 100644 (file)
@@ -748,10 +748,12 @@ class Config(Mapping):
         @raise ConfigError: If the path is invalid
         """
         s = 'self.' + path
-        try:
+        return eval(s)
+        '''try:
             return eval(s)
         except Exception as e:
-            raise ConfigError("Config path not found: '%s'" % path)
+            # raise ConfigError("Config path not found: '%s'" % path)
+            raise ConfigError(e)'''
 
 class Sequence(Container):
     """
diff --git a/src/pyconf_0_3_7_1.py b/src/pyconf_0_3_7_1.py
new file mode 100644 (file)
index 0000000..dacd921
--- /dev/null
@@ -0,0 +1,1741 @@
+#!/usr/bin/env python
+#-*- coding:utf-8 -*-
+
+# Copyright 2004-2007 by Vinay Sajip. All Rights Reserved.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appear in all copies and that
+# both that copyright notice and this permission notice appear in
+# supporting documentation, and that the name of Vinay Sajip
+# not be used in advertising or publicity pertaining to distribution
+# of the software without specific, written prior permission.
+# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#  Copyright (C) 2010-2013  CEA/DEN
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2.1 of the License.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+
+# CEA adds : 
+# Possibility to overwrites value in a pyconf file
+# Python 3 porting
+
+
+"""
+This is a configuration module for Python.
+
+This module should work under Python versions >= 2.2, and cannot be used with
+earlier versions since it uses new-style classes.
+
+Development and testing has only been carried out (so far) on Python 2.3.4 and
+Python 2.4.2. See the test module (test_config.py) included in the
+U{distribution<http://www.red-dove.com/python_config.html|_blank>} (follow the
+download link).
+
+A simple example - with the example configuration file::
+
+    messages:
+    [
+      {
+        stream : `sys.stderr`
+        message: 'Welcome'
+        name: 'Harry'
+      }
+      {
+        stream : `sys.stdout`
+        message: 'Welkom'
+        name: 'Ruud'
+      }
+      {
+        stream : $messages[0].stream
+        message: 'Bienvenue'
+        name: Yves
+      }
+    ]
+
+a program to read the configuration would be::
+
+    from config import Config
+
+    f = file('simple.cfg')
+    cfg = Config(f)
+    for m in cfg.messages:
+        s = '%s, %s' % (m.message, m.name)
+        try:
+            print >> m.stream, s
+        except IOError, e:
+            print e
+
+which, when run, would yield the console output::
+
+    Welcome, Harry
+    Welkom, Ruud
+    Bienvenue, Yves
+
+See U{this tutorial<http://www.red-dove.com/python_config.html|_blank>} for more
+information.
+
+#modified for salomeTools
+@version: 0.3.7.1
+
+@author: Vinay Sajip
+
+@copyright: Copyright (C) 2004-2007 Vinay Sajip. All Rights Reserved.
+
+
+@var streamOpener: The default stream opener. This is a factory function which
+takes a string (e.g. filename) and returns a stream suitable for reading. If
+unable to open the stream, an IOError exception should be thrown.
+
+The default value of this variable is L{defaultStreamOpener}. For an example
+of how it's used, see test_config.py (search for streamOpener).
+"""
+
+__author__  = "Vinay Sajip <vinay_sajip@red-dove.com>"
+__status__  = "alpha"
+__version__ = "0.3.7.1" #modified for salomeTools
+__date__    = "05 October 2007"
+
+import codecs
+import os
+import sys
+
+WORD = 'a'
+NUMBER = '9'
+STRING = '"'
+EOF = ''
+LCURLY = '{'
+RCURLY = '}'
+LBRACK = '['
+LBRACK2 = 'a['
+RBRACK = ']'
+LPAREN = '('
+LPAREN2 = '(('
+RPAREN = ')'
+DOT = '.'
+COMMA = ','
+COLON = ':'
+AT = '@'
+PLUS = '+'
+MINUS = '-'
+STAR = '*'
+SLASH = '/'
+MOD = '%'
+BACKTICK = '`'
+DOLLAR = '$'
+TRUE = 'True'
+FALSE = 'False'
+NONE = 'None'
+
+WORDCHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
+
+if sys.platform == 'win32':
+    NEWLINE = '\r\n'
+elif os.name == 'mac':
+    NEWLINE = '\r'
+else:
+    NEWLINE = '\n'
+
+try:
+    has_utf32 = True
+except:
+    has_utf32 = False
+
+class ConfigInputStream(object):
+    """
+    An input stream which can read either ANSI files with default encoding
+    or Unicode files with BOMs.
+
+    Handles UTF-8, UTF-16LE, UTF-16BE. Could handle UTF-32 if Python had
+    built-in support.
+    """
+    def __init__(self, stream):
+        """
+        Initialize an instance.
+
+        @param stream: The underlying stream to be read. Should be seekable.
+        @type stream: A stream (file-like object).
+        """
+        encoding = None
+        signature = stream.read(4)
+        used = -1
+        if has_utf32:
+            if signature == codecs.BOM_UTF32_LE:
+                encoding = 'utf-32le'
+            elif signature == codecs.BOM_UTF32_BE:
+                encoding = 'utf-32be'
+        if encoding is None:
+            if signature[:3] == codecs.BOM_UTF8:
+                used = 3
+                encoding = 'utf-8'
+            elif signature[:2] == codecs.BOM_UTF16_LE:
+                used = 2
+                encoding = 'utf-16le'
+            elif signature[:2] == codecs.BOM_UTF16_BE:
+                used = 2
+                encoding = 'utf-16be'
+            else:
+                used = 0
+        if used >= 0:
+            stream.seek(used)
+        if encoding:
+            reader = codecs.getreader(encoding)
+            stream = reader(stream)
+        self.stream = stream
+        self.encoding = encoding
+
+    def read(self, size):
+        if (size == 0) or (self.encoding is None):
+            rv = self.stream.read(size)
+        else:
+            rv = u''
+            while size > 0:
+                rv += self.stream.read(1)
+                size -= 1
+        return rv
+
+    def close(self):
+        self.stream.close()
+
+    def readline(self):
+        if self.encoding is None:
+            line = ''
+        else:
+            line = u''
+        while True:
+            c = self.stream.read(1)
+            if isinstance(c, bytes):
+                c = c.decode()
+            if c:
+                line += c
+            if c == '\n':
+                break
+        return line
+
+class ConfigOutputStream(object):
+    """
+    An output stream which can write either ANSI files with default encoding
+    or Unicode files with BOMs.
+
+    Handles UTF-8, UTF-16LE, UTF-16BE. Could handle UTF-32 if Python had
+    built-in support.
+    """
+
+    def __init__(self, stream, encoding=None):
+        """
+        Initialize an instance.
+
+        @param stream: The underlying stream to be written.
+        @type stream: A stream (file-like object).
+        @param encoding: The desired encoding.
+        @type encoding: str
+        """
+        if encoding is not None:
+            encoding = str(encoding).lower()
+        self.encoding = encoding
+        if encoding == "utf-8":
+            stream.write(codecs.BOM_UTF8)
+        elif encoding == "utf-16be":
+            stream.write(codecs.BOM_UTF16_BE)
+        elif encoding == "utf-16le":
+            stream.write(codecs.BOM_UTF16_LE)
+        elif encoding == "utf-32be":
+            stream.write(codecs.BOM_UTF32_BE)
+        elif encoding == "utf-32le":
+            stream.write(codecs.BOM_UTF32_LE)
+
+        if encoding is not None:
+            writer = codecs.getwriter(encoding)
+            stream = writer(stream)
+        self.stream = stream
+
+    def write(self, data):
+        self.stream.write(data)
+
+    def flush(self):
+        self.stream.flush()
+
+    def close(self):
+        self.stream.close()
+
+def defaultStreamOpener(name):
+    """
+    This function returns a read-only stream, given its name. The name passed
+    in should correspond to an existing stream, otherwise an exception will be
+    raised.
+
+    This is the default value of L{streamOpener}; assign your own callable to
+    streamOpener to return streams based on names. For example, you could use
+    urllib2.urlopen().
+
+    @param name: The name of a stream, most commonly a file name.
+    @type name: str
+    @return: A stream with the specified name.
+    @rtype: A read-only stream (file-like object)
+    """
+    return ConfigInputStream(open(name, 'rb'))
+
+streamOpener = None
+
+__resolveOverwrite__ = True
+
+class ConfigError(Exception):
+    """
+    This is the base class of exceptions raised by this module.
+    """
+    pass
+
+class ConfigFormatError(ConfigError):
+    """
+    This is the base class of exceptions raised due to syntax errors in
+    configurations.
+    """
+    pass
+
+class ConfigResolutionError(ConfigError):
+    """
+    This is the base class of exceptions raised due to semantic errors in
+    configurations.
+    """
+    pass
+
+def isWord(s):
+    """
+    See if a passed-in value is an identifier. If the value passed in is not a
+    string, False is returned. An identifier consists of alphanumerics or
+    underscore characters.
+
+    Examples::
+
+        isWord('a word') ->False
+        isWord('award') -> True
+        isWord(9) -> False
+        isWord('a_b_c_') ->True
+
+    @note: isWord('9abc') will return True - not exactly correct, but adequate
+    for the way it's used here.
+
+    @param s: The name to be tested
+    @type s: any
+    @return: True if a word, else False
+    @rtype: bool
+    """
+    if type(s) != type(''):
+        return False
+    s = s.replace('_', '')
+    return s.isalnum()
+
+def makePath(prefix, suffix):
+    """
+    Make a path from a prefix and suffix.
+
+    Examples::
+    makePath('', 'suffix') -> 'suffix'
+    makePath('prefix', 'suffix') -> 'prefix.suffix'
+    makePath('prefix', '[1]') -> 'prefix[1]'
+
+    @param prefix:  The prefix to use. If it evaluates as false, the suffix is returned.
+    @type prefix:   str
+    @param suffix:  The suffix to use. It is either an identifier or an index in brackets.
+    @type suffix:   str
+    @return:        The path concatenation of prefix and suffix, with a dot if the suffix is not a bracketed index.
+    @rtype:         str
+    """
+    if not prefix:
+        rv = suffix
+    elif suffix[0] == '[':
+        rv = prefix + suffix
+    else:
+        rv = prefix + '.' + suffix
+    return rv
+
+
+class Container(object):
+    """
+    This internal class is the base class for mappings and sequences.
+
+    @ivar path: A string which describes how to get
+    to this instance from the root of the hierarchy.
+
+    Example::
+
+        a.list.of[1].or['more'].elements
+    """
+    def __init__(self, parent):
+        """
+        Initialize an instance.
+
+        @param parent: The parent of this instance in the hierarchy.
+        @type parent: A L{Container} instance.
+        """
+        object.__setattr__(self, 'parent', parent)
+
+    def setPath(self, path):
+        """
+        Set the path for this instance.
+        @param path: The path - a string which describes how to get
+        to this instance from the root of the hierarchy.
+        @type path: str
+        """
+        object.__setattr__(self, 'path', path)
+
+    def evaluate(self, item):
+        """
+        Evaluate items which are instances of L{Reference} or L{Expression}.
+
+        L{Reference} instances are evaluated using L{Reference.resolve},
+        and L{Expression} instances are evaluated using
+        L{Expression.evaluate}.
+
+        @param item: The item to be evaluated.
+        @type item: any
+        @return: If the item is an instance of L{Reference} or L{Expression},
+        the evaluated value is returned, otherwise the item is returned
+        unchanged.
+        """
+        if isinstance(item, Reference):
+            item = item.resolve(self)
+        elif isinstance(item, Expression):
+            item = item.evaluate(self)
+        return item
+
+    def writeToStream(self, stream, indent, container):
+        """
+        Write this instance to a stream at the specified indentation level.
+
+        Should be redefined in subclasses.
+
+        @param stream: The stream to write to
+        @type stream: A writable stream (file-like object)
+        @param indent: The indentation level
+        @type indent: int
+        @param container: The container of this instance
+        @type container: L{Container}
+        @raise NotImplementedError: If a subclass does not override this
+        """
+        raise NotImplementedError
+
+    def writeValue(self, value, stream, indent):
+        if isinstance(self, Mapping):
+            indstr = ' '
+        else:
+            indstr = indent * '  '
+        if isinstance(value, Reference) or isinstance(value, Expression):
+            stream.write('%s%r%s' % (indstr, value, NEWLINE))
+        else:
+            if isinstance(value, str): # and not isWord(value):
+                value = repr(value)
+            stream.write('%s%s%s' % (indstr, value, NEWLINE))
+
+class Mapping(Container):
+    """
+    This internal class implements key-value mappings in configurations.
+    """
+
+    def __init__(self, parent=None):
+        """
+        Initialize an instance.
+
+        @param parent: The parent of this instance in the hierarchy.
+        @type parent: A L{Container} instance.
+        """
+        Container.__init__(self, parent)
+        object.__setattr__(self, 'path', '')
+        object.__setattr__(self, 'data', {})
+        object.__setattr__(self, 'order', [])   # to preserve ordering
+        object.__setattr__(self, 'comments', {})
+
+    def __delitem__(self, key):
+        """
+        Remove an item
+        """
+        data = object.__getattribute__(self, 'data')
+        if key not in data:
+            raise AttributeError(key)
+        order = object.__getattribute__(self, 'order')
+        comments = object.__getattribute__(self, 'comments')
+        del data[key]
+        order.remove(key)
+        del comments[key]
+
+    def __getitem__(self, key):
+        data = object.__getattribute__(self, 'data')
+        if key not in data:
+            raise AttributeError(key)
+        rv = data[key]
+        return self.evaluate(rv)
+
+    __getattr__ = __getitem__
+    
+    """
+    def __getattribute__(self, name):
+        if name == "__dict__":
+            return {}
+        if name in ["__methods__", "__members__"]:
+            return []
+        #if name == "__class__":
+        #    return ''
+        data = object.__getattribute__(self, "data")
+        useData = data.has_key(name)
+        if useData:
+            rv = getattr(data, name)
+        else:
+            rv = object.__getattribute__(self, name)
+            if rv is None:
+                raise AttributeError(name)
+        return rv
+    """
+
+    def iteritems(self):
+        for key in self.keys():
+            yield(key, self[key])
+        raise StopIteration
+
+    def __contains__(self, item):
+        order = object.__getattribute__(self, 'order')
+        return item in order
+
+    def addMapping(self, key, value, comment, setting=False):
+        """
+        Add a key-value mapping with a comment.
+
+        @param key: The key for the mapping.
+        @type key: str
+        @param value: The value for the mapping.
+        @type value: any
+        @param comment: The comment for the key (can be None).
+        @type comment: str
+        @param setting: If True, ignore clashes. This is set
+        to true when called from L{__setattr__}.
+        @raise ConfigFormatError: If an existing key is seen
+        again and setting is False.
+        """
+        data = object.__getattribute__(self, 'data')
+        order = object.__getattribute__(self, 'order')
+        comments = object.__getattribute__(self, 'comments')
+
+        data[key] = value
+        if key not in order:
+            order.append(key)
+        elif not setting:
+            raise ConfigFormatError("repeated key: %s" % key)
+        comments[key] = comment
+
+    def __setattr__(self, name, value):
+        self.addMapping(name, value, None, True)
+
+    __setitem__ = __setattr__
+
+    def keys(self):
+        """
+        Return the keys in a similar way to a dictionary.
+        """
+        return object.__getattribute__(self, 'order')
+
+    def get(self, key, default=None):
+        """
+        Allows a dictionary-style get operation.
+        """
+        if key in self:
+            return self[key]
+        return default
+
+    def __str__(self):
+        return str(object.__getattribute__(self, 'data'))
+
+    def __repr__(self):
+        return repr(object.__getattribute__(self, 'data'))
+
+    def __len__(self):
+        return len(object.__getattribute__(self, 'order'))
+
+    def __iter__(self):
+        return self.iterkeys()
+
+    def iterkeys(self):
+        order = object.__getattribute__(self, 'order')
+        return order.__iter__()
+
+    def writeToStream(self, stream, indent, container):
+        """
+        Write this instance to a stream at the specified indentation level.
+
+        Should be redefined in subclasses.
+
+        @param stream: The stream to write to
+        @type stream: A writable stream (file-like object)
+        @param indent: The indentation level
+        @type indent: int
+        @param container: The container of this instance
+        @type container: L{Container}
+        """
+        indstr = indent * '  '
+        if len(self) == 0:
+            stream.write(' { }%s' % NEWLINE)
+        else:
+            if isinstance(container, Mapping):
+                stream.write(NEWLINE)
+            stream.write('%s{%s' % (indstr, NEWLINE))
+            self.__save__(stream, indent + 1)
+            stream.write('%s}%s' % (indstr, NEWLINE))
+
+    def __save__(self, stream, indent=0):
+        """
+        Save this configuration to the specified stream.
+        @param stream: A stream to which the configuration is written.
+        @type stream: A write-only stream (file-like object).
+        @param indent: The indentation level for the output.
+        @type indent: int
+        """
+        indstr = indent * '  '
+        order = object.__getattribute__(self, 'order')
+        data = object.__getattribute__(self, 'data')
+        maxlen = 0 # max(map(lambda x: len(x), order))
+        for key in order:
+            comment = self.comments[key]
+            if isWord(key):
+                skey = key
+            else:
+                skey = repr(key)
+            if comment:
+                stream.write('%s#%s' % (indstr, comment))
+            if skey.startswith("u'"):
+                skey = skey[1:]
+            stream.write('%s%-*s :' % (indstr, maxlen, skey))
+            value = data[key]
+            if isinstance(value, Container):
+                value.writeToStream(stream, indent, self)
+            else:
+                self.writeValue(value, stream, indent)
+
+class Config(Mapping):
+    """
+    This class represents a configuration, and is the only one which clients
+    need to interface to, under normal circumstances.
+    """
+
+    class Namespace(object):
+        """
+        This internal class is used for implementing default namespaces.
+
+        An instance acts as a namespace.
+        """
+        def __init__(self):
+            self.sys = sys
+            self.os = os
+
+    def __init__(self, streamOrFile=None, parent=None, PWD = None):
+        """
+        Initializes an instance.
+
+        @param streamOrFile: If specified, causes this instance to be loaded
+        from the stream (by calling L{load}). If a string is provided, it is
+        passed to L{streamOpener} to open a stream. Otherwise, the passed
+        value is assumed to be a stream and used as is.
+        @type streamOrFile: A readable stream (file-like object) or a name.
+        @param parent: If specified, this becomes the parent of this instance
+        in the configuration hierarchy.
+        @type parent: a L{Container} instance.
+        """
+        try: # Python 3 compatibility
+            if isinstance(streamOrFile, unicode):
+                streamOrFile = streamOrFile.encode()
+        except NameError:
+            pass
+        Mapping.__init__(self, parent)
+        object.__setattr__(self, 'reader', ConfigReader(self))
+        object.__setattr__(self, 'namespaces', [Config.Namespace()])
+        if streamOrFile is not None:
+            if isinstance(streamOrFile, str) or isinstance(streamOrFile, bytes):
+                global streamOpener
+                if streamOpener is None:
+                    streamOpener = defaultStreamOpener
+                streamOrFile = streamOpener(streamOrFile)
+            load = object.__getattribute__(self, "load")
+            load(streamOrFile)
+            # Specific add for salomeTools : PWD
+            if PWD:
+                key, pwd = PWD
+                if key == "":
+                    self.PWD = pwd
+                else:
+                    self[key].PWD = pwd
+
+    def load(self, stream):
+        """
+        Load the configuration from the specified stream. Multiple streams can
+        be used to populate the same instance, as long as there are no
+        clashing keys. The stream is closed.
+        @param stream: A stream from which the configuration is read.
+        @type stream: A read-only stream (file-like object).
+        @raise ConfigError: if keys in the loaded configuration clash with
+        existing keys.
+        @raise ConfigFormatError: if there is a syntax error in the stream.
+        """
+        reader = object.__getattribute__(self, 'reader')
+        reader.load(stream)
+        stream.close()
+
+    def addNamespace(self, ns, name=None):
+        """
+        Add a namespace to this configuration which can be used to evaluate
+        (resolve) dotted-identifier expressions.
+        @param ns: The namespace to be added.
+        @type ns: A module or other namespace suitable for passing as an
+        argument to vars().
+        @param name: A name for the namespace, which, if specified, provides
+        an additional level of indirection.
+        @type name: str
+        """
+        namespaces = object.__getattribute__(self, 'namespaces')
+        if name is None:
+            namespaces.append(ns)
+        else:
+            setattr(namespaces[0], name, ns)
+
+    def removeNamespace(self, ns, name=None):
+        """
+        Remove a namespace added with L{addNamespace}.
+        @param ns: The namespace to be removed.
+        @param name: The name which was specified when L{addNamespace} was
+        called.
+        @type name: str
+        """
+        namespaces = object.__getattribute__(self, 'namespaces')
+        if name is None:
+            namespaces.remove(ns)
+        else:
+            delattr(namespaces[0], name)
+
+    def __save__(self, stream, indent=0, no_close=False):
+        """
+        Save this configuration to the specified stream. The stream is
+        closed if this is the top-level configuration in the hierarchy.
+        L{Mapping.__save__} is called to do all the work.
+        @param stream: A stream to which the configuration is written.
+        @type stream: A write-only stream (file-like object).
+        @param indent: The indentation level for the output.
+        @type indent: int
+        """
+        Mapping.__save__(self, stream, indent)
+        if indent == 0 and not no_close:
+            stream.close()
+
+    def getByPath(self, path):
+        """
+        Obtain a value in the configuration via its path.
+        @param path: The path of the required value
+        @type path: str
+        @return the value at the specified path.
+        @rtype: any
+        @raise ConfigError: If the path is invalid
+        """
+        s = 'self.' + path
+        return eval(s)
+        '''try:
+            return eval(s)
+        except Exception as e:
+            # raise ConfigError("Config path not found: '%s'" % path)
+            raise ConfigError(e)'''
+
+class Sequence(Container):
+    """
+    This internal class implements a value which is a sequence of other values.
+    """
+    class SeqIter(object):
+        """
+        This internal class implements an iterator for a L{Sequence} instance.
+        """
+        def __init__(self, seq):
+            self.seq = seq
+            self.limit = len(object.__getattribute__(seq, 'data'))
+            self.index = 0
+
+        def __iter__(self):
+            return self
+
+        def next(self):
+            if self.index >= self.limit:
+                raise StopIteration
+            rv = self.seq[self.index]
+            self.index += 1
+            return rv
+        
+        # This method is for python3 compatibility
+        def __next__(self): 
+            if self.index >= self.limit:
+                raise StopIteration
+            rv = self.seq[self.index]
+            self.index += 1
+            return rv
+
+    def __init__(self, parent=None):
+        """
+        Initialize an instance.
+
+        @param parent: The parent of this instance in the hierarchy.
+        @type parent: A L{Container} instance.
+        """
+        Container.__init__(self, parent)
+        object.__setattr__(self, 'data', [])
+        object.__setattr__(self, 'comments', [])
+
+    def append(self, item, comment):
+        """
+        Add an item to the sequence.
+
+        @param item: The item to add.
+        @type item: any
+        @param comment: A comment for the item.
+        @type comment: str
+        """
+        data = object.__getattribute__(self, 'data')
+        comments = object.__getattribute__(self, 'comments')
+        data.append(item)
+        comments.append(comment)
+
+    def __getitem__(self, index):
+        data = object.__getattribute__(self, 'data')
+        try:
+            rv = data[index]
+        except (IndexError, KeyError, TypeError):
+            raise ConfigResolutionError('%r is not a valid index for %r' % (index, object.__getattribute__(self, 'path')))
+        if not isinstance(rv, list):
+            rv = self.evaluate(rv)
+        else:
+            # deal with a slice
+            result = []
+            for a in rv:
+                result.append(self.evaluate(a))
+            rv = result
+        return rv
+
+    def __iter__(self):
+        return Sequence.SeqIter(self)
+
+    def __repr__(self):
+        return repr(object.__getattribute__(self, 'data'))
+
+    def __str__(self):
+        return str(self[:]) # using the slice evaluates the contents
+
+    def __len__(self):
+        return len(object.__getattribute__(self, 'data'))
+
+    def writeToStream(self, stream, indent, container):
+        """
+        Write this instance to a stream at the specified indentation level.
+
+        Should be redefined in subclasses.
+
+        @param stream: The stream to write to
+        @type stream: A writable stream (file-like object)
+        @param indent: The indentation level
+        @type indent: int
+        @param container: The container of this instance
+        @type container: L{Container}
+        """
+        indstr = indent * '  '
+        if len(self) == 0:
+            stream.write(' [ ]%s' % NEWLINE)
+        else:
+            if isinstance(container, Mapping):
+                stream.write(NEWLINE)
+            stream.write('%s[%s' % (indstr, NEWLINE))
+            self.__save__(stream, indent + 1)
+            stream.write('%s]%s' % (indstr, NEWLINE))
+
+    def __save__(self, stream, indent):
+        """
+        Save this instance to the specified stream.
+        @param stream: A stream to which the configuration is written.
+        @type stream: A write-only stream (file-like object).
+        @param indent: The indentation level for the output, > 0
+        @type indent: int
+        """
+        if indent == 0:
+            raise ConfigError("sequence cannot be saved as a top-level item")
+        data = object.__getattribute__(self, 'data')
+        comments = object.__getattribute__(self, 'comments')
+        indstr = indent * '  '
+        for i in range(0, len(data)):
+            value = data[i]
+            comment = comments[i]
+            if comment:
+                stream.write('%s#%s' % (indstr, comment))
+            if isinstance(value, Container):
+                value.writeToStream(stream, indent, self)
+            else:
+                self.writeValue(value, stream, indent)
+
+class Reference(object):
+    """
+    This internal class implements a value which is a reference to another value.
+    """
+    def __init__(self, config, type, ident):
+        """
+        Initialize an instance.
+
+        @param config: The configuration which contains this reference.
+        @type config: A L{Config} instance.
+        @param type: The type of reference.
+        @type type: BACKTICK or DOLLAR
+        @param ident: The identifier which starts the reference.
+        @type ident: str
+        """
+        self.config = config
+        self.type = type
+        self.elements = [ident]
+
+    def addElement(self, type, ident):
+        """
+        Add an element to the reference.
+
+        @param type: The type of reference.
+        @type type: BACKTICK or DOLLAR
+        @param ident: The identifier which continues the reference.
+        @type ident: str
+        """
+        self.elements.append((type, ident))
+
+    def findConfig(self, container):
+        """
+        Find the closest enclosing configuration to the specified container.
+
+        @param container: The container to start from.
+        @type container: L{Container}
+        @return: The closest enclosing configuration, or None.
+        @rtype: L{Config}
+        """
+        while (container is not None) and not isinstance(container, Config):
+            container = object.__getattribute__(container, 'parent')
+        return container
+
+    def resolve(self, container):
+        """
+        Resolve this instance in the context of a container.
+
+        @param container: The container to resolve from.
+        @type container: L{Container}
+        @return: The resolved value.
+        @rtype: any
+        @raise ConfigResolutionError: If resolution fails.
+        """
+        rv = None
+        path = object.__getattribute__(container, 'path')
+        current = container
+        while current is not None:
+            if self.type == BACKTICK:
+                namespaces = object.__getattribute__(current, 'namespaces')
+                found = False
+                for ns in namespaces:
+                    try:
+                        rv = eval(str(self)[1:-1], vars(ns))
+                        found = True
+                        break
+                    except:
+                        pass
+                if found:
+                    break
+            else:
+                key = self.elements[0]
+                try:
+                    rv = current[key]
+                    for item in self.elements[1:]:
+                        key = item[1]
+                        rv = rv[key]
+                    break
+                except:
+                    rv = None
+                    pass
+            current = object.__getattribute__(current, 'parent')
+        if current is None:
+            raise ConfigResolutionError("unable to evaluate %r in the configuration %s" % (self, path))
+        return rv
+
+    def __str__(self):
+        s = self.elements[0]
+        for tt, tv in self.elements[1:]:
+            if tt == DOT:
+                s += '.%s' % tv
+            else:
+                s += '[%r]' % tv
+        if self.type == BACKTICK:
+            return BACKTICK + s + BACKTICK
+        else:
+            return DOLLAR + s
+
+    def __repr__(self):
+        return self.__str__()
+
+class Expression(object):
+    """
+    This internal class implements a value which is obtained by evaluating an expression.
+    """
+    def __init__(self, op, lhs, rhs):
+        """
+        Initialize an instance.
+
+        @param op: the operation expressed in the expression.
+        @type op: PLUS, MINUS, STAR, SLASH, MOD
+        @param lhs: the left-hand-side operand of the expression.
+        @type lhs: any Expression or primary value.
+        @param rhs: the right-hand-side operand of the expression.
+        @type rhs: any Expression or primary value.
+        """
+        self.op = op
+        self.lhs = lhs
+        self.rhs = rhs
+
+    def __str__(self):
+        return '%r %s %r' % (self.lhs, self.op, self.rhs)
+
+    def __repr__(self):
+        return self.__str__()
+
+    def evaluate(self, container):
+        """
+        Evaluate this instance in the context of a container.
+
+        @param container: The container to evaluate in from.
+        @type container: L{Container}
+        @return: The evaluated value.
+        @rtype: any
+        @raise ConfigResolutionError: If evaluation fails.
+        @raise ZeroDivideError: If division by zero occurs.
+        @raise TypeError: If the operation is invalid, e.g.
+        subtracting one string from another.
+        """
+        lhs = self.lhs
+        if isinstance(lhs, Reference):
+            lhs = lhs.resolve(container)
+        elif isinstance(lhs, Expression):
+            lhs = lhs.evaluate(container)
+        rhs = self.rhs
+        if isinstance(rhs, Reference):
+            rhs = rhs.resolve(container)
+        elif isinstance(rhs, Expression):
+            rhs = rhs.evaluate(container)
+        op = self.op
+        if op == PLUS:
+            rv = lhs + rhs
+        elif op == MINUS:
+            rv = lhs - rhs
+        elif op == STAR:
+            rv = lhs * rhs
+        elif op == SLASH:
+            rv = lhs / rhs
+        else:
+            rv = lhs % rhs
+        return rv
+
+class ConfigReader(object):
+    """
+    This internal class implements a parser for configurations.
+    """
+
+    def __init__(self, config):
+        self.filename = None
+        self.config = config
+        self.lineno = 0
+        self.colno = 0
+        self.lastc = None
+        self.last_token = None
+        self.commentchars = '#'
+        self.whitespace = ' \t\r\n'
+        self.quotes = '\'"'
+        self.punct = ':-+*/%,.{}[]()@`$'
+        self.digits = '0123456789'
+        self.wordchars = '%s' % WORDCHARS # make a copy
+        self.identchars = self.wordchars + self.digits
+        self.pbchars = []
+        self.pbtokens = []
+        self.comment = None
+
+    def location(self):
+        """
+        Return the current location (filename, line, column) in the stream
+        as a string.
+
+        Used when printing error messages,
+
+        @return: A string representing a location in the stream being read.
+        @rtype: str
+        """
+        return "%s(%d,%d)" % (self.filename, self.lineno, self.colno)
+
+    def getChar(self):
+        """
+        Get the next char from the stream. Update line and column numbers
+        appropriately.
+
+        @return: The next character from the stream.
+        @rtype: str
+        """
+        if self.pbchars:
+            c = self.pbchars.pop()
+            if isinstance(c,bytes):
+                c = c.decode()
+        else:
+            c = self.stream.read(1)
+            if isinstance(c,bytes):
+                c = c.decode()
+            self.colno += 1
+            if c == '\n':
+                self.lineno += 1
+                self.colno = 1
+        return c
+
+    def __repr__(self):
+        return "<ConfigReader at 0x%08x>" % id(self)
+
+    __str__ = __repr__
+
+    def getToken(self):
+        """
+        Get a token from the stream. String values are returned in a form
+        where you need to eval() the returned value to get the actual
+        string. The return value is (token_type, token_value).
+
+        Multiline string tokenizing is thanks to David Janes (BlogMatrix)
+
+        @return: The next token.
+        @rtype: A token tuple.
+        """
+        if self.pbtokens:
+            return self.pbtokens.pop()
+        stream = self.stream
+        self.comment = None
+        token = ''
+        tt = EOF
+        while True:
+            c = self.getChar()
+            if not c:
+                break
+            elif c == '#':
+                if self.comment :
+                    self.comment += '#' + stream.readline()
+                else :
+                    self.comment = stream.readline()
+                self.lineno += 1
+                continue
+            if c in self.quotes:
+                token = c
+                quote = c
+                tt = STRING
+                escaped = False
+                multiline = False
+                c1 = self.getChar()
+                if c1 == quote:
+                    c2 = self.getChar()
+                    if c2 == quote:
+                        multiline = True
+                        token += quote
+                        token += quote
+                    else:
+                        self.pbchars.append(c2)
+                        self.pbchars.append(c1)
+                else:
+                    self.pbchars.append(c1)
+                while True:
+                    c = self.getChar()
+                    if not c:
+                        break
+                    token += c
+                    if (c == quote) and not escaped:
+                        if not multiline or (len(token) >= 6 and token.endswith(token[:3]) and token[-4] != '\\'):
+                            break
+                    if c == '\\':
+                        escaped = not escaped
+                    else:
+                        escaped = False
+                if not c:
+                    raise ConfigFormatError('%s: Unterminated quoted string: %r, %r' % (self.location(), token, c))
+                break
+            if c in self.whitespace:
+                self.lastc = c
+                continue
+            elif c in self.punct:
+                token = c
+                tt = c
+                if (self.lastc == ']') or (self.lastc in self.identchars):
+                    if c == '[':
+                        tt = LBRACK2
+                    elif c == '(':
+                        tt = LPAREN2
+                break
+            elif c in self.digits:
+                token = c
+                tt = NUMBER
+                while True:
+                    c = self.getChar()
+                    if not c:
+                        break
+                    if c in self.digits:
+                        token += c
+                    elif (c == '.') and token.find('.') < 0:
+                        token += c
+                    else:
+                        if c and (c not in self.whitespace):
+                            self.pbchars.append(c)
+                        break
+                break
+            elif c in self.wordchars:
+                token = c
+                tt = WORD
+                c = self.getChar()
+                while c and (c in self.identchars):
+                    token += c
+                    c = self.getChar()
+                if c: # and c not in self.whitespace:
+                    self.pbchars.append(c)
+                if token == "True":
+                    tt = TRUE
+                elif token == "False":
+                    tt = FALSE
+                elif token == "None":
+                    tt = NONE
+                break
+            else:
+                raise ConfigFormatError('%s: Unexpected character: %r' % (self.location(), c))
+        if token:
+            self.lastc = token[-1]
+        else:
+            self.lastc = None
+        self.last_token = tt
+        
+        # Python 2.x specific unicode conversion
+        if sys.version_info[0] == 2 and tt == WORD and isinstance(token, unicode):
+            token = token.encode('ascii')
+        return (tt, token)
+
+    def load(self, stream, parent=None, suffix=None):
+        """
+        Load the configuration from the specified stream.
+
+        @param stream: A stream from which to load the configuration.
+        @type stream: A stream (file-like object).
+        @param parent: The parent of the configuration (to which this reader
+        belongs) in the hierarchy. Specified when the configuration is
+        included in another one.
+        @type parent: A L{Container} instance.
+        @param suffix: The suffix of this configuration in the parent
+        configuration. Should be specified whenever the parent is not None.
+        @raise ConfigError: If parent is specified but suffix is not.
+        @raise ConfigFormatError: If there are syntax errors in the stream.
+        """
+        if parent is not None:
+            if suffix is None:
+                raise ConfigError("internal error: load called with parent but no suffix")
+            self.config.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
+        self.setStream(stream)
+        self.token = self.getToken()
+        self.parseMappingBody(self.config)
+        if self.token[0] != EOF:
+            raise ConfigFormatError('%s: expecting EOF, found %r' % (self.location(), self.token[1]))
+
+    def setStream(self, stream):
+        """
+        Set the stream to the specified value, and prepare to read from it.
+
+        @param stream: A stream from which to load the configuration.
+        @type stream: A stream (file-like object).
+        """
+        self.stream = stream
+        if hasattr(stream, 'name'):
+            filename = stream.name
+        else:
+            filename = '?'
+        self.filename = filename
+        self.lineno = 1
+        self.colno = 1
+
+    def match(self, t):
+        """
+        Ensure that the current token type matches the specified value, and
+        advance to the next token.
+
+        @param t: The token type to match.
+        @type t: A valid token type.
+        @return: The token which was last read from the stream before this
+        function is called.
+        @rtype: a token tuple - see L{getToken}.
+        @raise ConfigFormatError: If the token does not match what's expected.
+        """
+        if self.token[0] != t:
+            raise ConfigFormatError("%s: expecting %s, found %r" % (self.location(), t, self.token[1]))
+        rv = self.token
+        self.token = self.getToken()
+        return rv
+
+    def parseMappingBody(self, parent):
+        """
+        Parse the internals of a mapping, and add entries to the provided
+        L{Mapping}.
+
+        @param parent: The mapping to add entries to.
+        @type parent: A L{Mapping} instance.
+        """
+        while self.token[0] in [WORD, STRING]:
+            self.parseKeyValuePair(parent)
+
+    def parseKeyValuePair(self, parent):
+        """
+        Parse a key-value pair, and add it to the provided L{Mapping}.
+
+        @param parent: The mapping to add entries to.
+        @type parent: A L{Mapping} instance.
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        comment = self.comment
+        tt, tv = self.token
+        if tt == WORD:
+            key = tv
+            suffix = tv
+        elif tt == STRING:
+            key = eval(tv)
+            suffix = '[%s]' % tv
+        else:
+            msg = "%s: expecting word or string, found %r"
+            raise ConfigFormatError(msg % (self.location(), tv))
+        self.token = self.getToken()
+        # for now, we allow key on its own as a short form of key : True
+        if self.token[0] == COLON:
+            self.token = self.getToken()
+            value = self.parseValue(parent, suffix)
+        else:
+            value = True
+        try:
+            parent.addMapping(key, value, comment)
+        except Exception as e:
+            raise ConfigFormatError("%s: %s, %r" % (self.location(), e,
+                                    self.token[1]))
+        tt = self.token[0]
+        if tt not in [EOF, WORD, STRING, RCURLY, COMMA]:
+            msg = "%s: expecting one of EOF, WORD, STRING, \
+RCURLY, COMMA, found %r"
+            raise ConfigFormatError(msg  % (self.location(), self.token[1]))
+        if tt == COMMA:
+            self.token = self.getToken()
+
+    def parseValue(self, parent, suffix):
+        """
+        Parse a value.
+
+        @param parent: The container to which the value will be added.
+        @type parent: A L{Container} instance.
+        @param suffix: The suffix for the value.
+        @type suffix: str
+        @return: The value
+        @rtype: any
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        tt = self.token[0]
+        if tt in [STRING, WORD, NUMBER, LPAREN, DOLLAR,
+                  TRUE, FALSE, NONE, BACKTICK, MINUS]:
+            rv = self.parseScalar()
+        elif tt == LBRACK:
+            rv = self.parseSequence(parent, suffix)
+        elif tt in [LCURLY, AT]:
+            rv = self.parseMapping(parent, suffix)
+        else:
+            raise ConfigFormatError("%s: unexpected input: %r" %
+               (self.location(), self.token[1]))
+        return rv
+
+    def parseSequence(self, parent, suffix):
+        """
+        Parse a sequence.
+
+        @param parent: The container to which the sequence will be added.
+        @type parent: A L{Container} instance.
+        @param suffix: The suffix for the value.
+        @type suffix: str
+        @return: a L{Sequence} instance representing the sequence.
+        @rtype: L{Sequence}
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        rv = Sequence(parent)
+        rv.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
+        self.match(LBRACK)
+        comment = self.comment
+        tt = self.token[0]
+        while tt in [STRING, WORD, NUMBER, LCURLY, LBRACK, LPAREN, DOLLAR,
+                     TRUE, FALSE, NONE, BACKTICK]:
+            suffix = '[%d]' % len(rv)
+            value = self.parseValue(parent, suffix)
+            rv.append(value, comment)
+            tt = self.token[0]
+            comment = self.comment
+            if tt == COMMA:
+                self.match(COMMA)
+                tt = self.token[0]
+                comment = self.comment
+                continue
+        self.match(RBRACK)
+        return rv
+
+    def parseMapping(self, parent, suffix):
+        """
+        Parse a mapping.
+
+        @param parent: The container to which the mapping will be added.
+        @type parent: A L{Container} instance.
+        @param suffix: The suffix for the value.
+        @type suffix: str
+        @return: a L{Mapping} instance representing the mapping.
+        @rtype: L{Mapping}
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        if self.token[0] == LCURLY:
+            self.match(LCURLY)
+            rv = Mapping(parent)
+            rv.setPath(
+               makePath(object.__getattribute__(parent, 'path'), suffix))
+            self.parseMappingBody(rv)
+            self.match(RCURLY)
+        else:
+            self.match(AT)
+            _, fn = self.match(STRING)
+            rv = Config(eval(fn), parent)
+        return rv
+
+    def parseScalar(self):
+        """
+        Parse a scalar - a terminal value such as a string or number, or
+        an L{Expression} or L{Reference}.
+
+        @return: the parsed scalar
+        @rtype: any scalar
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        lhs = self.parseTerm()
+        tt = self.token[0]
+        while tt in [PLUS, MINUS]:
+            self.match(tt)
+            rhs = self.parseTerm()
+            lhs = Expression(tt, lhs, rhs)
+            tt = self.token[0]
+        return lhs
+
+    def parseTerm(self):
+        """
+        Parse a term in an additive expression (a + b, a - b)
+
+        @return: the parsed term
+        @rtype: any scalar
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        lhs = self.parseFactor()
+        tt = self.token[0]
+        while tt in [STAR, SLASH, MOD]:
+            self.match(tt)
+            rhs = self.parseFactor()
+            lhs = Expression(tt, lhs, rhs)
+            tt = self.token[0]
+        return lhs
+
+    def parseFactor(self):
+        """
+        Parse a factor in an multiplicative expression (a * b, a / b, a % b)
+
+        @return: the parsed factor
+        @rtype: any scalar
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        tt = self.token[0]
+        if tt in [NUMBER, WORD, STRING, TRUE, FALSE, NONE]:
+            rv = self.token[1]
+            if tt != WORD:
+                rv = eval(rv)
+            self.match(tt)
+        elif tt == LPAREN:
+            self.match(LPAREN)
+            rv = self.parseScalar()
+            self.match(RPAREN)
+        elif tt == DOLLAR:
+            self.match(DOLLAR)
+            rv = self.parseReference(DOLLAR)
+        elif tt == BACKTICK:
+            self.match(BACKTICK)
+            rv = self.parseReference(BACKTICK)
+            self.match(BACKTICK)
+        elif tt == MINUS:
+            self.match(MINUS)
+            rv = -self.parseScalar()
+        else:
+            raise ConfigFormatError("%s: unexpected input: %r" %
+               (self.location(), self.token[1]))
+        return rv
+
+    def parseReference(self, type):
+        """
+        Parse a reference.
+
+        @return: the parsed reference
+        @rtype: L{Reference}
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        word = self.match(WORD)
+        rv = Reference(self.config, type, word[1])
+        while self.token[0] in [DOT, LBRACK2]:
+            self.parseSuffix(rv)
+        return rv
+
+    def parseSuffix(self, ref):
+        """
+        Parse a reference suffix.
+
+        @param ref: The reference of which this suffix is a part.
+        @type ref: L{Reference}.
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        tt = self.token[0]
+        if tt == DOT:
+            self.match(DOT)
+            word = self.match(WORD)
+            ref.addElement(DOT, word[1])
+        else:
+            self.match(LBRACK2)
+            tt, tv = self.token
+            if tt not in [NUMBER, STRING]:
+                raise ConfigFormatError("%s: expected number or string, found %r" % (self.location(), tv))
+            self.token = self.getToken()
+            tv = eval(tv)
+            self.match(RBRACK)
+            ref.addElement(LBRACK, tv)
+
+def defaultMergeResolve(map1, map2, key):
+    """
+    A default resolver for merge conflicts. 
+    Returns a string indicating what action to take to resolve the conflict.
+
+    @param map1: The map being merged into.
+    @type map1: L{Mapping}.
+    @param map2: The map being used as the merge operand.
+    @type map2: L{Mapping}.
+    @param key: The key in map2 (which also exists in map1).
+    @type key: str
+    @return: One of "merge", "append", "mismatch" or "overwrite" 
+    indicating what action should be taken. This should
+    be appropriate to the objects being merged - e.g.
+    there is no point returning "merge" if the two objects
+    are instances of L{Sequence}.
+    @rtype: str
+    """
+    obj1 = map1[key]
+    obj2 = map2[key]
+    if isinstance(obj1, Mapping) and isinstance(obj2, Mapping):
+        rv = "merge"
+    elif isinstance(obj1, Sequence) and isinstance(obj2, Sequence):
+        rv = "append"
+    else:
+        rv = "mismatch"
+    return rv
+
+def overwriteMergeResolve(map1, map2, key):
+    """
+    An overwriting resolver for merge conflicts. Calls L{defaultMergeResolve},
+    but where a "mismatch" is detected, returns "overwrite" instead.
+
+    @param map1: The map being merged into.
+    @type map1: L{Mapping}.
+    @param map2: The map being used as the merge operand.
+    @type map2: L{Mapping}.
+    @param key: The key in map2 (which also exists in map1).
+    @type key: str
+    """
+    rv = defaultMergeResolve(map1, map2, key)
+    if rv == "mismatch":
+        rv = "overwrite"
+    return rv
+
+def deepCopyMapping(inMapping):
+    res = Mapping()
+    for element in inMapping:
+        res[element] = inMapping[element]
+    return res
+
+class ConfigMerger(object):
+    """
+    This class is used for merging two configurations. If a key exists in the
+    merge operand but not the merge target, then the entry is copied from the
+    merge operand to the merge target. If a key exists in both configurations,
+    then a resolver (a callable) is called to decide how to handle the
+    conflict.
+    """
+
+    def __init__(self, resolver=defaultMergeResolve):
+        """
+        Initialise an instance.
+
+        @param resolver:
+        @type resolver: A callable which takes the argument list
+        (map1, map2, key) where map1 is the mapping being merged into,
+        map2 is the merge operand and key is the clashing key. The callable
+        should return a string indicating how the conflict should be resolved.
+        For possible return values, see L{defaultMergeResolve}. The default
+        value preserves the old behaviour
+        """
+        self.resolver = resolver
+
+    def merge(self, merged, mergee):
+        """
+        Merge two configurations. The second configuration is unchanged,
+        and the first is changed to reflect the results of the merge.
+
+        @param merged: The configuration to merge into.
+        @type merged: L{Config}.
+        @param mergee: The configuration to merge.
+        @type mergee: L{Config}.
+        """
+        self.mergeMapping(merged, mergee)
+
+    def overwriteKeys(self, map1, seq2):
+        """
+        Renint variables. The second mapping is unchanged,
+        and the first is changed depending the keys of the second mapping.
+        @param map1: The mapping to reinit keys into.
+        @type map1: L{Mapping}.
+        @param map2: The mapping container reinit information.
+        @type map2: L{Mapping}.
+        """
+
+        overwrite_list = object.__getattribute__(seq2, 'data')
+        for overwrite_instruction in overwrite_list:
+            object.__setattr__(overwrite_instruction, 'parent', map1)
+            if "__condition__" in overwrite_instruction.keys():
+                overwrite_condition = overwrite_instruction["__condition__"]
+                if eval(overwrite_condition, globals(), map1):
+                    for key in overwrite_instruction.keys():
+                        if key == "__condition__":
+                            continue
+                        try:
+                            exec( 'map1.' + key + " = " + repr(overwrite_instruction[key]))
+                        except:
+                            exec('map1.' + key + " = " + str(overwrite_instruction[key]))
+            else:
+                for key in overwrite_instruction.keys():
+                    try:
+                        exec('map1.' + key + " = " + repr(overwrite_instruction[key]))
+                    except:
+                        exec('map1.' + key + " = " + str(overwrite_instruction[key]))
+
+    def mergeMapping(self, map1, map2):
+        """
+        Merge two mappings recursively. The second mapping is unchanged,
+        and the first is changed to reflect the results of the merge.
+
+        @param map1: The mapping to merge into.
+        @type map1: L{Mapping}.
+        @param map2: The mapping to merge.
+        @type map2: L{Mapping}.
+        """
+        keys = map1.keys()
+        global __resolveOverwrite__
+        for key in map2.keys():
+            if __resolveOverwrite__ and key == "__overwrite__":
+                self.overwriteKeys(map1,map2[key])
+
+            elif key not in keys:
+                map1[key] = map2[key]
+                if isinstance(map1[key], Container) :
+                    object.__setattr__(map1[key], 'parent', map1)
+            else:
+                obj1 = map1[key]
+                obj2 = map2[key]
+                decision = self.resolver(map1, map2, key)
+                if decision == "merge":
+                    self.mergeMapping(obj1, obj2)
+                elif decision == "append":
+                    self.mergeSequence(obj1, obj2)
+                elif decision == "overwrite":
+                    map1[key] = obj2
+                    if isinstance(map1[key], Container):
+                        object.__setattr__(map1[key], 'parent', map1)
+                elif decision == "mismatch":
+                    self.handleMismatch(obj1, obj2)
+                else:
+                    msg = "unable to merge: don't know how to implement %r"
+                    raise ValueError(msg % decision)
+
+    def mergeSequence(self, seq1, seq2):
+        """
+        Merge two sequences. The second sequence is unchanged,
+        and the first is changed to have the elements of the second
+        appended to it.
+
+        @param seq1: The sequence to merge into.
+        @type seq1: L{Sequence}.
+        @param seq2: The sequence to merge.
+        @type seq2: L{Sequence}.
+        """
+        data1 = object.__getattribute__(seq1, 'data')
+        data2 = object.__getattribute__(seq2, 'data')
+        for obj in data2:
+            data1.append(obj)
+        comment1 = object.__getattribute__(seq1, 'comments')
+        comment2 = object.__getattribute__(seq2, 'comments')
+        for obj in comment2:
+            comment1.append(obj)
+
+    def handleMismatch(self, obj1, obj2):
+        """
+        Handle a mismatch between two objects.
+
+        @param obj1: The object to merge into.
+        @type obj1: any
+        @param obj2: The object to merge.
+        @type obj2: any
+        """
+        raise ConfigError("unable to merge %r with %r" % (obj1, obj2))
+
+class ConfigList(list):
+    """
+    This class implements an ordered list of configurations and allows you
+    to try getting the configuration from each entry in turn, returning
+    the first successfully obtained value.
+    """
+
+    def getByPath(self, path):
+        """
+        Obtain a value from the first configuration in the list which defines
+        it.
+
+        @param path: The path of the value to retrieve.
+        @type path: str
+        @return: The value from the earliest configuration in the list which
+        defines it.
+        @rtype: any
+        @raise ConfigError: If no configuration in the list has an entry with
+        the specified path.
+        """
+        found = False
+        rv = None
+        for entry in self:
+            try:
+                rv = entry.getByPath(path)
+                found = True
+                break
+            except ConfigError:
+                pass
+        if not found:
+            raise ConfigError("ConfigList path not found '%r'" % path)
+        return rv
diff --git a/src/pyconf_0_3_9_1.py b/src/pyconf_0_3_9_1.py
new file mode 100644 (file)
index 0000000..6bde2da
--- /dev/null
@@ -0,0 +1,1780 @@
+# Copyright 2004-2010 by Vinay Sajip. All Rights Reserved.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appear in all copies and that
+# both that copyright notice and this permission notice appear in
+# supporting documentation, and that the name of Vinay Sajip
+# not be used in advertising or publicity pertaining to distribution
+# of the software without specific, written prior permission.
+# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#  Copyright (C) 2010-2018  CEA/DEN
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2.1 of the License.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+
+# CEA adds : 
+# Possibility to overwrites value in a pyconf file
+# Python 3 porting
+
+
+"""
+This is a configuration module for Python.
+
+This module should work under Python versions >= 2.2, and cannot be used with
+earlier versions since it uses new-style classes.
+
+Development and testing has only been carried out (so far) on Python 2.3.4 and
+Python 2.4.2. See the test module (test_config.py) included in the
+U{distribution<http://www.red-dove.com/python_config.html|_blank>} (follow the
+download link).
+
+A simple example - with the example configuration file::
+
+    messages:
+    [
+      {
+        stream : `sys.stderr`
+        message: 'Welcome'
+        name: 'Harry'
+      }
+      {
+        stream : `sys.stdout`
+        message: 'Welkom'
+        name: 'Ruud'
+      }
+      {
+        stream : $messages[0].stream
+        message: 'Bienvenue'
+        name: Yves
+      }
+    ]
+
+a program to read the configuration would be::
+
+    from config import Config
+
+    f = file('simple.cfg')
+    cfg = Config(f)
+    for m in cfg.messages:
+        s = '%s, %s' % (m.message, m.name)
+        try:
+            print >> m.stream, s
+        except IOError, e:
+            print e
+
+which, when run, would yield the console output::
+
+    Welcome, Harry
+    Welkom, Ruud
+    Bienvenue, Yves
+
+See U{this tutorial<http://www.red-dove.com/python_config.html|_blank>} for more
+information.
+
+@version: 0.3.9.1
+
+@author: Vinay Sajip
+
+@copyright: Copyright (C) 2004-2010 Vinay Sajip. All Rights Reserved.
+
+
+@var streamOpener: The default stream opener. This is a factory function which
+takes a string (e.g. filename) and returns a stream suitable for reading. If
+unable to open the stream, an IOError exception should be thrown.
+
+The default value of this variable is L{defaultStreamOpener}. For an example
+of how it's used, see test_config.py (search for streamOpener).
+"""
+
+__author__  = "Vinay Sajip <vinay_sajip@red-dove.com>"
+__status__  = "alpha"
+__version__ = "0.3.9.1" # cvw modified for salomeTools
+__date__    = "11 May 2018"
+
+from types import StringType, UnicodeType
+
+import codecs
+import logging
+import os
+import sys
+
+WORD = 'a'
+NUMBER = '9'
+STRING = '"'
+EOF = ''
+LCURLY = '{'
+RCURLY = '}'
+LBRACK = '['
+LBRACK2 = 'a['
+RBRACK = ']'
+LPAREN = '('
+LPAREN2 = '(('
+RPAREN = ')'
+DOT = '.'
+COMMA = ','
+COLON = ':'
+AT = '@'
+PLUS = '+'
+MINUS = '-'
+STAR = '*'
+SLASH = '/'
+MOD = '%'
+BACKTICK = '`'
+DOLLAR = '$'
+TRUE = 'True'
+FALSE = 'False'
+NONE = 'None'
+
+WORDCHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
+
+if sys.platform == 'win32':
+    NEWLINE = '\r\n'
+elif os.name == 'mac':
+    NEWLINE = '\r'
+else:
+    NEWLINE = '\n'
+
+try:
+    import encodings.utf_32
+    has_utf32 = True
+except:
+    has_utf32 = False
+
+try:
+    from logging.handlers import NullHandler
+except ImportError:
+    class NullHandler(logging.Handler):
+        def emit(self, record):
+            pass
+
+logger = logging.getLogger(__name__)
+if not logger.handlers:
+    logger.addHandler(NullHandler())
+
+class ConfigInputStream(object):
+    """
+    An input stream which can read either ANSI files with default encoding
+    or Unicode files with BOMs.
+
+    Handles UTF-8, UTF-16LE, UTF-16BE. Could handle UTF-32 if Python had
+    built-in support.
+    """
+    def __init__(self, stream):
+        """
+        Initialize an instance.
+
+        @param stream: The underlying stream to be read. Should be seekable.
+        @type stream: A stream (file-like object).
+        """
+        encoding = None
+        signature = stream.read(4)
+        used = -1
+        if has_utf32:
+            if signature == codecs.BOM_UTF32_LE:
+                encoding = 'utf-32le'
+            elif signature == codecs.BOM_UTF32_BE:
+                encoding = 'utf-32be'
+        if encoding is None:
+            if signature[:3] == codecs.BOM_UTF8:
+                used = 3
+                encoding = 'utf-8'
+            elif signature[:2] == codecs.BOM_UTF16_LE:
+                used = 2
+                encoding = 'utf-16le'
+            elif signature[:2] == codecs.BOM_UTF16_BE:
+                used = 2
+                encoding = 'utf-16be'
+            else:
+                used = 0
+        if used >= 0:
+            stream.seek(used)
+        if encoding:
+            reader = codecs.getreader(encoding)
+            stream = reader(stream)
+        self.stream = stream
+        self.encoding = encoding
+
+    def read(self, size):
+        if (size == 0) or (self.encoding is None):
+            rv = self.stream.read(size)
+        else:
+            rv = u''
+            while size > 0:
+                rv += self.stream.read(1)
+                size -= 1
+        return rv
+
+    def close(self):
+        self.stream.close()
+
+    def readline(self):
+        if self.encoding is None:
+            line = ''
+        else:
+            line = u''
+        while True:
+            c = self.stream.read(1)
+            if c:
+                line += c
+            if c == '\n':
+                break
+        return line
+
+class ConfigOutputStream(object):
+    """
+    An output stream which can write either ANSI files with default encoding
+    or Unicode files with BOMs.
+
+    Handles UTF-8, UTF-16LE, UTF-16BE. Could handle UTF-32 if Python had
+    built-in support.
+    """
+
+    def __init__(self, stream, encoding=None):
+        """
+        Initialize an instance.
+
+        @param stream: The underlying stream to be written.
+        @type stream: A stream (file-like object).
+        @param encoding: The desired encoding.
+        @type encoding: str
+        """
+        if encoding is not None:
+            encoding = str(encoding).lower()
+        self.encoding = encoding
+        if encoding == "utf-8":
+            stream.write(codecs.BOM_UTF8)
+        elif encoding == "utf-16be":
+            stream.write(codecs.BOM_UTF16_BE)
+        elif encoding == "utf-16le":
+            stream.write(codecs.BOM_UTF16_LE)
+        elif encoding == "utf-32be":
+            stream.write(codecs.BOM_UTF32_BE)
+        elif encoding == "utf-32le":
+            stream.write(codecs.BOM_UTF32_LE)
+
+        if encoding is not None:
+            writer = codecs.getwriter(encoding)
+            stream = writer(stream)
+        self.stream = stream
+
+    def write(self, data):
+        self.stream.write(data)
+
+    def flush(self):
+        self.stream.flush()
+
+    def close(self):
+        self.stream.close()
+
+def defaultStreamOpener(name):
+    """
+    This function returns a read-only stream, given its name. The name passed
+    in should correspond to an existing stream, otherwise an exception will be
+    raised.
+
+    This is the default value of L{streamOpener}; assign your own callable to
+    streamOpener to return streams based on names. For example, you could use
+    urllib2.urlopen().
+
+    @param name: The name of a stream, most commonly a file name.
+    @type name: str
+    @return: A stream with the specified name.
+    @rtype: A read-only stream (file-like object)
+    """
+    return ConfigInputStream(file(name, 'rb'))
+
+streamOpener = None
+
+__resolveOverwrite__ = True
+
+class ConfigError(Exception):
+    """
+    This is the base class of exceptions raised by this module.
+    """
+    pass
+
+class ConfigFormatError(ConfigError):
+    """
+    This is the base class of exceptions raised due to syntax errors in
+    configurations.
+    """
+    pass
+
+class ConfigResolutionError(ConfigError):
+    """
+    This is the base class of exceptions raised due to semantic errors in
+    configurations.
+    """
+    pass
+
+def isWord(s):
+    """
+    See if a passed-in value is an identifier. If the value passed in is not a
+    string, False is returned. An identifier consists of alphanumerics or
+    underscore characters.
+
+    Examples::
+
+        isWord('a word') ->False
+        isWord('award') -> True
+        isWord(9) -> False
+        isWord('a_b_c_') ->True
+
+    @note: isWord('9abc') will return True - not exactly correct, but adequate
+    for the way it's used here.
+
+    @param s: The name to be tested
+    @type s: any
+    @return: True if a word, else False
+    @rtype: bool
+    """
+    if type(s) != type(''):
+        return False
+    s = s.replace('_', '')
+    return s.isalnum()
+
+def makePath(prefix, suffix):
+    """
+    Make a path from a prefix and suffix.
+
+    Examples::
+
+        makePath('', 'suffix') -> 'suffix'
+        makePath('prefix', 'suffix') -> 'prefix.suffix'
+        makePath('prefix', '[1]') -> 'prefix[1]'
+
+    @param prefix:  The prefix to use. If it evaluates as false, the suffix
+                    is returned.
+    @type prefix:   str
+    @param suffix:  The suffix to use. It is either an identifier or an
+                    index in brackets.
+    @type suffix:   str
+    @return:        The path concatenation of prefix and suffix, with a
+                    dot if the suffix is not a bracketed index.
+    @rtype:         str
+
+    """
+    if not prefix:
+        rv = suffix
+    elif suffix[0] == '[':
+        rv = prefix + suffix
+    else:
+        rv = prefix + '.' + suffix
+    return rv
+
+
+class Container(object):
+    """
+    This internal class is the base class for mappings and sequences.
+
+    @ivar path: A string which describes how to get
+    to this instance from the root of the hierarchy.
+
+    Example::
+
+        a.list.of[1].or['more'].elements
+    """
+    def __init__(self, parent):
+        """
+        Initialize an instance.
+
+        @param parent: The parent of this instance in the hierarchy.
+        @type parent: A L{Container} instance.
+        """
+        object.__setattr__(self, 'parent', parent)
+
+    def setPath(self, path):
+        """
+        Set the path for this instance.
+        @param path: The path - a string which describes how to get
+        to this instance from the root of the hierarchy.
+        @type path: str
+        """
+        object.__setattr__(self, 'path', path)
+
+    def evaluate(self, item):
+        """
+        Evaluate items which are instances of L{Reference} or L{Expression}.
+
+        L{Reference} instances are evaluated using L{Reference.resolve},
+        and L{Expression} instances are evaluated using
+        L{Expression.evaluate}.
+
+        @param item: The item to be evaluated.
+        @type item: any
+        @return: If the item is an instance of L{Reference} or L{Expression},
+        the evaluated value is returned, otherwise the item is returned
+        unchanged.
+        """
+        if isinstance(item, Reference):
+            item = item.resolve(self)
+        elif isinstance(item, Expression):
+            item = item.evaluate(self)
+        return item
+
+    def writeToStream(self, stream, indent, container):
+        """
+        Write this instance to a stream at the specified indentation level.
+
+        Should be redefined in subclasses.
+
+        @param stream: The stream to write to
+        @type stream: A writable stream (file-like object)
+        @param indent: The indentation level
+        @type indent: int
+        @param container: The container of this instance
+        @type container: L{Container}
+        @raise NotImplementedError: If a subclass does not override this
+        """
+        raise NotImplementedError
+
+    def writeValue(self, value, stream, indent):
+        if isinstance(self, Mapping):
+            indstr = ' '
+        else:
+            indstr = indent * '  '
+        if isinstance(value, Reference) or isinstance(value, Expression):
+            stream.write('%s%r%s' % (indstr, value, NEWLINE))
+        else:
+            if (type(value) is StringType): # and not isWord(value):
+                value = repr(value)
+            stream.write('%s%s%s' % (indstr, value, NEWLINE))
+
+class Mapping(Container):
+    """
+    This internal class implements key-value mappings in configurations.
+    """
+
+    def __init__(self, parent=None):
+        """
+        Initialize an instance.
+
+        @param parent: The parent of this instance in the hierarchy.
+        @type parent: A L{Container} instance.
+        """
+        Container.__init__(self, parent)
+        object.__setattr__(self, 'path', '')
+        object.__setattr__(self, 'data', {})
+        object.__setattr__(self, 'order', [])   # to preserve ordering
+        object.__setattr__(self, 'comments', {})
+
+    def __delitem__(self, key):
+        """
+        Remove an item
+        """
+        data = object.__getattribute__(self, 'data')
+        if key not in data:
+            raise AttributeError(key)
+        order = object.__getattribute__(self, 'order')
+        comments = object.__getattribute__(self, 'comments')
+        del data[key]
+        order.remove(key)
+        del comments[key]
+
+    def __getitem__(self, key):
+        data = object.__getattribute__(self, 'data')
+        if key not in data:
+            raise AttributeError(key)
+        rv = data[key]
+        return self.evaluate(rv)
+
+    __getattr__ = __getitem__
+    
+    """
+    def __getattribute__(self, name):
+        if name == "__dict__":
+            return {}
+        if name in ["__methods__", "__members__"]:
+            return []
+        #if name == "__class__":
+        #    return ''
+        data = object.__getattribute__(self, "data")
+        useData = data.has_key(name)
+        if useData:
+            rv = getattr(data, name)
+        else:
+            rv = object.__getattribute__(self, name)
+            if rv is None:
+                raise AttributeError(name)
+        return rv
+    """
+
+    def iteritems(self):
+        for key in self.keys():
+            yield(key, self[key])
+        raise StopIteration
+
+    def __contains__(self, item):
+        order = object.__getattribute__(self, 'order')
+        return item in order
+
+    def addMapping(self, key, value, comment, setting=False):
+        """
+        Add a key-value mapping with a comment.
+
+        @param key: The key for the mapping.
+        @type key: str
+        @param value: The value for the mapping.
+        @type value: any
+        @param comment: The comment for the key (can be None).
+        @type comment: str
+        @param setting: If True, ignore clashes. This is set
+        to true when called from L{__setattr__}.
+        @raise ConfigFormatError: If an existing key is seen
+        again and setting is False.
+        """
+        data = object.__getattribute__(self, 'data')
+        order = object.__getattribute__(self, 'order')
+        comments = object.__getattribute__(self, 'comments')
+
+        data[key] = value
+        if key not in order:
+            order.append(key)
+        elif not setting:
+            raise ConfigFormatError("repeated key: %s" % key)
+        comments[key] = comment
+
+    def __setattr__(self, name, value):
+        self.addMapping(name, value, None, True)
+
+    __setitem__ = __setattr__
+
+    def keys(self):
+        """
+        Return the keys in a similar way to a dictionary.
+        """
+        return object.__getattribute__(self, 'order')
+
+    def get(self, key, default=None):
+        """
+        Allows a dictionary-style get operation.
+        """
+        if key in self:
+            return self[key]
+        return default
+
+    def __str__(self):
+        return str(object.__getattribute__(self, 'data'))
+
+    def __repr__(self):
+        return repr(object.__getattribute__(self, 'data'))
+
+    def __len__(self):
+        return len(object.__getattribute__(self, 'order'))
+
+    def __iter__(self):
+        return self.iterkeys()
+
+    def iterkeys(self):
+        order = object.__getattribute__(self, 'order')
+        return order.__iter__()
+
+    def writeToStream(self, stream, indent, container):
+        """
+        Write this instance to a stream at the specified indentation level.
+
+        Should be redefined in subclasses.
+
+        @param stream: The stream to write to
+        @type stream: A writable stream (file-like object)
+        @param indent: The indentation level
+        @type indent: int
+        @param container: The container of this instance
+        @type container: L{Container}
+        """
+        indstr = indent * '  '
+        if len(self) == 0:
+            stream.write(' { }%s' % NEWLINE)
+        else:
+            if isinstance(container, Mapping):
+                stream.write(NEWLINE)
+            stream.write('%s{%s' % (indstr, NEWLINE))
+            self.__save__(stream, indent + 1)
+            stream.write('%s}%s' % (indstr, NEWLINE))
+
+    def __save__(self, stream, indent=0):
+        """
+        Save this configuration to the specified stream.
+        @param stream: A stream to which the configuration is written.
+        @type stream: A write-only stream (file-like object).
+        @param indent: The indentation level for the output.
+        @type indent: int
+        """
+        indstr = indent * '  '
+        order = object.__getattribute__(self, 'order')
+        data = object.__getattribute__(self, 'data')
+        maxlen = 0 # max(map(lambda x: len(x), order))
+        for key in order:
+            comment = self.comments[key]
+            if isWord(key):
+                skey = key
+            else:
+                skey = repr(key)
+            if comment:
+                stream.write('%s#%s' % (indstr, comment))
+            if skey.startswith("u'"):
+                skey = skey[1:]
+            stream.write('%s%-*s :' % (indstr, maxlen, skey))
+            value = data[key]
+            if isinstance(value, Container):
+                value.writeToStream(stream, indent, self)
+            else:
+                self.writeValue(value, stream, indent)
+
+class Config(Mapping):
+    """
+    This class represents a configuration, and is the only one which clients
+    need to interface to, under normal circumstances.
+    """
+
+    class Namespace(object):
+        """
+        This internal class is used for implementing default namespaces.
+
+        An instance acts as a namespace.
+        """
+        def __init__(self):
+            self.sys = sys
+            self.os = os
+
+        def __repr__(self):
+            return "<Namespace('%s')>" % ','.join(self.__dict__.keys())
+
+    def __init__(self, streamOrFile=None, parent=None, PWD = None):
+        """
+        Initializes an instance.
+
+        @param streamOrFile: If specified, causes this instance to be loaded
+        from the stream (by calling L{load}). If a string is provided, it is
+        passed to L{streamOpener} to open a stream. Otherwise, the passed
+        value is assumed to be a stream and used as is.
+        @type streamOrFile: A readable stream (file-like object) or a name.
+        @param parent: If specified, this becomes the parent of this instance
+        in the configuration hierarchy.
+        @type parent: a L{Container} instance.
+        """
+        try: # Python 3 compatibility
+            if isinstance(streamOrFile, unicode):
+                streamOrFile = streamOrFile.encode()
+        except NameError:
+            pass
+        Mapping.__init__(self, parent)
+        object.__setattr__(self, 'reader', ConfigReader(self))
+        object.__setattr__(self, 'namespaces', [Config.Namespace()])
+        object.__setattr__(self, 'resolving', set())
+        if streamOrFile is not None:
+            if isinstance(streamOrFile, StringType) or isinstance(streamOrFile, UnicodeType):
+                global streamOpener
+                if streamOpener is None:
+                    streamOpener = defaultStreamOpener
+                streamOrFile = streamOpener(streamOrFile)
+            load = object.__getattribute__(self, "load")
+            load(streamOrFile)
+            # Specific add for salomeTools : PWD
+            if PWD:
+                key, pwd = PWD
+                if key == "":
+                    self.PWD = pwd
+                else:
+                    self[key].PWD = pwd
+
+    def load(self, stream):
+        """
+        Load the configuration from the specified stream. Multiple streams can
+        be used to populate the same instance, as long as there are no
+        clashing keys. The stream is closed.
+        @param stream: A stream from which the configuration is read.
+        @type stream: A read-only stream (file-like object).
+        @raise ConfigError: if keys in the loaded configuration clash with
+        existing keys.
+        @raise ConfigFormatError: if there is a syntax error in the stream.
+        """
+        reader = object.__getattribute__(self, 'reader')
+        #object.__setattr__(self, 'root', reader.load(stream))
+        reader.load(stream)
+        stream.close()
+
+    def addNamespace(self, ns, name=None):
+        """
+        Add a namespace to this configuration which can be used to evaluate
+        (resolve) dotted-identifier expressions.
+        @param ns: The namespace to be added.
+        @type ns: A module or other namespace suitable for passing as an
+        argument to vars().
+        @param name: A name for the namespace, which, if specified, provides
+        an additional level of indirection.
+        @type name: str
+        """
+        namespaces = object.__getattribute__(self, 'namespaces')
+        if name is None:
+            namespaces.append(ns)
+        else:
+            setattr(namespaces[0], name, ns)
+
+    def removeNamespace(self, ns, name=None):
+        """
+        Remove a namespace added with L{addNamespace}.
+        @param ns: The namespace to be removed.
+        @param name: The name which was specified when L{addNamespace} was
+        called.
+        @type name: str
+        """
+        namespaces = object.__getattribute__(self, 'namespaces')
+        if name is None:
+            namespaces.remove(ns)
+        else:
+            delattr(namespaces[0], name)
+
+    def __save__(self, stream, indent=0, no_close=False):
+        """
+        Save this configuration to the specified stream. The stream is
+        closed if this is the top-level configuration in the hierarchy.
+        L{Mapping.__save__} is called to do all the work.
+        @param stream: A stream to which the configuration is written.
+        @type stream: A write-only stream (file-like object).
+        @param indent: The indentation level for the output.
+        @type indent: int
+        """
+        Mapping.__save__(self, stream, indent)
+        if indent == 0 and not no_close:
+            stream.close()
+
+    def getByPath(self, path):
+        """
+        Obtain a value in the configuration via its path.
+        @param path: The path of the required value
+        @type path: str
+        @return the value at the specified path.
+        @rtype: any
+        @raise ConfigError: If the path is invalid
+        """
+        s = 'self.' + path
+        try:
+            return eval(s)
+        except Exception, e:
+            raise ConfigError(str(e))
+
+class Sequence(Container):
+    """
+    This internal class implements a value which is a sequence of other values.
+    """
+    class SeqIter(object):
+        """
+        This internal class implements an iterator for a L{Sequence} instance.
+        """
+        def __init__(self, seq):
+            self.seq = seq
+            self.limit = len(object.__getattribute__(seq, 'data'))
+            self.index = 0
+
+        def __iter__(self):
+            return self
+
+        def next(self):
+            if self.index >= self.limit:
+                raise StopIteration
+            rv = self.seq[self.index]
+            self.index += 1
+            return rv
+        
+        # This method is for python3 compatibility
+        def __next__(self): 
+            if self.index >= self.limit:
+                raise StopIteration
+            rv = self.seq[self.index]
+            self.index += 1
+            return rv
+
+    def __init__(self, parent=None):
+        """
+        Initialize an instance.
+
+        @param parent: The parent of this instance in the hierarchy.
+        @type parent: A L{Container} instance.
+        """
+        Container.__init__(self, parent)
+        object.__setattr__(self, 'data', [])
+        object.__setattr__(self, 'comments', [])
+
+    def append(self, item, comment):
+        """
+        Add an item to the sequence.
+
+        @param item: The item to add.
+        @type item: any
+        @param comment: A comment for the item.
+        @type comment: str
+        """
+        data = object.__getattribute__(self, 'data')
+        comments = object.__getattribute__(self, 'comments')
+        data.append(item)
+        comments.append(comment)
+
+    def __getitem__(self, index):
+        data = object.__getattribute__(self, 'data')
+        try:
+            rv = data[index]
+        except (IndexError, KeyError, TypeError):
+            raise ConfigResolutionError('%r is not a valid index for %r' % (index, object.__getattribute__(self, 'path')))
+        if not isinstance(rv, list):
+            rv = self.evaluate(rv)
+        else:
+            # deal with a slice
+            result = []
+            for a in rv:
+                result.append(self.evaluate(a))
+            rv = result
+        return rv
+
+    def __iter__(self):
+        return Sequence.SeqIter(self)
+
+    def __repr__(self):
+        return repr(object.__getattribute__(self, 'data'))
+
+    def __str__(self):
+        return str(self[:]) # using the slice evaluates the contents
+
+    def __len__(self):
+        return len(object.__getattribute__(self, 'data'))
+
+    def writeToStream(self, stream, indent, container):
+        """
+        Write this instance to a stream at the specified indentation level.
+
+        Should be redefined in subclasses.
+
+        @param stream: The stream to write to
+        @type stream: A writable stream (file-like object)
+        @param indent: The indentation level
+        @type indent: int
+        @param container: The container of this instance
+        @type container: L{Container}
+        """
+        indstr = indent * '  '
+        if len(self) == 0:
+            stream.write(' [ ]%s' % NEWLINE)
+        else:
+            if isinstance(container, Mapping):
+                stream.write(NEWLINE)
+            stream.write('%s[%s' % (indstr, NEWLINE))
+            self.__save__(stream, indent + 1)
+            stream.write('%s]%s' % (indstr, NEWLINE))
+
+    def __save__(self, stream, indent):
+        """
+        Save this instance to the specified stream.
+        @param stream: A stream to which the configuration is written.
+        @type stream: A write-only stream (file-like object).
+        @param indent: The indentation level for the output, > 0
+        @type indent: int
+        """
+        if indent == 0:
+            raise ConfigError("sequence cannot be saved as a top-level item")
+        data = object.__getattribute__(self, 'data')
+        comments = object.__getattribute__(self, 'comments')
+        indstr = indent * '  '
+        for i in range(0, len(data)):
+            value = data[i]
+            comment = comments[i]
+            if comment:
+                stream.write('%s#%s' % (indstr, comment))
+            if isinstance(value, Container):
+                value.writeToStream(stream, indent, self)
+            else:
+                self.writeValue(value, stream, indent)
+
+class Reference(object):
+    """
+    This internal class implements a value which is a reference to another value.
+    """
+    def __init__(self, config, type, ident):
+        """
+        Initialize an instance.
+
+        @param config: The configuration which contains this reference.
+        @type config: A L{Config} instance.
+        @param type: The type of reference.
+        @type type: BACKTICK or DOLLAR
+        @param ident: The identifier which starts the reference.
+        @type ident: str
+        """
+        self.config = config
+        self.type = type
+        self.elements = [ident]
+
+    def addElement(self, type, ident):
+        """
+        Add an element to the reference.
+
+        @param type: The type of reference.
+        @type type: BACKTICK or DOLLAR
+        @param ident: The identifier which continues the reference.
+        @type ident: str
+        """
+        self.elements.append((type, ident))
+
+    def findConfig(self, container):
+        """
+        Find the closest enclosing configuration to the specified container.
+
+        @param container: The container to start from.
+        @type container: L{Container}
+        @return: The closest enclosing configuration, or None.
+        @rtype: L{Config}
+        """
+        while (container is not None) and not isinstance(container, Config):
+            container = object.__getattribute__(container, 'parent')
+        return container
+
+    def resolve(self, container):
+        """
+        Resolve this instance in the context of a container.
+
+        @param container: The container to resolve from.
+        @type container: L{Container}
+        @return: The resolved value.
+        @rtype: any
+        @raise ConfigResolutionError: If resolution fails.
+        """
+        rv = None
+        path = object.__getattribute__(container, 'path')
+        current = self.findConfig(container)
+        while current is not None:
+            if self.type == BACKTICK:
+                namespaces = object.__getattribute__(current, 'namespaces')
+                found = False
+                s = str(self)[1:-1]
+                for ns in namespaces:
+                    try:
+                        try:
+                            rv = eval(s, vars(ns))
+                        except TypeError: #Python 2.7 - vars is a dictproxy
+                            rv = eval(s, {}, vars(ns))
+                        found = True
+                        break
+                    except:
+                        logger.debug("unable to resolve %r in %r", s, ns)
+                        pass
+                if found:
+                    break
+            else:
+                firstkey = self.elements[0]
+                if firstkey in current.resolving:
+                    current.resolving.remove(firstkey)
+                    raise ConfigResolutionError("Circular reference: %r" % firstkey)
+                current.resolving.add(firstkey)
+                key = firstkey
+                try:
+                    rv = current[key]
+                    for item in self.elements[1:]:
+                        key = item[1]
+                        rv = rv[key]
+                    current.resolving.remove(firstkey)
+                    break
+                except ConfigResolutionError:
+                    raise
+                except:
+                    logger.debug("Unable to resolve %r: %s", key, sys.exc_info()[1])
+                    rv = None
+                    pass
+                current.resolving.discard(firstkey)
+            current = self.findConfig(object.__getattribute__(current, 'parent'))
+        if current is None:
+            raise ConfigResolutionError("unable to evaluate %r in the configuration %s" % (self, path))
+        return rv
+
+    def __str__(self):
+        s = self.elements[0]
+        for tt, tv in self.elements[1:]:
+            if tt == DOT:
+                s += '.%s' % tv
+            else:
+                s += '[%r]' % tv
+        if self.type == BACKTICK:
+            return BACKTICK + s + BACKTICK
+        else:
+            return DOLLAR + s
+
+    def __repr__(self):
+        return self.__str__()
+
+class Expression(object):
+    """
+    This internal class implements a value which is obtained by evaluating an expression.
+    """
+    def __init__(self, op, lhs, rhs):
+        """
+        Initialize an instance.
+
+        @param op: the operation expressed in the expression.
+        @type op: PLUS, MINUS, STAR, SLASH, MOD
+        @param lhs: the left-hand-side operand of the expression.
+        @type lhs: any Expression or primary value.
+        @param rhs: the right-hand-side operand of the expression.
+        @type rhs: any Expression or primary value.
+        """
+        self.op = op
+        self.lhs = lhs
+        self.rhs = rhs
+
+    def __str__(self):
+        return '%r %s %r' % (self.lhs, self.op, self.rhs)
+
+    def __repr__(self):
+        return self.__str__()
+
+    def evaluate(self, container):
+        """
+        Evaluate this instance in the context of a container.
+
+        @param container: The container to evaluate in from.
+        @type container: L{Container}
+        @return: The evaluated value.
+        @rtype: any
+        @raise ConfigResolutionError: If evaluation fails.
+        @raise ZeroDivideError: If division by zero occurs.
+        @raise TypeError: If the operation is invalid, e.g.
+        subtracting one string from another.
+        """
+        lhs = self.lhs
+        if isinstance(lhs, Reference):
+            lhs = lhs.resolve(container)
+        elif isinstance(lhs, Expression):
+            lhs = lhs.evaluate(container)
+        rhs = self.rhs
+        if isinstance(rhs, Reference):
+            rhs = rhs.resolve(container)
+        elif isinstance(rhs, Expression):
+            rhs = rhs.evaluate(container)
+        op = self.op
+        if op == PLUS:
+            rv = lhs + rhs
+        elif op == MINUS:
+            rv = lhs - rhs
+        elif op == STAR:
+            rv = lhs * rhs
+        elif op == SLASH:
+            rv = lhs / rhs
+        else:
+            rv = lhs % rhs
+        return rv
+
+class ConfigReader(object):
+    """
+    This internal class implements a parser for configurations.
+    """
+
+    def __init__(self, config):
+        self.filename = None
+        self.config = config
+        self.lineno = 0
+        self.colno = 0
+        self.lastc = None
+        self.last_token = None
+        self.commentchars = '#'
+        self.whitespace = ' \t\r\n'
+        self.quotes = '\'"'
+        self.punct = ':-+*/%,.{}[]()@`$'
+        self.digits = '0123456789'
+        self.wordchars = '%s' % WORDCHARS # make a copy
+        self.identchars = self.wordchars + self.digits
+        self.pbchars = []
+        self.pbtokens = []
+        self.comment = None
+
+    def location(self):
+        """
+        Return the current location (filename, line, column) in the stream
+        as a string.
+
+        Used when printing error messages,
+
+        @return: A string representing a location in the stream being read.
+        @rtype: str
+        """
+        return "%s(%d,%d)" % (self.filename, self.lineno, self.colno)
+
+    def getChar(self):
+        """
+        Get the next char from the stream. Update line and column numbers
+        appropriately.
+
+        @return: The next character from the stream.
+        @rtype: str
+        """
+        if self.pbchars:
+            c = self.pbchars.pop()
+            if isinstance(c,bytes):
+                c = c.decode()
+        else:
+            c = self.stream.read(1)
+            if isinstance(c,bytes):
+                c = c.decode()
+            self.colno += 1
+            if c == '\n':
+                self.lineno += 1
+                self.colno = 1
+        return c
+
+    def __repr__(self):
+        return "<ConfigReader at 0x%08x>" % id(self)
+
+    __str__ = __repr__
+
+    def getToken(self):
+        """
+        Get a token from the stream. String values are returned in a form
+        where you need to eval() the returned value to get the actual
+        string. The return value is (token_type, token_value).
+
+        Multiline string tokenizing is thanks to David Janes (BlogMatrix)
+
+        @return: The next token.
+        @rtype: A token tuple.
+        """
+        if self.pbtokens:
+            return self.pbtokens.pop()
+        stream = self.stream
+        self.comment = None
+        token = ''
+        tt = EOF
+        while True:
+            c = self.getChar()
+            if not c:
+                break
+            elif c == '#':
+                if self.comment :
+                    self.comment += '#' + stream.readline()
+                else :
+                    self.comment = stream.readline()
+                self.lineno += 1
+                continue
+            if c in self.quotes:
+                token = c
+                quote = c
+                tt = STRING
+                escaped = False
+                multiline = False
+                c1 = self.getChar()
+                if c1 == quote:
+                    c2 = self.getChar()
+                    if c2 == quote:
+                        multiline = True
+                        token += quote
+                        token += quote
+                    else:
+                        self.pbchars.append(c2)
+                        self.pbchars.append(c1)
+                else:
+                    self.pbchars.append(c1)
+                while True:
+                    c = self.getChar()
+                    if not c:
+                        break
+                    token += c
+                    if (c == quote) and not escaped:
+                        if not multiline or (len(token) >= 6 and token.endswith(token[:3]) and token[-4] != '\\'):
+                            break
+                    if c == '\\':
+                        escaped = not escaped
+                    else:
+                        escaped = False
+                if not c:
+                    raise ConfigFormatError('%s: Unterminated quoted string: %r, %r' % (self.location(), token, c))
+                break
+            if c in self.whitespace:
+                self.lastc = c
+                continue
+            elif c in self.punct:
+                token = c
+                tt = c
+                if (self.lastc == ']') or (self.lastc in self.identchars):
+                    if c == '[':
+                        tt = LBRACK2
+                    elif c == '(':
+                        tt = LPAREN2
+                break
+            elif c in self.digits:
+                token = c
+                tt = NUMBER
+                in_exponent=False
+                while True:
+                    c = self.getChar()
+                    if not c:
+                        break
+                    if c in self.digits:
+                        token += c
+                    elif (c == '.') and token.find('.') < 0 and not in_exponent:
+                        token += c
+                    elif (c == '-') and token.find('-') < 0 and in_exponent:
+                        token += c
+                    elif (c in 'eE') and token.find('e') < 0 and\
+                         token.find('E') < 0:
+                        token += c
+                        in_exponent = True
+                    else:
+                        if c and (c not in self.whitespace):
+                            self.pbchars.append(c)
+                        break
+                break
+            elif c in self.wordchars:
+                token = c
+                tt = WORD
+                c = self.getChar()
+                while c and (c in self.identchars):
+                    token += c
+                    c = self.getChar()
+                if c: # and c not in self.whitespace:
+                    self.pbchars.append(c)
+                if token == "True":
+                    tt = TRUE
+                elif token == "False":
+                    tt = FALSE
+                elif token == "None":
+                    tt = NONE
+                break
+            else:
+                raise ConfigFormatError('%s: Unexpected character: %r' % (self.location(), c))
+        if token:
+            self.lastc = token[-1]
+        else:
+            self.lastc = None
+        self.last_token = tt
+        
+        # Python 2.x specific unicode conversion
+        if sys.version_info[0] == 2 and tt == WORD and isinstance(token, unicode):
+            token = token.encode('ascii')
+        return (tt, token)
+
+    def load(self, stream, parent=None, suffix=None):
+        """
+        Load the configuration from the specified stream.
+
+        @param stream: A stream from which to load the configuration.
+        @type stream: A stream (file-like object).
+        @param parent: The parent of the configuration (to which this reader
+        belongs) in the hierarchy. Specified when the configuration is
+        included in another one.
+        @type parent: A L{Container} instance.
+        @param suffix: The suffix of this configuration in the parent
+        configuration. Should be specified whenever the parent is not None.
+        @raise ConfigError: If parent is specified but suffix is not.
+        @raise ConfigFormatError: If there are syntax errors in the stream.
+        """
+        if parent is not None:
+            if suffix is None:
+                raise ConfigError("internal error: load called with parent but no suffix")
+            self.config.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
+        self.setStream(stream)
+        self.token = self.getToken()
+        self.parseMappingBody(self.config)
+        if self.token[0] != EOF:
+            raise ConfigFormatError('%s: expecting EOF, found %r' % (self.location(), self.token[1]))
+
+    def setStream(self, stream):
+        """
+        Set the stream to the specified value, and prepare to read from it.
+
+        @param stream: A stream from which to load the configuration.
+        @type stream: A stream (file-like object).
+        """
+        self.stream = stream
+        if hasattr(stream, 'name'):
+            filename = stream.name
+        else:
+            filename = '?'
+        self.filename = filename
+        self.lineno = 1
+        self.colno = 1
+
+    def match(self, t):
+        """
+        Ensure that the current token type matches the specified value, and
+        advance to the next token.
+
+        @param t: The token type to match.
+        @type t: A valid token type.
+        @return: The token which was last read from the stream before this
+        function is called.
+        @rtype: a token tuple - see L{getToken}.
+        @raise ConfigFormatError: If the token does not match what's expected.
+        """
+        if self.token[0] != t:
+            raise ConfigFormatError("%s: expecting %s, found %r" % (self.location(), t, self.token[1]))
+        rv = self.token
+        self.token = self.getToken()
+        return rv
+
+    def parseMappingBody(self, parent):
+        """
+        Parse the internals of a mapping, and add entries to the provided
+        L{Mapping}.
+
+        @param parent: The mapping to add entries to.
+        @type parent: A L{Mapping} instance.
+        """
+        while self.token[0] in [WORD, STRING]:
+            self.parseKeyValuePair(parent)
+
+    def parseKeyValuePair(self, parent):
+        """
+        Parse a key-value pair, and add it to the provided L{Mapping}.
+
+        @param parent: The mapping to add entries to.
+        @type parent: A L{Mapping} instance.
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        comment = self.comment
+        tt, tv = self.token
+        if tt == WORD:
+            key = tv
+            suffix = tv
+        elif tt == STRING:
+            key = eval(tv)
+            suffix = '[%s]' % tv
+        else:
+            msg = "%s: expecting word or string, found %r"
+            raise ConfigFormatError(msg % (self.location(), tv))
+        self.token = self.getToken()
+        # for now, we allow key on its own as a short form of key : True
+        if self.token[0] == COLON:
+            self.token = self.getToken()
+            value = self.parseValue(parent, suffix)
+        else:
+            value = True
+        try:
+            parent.addMapping(key, value, comment)
+        except Exception, e:
+            raise ConfigFormatError("%s: %s, %r" % (self.location(), e,
+                                    self.token[1]))
+        tt = self.token[0]
+        if tt not in [EOF, WORD, STRING, RCURLY, COMMA]:
+            msg = "%s: expecting one of EOF, WORD, STRING, \
+RCURLY, COMMA, found %r"
+            raise ConfigFormatError(msg  % (self.location(), self.token[1]))
+        if tt == COMMA:
+            self.token = self.getToken()
+
+    def parseValue(self, parent, suffix):
+        """
+        Parse a value.
+
+        @param parent: The container to which the value will be added.
+        @type parent: A L{Container} instance.
+        @param suffix: The suffix for the value.
+        @type suffix: str
+        @return: The value
+        @rtype: any
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        tt = self.token[0]
+        if tt in [STRING, WORD, NUMBER, LPAREN, DOLLAR,
+                  TRUE, FALSE, NONE, BACKTICK, MINUS]:
+            rv = self.parseScalar()
+        elif tt == LBRACK:
+            rv = self.parseSequence(parent, suffix)
+        elif tt in [LCURLY, AT]:
+            rv = self.parseMapping(parent, suffix)
+        else:
+            raise ConfigFormatError("%s: unexpected input: %r" %
+               (self.location(), self.token[1]))
+        return rv
+
+    def parseSequence(self, parent, suffix):
+        """
+        Parse a sequence.
+
+        @param parent: The container to which the sequence will be added.
+        @type parent: A L{Container} instance.
+        @param suffix: The suffix for the value.
+        @type suffix: str
+        @return: a L{Sequence} instance representing the sequence.
+        @rtype: L{Sequence}
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        rv = Sequence(parent)
+        rv.setPath(makePath(object.__getattribute__(parent, 'path'), suffix))
+        self.match(LBRACK)
+        comment = self.comment
+        tt = self.token[0]
+        while tt in [STRING, WORD, NUMBER, LCURLY, LBRACK, LPAREN, DOLLAR,
+                     TRUE, FALSE, NONE, BACKTICK, MINUS]:
+            suffix = '[%d]' % len(rv)
+            value = self.parseValue(parent, suffix)
+            rv.append(value, comment)
+            tt = self.token[0]
+            comment = self.comment
+            if tt == COMMA:
+                self.match(COMMA)
+                tt = self.token[0]
+                comment = self.comment
+                continue
+        self.match(RBRACK)
+        return rv
+
+    def parseMapping(self, parent, suffix):
+        """
+        Parse a mapping.
+
+        @param parent: The container to which the mapping will be added.
+        @type parent: A L{Container} instance.
+        @param suffix: The suffix for the value.
+        @type suffix: str
+        @return: a L{Mapping} instance representing the mapping.
+        @rtype: L{Mapping}
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        if self.token[0] == LCURLY:
+            self.match(LCURLY)
+            rv = Mapping(parent)
+            rv.setPath(
+               makePath(object.__getattribute__(parent, 'path'), suffix))
+            self.parseMappingBody(rv)
+            self.match(RCURLY)
+        else:
+            self.match(AT)
+            tt, fn = self.match(STRING)
+            rv = Config(eval(fn), parent)
+        return rv
+
+    def parseScalar(self):
+        """
+        Parse a scalar - a terminal value such as a string or number, or
+        an L{Expression} or L{Reference}.
+
+        @return: the parsed scalar
+        @rtype: any scalar
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        lhs = self.parseTerm()
+        tt = self.token[0]
+        while tt in [PLUS, MINUS]:
+            self.match(tt)
+            rhs = self.parseTerm()
+            lhs = Expression(tt, lhs, rhs)
+            tt = self.token[0]
+        return lhs
+
+    def parseTerm(self):
+        """
+        Parse a term in an additive expression (a + b, a - b)
+
+        @return: the parsed term
+        @rtype: any scalar
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        lhs = self.parseFactor()
+        tt = self.token[0]
+        while tt in [STAR, SLASH, MOD]:
+            self.match(tt)
+            rhs = self.parseFactor()
+            lhs = Expression(tt, lhs, rhs)
+            tt = self.token[0]
+        return lhs
+
+    def parseFactor(self):
+        """
+        Parse a factor in an multiplicative expression (a * b, a / b, a % b)
+
+        @return: the parsed factor
+        @rtype: any scalar
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        tt = self.token[0]
+        if tt in [NUMBER, WORD, STRING, TRUE, FALSE, NONE]:
+            rv = self.token[1]
+            if tt != WORD:
+                rv = eval(rv)
+            self.match(tt)
+        elif tt == LPAREN:
+            self.match(LPAREN)
+            rv = self.parseScalar()
+            self.match(RPAREN)
+        elif tt == DOLLAR:
+            self.match(DOLLAR)
+            rv = self.parseReference(DOLLAR)
+        elif tt == BACKTICK:
+            self.match(BACKTICK)
+            rv = self.parseReference(BACKTICK)
+            self.match(BACKTICK)
+        elif tt == MINUS:
+            self.match(MINUS)
+            rv = -self.parseScalar()
+        else:
+            raise ConfigFormatError("%s: unexpected input: %r" %
+               (self.location(), self.token[1]))
+        return rv
+
+    def parseReference(self, type):
+        """
+        Parse a reference.
+
+        @return: the parsed reference
+        @rtype: L{Reference}
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        word = self.match(WORD)
+        rv = Reference(self.config, type, word[1])
+        while self.token[0] in [DOT, LBRACK2]:
+            self.parseSuffix(rv)
+        return rv
+
+    def parseSuffix(self, ref):
+        """
+        Parse a reference suffix.
+
+        @param ref: The reference of which this suffix is a part.
+        @type ref: L{Reference}.
+        @raise ConfigFormatError: if a syntax error is found.
+        """
+        tt = self.token[0]
+        if tt == DOT:
+            self.match(DOT)
+            word = self.match(WORD)
+            ref.addElement(DOT, word[1])
+        else:
+            self.match(LBRACK2)
+            tt, tv = self.token
+            if tt not in [NUMBER, STRING]:
+                raise ConfigFormatError("%s: expected number or string, found %r" % (self.location(), tv))
+            self.token = self.getToken()
+            tv = eval(tv)
+            self.match(RBRACK)
+            ref.addElement(LBRACK, tv)
+
+def defaultMergeResolve(map1, map2, key):
+    """
+    A default resolver for merge conflicts. 
+    Returns a string indicating what action to take to resolve the conflict.
+
+    @param map1: The map being merged into.
+    @type map1: L{Mapping}.
+    @param map2: The map being used as the merge operand.
+    @type map2: L{Mapping}.
+    @param key: The key in map2 (which also exists in map1).
+    @type key: str
+    @return: One of "merge", "append", "mismatch" or "overwrite"
+             indicating what action should be taken. This should
+             be appropriate to the objects being merged - e.g.
+             there is no point returning "merge" if the two objects
+             are instances of L{Sequence}.
+    @rtype: str
+    """
+    obj1 = map1[key]
+    obj2 = map2[key]
+    if isinstance(obj1, Mapping) and isinstance(obj2, Mapping):
+        rv = "merge"
+    elif isinstance(obj1, Sequence) and isinstance(obj2, Sequence):
+        rv = "append"
+    else:
+        rv = "mismatch"
+    return rv
+
+def overwriteMergeResolve(map1, map2, key):
+    """
+    An overwriting resolver for merge conflicts. Calls L{defaultMergeResolve},
+    but where a "mismatch" is detected, returns "overwrite" instead.
+
+    @param map1: The map being merged into.
+    @type map1: L{Mapping}.
+    @param map2: The map being used as the merge operand.
+    @type map2: L{Mapping}.
+    @param key: The key in map2 (which also exists in map1).
+    @type key: str
+    """
+    rv = defaultMergeResolve(map1, map2, key)
+    if rv == "mismatch":
+        rv = "overwrite"
+    return rv
+
+def deepCopyMapping(inMapping):
+    res = Mapping()
+    for element in inMapping:
+        res[element] = inMapping[element]
+    return res
+
+class ConfigMerger(object):
+    """
+    This class is used for merging two configurations. If a key exists in the
+    merge operand but not the merge target, then the entry is copied from the
+    merge operand to the merge target. If a key exists in both configurations,
+    then a resolver (a callable) is called to decide how to handle the
+    conflict.
+    """
+
+    def __init__(self, resolver=defaultMergeResolve):
+        """
+        Initialise an instance.
+
+        @param resolver:
+        @type resolver: A callable which takes the argument list
+        (map1, map2, key) where map1 is the mapping being merged into,
+        map2 is the merge operand and key is the clashing key. The callable
+        should return a string indicating how the conflict should be resolved.
+        For possible return values, see L{defaultMergeResolve}. The default
+        value preserves the old behaviour
+        """
+        self.resolver = resolver
+
+    def merge(self, merged, mergee):
+        """
+        Merge two configurations. The second configuration is unchanged,
+        and the first is changed to reflect the results of the merge.
+
+        @param merged: The configuration to merge into.
+        @type merged: L{Config}.
+        @param mergee: The configuration to merge.
+        @type mergee: L{Config}.
+        """
+        self.mergeMapping(merged, mergee)
+
+    def overwriteKeys(self, map1, seq2):
+        """
+        Renint variables. The second mapping is unchanged,
+        and the first is changed depending the keys of the second mapping.
+        @param map1: The mapping to reinit keys into.
+        @type map1: L{Mapping}.
+        @param map2: The mapping container reinit information.
+        @type map2: L{Mapping}.
+        """
+
+        overwrite_list = object.__getattribute__(seq2, 'data')
+        for overwrite_instruction in overwrite_list:
+            object.__setattr__(overwrite_instruction, 'parent', map1)
+            if "__condition__" in overwrite_instruction.keys():
+                overwrite_condition = overwrite_instruction["__condition__"]
+                if eval(overwrite_condition, globals(), map1):
+                    for key in overwrite_instruction.keys():
+                        if key == "__condition__":
+                            continue
+                        try:
+                            exec( 'map1.' + key + " = " + repr(overwrite_instruction[key]))
+                        except:
+                            exec('map1.' + key + " = " + str(overwrite_instruction[key]))
+            else:
+                for key in overwrite_instruction.keys():
+                    try:
+                        exec('map1.' + key + " = " + repr(overwrite_instruction[key]))
+                    except:
+                        exec('map1.' + key + " = " + str(overwrite_instruction[key]))
+
+    def mergeMapping(self, map1, map2):
+        """
+        Merge two mappings recursively. The second mapping is unchanged,
+        and the first is changed to reflect the results of the merge.
+
+        @param map1: The mapping to merge into.
+        @type map1: L{Mapping}.
+        @param map2: The mapping to merge.
+        @type map2: L{Mapping}.
+        """
+        keys = map1.keys()
+        global __resolveOverwrite__
+        for key in map2.keys():
+            if __resolveOverwrite__ and key == "__overwrite__":
+                self.overwriteKeys(map1,map2[key])
+
+            elif key not in keys:
+                map1[key] = map2[key]
+                if isinstance(map1[key], Container) :
+                    object.__setattr__(map1[key], 'parent', map1)
+            else:
+                obj1 = map1[key]
+                obj2 = map2[key]
+                decision = self.resolver(map1, map2, key)
+                if decision == "merge":
+                    self.mergeMapping(obj1, obj2)
+                elif decision == "append":
+                    self.mergeSequence(obj1, obj2)
+                elif decision == "overwrite":
+                    map1[key] = obj2
+                    if isinstance(map1[key], Container):
+                        object.__setattr__(map1[key], 'parent', map1)
+                elif decision == "mismatch":
+                    self.handleMismatch(obj1, obj2)
+                else:
+                    msg = "unable to merge: don't know how to implement %r"
+                    raise ValueError(msg % decision)
+
+    def mergeSequence(self, seq1, seq2):
+        """
+        Merge two sequences. The second sequence is unchanged,
+        and the first is changed to have the elements of the second
+        appended to it.
+
+        @param seq1: The sequence to merge into.
+        @type seq1: L{Sequence}.
+        @param seq2: The sequence to merge.
+        @type seq2: L{Sequence}.
+        """
+        data1 = object.__getattribute__(seq1, 'data')
+        data2 = object.__getattribute__(seq2, 'data')
+        for obj in data2:
+            data1.append(obj)
+        comment1 = object.__getattribute__(seq1, 'comments')
+        comment2 = object.__getattribute__(seq2, 'comments')
+        for obj in comment2:
+            comment1.append(obj)
+
+    def handleMismatch(self, obj1, obj2):
+        """
+        Handle a mismatch between two objects.
+
+        @param obj1: The object to merge into.
+        @type obj1: any
+        @param obj2: The object to merge.
+        @type obj2: any
+        """
+        raise ConfigError("unable to merge %r with %r" % (obj1, obj2))
+
+class ConfigList(list):
+    """
+    This class implements an ordered list of configurations and allows you
+    to try getting the configuration from each entry in turn, returning
+    the first successfully obtained value.
+    """
+
+    def getByPath(self, path):
+        """
+        Obtain a value from the first configuration in the list which defines
+        it.
+
+        @param path: The path of the value to retrieve.
+        @type path: str
+        @return: The value from the earliest configuration in the list which
+        defines it.
+        @rtype: any
+        @raise ConfigError: If no configuration in the list has an entry with
+        the specified path.
+        """
+        found = False
+        rv = None
+        for entry in self:
+            try:
+                rv = entry.getByPath(path)
+                found = True
+                break
+            except ConfigError:
+                pass
+        if not found:
+            raise ConfigError("unable to resolve %r" % path)
+        return rv
index f34c47a84988ceb30607867d2e8d020f4e0c2817..f32435969f3332aeb165b56a448ba41290f7fd5f 100644 (file)
@@ -31,7 +31,7 @@ try: # For python2
 except:
     pass
 
-import src.ElementTree as etree
+import src.ElementTree as ETREE
 import src.utilsSat as UTS
 
 class XmlLogFile(object):
@@ -51,7 +51,7 @@ class XmlLogFile(object):
         self.logFile = filePath
         UTS.ensure_path_exists(os.path.dirname(filePath))
         # Initialize the field that contain the xml in memory
-        self.xmlroot = etree.Element(rootname, attrib = attrib)
+        self.xmlroot = ETREE.Element(rootname, attrib = attrib)
     
     def write_tree(self, stylesheet=None, file_path = None):
         """Write the xml tree in the log file path. Add the stylesheet if asked.
@@ -67,7 +67,7 @@ class XmlLogFile(object):
             if stylesheet:
                 f.write("<?xml-stylesheet type='text/xsl' href='%s'?>\n" % 
                         stylesheet)    
-            f.write(etree.tostring(self.xmlroot, encoding='utf-8'))
+            f.write(ETREE.tostring(self.xmlroot, encoding='utf-8'))
             f.close()
         except IOError:
             pass  
@@ -80,7 +80,7 @@ class XmlLogFile(object):
         :param attrib: (dict)
           The dictionary containing the attribute of the new node
         """
-        n = etree.Element(node_name, attrib=attrib)
+        n = ETREE.Element(node_name, attrib=attrib)
         n.text = text
         self.xmlroot.append(n)
         return n
@@ -115,7 +115,7 @@ class ReadXmlFile(object):
         :param filePath: (str) The xml file to be read
         """
         self.filePath = filePath
-        etree_inst = etree.parse(filePath)
+        etree_inst = ETREE.parse(filePath)
         self.xmlroot = etree_inst.parse(filePath)
 
     def getRootAttrib(self):
@@ -161,14 +161,14 @@ class ReadXmlFile(object):
 def add_simple_node(root_node, node_name, text=None, attrib={}):
     """Add a node with some attibutes and text to the root node.
 
-    :param root_node: (etree.Element) 
+    :param root_node: (ETREE.Element) 
       the Etree element where to add the new node    
     :param node_name: (str) the name of the node to add
     :param text: (str) the text of the node
     :param attrib: (dict) 
       the dictionary containing the attribute(s) of the new node
     """
-    n = etree.Element(node_name, attrib=attrib)
+    n = ETREE.Element(node_name, attrib=attrib)
     n.text = text
     root_node.append(n)
     return n
@@ -176,7 +176,7 @@ def add_simple_node(root_node, node_name, text=None, attrib={}):
 def append_node_attrib(root_node, attrib):
     """Append a new attributes to the node that has node_name as name
     
-    :param root_node: (etree.Element)
+    :param root_node: (ETREE.Element)
       the Etree element where to append the new attibutes
     :param attrib: (dict) The attrib to append
     """
@@ -188,12 +188,12 @@ def find_node_by_attrib(xmlroot, name_node, key, value):
     and that has in its attributes {key : value}. 
     Return the node
     
-    :param xmlroot: (etree.Element) 
+    :param xmlroot: (ETREE.Element) 
       the Etree element where to search
     :param name_node: (str) the name of node to search
     :param key: (str) the key to search
     :param value: (str) the value to search
-    :return: (etree.Element) the found node
+    :return: (ETREE.Element) the found node
     """
     l_nodes =  xmlroot.findall(name_node)
     for node in l_nodes:
@@ -208,7 +208,7 @@ def write_report(filename, xmlroot, stylesheet):
     """Writes a report file from a XML tree.
     
     :param filename: (str) The path to the file to create
-    :param xmlroot: (etree.Element) the Etree element to write to the file
+    :param xmlroot: (ETREE.Element) the Etree element to write to the file
     :param stylesheet: (str) The stylesheet to add to the begin of the file
     """
     if not os.path.exists(os.path.dirname(filename)):
@@ -218,6 +218,6 @@ def write_report(filename, xmlroot, stylesheet):
     f.write("<?xml version='1.0' encoding='utf-8'?>\n")
     if len(stylesheet) > 0:
         f.write("<?xml-stylesheet type='text/xsl' href='%s'?>\n" % stylesheet)
-    f.write(etree.tostring(xmlroot, encoding='utf-8'))
+    f.write(ETREE.tostring(xmlroot, encoding='utf-8'))
     f.close()   
     
\ No newline at end of file
index 393be86a21de1652151f5c5dd366e0a61de772de..b022b059cf37b1bfd9af70dc6be0cd8ea704f79d 100644 (file)
@@ -421,7 +421,10 @@ class TestConfig(unittest.TestCase):
         self.assertEqual(str(cfg.mixed), "['VALIGN', [0, 0], [-1, -1], 'TOP']")\r
 \r
     def testJSON(self):\r
-        data = StringIO('dummy: ' + open('styles.json', 'r').read())\r
+        import os\r
+        curDir, tmp = os.path.split(__file__)\r
+        fileJson = os.path.join(curDir, 'styles.json')\r
+        data = StringIO('dummy: ' + open(fileJson, 'r').read())\r
         self.cfg.load(data)\r
 \r
 def init_logging():\r
index 13544742920e200dc9fd512d5d94e1791131799e..d011e96410be9c84bebacd131cc4b5cdfe890dce 100755 (executable)
@@ -99,7 +99,7 @@ class TestCase(unittest.TestCase):
     inStream = DBG.InStream(_EXAMPLES[2])
     cfg = PYF.Config(inStream)
     res = DBG.getStrConfigDbg(cfg)
-    DBG.write("test_020 cfg dbg", res, True)
+    DBG.write("test_020 cfg dbg", res)
     ress = res.split("\n")
     self.assertTrue(".aa" in ress[0])
     self.assertTrue(": '111'" in ress[0])
index 11bc8a72bab20909e40f9c46571d838ed47e4ad9..9b64de39ffdb75c39be4b9a75e61e0899078c1f8 100755 (executable)
@@ -148,7 +148,7 @@ Bienvenue, Yves
     self.assertEqual(cfg.bb, "Hervé")
     
   def test_045(self):
-    """TODO: make Hervé valid with pyconf.py as 0.3.9"""
+    # make Hervé valid with pyconf.py as 0.3.9
     inStream = DBG.InStream(_EXAMPLES[4])
     outStream = DBG.OutStream()
     cfg = PYF9.Config(inStream)
@@ -167,33 +167,13 @@ Bienvenue, Yves
     cfg = PYF.Config(inStream) # KO
     cfg.__save__(outStream) # sat renamed save() in __save__()
     res = outStream.value
-    DBG.write("test_100 cfg save", res, True)
-    DBG.write("test_100 cfg debug", cfg, True)
-    DBG.write("test_100 cfg debug", cfg.cc, True)
+    DBG.write("test_100 cfg save", res)
+    DBG.write("test_100 cfg debug", cfg)
+    DBG.write("test_100 cfg debug", cfg.cc)
     
     cc = cfg.cc
     # DBG.write("test_100 type cc[3]", dir(cc), True)
-    # self.ssave(cc)
-    DBG.write("test_100 cc", [cc.data[i] for i in range(len(cc))], True)
-    
-  def ssave(self,  obj): #, stream):
-      """
-      Save this instance to the specified stream.
-      @param stream: A stream to which the configuration is written.
-      @type stream: A write-only stream (file-like object).
-      @param indent: The indentation level for the output, > 0
-      @type indent: int
-      """
-      data = object.__getattribute__(obj, 'data')
-      for i in range(0, len(data)):
-          value = data[i]
-          print "[%i]" % i, type(value)
-          """if isinstance(value, PYF.Container):
-              print "Container [%i]" % i
-              #value.writeToStream(stream, indent, self)
-          else:
-              print "Other [%i]" % i
-              #self.writeValue(value, stream, indent)"""
+    DBG.write("test_100 cc", [cc.data[i] for i in range(len(cc))])
       
   def test_999(self):
     # one shot tearDown() for this TestCase
index 8db797b60570ab51eb5471774a65b121b0ba9c06..88994aed4141c3638b4136ccc97527cde7547f5a 100755 (executable)
@@ -223,18 +223,18 @@ class TestConfig(unittest.TestCase):
     def tearDown(self):
         del self.cfg
 
-    def testCreation(self):
+    def test_010(self): # Creation(self):
         self.assertEqual(0, len(self.cfg))  # should be empty
 
-    def testSimple(self):
+    def test_020(self): # Simple(self):
         self.cfg.load(makeStream("simple_1"))
         self.failUnless('message' in self.cfg)
         self.failIf('root' in self.cfg)
         self.failIf('stream' in self.cfg)
         self.failIf('load' in self.cfg)
-        self.failIf('save' in self.cfg)
+        self.failIf('__save__' in self.cfg)
 
-    def testValueOnly(self):
+    def test_030(self): # ValueOnly(self):
         self.assertRaises(ConfigError, self.cfg.load,
            makeStream("malformed_1"))
         self.assertRaises(ConfigError, self.cfg.load,
@@ -242,36 +242,36 @@ class TestConfig(unittest.TestCase):
         self.assertRaises(ConfigError, self.cfg.load,
            makeStream("malformed_3"))
 
-    def testBadBracket(self):
+    def test_040(self): # BadBracket(self):
         self.assertRaises(ConfigError, self.cfg.load,
            makeStream("malformed_4"))
 
-    def testDuplicate(self):
+    def test_050(self): # Duplicate(self):
         self.assertRaises(ConfigError, self.cfg.load,
            makeStream("malformed_5"))
 
-    def testGoodBracket(self):
+    def test_060(self): # GoodBracket(self):
         self.cfg.load(makeStream("wellformed_1"))
 
-    def testBoolean(self):
+    def test_070(self): # Boolean(self):
         self.cfg.load(makeStream("boolean_1"))
         self.assertEqual(True, self.cfg.another_test)
         self.assertEqual(False, self.cfg.test)
 
-    def testNotBoolean(self):
+    def test_080(self): # NotBoolean(self):
         self.cfg.load(makeStream("boolean_2"))
         self.assertEqual('true', self.cfg.another_test)
         self.assertEqual('false', self.cfg.test)
 
-    def testNone(self):
+    def test_090(self): # None(self):
         self.cfg.load(makeStream("none_1"))
         self.assertEqual(None, self.cfg.test)
 
-    def testNotNone(self):
+    def test_100(self): # NotNone(self):
         self.cfg.load(makeStream("none_2"))
         self.assertEqual('none', self.cfg.test)
 
-    def testNumber(self):
+    def test_110(self): # Number(self):
         self.cfg.load(makeStream("number_1"))
         self.assertEqual(1, self.cfg.root)
         self.assertEqual(1.7, self.cfg.stream)
@@ -281,30 +281,30 @@ class TestConfig(unittest.TestCase):
         self.assertAlmostEqual(2.0999999e-08, self.cfg.posexponent)
         self.assertAlmostEqual(2.0999999e08, self.cfg.exponent)
 
-    def testChange(self):
+    def test_120(self): # Change(self):
         self.cfg.load(makeStream("simple_1"))
         self.cfg.message = 'Goodbye, cruel world!'
         self.assertEqual('Goodbye, cruel world!', self.cfg.message)
 
-    def testSave(self):
+    def test_130(self): # Save(self):
         self.cfg.load(makeStream("simple_1"))
         self.cfg.message = 'Goodbye, cruel world!'
         out = OutStream()
-        self.cfg.save(out)
+        self.cfg.__save__(out)
         self.assertEqual("message : 'Goodbye, cruel world!'" + config.NEWLINE,
            out.value)
 
-    def testInclude(self):
+    def test_140(self): # Include(self):
         config.streamOpener = makeStream
         self.cfg = Config("include_1")
         config.streamOpener = config.defaultStreamOpener
         out = OutStream()
-        self.cfg.save(out)
+        self.cfg.__save__(out)
         s = "included :%s{%s  test : 123%s  another_test : 'abc'%s}%s" % (5 *
            (config.NEWLINE,))
         self.assertEqual(s, out.value)
 
-    def testExpression(self):
+    def test_150(self): # Expression(self):
         self.cfg.load(makeStream("expr_1"))
         self.assertEqual(15, self.cfg.derived1)
         self.assertEqual(5, self.cfg.derived2)
@@ -320,7 +320,7 @@ class TestConfig(unittest.TestCase):
            lambda x: x.derived11, self.cfg)
         self.assertEqual(15, self.cfg.derived12)
 
-    def testEval(self):
+    def test_160(self): # Eval(self):
         import sys, logging
         self.cfg.load(makeStream("eval_1"))
         self.assertEqual(sys.stderr, self.cfg.stderr)
@@ -336,7 +336,7 @@ class TestConfig(unittest.TestCase):
         self.assertEqual(logging.debug, self.cfg.debug)
         self.assertEqual(logging.DEBUG * 10, self.cfg.derived)
 
-    def testFunctions(self):
+    def test_170(self): # Functions(self):
         makePath = config.makePath
         isWord = config.isWord
         self.assertEqual('suffix', makePath('', 'suffix'))
@@ -350,7 +350,7 @@ class TestConfig(unittest.TestCase):
         self.failIf(isWord(self))
         self.failIf(isWord(''))
 
-    def testMerge(self):
+    def test_180(self): # Merge(self):
         cfg1 = Config()
         cfg1.load(makeStream("merge_1"))
         cfg2 = Config(makeStream("merge_2"))
@@ -390,7 +390,7 @@ class TestConfig(unittest.TestCase):
         self.assertEqual("[2, 4, 6]", str(cfg3.value3))
         self.assertEqual("[1, 3, 5, 2, 4, 6]", str(cfg3.value4))
 
-    def testList(self):
+    def test_190(self): # List(self):
         list = ConfigList()
         list.append(Config(makeStream("list_1")))
         list.append(Config(makeStream("list_2")))
@@ -400,7 +400,7 @@ class TestConfig(unittest.TestCase):
         self.assertEqual(5, list.getByPath('suite_value'))
         self.assertRaises(ConfigError, list.getByPath, 'nonexistent_value')
 
-    def testGet(self):
+    def test_200(self): # Get(self):
         cfg = self.cfg
         cfg.load(makeStream("get_1"))
         self.assertEqual(123, cfg.get('value1'))
@@ -416,13 +416,13 @@ class TestConfig(unittest.TestCase):
         self.failUnless(cfg.value5.get('value3'))
         self.failIf(cfg.value5.get('value4') is not None)
 
-    def testMultiline(self):
+    def test_210(self): # Multiline(self):
         cfg = self.cfg
         cfg.load(makeStream("multiline_1"))
         self.assertEqual("Value One\nValue Two\n", cfg.get('value1'))
         self.assertEqual("Value Three\nValue Four", cfg.get('value2'))
 
-    def testSequence(self):
+    def test_220(self): # Sequence(self):
         cfg = self.cfg
         strm = makeStream("sequence_1")
         cfg.load(strm)
@@ -430,8 +430,11 @@ class TestConfig(unittest.TestCase):
         self.assertEqual(str(cfg.nested), "[1, [2, 3], [4, [5, 6]]]")
         self.assertEqual(str(cfg.mixed), "['VALIGN', [0, 0], [-1, -1], 'TOP']")
 
-    def testJSON(self):
-        data = StringIO('dummy: ' + open('styles.json', 'r').read())
+    def test_230(self): # JSON(self):
+        import os
+        curDir, tmp = os.path.split(__file__)
+        fileJson = os.path.join(curDir, 'config_0_3_9', 'styles.json')
+        data = StringIO('dummy: ' + open(fileJson, 'r').read())
         self.cfg.load(data)
 
 def init_logging():
@@ -450,7 +453,7 @@ if __name__ == "__main__":
     sys.stderr.write("""
                      
 ###########################################################
-WARNING: this test obviously have 'FAILED  (errors=6)', 
+WARNING: this test obviously have 'FAILED  (errors=6)', with config 0.3.7
 TODO:    fix upgrading 0.3.9, (or not).
 ###########################################################
 """)