ProcessJobOptions.py
Go to the documentation of this file.
1 import os, sys, re
2 import time
3 
4 import logging
5 _log = logging.getLogger(__name__)
6 
7 class LogFormatter(logging.Formatter):
8  def __init__(self, fmt=None, datefmt=None, prefix = "# ", with_time = False):
9  logging.Formatter.__init__(self, fmt, datefmt)
10  self.prefix = prefix
11  self.with_time = with_time
12  def format(self, record):
13  fmsg = logging.Formatter.format(self, record)
14  prefix = self.prefix
15  if self.with_time:
16  prefix += '%f ' % time.time()
17  if record.levelno >= logging.WARNING:
18  prefix += record.levelname + ": "
19  s = "\n".join([ prefix + line
20  for line in fmsg.splitlines() ])
21  return s
22 
23 class LogFilter(logging.Filter):
24  def __init__(self, name = ""):
25  logging.Filter.__init__(self, name)
26  self.printing_level = 0
27  self.enabled = True
28  self.threshold = logging.WARNING
29  def filter(self, record):
30  return record.levelno >= self.threshold or (self.enabled and self.printing_level <= 0)
31  def printOn(self, step = 1, force = False):
32  """
33  Decrease the printing_level of 'step' units. ( >0 means no print)
34  The level cannot go below 0, unless the force flag is set to True.
35  A negative value of the threshold disables subsequent "PrintOff"s.
36  """
37  if force:
38  self.printing_level -= step
39  else:
40  if self.printing_level > step:
41  self.printing_level -= step
42  else:
43  self.printing_level = 0
44  def printOff(self, step = 1):
45  """
46  Increase the printing_level of 'step' units. ( >0 means no print)
47  """
48  self.printing_level += step
49  def disable(self, allowed = logging.WARNING):
50  self.enabled = False
51  self.threshold = allowed
52  def enable(self, allowed = logging.WARNING):
53  self.enabled = True
54  self.threshold = allowed
55 
56 class ConsoleHandler(logging.StreamHandler):
57  def __init__(self, stream = None, prefix = None, with_time = False):
58  if stream is None:
59  stream = sys.stdout
60  logging.StreamHandler.__init__(self, stream)
61  if prefix is None:
62  prefix = "# "
63  self._filter = LogFilter(_log.name)
64  self._formatter = LogFormatter(prefix = prefix, with_time = with_time)
65  self.setFormatter(self._formatter)
66  self.addFilter(self._filter)
67  def setPrefix(self, prefix):
68  self._formatter.prefix = prefix
69  def printOn(self, step = 1, force = False):
70  """
71  Decrease the printing_level of 'step' units. ( >0 means no print)
72  The level cannot go below 0, unless the force flag is set to True.
73  A negative value of the threshold disables subsequent "PrintOff"s.
74  """
75  self._filter.printOn(step, force)
76  def printOff(self, step = 1):
77  """
78  Increase the printing_level of 'step' units. ( >0 means no print)
79  """
80  self._filter.printOff(step)
81  def disable(self, allowed = logging.WARNING):
82  self._filter.disable(allowed)
83  def enable(self, allowed = logging.WARNING):
84  self._filter.enable(allowed)
85 
86 _consoleHandler = None
87 def GetConsoleHandler(prefix = None, stream = None, with_time = False):
88  global _consoleHandler
89  if _consoleHandler is None:
90  _consoleHandler = ConsoleHandler(prefix = prefix, stream = stream, with_time = with_time)
91  elif prefix is not None:
92  _consoleHandler.setPrefix(prefix)
93  return _consoleHandler
94 
95 def InstallRootLoggingHandler(prefix = None, level = None, stream = None, with_time = False):
96  root_logger = logging.getLogger()
97  if not root_logger.handlers:
98  root_logger.addHandler(GetConsoleHandler(prefix, stream, with_time))
99  root_logger.setLevel(logging.WARNING)
100  if level is not None:
101  root_logger.setLevel(level)
102 
103 def PrintOn(step = 1, force = False):
104  GetConsoleHandler().printOn(step, force)
105 def PrintOff(step = 1):
106  GetConsoleHandler().printOff(step)
107 
108 class ParserError(RuntimeError):
109  pass
110 
111 def _find_file(f):
112  # expand environment variables in the filename
113  f = os.path.expandvars(f)
114  if os.path.isfile(f):
115  return os.path.realpath(f)
116 
117  path = os.environ.get('JOBOPTSEARCHPATH','').split(os.pathsep)
118  # find the full path to the option file
119  candidates = [d for d in path if os.path.isfile(os.path.join(d,f))]
120  if not candidates:
121  raise ParserError("Cannot find '%s' in %s" % (f,path))
122  return os.path.realpath(os.path.join(candidates[0],f))
123 
124 _included_files = set()
126  if f in _included_files:
127  _log.warning("file '%s' already included, ignored.", f)
128  return False
129  _included_files.add(f)
130  return True
131 
133  comment = re.compile(r'(//.*)$')
134  # non-perfect R-E to check if '//' is inside a string
135  # (a tokenizer would be better)
136  comment_in_string = re.compile(r'(["\']).*//.*\1')
137  directive = re.compile(r'^\s*#\s*([\w!]+)\s*(.*)\s*$')
138  comment_ml = ( re.compile(r'/\*'), re.compile(r'\*/') )
139  statement_sep = ";"
140  reference = re.compile(r'^@([\w.]*)$')
141 
142  def __init__(self):
143  # parser level states
144  self.units = {}
145  self.defines = {}
146  if sys.platform != 'win32':
147  self.defines[ "WIN32" ] = True
148 
149  def _include(self,file,function):
150  file = _find_file(file)
151  if _to_be_included(file):
152  _log.info("--> Including file '%s'", file)
153  function(file)
154  _log.info("<-- End of file '%s'", file)
155 
156  def parse(self,file):
157  # states for the "translation unit"
158  statement = ""
159 
160  ifdef_level = 0
161  ifdef_skipping = False
162  ifdef_skipping_level = 0
163 
164  f = open(_find_file(file))
165  l = f.readline()
166  if l.startswith("#!"):
167  # Skip the first line if it starts with "#!".
168  # It allows to use options files as scripts.
169  l = f.readline()
170 
171  while l:
172  l = l.rstrip()+'\n' # normalize EOL chars (to avoid problems with DOS new-line on Unix)
173 
174  # single line comment
175  m = self.comment.search(l)
176  if m:
177  # check if the '//' is part of a string
178  m2 = self.comment_in_string.search(l)
179  # the '//' is part of a string if we find the quotes around it
180  # and they are not part of the comment itself
181  if not ( m2 and m2.start() < m.start() ):
182  # if it is not the case, we can remove the comment from the
183  # statement
184  l = l[:m.start()]+l[m.end():]
185  # process directives
186  m = self.directive.search(l)
187  if m:
188  directive_name = m.group(1)
189  directive_arg = m.group(2).strip()
190  if directive_name == "include":
191  included_file = directive_arg.strip("'\"")
192  importOptions(included_file)
193  elif directive_name == "units":
194  units_file = directive_arg.strip("'\"")
195  self._include(units_file,self._parse_units)
196  elif directive_name in [ "ifdef", "ifndef"]:
197  ifdef_skipping_level = ifdef_level
198  ifdef_level += 1
199  if directive_arg in self.defines:
200  ifdef_skipping = directive_name == "ifndef"
201  else:
202  ifdef_skipping = directive_name == "ifdef"
203  elif directive_name == "else":
204  ifdef_skipping = not ifdef_skipping
205  elif directive_name == "endif":
206  ifdef_level -= 1
207  if ifdef_skipping and ifdef_skipping_level == ifdef_level:
208  ifdef_skipping = False
209  elif directive_name == "pragma":
210  if not directive_arg:
211  l = f.readline()
212  continue
213  pragma = directive_arg.split()
214  if pragma[0] == "print":
215  if len(pragma) > 1:
216  if pragma[1].upper() in [ "ON", "TRUE", "1" ]:
217  PrintOn()
218  else:
219  PrintOff()
220  else:
221  _log.warning("unknown directive '%s'", directive_name)
222  l = f.readline()
223  continue
224 
225  if ifdef_skipping:
226  l = f.readline()
227  continue
228 
229  # multi-line comment
230  m = self.comment_ml[0].search(l)
231  if m:
232  l,l1 = l[:m.start()],l[m.end():]
233  m = self.comment_ml[1].search(l1)
234  while not m:
235  l1 = f.readline()
236  if not l1:
237  break # EOF
238  m = self.comment_ml[1].search(l1)
239  if not l1 and not m:
240  raise ParserError("End Of File reached before end of multi-line comment")
241  l += l1[m.end():]
242 
243  if self.statement_sep in l:
244  i = l.index(self.statement_sep)
245  statement += l[:i]
246  self._eval_statement(statement.strip().replace("\n","\\n"))
247  statement = l[i+1:]
248  # it may happen (bug #37479) that the rest of the statement
249  # contains a comment.
250  if statement.lstrip().startswith("//"):
251  statement = ""
252  else:
253  statement += l
254 
255  l = f.readline()
256 
257  def _parse_units(self,file):
258  for line in open(file):
259  if '//' in line:
260  line = line[:line.index('//')]
261  line = line.strip()
262  if not line:
263  continue
264  nunit, value = line.split('=')
265  factor, unit = nunit.split()
266  value = eval(value)/eval(factor)
267  self.units[unit] = value
268 
269  def _eval_statement(self,statement):
270  from GaudiKernel.Proxy.Configurable import (ConfigurableGeneric,
271  Configurable,
272  PropertyReference)
273  #statement = statement.replace("\n","").strip()
274  _log.info("%s%s", statement, self.statement_sep)
275 
276  property,value = statement.split("=",1)
277 
278  inc = None
279  if property[-1] in [ "+", "-" ]:
280  inc = property[-1]
281  property = property[:-1]
282 
283  property = property.strip()
284  value = value.strip()
285 
286  ## find the configurable to apply the property to
287  #parent_cfg = None
288  #while '.' in property:
289  # component, property = property.split('.',1)
290  # if parent_cfg:
291  # if hasattr(parent_cfg,component):
292  # cfg = getattr(parent_cfg,component)
293  # else:
294  # cfg = ConfigurableGeneric(component)
295  # setattr(parent_cfg,component,cfg)
296  # else:
297  # cfg = ConfigurableGeneric(component)
298  # parent_cfg = cfg
299 
300  # remove spaces around dots
301  property = '.'.join([w.strip() for w in property.split('.')])
302  component, property = property.rsplit('.',1)
303  if component in Configurable.allConfigurables:
304  cfg = Configurable.allConfigurables[component]
305  else:
306  cfg = ConfigurableGeneric(component)
307 
308  #value = os.path.expandvars(value)
309  value = value.replace('true','True').replace('false','False')
310  if value[0] == '{' :
311  # Try to guess if the values looks like a dictionary
312  if ':' in value and not ( value[:value.index(':')].count('"')%2 or value[:value.index(':')].count("'")%2 ) :
313  # for dictionaries, keep the surrounding {}
314  value = '{'+value[1:-1].replace('{','[').replace('}',']')+'}'
315  else : # otherwise replace all {} with []
316  value = value.replace('{','[').replace('}',']')
317 
318  # We must escape '\' because eval tends to interpret them
319  value = value.replace('\\', '\\\\')
320  # Restore special cases ('\n', '\t' and '\"') (see GAUDI-1001)
321  value = (value.replace(r"\\n", r"\n")
322  .replace(r"\\t", r"\t")
323  .replace(r'\\"', r'\"'))
324  # replace r'\n' and r'\t' that are outside double quoted strings
325  value = '"'.join([(v if i % 2 else re.sub(r'\\[nt]', ' ', v))
326  for i, v in enumerate(value.split('"'))])
327 
328  # interprete the @ operator
329  m = self.reference.match(value)
330  if m:
331  # this allows late binding of references
332  value = PropertyReference(m.group(1))
333  else:
334  value = eval(value,self.units)
335 
336  #if type(value) is str : value = os.path.expandvars(value)
337  #elif type(value) is list : value = [ type(item) is str and os.path.expandvars(item) or item for item in value ]
338 
339  if property not in cfg.__slots__ and not hasattr(cfg,property):
340  # check if the case of the property is wrong (old options are case insensitive)
341  lprop = property.lower()
342  for p in cfg.__slots__:
343  if lprop == p.lower():
344  _log.warning("property '%s' was requested for %s, but the correct spelling is '%s'", property, cfg.name(), p)
345  property = p
346  break
347 
348  # consider the += and -=
349  if inc == "+":
350  if hasattr(cfg,property):
351  prop = getattr(cfg,property)
352  if type(prop) == dict:
353  for k in value:
354  prop[k] = value[k]
355  else:
356  prop += value
357  else:
358  setattr(cfg,property,value)
359  elif inc == "-":
360  if hasattr(cfg,property):
361  prop = getattr(cfg,property)
362  if type(prop) is dict:
363  for k in value:
364  if k in prop:
365  del prop[k]
366  else:
367  _log.warning("key '%s' not in %s.%s", k, cfg.name(), property)
368  else:
369  for k in value:
370  if k in prop:
371  prop.remove(k)
372  else:
373  _log.warning("value '%s' not in %s.%s", k, cfg.name(), property)
374  else:
375  setattr(cfg,property,value)
376 
378  def __init__(self, new_path):
379  self.old_path = sys.path
380  sys.path = new_path
381  def __del__(self):
382  sys.path = self.old_path
383 
384 _parser = JobOptsParser()
385 
386 def _import_python(file):
387  execfile(file, {})
388 
389 def _import_pickle(file):
390  import pickle
391  input = open(file, 'rb')
392  catalog = pickle.load(input)
393  _log.info('Unpickled %d configurables', len(catalog))
394 
395 def _import_opts(file):
396  _parser.parse(file)
397 
398 _import_function_mapping = {
399  ".py" : _import_python,
400  ".pkl" : _import_pickle,
401  ".opts" : _import_opts,
402  }
403 
404 def importOptions( optsfile ) :
405  # expand environment variables before checking the extension
406  optsfile = os.path.expandvars(optsfile)
407  # check the file type (extension)
408  dummy, ext = os.path.splitext(optsfile)
409  if ext in _import_function_mapping:
410  # check if the file has been already included
411  optsfile = _find_file(optsfile)
412  if _to_be_included(optsfile):
413  _log.info("--> Including file '%s'", optsfile)
414  # include the file
415  _import_function_mapping[ext](optsfile)
416  _log.info("<-- End of file '%s'", optsfile)
417  else:
418  raise ParserError("Unknown file type '%s' ('%s')" % (ext,optsfile))
419 
420 ## Import a file containing declaration of units.
421 # It is equivalent to:
422 #
423 # #units "unitsfile.opts"
424 #
425 def importUnits(unitsfile):
426  # expand environment variables
427  unitsfile = os.path.expandvars(unitsfile)
428  # we do not need to check the file type (extension) because it must be a
429  # units file
430  _parser._include(unitsfile, _parser._parse_units)
StatusCode parse(GaudiUtils::HashMap< K, V > &result, const std::string &input)
Basic parser for the types of HashMap used in DODBasicMapper.
string type
Definition: gaudirun.py:127
def importUnits(unitsfile)
Import a file containing declaration of units.