""" 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("")