Coverage for lintro / cli_utils / commands / list_tools.py: 47%
131 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""List tools command implementation for lintro CLI.
3This module provides the core logic for the 'list_tools' command.
4"""
6import json as json_lib
8import click
9from rich.console import Console
10from rich.panel import Panel
11from rich.table import Table
13from lintro.enums.action import Action
14from lintro.plugins.base import BaseToolPlugin
15from lintro.tools import tool_manager
16from lintro.utils.console import get_tool_emoji
17from lintro.utils.unified_config import get_tool_priority, is_tool_injectable
20def _resolve_conflicts(
21 plugin: BaseToolPlugin,
22 available_tools: dict[str, BaseToolPlugin],
23) -> list[str]:
24 """Resolve conflict names for a tool.
26 Args:
27 plugin: The plugin instance.
28 available_tools: Dictionary of available tools.
30 Returns:
31 List of conflict tool names.
32 """
33 conflict_names: list[str] = []
34 conflicts_with = plugin.definition.conflicts_with
35 if conflicts_with:
36 for conflict in conflicts_with:
37 conflict_lower = conflict.lower()
38 if conflict_lower in available_tools:
39 conflict_names.append(conflict_lower)
40 return conflict_names
43@click.command("list-tools")
44@click.option(
45 "--output",
46 type=click.Path(),
47 help="Output file path for writing results",
48)
49@click.option(
50 "--show-conflicts",
51 is_flag=True,
52 help="Show potential conflicts between tools",
53)
54@click.option(
55 "--json",
56 "json_output",
57 is_flag=True,
58 help="Output tool list as JSON",
59)
60@click.option(
61 "--verbose",
62 "-v",
63 is_flag=True,
64 help="Show verbose output including file extensions and patterns",
65)
66def list_tools_command(
67 output: str | None,
68 show_conflicts: bool,
69 json_output: bool,
70 verbose: bool,
71) -> None:
72 """List all available tools and their configurations.
74 Args:
75 output: Path to output file for writing results.
76 show_conflicts: Whether to show potential conflicts between tools.
77 json_output: Output tool list as JSON.
78 verbose: Show verbose output including file extensions and patterns.
79 """
80 list_tools(
81 output=output,
82 show_conflicts=show_conflicts,
83 json_output=json_output,
84 verbose=verbose,
85 )
88def list_tools(
89 output: str | None,
90 show_conflicts: bool,
91 json_output: bool = False,
92 verbose: bool = False,
93) -> None:
94 """List all available tools.
96 Args:
97 output: Output file path.
98 show_conflicts: Whether to show potential conflicts between tools.
99 json_output: Output tool list as JSON.
100 verbose: Show verbose output including file extensions and patterns.
101 """
102 available_tools = tool_manager.get_all_tools()
103 check_tools = tool_manager.get_check_tools()
104 fix_tools = tool_manager.get_fix_tools()
106 # JSON output mode
107 if json_output:
108 tools_data: dict[str, dict[str, object]] = {}
109 for tool_name, plugin in available_tools.items():
110 capabilities: list[str] = []
111 if tool_name in check_tools:
112 capabilities.append("check")
113 if tool_name in fix_tools:
114 capabilities.append("fix")
116 tool_info: dict[str, object] = {
117 "description": plugin.definition.description,
118 "capabilities": capabilities,
119 "priority": get_tool_priority(tool_name),
120 "syncable": is_tool_injectable(tool_name),
121 }
123 # Only include file_patterns in verbose mode (consistent with table output)
124 if verbose:
125 tool_info["file_patterns"] = plugin.definition.file_patterns
127 if show_conflicts:
128 conflict_names = _resolve_conflicts(
129 plugin=plugin,
130 available_tools=available_tools,
131 )
132 tool_info["conflicts_with"] = conflict_names
134 tools_data[tool_name] = tool_info
136 click.echo(json_lib.dumps(tools_data, indent=2))
137 return
139 console = Console()
141 # Header panel
142 console.print(
143 Panel.fit(
144 "[bold cyan]🔧 Available Tools[/bold cyan]",
145 border_style="cyan",
146 ),
147 )
148 console.print()
150 # Main tools table
151 table = Table(title="Tool Details")
152 table.add_column("Tool", style="cyan", no_wrap=True)
153 table.add_column("Description", style="white", max_width=40)
154 table.add_column("Capabilities", style="green")
155 table.add_column("Priority", justify="center", style="yellow")
156 table.add_column("Type", style="magenta")
158 if verbose:
159 table.add_column("Extensions", style="dim", max_width=30)
161 if show_conflicts:
162 table.add_column("Conflicts", style="red")
164 for tool_name, plugin in available_tools.items():
165 tool_description = plugin.definition.description
166 emoji = get_tool_emoji(tool_name)
168 # Capabilities
169 tool_capabilities: list[str] = []
170 if tool_name in check_tools:
171 tool_capabilities.append("check")
172 if tool_name in fix_tools:
173 tool_capabilities.append("fix")
174 caps_display = ", ".join(tool_capabilities) if tool_capabilities else "-"
176 # Priority and type
177 priority = get_tool_priority(tool_name)
178 injectable = is_tool_injectable(tool_name)
179 tool_type = "Syncable" if injectable else "Native only"
181 row = [
182 f"{emoji} {tool_name}",
183 tool_description,
184 caps_display,
185 str(priority),
186 tool_type,
187 ]
189 # File patterns (verbose mode)
190 if verbose:
191 patterns = plugin.definition.file_patterns or []
192 pat_display = ", ".join(patterns[:5])
193 if len(patterns) > 5:
194 pat_display += f" (+{len(patterns) - 5})"
195 row.append(pat_display if patterns else "-")
197 # Conflicts
198 if show_conflicts:
199 conflict_names = _resolve_conflicts(
200 plugin=plugin,
201 available_tools=available_tools,
202 )
203 row.append(", ".join(conflict_names) if conflict_names else "-")
205 table.add_row(*row)
207 console.print(table)
208 console.print()
210 # Summary table
211 summary_table = Table(
212 title="Summary",
213 show_header=False,
214 box=None,
215 )
216 summary_table.add_column("Metric", style="cyan", width=20)
217 summary_table.add_column("Count", style="yellow", justify="right")
219 summary_table.add_row("📊 Total tools", str(len(available_tools)))
220 summary_table.add_row("🔍 Check tools", str(len(check_tools)))
221 summary_table.add_row("🔧 Fix tools", str(len(fix_tools)))
223 console.print(summary_table)
225 # Write to file if specified
226 if output:
227 try:
228 # For file output, use plain text format
229 output_lines = _generate_plain_text_output(
230 available_tools=available_tools,
231 check_tools=check_tools,
232 fix_tools=fix_tools,
233 show_conflicts=show_conflicts,
234 )
235 with open(output, "w", encoding="utf-8") as f:
236 f.write("\n".join(output_lines) + "\n")
237 console.print()
238 console.print(f"[green]✅ Output written to: {output}[/green]")
239 except OSError as e:
240 console.print(f"[red]Error writing to file {output}: {e}[/red]")
243def _generate_plain_text_output(
244 available_tools: dict[str, BaseToolPlugin],
245 check_tools: dict[str, BaseToolPlugin],
246 fix_tools: dict[str, BaseToolPlugin],
247 show_conflicts: bool,
248) -> list[str]:
249 """Generate plain text output for file writing.
251 Args:
252 available_tools: Dictionary of available tools.
253 check_tools: Dictionary of check-capable tools.
254 fix_tools: Dictionary of fix-capable tools.
255 show_conflicts: Whether to include conflict information.
257 Returns:
258 List of output lines.
259 """
260 output_lines: list[str] = []
261 border = "=" * 70
263 output_lines.append(border)
264 output_lines.append("Available Tools")
265 output_lines.append(border)
266 output_lines.append("")
268 for tool_name, plugin in available_tools.items():
269 tool_description = plugin.definition.description
270 emoji = get_tool_emoji(tool_name)
272 capabilities: list[str] = []
273 if tool_name in check_tools:
274 capabilities.append(Action.CHECK.value)
275 if tool_name in fix_tools:
276 capabilities.append(Action.FIX.value)
278 capabilities_display = ", ".join(capabilities) if capabilities else "-"
280 output_lines.append(f"{emoji} {tool_name}: {tool_description}")
281 output_lines.append(f" Capabilities: {capabilities_display}")
283 if show_conflicts:
284 conflict_names = _resolve_conflicts(
285 plugin=plugin,
286 available_tools=available_tools,
287 )
288 if conflict_names:
289 output_lines.append(f" Conflicts with: {', '.join(conflict_names)}")
291 output_lines.append("")
293 summary_border = "-" * 70
294 output_lines.append(summary_border)
295 output_lines.append(f"Total tools: {len(available_tools)}")
296 output_lines.append(f"Check tools: {len(check_tools)}")
297 output_lines.append(f"Fix tools: {len(fix_tools)}")
298 output_lines.append(summary_border)
300 return output_lines