Dedublizierung gefixt

- lange existierender Updatecode wurde in jeder 2. Version angezeigt
- UPDATEANALYSE.md ergänzt.
This commit is contained in:
2026-08-20 13:27:23 +02:00
parent 5d227d2f61
commit b549063ff6
138 changed files with 43143 additions and 1472 deletions
+139 -105
View File
@@ -2,35 +2,137 @@
import json
from pathlib import Path
from collections import OrderedDict
DATA = Path("data")
REPORTS = Path("reports")
def versions():
return sorted(
[d for d in DATA.iterdir() if d.is_dir()],
key=lambda p: [int(x) for x in p.name.split(".")]
)
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 = versions()[0]
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.versionen = {}
self.classes = {}
self.tables = {}
self.operations = []
def read_data(self):
for version in versions():
for version in VERSION_DIRS:
vers = version.name
self.versionen[vers] = {}
infile = version / f"{self.name}.json"
if not infile.exists():
@@ -40,39 +142,19 @@ class Module(object):
for f in data["files"]:
f['version'] = vers
file = f['file']
classe = f.get('class', 'ohne')
method = f.get('method', 'ohne')
if classe not in self.versionen[vers]:
self.versionen[vers][classe] = []
self.versionen[vers][classe].append(f)
try:
if vers not in self.classes[classe]:
self.classes[classe][vers] = []
except KeyError:
self.classes[classe] = {}
self.classes[classe][vers] = []
self.classes[classe][vers].append(f)
for o in f['operations']:
o['class'] = classe
o['method'] = method
o['file'] = file
o['version'] = vers
self.operations.append(o)
def operation_key(self, operation):
# 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 (
operation.get("phase"),
operation.get("operation"),
operation.get("object"),
tuple(operation.get("args", [])),
operation.get("condition"),
)
def block_key(self, operations):
return tuple(self.operation_key(op) for op in 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):
@@ -80,78 +162,30 @@ class Module(object):
with outfile.open("w", encoding="utf8") as out:
out.write(f"# {self.name}\n\n")
out.write(f"# modul {self.name}\n\n")
# iterate all classes
for c in sorted(self.classes):
classe = self.classes[c]
# iterate all tables
for t in sorted(self.tables):
table = self.tables[t]
out.write(f"## Klasse {c}\n\n")
out.write(f"\n## Tabelle {t}\n")
last_blocks = None
# iterate all Versions
for k in sorted(classe.keys()):
v = classe.get(k, None)
if not v:
for tName, ops in sorted(table.versions.items()):
if not ops:
continue
if last_blocks == None:
last_blocks = len(v) * [None,]
new_version = k
new_version = tName
# iterate all files / occureces
for i, m in enumerate(v):
# repeating?
key = self.block_key(m["operations"])
old_block = last_blocks[i]
if key == old_block:
continue
elif old_block and key[:len(old_block)] == old_block:
m["operations"] = m['operations'][len(old_block):]
last_blocks[i] = key
for op in ops.values():
if new_version:
out.write("\n")
out.write(f"### {new_version}\n\n")
new_version = None
out.write(f"file: {m['file']} : {m['line']}\n")
for op in m["operations"]:
out.write(
f"- **{op['phase']}** "
f"`{op['operation']}`\n"
)
if "condition" in op:
out.write(
f" - condition: `{op['condition']}`\n"
)
if op["object"]:
out.write(
f" - object: `{op['object']}`\n"
)
if op["args"]:
out.write(
f" - args: `{', '.join(op['args'])}`\n"
)
if op["kwargs"]:
out.write(" - kwargs:\n")
for k, v in op["kwargs"].items():
out.write(
f" - {k}: `{v}`\n"
)
out.write("\n")
out.write(op.toText())
out.write("\n")