lintro 0.3.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.
Potentially problematic release.
This version of lintro might be problematic. Click here for more details.
- lintro/__init__.py +3 -0
- lintro/__main__.py +6 -0
- lintro/ascii-art/fail.txt +404 -0
- lintro/ascii-art/success.txt +484 -0
- lintro/cli.py +70 -0
- lintro/cli_utils/__init__.py +7 -0
- lintro/cli_utils/commands/__init__.py +7 -0
- lintro/cli_utils/commands/check.py +210 -0
- lintro/cli_utils/commands/format.py +167 -0
- lintro/cli_utils/commands/list_tools.py +114 -0
- lintro/enums/__init__.py +0 -0
- lintro/enums/action.py +29 -0
- lintro/enums/darglint_strictness.py +22 -0
- lintro/enums/group_by.py +31 -0
- lintro/enums/hadolint_enums.py +46 -0
- lintro/enums/output_format.py +40 -0
- lintro/enums/tool_name.py +36 -0
- lintro/enums/tool_type.py +27 -0
- lintro/enums/yamllint_format.py +22 -0
- lintro/exceptions/__init__.py +0 -0
- lintro/exceptions/errors.py +15 -0
- lintro/formatters/__init__.py +0 -0
- lintro/formatters/core/__init__.py +0 -0
- lintro/formatters/core/output_style.py +21 -0
- lintro/formatters/core/table_descriptor.py +24 -0
- lintro/formatters/styles/__init__.py +17 -0
- lintro/formatters/styles/csv.py +41 -0
- lintro/formatters/styles/grid.py +91 -0
- lintro/formatters/styles/html.py +48 -0
- lintro/formatters/styles/json.py +61 -0
- lintro/formatters/styles/markdown.py +41 -0
- lintro/formatters/styles/plain.py +39 -0
- lintro/formatters/tools/__init__.py +35 -0
- lintro/formatters/tools/darglint_formatter.py +72 -0
- lintro/formatters/tools/hadolint_formatter.py +84 -0
- lintro/formatters/tools/prettier_formatter.py +76 -0
- lintro/formatters/tools/ruff_formatter.py +116 -0
- lintro/formatters/tools/yamllint_formatter.py +87 -0
- lintro/models/__init__.py +0 -0
- lintro/models/core/__init__.py +0 -0
- lintro/models/core/tool.py +104 -0
- lintro/models/core/tool_config.py +23 -0
- lintro/models/core/tool_result.py +39 -0
- lintro/parsers/__init__.py +0 -0
- lintro/parsers/darglint/__init__.py +0 -0
- lintro/parsers/darglint/darglint_issue.py +9 -0
- lintro/parsers/darglint/darglint_parser.py +62 -0
- lintro/parsers/hadolint/__init__.py +1 -0
- lintro/parsers/hadolint/hadolint_issue.py +24 -0
- lintro/parsers/hadolint/hadolint_parser.py +65 -0
- lintro/parsers/prettier/__init__.py +0 -0
- lintro/parsers/prettier/prettier_issue.py +10 -0
- lintro/parsers/prettier/prettier_parser.py +60 -0
- lintro/parsers/ruff/__init__.py +1 -0
- lintro/parsers/ruff/ruff_issue.py +43 -0
- lintro/parsers/ruff/ruff_parser.py +89 -0
- lintro/parsers/yamllint/__init__.py +0 -0
- lintro/parsers/yamllint/yamllint_issue.py +24 -0
- lintro/parsers/yamllint/yamllint_parser.py +68 -0
- lintro/tools/__init__.py +40 -0
- lintro/tools/core/__init__.py +0 -0
- lintro/tools/core/tool_base.py +320 -0
- lintro/tools/core/tool_manager.py +167 -0
- lintro/tools/implementations/__init__.py +0 -0
- lintro/tools/implementations/tool_darglint.py +245 -0
- lintro/tools/implementations/tool_hadolint.py +302 -0
- lintro/tools/implementations/tool_prettier.py +270 -0
- lintro/tools/implementations/tool_ruff.py +618 -0
- lintro/tools/implementations/tool_yamllint.py +240 -0
- lintro/tools/tool_enum.py +17 -0
- lintro/utils/__init__.py +0 -0
- lintro/utils/ascii_normalize_cli.py +84 -0
- lintro/utils/config.py +39 -0
- lintro/utils/console_logger.py +783 -0
- lintro/utils/formatting.py +173 -0
- lintro/utils/output_manager.py +301 -0
- lintro/utils/path_utils.py +41 -0
- lintro/utils/tool_executor.py +443 -0
- lintro/utils/tool_utils.py +431 -0
- lintro-0.3.2.dist-info/METADATA +338 -0
- lintro-0.3.2.dist-info/RECORD +85 -0
- lintro-0.3.2.dist-info/WHEEL +5 -0
- lintro-0.3.2.dist-info/entry_points.txt +2 -0
- lintro-0.3.2.dist-info/licenses/LICENSE +21 -0
- lintro-0.3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"""Simplified runner for lintro commands.
|
|
2
|
+
|
|
3
|
+
Clean, straightforward approach using Loguru with rich formatting:
|
|
4
|
+
1. OutputManager - handles structured output files only
|
|
5
|
+
2. SimpleLintroLogger - handles console display AND logging with Loguru + rich
|
|
6
|
+
formatting
|
|
7
|
+
3. No tee, no stream redirection, no complex state management
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from lintro.enums.group_by import GroupBy, normalize_group_by
|
|
11
|
+
from lintro.enums.output_format import OutputFormat, normalize_output_format
|
|
12
|
+
from lintro.tools import tool_manager
|
|
13
|
+
from lintro.tools.tool_enum import ToolEnum
|
|
14
|
+
from lintro.utils.config import load_lintro_tool_config
|
|
15
|
+
from lintro.utils.console_logger import create_logger
|
|
16
|
+
from lintro.utils.output_manager import OutputManager
|
|
17
|
+
from lintro.utils.tool_utils import format_tool_output
|
|
18
|
+
|
|
19
|
+
# Constants
|
|
20
|
+
DEFAULT_EXIT_CODE_SUCCESS: int = 0
|
|
21
|
+
DEFAULT_EXIT_CODE_FAILURE: int = 1
|
|
22
|
+
DEFAULT_REMAINING_COUNT: int = 1
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_tools_to_run(
|
|
26
|
+
tools: str | None,
|
|
27
|
+
action: str,
|
|
28
|
+
) -> list[ToolEnum]:
|
|
29
|
+
"""Get the list of tools to run based on the tools string and action.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
tools: str | None: Comma-separated tool names, "all", or None.
|
|
33
|
+
action: str: "check" or "fmt".
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
list[ToolEnum]: List of ToolEnum instances to run.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError: If unknown tool names are provided.
|
|
40
|
+
"""
|
|
41
|
+
if tools == "all" or tools is None:
|
|
42
|
+
# Get all available tools for the action
|
|
43
|
+
if action == "fmt":
|
|
44
|
+
available_tools = tool_manager.get_fix_tools()
|
|
45
|
+
else: # check
|
|
46
|
+
available_tools = tool_manager.get_check_tools()
|
|
47
|
+
return list(available_tools.keys())
|
|
48
|
+
|
|
49
|
+
# Parse specific tools
|
|
50
|
+
tool_names: list[str] = [name.strip().upper() for name in tools.split(",")]
|
|
51
|
+
tools_to_run: list[ToolEnum] = []
|
|
52
|
+
|
|
53
|
+
for name in tool_names:
|
|
54
|
+
try:
|
|
55
|
+
tool_enum = ToolEnum[name]
|
|
56
|
+
# Verify the tool supports the requested action
|
|
57
|
+
if action == "fmt":
|
|
58
|
+
tool_instance = tool_manager.get_tool(tool_enum)
|
|
59
|
+
if not tool_instance.can_fix:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Tool '{name.lower()}' does not support formatting",
|
|
62
|
+
)
|
|
63
|
+
tools_to_run.append(tool_enum)
|
|
64
|
+
except KeyError:
|
|
65
|
+
available_names: list[str] = [e.name.lower() for e in ToolEnum]
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"Unknown tool '{name.lower()}'. Available tools: {available_names}",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return tools_to_run
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _coerce_value(raw: str) -> object:
|
|
74
|
+
"""Coerce a raw CLI value into a typed Python value.
|
|
75
|
+
|
|
76
|
+
Rules:
|
|
77
|
+
- "all"/"none" (case-insensitive) -> list[str]
|
|
78
|
+
- "True"/"False" (case-insensitive) -> bool
|
|
79
|
+
- "None"/"null" (case-insensitive) -> None
|
|
80
|
+
- integer (e.g., 88) -> int
|
|
81
|
+
- float (e.g., 0.75) -> float
|
|
82
|
+
- list via pipe-delimited values (e.g., "E|F|W") -> list[str]
|
|
83
|
+
Pipe is chosen to avoid conflict with the top-level comma separator.
|
|
84
|
+
- otherwise -> original string
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
raw: str: Raw CLI value to coerce.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
object: Coerced value.
|
|
91
|
+
"""
|
|
92
|
+
s = raw.strip()
|
|
93
|
+
# Lists via pipe (e.g., select=E|F)
|
|
94
|
+
if "|" in s:
|
|
95
|
+
return [part.strip() for part in s.split("|") if part.strip()]
|
|
96
|
+
|
|
97
|
+
low = s.lower()
|
|
98
|
+
if low == "true":
|
|
99
|
+
return True
|
|
100
|
+
if low == "false":
|
|
101
|
+
return False
|
|
102
|
+
if low in {"none", "null"}:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
# Try int
|
|
106
|
+
try:
|
|
107
|
+
return int(s)
|
|
108
|
+
except ValueError:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
# Try float
|
|
112
|
+
try:
|
|
113
|
+
return float(s)
|
|
114
|
+
except ValueError:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
return s
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _parse_tool_options(tool_options: str | None) -> dict[str, dict[str, object]]:
|
|
121
|
+
"""Parse tool options string into a typed dictionary.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
tool_options: str | None: String in format
|
|
125
|
+
"tool:option=value,tool2:option=value". Multiple values for a single
|
|
126
|
+
option can be provided using pipe separators (e.g., select=E|F).
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
dict[str, dict[str, object]]: Mapping tool names to typed options.
|
|
130
|
+
"""
|
|
131
|
+
if not tool_options:
|
|
132
|
+
return {}
|
|
133
|
+
|
|
134
|
+
tool_option_dict: dict[str, dict[str, object]] = {}
|
|
135
|
+
for opt in tool_options.split(","):
|
|
136
|
+
opt = opt.strip()
|
|
137
|
+
if not opt:
|
|
138
|
+
continue
|
|
139
|
+
if ":" not in opt:
|
|
140
|
+
# Skip malformed fragment
|
|
141
|
+
continue
|
|
142
|
+
tool_name, tool_opt = opt.split(":", 1)
|
|
143
|
+
if "=" not in tool_opt:
|
|
144
|
+
# Skip malformed fragment
|
|
145
|
+
continue
|
|
146
|
+
opt_name, opt_value = tool_opt.split("=", 1)
|
|
147
|
+
tool_name = tool_name.strip()
|
|
148
|
+
opt_name = opt_name.strip()
|
|
149
|
+
opt_value = opt_value.strip()
|
|
150
|
+
if not tool_name or not opt_name:
|
|
151
|
+
continue
|
|
152
|
+
if tool_name not in tool_option_dict:
|
|
153
|
+
tool_option_dict[tool_name] = {}
|
|
154
|
+
tool_option_dict[tool_name][opt_name] = _coerce_value(opt_value)
|
|
155
|
+
|
|
156
|
+
return tool_option_dict
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def run_lint_tools_simple(
|
|
160
|
+
*,
|
|
161
|
+
action: str,
|
|
162
|
+
paths: list[str],
|
|
163
|
+
tools: str | None,
|
|
164
|
+
tool_options: str | None,
|
|
165
|
+
exclude: str | None,
|
|
166
|
+
include_venv: bool,
|
|
167
|
+
group_by: str,
|
|
168
|
+
output_format: str,
|
|
169
|
+
verbose: bool,
|
|
170
|
+
raw_output: bool = False,
|
|
171
|
+
) -> int:
|
|
172
|
+
"""Simplified runner using Loguru-based logging with rich formatting.
|
|
173
|
+
|
|
174
|
+
Clean approach with beautiful output:
|
|
175
|
+
- SimpleLintroLogger handles ALL console output and file logging with rich
|
|
176
|
+
formatting
|
|
177
|
+
- OutputManager handles structured output files
|
|
178
|
+
- No tee, no complex state management
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
action: str: "check" or "fmt".
|
|
182
|
+
paths: list[str]: List of paths to check.
|
|
183
|
+
tools: str | None: Comma-separated list of tools to run.
|
|
184
|
+
tool_options: str | None: Additional tool options.
|
|
185
|
+
exclude: str | None: Patterns to exclude.
|
|
186
|
+
include_venv: bool: Whether to include virtual environments.
|
|
187
|
+
group_by: str: How to group results.
|
|
188
|
+
output_format: str: Output format for results.
|
|
189
|
+
verbose: bool: Whether to enable verbose output.
|
|
190
|
+
raw_output: bool: Whether to show raw tool output instead of formatted output.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
int: Exit code (0 for success, 1 for failures).
|
|
194
|
+
"""
|
|
195
|
+
# Initialize output manager for this run
|
|
196
|
+
output_manager = OutputManager()
|
|
197
|
+
run_dir: str = output_manager.run_dir
|
|
198
|
+
|
|
199
|
+
# Create simplified logger with rich formatting
|
|
200
|
+
logger = create_logger(run_dir=run_dir, verbose=verbose, raw_output=raw_output)
|
|
201
|
+
|
|
202
|
+
logger.debug(f"Starting {action} command")
|
|
203
|
+
logger.debug(f"Paths: {paths}")
|
|
204
|
+
logger.debug(f"Tools: {tools}")
|
|
205
|
+
logger.debug(f"Run directory: {run_dir}")
|
|
206
|
+
|
|
207
|
+
# For JSON output format, we'll collect results and output JSON at the end
|
|
208
|
+
# Normalize enums while maintaining backward compatibility
|
|
209
|
+
output_fmt_enum: OutputFormat = normalize_output_format(output_format)
|
|
210
|
+
group_by_enum: GroupBy = normalize_group_by(group_by)
|
|
211
|
+
json_output_mode = output_fmt_enum == OutputFormat.JSON
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
# Get tools to run
|
|
215
|
+
try:
|
|
216
|
+
tools_to_run = _get_tools_to_run(tools=tools, action=action)
|
|
217
|
+
except ValueError as e:
|
|
218
|
+
logger.error(str(e))
|
|
219
|
+
logger.save_console_log()
|
|
220
|
+
return DEFAULT_EXIT_CODE_FAILURE
|
|
221
|
+
|
|
222
|
+
if not tools_to_run:
|
|
223
|
+
logger.warning("No tools found to run")
|
|
224
|
+
logger.save_console_log()
|
|
225
|
+
return DEFAULT_EXIT_CODE_FAILURE
|
|
226
|
+
|
|
227
|
+
# Parse tool options
|
|
228
|
+
tool_option_dict = _parse_tool_options(tool_options)
|
|
229
|
+
|
|
230
|
+
# Print main header (skip for JSON mode)
|
|
231
|
+
tools_list: str = ", ".join(t.name.lower() for t in tools_to_run)
|
|
232
|
+
if not json_output_mode:
|
|
233
|
+
logger.print_lintro_header(
|
|
234
|
+
action=action,
|
|
235
|
+
tool_count=len(tools_to_run),
|
|
236
|
+
tools_list=tools_list,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Print verbose info if requested
|
|
240
|
+
paths_list: str = ", ".join(paths)
|
|
241
|
+
logger.print_verbose_info(
|
|
242
|
+
action=action,
|
|
243
|
+
tools_list=tools_list,
|
|
244
|
+
paths_list=paths_list,
|
|
245
|
+
output_format=output_format,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
all_results: list = []
|
|
249
|
+
total_issues: int = 0
|
|
250
|
+
total_fixed: int = 0
|
|
251
|
+
total_remaining: int = 0
|
|
252
|
+
|
|
253
|
+
# Run each tool with rich formatting
|
|
254
|
+
for tool_enum in tools_to_run:
|
|
255
|
+
tool = tool_manager.get_tool(tool_enum)
|
|
256
|
+
tool_name: str = tool_enum.name.lower()
|
|
257
|
+
|
|
258
|
+
# Print rich tool header (skip for JSON mode)
|
|
259
|
+
if not json_output_mode:
|
|
260
|
+
logger.print_tool_header(tool_name=tool_name, action=action)
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
# Configure tool options
|
|
264
|
+
# 1) Load config from pyproject.toml / lintro.toml
|
|
265
|
+
cfg: dict = load_lintro_tool_config(tool_name)
|
|
266
|
+
if cfg:
|
|
267
|
+
try:
|
|
268
|
+
tool.set_options(**cfg)
|
|
269
|
+
except Exception:
|
|
270
|
+
pass
|
|
271
|
+
# 2) CLI --tool-options overrides config file
|
|
272
|
+
if tool_name in tool_option_dict:
|
|
273
|
+
tool.set_options(**tool_option_dict[tool_name])
|
|
274
|
+
|
|
275
|
+
# Set common options
|
|
276
|
+
if exclude:
|
|
277
|
+
exclude_patterns: list[str] = [
|
|
278
|
+
pattern.strip() for pattern in exclude.split(",")
|
|
279
|
+
]
|
|
280
|
+
tool.set_options(exclude_patterns=exclude_patterns)
|
|
281
|
+
|
|
282
|
+
tool.set_options(include_venv=include_venv)
|
|
283
|
+
|
|
284
|
+
# Run the tool
|
|
285
|
+
logger.debug(f"Executing {tool_name}")
|
|
286
|
+
|
|
287
|
+
if action == "fmt":
|
|
288
|
+
# Respect tool defaults; allow overrides via --tool-options
|
|
289
|
+
result = tool.fix(paths=paths)
|
|
290
|
+
# Prefer standardized counters when present
|
|
291
|
+
fixed_count: int = (
|
|
292
|
+
getattr(result, "fixed_issues_count", None)
|
|
293
|
+
if hasattr(result, "fixed_issues_count")
|
|
294
|
+
else None
|
|
295
|
+
)
|
|
296
|
+
if fixed_count is None:
|
|
297
|
+
fixed_count = 0
|
|
298
|
+
total_fixed += fixed_count
|
|
299
|
+
|
|
300
|
+
remaining_count: int = (
|
|
301
|
+
getattr(result, "remaining_issues_count", None)
|
|
302
|
+
if hasattr(result, "remaining_issues_count")
|
|
303
|
+
else None
|
|
304
|
+
)
|
|
305
|
+
if remaining_count is None:
|
|
306
|
+
# Fallback to issues_count if standardized field absent
|
|
307
|
+
remaining_count = getattr(result, "issues_count", 0)
|
|
308
|
+
total_remaining += max(0, remaining_count)
|
|
309
|
+
|
|
310
|
+
# For display in per-tool logger call below
|
|
311
|
+
issues_count: int = remaining_count
|
|
312
|
+
else: # check
|
|
313
|
+
result = tool.check(paths=paths)
|
|
314
|
+
issues_count = getattr(result, "issues_count", 0)
|
|
315
|
+
total_issues += issues_count
|
|
316
|
+
|
|
317
|
+
# Format and display output
|
|
318
|
+
output = getattr(result, "output", None)
|
|
319
|
+
issues = getattr(result, "issues", None)
|
|
320
|
+
formatted_output: str = ""
|
|
321
|
+
|
|
322
|
+
# Call format_tool_output if we have output or issues
|
|
323
|
+
if (output and output.strip()) or issues:
|
|
324
|
+
formatted_output = format_tool_output(
|
|
325
|
+
tool_name=tool_name,
|
|
326
|
+
output=output or "",
|
|
327
|
+
group_by=group_by_enum.value,
|
|
328
|
+
output_format=output_fmt_enum.value,
|
|
329
|
+
issues=issues,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Print tool results with rich formatting (skip for JSON mode)
|
|
333
|
+
if not json_output_mode:
|
|
334
|
+
# Use raw output if raw_output is true, otherwise use
|
|
335
|
+
# formatted output
|
|
336
|
+
if raw_output:
|
|
337
|
+
display_output = output
|
|
338
|
+
else:
|
|
339
|
+
display_output = formatted_output
|
|
340
|
+
logger.print_tool_result(
|
|
341
|
+
tool_name=tool_name,
|
|
342
|
+
output=display_output,
|
|
343
|
+
issues_count=issues_count,
|
|
344
|
+
raw_output_for_meta=output,
|
|
345
|
+
action=action,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Store result
|
|
349
|
+
all_results.append(result)
|
|
350
|
+
|
|
351
|
+
if action == "fmt":
|
|
352
|
+
# Pull standardized counts again for debug log
|
|
353
|
+
fixed_dbg = getattr(result, "fixed_issues_count", fixed_count)
|
|
354
|
+
remaining_dbg = getattr(
|
|
355
|
+
result, "remaining_issues_count", issues_count
|
|
356
|
+
)
|
|
357
|
+
logger.debug(
|
|
358
|
+
f"Completed {tool_name}: {fixed_dbg} fixed, "
|
|
359
|
+
f"{remaining_dbg} remaining"
|
|
360
|
+
)
|
|
361
|
+
else:
|
|
362
|
+
logger.debug(f"Completed {tool_name}: {issues_count} issues found")
|
|
363
|
+
|
|
364
|
+
except Exception as e:
|
|
365
|
+
logger.error(f"Error running {tool_name}: {e}")
|
|
366
|
+
return DEFAULT_EXIT_CODE_FAILURE
|
|
367
|
+
|
|
368
|
+
# Handle output based on format
|
|
369
|
+
if json_output_mode:
|
|
370
|
+
# For JSON output, print JSON directly to stdout
|
|
371
|
+
import datetime
|
|
372
|
+
import json
|
|
373
|
+
|
|
374
|
+
# Create a simple JSON structure with all results
|
|
375
|
+
json_data = {
|
|
376
|
+
"action": action,
|
|
377
|
+
"timestamp": datetime.datetime.now().isoformat(),
|
|
378
|
+
"tools": [result.name for result in all_results],
|
|
379
|
+
"total_issues": sum(
|
|
380
|
+
getattr(result, "issues_count", 0) for result in all_results
|
|
381
|
+
),
|
|
382
|
+
"total_fixed": (
|
|
383
|
+
sum((getattr(r, "fixed_issues_count", 0) or 0) for r in all_results)
|
|
384
|
+
if action == "fmt"
|
|
385
|
+
else None
|
|
386
|
+
),
|
|
387
|
+
"total_remaining": (
|
|
388
|
+
sum(
|
|
389
|
+
(getattr(r, "remaining_issues_count", 0) or 0)
|
|
390
|
+
for r in all_results
|
|
391
|
+
)
|
|
392
|
+
if action == "fmt"
|
|
393
|
+
else None
|
|
394
|
+
),
|
|
395
|
+
"results": [],
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
for result in all_results:
|
|
399
|
+
result_data = {
|
|
400
|
+
"tool": result.name,
|
|
401
|
+
"success": getattr(result, "success", True),
|
|
402
|
+
"issues_count": getattr(result, "issues_count", 0),
|
|
403
|
+
"output": getattr(result, "output", ""),
|
|
404
|
+
"initial_issues_count": getattr(
|
|
405
|
+
result, "initial_issues_count", None
|
|
406
|
+
),
|
|
407
|
+
"fixed_issues_count": getattr(result, "fixed_issues_count", None),
|
|
408
|
+
"remaining_issues_count": getattr(
|
|
409
|
+
result, "remaining_issues_count", None
|
|
410
|
+
),
|
|
411
|
+
}
|
|
412
|
+
json_data["results"].append(result_data)
|
|
413
|
+
|
|
414
|
+
print(json.dumps(json_data, indent=2))
|
|
415
|
+
else:
|
|
416
|
+
# Print rich execution summary with table and ASCII art
|
|
417
|
+
logger.print_execution_summary(action=action, tool_results=all_results)
|
|
418
|
+
|
|
419
|
+
# Save outputs
|
|
420
|
+
try:
|
|
421
|
+
output_manager.write_reports_from_results(results=all_results)
|
|
422
|
+
logger.save_console_log()
|
|
423
|
+
logger.debug("Saved all output files")
|
|
424
|
+
except Exception as e:
|
|
425
|
+
logger.error(f"Error saving outputs: {e}")
|
|
426
|
+
|
|
427
|
+
# Return appropriate exit code
|
|
428
|
+
if action == "fmt":
|
|
429
|
+
# Format operations succeed if they complete successfully
|
|
430
|
+
# (even if there are remaining unfixable issues)
|
|
431
|
+
return DEFAULT_EXIT_CODE_SUCCESS
|
|
432
|
+
else: # check
|
|
433
|
+
# Check operations fail if issues are found
|
|
434
|
+
return (
|
|
435
|
+
DEFAULT_EXIT_CODE_SUCCESS
|
|
436
|
+
if total_issues == 0
|
|
437
|
+
else DEFAULT_EXIT_CODE_FAILURE
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
except Exception as e:
|
|
441
|
+
logger.error(f"Unexpected error: {e}")
|
|
442
|
+
logger.save_console_log()
|
|
443
|
+
return DEFAULT_EXIT_CODE_FAILURE
|