Fix linting errors in renderer.py - remove unused imports and variable
Some checks failed
CI / test (push) Failing after 6s
CI / build (push) Has been skipped

This commit is contained in:
2026-01-30 17:04:22 +00:00
parent 58c65f2719
commit a723e11e02

View File

@@ -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)