Mit KI überarbeitete Version

This commit is contained in:
Albert
2025-10-31 23:55:23 +01:00
parent 0c5f1d740d
commit b139ec03ce

View File

@@ -1,14 +1,32 @@
"""
repovizcheck.py
Kurzbeschreibung:
Dieses Skript vergleicht RepoViz-Metadaten (Annotationen in Kornshell-Skripten: "#@modul:", "#@quelle:", "#@ziel:")
mit tatsächlich im Skript verwendeten Tabellen (Erkennung über $SCHEMA.<TABELLE> oder ${SCHEMA}<TABELLE>). Es
ermöglicht einen schnellen Abgleich, welche Tabellen in RepoViz dokumentiert sind und welche im Code auftauchen.
Aufruf:
python repovizcheck.py <skript.ksh>
Wichtige Hinweise:
- Akzeptierte Dateiendung: .ksh (case-insensitive).
- Kodierung: versucht zunächst UTF-8, bei Fehlern Fallback auf ISO-8859-1.
- Tabellennamen werden für Vergleiche normalisiert (Uppercase); Regex-Parsing wurde robustifiziert.
"""
from __future__ import print_function from __future__ import print_function
import sys import sys
import re import re
import os.path as p import os.path as p
import argparse
print(" ____ __ ___ ____ _ _ ") print(r" ____ __ ___ ____ _ _ ")
print("| _ \ ___ _ __ ___ \ \ / (_)____ / ___| |__ ___ ___| | _____ _ __ ") print(r"| _ \ ___ _ __ ___ \ \ / (_)____ / ___| |__ ___ ___| | _____ _ __ ")
print("| |_) / _ \ '_ \ / _ \ \ \ / /| |_ / | | | '_ \ / _ \/ __| |/ / _ \ '__|") print(r"| |_) / _ \ '_ \ / _ \ \ \ / /| |_ / | | | '_ \ / _ \/ __| |/ / _ \ '__|")
print("| _ < __/ |_) | (_) | \ V / | |/ / | |___| | | | __/ (__| < __/ | ") print(r"| _ < __/ |_) | (_) | \ V / | |/ / | |___| | | | __/ (__| < __/ | ")
print("|_| \_\___| .__/ \___/ \_/ |_/___| \____|_| |_|\___|\___|_|\_\___|_| ") print(r"|_| \_\___| .__/ \___/ \_/ |_/___| \____|_| |_|\___|\___|_|\_\___|_| ")
print(" |_| ") print(r" |_| ")
such_quelle = "#@quelle:" # such_quelle = "#@quelle:" #
@@ -18,59 +36,88 @@ such_modul = "#@modul:" #
allezeilen = [] # Das Skript wird zeilenwiese in diese Liste geschrieben, eine Zeile gleich ein Element der Liste allezeilen = [] # Das Skript wird zeilenwiese in diese Liste geschrieben, eine Zeile gleich ein Element der Liste
kommentarzeilen = [] # Ein Teil der nicht benoetigten Zeilen kommt in diese Liste kommentarzeilen = [] # Ein Teil der nicht benoetigten Zeilen kommt in diese Liste
codezeilen = [] # Zeilen mit Code kommen in diese Liste codezeilen = [] # Zeilen mit Code kommen in diese Liste
# Liste der gueltigen Dateiendungen (case-insensitive)
perm_file_suffix = ['.ksh'] # Liste der gueltigen Dateiendungen perm_file_suffix = ['.ksh'] # Liste der gueltigen Dateiendungen
# Aufruf ohne Parameter abfangen # Verwende argparse für robusten CLI-Aufruf und Help
try: parser = argparse.ArgumentParser(description='Check RepoViz annotations against SQL in a ksh script')
sourcefile = sys.argv[1] # Schreibe den 1.Parameter <Dateiname> in eine Variable parser.add_argument('sourcefile', help='Kornshell script to check (must end with .ksh)')
# print(sourcefile) # Ausgabe dateiname args = parser.parse_args()
except IndexError: sourcefile = args.sourcefile
print()
print("Missing Parameter: Filename")
print()
sys.exit(0)
file_suffix = p.splitext(sourcefile)[1] # Dateiendung ermitteln # Dateiendung prüfen (case-insensitive)
if file_suffix not in perm_file_suffix: # In Liste Ja/Nein file_suffix = p.splitext(sourcefile)[1].lower()
print("") # Gib eine leere Zeile aus if file_suffix not in perm_file_suffix:
print() # Gib eine leere Zeile aus
print("Only Kornshell scripts can be processed.") # Gib diese Meldung aus print("Only Kornshell scripts can be processed.") # Gib diese Meldung aus
print("") # Gib eine leere Zeile aus print()
sys.exit(0) # Beende das Programm sys.exit(1)
# Funktion zum Oeffnen einer Datei mit Pruefung # Funktion zum Oeffnen einer Datei mit Pruefung
def get(name): def get(name):
'''Funktion zum Oeffnen einer Datei mit Pruefung (prüft Encoding)'''
# Versuche zuerst UTF-8, lese einen kleinen Block zur Validierung.
try: try:
# return open(name, "r", encoding='ISO-8859-1') # oeffne die Datei zum Lesen <r> f = open(name, "r", encoding='utf-8')
return open(name, "r") # oeffne die Datei zum Lesen <r> except IOError:
# except FileNotFoundError: # Bei einem Fehler ... Python3 print("")
except IOError: # Bei einem Fehler ... Python2 print("The File", name, "doesn't exist or can't be opened!")
print("") # Gib eine leere Zeile aus print("")
print("The File ", name, "doesn't exists!") # Gib diese Meldung aus sys.exit(1)
print("") # Gib eine leere Zeile aus
sys.exit(0) # Beende das Programm try:
# Lese ein kleines Stück, um mögliche Decode-Fehler sofort zu erzeugen
f.read(2048)
f.seek(0)
return f
except UnicodeDecodeError:
# UTF-8 scheint nicht zu passen — versuche ISO-8859-1
try:
f.close()
return open(name, "r", encoding='ISO-8859-1')
except IOError:
print("")
print("The File", name, "can't be opened with fallback encoding!")
print("")
sys.exit(1)
except Exception:
# unerwarteter Fehler beim Lesen
f.close()
print("")
print("Error reading the file", name)
print("")
sys.exit(1)
return None return None
# Funktion zum Erstellen einer Tabellenliste aus den RepoViz Informationen # Funktion zum Erstellen einer Tabellenliste aus den RepoViz Informationen
def erstelle_liste(datei, typ): # 2 Parameter def erstelle_liste(datei, typ): # 2 Parameter
laenge = len(typ) # bestimme die Laenge '''Erstelle eine Liste aus der übergebenen Datei für den Typ der übergeben wurde'''
tabellenliste = [] # erstelle leere Liste laenge = len(typ)
fobj_in = get(datei) # uebergebe den Dateinamen an die Funktion tabellenliste = []
for line in fobj_in: # gehe zeilenwiese durch die Quelldatei fobj_in = get(datei)
if line[0:laenge] == typ: # Erstelle Liste mit den Quelltabellen for line in fobj_in:
zeile = (line[laenge:]) # entferne nur führende Leerzeichen vor der Prüfung
if zeile[-2] == ",": # ist ein Komma am Zeilenende content = line.lstrip()
zeile = zeile[0:-2] # entferne Komma am Zeilenende if content.startswith(typ):
liste = zeile.split(",") # Teile die Zeile am Komma auf und speichere die Elemente in einer Liste zeile = content[len(typ):].strip()
for item in liste: # gehe durch die Liste # entferne ein mögliches abschließendes Komma
tabellenliste.append(item.strip()) # entferne Leerzeichen und fuege die Elemente der Liste <tabellenliste> hinzu if zeile.endswith(','):
fobj_in.close() # schliesse Quelldatei zeile = zeile[:-1]
return(tabellenliste) # gebe die fertige Liste <tabellenliste> zurueck liste = [it.strip() for it in zeile.split(',') if it.strip()]
for item in liste:
# normalisiere auf Großbuchstaben für spätere Vergleiche
tabellenliste.append(item.strip().upper())
fobj_in.close()
return tabellenliste
def trennzeile(typ): # Funktion zum Ausgeben einer 80 Zeichen breiten Trennzeile. def trennzeile(typ): # Funktion zum Ausgeben einer 80 Zeichen breiten Trennzeile.
return(print(typ * 80)) # Das Trennzeichen ist variabel und wird der Funktion als Parameter uebergeben. '''
Erstellt eine 80 zeichenbreite Zeile mit dem übergebenen Zeichen
'''
print(typ * 80) # Das Trennzeichen ist variabel und wird der Funktion als Parameter uebergeben.
modulliste = erstelle_liste(sourcefile, such_modul) # Erstelle Liste mit den Modulen modulliste = erstelle_liste(sourcefile, such_modul) # Erstelle Liste mit den Modulen
@@ -79,26 +126,25 @@ zielliste = erstelle_liste(sourcefile, such_ziel) # Erstelle Liste mit den
datei = get(sourcefile) # Oeffnen der Datei mit einer Funktion, die auch prueft ob die Datei existiert datei = get(sourcefile) # Oeffnen der Datei mit einer Funktion, die auch prueft ob die Datei existiert
allezeilen = datei.readlines() # Lesen aller Zeilen in eine Liste allezeilen = datei.readlines() # Lesen aller Zeilen in eine Liste
datei.close() # Schliessen der Datei datei.close()
# Versuch die Kommentarzeilen loszuwerden # Versuch die Kommentarzeilen loszuwerden
for zeile in allezeilen: # gehe durch die <allezeilen> for zeile in allezeilen:
if zeile.strip()[0:8] == '#@modul:': # Damit diese Zeile auch geprueft wird !! s = zeile.strip()
codezeilen.append(zeile.strip()) # entferne Leerzeichen und fuege die Elemente der Liste <codezeilen> hinzu if not s:
# Alle Zeilen die nicht betrachtet werden, kommen in diese Liste. Mir ist bis jetzt keine andere Loesung eingefallen. # leere Zeilen überspringen
if zeile.strip()[0:1] == '#' \ continue
or zeile.strip()[0:2] == '--' \ if s.startswith('#@modul:'):
or zeile.strip()[0:4] == 'j000' \ codezeilen.append(s)
or zeile.strip()[0:7] == 'SQLFILE' \ continue
or zeile.strip()[0:8] == 'fmeldung' \ # Gruppiere typische Kommentar-/Meta-Zeilen (case-insensitive)
or zeile.strip()[0:5] == 'ALTER' \ up = s.upper()
or zeile.strip()[0:10] == 'f_truncate' \ if s.startswith('#') or s.startswith('--') or up.startswith('J000') or up.startswith('SQLFILE') \
or zeile.strip()[0:2] == 'fi' \ or up.startswith('FMELDUNG') or up.startswith('ALTER') or up.startswith('F_TRUNCATE') \
or zeile.strip()[0:2] == 'if' \ or up == 'FI' or up == 'IF' or up.startswith('F_RUNSTATS'):
or zeile.strip()[0:10] == 'f_runstats': kommentarzeilen.append(s)
kommentarzeilen.append(zeile.strip()) # entferne Leerzeichen und fuege die Elemente der Liste <kommentarzeilen> hinzu else:
else: # Die Codezeilen und ein paar mehr. codezeilen.append(s)
codezeilen.append(zeile.strip()) # entferne Leerzeichen und fuege die Elemente der Liste <codezeilen> hinzu
trennzeile("+") trennzeile("+")
@@ -116,13 +162,12 @@ print(ausgabe.format(such_ziel, zielliste)) # Ausgabe <zielliste>
trennzeile("~") trennzeile("~")
trennzeile("") trennzeile("")
print("Comparison of given script name with RepoVizInformation") print("Comparison of given script name with RepoVizInformation")
for item in modulliste: # Vergleiche nur einmal, normalisiere auf Großbuchstaben
trennzeile("") script_basename = p.basename(sourcefile).upper()
trennzeile("~") if script_basename in [m.strip().upper() for m in modulliste]:
if item == p.basename(sourcefile): print("Script", p.basename(sourcefile), "is in the RepoViz information!")
print("Script", p.basename(sourcefile), "is in the RepoViz information!") else:
else: print("Script", p.basename(sourcefile), "is missing in the RepoViz information!")
print("Script", p.basename(sourcefile), "is missing in the RepoViz information!")
# Suche Objekte anhand der RepoVizInformationen # Suche Objekte anhand der RepoVizInformationen
trennzeile("~") trennzeile("~")
@@ -131,17 +176,22 @@ print("")
print("Create a list of tables from the existing code: list <allezeilen>") print("Create a list of tables from the existing code: list <allezeilen>")
# Der Code steht schon in der Liste <codezeilen> # Der Code steht schon in der Liste <codezeilen>
neue_liste = [] neue_liste = []
# for line in codezeilen: # verbessertes Regex-Parsing: capture-Gruppe für Tabellennamen, compile einmal
regex = re.compile(r"(?:\$SCHEMA\.|\${SCHEMA}\.)\s*([A-Z0-9_]+)", re.IGNORECASE)
# nutze Set für eindeutige Sammlung
neue_set = set()
for line in allezeilen: for line in allezeilen:
regex = re.compile(r"(?:\$SCHEMA\.|\${SCHEMA}\.).*?\s") for match in regex.findall(line):
sql = re.findall(regex, line.upper()) # Suche nach $SCHEMA. oder ${SCHEMA}. tabname = match.strip()
if len(sql) != 0: if not tabname:
tabname = sql[0].split(".")[1].strip() # der Name wird ... continue
if tabname.strip()[0:1] != '$': # ignoriere Variablen oder temporäre Tabellen
tabname = re.sub("\\W", "", tabname) if tabname.startswith('$'):
if tabname not in neue_liste and tabname[0:4] != "TMP_": # Es werden nur Tabellen hinzugefuegt die noch nicht in der continue
# Ergebnisliste sind und nicht mit TMP_ beginnen. tabname_norm = re.sub(r"[^A-Z0-9_]", "", tabname.upper())
neue_liste.append(tabname.strip()) if tabname_norm and not tabname_norm.startswith('TMP_'):
neue_set.add(tabname_norm)
neue_liste = sorted(neue_set)
print("The list contains:", len(neue_liste), "entries") print("The list contains:", len(neue_liste), "entries")
trennzeile("~") trennzeile("~")
@@ -155,7 +205,8 @@ trennzeile("~")
print("Are the tables of the list", such_quelle, "included in the SQL?") print("Are the tables of the list", such_quelle, "included in the SQL?")
trennzeile("~") trennzeile("~")
for item in quelleliste: for item in quelleliste:
if item in neue_liste: # quelleliste wurde in erstelle_liste bereits normalisiert (upper)
if item.upper() in neue_liste:
ausgabe = "{:12}{:40}{:20}" ausgabe = "{:12}{:40}{:20}"
print(ausgabe.format("The Table", item, "is available")) print(ausgabe.format("The Table", item, "is available"))
else: else:
@@ -168,7 +219,7 @@ trennzeile("~")
print("Are the tables of the list", such_ziel, "included in the SQL?") print("Are the tables of the list", such_ziel, "included in the SQL?")
trennzeile("~") trennzeile("~")
for item in zielliste: for item in zielliste:
if item in neue_liste: if item.upper() in neue_liste:
ausgabe = "{:12}{:40}{:20}" ausgabe = "{:12}{:40}{:20}"
print(ausgabe.format("The Table", item, "is available")) print(ausgabe.format("The Table", item, "is available"))
else: else:
@@ -177,12 +228,7 @@ for item in zielliste:
trennzeile("~") trennzeile("~")
print("Create unique sorted list from ", such_quelle, "and", such_ziel) print("Create unique sorted list from ", such_quelle, "and", such_ziel)
q_z_liste = [] q_z_liste = sorted({t.upper() for t in (quelleliste + zielliste)})
basis_liste = quelleliste + zielliste
for tabname in basis_liste:
if tabname not in q_z_liste:
q_z_liste.append(tabname)
q_z_liste.sort()
trennzeile("~") trennzeile("~")
@@ -202,9 +248,9 @@ for item in neue_liste:
print(ausgabe.format("The Table", item, "is not available")) print(ausgabe.format("The Table", item, "is not available"))
trennzeile("+") trennzeile("+")
print(" _____ _ _____ _ ") print(r" _____ _ _____ _ ")
print("|_ _| |__ ___ | ____|_ __ __| |") print(r"|_ _| |__ ___ | ____|_ __ __| |")
print(" | | | '_ \ / _ \ | _| | '_ \ / _` |") print(r" | | | '_ \ / _ \ | _| | '_ \ / _` |")
print(" | | | | | | __/ | |___| | | | (_| |") print(r" | | | | | | __/ | |___| | | | (_| |")
print(" |_| |_| |_|\___| |_____|_| |_|\__,_|") print(r" |_| |_| |_|\___| |_____|_| |_|\__,_|")
print("") print("")