5 from io import StringIO
8 logConfigParser = logging.getLogger(__name__)
10 RESERVED_PREFIX = 'ADD_TO_'
13 # :TRICKY: So ugly solution...
14 class MultiOptSafeConfigParser(ConfigParser.SafeConfigParser):
16 ConfigParser.SafeConfigParser.__init__(self)
18 # copied from python 2.6.8 Lib.ConfigParser.py
19 # modified (see code comments) to handle duplicate keys
20 def _read(self, fp, fpname):
21 """Parse a sectioned setup file.
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.
30 cursect = None # None, or a dictionary
33 e = None # None, or an exception
39 # comment or blank line?
40 if line.strip() == '' or line[0] in '#;':
42 if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
43 # no leading whitespace
46 if line[0].isspace() and cursect is not None and optname:
49 cursect[optname].append(value)
50 # a section header or option header?
52 # is it a section header?
53 mo = self.SECTCRE.match(line)
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
61 cursect = self._dict()
62 cursect['__name__'] = sectname
63 self._sections[sectname] = cursect
64 # So sections can't start with a continuation line
66 # no section header in the file?
68 raise MissingSectionHeaderError(fpname, lineno, line)
71 mo = self.OPTCRE.match(line)
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
81 pos = optval.find(';')
82 if pos != -1 and optval[pos-1].isspace():
84 optval = optval.strip()
88 # REPLACE following line (original):
89 #cursect[optname] = [optval]
91 # Check if this optname already exists
92 if (optname in cursect) and (cursect[optname] is not None):
93 cursect[optname][0] += ','+optval
95 cursect[optname] = [optval]
98 # valueless option handling
99 cursect[optname] = optval
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
106 e = ConfigParser.ParsingError(fpname)
107 e.append(lineno, repr(line))
108 # if any parsing errors occurred, raise an exception
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)
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
130 # :TODO: test file existence
134 config.read(filename)
135 except ConfigParser.MissingSectionHeaderError:
136 logConfigParser.error("No section found in file: %s"%(filename))
139 return _processConfigFile(config, reserved)
142 def _processConfigFile(config, reserved = []):
143 # :TODO: may detect duplicated variables in the same section (raise a warning)
144 # or even duplicate sections
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))
156 for key,val in entries:
158 logConfigParser.error("Invalid use of reserved variable: %s in file: %s"%(key, filename))
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
165 expandedVal = _trimColons(expandedVal)
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]
174 outputVariables.append((key, expandedVal))
177 pass # end for key,val
178 pass # end for section
180 return outputVariables, reservedValues
183 def _trimColons(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
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):
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' ]
213 if self.outputFile is not None:
214 self.outputFile.write(self.sechead)
219 line = self.fp.readline()
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):
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):
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
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}
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)
254 pattern = re.compile(r'\"', re.VERBOSE)
255 line = pattern.sub(r'', line)
257 # Replace `shell_command` by its result
259 obj = re.sub('`', r'', obj.group(0)) # remove quotes
261 res = subprocess.Popen([obj], stdout=subprocess.PIPE).communicate()[0]
262 res = res.strip(' \t\n\r') # trim whitespaces
265 line = re.sub('`[^`]+`', myrep, line)
267 if self.outputFile is not None:
268 self.outputFile.write(line+'\n')
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)
278 value = _trimColons(value)
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()
289 pattern = re.compile('\n[\n]+', re.VERBOSE) # multiple '\n'
290 fileContents = pattern.sub(r'\n', fileContents) # replace by a single '\n'
292 finput = StringIO(unicode(fileContents))
293 foutput = open(configFilename, 'w')
295 config = MultiOptSafeConfigParser()
296 config.optionxform = str # case sensitive
297 config.readfp(EnvFileConverter(finput, 'SALOME Configuration', reserved, outputFile=foutput))
300 logConfigParser.info('Configuration file generated: %s'%configFilename)