Initial commit: CodeMap v0.1.0 - CLI tool for code analysis and diagram generation
This commit is contained in:
144
codemap/cli/analyze.py
Normal file
144
codemap/cli/analyze.py
Normal file
@@ -0,0 +1,144 @@
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
from codemap.parsers import PythonParser, JavaScriptParser, GoParser
|
||||
from codemap.core import GraphBuilder, MermaidGenerator
|
||||
from codemap.templates import render_html
|
||||
|
||||
console = Console()
|
||||
|
||||
def analyze(
|
||||
path: str = typer.Argument(..., help="Directory or file to analyze"),
|
||||
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
||||
format: str = typer.Option("mermaid", "--format", "-f", help="Output format (mermaid, html, markdown)"),
|
||||
max_depth: int = typer.Option(3, "--max-depth", "-d", help="Maximum dependency depth"),
|
||||
include_packages: bool = typer.Option(True, "--include-packages/--no-packages", help="Include package groupings"),
|
||||
) -> None:
|
||||
target_path = Path(path)
|
||||
if not target_path.exists():
|
||||
console.print(f"[red]Error: Path '{path}' does not exist[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
if not target_path.is_dir() and not target_path.suffix in [".py", ".js", ".jsx", ".ts", ".tsx", ".go", ".mjs"]:
|
||||
console.print(f"[red]Error: Unsupported file type[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
with console.status("[bold green]Analyzing codebase...") as status:
|
||||
files = _collect_files(target_path)
|
||||
if not files:
|
||||
console.print("[yellow]No supported files found[/yellow]")
|
||||
return
|
||||
|
||||
status.update("[bold green]Parsing files...")
|
||||
|
||||
parsers = [PythonParser(), JavaScriptParser(), GoParser()]
|
||||
parsed_files = []
|
||||
errors = []
|
||||
|
||||
for file_path in files:
|
||||
for parser in parsers:
|
||||
if parser.can_parse(file_path):
|
||||
try:
|
||||
parsed = parser.parse(file_path)
|
||||
parsed_files.append(parsed)
|
||||
except Exception as e:
|
||||
errors.append((file_path, str(e)))
|
||||
break
|
||||
|
||||
status.update("[bold green]Building dependency graph...")
|
||||
|
||||
builder = GraphBuilder()
|
||||
builder.build_from_files(parsed_files)
|
||||
|
||||
status.update("[bold green]Generating diagram...")
|
||||
|
||||
if max_depth > 0:
|
||||
start_nodes = [str(f.file_path.absolute()) for f in parsed_files]
|
||||
filtered_graph = builder.filter_by_depth(start_nodes, max_depth)
|
||||
graph_data = builder._build_graph_data_from_filtered(filtered_graph)
|
||||
else:
|
||||
graph_data = builder.get_graph_data()
|
||||
|
||||
mermaid_gen = MermaidGenerator(graph_data)
|
||||
if format == "mermaid":
|
||||
output_content = mermaid_gen.generate_flowchart(include_packages)
|
||||
elif format == "markdown":
|
||||
output_content = _generate_markdown(graph_data, mermaid_gen, include_packages)
|
||||
elif format == "html":
|
||||
output_content = render_html(mermaid_gen.generate_flowchart(include_packages))
|
||||
else:
|
||||
output_content = mermaid_gen.generate_flowchart(include_packages)
|
||||
|
||||
_display_results(builder, errors, path)
|
||||
|
||||
if output:
|
||||
Path(output).write_text(output_content, encoding="utf-8")
|
||||
console.print(f"[green]Output written to {output}[/green]")
|
||||
else:
|
||||
console.print(output_content)
|
||||
|
||||
def _collect_files(target_path: Path) -> List[Path]:
|
||||
files = []
|
||||
extensions = [".py", ".pyi", ".js", ".jsx", ".ts", ".tsx", ".go", ".mjs"]
|
||||
|
||||
if target_path.is_file():
|
||||
if target_path.suffix in extensions:
|
||||
return [target_path]
|
||||
return []
|
||||
|
||||
for ext in extensions:
|
||||
files.extend(target_path.rglob(f"*{ext}"))
|
||||
|
||||
return sorted(set(files))
|
||||
|
||||
def _display_results(builder: GraphBuilder, errors: List, path: str) -> None:
|
||||
stats = builder.get_stats()
|
||||
|
||||
table = Table(title="Analysis Summary")
|
||||
table.add_column("Metric", style="cyan")
|
||||
table.add_column("Value", style="magenta")
|
||||
|
||||
table.add_row("Files Analyzed", str(stats["file_count"]))
|
||||
table.add_row("Nodes", str(stats["node_count"]))
|
||||
table.add_row("Dependencies", str(stats["edge_count"]))
|
||||
table.add_row("Is DAG", str(stats["is_dag"]))
|
||||
|
||||
console.print(Panel(table, title=f"Analysis Results for {path}", expand=False))
|
||||
|
||||
if errors:
|
||||
error_table = Table(title="Parse Errors")
|
||||
error_table.add_column("File", style="red")
|
||||
error_table.add_column("Error", style="yellow")
|
||||
|
||||
for file_path, error in errors[:10]:
|
||||
error_table.add_row(str(file_path), error)
|
||||
|
||||
console.print(Panel(error_table, title="Errors", expand=False))
|
||||
|
||||
def _generate_markdown(graph_data, mermaid_gen: MermaidGenerator, include_packages: bool) -> str:
|
||||
md = ["# Code Map Analysis\n"]
|
||||
md.append(f"## Summary\n")
|
||||
md.append(f"- **Nodes**: {len(graph_data.nodes)}")
|
||||
md.append(f"- **Edges**: {len(graph_data.edges)}")
|
||||
md.append(f"- **Packages**: {len(graph_data.packages)}\n")
|
||||
md.append("## Dependency Graph\n")
|
||||
md.append("```mermaid")
|
||||
md.append(mermaid_gen.generate_flowchart(include_packages))
|
||||
md.append("```\n")
|
||||
|
||||
if graph_data.packages:
|
||||
md.append("## Packages\n")
|
||||
for package, nodes in graph_data.packages.items():
|
||||
md.append(f"### {package}")
|
||||
for node_id in nodes:
|
||||
node = next((n for n in graph_data.nodes if n.id == node_id), None)
|
||||
if node:
|
||||
md.append(f"- {node.name}")
|
||||
|
||||
return "\n".join(md)
|
||||
Reference in New Issue
Block a user