The Gaudi Framework  master (181af51f)
Loading...
Searching...
No Matches
ProcessJobOptions.py
Go to the documentation of this file.
11import logging
12import os
13import re
14import sys
15import time
16
17_log = logging.getLogger(__name__)
18
19
20class LogFormatter(logging.Formatter):
21 def __init__(self, fmt=None, datefmt=None, prefix="# ", with_time=False):
22 logging.Formatter.__init__(self, fmt, datefmt)
23 self.prefix = prefix
24 self.with_time = with_time
25
26 def format(self, record):
27 fmsg = logging.Formatter.format(self, record)
28 prefix = self.prefix
29 if self.with_time:
30 prefix += "%f " % time.time()
31 if record.levelno >= logging.WARNING:
32 prefix += record.levelname + ": "
33 s = "\n".join([prefix + line for line in fmsg.splitlines()])
34 return s
35
36
37class LogFilter(logging.Filter):
38 def __init__(self, name=""):
39 logging.Filter.__init__(self, name)
41 self.enabled = True
42 self.threshold = logging.WARNING
43
44 def filter(self, record):
45 return record.levelno >= self.threshold or (
46 self.enabled and self.printing_level <= 0
47 )
48
49 def printOn(self, step=1, force=False):
50 """
51 Decrease the printing_level of 'step' units. ( >0 means no print)
52 The level cannot go below 0, unless the force flag is set to True.
53 A negative value of the threshold disables subsequent "PrintOff"s.
54 """
55 if force:
56 self.printing_level -= step
57 else:
58 if self.printing_level > step:
59 self.printing_level -= step
60 else:
61 self.printing_level = 0
62
63 def printOff(self, step=1):
64 """
65 Increase the printing_level of 'step' units. ( >0 means no print)
66 """
67 self.printing_level += step
68
69 def disable(self, allowed=logging.WARNING):
70 self.enabled = False
71 self.threshold = allowed
72
73 def enable(self, allowed=logging.WARNING):
74 self.enabled = True
75 self.threshold = allowed
76
77
78class ConsoleHandler(logging.StreamHandler):
79 def __init__(self, stream=None, prefix=None, with_time=False):
80 if stream is None:
81 stream = sys.stdout
82 logging.StreamHandler.__init__(self, stream)
83 if prefix is None:
84 prefix = "# "
85 self._filter = LogFilter(_log.name)
86 self._formatter = LogFormatter(prefix=prefix, with_time=with_time)
87 self.setFormatter(self._formatter)
88 self.addFilter(self._filter)
89
90 def setPrefix(self, prefix):
91 self._formatter.prefix = prefix
92
93 def printOn(self, step=1, force=False):
94 """
95 Decrease the printing_level of 'step' units. ( >0 means no print)
96 The level cannot go below 0, unless the force flag is set to True.
97 A negative value of the threshold disables subsequent "PrintOff"s.
98 """
99 self._filter.printOn(step, force)
100
101 def printOff(self, step=1):
102 """
103 Increase the printing_level of 'step' units. ( >0 means no print)
104 """
105 self._filter.printOff(step)
106
107 def disable(self, allowed=logging.WARNING):
108 self._filter.disable(allowed)
109
110 def enable(self, allowed=logging.WARNING):
111 self._filter.enable(allowed)
112
113
114_consoleHandler = None
115
116
117def GetConsoleHandler(prefix=None, stream=None, with_time=False):
118 global _consoleHandler
119 if _consoleHandler is None:
120 _consoleHandler = ConsoleHandler(
121 prefix=prefix, stream=stream, with_time=with_time
122 )
123 elif prefix is not None:
124 _consoleHandler.setPrefix(prefix)
125 return _consoleHandler
126
127
128def InstallRootLoggingHandler(prefix=None, level=None, stream=None, with_time=False):
129 root_logger = logging.getLogger()
130 if not root_logger.handlers:
131 root_logger.addHandler(GetConsoleHandler(prefix, stream, with_time))
132 root_logger.setLevel(logging.WARNING)
133 if level is not None:
134 root_logger.setLevel(level)
135
136
137def PrintOn(step=1, force=False):
138 GetConsoleHandler().printOn(step, force)
139
140
141def PrintOff(step=1):
142 GetConsoleHandler().printOff(step)
143
144
145class ParserError(RuntimeError):
146 pass
147
148
150 # expand environment variables in the filename
151 f = os.path.expandvars(f)
152 if os.path.isfile(f):
153 return os.path.realpath(f)
154
155 path = os.environ.get("JOBOPTSEARCHPATH", "").split(os.pathsep)
156 # find the full path to the option file
157 candidates = [d for d in path if os.path.isfile(os.path.join(d, f))]
158 if not candidates:
159 raise ParserError("Cannot find '%s' in %s" % (f, path))
160 return os.path.realpath(os.path.join(candidates[0], f))
161
162
163_included_files = set()
164
165
167 if f in _included_files:
168 _log.warning("file '%s' already included, ignored.", f)
169 return False
170 _included_files.add(f)
171 return True
172
173
175 comment = re.compile(r"(//.*)$")
176 # non-perfect R-E to check if '//' is inside a string
177 # (a tokenizer would be better)
178 comment_in_string = re.compile(r'(["\']).*//.*\1')
179 directive = re.compile(r"^\s*#\s*([\w!]+)\s*(.*)\s*$")
180 comment_ml = (re.compile(r"/\*"), re.compile(r"\*/"))
181 statement_sep = ";"
182 reference = re.compile(r"^@([\w.]*)$")
183
184 def __init__(self):
185 # parser level states
186 self.units = {}
187 self.defines = {}
188
189 def _include(self, file, function):
190 file = _find_file(file)
191 if _to_be_included(file):
192 _log.info("--> Including file '%s'", file)
193 function(file)
194 _log.info("<-- End of file '%s'", file)
195
196 def parse(self, file):
197 # states for the "translation unit"
198 statement = ""
199
200 ifdef_level = 0
201 ifdef_skipping = False
202 ifdef_skipping_level = 0
203
204 in_string = False
205
206 f = open(_find_file(file))
207 l = f.readline()
208 if l.startswith("#!"):
209 # Skip the first line if it starts with "#!".
210 # It allows to use options files as scripts.
211 l = f.readline()
212
213 while l:
214 l = (
215 l.rstrip() + "\n"
216 ) # normalize EOL chars (to avoid problems with DOS new-line on Unix)
217
218 # single line comment
219 m = self.comment.search(l)
220 if m:
221 # check if the '//' is part of a string
222 m2 = self.comment_in_string.search(l)
223 # the '//' is part of a string if we find the quotes around it
224 # and they are not part of the comment itself
225 if not (m2 and m2.start() < m.start()):
226 # if it is not the case, we can remove the comment from the
227 # statement
228 l = l[: m.start()] + l[m.end() :]
229 # process directives
230 m = self.directive.search(l)
231 if m:
232 directive_name = m.group(1)
233 directive_arg = m.group(2).strip()
234 if directive_name == "include":
235 included_file = directive_arg.strip("'\"")
236 importOptions(included_file)
237 elif directive_name == "units":
238 units_file = directive_arg.strip("'\"")
239 self._include(units_file, self._parse_units)
240 elif directive_name in ["ifdef", "ifndef"]:
241 ifdef_skipping_level = ifdef_level
242 ifdef_level += 1
243 if directive_arg in self.defines:
244 ifdef_skipping = directive_name == "ifndef"
245 else:
246 ifdef_skipping = directive_name == "ifdef"
247 elif directive_name == "else":
248 ifdef_skipping = not ifdef_skipping
249 elif directive_name == "endif":
250 ifdef_level -= 1
251 if ifdef_skipping and ifdef_skipping_level == ifdef_level:
252 ifdef_skipping = False
253 elif directive_name == "pragma":
254 if not directive_arg:
255 l = f.readline()
256 continue
257 pragma = directive_arg.split()
258 if pragma[0] == "print":
259 if len(pragma) > 1:
260 if pragma[1].upper() in ["ON", "TRUE", "1"]:
261 PrintOn()
262 else:
263 PrintOff()
264 else:
265 _log.warning("unknown directive '%s'", directive_name)
266 l = f.readline()
267 continue
268
269 if ifdef_skipping:
270 l = f.readline()
271 continue
272
273 # multi-line comment
274 m = self.comment_ml[0].search(l)
275 if m:
276 l, l1 = l[: m.start()], l[m.end() :]
277 m = self.comment_ml[1].search(l1)
278 while not m:
279 l1 = f.readline()
280 if not l1:
281 break # EOF
282 m = self.comment_ml[1].search(l1)
283 if not l1 and not m:
284 raise ParserError(
285 "End Of File reached before end of multi-line comment"
286 )
287 l += l1[m.end() :]
288
289 # if we are in a multiline string, we add to the statement
290 # everything until the next '"'
291 if in_string:
292 string_end = l.find('"')
293 if string_end >= 0:
294 statement += l[: string_end + 1]
295 l = l[string_end + 1 :]
296 in_string = False # the string ends here
297 else:
298 statement += l
299 l = ""
300 else: # check if we have a string
301 string_start = l.find('"')
302 if string_start >= 0:
303 string_end = l.find('"', string_start + 1)
304 if string_end >= 0:
305 # the string is opened and closed
306 statement += l[: string_end + 1]
307 l = l[string_end + 1 :]
308 else:
309 # the string is only opened
310 statement += l
311 in_string = True
312 l = f.readline()
313 continue
314
315 if self.statement_sep in l:
316 i = l.index(self.statement_sep)
317 statement += l[:i]
318 self._eval_statement(statement.strip().replace("\n", "\\n"))
319 statement = l[i + 1 :]
320 # it may happen (bug #37479) that the rest of the statement
321 # contains a comment.
322 if statement.lstrip().startswith("//"):
323 statement = ""
324 else:
325 statement += l
326
327 l = f.readline()
328
329 def _parse_units(self, file):
330 for line in open(file):
331 if "//" in line:
332 line = line[: line.index("//")]
333 line = line.strip()
334 if not line:
335 continue
336 nunit, value = line.split("=")
337 factor, unit = nunit.split()
338 value = eval(value) / eval(factor)
339 self.units[unit] = value
340
341 def _eval_statement(self, statement):
342 from GaudiKernel.Proxy.Configurable import (
343 Configurable,
344 ConfigurableGeneric,
345 PropertyReference,
346 )
347
348 # statement = statement.replace("\n","").strip()
349 _log.info("%s%s", statement, self.statement_sep)
350
351 property, value = statement.split("=", 1)
352
353 inc = None
354 if property[-1] in ["+", "-"]:
355 inc = property[-1]
356 property = property[:-1]
357
358 property = property.strip()
359 value = value.strip()
360
361 # find the configurable to apply the property to
362 # parent_cfg = None
363 # while '.' in property:
364 # component, property = property.split('.',1)
365 # if parent_cfg:
366 # if hasattr(parent_cfg,component):
367 # cfg = getattr(parent_cfg,component)
368 # else:
369 # cfg = ConfigurableGeneric(component)
370 # setattr(parent_cfg,component,cfg)
371 # else:
372 # cfg = ConfigurableGeneric(component)
373 # parent_cfg = cfg
374
375 # remove spaces around dots
376 property = ".".join([w.strip() for w in property.split(".")])
377 component, property = property.rsplit(".", 1)
378 if component in Configurable.allConfigurables:
379 cfg = Configurable.allConfigurables[component]
380 else:
381 cfg = ConfigurableGeneric(component)
382
383 # value = os.path.expandvars(value)
384 value = value.replace("true", "True").replace("false", "False")
385 if value[0] == "{":
386 # Try to guess if the values looks like a dictionary
387 if ":" in value and not (
388 value[: value.index(":")].count('"') % 2
389 or value[: value.index(":")].count("'") % 2
390 ):
391 # for dictionaries, keep the surrounding {}
392 value = "{" + value[1:-1].replace("{", "[").replace("}", "]") + "}"
393 else: # otherwise replace all {} with []
394 value = value.replace("{", "[").replace("}", "]")
395
396 # We must escape '\' because eval tends to interpret them
397 value = value.replace("\\", "\\\\")
398 # Restore special cases ('\n', '\t' and '\"') (see GAUDI-1001)
399 value = (
400 value.replace(r"\\n", r"\n").replace(r"\\t", r"\t").replace(r'\\"', r"\"")
401 )
402 # replace r'\n' and r'\t' that are outside double quoted strings
403 value = '"'.join(
404 [
405 (v if i % 2 else re.sub(r"\\[nt]", " ", v))
406 for i, v in enumerate(value.split('"'))
407 ]
408 )
409
410 # interprete the @ operator
411 m = self.reference.match(value)
412 if m:
413 # this allows late binding of references
414 value = PropertyReference(m.group(1))
415 else:
416 value = eval(value, self.units)
417
418 # if type(value) is str : value = os.path.expandvars(value)
419 # elif type(value) is list : value = [ type(item) is str and os.path.expandvars(item) or item for item in value ]
420
421 if property not in cfg.__slots__ and not hasattr(cfg, property):
422 # check if the case of the property is wrong (old options are case insensitive)
423 lprop = property.lower()
424 for p in cfg.__slots__:
425 if lprop == p.lower():
426 _log.warning(
427 "property '%s' was requested for %s, but the correct spelling is '%s'",
428 property,
429 cfg.name(),
430 p,
431 )
432 property = p
433 break
434
435 # consider the += and -=
436 if inc == "+":
437 if hasattr(cfg, property):
438 prop = getattr(cfg, property)
439 if isinstance(prop, dict):
440 for k in value:
441 prop[k] = value[k]
442 else:
443 prop += value
444 else:
445 setattr(cfg, property, value)
446 elif inc == "-":
447 if hasattr(cfg, property):
448 prop = getattr(cfg, property)
449 if isinstance(prop, dict):
450 for k in value:
451 if k in prop:
452 del prop[k]
453 else:
454 _log.warning(
455 "key '%s' not in %s.%s", k, cfg.name(), property
456 )
457 else:
458 for k in value:
459 if k in prop:
460 prop.remove(k)
461 else:
462 _log.warning(
463 "value '%s' not in %s.%s", k, cfg.name(), property
464 )
465 else:
466 setattr(cfg, property, value)
467
468
470 def __init__(self, new_path):
471 self.old_path = sys.path
472 sys.path = new_path
473
474 def __del__(self):
475 sys.path = self.old_path
476
477
478_parser = JobOptsParser()
479
480
482 with open(file) as f:
483 code = compile(f.read(), file, "exec")
484 exec(code, {"__file__": file})
485
486
488 import pickle
489
490 input = open(file, "rb")
491 catalog = pickle.load(input)
492 _log.info("Unpickled %d configurables", len(catalog))
493
494
495def _import_opts(file):
496 _parser.parse(file)
497
498
499def _import_dict(data):
500 from GaudiKernel.Proxy.Configurable import Configurable, ConfigurableGeneric
501
502 for property, value_repr in data.items():
503 component, property = property.rsplit(".", 1)
504 if component in Configurable.allConfigurables:
505 cfg = Configurable.allConfigurables[component]
506 else:
507 cfg = ConfigurableGeneric(component)
508 value = eval(value_repr)
509 setattr(cfg, property, value)
510
511
512def _import_json(filename):
513 import json
514
515 with open(filename) as f:
516 _import_dict(json.load(f))
517
518
519_import_function_mapping = {
520 ".py": _import_python,
521 ".pkl": _import_pickle,
522 ".opts": _import_opts,
523 ".json": _import_json,
524}
525
526try:
527 import yaml
528
529 def _import_yaml(filename):
530 with open(filename) as f:
531 _import_dict(yaml.safe_load(f))
532
533 _import_function_mapping[".yaml"] = _import_yaml
534 _import_function_mapping[".yml"] = _import_function_mapping[".yaml"]
535except ImportError:
536 pass # yaml support is optional
537
538
539def importOptions(optsfile):
540 # expand environment variables before checking the extension
541 optsfile = os.path.expandvars(optsfile)
542 # check the file type (extension)
543 dummy, ext = os.path.splitext(optsfile)
544 if ext in _import_function_mapping:
545 # check if the file has been already included
546 optsfile = _find_file(optsfile)
547 if _to_be_included(optsfile):
548 _log.info("--> Including file '%s'", optsfile)
549 # include the file
550 _import_function_mapping[ext](optsfile)
551 _log.info("<-- End of file '%s'", optsfile)
552 else:
553 raise ParserError("Unknown file type '%s' ('%s')" % (ext, optsfile))
554
555
556# Import a file containing declaration of units.
557# It is equivalent to:
558#
559# #units "unitsfile.opts"
560#
561
562
563def importUnits(unitsfile):
564 # expand environment variables
565 unitsfile = os.path.expandvars(unitsfile)
566 # we do not need to check the file type (extension) because it must be a
567 # units file
568 _parser._include(unitsfile, _parser._parse_units)
__init__(self, stream=None, prefix=None, with_time=False)
disable(self, allowed=logging.WARNING)
enable(self, allowed=logging.WARNING)
__init__(self, fmt=None, datefmt=None, prefix="# ", with_time=False)
GetConsoleHandler(prefix=None, stream=None, with_time=False)
InstallRootLoggingHandler(prefix=None, level=None, stream=None, with_time=False)