1 # Copyright (C) 2013-2016 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
26 from salomeContextUtils import SalomeContextException #@UnresolvedImport
29 logConfigParser = logging.getLogger(__name__)
31 ADD_TO_PREFIX = 'ADD_TO_'
32 UNSET_KEYWORD = 'UNSET'
35 def _expandSystemVariables(key, val):
36 expandedVal = os.path.expandvars(val) # expand environment variables
37 # Search for not expanded variables (i.e. non-existing environment variables)
38 pattern = re.compile('\${ ( [^}]* ) }', re.VERBOSE) # string enclosed in ${ and }
39 expandedVal = pattern.sub(r'', expandedVal) # remove matching patterns
41 if not "DLIM8VAR" in key: # special case: DISTENE licence key can contain double clons (::)
42 expandedVal = _trimColons(expandedVal)
46 # :TRICKY: So ugly solution...
47 class MultiOptSafeConfigParser(ConfigParser.SafeConfigParser):
49 ConfigParser.SafeConfigParser.__init__(self)
51 # copied from python 2.6.8 Lib.ConfigParser.py
52 # modified (see code comments) to handle duplicate keys
53 def _read(self, fp, fpname):
54 """Parse a sectioned setup file.
56 The sections in setup file contains a title line at the top,
57 indicated by a name in square brackets (`[]'), plus key/value
58 options lines, indicated by `name: value' format lines.
59 Continuations are represented by an embedded newline then
60 leading whitespace. Blank lines, lines beginning with a '#',
61 and just about everything else are ignored.
63 cursect = None # None, or a dictionary
66 e = None # None, or an exception
72 # comment or blank line?
73 if line.strip() == '' or line[0] in '#;':
75 if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
76 # no leading whitespace
79 if line[0].isspace() and cursect is not None and optname:
82 cursect[optname].append(value)
83 # a section header or option header?
85 # is it a section header?
86 mo = self.SECTCRE.match(line)
88 sectname = mo.group('header')
89 if sectname in self._sections:
90 cursect = self._sections[sectname]
91 elif sectname == ConfigParser.DEFAULTSECT:
92 cursect = self._defaults
94 cursect = self._dict()
95 cursect['__name__'] = sectname
96 self._sections[sectname] = cursect
97 # So sections can't start with a continuation line
99 # no section header in the file?
100 elif cursect is None:
101 raise ConfigParser.MissingSectionHeaderError(fpname, lineno, line)
104 mo = self.OPTCRE.match(line)
106 optname, vi, optval = mo.group('option', 'vi', 'value')
107 optname = self.optionxform(optname.rstrip())
108 # This check is fine because the OPTCRE cannot
109 # match if it would set optval to None
110 if optval is not None:
111 if vi in ('=', ':') and ';' in optval:
112 # ';' is a comment delimiter only if it follows
113 # a spacing character
114 pos = optval.find(';')
115 if pos != -1 and optval[pos-1].isspace():
116 optval = optval[:pos]
117 optval = optval.strip()
119 splittedComments = optval.split('#')
120 s = _expandSystemVariables(optname, splittedComments[0])
121 optval = s.strip().strip("'").strip('"')
122 #if len(splittedComments) > 1:
123 # optval += " #" + " ".join(splittedComments[1:])
128 # REPLACE following line (original):
129 #cursect[optname] = [optval]
131 # Check if this optname already exists
132 if (optname in cursect) and (cursect[optname] is not None):
133 cursect[optname][0] += ','+optval
135 cursect[optname] = [optval]
136 # END OF SUBSTITUTION
138 # valueless option handling
139 cursect[optname] = optval
141 # a non-fatal parsing error occurred. set up the
142 # exception but keep going. the exception will be
143 # raised at the end of the file and will contain a
144 # list of all bogus lines
146 e = ConfigParser.ParsingError(fpname)
147 e.append(lineno, repr(line))
148 # if any parsing errors occurred, raise an exception
152 # join the multi-line values collected while reading
153 all_sections = [self._defaults]
154 all_sections.extend(self._sections.values())
155 for options in all_sections:
156 for name, val in options.items():
157 if isinstance(val, list):
158 options[name] = '\n'.join(val)
162 # Parse configuration file
163 # Input: filename, and a list of reserved keywords (environment variables)
164 # Output: a list of pairs (variable, value), and a dictionary associating a list of user-defined values to each reserved keywords
165 # Note: Does not support duplicate keys in a same section
166 def parseConfigFile(filename, reserved = None):
169 config = MultiOptSafeConfigParser()
170 config.optionxform = str # case sensitive
172 # :TODO: test file existence
176 config.read(filename)
177 except ConfigParser.MissingSectionHeaderError:
178 logConfigParser.error("No section found in file: %s"%(filename))
182 return __processConfigFile(config, reserved, filename)
183 except ConfigParser.InterpolationMissingOptionError, e:
184 msg = "A variable may be undefined in SALOME context file: %s\nParser error is: %s\n"%(filename, e)
185 raise SalomeContextException(msg)
188 def __processConfigFile(config, reserved = None, filename="UNKNOWN FILENAME"):
189 # :TODO: may detect duplicated variables in the same section (raise a warning)
190 # or even duplicate sections
196 # Get raw items for each section, and make some processing for environment variables management
197 reservedKeys = [ADD_TO_PREFIX+str(x) for x in reserved] # produce [ 'ADD_TO_reserved_1', 'ADD_TO_reserved_2', ..., ADD_TO_reserved_n ]
198 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:[] }
199 sections = config.sections()
200 for section in sections:
201 entries = config.items(section, raw=False) # use interpolation
202 if len(entries) == 0: # empty section
203 logConfigParser.warning("Empty section: %s in file: %s"%(section, filename))
205 for key,val in entries:
207 logConfigParser.error("Invalid use of reserved variable: %s in file: %s"%(key, filename))
208 elif key == UNSET_KEYWORD:
209 unsetVariables += val.replace(',', ' ').split()
211 expandedVal = _expandSystemVariables(key, val)
213 if key in reservedKeys:
214 shortKey = key[len(ADD_TO_PREFIX):]
215 vals = expandedVal.split(',')
216 reservedValues[shortKey] += vals
217 # remove left&right spaces on each element
218 vals = [v.strip(' \t\n\r') for v in vals]
220 outputVariables.append((key, expandedVal))
223 pass # end for key,val
224 pass # end for section
226 # remove duplicate values
228 for (var, values) in outputVariables:
229 vals = values.split(',')
230 vals = list(set(vals))
231 outVars.append((var, ','.join(vals)))
233 return unsetVariables, outVars, reservedValues
236 def _trimColons(var):
238 # Remove leading and trailing colons (:)
239 pattern = re.compile('^:+ | :+$', re.VERBOSE)
240 v = pattern.sub(r'', v) # remove matching patterns
241 # Remove multiple colons
242 pattern = re.compile('::+', re.VERBOSE)
243 v = pattern.sub(r':', v) # remove matching patterns