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
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")
# 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"
# 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):
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:
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):
result = str(exc)
if result != RCO._OK_STATUS:
- result = _("ERROR: %s") % result
details.append([product, result])
if len(details) != 0:
# 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
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
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")
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
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
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)
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):
#from colorama import AnsiToWin32
from colorama import AnsiToWin32 # debug is os.name == 'nt' ?
+verbose = False
+
CLRM.init(wrap=False) # choose NO wrapping
"""
("<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"""
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)
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")]
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
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.
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'
outStream = DBG.OutStream()
DBG.saveConfigDbg(val, outStream, path=path)
res = outStream.value
- logger.info(res)
- return
+ return res
def get_config_children(config, args):
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
'''
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]
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))
# to set color prefix, problem with indent format
_ColorLevelname = {
- "DEBUG": "<blue>",
+ "DEBUG": "<green>",
"INFO": "<green>",
"WARNING": "<red>",
"ERROR": "<yellow>",
}
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)
@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):
"""
--- /dev/null
+#!/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
--- /dev/null
+# 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
except:
pass
-import src.ElementTree as etree
+import src.ElementTree as ETREE
import src.utilsSat as UTS
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.
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
: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
: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):
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
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
"""
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:
"""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)):
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
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
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])
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)
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
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,
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)
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)
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)
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'))
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"))
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")))
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'))
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)
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():
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).
###########################################################
""")