13 from AthenaConfiguration.iconfTool.models.loaders
import loadConfigFile, baseParser, componentRenamingDict, loadDifferencesFile, isReference
17 knowndifference=
"\033[35m"
38 "-q",
"--quiet", action=
"store_true", help=
"Don't print command arguments"
41 "-p",
"--printConf", action=
"store_true", help=
"Prints entire configuration"
44 "--printComps", action=
"store_true", help=
"Prints only the components"
47 "--diff", dest=
"diff", action=
"store_true", help=
"Diffs two files"
49 parser.add_argument(
"--toJSON", action=
"store_true", help=
"Convert pickle to JSON file")
50 parser.add_argument(
"--toPickle", action=
"store_true", help=
"Convert JSON to pickle file")
52 parser.add_argument(
"file", nargs=
"+", help=
"Files to work with")
55 help=
"Don't report components existing in only one of the two configurations",
63 help=
"Ignore order for sequence properties matching regex",
66 "--allComponentPrint",
67 help=
"Print all component if there are differences in any of its properties",
71 "--printIdenticalComponents",
72 help=
"Print all components even, if there are no differences.",
76 "--printIdenticalPerParameter",
77 help=
"Print all parameters in component with difference even, if there are no differences.",
80 parser.add_argument(
"--knownDifferencesFile",
81 help=
"Ignore differences enlisted in file (to be used only with diffing)")
83 parser.add_argument(
"--color",
84 help=
"Use colored output even for file output (useful when piping to less -R to to HTML conversion",
87 parser.add_argument(
"-s",
"--structured",
90 help=
"Print only a single component, in a structured manner (reflecting components parent children)")
92 parser.add_argument(
"--includeClassesSub",
95 help=
"Below the top-level, also include the components selected by the given classname (anchored regular expression)")
97 parser.add_argument(
"--includeSequences",
98 help=
"Include sequences in the structured printout",
101 parser.add_argument(
"--classes",
103 help=
"Only show class names")
105 parser.add_argument(
"--uniqueClasses",
107 help=
"Only show unique classes")
109 parser.add_argument(
"--showComponentName",
110 help=
"Show component name with --classes",
113 parser.add_argument(
"--maxDepth",
114 help=
"Maximum depth for structured printout",
118 args = parser.parse_args()
120 print(
"Run with arguments:")
121 print(
"confTool.py",
" ".
join(sys.argv[1:]))
129 if not args.quiet
and args.ignoreIrrelevant:
130 print(f
"Properties to ignore: {args.ignore}")
132 if not sys.stdout.isatty()
and not args.color:
135 for fileName
in args.file:
140 for fileName
in args.file:
145 for fileName
in args.file:
151 if len(args.file) != 1:
153 "ERROR, can convert single file at a time, got: %s" % args.file
156 oFileName = args.file[0].
replace(
".pkl",
".json")
157 with open(oFileName,
"w")
as oFile:
158 json.dump(conf, oFile, indent=4, sort_keys=
True, ensure_ascii=
True)
159 print(
"Wrote " + args.file[0] +
" to " + oFileName)
163 if len(args.file) != 1:
165 "ERROR, can convert single file at a time, got: %s" % args.file
168 oFileName = args.file[0].
replace(
".json",
".pkl")
169 with open(oFileName,
"wb")
as oFile:
171 pickle.dump(item, oFile)
172 print(
"Wrote " + args.file[0] +
" to " + oFileName)
175 if len(args.file) != 2:
177 "ERROR, can diff exactly two files at a time, got: %s"
182 args.renameComps=
None
183 args.renameCompsFile=
None
185 global knownDifferences
186 if args.knownDifferencesFile:
188 exit_code =
_compareConfig(configRef, configChk, args, color) != 0
194 for k, settings
in conf.items():
195 print(f
"{color.component}{k}{color.reset}")
196 if isinstance(settings, dict):
197 for prop,val
in settings.items():
198 print(f
" {color.property}{prop} = {color.value}{val}")
203 for k, item
in conf.items():
204 if isinstance(item, dict):
208 showClasses = args.classes
or args.uniqueClasses
209 def _oneCompPrint(d, comp, cl, done={}, depth=0, indent = "") -> list:
210 show = ((
not showClasses
or cl
is not None)
and
211 (depth>0
and any([re.match(f
"({regex})$",cl)
for regex
in args.includeClassesSub]))
or
212 ((
not args.includeClasses
or any([re.match(f
"({regex})$",cl)
for regex
in args.includeClasses]))
and
213 not any([re.match(f
"({regex})$",cl)
for regex
in args.excludeClasses])
and
214 not any([re.match(f
"({regex})$",comp)
for regex
in args.excludeComponents])))
216 if not args.uniqueClasses:
218 cc = cl
if (
not args.showComponentName)
else f
"{cl}/{comp}"
219 print(f
"{indent}{cc}")
221 print(f
"{indent}{comp}")
222 newindent = indent +
" - "
224 elif args.includeSequences
and ((len(d) == 1
and "Members" in d)
or (comp ==
"ApplicationMgr" and "TopAlg" in d)):
228 if showClasses
and args.maxDepth
is not None and depth > args.maxDepth:
232 for prop,val
in sorted(d.items()):
233 if not showClasses
and show:
234 print(f
"{indent} {prop} = {val}")
235 if args.maxDepth
is not None and depth > args.maxDepth:
237 if not args.includeSequences
and (prop ==
"Members" or prop ==
"TopAlg"):
240 if showClasses
and not c:
242 if ref
in conf
and ref
not in done:
243 r = _oneCompPrint(conf[ref], ref, c
or ref, {**done, ref:
True}, depth, newindent)
244 if args.uniqueClasses:
247 return [[cl, comp, sub]]
253 for ref,cl
in isReference(start,
None, conf)
or [(
None,
None)]:
254 if ref
and ref
in conf:
255 settings = conf.get(ref)
256 if isinstance(settings, dict):
257 r = _oneCompPrint(settings, ref, cl
or ref)
259 elif not showClasses:
262 print(f
"{comp} is absent in the config, unfortunately the name has to be exact")
264 if not args.uniqueClasses:
268 """copy structure without the component names, so equality tests for the same class names."""
275 from collections
import defaultdict
276 cls = defaultdict(list)
277 def _oneCompPrune(top):
278 """Duplicate classes (with identical structure below) are listed, without substructure
279 - or not listed at all if they already showed up at this level.
280 Classes with the same name are grouped next to each other and shown with their component name."""
283 ord = defaultdict(list)
284 for cl,comp,sub
in top:
285 ord[cl].
append([cl,comp,sub])
289 dupcls = defaultdict(int)
290 for o
in ord.values():
291 for cl,comp,sub
in o:
292 comp = comp.split(
'.')[-1]
294 if cl
in cls
and nsub
in cls[cl]:
300 r = _oneCompPrune(sub)
302 new.append([cl,comp,r])
305 if not args.showComponentName:
311 def _structPrint(top, indent = ""):
312 for cl,comp,sub
in top:
313 cc = f
"{cl}/{comp}" if args.showComponentName
else \
314 f
"{cl} ({comp})" if comp
is not None else cl
315 print(f
"{indent}{cc}")
317 _structPrint(sub, indent +
" - ")
319 _structPrint(_oneCompPrune(sub))
324 allComps =
list(
set(configRef.keys()) |
set(configChk.keys()))
327 print(
"Step 1: reference file #components:", len(configRef))
328 print(
"Step 2: file to check #components:", len(configChk))
330 print(f
"{color.difference}Differences in components {color.first}Settings in 1st file {color.second}Settings in 2nd file{color.reset}")
332 componentReverseRenamig = {v: k
for k, v
in componentRenamingDict.items()}
333 def _componentDescription(comp_name):
334 return (comp_name+
" renamed from " + componentReverseRenamig[comp_name])
if comp_name
in componentReverseRenamig
else comp_name
337 for component
in allComps:
338 if component
not in configRef:
339 if not args.ignoreMissing:
341 f
"\n{color.second} Component ",
342 _componentDescription(component),
343 f
"{color.reset} only in 2nd file {color.reset} \n",
347 if component
not in configChk:
348 if not args.ignoreMissing:
350 f
"\n{color.first} Component",
351 _componentDescription(component),
352 f
"{color.reset}only in 1st file {color.reset} \n",
356 refValue = configRef[component]
357 chkValue = configChk[component]
359 if chkValue == refValue:
360 if args.printIdenticalComponents:
361 print(
"Component", _componentDescription(component),
"identical")
363 print(f
"{color.difference}Component", _componentDescription(component), f
"may differ{color.reset}")
364 if not args.allComponentPrint:
365 countDifferent =
_compareComponent(refValue, chkValue,
"\t", args, component,
"", color)
366 if countDifferent == 0:
367 print(
" but all are suppressed by renaming/known differences/...")
369 print(f
" {color.difference} {countDifferent} relevant differences{color.reset}")
372 f
"\t{color.first}Ref{color.reset}\t",
373 sorted(configRef[component].
items(), key=
lambda kv: kv[0]),
376 f
"\t{color.second}Chk{color.reset}\t",
377 sorted(configChk[component].
items(), key=
lambda kv: kv[0]),
379 return countDifferent
383 """Ensure all numeric values are of the same type (int or float)"""
384 if any(isinstance(val, float)
for val
in values):
385 return tuple(
float(val)
for val
in values)
386 elif all(isinstance(val, int)
for val
in values):
387 return tuple(
int(val)
for val
in values)
393 if comp
in knownDifferences:
394 if prop
in knownDifferences[comp]:
395 acceptedDifference = knownDifferences[comp][prop]
396 if acceptedDifference == (
None,
None):
398 if acceptedDifference[0]
is None:
399 return chkVal == acceptedDifference[1]
400 if acceptedDifference[1]
is None:
401 return refVal == acceptedDifference[0]
403 return refVal == acceptedDifference[0]
and chkVal == acceptedDifference[1]
407 """ Rename values in reference as long as they are hashable (and in renamingDict)
408 It is a bit of heuristics that is invoved, the assumption is that the property has value of the form A/B if it is name of the compoemnt or it is a just single string"""
409 refList = refVal
if isinstance(refVal, list)
else [refVal]
412 if isinstance(v, str):
413 if "/" in v
and len(v.split(
"/")) == 2:
414 compType,compName = v.split(
"/")
415 newName = componentRenamingDict.get(compName, compName)
416 updatedRef.append( f
"{compType}/{newName}" )
418 updatedRef.append( componentRenamingDict.get(v, v) )
421 return updatedRef
if isinstance(refVal, list)
else updatedRef[0]
425 if isinstance(compRef, dict):
426 allProps =
list(
set(compRef.keys()) |
set(compChk.keys()))
429 for prop
in allProps:
430 if prop
not in compRef.keys():
432 print(f
"{prefix}{color.property}{prop} = {color.second}{compChk[prop]} {color.reset} only in 2nd file {color.reset}")
435 print(f
"{prefix}known difference in: {prop}")
438 if prop
not in compChk.keys():
440 print(f
"{prefix}{color.property}{prop} = {color.first}{compRef[prop]} {color.reset} only in 1st file {color.reset}")
443 print(f
"{prefix}known difference in: {prop}")
446 refVal = compRef[prop]
447 chkVal = compChk[prop]
450 refVal = ast.literal_eval(
str(refVal))
if refVal
else ""
451 chkVal = ast.literal_eval(
str(chkVal))
if chkVal
else ""
462 if not args.printIdenticalPerParameter:
465 print(f
"{prefix}known difference in: {prop}")
466 if not args.printIdenticalPerParameter:
469 diffmarker = f
" {color.difference}<<{color.reset}"
471 if not (component ==
"IOVDbSvc" and prop ==
"Folders"):
472 print(f
"{prefix}{color.property}{prop} = {color.first} {refVal} {color.reset} vs {color.second} {chkVal} {color.reset} {diffmarker}")
474 if refVal
and ( isinstance(refVal, list)
or isinstance(refVal, dict) ):
475 if component ==
"IOVDbSvc" and prop ==
"Folders":
479 refVal, chkVal,
"\t" + prefix +
">> ", args, component, prop, color
482 elif isinstance(compRef, (list, tuple, set))
and len(compRef) > 1:
484 if isinstance(compRef[0], list):
487 if len(compChk) > 0
and isinstance(compChk[0], list):
491 if compRef == compChk:
492 return countDifferent
498 print(f
"{prefix} {color.reset}only in 1st file : {color.first} {diffRef} {color.reset}")
501 print(f
"{prefix} {color.reset}only in 2nd file : {color.second} {diffChk} {color.reset}")
504 if len(compRef) == len(compChk):
506 if any(re.match(f
"^{regex}$",f
"{component}.{propname}")
for regex
in args.ignoreOrder):
507 print(f
"{prefix} : {color.knowndifference} ^^ Different order ignored ^^ {color.reset}")
509 print(f
"{prefix} : {color.difference} ^^ Different order ^^ {color.reset}")
512 for i, (refVal, chkVal)
in enumerate(zip(compRef, compChk)):
514 print(f
"{prefix} : {color.first} {refVal} {color.reset} vs {color.second} {chkVal} {color.reset} {color.difference}<< at index {i} {color.reset}")
516 refVal, chkVal,
"\t" + prefix +
">> ", args,
"",
"", color
518 return countDifferent
523 db_match = re.search(
r"<db>(.*)</db>", definition)
525 result[
"db"] = db_match.group(1)
526 definition = definition.replace(db_match.group(0),
"")
528 key_match = re.search(
r"<key>(.*)</key>", definition)
530 result[
"key"] = key_match.group(1)
531 definition = definition.replace(key_match.group(0),
"")
533 tag_match = re.search(
r"<tag>(.*)</tag>", definition)
535 result[
"tag"] = tag_match.group(1)
536 definition = definition.replace(tag_match.group(0),
"")
538 cache_match = re.search(
r"<cache>(.*)</cache>", definition)
540 definition = definition.replace(cache_match.group(0),
"")
542 noover_match = re.search(
r"<noover/>", definition)
544 result[
"noover"] =
True
545 definition = definition.replace(noover_match.group(0),
"")
547 result[
"name"] = definition.strip()
549 return json.dumps(result)
562 if __name__ ==
"__main__":