arch-researcher/main.py
2026-04-27 23:44:22 +05:00

132 lines
5.0 KiB
Python

#!/usr/bin/env python3
"""
CLI — точка входа для запуска из командной строки.
Использование:
python main.py /path/to/project
python main.py /path/to/project --output my_report --formats json html
python main.py /path/to/project --ccn-warn 15 --ccn-critical 25
python main.py /path/to/project --libclang /usr/lib/llvm-14/lib/libclang.so.1
"""
import argparse
import logging
import sys
from pathlib import Path
from config import AnalyzerConfig, ThresholdConfig
from analyzer import Analyzer
def build_arg_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
prog="legacy_analyzer",
description=(
"Методический инструментарий модернизации унаследованных систем.\n"
"Анализирует C++ и JavaScript проекты, выявляет архитектурные проблемы\n"
"и рекомендует стратегию модернизации."
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument(
"project_root",
help="Путь к корневой директории анализируемого проекта",
)
p.add_argument(
"--output", "-o",
default="legacy_report",
help="Директория для сохранения отчётов (по умолчанию: legacy_report)",
)
p.add_argument(
"--formats", "-f",
nargs="+",
choices=["json", "html"],
default=["json", "html"],
help="Форматы отчётов (по умолчанию: json html)",
)
p.add_argument(
"--libclang",
default=None,
help="Путь к libclang (.so/.dylib/.dll). Если не указан — используется автоопределение или regex-fallback",
)
# Пороговые значения
thresholds = p.add_argument_group("Пороговые значения метрик")
thresholds.add_argument("--ccn-warn", type=int, default=10, metavar="N",
help="CCN порог предупреждения (по умолчанию: 10)")
thresholds.add_argument("--ccn-critical", type=int, default=20, metavar="N",
help="CCN критический порог (по умолчанию: 20)")
thresholds.add_argument("--coupling-out-warn", type=int, default=5, metavar="N",
help="Порог исходящей связности — предупреждение (по умолчанию: 5)")
thresholds.add_argument("--coupling-out-critical", type=int, default=10, metavar="N",
help="Порог исходящей связности — критический (по умолчанию: 10)")
p.add_argument(
"--verbose", "-v",
action="store_true",
help="Подробный вывод (DEBUG)",
)
return p
def main() -> None:
parser = build_arg_parser()
args = parser.parse_args()
# --- Настройка логирования ---
level = logging.DEBUG if args.verbose else logging.INFO
logging.basicConfig(
level=level,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%H:%M:%S",
)
# --- Проверка входного пути ---
root = Path(args.project_root)
if not root.exists():
print(f"Ошибка: директория не найдена: {root}", file=sys.stderr)
sys.exit(1)
# --- Сборка конфигурации ---
thresholds = ThresholdConfig(
ccn_warn=args.ccn_warn,
ccn_critical=args.ccn_critical,
coupling_out_warn=args.coupling_out_warn,
coupling_out_critical=args.coupling_out_critical,
)
config = AnalyzerConfig(
output_formats=args.formats,
output_dir=args.output,
libclang_path=args.libclang,
thresholds=thresholds,
)
# --- Запуск ---
try:
analyzer = Analyzer(config)
report = analyzer.run(str(root))
# Краткий итог в stdout
print("\n" + "=" * 50)
print(f" Системный риск: {report.system_risk_level.name}")
print(f" Стратегия: {report.recommended_system_strategy.name}")
print(f" Модулей: {len(report.modules)}")
print(f" Функций: {report.total_functions}")
print(f" Средняя CCN: {report.avg_system_ccn:.2f}")
print(f" Циклов зависим.: {len(report.dependency_cycles)}")
print(f" Отчёты в: {args.output}/")
print("=" * 50)
except RuntimeError as exc:
print(f"Ошибка: {exc}", file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("\nПрервано пользователем", file=sys.stderr)
sys.exit(130)
if __name__ == "__main__":
main()