import itertools import operator import re import sys def fullmatch (pattern, input): m = re.fullmatch (pattern, input) if m is None: raise ValueError (input) return m def parse_scripts (f): return list ( map (lambda m: { "line": int (m.group (2), base = 10), "text": m.group (3), "src": m.group (1), "maybe": m.group (4) != "", "script": m.group (6)}, map (lambda s: fullmatch (r"[^:]+/(js/[^:]+):(\d+):(\s*[*]?\s*((//)?)\s*\$config\['additional_javascript'\]\[\] = '([^']+)';.*)", s), map (lambda s: s.rstrip ("\n"), f)))) def parse_config (f): return list ( map (lambda m: { "line": int (m.group (1), base = 10), "text": m.group (2), "script": m.group (3)}, map (lambda s: fullmatch (r"(\d+):(\$config\['additional_javascript'\]\[\] = '([^']+)';)", s), map (lambda s: s.rstrip ("\n"), f)))) def compute_scripts (parsed, problems): groups = list (itertools.starmap ( lambda key, g: (key, list (g)), itertools.groupby (parsed, operator.itemgetter ("src")))) return { "parsed": parsed, "groups": groups, } def compute_config (parsed, problems): map = {} for e in parsed: line = e ["line" ] script = e ["script"] if script in map: problems.append ("{} {} at {} {}".format ( "duplicate config", script, map [script], line)) else: map [script] = line return { "parsed": parsed, "map": map, } def verify_depends (scripts, config, problems): map = config ["map"] for src, g in scripts ["groups"]: if src not in map: continue for e in g: if (not e ["maybe"]) and (e ["script"] not in map): problems.append ("{} from {} {} {}".format ( e ["script"], src, e ["line"], "not in config")) def verify_order (scripts, config, problems): map = config ["map"] for src, g in scripts ["groups"]: n = len (g) for p in range (n - 1): pscript = g [p] ["script"] pline = g [p] ["line" ] pmap = map.get (pscript) if pmap is None: continue for q in range (p + 1, n): qscript = g [q] ["script"] qline = g [q] ["line" ] qmap = map.get (qscript) if qmap is None: continue if pmap > qmap: problems.append ("{} has {}@{} {}@{} but {} has {}@{} {}@{}".format ( src, pscript, pline, qscript, qline, "config", pscript, pmap, qscript, qmap)) def verify_possible (scripts, problems): precede = {} for src, g in scripts ["groups"]: n = len (g) for p in range (n - 1): pscript = g [p] ["script"] for q in range (p + 1, n): qscript = g [q] ["script"] qprec = precede.get (qscript) if qprec is None: precede [qscript] = {pscript} else: qprec.add (pscript) left = {src for src, g in scripts ["groups"]}.union (* precede.values ()) def precount (key): prec = precede.get (key) return 0 if prec is None else len (prec) while left: prune = {x for x in left if precount (x) == 0} if not prune: problems.append ("{} {}".format ( "unsatisfiable", precede)) break left -= prune for v in precede.values (): v -= prune def work (scripts, config): with open (scripts, "r") as f: parsed_scripts = parse_scripts (f) with open (config, "r") as f: parsed_config = parse_config (f) problems = [] computed_scripts = compute_scripts (parsed_scripts, problems) computed_config = compute_config (parsed_config, problems) verify_depends (computed_scripts, computed_config, problems) verify_order (computed_scripts, computed_config, problems) verify_possible (computed_scripts, problems) print ("\n".join (problems)) print ("problems:", len (problems)) def main (): argv = sys.argv scripts = argv [1] config = argv [2] work (scripts, config) if __name__ == "__main__": main ()