+import ConfigParser
+import os
+import logging
+import re
+from io import StringIO
+
+logging.basicConfig()
+logConfigParser = logging.getLogger(__name__)
+
+RESERVED_PREFIX = 'ADD_TO_'
+
+
+# :TRICKY: So ugly solution...
+class MultiOptSafeConfigParser(ConfigParser.SafeConfigParser):
+ def __init__(self):
+ ConfigParser.SafeConfigParser.__init__(self)
+
+ # copied from python 2.6.8 Lib.ConfigParser.py
+ # modified (see code comments) to handle duplicate keys
+ def _read(self, fp, name):
+ """Parse a sectioned setup file.
+
+ The sections in setup file contains a title line at the top,
+ indicated by a name in square brackets (`[]'), plus key/value
+ options lines, indicated by `name: value' format lines.
+ Continuations are represented by an embedded newline then
+ leading whitespace. Blank lines, lines beginning with a '#',
+ and just about everything else are ignored.
+ """
+ cursect = None # None, or a dictionary
+ optname = None
+ lineno = 0
+ e = None # None, or an exception
+ while True:
+ line = fp.readline()
+ if not line:
+ break
+ lineno = lineno + 1
+ # comment or blank line?
+ if line.strip() == '' or line[0] in '#;':
+ continue
+ if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
+ # no leading whitespace
+ continue
+ # continuation line?
+ if line[0].isspace() and cursect is not None and optname:
+ value = line.strip()
+ if value:
+ cursect[optname].append(value)
+ # a section header or option header?
+ else:
+ # is it a section header?
+ mo = self.SECTCRE.match(line)
+ if mo:
+ sectname = mo.group('header')
+ if sectname in self._sections:
+ cursect = self._sections[sectname]
+ elif sectname == ConfigParser.DEFAULTSECT:
+ cursect = self._defaults
+ else:
+ cursect = self._dict()
+ cursect['__name__'] = sectname
+ self._sections[sectname] = cursect
+ # So sections can't start with a continuation line
+ optname = None
+ # no section header in the file?
+ elif cursect is None:
+ raise MissingSectionHeaderError(fpname, lineno, line)
+ # an option line?
+ else:
+ mo = self.OPTCRE.match(line)
+ if mo:
+ optname, vi, optval = mo.group('option', 'vi', 'value')
+ optname = self.optionxform(optname.rstrip())
+ # This check is fine because the OPTCRE cannot
+ # match if it would set optval to None
+ if optval is not None:
+ if vi in ('=', ':') and ';' in optval:
+ # ';' is a comment delimiter only if it follows
+ # a spacing character
+ pos = optval.find(';')
+ if pos != -1 and optval[pos-1].isspace():
+ optval = optval[:pos]
+ optval = optval.strip()
+ # allow empty values
+ if optval == '""':
+ optval = ''
+ # REPLACE following line (original):
+ #cursect[optname] = [optval]
+ # BY THESE LINES:
+ # Check if this optname already exists
+ if (optname in cursect) and (cursect[optname] is not None):
+ cursect[optname][0] += ','+optval
+ else:
+ cursect[optname] = [optval]
+ # END OF SUBSITUTION
+ else:
+ # valueless option handling
+ cursect[optname] = optval
+ else:
+ # a non-fatal parsing error occurred. set up the
+ # exception but keep going. the exception will be
+ # raised at the end of the file and will contain a
+ # list of all bogus lines
+ if not e:
+ e = ParsingError(fpname)
+ e.append(lineno, repr(line))
+ # if any parsing errors occurred, raise an exception
+ if e:
+ raise e
+
+ # join the multi-line values collected while reading
+ all_sections = [self._defaults]
+ all_sections.extend(self._sections.values())
+ for options in all_sections:
+ for name, val in options.items():
+ if isinstance(val, list):
+ options[name] = '\n'.join(val)
+ #
+
+
+# Parse configuration file
+# Input: filename, and a list of reserved keywords (environment variables)
+# Output: a list of pairs (variable, value), and a dictionary associating a list of user-defined values to each reserved keywords
+# Note: Does not support duplicate keys in a same section
+def parseConfigFile(filename, reserved = []):
+ config = MultiOptSafeConfigParser()
+ config.optionxform = str # case sensitive
+
+ # :TODO: test file existence
+
+ # Read config file
+ try:
+ config.read(filename)
+ except ConfigParser.MissingSectionHeaderError:
+ logConfigParser.error("No section found in file: %s"%(filename))
+ return []
+
+ return _processConfigFile(config, reserved)
+#
+
+def _processConfigFile(config, reserved = []):
+ # :TODO: may detect duplicated variables in the same section (raise a warning)
+ # or even duplicate sections
+
+ outputVariables = []
+ # Get raw items for each section, and make some processing for environment variables management
+ reservedKeys = [RESERVED_PREFIX+str(x) for x in reserved] # produce [ 'ADD_TO_reserved_1', 'ADD_TO_reserved_2', ..., ADD_TO_reserved_n ]
+ reservedValues = dict([str(i),[]] for i in reserved) # create a dictionary in which keys are the 'ADD_TO_reserved_i' and associated values are empty lists: { 'reserved_1':[], 'reserved_2':[], ..., reserved_n:[] }
+ sections = config.sections()
+ for section in sections:
+ entries = config.items(section, raw=False) # use interpolation
+ if len(entries) == 0: # empty section
+ logConfigParser.warning("Empty section: %s in file: %s"%(section, filename))
+ pass
+ for key,val in entries:
+ if key in reserved:
+ logConfigParser.error("Invalid use of reserved variable: %s in file: %s"%(key, filename))
+ else:
+ expandedVal = os.path.expandvars(val) # expand environment variables
+ # Search for not expanded variables (i.e. non-existing environment variables)
+ pattern = re.compile('\${ ( [^}]* ) }', re.VERBOSE) # string enclosed in ${ and }
+ expandedVal = pattern.sub(r'', expandedVal) # remove matching patterns
+ # Trim colons
+ expandedVal = _trimColons(expandedVal)
+
+ if key in reservedKeys:
+ shortKey = key[len(RESERVED_PREFIX):]
+ vals = expandedVal.split(',')
+ reservedValues[shortKey] += vals
+ # remove left&right spaces on each element
+ vals = [v.strip(' \t\n\r') for v in vals]
+ else:
+ outputVariables.append((key, expandedVal))
+ pass
+ pass # end if key
+ pass # end for key,val
+ pass # end for section
+
+ return outputVariables, reservedValues
+#
+
+def _trimColons(var):
+ v = var
+ # Remove leading and trailing colons (:)
+ pattern = re.compile('^:+ | :+$', re.VERBOSE)
+ v = pattern.sub(r'', v) # remove matching patterns
+ # Remove multiple colons
+ pattern = re.compile('::+', re.VERBOSE)
+ v = pattern.sub(r':', v) # remove matching patterns
+ return v
+#
+
+# This class is used to parse .sh environment file
+# It deals with specific treatments:
+# - virtually add a section to configuration file
+# - process shell keywords (if, then...)
+class EnvFileConverter(object):
+ def __init__(self, fp, section_name, reserved = [], outputFile=None):
+ self.fp = fp
+ self.sechead = '[' + section_name + ']\n'
+ self.reserved = reserved
+ self.outputFile = outputFile
+ self.allParsedVariableNames=[]
+ # exclude line that begin with:
+ self.exclude = [ 'if', 'then', 'fi', '#' ]
+ # discard the following keywords if at the beginning of line:
+ self.discard = [ 'export' ]
+
+ def readline(self):
+ if self.sechead:
+ try:
+ if self.outputFile is not None:
+ self.outputFile.write(self.sechead)
+ return self.sechead
+ finally:
+ self.sechead = None
+ else:
+ line = self.fp.readline()
+ # trim whitespaces
+ line = line.strip(' \t\n\r')
+ # line of interest? (not beginning by a keyword of self.exclude)
+ for k in self.exclude:
+ if line.startswith(k):
+ return '\n'
+ # look for substrinsg beginning with sharp charcter ('#')
+ line = re.sub(r'#.*$', r'', line)
+ # line to be pre-processed? (beginning by a keyword of self.discard)
+ for k in self.discard:
+ if line.startswith(k):
+ line = line[len(k):]
+ line = line.strip(' \t\n\r')
+ # process reserved keywords
+ for k in self.reserved:
+ if line.startswith(k) and "=" in line:
+ variable, value = line.split('=')
+ value = self._purgeValue(value, k)
+ line = RESERVED_PREFIX + k + ": " + value
+ # Update list of variable names
+ if "=" in line:
+ variable, value = line.split('=')
+ self.allParsedVariableNames.append(variable)
+ # Self-extending variables that are not in reserved keywords
+ # Example: FOO=something:${FOO}
+ # :TODO:
+ #
+ # replace "${FOO}" and "$FOO" and ${FOO} and $FOO by %(FOO)s if FOO is
+ # defined in current file (i.e. it is not an external environment variable)
+ for k in self.allParsedVariableNames:
+ key = r'\$\{?'+k+'\}?'
+ pattern = re.compile(key, re.VERBOSE)
+ line = pattern.sub(r'%('+k+')s', line)
+ # Remove quotes
+ pattern = re.compile(r'\"', re.VERBOSE)
+ line = pattern.sub(r'', line)
+ #
+ # Replace `shell_command` by its result
+ def myrep(obj):
+ obj = re.sub('`', r'', obj.group(0)) # remove quotes
+ import subprocess
+ res = subprocess.Popen([obj], stdout=subprocess.PIPE).communicate()[0]
+ res = res.strip(' \t\n\r') # trim whitespaces
+ return res
+ #
+ line = re.sub('`[^`]+`', myrep, line)
+ #
+ if self.outputFile is not None:
+ self.outputFile.write(line+'\n')
+ return line
+
+ def _purgeValue(self, value, name):
+ # Replace foo:${PATTERN}:bar or foo:$PATTERN:bar by foo:bar
+ key = r'\$\{?'+name+'\}?'
+ pattern = re.compile(key, re.VERBOSE)
+ value = pattern.sub(r'', value)
+
+ # trim colons
+ value = _trimColons(value)
+
+ return value
+ #
+
+# Convert .sh environment file to configuration file format
+def convertEnvFileToConfigFile(envFilename, configFilename):
+ #reserved=['PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH']
+ reserved=['PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'R_LIBS', 'PV_PLUGIN_PATH']
+ fileContents = open(envFilename, 'r').read()
+
+ pattern = re.compile('\n[\n]+', re.VERBOSE) # multiple '\n'
+ fileContents = pattern.sub(r'\n', fileContents) # replace by a single '\n'
+
+ finput = StringIO(unicode(fileContents))
+ foutput = open(configFilename, 'w')
+
+ config = MultiOptSafeConfigParser()
+ config.optionxform = str # case sensitive
+ config.readfp(EnvFileConverter(finput, 'SALOME Configuration', reserved, outputFile=foutput))
+ foutput.close()
+
+ logConfigParser.info('Configuration file generated: %s'%configFilename)
+#