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.
Files changed (72) hide show
  1. lintro/__init__.py +1 -1
  2. lintro/cli.py +226 -16
  3. lintro/cli_utils/commands/__init__.py +8 -1
  4. lintro/cli_utils/commands/check.py +1 -0
  5. lintro/cli_utils/commands/config.py +325 -0
  6. lintro/cli_utils/commands/init.py +361 -0
  7. lintro/cli_utils/commands/list_tools.py +180 -42
  8. lintro/cli_utils/commands/test.py +316 -0
  9. lintro/cli_utils/commands/versions.py +81 -0
  10. lintro/config/__init__.py +62 -0
  11. lintro/config/config_loader.py +420 -0
  12. lintro/config/lintro_config.py +189 -0
  13. lintro/config/tool_config_generator.py +403 -0
  14. lintro/enums/tool_name.py +2 -0
  15. lintro/enums/tool_type.py +2 -0
  16. lintro/formatters/tools/__init__.py +12 -0
  17. lintro/formatters/tools/eslint_formatter.py +108 -0
  18. lintro/formatters/tools/markdownlint_formatter.py +88 -0
  19. lintro/formatters/tools/pytest_formatter.py +201 -0
  20. lintro/parsers/__init__.py +69 -9
  21. lintro/parsers/bandit/__init__.py +6 -0
  22. lintro/parsers/bandit/bandit_issue.py +49 -0
  23. lintro/parsers/bandit/bandit_parser.py +99 -0
  24. lintro/parsers/black/black_issue.py +4 -0
  25. lintro/parsers/eslint/__init__.py +6 -0
  26. lintro/parsers/eslint/eslint_issue.py +26 -0
  27. lintro/parsers/eslint/eslint_parser.py +63 -0
  28. lintro/parsers/markdownlint/__init__.py +6 -0
  29. lintro/parsers/markdownlint/markdownlint_issue.py +22 -0
  30. lintro/parsers/markdownlint/markdownlint_parser.py +113 -0
  31. lintro/parsers/pytest/__init__.py +21 -0
  32. lintro/parsers/pytest/pytest_issue.py +28 -0
  33. lintro/parsers/pytest/pytest_parser.py +483 -0
  34. lintro/tools/__init__.py +2 -0
  35. lintro/tools/core/timeout_utils.py +112 -0
  36. lintro/tools/core/tool_base.py +255 -45
  37. lintro/tools/core/tool_manager.py +77 -24
  38. lintro/tools/core/version_requirements.py +482 -0
  39. lintro/tools/implementations/pytest/pytest_command_builder.py +311 -0
  40. lintro/tools/implementations/pytest/pytest_config.py +200 -0
  41. lintro/tools/implementations/pytest/pytest_error_handler.py +128 -0
  42. lintro/tools/implementations/pytest/pytest_executor.py +122 -0
  43. lintro/tools/implementations/pytest/pytest_handlers.py +375 -0
  44. lintro/tools/implementations/pytest/pytest_option_validators.py +212 -0
  45. lintro/tools/implementations/pytest/pytest_output_processor.py +408 -0
  46. lintro/tools/implementations/pytest/pytest_result_processor.py +113 -0
  47. lintro/tools/implementations/pytest/pytest_utils.py +697 -0
  48. lintro/tools/implementations/tool_actionlint.py +106 -16
  49. lintro/tools/implementations/tool_bandit.py +23 -7
  50. lintro/tools/implementations/tool_black.py +236 -29
  51. lintro/tools/implementations/tool_darglint.py +180 -21
  52. lintro/tools/implementations/tool_eslint.py +374 -0
  53. lintro/tools/implementations/tool_hadolint.py +94 -25
  54. lintro/tools/implementations/tool_markdownlint.py +354 -0
  55. lintro/tools/implementations/tool_prettier.py +313 -26
  56. lintro/tools/implementations/tool_pytest.py +327 -0
  57. lintro/tools/implementations/tool_ruff.py +247 -70
  58. lintro/tools/implementations/tool_yamllint.py +448 -34
  59. lintro/tools/tool_enum.py +6 -0
  60. lintro/utils/config.py +41 -18
  61. lintro/utils/console_logger.py +211 -25
  62. lintro/utils/path_utils.py +42 -0
  63. lintro/utils/tool_executor.py +336 -39
  64. lintro/utils/tool_utils.py +38 -2
  65. lintro/utils/unified_config.py +926 -0
  66. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/METADATA +131 -29
  67. lintro-0.17.2.dist-info/RECORD +134 -0
  68. lintro-0.13.2.dist-info/RECORD +0 -96
  69. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/WHEEL +0 -0
  70. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/entry_points.txt +0 -0
  71. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/licenses/LICENSE +0 -0
  72. {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,316 @@
1
+ """Test command implementation for running pytest tests."""
2
+
3
+ import click
4
+ from click.testing import CliRunner
5
+
6
+ from lintro.utils.tool_executor import run_lint_tools_simple
7
+
8
+ # Constants
9
+ DEFAULT_PATHS: list[str] = ["."]
10
+ DEFAULT_EXIT_CODE: int = 0
11
+ DEFAULT_ACTION: str = "test"
12
+
13
+
14
+ def _ensure_pytest_prefix(option_fragment: str) -> str:
15
+ """Normalize tool option fragments to use the pytest prefix.
16
+
17
+ Args:
18
+ option_fragment: Raw option fragment from --tool-options.
19
+
20
+ Returns:
21
+ str: Fragment guaranteed to start with ``pytest:``.
22
+ """
23
+ fragment = option_fragment.strip()
24
+ if not fragment:
25
+ return fragment
26
+
27
+ lowered = fragment.lower()
28
+ if lowered.startswith("pytest:"):
29
+ _, rest = fragment.split(":", 1)
30
+ return f"pytest:{rest}"
31
+ return f"pytest:{fragment}"
32
+
33
+
34
+ @click.command("test")
35
+ @click.argument("paths", nargs=-1, type=click.Path(exists=True))
36
+ @click.option(
37
+ "--exclude",
38
+ type=str,
39
+ help="Comma-separated list of patterns to exclude from testing",
40
+ )
41
+ @click.option(
42
+ "--include-venv",
43
+ is_flag=True,
44
+ help="Include virtual environment directories in testing",
45
+ )
46
+ @click.option(
47
+ "--output",
48
+ type=click.Path(),
49
+ help="Output file path for writing results",
50
+ )
51
+ @click.option(
52
+ "--output-format",
53
+ type=click.Choice(["plain", "grid", "markdown", "html", "json", "csv"]),
54
+ default="grid",
55
+ help="Output format for displaying results",
56
+ )
57
+ @click.option(
58
+ "--group-by",
59
+ type=click.Choice(["file", "code", "none", "auto"]),
60
+ default="file",
61
+ help="How to group issues in the output",
62
+ )
63
+ @click.option(
64
+ "--verbose",
65
+ is_flag=True,
66
+ help="Show verbose output",
67
+ )
68
+ @click.option(
69
+ "--raw-output",
70
+ is_flag=True,
71
+ help="Show raw tool output instead of formatted output",
72
+ )
73
+ @click.option(
74
+ "--tool-options",
75
+ type=str,
76
+ help="Tool-specific options in the format option=value,option=value",
77
+ )
78
+ @click.option(
79
+ "--enable-docker",
80
+ is_flag=True,
81
+ default=False,
82
+ help="Enable Docker-specific tests",
83
+ )
84
+ @click.option(
85
+ "--list-plugins",
86
+ is_flag=True,
87
+ default=False,
88
+ help="List all installed pytest plugins",
89
+ )
90
+ @click.option(
91
+ "--check-plugins",
92
+ is_flag=True,
93
+ default=False,
94
+ help=(
95
+ "Check if required plugins are installed "
96
+ "(use with --tool-options pytest:required_plugins=plugin1,plugin2)"
97
+ ),
98
+ )
99
+ @click.option(
100
+ "--collect-only",
101
+ is_flag=True,
102
+ default=False,
103
+ help="List tests without executing them",
104
+ )
105
+ @click.option(
106
+ "--fixtures",
107
+ is_flag=True,
108
+ default=False,
109
+ help="List all available fixtures",
110
+ )
111
+ @click.option(
112
+ "--fixture-info",
113
+ type=str,
114
+ default=None,
115
+ help="Show detailed information about a specific fixture",
116
+ )
117
+ @click.option(
118
+ "--markers",
119
+ is_flag=True,
120
+ default=False,
121
+ help="List all available markers",
122
+ )
123
+ @click.option(
124
+ "--parametrize-help",
125
+ is_flag=True,
126
+ default=False,
127
+ help="Show help for parametrized tests",
128
+ )
129
+ def test_command(
130
+ paths,
131
+ exclude,
132
+ include_venv,
133
+ output,
134
+ output_format,
135
+ group_by,
136
+ verbose,
137
+ raw_output,
138
+ tool_options,
139
+ enable_docker,
140
+ list_plugins,
141
+ check_plugins,
142
+ collect_only,
143
+ fixtures,
144
+ fixture_info,
145
+ markers,
146
+ parametrize_help,
147
+ ) -> None:
148
+ """Run tests using pytest.
149
+
150
+ Args:
151
+ paths: tuple: List of file/directory paths to test.
152
+ exclude: str | None: Comma-separated patterns of files/dirs to exclude.
153
+ include_venv: bool: Whether to include virtual environment directories.
154
+ output: str | None: Path to output file for results.
155
+ output_format: str: Format for displaying results (grid, json, etc).
156
+ group_by: str: How to group issues in output (file, code, etc).
157
+ verbose: bool: Whether to show verbose output during execution.
158
+ raw_output: bool: Whether to show raw tool output instead of formatted output.
159
+ tool_options: str | None: Tool-specific options.
160
+ enable_docker: bool: Whether to enable Docker-specific tests.
161
+ list_plugins: bool: Whether to list installed pytest plugins.
162
+ check_plugins: bool: Whether to check required plugins.
163
+ collect_only: bool: Whether to list tests without executing.
164
+ fixtures: bool: Whether to list available fixtures.
165
+ fixture_info: str | None: Name of fixture to get info for.
166
+ markers: bool: Whether to list available markers.
167
+ parametrize_help: bool: Whether to show parametrization help.
168
+
169
+ Raises:
170
+ SystemExit: Process exit with the aggregated exit code.
171
+ """
172
+ # Add default paths if none provided
173
+ if not paths:
174
+ paths = DEFAULT_PATHS
175
+
176
+ # Build tool options with pytest prefix
177
+ tool_option_parts: list[str] = []
178
+
179
+ # Add Docker enable option if flag is set
180
+ if enable_docker:
181
+ tool_option_parts.append("pytest:run_docker_tests=True")
182
+
183
+ # Add special mode flags
184
+ boolean_flags: list[tuple[bool, str]] = [
185
+ (list_plugins, "pytest:list_plugins=True"),
186
+ (check_plugins, "pytest:check_plugins=True"),
187
+ (collect_only, "pytest:collect_only=True"),
188
+ (fixtures, "pytest:list_fixtures=True"),
189
+ (markers, "pytest:list_markers=True"),
190
+ (parametrize_help, "pytest:parametrize_help=True"),
191
+ ]
192
+
193
+ for flag_value, option_string in boolean_flags:
194
+ if flag_value:
195
+ tool_option_parts.append(option_string)
196
+
197
+ # Handle fixture_info as special case (requires non-empty value)
198
+ if fixture_info:
199
+ tool_option_parts.append(f"pytest:fixture_info={fixture_info}")
200
+
201
+ if tool_options:
202
+ # Prefix with "pytest:" for pytest tool
203
+ # Parse options carefully to handle values containing commas
204
+ # Format: key=value,key=value where values can contain commas
205
+ prefixed_options: list[str] = []
206
+ parts = tool_options.split(",")
207
+ i = 0
208
+
209
+ while i < len(parts):
210
+ current_part = parts[i].strip()
211
+ if not current_part:
212
+ i += 1
213
+ continue
214
+
215
+ # Check if this part looks like a complete option (contains =)
216
+ # or starts with pytest prefix (already namespaced)
217
+ if "=" in current_part or current_part.lower().startswith("pytest:"):
218
+ normalized_part = _ensure_pytest_prefix(current_part)
219
+ prefixed_options.append(normalized_part)
220
+ i += 1
221
+ else:
222
+ # This part doesn't have =, might be a value continuation
223
+ # Merge with previous part if it exists and had an =
224
+ if prefixed_options and "=" in prefixed_options[-1]:
225
+ # Merge with previous option's value
226
+ prefixed_options[-1] = f"{prefixed_options[-1]},{current_part}"
227
+ else:
228
+ # Standalone option without =, prefix it
229
+ prefixed_options.append(_ensure_pytest_prefix(current_part))
230
+ i += 1
231
+
232
+ tool_option_parts.append(",".join(prefixed_options))
233
+
234
+ combined_tool_options: str | None = (
235
+ ",".join(tool_option_parts) if tool_option_parts else None
236
+ )
237
+
238
+ # Run with pytest tool
239
+ exit_code: int = run_lint_tools_simple(
240
+ action=DEFAULT_ACTION,
241
+ paths=list(paths),
242
+ tools="pytest",
243
+ tool_options=combined_tool_options,
244
+ exclude=exclude,
245
+ include_venv=include_venv,
246
+ group_by=group_by,
247
+ output_format=output_format,
248
+ verbose=verbose,
249
+ raw_output=raw_output,
250
+ output_file=output,
251
+ )
252
+
253
+ # Exit with code only
254
+ raise SystemExit(exit_code)
255
+
256
+
257
+ # Exclude from pytest collection - this is a Click command, not a test function
258
+ test_command.__test__ = False
259
+
260
+
261
+ def test(
262
+ paths,
263
+ exclude,
264
+ include_venv,
265
+ output,
266
+ output_format,
267
+ group_by,
268
+ verbose,
269
+ raw_output=False,
270
+ tool_options=None,
271
+ ) -> None:
272
+ """Programmatic test function for backward compatibility.
273
+
274
+ Args:
275
+ paths: tuple: List of file/directory paths to test.
276
+ exclude: str | None: Comma-separated patterns of files/dirs to exclude.
277
+ include_venv: bool: Whether to include virtual environment directories.
278
+ output: str | None: Path to output file for results.
279
+ output_format: str: Format for displaying results.
280
+ group_by: str: How to group issues in output.
281
+ verbose: bool: Whether to show verbose output during execution.
282
+ raw_output: bool: Whether to show raw tool output instead of formatted output.
283
+ tool_options: str | None: Tool-specific options.
284
+
285
+ Returns:
286
+ None: This function does not return a value.
287
+ """
288
+ # Build arguments for the click command
289
+ args: list[str] = []
290
+ if paths:
291
+ args.extend(paths)
292
+ if exclude:
293
+ args.extend(["--exclude", exclude])
294
+ if include_venv:
295
+ args.append("--include-venv")
296
+ if output:
297
+ args.extend(["--output", output])
298
+ if output_format:
299
+ args.extend(["--output-format", output_format])
300
+ if group_by:
301
+ args.extend(["--group-by", group_by])
302
+ if verbose:
303
+ args.append("--verbose")
304
+ if raw_output:
305
+ args.append("--raw-output")
306
+ if tool_options:
307
+ args.extend(["--tool-options", tool_options])
308
+
309
+ runner = CliRunner()
310
+ result = runner.invoke(test_command, args)
311
+
312
+ if result.exit_code != DEFAULT_EXIT_CODE:
313
+ import sys
314
+
315
+ sys.exit(result.exit_code)
316
+ return None
@@ -0,0 +1,81 @@
1
+ """Versions command for displaying tool version information."""
2
+
3
+ import click
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+
7
+ from lintro.tools.core.version_requirements import get_all_tool_versions
8
+
9
+
10
+ @click.command()
11
+ @click.option(
12
+ "--verbose",
13
+ "-v",
14
+ is_flag=True,
15
+ help="Show detailed version information including install hints.",
16
+ )
17
+ def versions_command(verbose: bool) -> None:
18
+ """Display version information for all supported tools.
19
+
20
+ Shows each tool's current version, minimum required version, and status.
21
+ Use --verbose to see installation hints for tools that don't meet requirements.
22
+
23
+ Args:
24
+ verbose: Show detailed version information including install hints.
25
+ """
26
+ console = Console()
27
+ tool_versions = get_all_tool_versions()
28
+
29
+ table = Table(title="Tool Versions")
30
+ table.add_column("Tool", style="cyan", no_wrap=True)
31
+ table.add_column("Current", style="yellow")
32
+ table.add_column("Minimum", style="green")
33
+ table.add_column("Status", justify="center")
34
+
35
+ if verbose:
36
+ table.add_column("Install Hint", style="dim")
37
+
38
+ # Sort tools by name for consistent output
39
+ sorted_tools = sorted(tool_versions.items())
40
+
41
+ for tool_name, version_info in sorted_tools:
42
+ current = version_info.current_version or "unknown"
43
+ minimum = version_info.min_version
44
+
45
+ if version_info.version_check_passed:
46
+ status = "[green]✓ PASS[/green]"
47
+ elif version_info.error_message:
48
+ status = "[red]✗ FAIL[/red]"
49
+ else:
50
+ status = "[yellow]? UNKNOWN[/yellow]"
51
+
52
+ row = [tool_name, current, minimum, status]
53
+
54
+ if verbose:
55
+ hint = version_info.install_hint
56
+ if version_info.error_message:
57
+ hint = f"{version_info.error_message}. {hint}"
58
+ row.append(hint)
59
+
60
+ table.add_row(*row)
61
+
62
+ console.print(table)
63
+
64
+ # Summary
65
+ total_tools = len(tool_versions)
66
+ passed_tools = sum(1 for v in tool_versions.values() if v.version_check_passed)
67
+ failed_tools = total_tools - passed_tools
68
+
69
+ if failed_tools > 0:
70
+ console.print(
71
+ f"\n[red]⚠️ {failed_tools} tool(s) do not meet "
72
+ f"minimum version requirements.[/red]",
73
+ )
74
+ console.print(
75
+ "[dim]Run with --verbose to see installation instructions.[/dim]",
76
+ )
77
+ else:
78
+ console.print(
79
+ f"\n[green]✅ All {total_tools} tools meet "
80
+ f"minimum version requirements.[/green]",
81
+ )
@@ -0,0 +1,62 @@
1
+ """Lintro configuration module.
2
+
3
+ This module provides a tiered configuration system:
4
+ 1. EXECUTION: What tools run and how
5
+ 2. ENFORCE: Cross-cutting settings injected via CLI flags
6
+ 3. DEFAULTS: Fallback config when no native config exists
7
+ 4. TOOLS: Per-tool enable/disable and config source
8
+
9
+ Key components:
10
+ - LintroConfig: Main configuration dataclass
11
+ - EnforceConfig: Cross-cutting settings enforced via CLI
12
+ - ConfigLoader: Loads .lintro-config.yaml
13
+ - ToolConfigGenerator: CLI injection and defaults generation
14
+ """
15
+
16
+ from lintro.config.config_loader import (
17
+ clear_config_cache,
18
+ get_config,
19
+ get_default_config,
20
+ load_config,
21
+ )
22
+ from lintro.config.lintro_config import (
23
+ EnforceConfig,
24
+ ExecutionConfig,
25
+ GlobalConfig,
26
+ LintroConfig,
27
+ ToolConfig,
28
+ )
29
+ from lintro.config.tool_config_generator import (
30
+ cleanup_temp_config,
31
+ generate_defaults_config,
32
+ generate_tool_config,
33
+ get_config_injection_args,
34
+ get_defaults_injection_args,
35
+ get_enforce_cli_args,
36
+ get_no_auto_config_args,
37
+ has_native_config,
38
+ )
39
+
40
+ __all__ = [
41
+ # Config dataclasses
42
+ "EnforceConfig",
43
+ "ExecutionConfig",
44
+ "GlobalConfig", # Deprecated alias for EnforceConfig
45
+ "LintroConfig",
46
+ "ToolConfig",
47
+ # Config loading
48
+ "clear_config_cache",
49
+ "get_config",
50
+ "get_default_config",
51
+ "load_config",
52
+ # New tiered model functions
53
+ "get_enforce_cli_args",
54
+ "has_native_config",
55
+ "generate_defaults_config",
56
+ "get_defaults_injection_args",
57
+ # Deprecated functions (kept for backward compatibility)
58
+ "cleanup_temp_config",
59
+ "generate_tool_config",
60
+ "get_config_injection_args",
61
+ "get_no_auto_config_args",
62
+ ]