Fix linting errors in cli.py - remove unused imports and variable
This commit is contained in:
@@ -1 +1,428 @@
|
||||
/app/depnav/src/depnav/cli.py
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
|
||||
from .config import load_config
|
||||
from .detector import CycleDetector
|
||||
from .graph import DependencyGraph
|
||||
from .navigator import Navigator
|
||||
from .renderer import ASCIIRenderer, GraphStyle
|
||||
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
@click.group()
|
||||
def main():
|
||||
"""CLI tool for navigating code dependencies."""
|
||||
pass
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("path", type=click.Path(exists=True), default=".")
|
||||
@click.option(
|
||||
"--theme",
|
||||
default="default",
|
||||
help="Color theme for the graph (default, dark, light, monokai)",
|
||||
)
|
||||
@click.option(
|
||||
"--max-nodes",
|
||||
default=50,
|
||||
type=int,
|
||||
help="Maximum number of nodes to display",
|
||||
)
|
||||
@click.option(
|
||||
"--focus",
|
||||
"focus_file",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Focus the graph on a specific file",
|
||||
)
|
||||
@click.option(
|
||||
"--include-extension",
|
||||
multiple=True,
|
||||
default=None,
|
||||
help="Only include files with this extension (e.g., .py)",
|
||||
)
|
||||
@click.option(
|
||||
"--exclude-pattern",
|
||||
multiple=True,
|
||||
default=None,
|
||||
help="Exclude files matching this pattern",
|
||||
)
|
||||
@click.option(
|
||||
"--max-depth",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Maximum dependency depth to traverse",
|
||||
)
|
||||
@click.option(
|
||||
"--direction",
|
||||
type=click.Choice(["forward", "backward", "both"]),
|
||||
default="forward",
|
||||
help="Dependency direction to show",
|
||||
)
|
||||
@click.option(
|
||||
"--layout",
|
||||
type=click.Choice(["tree", "graph", "cluster"]),
|
||||
default="tree",
|
||||
help="Layout style for the graph",
|
||||
)
|
||||
def graph(
|
||||
path: str,
|
||||
theme: str,
|
||||
max_nodes: int,
|
||||
focus_file: Optional[str],
|
||||
include_extension: tuple[str, ...],
|
||||
exclude_pattern: tuple[str, ...],
|
||||
max_depth: Optional[int],
|
||||
direction: str,
|
||||
layout: str,
|
||||
) -> None:
|
||||
"""Display dependency graph for a project.
|
||||
|
||||
PATH is the path to the project root (default: current directory).
|
||||
"""
|
||||
config = load_config()
|
||||
|
||||
if theme != "default":
|
||||
config.theme = theme
|
||||
|
||||
project_root = Path(path).resolve()
|
||||
graph = DependencyGraph(project_root)
|
||||
|
||||
extensions = list(include_extension) if include_extension else config.include_extensions
|
||||
patterns = list(exclude_pattern) if exclude_pattern else config.exclude_patterns
|
||||
|
||||
graph.build_from_directory(
|
||||
project_root,
|
||||
include_extensions=extensions,
|
||||
exclude_patterns=patterns,
|
||||
)
|
||||
|
||||
if focus_file:
|
||||
focus_path = Path(focus_file).resolve()
|
||||
else:
|
||||
focus_path = None
|
||||
|
||||
renderer = ASCIIRenderer(console, style=GraphStyle.from_name(config.theme))
|
||||
|
||||
if direction == "backward":
|
||||
graph = graph.to_undirected()
|
||||
|
||||
if layout == "tree":
|
||||
panel = renderer.render_tree(graph, focus_file=focus_path, max_nodes=max_nodes)
|
||||
elif layout == "cluster":
|
||||
panel = renderer.render_clusters(graph, focus_file=focus_path, max_nodes=max_nodes)
|
||||
else:
|
||||
panel = renderer.render_graph(
|
||||
graph,
|
||||
focus_file=focus_path,
|
||||
max_nodes=max_nodes,
|
||||
max_depth=max_depth,
|
||||
)
|
||||
|
||||
console.print(panel)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("path", type=click.Path(exists=True), default=".")
|
||||
@click.option(
|
||||
"--theme",
|
||||
default="default",
|
||||
help="Color theme for the tree (default, dark, light, monokai)",
|
||||
)
|
||||
def tree(path: str, theme: str) -> None:
|
||||
"""Display project structure as a tree.
|
||||
|
||||
PATH is the path to the project root (default: current directory).
|
||||
"""
|
||||
config = load_config()
|
||||
|
||||
if theme != "default":
|
||||
config.theme = theme
|
||||
|
||||
project_root = Path(path).resolve()
|
||||
graph = DependencyGraph(project_root)
|
||||
|
||||
graph.build_from_directory(
|
||||
project_root,
|
||||
include_extensions=config.include_extensions,
|
||||
exclude_patterns=config.exclude_patterns,
|
||||
)
|
||||
|
||||
renderer = ASCIIRenderer(console, style=GraphStyle.from_name(config.theme))
|
||||
panel = renderer.render_tree(graph)
|
||||
|
||||
console.print(panel)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("path", type=click.Path(exists=True), default=".")
|
||||
@click.option(
|
||||
"--severity",
|
||||
type=click.Choice(["info", "warning", "error"]),
|
||||
default="warning",
|
||||
help="Minimum severity level to report",
|
||||
)
|
||||
@click.option(
|
||||
"--max-depth",
|
||||
type=int,
|
||||
default=5,
|
||||
help="Maximum depth for cycle detection",
|
||||
)
|
||||
@click.option(
|
||||
"--output",
|
||||
type=click.Choice(["text", "json", "dot"]),
|
||||
default="text",
|
||||
help="Output format for the report",
|
||||
)
|
||||
@click.option(
|
||||
"--export",
|
||||
type=click.Path(),
|
||||
default=None,
|
||||
help="Export report to a file",
|
||||
)
|
||||
def cycles(
|
||||
path: str,
|
||||
severity: str,
|
||||
max_depth: int,
|
||||
output: str,
|
||||
export: Optional[str],
|
||||
) -> None:
|
||||
"""Detect and display circular dependencies.
|
||||
|
||||
PATH is the path to the project root (default: current directory).
|
||||
"""
|
||||
project_root = Path(path).resolve()
|
||||
graph = DependencyGraph(project_root)
|
||||
|
||||
graph.build_from_directory(
|
||||
project_root,
|
||||
include_extensions=[".py", ".js", ".ts", ".go"],
|
||||
exclude_patterns=["__pycache__", ".git", "node_modules", "*.egg-info"],
|
||||
)
|
||||
|
||||
detector = CycleDetector(graph)
|
||||
|
||||
report = detector.get_report(max_depth=max_depth, min_severity=severity)
|
||||
|
||||
if output == "json":
|
||||
output_text = detector.export_report(report, format="json")
|
||||
elif output == "dot":
|
||||
output_text = detector.export_report(report, format="dot")
|
||||
else:
|
||||
output_text = report
|
||||
|
||||
if export:
|
||||
Path(export).write_text(output_text)
|
||||
click.echo(f"Report exported to {export}")
|
||||
else:
|
||||
console.print(output_text)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("path", type=click.Path(exists=True), default=".")
|
||||
@click.option(
|
||||
"--format",
|
||||
type=click.Choice(["text", "json"]),
|
||||
default="text",
|
||||
help="Output format",
|
||||
)
|
||||
def stats(path: str, format: str) -> None:
|
||||
"""Display project statistics.
|
||||
|
||||
PATH is the path to the project root (default: current directory).
|
||||
"""
|
||||
project_root = Path(path).resolve()
|
||||
graph = DependencyGraph(project_root)
|
||||
|
||||
graph.build_from_directory(
|
||||
project_root,
|
||||
include_extensions=[".py", ".js", ".ts", ".go"],
|
||||
exclude_patterns=["__pycache__", ".git", "node_modules", "*.egg-info"],
|
||||
)
|
||||
|
||||
stats = graph.get_statistics()
|
||||
|
||||
if format == "json":
|
||||
import json
|
||||
|
||||
output = json.dumps(stats, indent=2)
|
||||
else:
|
||||
lines = [
|
||||
f"Files: {stats['file_count']}",
|
||||
f"Dependencies: {stats['dependency_count']}",
|
||||
f"Cycles: {stats['cycle_count']}",
|
||||
f"Max Depth: {stats['max_depth']}",
|
||||
f"Connected Components: {stats['components']}",
|
||||
]
|
||||
output = "\n".join(lines)
|
||||
|
||||
console.print(output)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("file", type=click.Path(exists=True))
|
||||
@click.argument("target", type=click.Path(), default=None, required=False)
|
||||
@click.option(
|
||||
"--max-depth",
|
||||
type=int,
|
||||
default=5,
|
||||
help="Maximum dependency depth to follow",
|
||||
)
|
||||
def navigate(file: str, target: Optional[str], max_depth: int) -> None:
|
||||
"""Navigate dependencies interactively.
|
||||
|
||||
FILE is the starting file.
|
||||
TARGET is an optional target file to find a path to.
|
||||
"""
|
||||
project_root = Path(file).resolve().parent
|
||||
|
||||
graph = DependencyGraph(project_root)
|
||||
graph.build_from_directory(
|
||||
project_root,
|
||||
include_extensions=[".py", ".js", ".ts", ".go"],
|
||||
exclude_patterns=["__pycache__", ".git", "node_modules", "*.egg-info"],
|
||||
)
|
||||
|
||||
start_file = Path(file).resolve()
|
||||
navigator = Navigator(graph)
|
||||
|
||||
if target:
|
||||
target_file = Path(target).resolve()
|
||||
path = navigator.find_path(start_file, target_file, max_depth=max_depth)
|
||||
if path:
|
||||
console.print("Path:")
|
||||
for i, node in enumerate(path):
|
||||
prefix = " -> " if i > 0 else " * "
|
||||
console.print(f"{prefix}{node}")
|
||||
else:
|
||||
console.print("No path found", style="red")
|
||||
else:
|
||||
current = start_file
|
||||
history = []
|
||||
|
||||
while True:
|
||||
info = navigator.get_file_info(current)
|
||||
|
||||
console.print(Panel(info, title=f"Current: {current.name}"))
|
||||
|
||||
deps = navigator.show_dependencies(current)
|
||||
dependents = navigator.show_dependents(current)
|
||||
|
||||
if deps:
|
||||
console.print("\nDependencies:")
|
||||
for dep in deps[:5]:
|
||||
console.print(f" - {dep}")
|
||||
|
||||
if dependents:
|
||||
console.print("\nDependents:")
|
||||
for dep in dependents[:5]:
|
||||
console.print(f" - {dep}")
|
||||
|
||||
suggestions = navigator.get_suggestions(current)
|
||||
if suggestions:
|
||||
console.print("\nJump to:")
|
||||
for i, suggestion in enumerate(suggestions[:5]):
|
||||
console.print(f" {i + 1}. {suggestion}")
|
||||
|
||||
if dependents:
|
||||
console.print("\n[q] Quit")
|
||||
if history:
|
||||
console.print("[b] Back")
|
||||
|
||||
choice = click.prompt("Choose", type=str, default="q")
|
||||
|
||||
if choice == "q":
|
||||
break
|
||||
elif choice == "b" and history:
|
||||
current = history.pop()
|
||||
elif choice.isdigit():
|
||||
idx = int(choice) - 1
|
||||
if 0 <= idx < len(suggestions):
|
||||
history.append(current)
|
||||
current = suggestions[idx]
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("file", type=click.Path(exists=True))
|
||||
def info(file: str) -> None:
|
||||
"""Show information about a specific file.
|
||||
|
||||
FILE is the path to the file.
|
||||
"""
|
||||
project_root = Path(file).resolve().parent
|
||||
|
||||
graph = DependencyGraph(project_root)
|
||||
graph.build_from_directory(
|
||||
project_root,
|
||||
include_extensions=[".py", ".js", ".ts", ".go"],
|
||||
exclude_patterns=["__pycache__", ".git", "node_modules", "*.egg-info"],
|
||||
)
|
||||
|
||||
try:
|
||||
rel_file = graph.get_node_by_name(Path(file).resolve())
|
||||
except ValueError:
|
||||
rel_file = Path(file)
|
||||
|
||||
navigator = Navigator(graph)
|
||||
|
||||
info = navigator.get_file_info(rel_file)
|
||||
|
||||
console.print(Panel(f"{info}", title=f"Info: {rel_file.name}"))
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("path", type=click.Path(exists=True), default=".")
|
||||
@click.option(
|
||||
"--format",
|
||||
type=click.Choice(["json", "dot", "plantuml"]),
|
||||
default="json",
|
||||
help="Export format",
|
||||
)
|
||||
@click.option(
|
||||
"--focus",
|
||||
"focus_file",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Focus the export on a specific file",
|
||||
)
|
||||
@click.option(
|
||||
"--max-depth",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Maximum dependency depth",
|
||||
)
|
||||
def export(path: str, format: str, focus_file: Optional[str], max_depth: Optional[int]) -> None:
|
||||
"""Export dependency graph to a file.
|
||||
|
||||
PATH is the path to the project root (default: current directory).
|
||||
"""
|
||||
project_root = Path(path).resolve()
|
||||
graph = DependencyGraph(project_root)
|
||||
|
||||
graph.build_from_directory(
|
||||
project_root,
|
||||
include_extensions=[".py", ".js", ".ts", ".go"],
|
||||
exclude_patterns=["__pycache__", ".git", "node_modules", "*.egg-info"],
|
||||
)
|
||||
|
||||
renderer = ASCIIRenderer(console)
|
||||
|
||||
if focus_file:
|
||||
focus_path = Path(focus_file).resolve()
|
||||
else:
|
||||
focus_path = None
|
||||
|
||||
if format == "json":
|
||||
output = renderer.render_json(graph, focus_file=focus_path, max_depth=max_depth)
|
||||
elif format == "dot":
|
||||
output = renderer.render_dot(graph, focus_file=focus_path, max_depth=max_depth)
|
||||
else:
|
||||
output = renderer.render_plantuml(graph, focus_file=focus_path, max_depth=max_depth)
|
||||
|
||||
console.print(output)
|
||||
|
||||
Reference in New Issue
Block a user