/
hyperruss
/
ku2
Обзор
Документация
Войти
/
hyperruss
/
ku2
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
visualizer.py
258 строк
8 KB
Игорь Грибунов
finish finish finish
15 ноя 2025, 10:25
15 ноя 2025, 10:25
0ce3eed
Код
Авторство
О чём код?
""" Этап 5: Визуализация Генерация диаграмм в формате D2 и ASCII-дерева Сохранение в файл PNG """ from typing import Dict, List, Optional, Set import subprocess import sys import os class D2Visualizer: """Визуализатор графа зависимостей на D2""" def __init__(self): """Инициализирует визуализатор""" self.d2_content = [] def generate_d2_diagram(self, graph: Dict[str, List[str]], title: str = "") -> str: """ Генерирует диаграмму в формате D2 Args: graph: Граф зависимостей title: Заголовок диаграммы Returns: Строка с кодом D2 диаграммы """ lines = [] # Заголовок if title: lines.append(f"# {title}\n") # Стили и конфигурация # Добавляем узлы и связи visited_edges = set() for package, deps in graph.items(): # Безопасное имя узла (D2 не любит точки и особые символы) safe_package = self._sanitize_name(package) if not deps: # Узел без зависимостей lines.append(f"{safe_package}") else: for dep in deps: safe_dep = self._sanitize_name(dep) edge = f"{safe_package} -> {safe_dep}" if edge not in visited_edges: lines.append(edge) visited_edges.add(edge) lines.append("") # Финальная пустая строка return "\n".join(lines) def _sanitize_name(self, name: str) -> str: """ Преобразует имя пакета в безопасное имя для D2 Args: name: Оригинальное имя пакета Returns: Безопасное имя """ # Убираем точки и специальные символы safe = name.replace('.', '_').replace('-', '_') # Если имя начинается с цифры, добавляем префикс if safe and safe[0].isdigit(): safe = f"pkg_{safe}" return safe def save_d2_file(self, content: str, file_path: str) -> bool: """ Сохраняет D2 диаграмму в файл Args: content: Содержимое D2 файла file_path: Путь для сохранения Returns: True если успешно, False в противном случае """ try: with open(file_path, 'w', encoding='utf-8') as f: f.write(content) print(f" D2 диаграмма сохранена: {file_path}") return True except Exception as e: print(f" Ошибка при сохранении D2 файла: {e}") return False def d2_to_png(self, d2_file: str, output_png: str, theme: str = "0") -> bool: """ Конвертирует D2 файл в PNG Args: d2_file: Путь к D2 файлу output_png: Путь для сохранения PNG theme: Тема для D2 (0=default, 1=dark, etc.) Returns: True если успешно, False в противном случае """ try: # Проверяем установлена ли утилита d2 result = subprocess.run( ['d2', d2_file, output_png, '--theme', theme], capture_output=True, text=True, timeout=30 ) if result.returncode == 0: print(f" PNG диаграмма сохранена: {output_png}") return True else: print(f" Ошибка D2: {result.stderr}") return False except FileNotFoundError: print(" Утилита 'd2' не найдена") print(" Установите D2: curl -fsSL https://d2lang.com/install.sh | sh -s --") return False except subprocess.TimeoutExpired: print(" Таймаут при генерации PNG") return False except Exception as e: print(f" Ошибка при конвертации D2 в PNG: {e}") return False class ASCIITreeVisualizer: """Визуализатор графа в виде ASCII-дерева""" @staticmethod def print_tree( graph: Dict[str, List[str]], root: str, visited: Optional[Set[str]] = None, prefix: str = "", is_last: bool = True ) -> None: """ Выводит граф в виде ASCII-дерева Args: graph: Граф зависимостей root: Корневой узел visited: Множество уже посещенных узлов prefix: Префикс для отступов is_last: Является ли узел последним в списке """ if visited is None: visited = set() # Символы для дерева connector = "└── " if is_last else "├── " extension = " " if is_last else "│ " # Проверяем на цикл if root in visited: print(f"{prefix}{connector}{root} [CYCLE]") return visited.add(root) print(f"{prefix}{connector}{root}") # Получаем зависимости deps = graph.get(root, []) # Выводим зависимости for i, dep in enumerate(deps): is_last_dep = (i == len(deps) - 1) ASCIITreeVisualizer.print_tree( graph, dep, visited.copy(), prefix + extension, is_last_dep ) @staticmethod def print_simple_tree(graph: Dict[str, List[str]], root: str) -> None: """ Выводит простое ASCII-дерево Args: graph: Граф зависимостей root: Корневой узел """ print("\n" + "="*70) print(f" ASCII-ДЕРЕВО ЗАВИСИМОСТЕЙ: {root}") print("="*70 + "\n") ASCIITreeVisualizer.print_tree(graph, root) print("\n" + "="*70 + "\n") def demonstrate_stage_5(): """Функция для демонстрации Этапа 5""" print("\n" + "="*70) print(" ДЕМОНСТРАЦИЯ ЭТАПА 5: ВИЗУАЛИЗАЦИЯ") print("="*70 + "\n") # Создаем тестовый граф test_graph = { 'A': ['B', 'C'], 'B': ['D', 'E'], 'C': ['E', 'F'], 'D': ['G'], 'E': ['G'], 'F': [], 'G': [] } # Визуализатор D2 visualizer = D2Visualizer() # Генерируем D2 диаграмму print(" Генерация D2 диаграммы...") d2_content = visualizer.generate_d2_diagram( test_graph, title="Граф зависимостей (Вариант 5)" ) print("\n Содержимое D2 файла:\n") print("-" * 70) print(d2_content) print("-" * 70) # Сохраняем D2 файл d2_file = "demo_graph.d2" visualizer.save_d2_file(d2_content, d2_file) # Пытаемся конвертировать в PNG png_file = "demo_graph.png" print(f"\n Попытка генерации PNG...") visualizer.d2_to_png(d2_file, png_file) # Выводим ASCII-дерево print("\n" + "="*70) ascii_viz = ASCIITreeVisualizer() ascii_viz.print_simple_tree(test_graph, 'A') if __name__ == '__main__': demonstrate_stage_5()