Fix linting errors in renderer.py - remove unused imports and variable
This commit is contained in:
@@ -1 +1,309 @@
|
|||||||
/app/depnav/src/depnav/renderer.py
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from rich import box
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.panel import Panel
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
|
from .graph import DependencyGraph
|
||||||
|
|
||||||
|
|
||||||
|
class GraphStyle:
|
||||||
|
"""Style configuration for the graph renderer."""
|
||||||
|
|
||||||
|
STYLES = {
|
||||||
|
"default": {
|
||||||
|
"node": "green",
|
||||||
|
"edge": "dim",
|
||||||
|
"focus": "bold red",
|
||||||
|
"cycle": "red",
|
||||||
|
"background": "",
|
||||||
|
},
|
||||||
|
"dark": {
|
||||||
|
"node": "cyan",
|
||||||
|
"edge": "grey50",
|
||||||
|
"focus": "bold yellow",
|
||||||
|
"cycle": "red",
|
||||||
|
"background": "on #1e1e1e",
|
||||||
|
},
|
||||||
|
"light": {
|
||||||
|
"node": "blue",
|
||||||
|
"edge": "grey70",
|
||||||
|
"focus": "bold green",
|
||||||
|
"cycle": "red",
|
||||||
|
"background": "on #f5f5f5",
|
||||||
|
},
|
||||||
|
"monokai": {
|
||||||
|
"node": "magenta",
|
||||||
|
"edge": "grey50",
|
||||||
|
"focus": "bold yellow",
|
||||||
|
"cycle": "red",
|
||||||
|
"background": "on #272822",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, node: str = "green", edge: str = "dim", focus: str = "bold red", cycle: str = "red", background: str = ""):
|
||||||
|
self.node = node
|
||||||
|
self.edge = edge
|
||||||
|
self.focus = focus
|
||||||
|
self.cycle = cycle
|
||||||
|
self.background = background
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_name(cls, name: str) -> "GraphStyle":
|
||||||
|
"""Create a style from a preset name."""
|
||||||
|
style_config = cls.STYLES.get(name, cls.STYLES["default"])
|
||||||
|
return cls(**style_config)
|
||||||
|
|
||||||
|
|
||||||
|
class ASCIIRenderer:
|
||||||
|
"""Renderer for ASCII dependency graphs."""
|
||||||
|
|
||||||
|
def __init__(self, console: Console, style: Optional[GraphStyle] = None):
|
||||||
|
self.console = console
|
||||||
|
self.style = style or GraphStyle()
|
||||||
|
|
||||||
|
def render_graph(
|
||||||
|
self,
|
||||||
|
graph: DependencyGraph,
|
||||||
|
focus_file: Optional[Path] = None,
|
||||||
|
max_nodes: int = 50,
|
||||||
|
max_depth: Optional[int] = None,
|
||||||
|
) -> Panel:
|
||||||
|
"""Render the dependency graph as ASCII art."""
|
||||||
|
nodes = graph.get_nodes()
|
||||||
|
|
||||||
|
if not nodes:
|
||||||
|
content = Text("No files found in the project.", style="dim")
|
||||||
|
return Panel(content, title="Dependency Graph")
|
||||||
|
|
||||||
|
if len(nodes) > max_nodes:
|
||||||
|
content = Text(
|
||||||
|
f"Showing {max_nodes} of {len(nodes)} files. Use --max-nodes to adjust.",
|
||||||
|
style="dim",
|
||||||
|
)
|
||||||
|
return Panel(content, title="Dependency Graph (truncated)")
|
||||||
|
|
||||||
|
content = self._format_graph(nodes, graph, focus_file, max_depth)
|
||||||
|
return Panel(content, title="Dependency Graph", box=box.ROUNDED)
|
||||||
|
|
||||||
|
def render_tree(
|
||||||
|
self,
|
||||||
|
graph: DependencyGraph,
|
||||||
|
focus_file: Optional[Path] = None,
|
||||||
|
max_nodes: int = 50,
|
||||||
|
) -> Panel:
|
||||||
|
"""Render the dependency graph as a tree."""
|
||||||
|
nodes = graph.get_nodes()
|
||||||
|
|
||||||
|
if not nodes:
|
||||||
|
content = Text("No files found in the project.", style="dim")
|
||||||
|
return Panel(content, title="Project Tree")
|
||||||
|
|
||||||
|
content = self._format_tree(nodes, focus_file)
|
||||||
|
return Panel(content, title="Project Tree", box=box.ROUNDED)
|
||||||
|
|
||||||
|
def render_clusters(
|
||||||
|
self,
|
||||||
|
graph: DependencyGraph,
|
||||||
|
focus_file: Optional[Path] = None,
|
||||||
|
max_nodes: int = 50,
|
||||||
|
) -> Panel:
|
||||||
|
"""Render the dependency graph with clustered layout."""
|
||||||
|
nodes = graph.get_nodes()
|
||||||
|
|
||||||
|
if not nodes:
|
||||||
|
content = Text("No files found in the project.", style="dim")
|
||||||
|
return Panel(content, title="Clustered Graph")
|
||||||
|
|
||||||
|
content = self._format_clusters(nodes, graph, focus_file)
|
||||||
|
return Panel(content, title="Clustered Graph", box=box.ROUNDED)
|
||||||
|
|
||||||
|
def _format_graph(
|
||||||
|
self,
|
||||||
|
nodes: list[Path],
|
||||||
|
graph: DependencyGraph,
|
||||||
|
focus_file: Optional[Path],
|
||||||
|
max_depth: Optional[int],
|
||||||
|
) -> Text:
|
||||||
|
"""Format the graph as ASCII art."""
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
rel_path = node.relative_to(graph.project_root)
|
||||||
|
|
||||||
|
style = self.style.node
|
||||||
|
if focus_file and node == focus_file.resolve():
|
||||||
|
style = self.style.focus
|
||||||
|
|
||||||
|
lines.append(f"[{style}]{rel_path}[/]")
|
||||||
|
|
||||||
|
if max_depth:
|
||||||
|
deps = graph.get_dependencies(node)
|
||||||
|
for dep in deps[:max_depth]:
|
||||||
|
dep_rel = dep.relative_to(graph.project_root)
|
||||||
|
lines.append(f" └── [{self.style.edge}]{dep_rel}[/]")
|
||||||
|
|
||||||
|
return Text("\n".join(lines))
|
||||||
|
|
||||||
|
def _format_tree(self, nodes: list[Path], focus_file: Optional[Path]) -> Text:
|
||||||
|
"""Format the graph as a tree structure."""
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
root = nodes[0] if nodes else None
|
||||||
|
|
||||||
|
def add_node(file_path: Path, prefix: str = "", is_last: bool = True) -> None:
|
||||||
|
if file_path in visited:
|
||||||
|
return
|
||||||
|
|
||||||
|
visited.add(file_path)
|
||||||
|
|
||||||
|
rel_path = file_path.relative_to(file_path.parent)
|
||||||
|
connector = "└── " if is_last else "├── "
|
||||||
|
lines.append(f"{prefix}{connector}[{self.style.node}]{rel_path}[/]")
|
||||||
|
|
||||||
|
children = []
|
||||||
|
for node in nodes:
|
||||||
|
if node.parent == file_path and node not in visited:
|
||||||
|
children.append(node)
|
||||||
|
|
||||||
|
for i, child in enumerate(children):
|
||||||
|
new_prefix = prefix + (" " if is_last else "│ ")
|
||||||
|
add_node(child, new_prefix, i == len(children) - 1)
|
||||||
|
|
||||||
|
visited = set()
|
||||||
|
|
||||||
|
if root:
|
||||||
|
add_node(root, is_last=True)
|
||||||
|
|
||||||
|
return Text("\n".join(lines))
|
||||||
|
|
||||||
|
def _format_clusters(
|
||||||
|
self,
|
||||||
|
nodes: list[Path],
|
||||||
|
graph: DependencyGraph,
|
||||||
|
focus_file: Optional[Path],
|
||||||
|
) -> Text:
|
||||||
|
"""Format the graph with cluster layout."""
|
||||||
|
lines = []
|
||||||
|
components = graph.get_connected_components()
|
||||||
|
|
||||||
|
for i, component in enumerate(components):
|
||||||
|
lines.append(f"\n--- Component {i + 1} ---")
|
||||||
|
|
||||||
|
for node in component:
|
||||||
|
rel_path = node.relative_to(graph.project_root)
|
||||||
|
|
||||||
|
style = self.style.node
|
||||||
|
if focus_file and node == focus_file.resolve():
|
||||||
|
style = self.style.focus
|
||||||
|
|
||||||
|
lines.append(f" [{style}]{rel_path}[/]")
|
||||||
|
|
||||||
|
return Text("\n".join(lines))
|
||||||
|
|
||||||
|
def render_cycles(self, graph: DependencyGraph) -> Text:
|
||||||
|
"""Render cycle warnings."""
|
||||||
|
cycles = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
cycles = list(nx.simple_cycles(graph.graph))
|
||||||
|
except nx.NetworkXNoCycle:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not cycles:
|
||||||
|
return Text("No cycles detected.", style="green")
|
||||||
|
|
||||||
|
lines = [f"Found {len(cycles)} cycle(s):", ""]
|
||||||
|
|
||||||
|
for i, cycle in enumerate(cycles):
|
||||||
|
cycle_str = " -> ".join(str(node.relative_to(graph.project_root)) for node in cycle)
|
||||||
|
lines.append(f"[{self.style.cycle}]{i + 1}. {cycle_str}[/]")
|
||||||
|
|
||||||
|
return Text("\n".join(lines))
|
||||||
|
|
||||||
|
def render_statistics(self, graph: DependencyGraph) -> Text:
|
||||||
|
"""Render graph statistics."""
|
||||||
|
stats = graph.get_statistics()
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"Files: [{self.style.node}]{stats['file_count']}[/]",
|
||||||
|
f"Dependencies: [{self.style.node}]{stats['dependency_count']}[/]",
|
||||||
|
f"Cycles: [{self.style.cycle if stats['cycle_count'] > 0 else 'green'}]{stats['cycle_count']}[/]",
|
||||||
|
f"Max Depth: [{self.style.node}]{stats['max_depth']}[/]",
|
||||||
|
f"Components: [{self.style.node}]{stats['components']}[/]",
|
||||||
|
]
|
||||||
|
|
||||||
|
return Text("\n".join(lines))
|
||||||
|
|
||||||
|
def render_json(
|
||||||
|
self,
|
||||||
|
graph: DependencyGraph,
|
||||||
|
focus_file: Optional[Path] = None,
|
||||||
|
max_depth: Optional[int] = None,
|
||||||
|
) -> str:
|
||||||
|
"""Render graph as JSON."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
nodes_data = []
|
||||||
|
for node in graph.get_nodes():
|
||||||
|
node_data = {
|
||||||
|
"path": str(node.relative_to(graph.project_root)),
|
||||||
|
"dependencies": [
|
||||||
|
str(dep.relative_to(graph.project_root))
|
||||||
|
for dep in graph.get_dependencies(node)[:max_depth] if max_depth is None or graph.get_dependencies(node).index(dep) < max_depth
|
||||||
|
],
|
||||||
|
}
|
||||||
|
if focus_file and node == focus_file.resolve():
|
||||||
|
node_data["focus"] = True
|
||||||
|
nodes_data.append(node_data)
|
||||||
|
|
||||||
|
return json.dumps({"nodes": nodes_data, "project_root": str(graph.project_root)}, indent=2)
|
||||||
|
|
||||||
|
def render_dot(
|
||||||
|
self,
|
||||||
|
graph: DependencyGraph,
|
||||||
|
focus_file: Optional[Path] = None,
|
||||||
|
max_depth: Optional[int] = None,
|
||||||
|
) -> str:
|
||||||
|
"""Render graph as DOT format."""
|
||||||
|
lines = ["digraph dependencies {", ' rankdir=TB;', ' node [shape=box];']
|
||||||
|
|
||||||
|
for node in graph.get_nodes():
|
||||||
|
node_name = str(node.relative_to(graph.project_root)).replace("\", "_").replace(".", "_")
|
||||||
|
focus_suffix = " [style=filled, fillcolor=yellow]" if focus_file and node == focus_file.resolve() else ""
|
||||||
|
lines.append(f' {node_name}{suffix}')
|
||||||
|
|
||||||
|
for node in graph.get_nodes():
|
||||||
|
node_name = str(node.relative_to(graph.project_root)).replace("\", "_").replace(".", "_")
|
||||||
|
for dep in graph.get_dependencies(node)[:max_depth] if max_depth is None else graph.get_dependencies(node)[:max_depth]:
|
||||||
|
dep_name = str(dep.relative_to(graph.project_root)).replace("\", "_").replace(".", "_")
|
||||||
|
lines.append(f' {node_name} -> {dep_name};')
|
||||||
|
|
||||||
|
lines.append("}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def render_plantuml(
|
||||||
|
self,
|
||||||
|
graph: DependencyGraph,
|
||||||
|
focus_file: Optional[Path] = None,
|
||||||
|
max_depth: Optional[int] = None,
|
||||||
|
) -> str:
|
||||||
|
"""Render graph as PlantUML format."""
|
||||||
|
lines = ["@startuml", "!theme plain"]
|
||||||
|
|
||||||
|
for node in graph.get_nodes():
|
||||||
|
node_name = str(node.relative_to(graph.project_root))
|
||||||
|
focus_marker = " (**) " if focus_file and node == focus_file.resolve() else ""
|
||||||
|
lines.append(f"class {node_name} {focus_marker}")
|
||||||
|
|
||||||
|
for node in graph.get_nodes():
|
||||||
|
for dep in graph.get_dependencies(node)[:max_depth] if max_depth is None else graph.get_dependencies(node)[:max_depth]:
|
||||||
|
dep_name = str(dep.relative_to(graph.project_root))
|
||||||
|
node_name = str(node.relative_to(graph.project_root))
|
||||||
|
lines.append(f"{node_name} --> {dep_name}")
|
||||||
|
|
||||||
|
lines.append("@enduml")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|||||||
Reference in New Issue
Block a user