diff --git a/repovizcheck.py b/repovizcheck.py index c7a0248..6b8342d 100644 --- a/repovizcheck.py +++ b/repovizcheck.py @@ -1,256 +1,256 @@ -""" -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. oder ${SCHEMA}). Es -ermöglicht einen schnellen Abgleich, welche Tabellen in RepoViz dokumentiert sind und welche im Code auftauchen. - -Aufruf: - python repovizcheck.py - -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 -import sys -import re -import os.path as p -import argparse - -print(r" ____ __ ___ ____ _ _ ") -print(r"| _ \ ___ _ __ ___ \ \ / (_)____ / ___| |__ ___ ___| | _____ _ __ ") -print(r"| |_) / _ \ '_ \ / _ \ \ \ / /| |_ / | | | '_ \ / _ \/ __| |/ / _ \ '__|") -print(r"| _ < __/ |_) | (_) | \ V / | |/ / | |___| | | | __/ (__| < __/ | ") -print(r"|_| \_\___| .__/ \___/ \_/ |_/___| \____|_| |_|\___|\___|_|\_\___|_| ") -print(r" |_| ") - - -such_quelle = "#@quelle:" # -such_ziel = "#@ziel:" # -such_modul = "#@modul:" # - -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 -codezeilen = [] # Zeilen mit Code kommen in diese Liste -# Liste der gueltigen Dateiendungen (case-insensitive) -perm_file_suffix = ['.ksh'] # Liste der gueltigen Dateiendungen - -# Verwende argparse für robusten CLI-Aufruf und Help -parser = argparse.ArgumentParser(description='Check RepoViz annotations against SQL in a ksh script') -parser.add_argument('sourcefile', help='Kornshell script to check (must end with .ksh)') -args = parser.parse_args() -sourcefile = args.sourcefile - -# Dateiendung prüfen (case-insensitive) -file_suffix = p.splitext(sourcefile)[1].lower() -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() - sys.exit(1) - - -# Funktion zum Oeffnen einer Datei mit Pruefung -def get(name): - '''Funktion zum Oeffnen einer Datei mit Pruefung (prüft Encoding)''' - # Versuche zuerst UTF-8, lese einen kleinen Block zur Validierung. - try: - f = open(name, "r", encoding='utf-8') - except IOError: - print("") - print("The File", name, "doesn't exist or can't be opened!") - print("") - sys.exit(1) - - 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 - - -# Funktion zum Erstellen einer Tabellenliste aus den RepoViz Informationen -def erstelle_liste(datei, typ): # 2 Parameter - '''Erstelle eine Liste aus der übergebenen Datei für den Typ der übergeben wurde''' - laenge = len(typ) - tabellenliste = [] - fobj_in = get(datei) - for line in fobj_in: - # entferne nur führende Leerzeichen vor der Prüfung - content = line.lstrip() - if content.startswith(typ): - zeile = content[len(typ):].strip() - # entferne ein mögliches abschließendes Komma - if zeile.endswith(','): - zeile = zeile[:-1] - 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. - ''' - 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 -quelleliste = erstelle_liste(sourcefile, such_quelle) # Erstelle Liste mit den Quellen -zielliste = erstelle_liste(sourcefile, such_ziel) # Erstelle Liste mit den Zielen - -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 -datei.close() - -# Versuch die Kommentarzeilen loszuwerden -for zeile in allezeilen: - s = zeile.strip() - if not s: - # leere Zeilen überspringen - continue - if s.startswith('#@modul:'): - codezeilen.append(s) - continue - # Gruppiere typische Kommentar-/Meta-Zeilen (case-insensitive) - up = s.upper() - if s.startswith('#') or s.startswith('--') or up.startswith('J000') or up.startswith('SQLFILE') \ - or up.startswith('FMELDUNG') or up.startswith('ALTER') or up.startswith('F_TRUNCATE') \ - or up == 'FI' or up == 'IF' or up.startswith('F_RUNSTATS'): - kommentarzeilen.append(s) - else: - codezeilen.append(s) - - -trennzeile("+") -print("Sorted output of RepoViz information.") -print("") -ausgabe = "{:10}{:}" # Definition AusgabeFormat 1.Feld begrenzt auf 10 Zeichen, das 2.Feld unbegrenzt -quelleliste.sort() # Sortiere Liste -zielliste.sort() # Sortiere Liste -print(ausgabe.format(such_modul, modulliste)) # Ausgabe -print(ausgabe.format(such_quelle, quelleliste)) # Ausgabe -print(ausgabe.format(such_ziel, zielliste)) # Ausgabe - -# Vergleiche sourcefile mit modulliste hier einfuegen -# Eventuell anpassen, es steht nur der Skriptname in den RepoVizInfos -trennzeile("~") -trennzeile("") -print("Comparison of given script name with RepoVizInformation") -# Vergleiche nur einmal, normalisiere auf Großbuchstaben -script_basename = p.basename(sourcefile).upper() -if script_basename in [m.strip().upper() for m in modulliste]: - print("Script", p.basename(sourcefile), "is in the RepoViz information!") -else: - print("Script", p.basename(sourcefile), "is missing in the RepoViz information!") - -# Suche Objekte anhand der RepoVizInformationen -trennzeile("~") - -print("") -print("Create a list of tables from the existing code: list ") -# Der Code steht schon in der Liste -neue_liste = [] -# 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 match in regex.findall(line): - tabname = match.strip() - if not tabname: - continue - # ignoriere Variablen oder temporäre Tabellen - if tabname.startswith('$'): - continue - tabname_norm = re.sub(r"[^A-Z0-9_]", "", tabname.upper()) - 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") -trennzeile("~") - - -# Vergleich repo -> SQL -# 2 Teile -# Quelle - quelleliste -# nehme Liste "quelleliste" und suche damit in Liste "neue_liste" -# trennzeile("#") -print("Are the tables of the list", such_quelle, "included in the SQL?") -trennzeile("~") -for item in quelleliste: - # quelleliste wurde in erstelle_liste bereits normalisiert (upper) - if item.upper() in neue_liste: - ausgabe = "{:12}{:40}{:20}" - print(ausgabe.format("The Table", item, "is available")) - else: - ausgabe = "{:12}{:40}{:20}" - print(ausgabe.format("The Table", item, "is not available")) - -# Quelle - zielliste -# nehme Liste "zielliste" und suche damit in Liste "neue_liste" -trennzeile("~") -print("Are the tables of the list", such_ziel, "included in the SQL?") -trennzeile("~") -for item in zielliste: - if item.upper() in neue_liste: - ausgabe = "{:12}{:40}{:20}" - print(ausgabe.format("The Table", item, "is available")) - else: - ausgabe = "{:12}{:40}{:20}" - print(ausgabe.format("The Table", item, "is not available")) - -trennzeile("~") -print("Create unique sorted list from ", such_quelle, "and", such_ziel) -q_z_liste = sorted({t.upper() for t in (quelleliste + zielliste)}) - -trennzeile("~") - -# Vergleich SQL -> repo -# Quelle - neue_liste -# nehme Liste "quelleliste" und suche damit in Liste "neue_liste" -trennzeile("~") -print("Are the tables from the SQL included in the RepoViz information?") -print("Note: I've merged <#@quelle> and <#@ziel> into one list ") -trennzeile("~") -for item in neue_liste: - if item in q_z_liste: - ausgabe = "{:12}{:40}{:20}" - print(ausgabe.format("The Table", item, "is available")) - else: - ausgabe = "{:12}{:40}{:20}" - print(ausgabe.format("The Table", item, "is not available")) - -trennzeile("+") -print(r" _____ _ _____ _ ") -print(r"|_ _| |__ ___ | ____|_ __ __| |") -print(r" | | | '_ \ / _ \ | _| | '_ \ / _` |") -print(r" | | | | | | __/ | |___| | | | (_| |") -print(r" |_| |_| |_|\___| |_____|_| |_|\__,_|") -print("") +""" +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. oder ${SCHEMA}). Es +ermöglicht einen schnellen Abgleich, welche Tabellen in RepoViz dokumentiert sind und welche im Code auftauchen. + +Aufruf: + python repovizcheck.py + +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 +import sys +import re +import os.path as p +import argparse + +print(r" ____ __ ___ ____ _ _ ") +print(r"| _ \ ___ _ __ ___ \ \ / (_)____ / ___| |__ ___ ___| | _____ _ __ ") +print(r"| |_) / _ \ '_ \ / _ \ \ \ / /| |_ / | | | '_ \ / _ \/ __| |/ / _ \ '__|") +print(r"| _ < __/ |_) | (_) | \ V / | |/ / | |___| | | | __/ (__| < __/ | ") +print(r"|_| \_\___| .__/ \___/ \_/ |_/___| \____|_| |_|\___|\___|_|\_\___|_| ") +print(r" |_| ") + + +such_quelle = "#@quelle:" # +such_ziel = "#@ziel:" # +such_modul = "#@modul:" # + +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 +codezeilen = [] # Zeilen mit Code kommen in diese Liste +# Liste der gueltigen Dateiendungen (case-insensitive) +perm_file_suffix = ['.ksh'] # Liste der gueltigen Dateiendungen + +# Verwende argparse für robusten CLI-Aufruf und Help +parser = argparse.ArgumentParser(description='Check RepoViz annotations against SQL in a ksh script') +parser.add_argument('sourcefile', help='Kornshell script to check (must end with .ksh)') +args = parser.parse_args() +sourcefile = args.sourcefile + +# Dateiendung prüfen (case-insensitive) +file_suffix = p.splitext(sourcefile)[1].lower() +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() + sys.exit(1) + + +# Funktion zum Oeffnen einer Datei mit Pruefung +def get(name): + '''Funktion zum Oeffnen einer Datei mit Pruefung (prüft Encoding)''' + # Versuche zuerst UTF-8, lese die gesamte Datei zur Validierung. + try: + f = open(name, "r", encoding='utf-8') + f.read() # komplette Datei lesen, um Decode-Fehler sicher zu erkennen + f.seek(0) + return f + except UnicodeDecodeError: + # UTF-8 passt nicht — 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 IOError: + print("") + print("The File", name, "doesn't exist or can't be opened!") + print("") + sys.exit(1) + except Exception: + # unerwarteter Fehler beim Lesen + try: + f.close() + except Exception: + pass + print("") + print("Error reading the file", name) + print("") + sys.exit(1) + return None + + +# Funktion zum Erstellen einer Tabellenliste aus den RepoViz Informationen +def erstelle_liste(datei, typ): # 2 Parameter + '''Erstelle eine Liste aus der übergebenen Datei für den Typ der übergeben wurde''' + laenge = len(typ) + tabellenliste = [] + fobj_in = get(datei) + for line in fobj_in: + # entferne nur führende Leerzeichen vor der Prüfung + content = line.lstrip() + if content.startswith(typ): + zeile = content[len(typ):].strip() + # entferne ein mögliches abschließendes Komma + if zeile.endswith(','): + zeile = zeile[:-1] + 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. + ''' + 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 +quelleliste = erstelle_liste(sourcefile, such_quelle) # Erstelle Liste mit den Quellen +zielliste = erstelle_liste(sourcefile, such_ziel) # Erstelle Liste mit den Zielen + +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 +datei.close() + +# Versuch die Kommentarzeilen loszuwerden +for zeile in allezeilen: + s = zeile.strip() + if not s: + # leere Zeilen überspringen + continue + if s.startswith('#@modul:'): + codezeilen.append(s) + continue + # Gruppiere typische Kommentar-/Meta-Zeilen (case-insensitive) + up = s.upper() + if s.startswith('#') or s.startswith('--') or up.startswith('J000') or up.startswith('SQLFILE') \ + or up.startswith('FMELDUNG') or up.startswith('ALTER') or up.startswith('F_TRUNCATE') \ + or up == 'FI' or up == 'IF' or up.startswith('F_RUNSTATS'): + kommentarzeilen.append(s) + else: + codezeilen.append(s) + + +trennzeile("+") +print("Sorted output of RepoViz information.") +print("") +ausgabe = "{:10}{:}" # Definition AusgabeFormat 1.Feld begrenzt auf 10 Zeichen, das 2.Feld unbegrenzt +quelleliste.sort() # Sortiere Liste +zielliste.sort() # Sortiere Liste +print(ausgabe.format(such_modul, modulliste)) # Ausgabe +print(ausgabe.format(such_quelle, quelleliste)) # Ausgabe +print(ausgabe.format(such_ziel, zielliste)) # Ausgabe + +# Vergleiche sourcefile mit modulliste hier einfuegen +# Eventuell anpassen, es steht nur der Skriptname in den RepoVizInfos +trennzeile("~") +trennzeile("") +print("Comparison of given script name with RepoVizInformation") +# Vergleiche nur einmal, normalisiere auf Großbuchstaben +script_basename = p.basename(sourcefile).upper() +if script_basename in [m.strip().upper() for m in modulliste]: + print("Script", p.basename(sourcefile), "is in the RepoViz information!") +else: + print("Script", p.basename(sourcefile), "is missing in the RepoViz information!") + +# Suche Objekte anhand der RepoVizInformationen +trennzeile("~") + +print("") +print("Create a list of tables from the existing code: list ") +# Der Code steht schon in der Liste +neue_liste = [] +# 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 match in regex.findall(line): + tabname = match.strip() + if not tabname: + continue + # ignoriere Variablen oder temporäre Tabellen + if tabname.startswith('$'): + continue + tabname_norm = re.sub(r"[^A-Z0-9_]", "", tabname.upper()) + 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") +trennzeile("~") + + +# Vergleich repo -> SQL +# 2 Teile +# Quelle - quelleliste +# nehme Liste "quelleliste" und suche damit in Liste "neue_liste" +# trennzeile("#") +print("Are the tables of the list", such_quelle, "included in the SQL?") +trennzeile("~") +for item in quelleliste: + # quelleliste wurde in erstelle_liste bereits normalisiert (upper) + if item.upper() in neue_liste: + ausgabe = "{:12}{:40}{:20}" + print(ausgabe.format("The Table", item, "is available")) + else: + ausgabe = "{:12}{:40}{:20}" + print(ausgabe.format("The Table", item, "is not available")) + +# Quelle - zielliste +# nehme Liste "zielliste" und suche damit in Liste "neue_liste" +trennzeile("~") +print("Are the tables of the list", such_ziel, "included in the SQL?") +trennzeile("~") +for item in zielliste: + if item.upper() in neue_liste: + ausgabe = "{:12}{:40}{:20}" + print(ausgabe.format("The Table", item, "is available")) + else: + ausgabe = "{:12}{:40}{:20}" + print(ausgabe.format("The Table", item, "is not available")) + +trennzeile("~") +print("Create unique sorted list from ", such_quelle, "and", such_ziel) +q_z_liste = sorted({t.upper() for t in (quelleliste + zielliste)}) + +trennzeile("~") + +# Vergleich SQL -> repo +# Quelle - neue_liste +# nehme Liste "quelleliste" und suche damit in Liste "neue_liste" +trennzeile("~") +print("Are the tables from the SQL included in the RepoViz information?") +print("Note: I've merged <#@quelle> and <#@ziel> into one list ") +trennzeile("~") +for item in neue_liste: + if item in q_z_liste: + ausgabe = "{:12}{:40}{:20}" + print(ausgabe.format("The Table", item, "is available")) + else: + ausgabe = "{:12}{:40}{:20}" + print(ausgabe.format("The Table", item, "is not available")) + +trennzeile("+") +print(r" _____ _ _____ _ ") +print(r"|_ _| |__ ___ | ____|_ __ __| |") +print(r" | | | '_ \ / _ \ | _| | '_ \ / _` |") +print(r" | | | | | | __/ | |___| | | | (_| |") +print(r" |_| |_| |_|\___| |_____|_| |_|\__,_|") +print("")