lintro 0.13.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 +226 -16
- 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/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/tool_name.py +2 -0
- lintro/enums/tool_type.py +2 -0
- lintro/formatters/tools/__init__.py +12 -0
- lintro/formatters/tools/eslint_formatter.py +108 -0
- lintro/formatters/tools/markdownlint_formatter.py +88 -0
- lintro/formatters/tools/pytest_formatter.py +201 -0
- lintro/parsers/__init__.py +69 -9
- 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/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/pytest/__init__.py +21 -0
- lintro/parsers/pytest/pytest_issue.py +28 -0
- lintro/parsers/pytest/pytest_parser.py +483 -0
- lintro/tools/__init__.py +2 -0
- lintro/tools/core/timeout_utils.py +112 -0
- lintro/tools/core/tool_base.py +255 -45
- lintro/tools/core/tool_manager.py +77 -24
- lintro/tools/core/version_requirements.py +482 -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 +23 -7
- lintro/tools/implementations/tool_black.py +236 -29
- lintro/tools/implementations/tool_darglint.py +180 -21
- 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 +313 -26
- lintro/tools/implementations/tool_pytest.py +327 -0
- lintro/tools/implementations/tool_ruff.py +247 -70
- lintro/tools/implementations/tool_yamllint.py +448 -34
- lintro/tools/tool_enum.py +6 -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 +336 -39
- lintro/utils/tool_utils.py +38 -2
- lintro/utils/unified_config.py +926 -0
- {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/METADATA +131 -29
- lintro-0.17.2.dist-info/RECORD +134 -0
- lintro-0.13.2.dist-info/RECORD +0 -96
- {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/WHEEL +0 -0
- {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/entry_points.txt +0 -0
- {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/licenses/LICENSE +0 -0
- {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/top_level.txt +0 -0
lintro/__init__.py
CHANGED
lintro/cli.py
CHANGED
|
@@ -1,37 +1,70 @@
|
|
|
1
1
|
"""Command-line interface for Lintro."""
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
from rich.text import Text
|
|
4
9
|
|
|
5
10
|
from lintro import __version__
|
|
6
11
|
from lintro.cli_utils.commands.check import check_command
|
|
12
|
+
from lintro.cli_utils.commands.config import config_command
|
|
7
13
|
from lintro.cli_utils.commands.format import format_code
|
|
14
|
+
from lintro.cli_utils.commands.init import init_command
|
|
8
15
|
from lintro.cli_utils.commands.list_tools import list_tools_command
|
|
16
|
+
from lintro.cli_utils.commands.test import test_command
|
|
17
|
+
from lintro.cli_utils.commands.versions import versions_command
|
|
9
18
|
|
|
10
19
|
|
|
11
20
|
class LintroGroup(click.Group):
|
|
12
|
-
"""Custom Click group with enhanced help rendering.
|
|
21
|
+
"""Custom Click group with enhanced help rendering and command chaining.
|
|
13
22
|
|
|
14
23
|
This group prints command aliases alongside their canonical names to make
|
|
15
|
-
the CLI help output more discoverable.
|
|
24
|
+
the CLI help output more discoverable. It also supports command chaining
|
|
25
|
+
with comma-separated commands (e.g., lintro fmt , chk , tst).
|
|
16
26
|
"""
|
|
17
27
|
|
|
18
|
-
def
|
|
28
|
+
def format_help(
|
|
19
29
|
self,
|
|
20
30
|
ctx: click.Context,
|
|
21
31
|
formatter: click.HelpFormatter,
|
|
22
32
|
) -> None:
|
|
23
|
-
"""Render
|
|
33
|
+
"""Render help with Rich formatting.
|
|
24
34
|
|
|
25
35
|
Args:
|
|
26
36
|
ctx: click.Context: The Click context.
|
|
27
|
-
formatter: click.HelpFormatter: The help formatter
|
|
37
|
+
formatter: click.HelpFormatter: The help formatter (unused, we use Rich).
|
|
28
38
|
"""
|
|
29
|
-
|
|
39
|
+
console = Console()
|
|
40
|
+
|
|
41
|
+
# Header panel
|
|
42
|
+
header = Text()
|
|
43
|
+
header.append("🔧 Lintro", style="bold cyan")
|
|
44
|
+
header.append(f" v{__version__}", style="dim")
|
|
45
|
+
console.print(Panel(header, border_style="cyan"))
|
|
46
|
+
console.print()
|
|
47
|
+
|
|
48
|
+
# Description
|
|
49
|
+
console.print(
|
|
50
|
+
"[white]Unified CLI for code formatting, linting, "
|
|
51
|
+
"and quality assurance.[/white]",
|
|
52
|
+
)
|
|
53
|
+
console.print()
|
|
54
|
+
|
|
55
|
+
# Usage
|
|
56
|
+
console.print("[bold cyan]Usage:[/bold cyan]")
|
|
57
|
+
console.print(" lintro [OPTIONS] COMMAND [ARGS]...")
|
|
58
|
+
console.print(" lintro COMMAND1 , COMMAND2 , ... [dim](chain commands)[/dim]")
|
|
59
|
+
console.print()
|
|
60
|
+
|
|
61
|
+
# Commands table
|
|
30
62
|
commands = self.list_commands(ctx)
|
|
31
|
-
|
|
32
|
-
canonical_map = {}
|
|
63
|
+
canonical_map: dict[str, tuple[click.Command, list[str]]] = {}
|
|
33
64
|
for name in commands:
|
|
34
65
|
cmd = self.get_command(ctx, name)
|
|
66
|
+
if cmd is None:
|
|
67
|
+
continue
|
|
35
68
|
if not hasattr(cmd, "_canonical_name"):
|
|
36
69
|
cmd._canonical_name = name
|
|
37
70
|
canonical = cmd._canonical_name
|
|
@@ -39,14 +72,180 @@ class LintroGroup(click.Group):
|
|
|
39
72
|
canonical_map[canonical] = (cmd, [])
|
|
40
73
|
if name != canonical:
|
|
41
74
|
canonical_map[canonical][1].append(name)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
75
|
+
|
|
76
|
+
table = Table(title="Commands", show_header=True, header_style="bold cyan")
|
|
77
|
+
table.add_column("Command", style="cyan", no_wrap=True)
|
|
78
|
+
table.add_column("Alias", style="yellow", no_wrap=True)
|
|
79
|
+
table.add_column("Description", style="white")
|
|
80
|
+
|
|
81
|
+
for canonical, (cmd, aliases) in sorted(canonical_map.items()):
|
|
82
|
+
alias_str = ", ".join(aliases) if aliases else "-"
|
|
83
|
+
table.add_row(canonical, alias_str, cmd.get_short_help_str())
|
|
84
|
+
|
|
85
|
+
console.print(table)
|
|
86
|
+
console.print()
|
|
87
|
+
|
|
88
|
+
# Options
|
|
89
|
+
console.print("[bold cyan]Options:[/bold cyan]")
|
|
90
|
+
console.print(" [yellow]--version[/yellow] Show the version and exit.")
|
|
91
|
+
console.print(" [yellow]--help[/yellow] Show this message and exit.")
|
|
92
|
+
console.print()
|
|
93
|
+
|
|
94
|
+
# Examples
|
|
95
|
+
console.print("[bold cyan]Examples:[/bold cyan]")
|
|
96
|
+
console.print(" [dim]# Check all files[/dim]")
|
|
97
|
+
console.print(" lintro check .")
|
|
98
|
+
console.print()
|
|
99
|
+
console.print(" [dim]# Format and then check[/dim]")
|
|
100
|
+
console.print(" lintro fmt . , chk .")
|
|
101
|
+
console.print()
|
|
102
|
+
console.print(" [dim]# Show tool versions[/dim]")
|
|
103
|
+
console.print(" lintro versions")
|
|
104
|
+
|
|
105
|
+
def format_commands(
|
|
106
|
+
self,
|
|
107
|
+
ctx: click.Context,
|
|
108
|
+
formatter: click.HelpFormatter,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Render command list with aliases in the help output.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
ctx: click.Context: The Click context.
|
|
114
|
+
formatter: click.HelpFormatter: The help formatter to write to.
|
|
115
|
+
"""
|
|
116
|
+
# This is now handled by format_help, but keep for compatibility
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
def invoke(
|
|
120
|
+
self,
|
|
121
|
+
ctx: click.Context,
|
|
122
|
+
) -> int:
|
|
123
|
+
"""Handle command execution with support for command chaining.
|
|
124
|
+
|
|
125
|
+
Supports chaining commands with commas, e.g.: lintro fmt , chk , tst
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
ctx: click.Context: The Click context.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
int: Exit code from command execution.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
KeyboardInterrupt: If the user interrupts command execution.
|
|
135
|
+
SystemExit: If a command exits with a non-zero exit code.
|
|
136
|
+
"""
|
|
137
|
+
all_args = ctx.protected_args + ctx.args
|
|
138
|
+
if all_args:
|
|
139
|
+
# Get set of known command names/aliases
|
|
140
|
+
command_names = set(self.list_commands(ctx))
|
|
141
|
+
normalized_args: list[str] = []
|
|
142
|
+
saw_separator = False
|
|
143
|
+
|
|
144
|
+
for arg in all_args:
|
|
145
|
+
if arg == ",":
|
|
146
|
+
normalized_args.append(arg)
|
|
147
|
+
saw_separator = True
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
if "," in arg:
|
|
151
|
+
# Check if this looks like comma-separated commands
|
|
152
|
+
raw_parts = [part.strip() for part in arg.split(",")]
|
|
153
|
+
# Filter out empty fragments after splitting
|
|
154
|
+
fragments = [part for part in raw_parts if part]
|
|
155
|
+
# Only split if all parts are known commands
|
|
156
|
+
if fragments and all(part in command_names for part in fragments):
|
|
157
|
+
# Split into separate tokens
|
|
158
|
+
for idx, part in enumerate(fragments):
|
|
159
|
+
if part:
|
|
160
|
+
normalized_args.append(part)
|
|
161
|
+
if idx < len(fragments) - 1:
|
|
162
|
+
normalized_args.append(",")
|
|
163
|
+
saw_separator = True
|
|
164
|
+
continue
|
|
165
|
+
# Not all parts are commands, keep as-is (e.g., --tools ruff,bandit)
|
|
166
|
+
normalized_args.append(arg)
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
normalized_args.append(arg)
|
|
170
|
+
|
|
171
|
+
if saw_separator:
|
|
172
|
+
# Parse chained commands from normalized args
|
|
173
|
+
command_groups: list[list[str]] = []
|
|
174
|
+
current_group: list[str] = []
|
|
175
|
+
|
|
176
|
+
for arg in normalized_args:
|
|
177
|
+
if arg == ",":
|
|
178
|
+
if current_group:
|
|
179
|
+
command_groups.append(current_group)
|
|
180
|
+
current_group = []
|
|
181
|
+
continue
|
|
182
|
+
current_group.append(arg)
|
|
183
|
+
|
|
184
|
+
if current_group:
|
|
185
|
+
command_groups.append(current_group)
|
|
186
|
+
|
|
187
|
+
# Execute each command group
|
|
188
|
+
exit_codes: list[int] = []
|
|
189
|
+
for cmd_args in command_groups:
|
|
190
|
+
if not cmd_args:
|
|
191
|
+
continue
|
|
192
|
+
# Create a new context for each command
|
|
193
|
+
ctx_copy = self.make_context(
|
|
194
|
+
ctx.info_name,
|
|
195
|
+
cmd_args,
|
|
196
|
+
parent=ctx,
|
|
197
|
+
allow_extra_args=True,
|
|
198
|
+
allow_interspersed_args=False,
|
|
199
|
+
)
|
|
200
|
+
# Invoke the command
|
|
201
|
+
with ctx_copy.scope() as subctx:
|
|
202
|
+
try:
|
|
203
|
+
result = super().invoke(subctx)
|
|
204
|
+
exit_codes.append(result if isinstance(result, int) else 0)
|
|
205
|
+
except SystemExit as e:
|
|
206
|
+
exit_codes.append(
|
|
207
|
+
(
|
|
208
|
+
e.code
|
|
209
|
+
if isinstance(e.code, int)
|
|
210
|
+
else (0 if e.code is None else 1)
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
except KeyboardInterrupt:
|
|
214
|
+
# Re-raise KeyboardInterrupt to allow normal interruption
|
|
215
|
+
raise
|
|
216
|
+
except Exception as e:
|
|
217
|
+
# Catch all other exceptions to allow command chain to
|
|
218
|
+
# continue
|
|
219
|
+
exit_code = getattr(e, "exit_code", 1)
|
|
220
|
+
exit_codes.append(exit_code)
|
|
221
|
+
# Log the exception with full traceback
|
|
222
|
+
logger.exception(
|
|
223
|
+
(
|
|
224
|
+
f"Error executing command "
|
|
225
|
+
f"'{' '.join(cmd_args)}': {type(e).__name__}: {e}"
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
# Also echo to stderr for immediate user feedback
|
|
229
|
+
click.echo(
|
|
230
|
+
click.style(
|
|
231
|
+
(
|
|
232
|
+
f"Error executing command "
|
|
233
|
+
f"'{' '.join(cmd_args)}': "
|
|
234
|
+
f"{type(e).__name__}: {e}"
|
|
235
|
+
),
|
|
236
|
+
fg="red",
|
|
237
|
+
),
|
|
238
|
+
err=True,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Return aggregated exit code (0 only if all succeeded)
|
|
242
|
+
final_exit_code = max(exit_codes) if exit_codes else 0
|
|
243
|
+
if final_exit_code != 0:
|
|
244
|
+
raise SystemExit(final_exit_code)
|
|
245
|
+
return 0
|
|
246
|
+
|
|
247
|
+
# Normal single command execution
|
|
248
|
+
return super().invoke(ctx)
|
|
50
249
|
|
|
51
250
|
|
|
52
251
|
@click.group(cls=LintroGroup, invoke_without_command=True)
|
|
@@ -58,17 +257,28 @@ def cli() -> None:
|
|
|
58
257
|
|
|
59
258
|
# Register canonical commands and set _canonical_name for help
|
|
60
259
|
check_command._canonical_name = "check"
|
|
260
|
+
config_command._canonical_name = "config"
|
|
61
261
|
format_code._canonical_name = "format"
|
|
262
|
+
init_command._canonical_name = "init"
|
|
263
|
+
test_command._canonical_name = "test"
|
|
62
264
|
list_tools_command._canonical_name = "list-tools"
|
|
265
|
+
versions_command._canonical_name = "versions"
|
|
63
266
|
|
|
64
267
|
cli.add_command(check_command, name="check")
|
|
268
|
+
cli.add_command(config_command, name="config")
|
|
65
269
|
cli.add_command(format_code, name="format")
|
|
270
|
+
cli.add_command(init_command, name="init")
|
|
271
|
+
cli.add_command(test_command, name="test")
|
|
66
272
|
cli.add_command(list_tools_command, name="list-tools")
|
|
273
|
+
cli.add_command(versions_command, name="versions")
|
|
67
274
|
|
|
68
275
|
# Register aliases
|
|
69
276
|
cli.add_command(check_command, name="chk")
|
|
277
|
+
cli.add_command(config_command, name="cfg")
|
|
70
278
|
cli.add_command(format_code, name="fmt")
|
|
279
|
+
cli.add_command(test_command, name="tst")
|
|
71
280
|
cli.add_command(list_tools_command, name="ls")
|
|
281
|
+
cli.add_command(versions_command, name="ver")
|
|
72
282
|
|
|
73
283
|
|
|
74
284
|
def main() -> None:
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from .check import check_command
|
|
4
4
|
from .format import format_code, format_code_legacy
|
|
5
|
+
from .init import init_command
|
|
5
6
|
from .list_tools import list_tools
|
|
6
7
|
|
|
7
|
-
__all__ = [
|
|
8
|
+
__all__ = [
|
|
9
|
+
"check_command",
|
|
10
|
+
"format_code",
|
|
11
|
+
"format_code_legacy",
|
|
12
|
+
"init_command",
|
|
13
|
+
"list_tools",
|
|
14
|
+
]
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""Config command for displaying Lintro configuration status."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from lintro.config import LintroConfig, get_config
|
|
11
|
+
from lintro.utils.unified_config import (
|
|
12
|
+
_load_native_tool_config,
|
|
13
|
+
get_ordered_tools,
|
|
14
|
+
get_tool_priority,
|
|
15
|
+
is_tool_injectable,
|
|
16
|
+
validate_config_consistency,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _get_all_tool_names() -> list[str]:
|
|
21
|
+
"""Get list of all known tool names.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
list[str]: Sorted list of tool names.
|
|
25
|
+
"""
|
|
26
|
+
return [
|
|
27
|
+
"ruff",
|
|
28
|
+
"black",
|
|
29
|
+
"prettier",
|
|
30
|
+
"eslint",
|
|
31
|
+
"yamllint",
|
|
32
|
+
"markdownlint",
|
|
33
|
+
"darglint",
|
|
34
|
+
"bandit",
|
|
35
|
+
"hadolint",
|
|
36
|
+
"actionlint",
|
|
37
|
+
"pytest",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@click.command()
|
|
42
|
+
@click.option(
|
|
43
|
+
"--verbose",
|
|
44
|
+
"-v",
|
|
45
|
+
is_flag=True,
|
|
46
|
+
help="Show detailed configuration including native tool configs.",
|
|
47
|
+
)
|
|
48
|
+
@click.option(
|
|
49
|
+
"--json",
|
|
50
|
+
"json_output",
|
|
51
|
+
is_flag=True,
|
|
52
|
+
help="Output configuration as JSON.",
|
|
53
|
+
)
|
|
54
|
+
def config_command(
|
|
55
|
+
verbose: bool,
|
|
56
|
+
json_output: bool,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Display Lintro configuration status.
|
|
59
|
+
|
|
60
|
+
Shows the unified configuration for all tools including:
|
|
61
|
+
- Config source (.lintro-config.yaml or pyproject.toml)
|
|
62
|
+
- Global settings (line_length, tool ordering strategy)
|
|
63
|
+
- Tool execution order based on configured strategy
|
|
64
|
+
- Per-tool effective configuration
|
|
65
|
+
- Configuration warnings and inconsistencies
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
verbose: Show detailed configuration including native tool configs.
|
|
69
|
+
json_output: Output configuration as JSON.
|
|
70
|
+
"""
|
|
71
|
+
console = Console()
|
|
72
|
+
config = get_config(reload=True)
|
|
73
|
+
|
|
74
|
+
if json_output:
|
|
75
|
+
_output_json(config=config, verbose=verbose)
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
_output_rich(
|
|
79
|
+
console=console,
|
|
80
|
+
config=config,
|
|
81
|
+
verbose=verbose,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _output_json(
|
|
86
|
+
config: LintroConfig,
|
|
87
|
+
verbose: bool = False,
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Output configuration as JSON.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
config: LintroConfig instance from get_config()
|
|
93
|
+
verbose: Include native configs in output when True
|
|
94
|
+
"""
|
|
95
|
+
import json
|
|
96
|
+
|
|
97
|
+
# Get tool order settings
|
|
98
|
+
tool_order = config.execution.tool_order
|
|
99
|
+
if isinstance(tool_order, list):
|
|
100
|
+
order_strategy = "custom"
|
|
101
|
+
custom_order = tool_order
|
|
102
|
+
else:
|
|
103
|
+
order_strategy = tool_order or "priority"
|
|
104
|
+
custom_order = []
|
|
105
|
+
|
|
106
|
+
# Get list of all known tools
|
|
107
|
+
tool_names = _get_all_tool_names()
|
|
108
|
+
ordered_tools = get_ordered_tools(
|
|
109
|
+
tool_names=tool_names,
|
|
110
|
+
tool_order=config.execution.tool_order,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
output = {
|
|
114
|
+
"config_source": config.config_path or "defaults",
|
|
115
|
+
"global_settings": {
|
|
116
|
+
"line_length": config.enforce.line_length,
|
|
117
|
+
"target_python": config.enforce.target_python,
|
|
118
|
+
"tool_order": order_strategy,
|
|
119
|
+
"custom_order": custom_order,
|
|
120
|
+
},
|
|
121
|
+
"execution": {
|
|
122
|
+
"enabled_tools": config.execution.enabled_tools or "all",
|
|
123
|
+
"fail_fast": config.execution.fail_fast,
|
|
124
|
+
"parallel": config.execution.parallel,
|
|
125
|
+
},
|
|
126
|
+
"tool_execution_order": [
|
|
127
|
+
{"tool": t, "priority": get_tool_priority(t)} for t in ordered_tools
|
|
128
|
+
],
|
|
129
|
+
"tool_configs": {},
|
|
130
|
+
"warnings": validate_config_consistency(),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for tool_name in tool_names:
|
|
134
|
+
tool_config = config.get_tool_config(tool_name)
|
|
135
|
+
effective_ll = config.get_effective_line_length(tool_name)
|
|
136
|
+
|
|
137
|
+
tool_output: dict[str, Any] = {
|
|
138
|
+
"enabled": tool_config.enabled,
|
|
139
|
+
"is_injectable": is_tool_injectable(tool_name),
|
|
140
|
+
"effective_line_length": effective_ll,
|
|
141
|
+
"config_source": tool_config.config_source,
|
|
142
|
+
}
|
|
143
|
+
if verbose:
|
|
144
|
+
native = _load_native_tool_config(tool_name)
|
|
145
|
+
tool_output["native_config"] = native if native else None
|
|
146
|
+
tool_output["defaults"] = config.get_tool_defaults(tool_name) or None
|
|
147
|
+
|
|
148
|
+
output["tool_configs"][tool_name] = tool_output
|
|
149
|
+
|
|
150
|
+
print(json.dumps(output, indent=2))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _output_rich(
|
|
154
|
+
console: Console,
|
|
155
|
+
config: LintroConfig,
|
|
156
|
+
verbose: bool,
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Output configuration using Rich formatting.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
console: Rich Console instance
|
|
162
|
+
config: LintroConfig instance from get_config()
|
|
163
|
+
verbose: Whether to show verbose output
|
|
164
|
+
"""
|
|
165
|
+
# Header panel
|
|
166
|
+
console.print(
|
|
167
|
+
Panel.fit(
|
|
168
|
+
"[bold cyan]Lintro Configuration Report[/bold cyan]",
|
|
169
|
+
border_style="cyan",
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
console.print()
|
|
173
|
+
|
|
174
|
+
# Config Source Section
|
|
175
|
+
config_source = config.config_path or "[dim]No config file (using defaults)[/dim]"
|
|
176
|
+
console.print(f"[bold]Config Source:[/bold] {config_source}")
|
|
177
|
+
console.print()
|
|
178
|
+
|
|
179
|
+
# Global Settings Section
|
|
180
|
+
global_table = Table(
|
|
181
|
+
title="Enforce Settings",
|
|
182
|
+
show_header=False,
|
|
183
|
+
box=None,
|
|
184
|
+
)
|
|
185
|
+
global_table.add_column("Setting", style="cyan", width=25)
|
|
186
|
+
global_table.add_column("Value", style="yellow")
|
|
187
|
+
|
|
188
|
+
line_length = config.enforce.line_length
|
|
189
|
+
global_table.add_row(
|
|
190
|
+
"line_length",
|
|
191
|
+
str(line_length) if line_length else "[dim]Not configured[/dim]",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
target_python = config.enforce.target_python
|
|
195
|
+
global_table.add_row(
|
|
196
|
+
"target_python",
|
|
197
|
+
target_python if target_python else "[dim]Not configured[/dim]",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
console.print(global_table)
|
|
201
|
+
console.print()
|
|
202
|
+
|
|
203
|
+
# Execution Settings Section
|
|
204
|
+
exec_table = Table(
|
|
205
|
+
title="Execution Settings",
|
|
206
|
+
show_header=False,
|
|
207
|
+
box=None,
|
|
208
|
+
)
|
|
209
|
+
exec_table.add_column("Setting", style="cyan", width=25)
|
|
210
|
+
exec_table.add_column("Value", style="yellow")
|
|
211
|
+
|
|
212
|
+
tool_order = config.execution.tool_order
|
|
213
|
+
if isinstance(tool_order, list):
|
|
214
|
+
order_strategy = "custom"
|
|
215
|
+
exec_table.add_row("tool_order", order_strategy)
|
|
216
|
+
exec_table.add_row("custom_order", ", ".join(tool_order))
|
|
217
|
+
else:
|
|
218
|
+
exec_table.add_row("tool_order", tool_order or "priority")
|
|
219
|
+
|
|
220
|
+
enabled_tools = config.execution.enabled_tools
|
|
221
|
+
exec_table.add_row(
|
|
222
|
+
"enabled_tools",
|
|
223
|
+
", ".join(enabled_tools) if enabled_tools else "[dim]all[/dim]",
|
|
224
|
+
)
|
|
225
|
+
exec_table.add_row("fail_fast", str(config.execution.fail_fast))
|
|
226
|
+
exec_table.add_row("parallel", str(config.execution.parallel))
|
|
227
|
+
|
|
228
|
+
console.print(exec_table)
|
|
229
|
+
console.print()
|
|
230
|
+
|
|
231
|
+
# Tool Execution Order Section
|
|
232
|
+
tool_names = _get_all_tool_names()
|
|
233
|
+
ordered_tools = get_ordered_tools(
|
|
234
|
+
tool_names=tool_names,
|
|
235
|
+
tool_order=config.execution.tool_order,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
order_table = Table(title="Tool Execution Order")
|
|
239
|
+
order_table.add_column("#", style="dim", justify="right", width=3)
|
|
240
|
+
order_table.add_column("Tool", style="cyan")
|
|
241
|
+
order_table.add_column("Priority", justify="center", style="yellow")
|
|
242
|
+
order_table.add_column("Type", style="green")
|
|
243
|
+
order_table.add_column("Enabled", justify="center")
|
|
244
|
+
|
|
245
|
+
for idx, tool_name in enumerate(ordered_tools, 1):
|
|
246
|
+
priority = get_tool_priority(tool_name)
|
|
247
|
+
injectable = is_tool_injectable(tool_name)
|
|
248
|
+
tool_type = "Syncable" if injectable else "Native only"
|
|
249
|
+
enabled = config.is_tool_enabled(tool_name)
|
|
250
|
+
enabled_display = "[green]✓[/green]" if enabled else "[red]✗[/red]"
|
|
251
|
+
|
|
252
|
+
order_table.add_row(
|
|
253
|
+
str(idx),
|
|
254
|
+
tool_name,
|
|
255
|
+
str(priority),
|
|
256
|
+
tool_type,
|
|
257
|
+
enabled_display,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
console.print(order_table)
|
|
261
|
+
console.print()
|
|
262
|
+
|
|
263
|
+
# Per-Tool Configuration Section
|
|
264
|
+
config_table = Table(title="Per-Tool Configuration")
|
|
265
|
+
config_table.add_column("Tool", style="cyan")
|
|
266
|
+
config_table.add_column("Sync Status", justify="center")
|
|
267
|
+
config_table.add_column("Line Length", justify="center", style="yellow")
|
|
268
|
+
config_table.add_column("Config Source", style="dim")
|
|
269
|
+
|
|
270
|
+
if verbose:
|
|
271
|
+
config_table.add_column("Native Config", style="dim")
|
|
272
|
+
|
|
273
|
+
for tool_name in tool_names:
|
|
274
|
+
tool_config = config.get_tool_config(tool_name)
|
|
275
|
+
injectable = is_tool_injectable(tool_name)
|
|
276
|
+
status = (
|
|
277
|
+
"[green]✓ Syncable[/green]"
|
|
278
|
+
if injectable
|
|
279
|
+
else "[yellow]⚠ Native only[/yellow]"
|
|
280
|
+
)
|
|
281
|
+
effective_ll = config.get_effective_line_length(tool_name)
|
|
282
|
+
ll_display = str(effective_ll) if effective_ll else "[dim]default[/dim]"
|
|
283
|
+
|
|
284
|
+
cfg_source = tool_config.config_source or "[dim]auto[/dim]"
|
|
285
|
+
|
|
286
|
+
row = [tool_name, status, ll_display, cfg_source]
|
|
287
|
+
|
|
288
|
+
if verbose:
|
|
289
|
+
native = _load_native_tool_config(tool_name)
|
|
290
|
+
native_cfg = str(native) if native else "[dim]None[/dim]"
|
|
291
|
+
row.append(native_cfg)
|
|
292
|
+
|
|
293
|
+
config_table.add_row(*row)
|
|
294
|
+
|
|
295
|
+
console.print(config_table)
|
|
296
|
+
console.print()
|
|
297
|
+
|
|
298
|
+
# Warnings Section
|
|
299
|
+
warnings = validate_config_consistency()
|
|
300
|
+
if warnings:
|
|
301
|
+
console.print("[bold red]Configuration Warnings[/bold red]")
|
|
302
|
+
for warning in warnings:
|
|
303
|
+
console.print(f" [yellow]⚠️[/yellow] {warning}")
|
|
304
|
+
console.print()
|
|
305
|
+
console.print(
|
|
306
|
+
"[dim]Tools marked 'Native only' cannot be configured by Lintro. "
|
|
307
|
+
"Update their config files manually for consistency.[/dim]",
|
|
308
|
+
)
|
|
309
|
+
else:
|
|
310
|
+
console.print(
|
|
311
|
+
"[green]✅ All configurations are consistent![/green]",
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
console.print()
|
|
315
|
+
|
|
316
|
+
# Help text
|
|
317
|
+
console.print(
|
|
318
|
+
"[dim]Configure Lintro in .lintro-config.yaml:[/dim]",
|
|
319
|
+
)
|
|
320
|
+
console.print(
|
|
321
|
+
"[dim] Run 'lintro init' to create a config file[/dim]",
|
|
322
|
+
)
|
|
323
|
+
console.print(
|
|
324
|
+
'[dim] tool_order: "priority" | "alphabetical" | ["tool1", "tool2"][/dim]',
|
|
325
|
+
)
|