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