Salome HOME
Windows fix
[modules/kernel.git] / bin / parseConfigFile.py
1 import ConfigParser
2 import os
3 import logging
4 import re
5 from io import StringIO
6 import subprocess
7
8 logging.basicConfig()
9 logConfigParser = logging.getLogger(__name__)
10
11 ADD_TO_PREFIX = 'ADD_TO_'
12 UNSET_KEYWORD = 'UNSET'
13
14
15 # :TRICKY: So ugly solution...
16 class MultiOptSafeConfigParser(ConfigParser.SafeConfigParser):
17   def __init__(self):
18     ConfigParser.SafeConfigParser.__init__(self)
19
20   # copied from python 2.6.8 Lib.ConfigParser.py
21   # modified (see code comments) to handle duplicate keys
22   def _read(self, fp, fpname):
23     """Parse a sectioned setup file.
24
25     The sections in setup file contains a title line at the top,
26     indicated by a name in square brackets (`[]'), plus key/value
27     options lines, indicated by `name: value' format lines.
28     Continuations are represented by an embedded newline then
29     leading whitespace.  Blank lines, lines beginning with a '#',
30     and just about everything else are ignored.
31     """
32     cursect = None                        # None, or a dictionary
33     optname = None
34     lineno = 0
35     e = None                              # None, or an exception
36     while True:
37       line = fp.readline()
38       if not line:
39         break
40       lineno = lineno + 1
41       # comment or blank line?
42       if line.strip() == '' or line[0] in '#;':
43         continue
44       if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
45         # no leading whitespace
46         continue
47       # continuation line?
48       if line[0].isspace() and cursect is not None and optname:
49         value = line.strip()
50         if value:
51           cursect[optname].append(value)
52       # a section header or option header?
53       else:
54         # is it a section header?
55         mo = self.SECTCRE.match(line)
56         if mo:
57           sectname = mo.group('header')
58           if sectname in self._sections:
59             cursect = self._sections[sectname]
60           elif sectname == ConfigParser.DEFAULTSECT:
61             cursect = self._defaults
62           else:
63             cursect = self._dict()
64             cursect['__name__'] = sectname
65             self._sections[sectname] = cursect
66           # So sections can't start with a continuation line
67           optname = None
68         # no section header in the file?
69         elif cursect is None:
70           raise MissingSectionHeaderError(fpname, lineno, line)
71         # an option line?
72         else:
73           mo = self.OPTCRE.match(line)
74           if mo:
75             optname, vi, optval = mo.group('option', 'vi', 'value')
76             optname = self.optionxform(optname.rstrip())
77             # This check is fine because the OPTCRE cannot
78             # match if it would set optval to None
79             if optval is not None:
80               if vi in ('=', ':') and ';' in optval:
81                 # ';' is a comment delimiter only if it follows
82                 # a spacing character
83                 pos = optval.find(';')
84                 if pos != -1 and optval[pos-1].isspace():
85                   optval = optval[:pos]
86               optval = optval.strip()
87               # allow empty values
88               if optval == '""':
89                 optval = ''
90               # REPLACE following line (original):
91               #cursect[optname] = [optval]
92               # BY THESE LINES:
93               # Check if this optname already exists
94               if (optname in cursect) and (cursect[optname] is not None):
95                 cursect[optname][0] += ','+optval
96               else:
97                 cursect[optname] = [optval]
98               # END OF SUBSITUTION
99             else:
100               # valueless option handling
101               cursect[optname] = optval
102           else:
103             # a non-fatal parsing error occurred.  set up the
104             # exception but keep going. the exception will be
105             # raised at the end of the file and will contain a
106             # list of all bogus lines
107             if not e:
108               e = ConfigParser.ParsingError(fpname)
109             e.append(lineno, repr(line))
110     # if any parsing errors occurred, raise an exception
111     if e:
112       raise e
113
114     # join the multi-line values collected while reading
115     all_sections = [self._defaults]
116     all_sections.extend(self._sections.values())
117     for options in all_sections:
118       for name, val in options.items():
119         if isinstance(val, list):
120           options[name] = '\n'.join(val)
121   #
122
123
124 # Parse configuration file
125 # Input: filename, and a list of reserved keywords (environment variables)
126 # Output: a list of pairs (variable, value), and a dictionary associating a list of user-defined values to each reserved keywords
127 # Note: Does not support duplicate keys in a same section
128 def parseConfigFile(filename, reserved = []):
129   config = MultiOptSafeConfigParser()
130   config.optionxform = str # case sensitive
131
132   # :TODO: test file existence
133
134   # Read config file
135   try:
136     config.read(filename)
137   except ConfigParser.MissingSectionHeaderError:
138     logConfigParser.error("No section found in file: %s"%(filename))
139     return []
140
141   return __processConfigFile(config, reserved, filename)
142 #
143
144 def __processConfigFile(config, reserved = [], filename="UNKNOWN FILENAME"):
145   # :TODO: may detect duplicated variables in the same section (raise a warning)
146   #        or even duplicate sections
147
148   unsetVariables = []
149   outputVariables = []
150   # Get raw items for each section, and make some processing for environment variables management
151   reservedKeys = [ADD_TO_PREFIX+str(x) for x in reserved] # produce [ 'ADD_TO_reserved_1', 'ADD_TO_reserved_2', ..., ADD_TO_reserved_n ]
152   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:[] }
153   sections = config.sections()
154   for section in sections:
155     entries = config.items(section, raw=False) # use interpolation
156     if len(entries) == 0: # empty section
157       logConfigParser.warning("Empty section: %s in file: %s"%(section, filename))
158       pass
159     for key,val in entries:
160       if key in reserved:
161         logConfigParser.error("Invalid use of reserved variable: %s in file: %s"%(key, filename))
162       elif key == UNSET_KEYWORD:
163         unsetVariables += val.replace(',', ' ').split()
164       else:
165         expandedVal = os.path.expandvars(val) # expand environment variables
166         # Search for not expanded variables (i.e. non-existing environment variables)
167         pattern = re.compile('\${ ( [^}]* ) }', re.VERBOSE) # string enclosed in ${ and }
168         expandedVal = pattern.sub(r'', expandedVal) # remove matching patterns
169         # Trim colons
170         if not "DLIM8VAR" in key: # special case: DISTENE licence key can contain double clons (::)
171           expandedVal = _trimColons(expandedVal)
172
173         if key in reservedKeys:
174           shortKey = key[len(ADD_TO_PREFIX):]
175           vals = expandedVal.split(',')
176           reservedValues[shortKey] += vals
177           # remove left&right spaces on each element
178           vals = [v.strip(' \t\n\r') for v in vals]
179         else:
180           outputVariables.append((key, expandedVal))
181           pass
182         pass # end if key
183       pass # end for key,val
184     pass # end for section
185
186   return unsetVariables, outputVariables, reservedValues
187 #
188
189 def _trimColons(var):
190   v = var
191   # Remove leading and trailing colons (:)
192   pattern = re.compile('^:+ | :+$', re.VERBOSE)
193   v = pattern.sub(r'', v) # remove matching patterns
194   # Remove multiple colons
195   pattern = re.compile('::+', re.VERBOSE)
196   v = pattern.sub(r':', v) # remove matching patterns
197   return v
198 #
199
200 # This class is used to parse .sh environment file
201 # It deals with specific treatments:
202 #    - virtually add a section to configuration file
203 #    - process shell keywords (if, then...)
204 class EnvFileConverter(object):
205   def __init__(self, fp, section_name, reserved = [], outputFile=None):
206     self.fp = fp
207     self.sechead = '[' + section_name + ']\n'
208     self.reserved = reserved
209     self.outputFile = outputFile
210     self.allParsedVariableNames=[]
211     # exclude line that begin with:
212     self.exclude = [ 'if', 'then', 'else', 'fi', '#', 'echo' ]
213     self.exclude.append('$gconfTool') # QUICK FIX :TODO: provide method to extend this variable
214     # discard the following keywords if at the beginning of line:
215     self.discard = [ 'export' ]
216     # the following keywords imply a special processing if at the beginning of line:
217     self.special = [ 'unset' ]
218
219   def readline(self):
220     if self.sechead:
221       try:
222         if self.outputFile is not None:
223           self.outputFile.write(self.sechead)
224         return self.sechead
225       finally:
226         self.sechead = None
227     else:
228       line = self.fp.readline()
229       # trim  whitespaces
230       line = line.strip(' \t\n\r')
231       # line of interest? (not beginning by a keyword of self.exclude)
232       for k in self.exclude:
233         if line.startswith(k):
234           return '\n'
235       # look for substrinsg beginning with sharp charcter ('#')
236       line = re.sub(r'#.*$', r'', line)
237       # line to be pre-processed? (beginning by a keyword of self.special)
238       for k in self.special:
239         if k == "unset" and line.startswith(k):
240           line = line[len(k):]
241           line = line.strip(' \t\n\r')
242           line = UNSET_KEYWORD + ": " + line
243       # line to be pre-processed? (beginning by a keyword of self.discard)
244       for k in self.discard:
245         if line.startswith(k):
246           line = line[len(k):]
247           line = line.strip(' \t\n\r')
248       # process reserved keywords
249       for k in self.reserved:
250         if line.startswith(k) and "=" in line:
251           variable, value = line.split('=')
252           value = self._purgeValue(value, k)
253           line = ADD_TO_PREFIX + k + ": " + value
254       # Update list of variable names
255       # :TODO: define excludeBlock variable (similar to exclude) and provide method to extend it
256       if "cleandup()" in line:
257         print "WARNING: parseConfigFile.py: skip cleandup and look for '# PRODUCT environment'"
258         while True:
259           line = self.fp.readline()
260           if "# PRODUCT environment" in line:
261             print "WARNING: parseConfigFile.py: '# PRODUCT environment' found"
262             break
263       while "clean " in line[0:6]: #skip clean calls with ending ";" crash
264         line = self.fp.readline()
265       # Extract variable=value
266       if "=" in line:
267         try:
268           variable, value = line.split('=')
269         except: #avoid error for complicated sh line xx=`...=...`, but warning
270           print "WARNING: parseConfigFile.py: line with multiples '=' character are hazardous: '"+line+"'"
271           variable, value = line.split('=',1)
272           pass
273
274         # Self-extending variables that are not in reserved keywords
275         # Example: FOO=something:${FOO}
276         # In this case, remove the ${FOO} in value
277         if variable in value:
278           value = self._purgeValue(value, variable)
279           line = "%s=%s"%(variable,value)
280
281         self.allParsedVariableNames.append(variable)
282       # End of extraction
283
284       if not line:
285         return line
286
287       #
288       # replace "${FOO}" and "$FOO" and ${FOO} and $FOO by %(FOO)s if FOO is
289       # defined in current file (i.e. it is not an external environment variable)
290       for k in self.allParsedVariableNames:
291         key = r'\$\{?'+k+'\}?'
292         pattern = re.compile(key, re.VERBOSE)
293         line = pattern.sub(r'%('+k+')s', line)
294         # Remove quotes (if line does not contain whitespaces)
295         try:
296           variable, value = line.split('=', 1)
297         except ValueError:
298           variable, value = line.split(':', 1)
299         if not ' ' in value.strip():
300           pattern = re.compile(r'\"', re.VERBOSE)
301           line = pattern.sub(r'', line)
302       #
303
304       # Replace `shell_command` by its result
305       def myrep(obj):
306         obj = re.sub('`', r'', obj.group(0)) # remove quotes
307         obj = obj.split()
308         res = subprocess.Popen(obj, stdout=subprocess.PIPE).communicate()[0]
309         res = res.strip(' \t\n\r') # trim whitespaces
310         return res
311       #
312       line = re.sub('`[^`]+`', myrep, line)
313       #
314       if self.outputFile is not None:
315         self.outputFile.write(line+'\n')
316       return line
317
318   def _purgeValue(self, value, name):
319     # Replace foo:${PATTERN}:bar or foo:$PATTERN:bar by foo:bar
320     key = r'\$\{?'+name+'\}?'
321     pattern = re.compile(key, re.VERBOSE)
322     value = pattern.sub(r'', value)
323
324     # trim colons
325     value = _trimColons(value)
326
327     return value
328   #
329
330 # Convert .sh environment file to configuration file format
331 def convertEnvFileToConfigFile(envFilename, configFilename, reserved=[]):
332   logConfigParser.debug('convert env file %s to %s'%(envFilename, configFilename))
333   fileContents = open(envFilename, 'r').read()
334
335   pattern = re.compile('\n[\n]+', re.VERBOSE) # multiple '\n'
336   fileContents = pattern.sub(r'\n', fileContents) # replace by a single '\n'
337
338   finput = StringIO(unicode(fileContents))
339   foutput = open(configFilename, 'w')
340
341   config = MultiOptSafeConfigParser()
342   config.optionxform = str # case sensitive
343   config.readfp(EnvFileConverter(finput, 'SALOME Configuration', reserved, outputFile=foutput))
344
345   foutput.close()
346
347   logConfigParser.info('Configuration file generated: %s'%configFilename)
348 #