#!/usr/bin/env python3 """Controlli statici sulla QML di harbour-henflow. La UI Silica non e' eseguibile qui (serve l'SDK Sailfish), ma quasi tutti gli errori che si vedono sul device sono verificabili PRIMA di consegnare: * chiavi di traduzione inesistenti (schermata con la chiave grezza a video) * funzioni del motore JS chiamate ma non esistenti (TypeError a runtime) * pagine aperte con pageStack ma senza file (pagina che non si apre) * componenti Silica/QtQuick inesistenti (\"Impossibile caricare la pagina\") * parentesi non bilanciate (l'intero file QML non compila) Uso: python3 tools/check_qml.py # esce 1 se trova problemi """ import json import os import re import sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) QML_DIR = os.path.join(ROOT, "qml") JS_DIR = os.path.join(QML_DIR, "js") # Componenti disponibili: Silica 1.0 (elenco ufficiale) + QtQuick/JS di base. SILICA = """ AddAnimation ApplicationWindow BackgroundItem BusyIndicator BusyLabel Button ButtonLayout Clipboard ColorPicker ColorPickerDialog ColorPickerPage ColumnView ComboBox ContextMenu Cover CoverAction CoverActionList CoverBackground CoverPlaceholder DatePicker DatePickerDialog DetailItem Dialog DialogHeader DockedPanel Drawer EnterKey FadeAnimation FadeAnimator FirstTimeUseCounter GridItem HighlightImage HorizontalScrollDecorator Icon IconButton IconTextSwitch InteractionHintLabel Keypad Label LinkedLabel ListItem MenuItem MenuLabel Notices OpacityRampEffect Page PageBusyIndicator PagedView PageHeader PageStack Palette PasswordField ProgressBar PullDownMenu PushUpMenu Remorse RemorseItem RemorsePopup RemoveAnimation Screen ScrollDecorator SearchField SectionHeader Separator SilicaControl SilicaFlickable SilicaGridView SilicaItem SilicaListView Slider SlideshowView StandardPaths Switch TapInteractionHint TextArea TextField TextSwitch Theme TimePicker TimePickerDialog TouchBlocker TouchInteractionHint ValueButton VerticalScrollDecorator ViewPlaceholder """.split() QTQUICK = """ AnchorChanges AnchorAnimation Behavior Binding BorderImage Canvas Column Connections Component DoubleValidator Easing Enumerators Flipable Flow FontLoader FontMetrics Grid Image IntValidator Item ItemGradient KeyNavigation Keys LayoutMirroring ListElement ListModel ListView Loader MouseArea NumberAnimation NumberAnimationOnRotation ParallelAnimation ParentAnimation ParentChange Path PathAnimation PauseAnimation PointDirection PropertyAction PropertyAnimation PropertyChanges Rectangle Repeater RotationAnimation Row ScaleAnimator ScriptAction SequentialAnimation Shape SmoothedAnimation SpringAnimation State StateGroup StateChangeScript StateMachine Text TextEdit TextInput TextMetrics Timer Transition Translate VectorImage ViewTransition XAnimator YAnimator NumberAnimator OpacityAnimator RegExpValidator ScrollView SplitView StackView TabBar TabView ToolBar ToolButton GroupBox CheckBox RadioButton Slider SpinBox DialogButtonBox """.split() KNOWN = set(SILICA) | set(QTQUICK) def strip_strings(src): """Rimuove stringhe e commenti (per bilanciamento e per i controlli sui tipi).""" out = [] i, n = 0, len(src) while i < n: c = src[i] if c == "/" and i + 1 < n and src[i + 1] == "/": while i < n and src[i] != "\n": i += 1 elif c == "/" and i + 1 < n and src[i + 1] == "*": i += 2 while i + 1 < n and not (src[i] == "*" and src[i + 1] == "/"): i += 1 i += 2 elif c in "\"'": quote = c i += 1 while i < n and src[i] != quote: i += 2 if src[i] == "\\" else 1 i += 1 out.append('""') else: out.append(c) i += 1 return "".join(out) def js_symbols(path): """Simboli di primo livello esportati da un modulo JS (var e function).""" src = open(path, encoding="utf-8").read() names = set(re.findall(r"^function\s+(\w+)", src, re.M)) names |= set(re.findall(r"^var\s+(\w+)", src, re.M)) names |= set(re.findall(r"^ (\w+):\s", src, re.M)) # voci di module.exports return names def i18n_languages(path): """{lingua: insieme di chiavi} dal modulo i18n.js.""" src = open(path, encoding="utf-8").read() m = re.search(r"var HF_STRINGS = (\{.*?\n\});", src, re.S) if not m: raise SystemExit("check_qml: HF_STRINGS non trovato in i18n.js") data = json.loads(m.group(1)) return {lang: set(keys) for lang, keys in data.items()} def qml_files(): found = [] for base, _dirs, files in os.walk(QML_DIR): for f in files: if f.endswith(".qml"): found.append(os.path.join(base, f)) return sorted(found) def main(): problems = [] files = qml_files() if not files: raise SystemExit("check_qml: nessun file QML trovato") hf = js_symbols(os.path.join(JS_DIR, "henflow.js")) cat = js_symbols(os.path.join(JS_DIR, "catalog.js")) i18n = js_symbols(os.path.join(JS_DIR, "i18n.js")) langs = i18n_languages(os.path.join(JS_DIR, "i18n.js")) all_keys = set.intersection(*langs.values()) if langs else set() for path in files: rel = os.path.relpath(path, ROOT) raw = open(path, encoding="utf-8").read() src = strip_strings(raw) # 1. bilanciamento parentesi for opener, closer in (("{", "}"), ("(", ")"), ("[", "]")): if src.count(opener) != src.count(closer): problems.append("%s: %s%s non bilanciate (%d vs %d)" % (rel, opener, closer, src.count(opener), src.count(closer))) # 2. riferimenti ai moduli JS for mod, symbols, spelled in (("HF", hf, "henflow.js"), ("Catalog", cat, "catalog.js"), ("I18N", i18n, "i18n.js")): for name in sorted(set(re.findall(r"\b%s\.(\w+)" % mod, src))): if name not in symbols: problems.append("%s: %s.%s non esiste in %s" % (rel, mod, name, spelled)) # 3. chiavi di traduzione usate for key in sorted(set(re.findall(r'\b(?:I18N\.t|tr)\(\s*"([A-Za-z0-9_]+)"', raw))): if key not in all_keys: problems.append("%s: chiave di traduzione mancante: %s" % (rel, key)) # 4. pagine aperte con pageStack for target in sorted(set(re.findall(r'Qt\.resolvedUrl\(\s*"([^"]+\.qml)"', raw))): if not os.path.exists(os.path.join(os.path.dirname(path), target)): problems.append("%s: pagina inesistente: %s" % (rel, target)) # 5. componenti sconosciuti (Toast & co.) for name in sorted(set(re.findall(r"^\s*([A-Z][A-Za-z0-9_]*)\s*\{", src, re.M))): if name not in KNOWN: problems.append("%s: componente non riconosciuto: %s" % (rel, name)) # 6. le chiavi devono esistere in TUTTE le lingue for lang, keys in sorted(langs.items()): missing = all_keys - keys if missing: problems.append("i18n.js: chiavi assenti in '%s': %s" % (lang, ", ".join(sorted(missing)))) print("check_qml: %d file QML, %d chiavi di traduzione, %d lingue" % (len(files), len(all_keys), len(langs))) if problems: print("\nPROBLEMI (%d):" % len(problems)) for p in problems: print(" - " + p) return 1 print("nessun problema trovato") return 0 if __name__ == "__main__": sys.exit(main())