#!/usr/bin/env python3 import json from pathlib import Path from collections import OrderedDict DATA = Path("data") REPORTS = Path("reports/updateChanges") VERSION_DIRS = sorted( [d for d in DATA.iterdir() if d.is_dir()], key=lambda p: [int(x) for x in p.name.split(".")] ) VERSIONS = [v.name for v in VERSION_DIRS] def modules(): first = VERSION_DIRS[0] return sorted(f.stem for f in first.glob("*.json")) class Operation(object): def __init__(self, data, source): self.condition = None self.via = None self.unresolved = False self.__dict__.update(data) self.sources = [source,] self.tables_unknown = not self.tables if not self.tables: self.tables = ('unknown',) def toText(self): marker = "unresolved " if self.unresolved else "" text = [f"- **{self.phase}** {marker}`{self.operation}`"] if self.condition: text.append( f" - condition: `{self.condition}`") if self.object: text.append( f" - object: `{self.object}`" ) if self.via: chain = " -> ".join(f"{v['method']} (Zeile {v['line']})" for v in self.via) text.append( f" - via: {chain}" ) if self.tables_unknown: text.append(" - Tabelle nicht erkannt!") text.append( " - files:" ) for s in self.sources: text.append( f" - `{s['file']}`" ) if self.args: text.append( f" - args: `{', '.join(self.args)}`" ) if self.kwargs: text.append(" - kwargs:") for k, v in self.kwargs.items(): text.append( f" - {k}: `{v}`" ) if self.code: text.append(" - code:") text.append(f" '{self.code}'") text.append('') return '\n'.join(text) @property def key(self): # kwargs are intentionally ignored for now. # The key is only used to collapse repeated migration blocks; # it is not intended to establish semantic equality. return ( self.phase, self.operation, self.object, tuple(self.tables), tuple(self.args), self.code, ) class Table: _allTables = {} @classmethod def get(cls, name): if name not in cls._allTables: cls._allTables[name] = cls(name) return cls._allTables[name] @classmethod def addObject(cls, obj): new = False for t in obj.tables: if cls.get(t).add(obj): new = True return new def __init__(self, name): self.versions = {key: {} for key in VERSIONS} # was angezeigt wird # _current_presence: der TATSÄCHLICHE, vollständige Operationsbestand # der Version, die wir gerade verarbeiten (unabhängig davon, ob wir # sie anzeigen). _prev_presence: derselbe vollständige Bestand der # UNMITTELBAR VORHERIGEN Version. Nur dieser Vergleich (nicht ein # kumulatives "jemals gesehen") ist korrekt: taucht ein Block in # einer Zwischenversion NICHT auf (weil er dort entfernt/ersetzt # wurde) und kommt später identisch zurück, muss er wieder als neu # gelten - auch wenn exakt derselbe Code schon vor langer Zeit # existierte. self._current_version = None self._current_presence = {} self._prev_presence = {} def _advance_to(self, version): if version == self._current_version: return if self._current_version is not None: self._prev_presence = self._current_presence self._current_version = version self._current_presence = {} def add(self, obj): self._advance_to(obj.version) key = obj.key already_in_this_version = key in self._current_presence self._current_presence[key] = obj # immer im tatsächlichen Bestand vermerken if already_in_this_version: return None # exakt dieselbe Operation kam mehrfach in DIESER Version vor if key in self._prev_presence: return None # unverändert gegenüber der unmittelbar vorherigen Version self.versions[obj.version][key] = obj return obj class Module(object): def __init__(self, module): self.name = module self.tables = {} self.operations = [] def read_data(self): for version in VERSION_DIRS: vers = version.name infile = version / f"{self.name}.json" if not infile.exists(): continue data = json.loads(infile.read_text()) for f in data["files"]: f['version'] = vers for o in f['operations']: source = {'module': f['module'], 'file': f['file'], 'class': f['class'], 'method': f['method'], 'line': f['line']} op = Operation(o, source) op.version = vers if Table.addObject(op): for t in op.tables: if t not in self.tables: self.tables[t] = Table.get(t) def write_data(self): outfile = REPORTS / f"{self.name}.md" with outfile.open("w", encoding="utf8") as out: out.write(f"# modul {self.name}\n\n") # iterate all tables for t in sorted(self.tables): table = self.tables[t] out.write(f"\n## Tabelle {t}\n") # iterate all Versions for tName, ops in sorted(table.versions.items()): if not ops: continue new_version = tName # iterate all files / occureces for op in ops.values(): if new_version: out.write("\n") out.write(f"### {new_version}\n\n") new_version = None out.write(op.toText()) out.write("\n") def main(): REPORTS.mkdir(exist_ok=True) for module in modules(): print(module) mod = Module(module) mod.read_data() mod.write_data() if __name__ == "__main__": main()