1 # Copyright (C) 2013-2014 CEA/DEN, EDF R&D, OPEN CASCADE
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # Lesser General Public License for more details.
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
24 from io import StringIO
28 logConfigParser = logging.getLogger(__name__)
30 ADD_TO_PREFIX = 'ADD_TO_'
31 UNSET_KEYWORD = 'UNSET'
34 # :TRICKY: So ugly solution...
35 class MultiOptSafeConfigParser(ConfigParser.SafeConfigParser):
37 ConfigParser.SafeConfigParser.__init__(self)
39 # copied from python 2.6.8 Lib.ConfigParser.py
40 # modified (see code comments) to handle duplicate keys
41 def _read(self, fp, fpname):
42 """Parse a sectioned setup file.
44 The sections in setup file contains a title line at the top,
45 indicated by a name in square brackets (`[]'), plus key/value
46 options lines, indicated by `name: value' format lines.
47 Continuations are represented by an embedded newline then
48 leading whitespace. Blank lines, lines beginning with a '#',
49 and just about everything else are ignored.
51 cursect = None # None, or a dictionary
54 e = None # None, or an exception
60 # comment or blank line?
61 if line.strip() == '' or line[0] in '#;':
63 if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
64 # no leading whitespace
67 if line[0].isspace() and cursect is not None and optname:
70 cursect[optname].append(value)
71 # a section header or option header?
73 # is it a section header?
74 mo = self.SECTCRE.match(line)
76 sectname = mo.group('header')
77 if sectname in self._sections:
78 cursect = self._sections[sectname]
79 elif sectname == ConfigParser.DEFAULTSECT:
80 cursect = self._defaults
82 cursect = self._dict()
83 cursect['__name__'] = sectname
84 self._sections[sectname] = cursect
85 # So sections can't start with a continuation line
87 # no section header in the file?
89 raise ConfigParser.MissingSectionHeaderError(fpname, lineno, line)
92 mo = self.OPTCRE.match(line)
94 optname, vi, optval = mo.group('option', 'vi', 'value')
95 optname = self.optionxform(optname.rstrip())
96 # This check is fine because the OPTCRE cannot
97 # match if it would set optval to None
98 if optval is not None:
99 if vi in ('=', ':') and ';' in optval:
100 # ';' is a comment delimiter only if it follows
101 # a spacing character
102 pos = optval.find(';')
103 if pos != -1 and optval[pos-1].isspace():
104 optval = optval[:pos]
105 optval = optval.strip()
107 splittedComments = optval.split('#')
108 optval = splittedComments[0].strip().strip("'").strip('"')
109 #if len(splittedComments) > 1:
110 # optval += " #" + " ".join(splittedComments[1:])
115 # REPLACE following line (original):
116 #cursect[optname] = [optval]
118 # Check if this optname already exists
119 if (optname in cursect) and (cursect[optname] is not None):
120 cursect[optname][0] += ','+optval
122 cursect[optname] = [optval]
125 # valueless option handling
126 cursect[optname] = optval
128 # a non-fatal parsing error occurred. set up the
129 # exception but keep going. the exception will be
130 # raised at the end of the file and will contain a
131 # list of all bogus lines
133 e = ConfigParser.ParsingError(fpname)
134 e.append(lineno, repr(line))
135 # if any parsing errors occurred, raise an exception
139 # join the multi-line values collected while reading
140 all_sections = [self._defaults]
141 all_sections.extend(self._sections.values())
142 for options in all_sections:
143 for name, val in options.items():
144 if isinstance(val, list):
145 options[name] = '\n'.join(val)
149 # Parse configuration file
150 # Input: filename, and a list of reserved keywords (environment variables)
151 # Output: a list of pairs (variable, value), and a dictionary associating a list of user-defined values to each reserved keywords
152 # Note: Does not support duplicate keys in a same section
153 def parseConfigFile(filename, reserved = []):
154 config = MultiOptSafeConfigParser()
155 config.optionxform = str # case sensitive
157 # :TODO: test file existence
161 config.read(filename)
162 except ConfigParser.MissingSectionHeaderError:
163 logConfigParser.error("No section found in file: %s"%(filename))
166 return __processConfigFile(config, reserved, filename)
169 def __processConfigFile(config, reserved = [], filename="UNKNOWN FILENAME"):
170 # :TODO: may detect duplicated variables in the same section (raise a warning)
171 # or even duplicate sections
175 # Get raw items for each section, and make some processing for environment variables management
176 reservedKeys = [ADD_TO_PREFIX+str(x) for x in reserved] # produce [ 'ADD_TO_reserved_1', 'ADD_TO_reserved_2', ..., ADD_TO_reserved_n ]
177 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:[] }
178 sections = config.sections()
179 for section in sections:
180 entries = config.items(section, raw=False) # use interpolation
181 if len(entries) == 0: # empty section
182 logConfigParser.warning("Empty section: %s in file: %s"%(section, filename))
184 for key,val in entries:
186 logConfigParser.error("Invalid use of reserved variable: %s in file: %s"%(key, filename))
187 elif key == UNSET_KEYWORD:
188 unsetVariables += val.replace(',', ' ').split()
190 expandedVal = os.path.expandvars(val) # expand environment variables
191 # Search for not expanded variables (i.e. non-existing environment variables)
192 pattern = re.compile('\${ ( [^}]* ) }', re.VERBOSE) # string enclosed in ${ and }
193 expandedVal = pattern.sub(r'', expandedVal) # remove matching patterns
195 if not "DLIM8VAR" in key: # special case: DISTENE licence key can contain double clons (::)
196 expandedVal = _trimColons(expandedVal)
198 if key in reservedKeys:
199 shortKey = key[len(ADD_TO_PREFIX):]
200 vals = expandedVal.split(',')
201 reservedValues[shortKey] += vals
202 # remove left&right spaces on each element
203 vals = [v.strip(' \t\n\r') for v in vals]
205 outputVariables.append((key, expandedVal))
208 pass # end for key,val
209 pass # end for section
211 # remove duplicate values
213 for (var, values) in outputVariables:
214 vals = values.split(',')
215 vals = list(set(vals))
216 outVars.append((var, ','.join(vals)))
218 return unsetVariables, outVars, reservedValues
221 def _trimColons(var):
223 # Remove leading and trailing colons (:)
224 pattern = re.compile('^:+ | :+$', re.VERBOSE)
225 v = pattern.sub(r'', v) # remove matching patterns
226 # Remove multiple colons
227 pattern = re.compile('::+', re.VERBOSE)
228 v = pattern.sub(r':', v) # remove matching patterns
232 # This class is used to parse .sh environment file
233 # It deals with specific treatments:
234 # - virtually add a section to configuration file
235 # - process shell keywords (if, then...)
236 class EnvFileConverter(object):
237 def __init__(self, fp, section_name, reserved = [], outputFile=None):
239 self.sechead = '[' + section_name + ']\n'
240 self.reserved = reserved
241 self.outputFile = outputFile
242 self.allParsedVariableNames=[]
243 # exclude line that begin with:
244 self.exclude = [ 'if', 'then', 'else', 'fi', '#', 'echo', 'exit' ]
245 self.exclude.append('$gconfTool') # QUICK FIX :TODO: provide method to extend this variable
246 # discard the following keywords if at the beginning of line:
247 self.discard = [ 'export' ]
248 # the following keywords imply a special processing if at the beginning of line:
249 self.special = [ 'unset' ]
254 if self.outputFile is not None:
255 self.outputFile.write(self.sechead)
260 line = self.fp.readline()
262 line = line.strip(' \t\n\r')
263 # line of interest? (not beginning by a keyword of self.exclude)
264 for k in self.exclude:
265 if line.startswith(k):
267 # look for substrinsg beginning with sharp charcter ('#')
268 line = re.sub(r'#.*$', r'', line)
269 # line to be pre-processed? (beginning by a keyword of self.special)
270 for k in self.special:
271 if k == "unset" and line.startswith(k):
273 line = line.strip(' \t\n\r')
274 line = UNSET_KEYWORD + ": " + line
275 # line to be pre-processed? (beginning by a keyword of self.discard)
276 for k in self.discard:
277 if line.startswith(k):
279 line = line.strip(' \t\n\r')
280 # process reserved keywords
281 for k in self.reserved:
282 if line.startswith(k) and "=" in line:
283 variable, value = line.split('=')
284 value = self._purgeValue(value, k)
285 line = ADD_TO_PREFIX + k + ": " + value
286 # Update list of variable names
287 # :TODO: define excludeBlock variable (similar to exclude) and provide method to extend it
288 if line.startswith("LOGNAME="):
290 if "cleandup()" in line:
291 print "WARNING: parseConfigFile.py: skip cleandup and look for '# PRODUCT environment'"
293 line = self.fp.readline()
294 if "# PRODUCT environment" in line:
295 print "WARNING: parseConfigFile.py: '# PRODUCT environment' found"
297 while "clean " in line[0:6]: #skip clean calls with ending ";" crash
298 line = self.fp.readline()
299 # Extract variable=value
302 variable, value = line.split('=')
303 except: #avoid error for complicated sh line xx=`...=...`, but warning
304 print "WARNING: parseConfigFile.py: line with multiples '=' character are hazardous: '"+line+"'"
305 variable, value = line.split('=',1)
308 # Self-extending variables that are not in reserved keywords
309 # Example: FOO=something:${FOO}
310 # In this case, remove the ${FOO} in value
311 if variable in value:
312 value = self._purgeValue(value, variable)
313 line = "%s=%s"%(variable,value)
315 self.allParsedVariableNames.append(variable)
322 # replace "${FOO}" and "$FOO" and ${FOO} and $FOO by %(FOO)s if FOO is
323 # defined in current file (i.e. it is not an external environment variable)
324 for k in self.allParsedVariableNames:
325 key = r'\$\{?'+k+'\}?'
326 pattern = re.compile(key, re.VERBOSE)
327 line = pattern.sub(r'%('+k+')s', line)
328 # Remove quotes (if line does not contain whitespaces)
330 variable, value = line.split('=', 1)
332 variable, value = line.split(':', 1)
333 if not ' ' in value.strip():
334 pattern = re.compile(r'\"', re.VERBOSE)
335 line = pattern.sub(r'', line)
338 # Replace `shell_command` by its result
340 obj = re.sub('`', r'', obj.group(0)) # remove quotes
342 res = subprocess.Popen(obj, stdout=subprocess.PIPE).communicate()[0]
343 res = res.strip(' \t\n\r') # trim whitespaces
346 line = re.sub('`[^`]+`', myrep, line)
348 if self.outputFile is not None:
349 self.outputFile.write(line+'\n')
352 def _purgeValue(self, value, name):
353 # Replace foo:${PATTERN}:bar or foo:$PATTERN:bar by foo:bar
354 key = r'\$\{?'+name+'\}?'
355 pattern = re.compile(key, re.VERBOSE)
356 value = pattern.sub(r'', value)
359 value = _trimColons(value)
364 # Convert .sh environment file to configuration file format
365 def convertEnvFileToConfigFile(envFilename, configFilename, reserved=[]):
366 logConfigParser.debug('convert env file %s to %s'%(envFilename, configFilename))
367 fileContents = open(envFilename, 'r').read()
369 pattern = re.compile('\n[\n]+', re.VERBOSE) # multiple '\n'
370 fileContents = pattern.sub(r'\n', fileContents) # replace by a single '\n'
372 finput = StringIO(unicode(fileContents))
373 foutput = open(configFilename, 'w')
375 config = MultiOptSafeConfigParser()
376 config.optionxform = str # case sensitive
377 config.readfp(EnvFileConverter(finput, 'SALOME Configuration', reserved, outputFile=foutput))
381 logConfigParser.info('Configuration file generated: %s'%configFilename)