#!/usr/bin/env python3 """Inspect flow_data references without executing or importing any block.""" import argparse import json import sys from pathlib import Path def check_graph(data): nodes = data.get("flow_data") if isinstance(data, dict) else data errors, warnings, by_id = [], [], {} if not isinstance(nodes, list): return {"status": "invalid", "errors": ["ожидается массив flow_data"], "warnings": [], "nodes": 0, "links": 0} for index, node in enumerate(nodes): if not isinstance(node, dict): errors.append(f"узел {index}: ожидается объект") continue identity = node.get("id") if not isinstance(identity, str) or not identity: errors.append(f"узел {index}: требуется строковый id") continue if identity in by_id: errors.append(identity + ": повторяющийся id") by_id[identity] = node if not isinstance(node.get("type"), str) or not node["type"]: errors.append(identity + ": требуется строковый type") elif "<" in node["type"]: warnings.append(identity + ": учебный type; замените блоком из палитры своей установки") tabs = {key for key, value in by_id.items() if value.get("type") == "tab"} links = 0 for identity, node in by_id.items(): if node.get("type") == "tab": continue if node.get("type") == "subflow": warnings.append(identity + ": subflow не проверяется этим примером") continue # Config nodes may omit z and wires. if "z" in node and (not isinstance(node["z"], str) or node["z"] not in tabs): errors.append(identity + ": z не указывает на вкладку tab; subflow требует отдельной проверки") wires = node.get("wires", []) if not isinstance(wires, list): errors.append(identity + ": wires должен быть string[][]") continue for output in wires: if not isinstance(output, list): errors.append(identity + ": каждый выход wires должен быть массивом") continue for target in output: if not isinstance(target, str) or target not in by_id: errors.append(identity + ": wires ссылается на отсутствующий узел " + str(target)) continue links += 1 if by_id[target].get("type") == "tab": errors.append(identity + ": wires ссылается на вкладку вместо блока") if node.get("z") and by_id[target].get("z") != node.get("z"): errors.append(identity + ": wires пересекает вкладки; проверьте проектную структуру") warnings.append("Не проверены палитра, flow_settings, входы/выходы блоков, модели и исполнение сценария") return {"status": "invalid" if errors else "structure_checked", "errors": errors, "warnings": warnings, "nodes": len(by_id), "links": links} def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("file") args = parser.parse_args() try: result = check_graph(json.loads(Path(args.file).read_text(encoding="utf-8"))) except (OSError, ValueError) as error: print("Ошибка: " + str(error), file=sys.stderr) return 1 print(json.dumps(result, ensure_ascii=False, indent=2)) return 1 if result["errors"] else 0 if __name__ == "__main__": sys.exit(main())