lintro 0.6.2__py3-none-any.whl → 0.17.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lintro/__init__.py +1 -1
- lintro/cli.py +230 -14
- lintro/cli_utils/commands/__init__.py +8 -1
- lintro/cli_utils/commands/check.py +1 -0
- lintro/cli_utils/commands/config.py +325 -0
- lintro/cli_utils/commands/format.py +2 -2
- lintro/cli_utils/commands/init.py +361 -0
- lintro/cli_utils/commands/list_tools.py +180 -42
- lintro/cli_utils/commands/test.py +316 -0
- lintro/cli_utils/commands/versions.py +81 -0
- lintro/config/__init__.py +62 -0
- lintro/config/config_loader.py +420 -0
- lintro/config/lintro_config.py +189 -0
- lintro/config/tool_config_generator.py +403 -0
- lintro/enums/__init__.py +1 -0
- lintro/enums/darglint_strictness.py +10 -0
- lintro/enums/hadolint_enums.py +22 -0
- lintro/enums/tool_name.py +2 -0
- lintro/enums/tool_type.py +2 -0
- lintro/enums/yamllint_format.py +11 -0
- lintro/exceptions/__init__.py +1 -0
- lintro/formatters/__init__.py +1 -0
- lintro/formatters/core/__init__.py +1 -0
- lintro/formatters/core/output_style.py +11 -0
- lintro/formatters/core/table_descriptor.py +8 -0
- lintro/formatters/styles/csv.py +2 -0
- lintro/formatters/styles/grid.py +2 -0
- lintro/formatters/styles/html.py +2 -0
- lintro/formatters/styles/json.py +2 -0
- lintro/formatters/styles/markdown.py +2 -0
- lintro/formatters/styles/plain.py +2 -0
- lintro/formatters/tools/__init__.py +12 -0
- lintro/formatters/tools/black_formatter.py +27 -5
- lintro/formatters/tools/darglint_formatter.py +16 -1
- lintro/formatters/tools/eslint_formatter.py +108 -0
- lintro/formatters/tools/hadolint_formatter.py +13 -0
- lintro/formatters/tools/markdownlint_formatter.py +88 -0
- lintro/formatters/tools/prettier_formatter.py +15 -0
- lintro/formatters/tools/pytest_formatter.py +201 -0
- lintro/formatters/tools/ruff_formatter.py +26 -5
- lintro/formatters/tools/yamllint_formatter.py +14 -1
- lintro/models/__init__.py +1 -0
- lintro/models/core/__init__.py +1 -0
- lintro/models/core/tool_config.py +11 -7
- lintro/parsers/__init__.py +69 -9
- lintro/parsers/actionlint/actionlint_parser.py +1 -1
- lintro/parsers/bandit/__init__.py +6 -0
- lintro/parsers/bandit/bandit_issue.py +49 -0
- lintro/parsers/bandit/bandit_parser.py +99 -0
- lintro/parsers/black/black_issue.py +4 -0
- lintro/parsers/darglint/__init__.py +1 -0
- lintro/parsers/darglint/darglint_issue.py +11 -0
- lintro/parsers/eslint/__init__.py +6 -0
- lintro/parsers/eslint/eslint_issue.py +26 -0
- lintro/parsers/eslint/eslint_parser.py +63 -0
- lintro/parsers/markdownlint/__init__.py +6 -0
- lintro/parsers/markdownlint/markdownlint_issue.py +22 -0
- lintro/parsers/markdownlint/markdownlint_parser.py +113 -0
- lintro/parsers/prettier/__init__.py +1 -0
- lintro/parsers/prettier/prettier_issue.py +12 -0
- lintro/parsers/prettier/prettier_parser.py +1 -1
- lintro/parsers/pytest/__init__.py +21 -0
- lintro/parsers/pytest/pytest_issue.py +28 -0
- lintro/parsers/pytest/pytest_parser.py +483 -0
- lintro/parsers/ruff/ruff_parser.py +6 -2
- lintro/parsers/yamllint/__init__.py +1 -0
- lintro/tools/__init__.py +3 -1
- lintro/tools/core/__init__.py +1 -0
- lintro/tools/core/timeout_utils.py +112 -0
- lintro/tools/core/tool_base.py +286 -50
- lintro/tools/core/tool_manager.py +77 -24
- lintro/tools/core/version_requirements.py +482 -0
- lintro/tools/implementations/__init__.py +1 -0
- lintro/tools/implementations/pytest/pytest_command_builder.py +311 -0
- lintro/tools/implementations/pytest/pytest_config.py +200 -0
- lintro/tools/implementations/pytest/pytest_error_handler.py +128 -0
- lintro/tools/implementations/pytest/pytest_executor.py +122 -0
- lintro/tools/implementations/pytest/pytest_handlers.py +375 -0
- lintro/tools/implementations/pytest/pytest_option_validators.py +212 -0
- lintro/tools/implementations/pytest/pytest_output_processor.py +408 -0
- lintro/tools/implementations/pytest/pytest_result_processor.py +113 -0
- lintro/tools/implementations/pytest/pytest_utils.py +697 -0
- lintro/tools/implementations/tool_actionlint.py +106 -16
- lintro/tools/implementations/tool_bandit.py +34 -29
- lintro/tools/implementations/tool_black.py +236 -29
- lintro/tools/implementations/tool_darglint.py +183 -22
- lintro/tools/implementations/tool_eslint.py +374 -0
- lintro/tools/implementations/tool_hadolint.py +94 -25
- lintro/tools/implementations/tool_markdownlint.py +354 -0
- lintro/tools/implementations/tool_prettier.py +317 -24
- lintro/tools/implementations/tool_pytest.py +327 -0
- lintro/tools/implementations/tool_ruff.py +278 -84
- lintro/tools/implementations/tool_yamllint.py +448 -34
- lintro/tools/tool_enum.py +8 -0
- lintro/utils/__init__.py +1 -0
- lintro/utils/ascii_normalize_cli.py +5 -0
- lintro/utils/config.py +41 -18
- lintro/utils/console_logger.py +211 -25
- lintro/utils/path_utils.py +42 -0
- lintro/utils/tool_executor.py +339 -45
- lintro/utils/tool_utils.py +51 -24
- lintro/utils/unified_config.py +926 -0
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/METADATA +172 -30
- lintro-0.17.2.dist-info/RECORD +134 -0
- lintro-0.6.2.dist-info/RECORD +0 -96
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/WHEEL +0 -0
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/entry_points.txt +0 -0
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/licenses/LICENSE +0 -0
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""Init command for Lintro.
|
|
2
|
+
|
|
3
|
+
Creates configuration files for Lintro and optionally native tool configs.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from loguru import logger
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
|
|
14
|
+
# Default Lintro config template
|
|
15
|
+
DEFAULT_CONFIG_TEMPLATE = """\
|
|
16
|
+
# Lintro Configuration
|
|
17
|
+
# https://github.com/TurboCoder13/py-lintro
|
|
18
|
+
#
|
|
19
|
+
# Lintro acts as the master configuration source for all tools.
|
|
20
|
+
# Native tool configs (e.g., .prettierrc) are ignored by default unless
|
|
21
|
+
# explicitly referenced via config_source.
|
|
22
|
+
|
|
23
|
+
enforce:
|
|
24
|
+
# Line length limit applied to all supporting tools
|
|
25
|
+
# Maps to: ruff line-length, black line-length, prettier printWidth, etc.
|
|
26
|
+
line_length: 88
|
|
27
|
+
|
|
28
|
+
# Python version target (e.g., "py313", "py312", "py310")
|
|
29
|
+
# Maps to: ruff target-version, black target-version
|
|
30
|
+
# Omit to let tools infer from requires-python in pyproject.toml
|
|
31
|
+
# target_python: "py310"
|
|
32
|
+
|
|
33
|
+
execution:
|
|
34
|
+
# List of tools to run (empty = all available tools)
|
|
35
|
+
# enabled_tools: ["ruff", "prettier", "markdownlint", "yamllint"]
|
|
36
|
+
enabled_tools: []
|
|
37
|
+
|
|
38
|
+
# Execution order strategy:
|
|
39
|
+
# - "priority": Formatters before linters (default)
|
|
40
|
+
# - "alphabetical": Alphabetical order
|
|
41
|
+
# - ["tool1", "tool2", ...]: Custom order
|
|
42
|
+
tool_order: "priority"
|
|
43
|
+
|
|
44
|
+
# Stop on first tool failure
|
|
45
|
+
fail_fast: false
|
|
46
|
+
|
|
47
|
+
tools:
|
|
48
|
+
# Ruff - Python linter and formatter
|
|
49
|
+
ruff:
|
|
50
|
+
enabled: true
|
|
51
|
+
# config_source: ".ruff.toml" # Optional: inherit from native config
|
|
52
|
+
# Settings are passed directly to Ruff
|
|
53
|
+
# select: ["E", "F", "W", "I"]
|
|
54
|
+
# ignore: ["E501"]
|
|
55
|
+
|
|
56
|
+
# Black - Python formatter
|
|
57
|
+
black:
|
|
58
|
+
enabled: true
|
|
59
|
+
# config_source: "pyproject.toml" # Optional: use [tool.black] section
|
|
60
|
+
|
|
61
|
+
# Prettier - Multi-language formatter
|
|
62
|
+
prettier:
|
|
63
|
+
enabled: true
|
|
64
|
+
# config_source: ".prettierrc" # Optional: inherit from native config
|
|
65
|
+
# overrides:
|
|
66
|
+
# printWidth: 88 # Override to match enforce line_length
|
|
67
|
+
|
|
68
|
+
# Markdownlint - Markdown linter
|
|
69
|
+
markdownlint:
|
|
70
|
+
enabled: true
|
|
71
|
+
# MD013 line_length is automatically synced from enforce.line_length
|
|
72
|
+
|
|
73
|
+
# Yamllint - YAML linter
|
|
74
|
+
yamllint:
|
|
75
|
+
enabled: true
|
|
76
|
+
|
|
77
|
+
# Bandit - Security linter
|
|
78
|
+
bandit:
|
|
79
|
+
enabled: true
|
|
80
|
+
|
|
81
|
+
# Hadolint - Dockerfile linter
|
|
82
|
+
hadolint:
|
|
83
|
+
enabled: true
|
|
84
|
+
|
|
85
|
+
# Actionlint - GitHub Actions linter
|
|
86
|
+
actionlint:
|
|
87
|
+
enabled: true
|
|
88
|
+
|
|
89
|
+
# Darglint - Docstring linter
|
|
90
|
+
darglint:
|
|
91
|
+
enabled: true
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
MINIMAL_CONFIG_TEMPLATE = """\
|
|
95
|
+
# Lintro Configuration (Minimal)
|
|
96
|
+
# https://github.com/TurboCoder13/py-lintro
|
|
97
|
+
|
|
98
|
+
enforce:
|
|
99
|
+
line_length: 88
|
|
100
|
+
# target_python: "py310" # Omit to infer from requires-python
|
|
101
|
+
|
|
102
|
+
execution:
|
|
103
|
+
tool_order: "priority"
|
|
104
|
+
|
|
105
|
+
tools:
|
|
106
|
+
ruff:
|
|
107
|
+
enabled: true
|
|
108
|
+
black:
|
|
109
|
+
enabled: true
|
|
110
|
+
prettier:
|
|
111
|
+
enabled: true
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
# Native config templates
|
|
115
|
+
PRETTIERRC_TEMPLATE = {
|
|
116
|
+
"semi": True,
|
|
117
|
+
"singleQuote": True,
|
|
118
|
+
"tabWidth": 2,
|
|
119
|
+
"printWidth": 88,
|
|
120
|
+
"trailingComma": "es5",
|
|
121
|
+
"proseWrap": "always",
|
|
122
|
+
"overrides": [
|
|
123
|
+
{
|
|
124
|
+
"files": "*.md",
|
|
125
|
+
"options": {
|
|
126
|
+
"printWidth": 88,
|
|
127
|
+
"proseWrap": "always",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
PRETTIERIGNORE_TEMPLATE = """\
|
|
134
|
+
# Ignore artifacts
|
|
135
|
+
build
|
|
136
|
+
dist
|
|
137
|
+
node_modules
|
|
138
|
+
.venv
|
|
139
|
+
.lintro
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
MARKDOWNLINT_TEMPLATE = {
|
|
143
|
+
"config": {
|
|
144
|
+
"MD013": {
|
|
145
|
+
"line_length": 88,
|
|
146
|
+
"code_blocks": False,
|
|
147
|
+
"tables": False,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _write_file(
|
|
154
|
+
path: Path,
|
|
155
|
+
content: str,
|
|
156
|
+
console: Console,
|
|
157
|
+
force: bool,
|
|
158
|
+
) -> bool:
|
|
159
|
+
"""Write content to a file, handling existing files.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
path: Path to write to.
|
|
163
|
+
content: Content to write.
|
|
164
|
+
console: Rich console for output.
|
|
165
|
+
force: Whether to overwrite existing files.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
bool: True if file was written, False if skipped.
|
|
169
|
+
"""
|
|
170
|
+
if path.exists() and not force:
|
|
171
|
+
console.print(f" [yellow]⏭️ Skipped {path} (already exists)[/yellow]")
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
path.write_text(content, encoding="utf-8")
|
|
176
|
+
console.print(f" [green]✅ Created {path}[/green]")
|
|
177
|
+
return True
|
|
178
|
+
except (OSError, Exception) as e:
|
|
179
|
+
console.print(f" [red]❌ Failed to write {path}: {e}[/red]")
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _write_json_file(
|
|
184
|
+
path: Path,
|
|
185
|
+
data: dict,
|
|
186
|
+
console: Console,
|
|
187
|
+
force: bool,
|
|
188
|
+
) -> bool:
|
|
189
|
+
"""Write JSON content to a file, handling existing files.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
path: Path to write to.
|
|
193
|
+
data: Dictionary to serialize as JSON.
|
|
194
|
+
console: Rich console for output.
|
|
195
|
+
force: Whether to overwrite existing files.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
bool: True if file was written, False if skipped.
|
|
199
|
+
"""
|
|
200
|
+
content = json.dumps(obj=data, indent=2) + "\n"
|
|
201
|
+
return _write_file(path=path, content=content, console=console, force=force)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _generate_native_configs(
|
|
205
|
+
console: Console,
|
|
206
|
+
force: bool,
|
|
207
|
+
) -> list[str]:
|
|
208
|
+
"""Generate native tool configuration files.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
console: Rich console for output.
|
|
212
|
+
force: Whether to overwrite existing files.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
list[str]: List of created file names.
|
|
216
|
+
"""
|
|
217
|
+
created: list[str] = []
|
|
218
|
+
|
|
219
|
+
console.print("\n[bold cyan]Generating native tool configs:[/bold cyan]")
|
|
220
|
+
|
|
221
|
+
# Prettier config
|
|
222
|
+
if _write_json_file(
|
|
223
|
+
path=Path(".prettierrc.json"),
|
|
224
|
+
data=PRETTIERRC_TEMPLATE,
|
|
225
|
+
console=console,
|
|
226
|
+
force=force,
|
|
227
|
+
):
|
|
228
|
+
created.append(".prettierrc.json")
|
|
229
|
+
|
|
230
|
+
# Prettier ignore
|
|
231
|
+
if _write_file(
|
|
232
|
+
path=Path(".prettierignore"),
|
|
233
|
+
content=PRETTIERIGNORE_TEMPLATE,
|
|
234
|
+
console=console,
|
|
235
|
+
force=force,
|
|
236
|
+
):
|
|
237
|
+
created.append(".prettierignore")
|
|
238
|
+
|
|
239
|
+
# Markdownlint config
|
|
240
|
+
if _write_json_file(
|
|
241
|
+
path=Path(".markdownlint-cli2.jsonc"),
|
|
242
|
+
data=MARKDOWNLINT_TEMPLATE,
|
|
243
|
+
console=console,
|
|
244
|
+
force=force,
|
|
245
|
+
):
|
|
246
|
+
created.append(".markdownlint-cli2.jsonc")
|
|
247
|
+
|
|
248
|
+
return created
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@click.command("init")
|
|
252
|
+
@click.option(
|
|
253
|
+
"--minimal",
|
|
254
|
+
"-m",
|
|
255
|
+
is_flag=True,
|
|
256
|
+
help="Create a minimal config file with fewer comments.",
|
|
257
|
+
)
|
|
258
|
+
@click.option(
|
|
259
|
+
"--force",
|
|
260
|
+
"-f",
|
|
261
|
+
is_flag=True,
|
|
262
|
+
help="Overwrite existing configuration files.",
|
|
263
|
+
)
|
|
264
|
+
@click.option(
|
|
265
|
+
"--output",
|
|
266
|
+
"-o",
|
|
267
|
+
type=click.Path(),
|
|
268
|
+
default=".lintro-config.yaml",
|
|
269
|
+
help="Output file path (default: .lintro-config.yaml).",
|
|
270
|
+
)
|
|
271
|
+
@click.option(
|
|
272
|
+
"--with-native-configs",
|
|
273
|
+
is_flag=True,
|
|
274
|
+
help="Also generate native tool configs (.prettierrc.json, etc.).",
|
|
275
|
+
)
|
|
276
|
+
def init_command(
|
|
277
|
+
minimal: bool,
|
|
278
|
+
force: bool,
|
|
279
|
+
output: str,
|
|
280
|
+
with_native_configs: bool,
|
|
281
|
+
) -> None:
|
|
282
|
+
"""Initialize Lintro configuration for your project.
|
|
283
|
+
|
|
284
|
+
Creates a scaffold configuration file with sensible defaults.
|
|
285
|
+
Lintro will use this file as the master configuration source,
|
|
286
|
+
ignoring native tool configs unless explicitly referenced.
|
|
287
|
+
|
|
288
|
+
Use --with-native-configs to also generate native tool configuration
|
|
289
|
+
files for IDE integration (e.g., Prettier extension, markdownlint extension).
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
minimal: Use minimal template with fewer comments.
|
|
293
|
+
force: Overwrite existing config file if it exists.
|
|
294
|
+
output: Output file path for the config file.
|
|
295
|
+
with_native_configs: Also generate native tool config files.
|
|
296
|
+
|
|
297
|
+
Raises:
|
|
298
|
+
SystemExit: If file exists and --force not provided, or write fails.
|
|
299
|
+
"""
|
|
300
|
+
console = Console()
|
|
301
|
+
output_path = Path(output)
|
|
302
|
+
created_files: list[str] = []
|
|
303
|
+
|
|
304
|
+
# Check if main config file already exists
|
|
305
|
+
if output_path.exists() and not force:
|
|
306
|
+
console.print(
|
|
307
|
+
f"[red]Error: {output_path} already exists. "
|
|
308
|
+
"Use --force to overwrite.[/red]",
|
|
309
|
+
)
|
|
310
|
+
raise SystemExit(1)
|
|
311
|
+
|
|
312
|
+
# Select template
|
|
313
|
+
template = MINIMAL_CONFIG_TEMPLATE if minimal else DEFAULT_CONFIG_TEMPLATE
|
|
314
|
+
|
|
315
|
+
# Write main config file
|
|
316
|
+
try:
|
|
317
|
+
output_path.write_text(template, encoding="utf-8")
|
|
318
|
+
created_files.append(str(output_path))
|
|
319
|
+
logger.debug(f"Created config file: {output_path.resolve()}")
|
|
320
|
+
|
|
321
|
+
except (OSError, PermissionError) as e:
|
|
322
|
+
console.print(f"[red]Error: Failed to write {output_path}: {e}[/red]")
|
|
323
|
+
raise SystemExit(1) from e
|
|
324
|
+
|
|
325
|
+
# Generate native configs if requested
|
|
326
|
+
if with_native_configs:
|
|
327
|
+
native_files = _generate_native_configs(console=console, force=force)
|
|
328
|
+
created_files.extend(native_files)
|
|
329
|
+
|
|
330
|
+
# Success panel
|
|
331
|
+
console.print()
|
|
332
|
+
if len(created_files) == 1:
|
|
333
|
+
console.print(
|
|
334
|
+
Panel.fit(
|
|
335
|
+
f"[bold green]✅ Created {output_path}[/bold green]",
|
|
336
|
+
border_style="green",
|
|
337
|
+
),
|
|
338
|
+
)
|
|
339
|
+
else:
|
|
340
|
+
files_list = "\n".join(f" • {f}" for f in created_files)
|
|
341
|
+
msg = f"[bold green]✅ Created {len(created_files)} files:[/bold green]"
|
|
342
|
+
console.print(
|
|
343
|
+
Panel.fit(
|
|
344
|
+
f"{msg}\n{files_list}",
|
|
345
|
+
border_style="green",
|
|
346
|
+
),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
console.print()
|
|
350
|
+
|
|
351
|
+
# Next steps
|
|
352
|
+
console.print("[bold cyan]Next steps:[/bold cyan]")
|
|
353
|
+
console.print(" [dim]1.[/dim] Review and customize the configuration")
|
|
354
|
+
console.print(
|
|
355
|
+
" [dim]2.[/dim] Run [cyan]lintro config[/cyan] to view config",
|
|
356
|
+
)
|
|
357
|
+
console.print(" [dim]3.[/dim] Run [cyan]lintro check .[/cyan] to lint")
|
|
358
|
+
if with_native_configs:
|
|
359
|
+
console.print(
|
|
360
|
+
" [dim]3.[/dim] Commit the config files to your repository",
|
|
361
|
+
)
|
|
@@ -3,11 +3,46 @@
|
|
|
3
3
|
This module provides the core logic for the 'list_tools' command.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
6
8
|
import click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
7
12
|
|
|
8
13
|
from lintro.enums.action import Action
|
|
9
14
|
from lintro.tools import tool_manager
|
|
15
|
+
from lintro.tools.tool_enum import ToolEnum
|
|
10
16
|
from lintro.utils.console_logger import get_tool_emoji
|
|
17
|
+
from lintro.utils.unified_config import get_tool_priority, is_tool_injectable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _resolve_conflicts(
|
|
21
|
+
tool_config: Any,
|
|
22
|
+
name_to_enum_map: dict[str, ToolEnum],
|
|
23
|
+
available_tools: dict[ToolEnum, Any],
|
|
24
|
+
) -> list[str]:
|
|
25
|
+
"""Resolve conflict names for a tool.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
tool_config: Tool configuration object.
|
|
29
|
+
name_to_enum_map: Mapping of tool names to ToolEnum.
|
|
30
|
+
available_tools: Dictionary of available tools.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of conflict tool names.
|
|
34
|
+
"""
|
|
35
|
+
conflict_names: list[str] = []
|
|
36
|
+
if hasattr(tool_config, "conflicts_with") and tool_config.conflicts_with:
|
|
37
|
+
for conflict in tool_config.conflicts_with:
|
|
38
|
+
conflict_enum: ToolEnum | None = None
|
|
39
|
+
if isinstance(conflict, str):
|
|
40
|
+
conflict_enum = name_to_enum_map.get(conflict.lower())
|
|
41
|
+
elif isinstance(conflict, ToolEnum):
|
|
42
|
+
conflict_enum = conflict
|
|
43
|
+
if conflict_enum is not None and conflict_enum in available_tools:
|
|
44
|
+
conflict_names.append(conflict_enum.name.lower())
|
|
45
|
+
return conflict_names
|
|
11
46
|
|
|
12
47
|
|
|
13
48
|
@click.command("list-tools")
|
|
@@ -44,24 +79,145 @@ def list_tools(
|
|
|
44
79
|
output: Output file path.
|
|
45
80
|
show_conflicts: Whether to show potential conflicts between tools.
|
|
46
81
|
"""
|
|
82
|
+
console = Console()
|
|
47
83
|
available_tools = tool_manager.get_available_tools()
|
|
48
84
|
check_tools = tool_manager.get_check_tools()
|
|
49
85
|
fix_tools = tool_manager.get_fix_tools()
|
|
50
86
|
|
|
51
|
-
|
|
87
|
+
# Header panel
|
|
88
|
+
console.print(
|
|
89
|
+
Panel.fit(
|
|
90
|
+
"[bold cyan]🔧 Available Tools[/bold cyan]",
|
|
91
|
+
border_style="cyan",
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
console.print()
|
|
95
|
+
|
|
96
|
+
# Main tools table
|
|
97
|
+
table = Table(title="Tool Details")
|
|
98
|
+
table.add_column("Tool", style="cyan", no_wrap=True)
|
|
99
|
+
table.add_column("Description", style="white", max_width=40)
|
|
100
|
+
table.add_column("Capabilities", style="green")
|
|
101
|
+
table.add_column("Priority", justify="center", style="yellow")
|
|
102
|
+
table.add_column("Type", style="magenta")
|
|
103
|
+
|
|
104
|
+
if show_conflicts:
|
|
105
|
+
table.add_column("Conflicts", style="red")
|
|
106
|
+
|
|
107
|
+
# Build name-to-enum map for conflict resolution
|
|
108
|
+
name_to_enum_map = {t.name.lower(): t for t in ToolEnum}
|
|
109
|
+
|
|
110
|
+
for tool_enum, tool in available_tools.items():
|
|
111
|
+
tool_name = tool_enum.name.lower()
|
|
112
|
+
tool_description = getattr(
|
|
113
|
+
tool.config,
|
|
114
|
+
"description",
|
|
115
|
+
tool.__class__.__name__,
|
|
116
|
+
)
|
|
117
|
+
emoji = get_tool_emoji(tool_name)
|
|
118
|
+
|
|
119
|
+
# Capabilities
|
|
120
|
+
capabilities: list[str] = []
|
|
121
|
+
if tool_enum in check_tools:
|
|
122
|
+
capabilities.append("check")
|
|
123
|
+
if tool_enum in fix_tools:
|
|
124
|
+
capabilities.append("fix")
|
|
125
|
+
caps_display = ", ".join(capabilities) if capabilities else "-"
|
|
126
|
+
|
|
127
|
+
# Priority and type
|
|
128
|
+
priority = get_tool_priority(tool_name)
|
|
129
|
+
injectable = is_tool_injectable(tool_name)
|
|
130
|
+
tool_type = "Syncable" if injectable else "Native only"
|
|
131
|
+
|
|
132
|
+
row = [
|
|
133
|
+
f"{emoji} {tool_name}",
|
|
134
|
+
tool_description,
|
|
135
|
+
caps_display,
|
|
136
|
+
str(priority),
|
|
137
|
+
tool_type,
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
# Conflicts
|
|
141
|
+
if show_conflicts:
|
|
142
|
+
conflict_names = _resolve_conflicts(
|
|
143
|
+
tool_config=tool.config,
|
|
144
|
+
name_to_enum_map=name_to_enum_map,
|
|
145
|
+
available_tools=available_tools,
|
|
146
|
+
)
|
|
147
|
+
row.append(", ".join(conflict_names) if conflict_names else "-")
|
|
148
|
+
|
|
149
|
+
table.add_row(*row)
|
|
150
|
+
|
|
151
|
+
console.print(table)
|
|
152
|
+
console.print()
|
|
153
|
+
|
|
154
|
+
# Summary table
|
|
155
|
+
summary_table = Table(
|
|
156
|
+
title="Summary",
|
|
157
|
+
show_header=False,
|
|
158
|
+
box=None,
|
|
159
|
+
)
|
|
160
|
+
summary_table.add_column("Metric", style="cyan", width=20)
|
|
161
|
+
summary_table.add_column("Count", style="yellow", justify="right")
|
|
162
|
+
|
|
163
|
+
summary_table.add_row("📊 Total tools", str(len(available_tools)))
|
|
164
|
+
summary_table.add_row("🔍 Check tools", str(len(check_tools)))
|
|
165
|
+
summary_table.add_row("🔧 Fix tools", str(len(fix_tools)))
|
|
166
|
+
|
|
167
|
+
console.print(summary_table)
|
|
168
|
+
|
|
169
|
+
# Write to file if specified
|
|
170
|
+
if output:
|
|
171
|
+
try:
|
|
172
|
+
# For file output, use plain text format
|
|
173
|
+
output_lines = _generate_plain_text_output(
|
|
174
|
+
available_tools=available_tools,
|
|
175
|
+
check_tools=check_tools,
|
|
176
|
+
fix_tools=fix_tools,
|
|
177
|
+
show_conflicts=show_conflicts,
|
|
178
|
+
)
|
|
179
|
+
with open(output, "w", encoding="utf-8") as f:
|
|
180
|
+
f.write("\n".join(output_lines) + "\n")
|
|
181
|
+
console.print()
|
|
182
|
+
console.print(f"[green]✅ Output written to: {output}[/green]")
|
|
183
|
+
except OSError as e:
|
|
184
|
+
console.print(f"[red]Error writing to file {output}: {e}[/red]")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _generate_plain_text_output(
|
|
188
|
+
available_tools: dict[ToolEnum, Any],
|
|
189
|
+
check_tools: dict[ToolEnum, Any],
|
|
190
|
+
fix_tools: dict[ToolEnum, Any],
|
|
191
|
+
show_conflicts: bool,
|
|
192
|
+
) -> list[str]:
|
|
193
|
+
"""Generate plain text output for file writing.
|
|
52
194
|
|
|
53
|
-
|
|
195
|
+
Args:
|
|
196
|
+
available_tools: Dictionary of available tools.
|
|
197
|
+
check_tools: Dictionary of check-capable tools.
|
|
198
|
+
fix_tools: Dictionary of fix-capable tools.
|
|
199
|
+
show_conflicts: Whether to include conflict information.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
List of output lines.
|
|
203
|
+
"""
|
|
204
|
+
output_lines: list[str] = []
|
|
54
205
|
border = "=" * 70
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
output_lines.append(
|
|
58
|
-
output_lines.append(
|
|
59
|
-
output_lines.append(f"{border}")
|
|
206
|
+
|
|
207
|
+
output_lines.append(border)
|
|
208
|
+
output_lines.append("Available Tools")
|
|
209
|
+
output_lines.append(border)
|
|
60
210
|
output_lines.append("")
|
|
61
211
|
|
|
212
|
+
name_to_enum_map = {t.name.lower(): t for t in ToolEnum}
|
|
213
|
+
|
|
62
214
|
for tool_enum, tool in available_tools.items():
|
|
63
215
|
tool_name = tool_enum.name.lower()
|
|
64
|
-
tool_description = getattr(
|
|
216
|
+
tool_description = getattr(
|
|
217
|
+
tool.config,
|
|
218
|
+
"description",
|
|
219
|
+
tool.__class__.__name__,
|
|
220
|
+
)
|
|
65
221
|
emoji = get_tool_emoji(tool_name)
|
|
66
222
|
|
|
67
223
|
capabilities: list[str] = []
|
|
@@ -70,45 +226,27 @@ def list_tools(
|
|
|
70
226
|
if tool_enum in fix_tools:
|
|
71
227
|
capabilities.append(Action.FIX.value)
|
|
72
228
|
|
|
229
|
+
capabilities_display = ", ".join(capabilities) if capabilities else "-"
|
|
230
|
+
|
|
73
231
|
output_lines.append(f"{emoji} {tool_name}: {tool_description}")
|
|
74
|
-
output_lines.append(f" Capabilities: {
|
|
75
|
-
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if conflict in available_tools
|
|
85
|
-
]
|
|
86
|
-
if conflicts:
|
|
87
|
-
output_lines.append(f" Conflicts with: {', '.join(conflicts)}")
|
|
232
|
+
output_lines.append(f" Capabilities: {capabilities_display}")
|
|
233
|
+
|
|
234
|
+
if show_conflicts:
|
|
235
|
+
conflict_names = _resolve_conflicts(
|
|
236
|
+
tool_config=tool.config,
|
|
237
|
+
name_to_enum_map=name_to_enum_map,
|
|
238
|
+
available_tools=available_tools,
|
|
239
|
+
)
|
|
240
|
+
if conflict_names:
|
|
241
|
+
output_lines.append(f" Conflicts with: {', '.join(conflict_names)}")
|
|
88
242
|
|
|
89
243
|
output_lines.append("")
|
|
90
244
|
|
|
91
|
-
# Add summary footer
|
|
92
245
|
summary_border = "-" * 70
|
|
93
246
|
output_lines.append(summary_border)
|
|
94
|
-
output_lines.append(f"
|
|
95
|
-
output_lines.append(f"
|
|
96
|
-
output_lines.append(f"
|
|
247
|
+
output_lines.append(f"Total tools: {len(available_tools)}")
|
|
248
|
+
output_lines.append(f"Check tools: {len(check_tools)}")
|
|
249
|
+
output_lines.append(f"Fix tools: {len(fix_tools)}")
|
|
97
250
|
output_lines.append(summary_border)
|
|
98
251
|
|
|
99
|
-
|
|
100
|
-
output_text = "\n".join(output_lines)
|
|
101
|
-
|
|
102
|
-
# Print to console using click.echo for consistency
|
|
103
|
-
click.echo(output_text)
|
|
104
|
-
|
|
105
|
-
# Write to file if specified
|
|
106
|
-
if output:
|
|
107
|
-
try:
|
|
108
|
-
with open(output, "w", encoding="utf-8") as f:
|
|
109
|
-
f.write(output_text + "\n")
|
|
110
|
-
success_msg = f"Output written to: {output}"
|
|
111
|
-
click.echo(success_msg)
|
|
112
|
-
except IOError as e:
|
|
113
|
-
error_msg = f"Error writing to file {output}: {e}"
|
|
114
|
-
click.echo(error_msg, err=True)
|
|
252
|
+
return output_lines
|