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.
Files changed (109) hide show
  1. lintro/__init__.py +1 -1
  2. lintro/cli.py +230 -14
  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/format.py +2 -2
  7. lintro/cli_utils/commands/init.py +361 -0
  8. lintro/cli_utils/commands/list_tools.py +180 -42
  9. lintro/cli_utils/commands/test.py +316 -0
  10. lintro/cli_utils/commands/versions.py +81 -0
  11. lintro/config/__init__.py +62 -0
  12. lintro/config/config_loader.py +420 -0
  13. lintro/config/lintro_config.py +189 -0
  14. lintro/config/tool_config_generator.py +403 -0
  15. lintro/enums/__init__.py +1 -0
  16. lintro/enums/darglint_strictness.py +10 -0
  17. lintro/enums/hadolint_enums.py +22 -0
  18. lintro/enums/tool_name.py +2 -0
  19. lintro/enums/tool_type.py +2 -0
  20. lintro/enums/yamllint_format.py +11 -0
  21. lintro/exceptions/__init__.py +1 -0
  22. lintro/formatters/__init__.py +1 -0
  23. lintro/formatters/core/__init__.py +1 -0
  24. lintro/formatters/core/output_style.py +11 -0
  25. lintro/formatters/core/table_descriptor.py +8 -0
  26. lintro/formatters/styles/csv.py +2 -0
  27. lintro/formatters/styles/grid.py +2 -0
  28. lintro/formatters/styles/html.py +2 -0
  29. lintro/formatters/styles/json.py +2 -0
  30. lintro/formatters/styles/markdown.py +2 -0
  31. lintro/formatters/styles/plain.py +2 -0
  32. lintro/formatters/tools/__init__.py +12 -0
  33. lintro/formatters/tools/black_formatter.py +27 -5
  34. lintro/formatters/tools/darglint_formatter.py +16 -1
  35. lintro/formatters/tools/eslint_formatter.py +108 -0
  36. lintro/formatters/tools/hadolint_formatter.py +13 -0
  37. lintro/formatters/tools/markdownlint_formatter.py +88 -0
  38. lintro/formatters/tools/prettier_formatter.py +15 -0
  39. lintro/formatters/tools/pytest_formatter.py +201 -0
  40. lintro/formatters/tools/ruff_formatter.py +26 -5
  41. lintro/formatters/tools/yamllint_formatter.py +14 -1
  42. lintro/models/__init__.py +1 -0
  43. lintro/models/core/__init__.py +1 -0
  44. lintro/models/core/tool_config.py +11 -7
  45. lintro/parsers/__init__.py +69 -9
  46. lintro/parsers/actionlint/actionlint_parser.py +1 -1
  47. lintro/parsers/bandit/__init__.py +6 -0
  48. lintro/parsers/bandit/bandit_issue.py +49 -0
  49. lintro/parsers/bandit/bandit_parser.py +99 -0
  50. lintro/parsers/black/black_issue.py +4 -0
  51. lintro/parsers/darglint/__init__.py +1 -0
  52. lintro/parsers/darglint/darglint_issue.py +11 -0
  53. lintro/parsers/eslint/__init__.py +6 -0
  54. lintro/parsers/eslint/eslint_issue.py +26 -0
  55. lintro/parsers/eslint/eslint_parser.py +63 -0
  56. lintro/parsers/markdownlint/__init__.py +6 -0
  57. lintro/parsers/markdownlint/markdownlint_issue.py +22 -0
  58. lintro/parsers/markdownlint/markdownlint_parser.py +113 -0
  59. lintro/parsers/prettier/__init__.py +1 -0
  60. lintro/parsers/prettier/prettier_issue.py +12 -0
  61. lintro/parsers/prettier/prettier_parser.py +1 -1
  62. lintro/parsers/pytest/__init__.py +21 -0
  63. lintro/parsers/pytest/pytest_issue.py +28 -0
  64. lintro/parsers/pytest/pytest_parser.py +483 -0
  65. lintro/parsers/ruff/ruff_parser.py +6 -2
  66. lintro/parsers/yamllint/__init__.py +1 -0
  67. lintro/tools/__init__.py +3 -1
  68. lintro/tools/core/__init__.py +1 -0
  69. lintro/tools/core/timeout_utils.py +112 -0
  70. lintro/tools/core/tool_base.py +286 -50
  71. lintro/tools/core/tool_manager.py +77 -24
  72. lintro/tools/core/version_requirements.py +482 -0
  73. lintro/tools/implementations/__init__.py +1 -0
  74. lintro/tools/implementations/pytest/pytest_command_builder.py +311 -0
  75. lintro/tools/implementations/pytest/pytest_config.py +200 -0
  76. lintro/tools/implementations/pytest/pytest_error_handler.py +128 -0
  77. lintro/tools/implementations/pytest/pytest_executor.py +122 -0
  78. lintro/tools/implementations/pytest/pytest_handlers.py +375 -0
  79. lintro/tools/implementations/pytest/pytest_option_validators.py +212 -0
  80. lintro/tools/implementations/pytest/pytest_output_processor.py +408 -0
  81. lintro/tools/implementations/pytest/pytest_result_processor.py +113 -0
  82. lintro/tools/implementations/pytest/pytest_utils.py +697 -0
  83. lintro/tools/implementations/tool_actionlint.py +106 -16
  84. lintro/tools/implementations/tool_bandit.py +34 -29
  85. lintro/tools/implementations/tool_black.py +236 -29
  86. lintro/tools/implementations/tool_darglint.py +183 -22
  87. lintro/tools/implementations/tool_eslint.py +374 -0
  88. lintro/tools/implementations/tool_hadolint.py +94 -25
  89. lintro/tools/implementations/tool_markdownlint.py +354 -0
  90. lintro/tools/implementations/tool_prettier.py +317 -24
  91. lintro/tools/implementations/tool_pytest.py +327 -0
  92. lintro/tools/implementations/tool_ruff.py +278 -84
  93. lintro/tools/implementations/tool_yamllint.py +448 -34
  94. lintro/tools/tool_enum.py +8 -0
  95. lintro/utils/__init__.py +1 -0
  96. lintro/utils/ascii_normalize_cli.py +5 -0
  97. lintro/utils/config.py +41 -18
  98. lintro/utils/console_logger.py +211 -25
  99. lintro/utils/path_utils.py +42 -0
  100. lintro/utils/tool_executor.py +339 -45
  101. lintro/utils/tool_utils.py +51 -24
  102. lintro/utils/unified_config.py +926 -0
  103. {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/METADATA +172 -30
  104. lintro-0.17.2.dist-info/RECORD +134 -0
  105. lintro-0.6.2.dist-info/RECORD +0 -96
  106. {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/WHEEL +0 -0
  107. {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/entry_points.txt +0 -0
  108. {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/licenses/LICENSE +0 -0
  109. {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,403 @@
1
+ """Tool configuration generator for Lintro.
2
+
3
+ This module provides CLI argument injection for enforced settings and
4
+ default config generation for tools without native configs.
5
+
6
+ The tiered configuration model:
7
+ 1. EXECUTION: What tools run and how
8
+ 2. ENFORCE: Cross-cutting settings injected via CLI flags
9
+ 3. DEFAULTS: Fallback config when no native config exists
10
+ 4. TOOLS: Per-tool enable/disable and config source
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import atexit
16
+ import json
17
+ import os
18
+ import tempfile
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from loguru import logger
23
+
24
+ from lintro.config.lintro_config import LintroConfig
25
+
26
+ try:
27
+ import yaml
28
+ except ImportError:
29
+ yaml = None # type: ignore[assignment]
30
+
31
+
32
+ # CLI flags for enforced settings: setting -> {tool: flag}
33
+ ENFORCE_CLI_FLAGS: dict[str, dict[str, str]] = {
34
+ "line_length": {
35
+ "ruff": "--line-length",
36
+ "black": "--line-length",
37
+ "prettier": "--print-width",
38
+ },
39
+ "target_python": {
40
+ "ruff": "--target-version",
41
+ "black": "--target-version",
42
+ },
43
+ }
44
+
45
+ # Tool config format for defaults generation
46
+ TOOL_CONFIG_FORMATS: dict[str, str] = {
47
+ "prettier": "json",
48
+ "yamllint": "yaml",
49
+ "markdownlint": "json",
50
+ "hadolint": "yaml",
51
+ "bandit": "yaml",
52
+ }
53
+
54
+ # Native config file patterns for checking if tool has native config
55
+ NATIVE_CONFIG_PATTERNS: dict[str, list[str]] = {
56
+ "prettier": [
57
+ ".prettierrc",
58
+ ".prettierrc.json",
59
+ ".prettierrc.yaml",
60
+ ".prettierrc.yml",
61
+ ".prettierrc.js",
62
+ ".prettierrc.cjs",
63
+ ".prettierrc.toml",
64
+ "prettier.config.js",
65
+ "prettier.config.cjs",
66
+ ],
67
+ "markdownlint": [
68
+ ".markdownlint-cli2.jsonc",
69
+ ".markdownlint-cli2.yaml",
70
+ ".markdownlint-cli2.cjs",
71
+ ".markdownlint.jsonc",
72
+ ".markdownlint.json",
73
+ ".markdownlint.yaml",
74
+ ".markdownlint.yml",
75
+ ".markdownlint.cjs",
76
+ ],
77
+ "yamllint": [
78
+ ".yamllint",
79
+ ".yamllint.yaml",
80
+ ".yamllint.yml",
81
+ ],
82
+ "hadolint": [
83
+ ".hadolint.yaml",
84
+ ".hadolint.yml",
85
+ ],
86
+ "bandit": [
87
+ ".bandit",
88
+ ".bandit.yaml",
89
+ ".bandit.yml",
90
+ "bandit.yaml",
91
+ "bandit.yml",
92
+ ],
93
+ }
94
+
95
+ # Track temporary files for cleanup
96
+ _temp_files: list[Path] = []
97
+
98
+
99
+ def _cleanup_temp_files() -> None:
100
+ """Clean up temporary config files on exit."""
101
+ for temp_file in _temp_files:
102
+ try:
103
+ if temp_file.exists():
104
+ temp_file.unlink()
105
+ logger.debug(f"Cleaned up temp config: {temp_file}")
106
+ except Exception as e:
107
+ logger.debug(f"Failed to clean up {temp_file}: {e}")
108
+
109
+
110
+ # Register cleanup on exit
111
+ atexit.register(_cleanup_temp_files)
112
+
113
+
114
+ def get_enforce_cli_args(
115
+ tool_name: str,
116
+ lintro_config: LintroConfig,
117
+ ) -> list[str]:
118
+ """Get CLI arguments for enforced settings.
119
+
120
+ These settings override native tool configs to ensure consistency
121
+ across different tools for shared concerns like line length.
122
+
123
+ Args:
124
+ tool_name: Name of the tool (e.g., "ruff", "prettier").
125
+ lintro_config: Lintro configuration.
126
+
127
+ Returns:
128
+ list[str]: CLI arguments to inject (e.g., ["--line-length", "88"]).
129
+ """
130
+ args: list[str] = []
131
+ tool_lower = tool_name.lower()
132
+ enforce = lintro_config.enforce
133
+
134
+ # Inject line_length if set
135
+ if enforce.line_length is not None:
136
+ flag = ENFORCE_CLI_FLAGS.get("line_length", {}).get(tool_lower)
137
+ if flag:
138
+ args.extend([flag, str(enforce.line_length)])
139
+ logger.debug(
140
+ f"Injecting enforce.line_length={enforce.line_length} "
141
+ f"to {tool_name} as {flag}",
142
+ )
143
+
144
+ # Inject target_python if set
145
+ if enforce.target_python is not None:
146
+ flag = ENFORCE_CLI_FLAGS.get("target_python", {}).get(tool_lower)
147
+ if flag:
148
+ args.extend([flag, enforce.target_python])
149
+ logger.debug(
150
+ f"Injecting enforce.target_python={enforce.target_python} "
151
+ f"to {tool_name} as {flag}",
152
+ )
153
+
154
+ return args
155
+
156
+
157
+ def has_native_config(tool_name: str) -> bool:
158
+ """Check if a tool has a native config file in the project.
159
+
160
+ Searches for known native config file patterns starting from the
161
+ current working directory and moving upward to find the project root.
162
+
163
+ Args:
164
+ tool_name: Name of the tool (e.g., "prettier", "markdownlint").
165
+
166
+ Returns:
167
+ bool: True if a native config file exists.
168
+ """
169
+ tool_lower = tool_name.lower()
170
+ patterns = NATIVE_CONFIG_PATTERNS.get(tool_lower, [])
171
+
172
+ if not patterns:
173
+ return False
174
+
175
+ # Search from current directory upward
176
+ current = Path.cwd().resolve()
177
+
178
+ while True:
179
+ for pattern in patterns:
180
+ config_path = current / pattern
181
+ if config_path.exists():
182
+ logger.debug(
183
+ f"Found native config for {tool_name}: {config_path}",
184
+ )
185
+ return True
186
+
187
+ # Move up one directory
188
+ parent = current.parent
189
+ if parent == current:
190
+ # Reached filesystem root
191
+ break
192
+ current = parent
193
+
194
+ return False
195
+
196
+
197
+ def generate_defaults_config(
198
+ tool_name: str,
199
+ lintro_config: LintroConfig,
200
+ ) -> Path | None:
201
+ """Generate a temporary config file from defaults.
202
+
203
+ Only used when a tool has no native config file and defaults
204
+ are specified in the Lintro config.
205
+
206
+ Args:
207
+ tool_name: Name of the tool.
208
+ lintro_config: Lintro configuration.
209
+
210
+ Returns:
211
+ Path | None: Path to generated config file, or None if not needed.
212
+ """
213
+ tool_lower = tool_name.lower()
214
+
215
+ # Check if tool has native config - if so, don't generate defaults
216
+ if has_native_config(tool_lower):
217
+ logger.debug(
218
+ f"Tool {tool_name} has native config, skipping defaults generation",
219
+ )
220
+ return None
221
+
222
+ # Get defaults for this tool
223
+ defaults = lintro_config.get_tool_defaults(tool_lower)
224
+ if not defaults:
225
+ return None
226
+
227
+ # Get config format for this tool
228
+ config_format = TOOL_CONFIG_FORMATS.get(tool_lower, "json")
229
+
230
+ try:
231
+ return _write_defaults_config(
232
+ defaults=defaults,
233
+ tool_name=tool_lower,
234
+ config_format=config_format,
235
+ )
236
+ except Exception as e:
237
+ logger.error(f"Failed to generate defaults config for {tool_name}: {e}")
238
+ return None
239
+
240
+
241
+ def _write_defaults_config(
242
+ defaults: dict[str, Any],
243
+ tool_name: str,
244
+ config_format: str,
245
+ ) -> Path:
246
+ """Write defaults configuration to a temporary file.
247
+
248
+ Args:
249
+ defaults: Default configuration dictionary.
250
+ tool_name: Name of the tool.
251
+ config_format: Output format (json, yaml).
252
+
253
+ Returns:
254
+ Path: Path to temporary config file.
255
+
256
+ Raises:
257
+ ImportError: If PyYAML is not installed and YAML format is requested.
258
+ """
259
+ suffix_map = {"json": ".json", "yaml": ".yaml"}
260
+ suffix = suffix_map.get(config_format, ".json")
261
+
262
+ temp_fd, temp_path_str = tempfile.mkstemp(
263
+ prefix=f"lintro-{tool_name}-defaults-",
264
+ suffix=suffix,
265
+ )
266
+ os.close(temp_fd)
267
+ temp_path = Path(temp_path_str)
268
+ _temp_files.append(temp_path)
269
+
270
+ if config_format == "yaml":
271
+ if yaml is None:
272
+ raise ImportError("PyYAML required for YAML output")
273
+ content = yaml.dump(defaults, default_flow_style=False)
274
+ else:
275
+ content = json.dumps(defaults, indent=2)
276
+
277
+ temp_path.write_text(content, encoding="utf-8")
278
+ logger.debug(f"Generated defaults config for {tool_name}: {temp_path}")
279
+
280
+ return temp_path
281
+
282
+
283
+ def get_defaults_injection_args(
284
+ tool_name: str,
285
+ config_path: Path | None,
286
+ ) -> list[str]:
287
+ """Get CLI arguments to inject defaults config file into a tool.
288
+
289
+ Args:
290
+ tool_name: Name of the tool.
291
+ config_path: Path to defaults config file (or None).
292
+
293
+ Returns:
294
+ list[str]: CLI arguments to pass to the tool.
295
+ """
296
+ if config_path is None:
297
+ return []
298
+
299
+ tool_lower = tool_name.lower()
300
+ config_str = str(config_path)
301
+
302
+ # Tool-specific config flags
303
+ config_flags: dict[str, list[str]] = {
304
+ "prettier": ["--config", config_str],
305
+ "yamllint": ["-c", config_str],
306
+ "markdownlint": ["--config", config_str],
307
+ "hadolint": ["--config", config_str],
308
+ "bandit": ["-c", config_str],
309
+ }
310
+
311
+ return config_flags.get(tool_lower, [])
312
+
313
+
314
+ def cleanup_temp_config(config_path: Path) -> None:
315
+ """Explicitly clean up a temporary config file.
316
+
317
+ Args:
318
+ config_path: Path to temporary config file.
319
+ """
320
+ try:
321
+ if config_path in _temp_files:
322
+ _temp_files.remove(config_path)
323
+ if config_path.exists():
324
+ config_path.unlink()
325
+ logger.debug(f"Cleaned up temp config: {config_path}")
326
+ except Exception as e:
327
+ logger.debug(f"Failed to clean up {config_path}: {e}")
328
+
329
+
330
+ # =============================================================================
331
+ # DEPRECATED: Legacy functions for backward compatibility
332
+ # These will be removed in a future version.
333
+ # =============================================================================
334
+
335
+
336
+ def generate_tool_config(
337
+ tool_name: str,
338
+ lintro_config: LintroConfig,
339
+ ) -> Path | None:
340
+ """Generate a temporary configuration file for a tool.
341
+
342
+ DEPRECATED: This function is deprecated. Use get_enforce_cli_args() for
343
+ CLI flag injection and generate_defaults_config() for defaults.
344
+
345
+ Args:
346
+ tool_name: Name of the tool.
347
+ lintro_config: Lintro configuration.
348
+
349
+ Returns:
350
+ Path | None: Path to generated config file, or None.
351
+ """
352
+ logger.warning(
353
+ f"generate_tool_config() is deprecated for {tool_name}. "
354
+ "Use get_enforce_cli_args() instead.",
355
+ )
356
+ return generate_defaults_config(
357
+ tool_name=tool_name,
358
+ lintro_config=lintro_config,
359
+ )
360
+
361
+
362
+ def get_config_injection_args(
363
+ tool_name: str,
364
+ config_path: Path | None,
365
+ ) -> list[str]:
366
+ """Get CLI arguments to inject config file into a tool.
367
+
368
+ DEPRECATED: Use get_defaults_injection_args() instead.
369
+
370
+ Args:
371
+ tool_name: Name of the tool.
372
+ config_path: Path to config file (or None).
373
+
374
+ Returns:
375
+ list[str]: CLI arguments to pass to the tool.
376
+ """
377
+ logger.warning(
378
+ f"get_config_injection_args() is deprecated for {tool_name}. "
379
+ "Use get_defaults_injection_args() instead.",
380
+ )
381
+ return get_defaults_injection_args(
382
+ tool_name=tool_name,
383
+ config_path=config_path,
384
+ )
385
+
386
+
387
+ def get_no_auto_config_args(tool_name: str) -> list[str]:
388
+ """Get CLI arguments to disable auto-config discovery.
389
+
390
+ DEPRECATED: No longer needed with the tiered model.
391
+ Tools use their native configs by default.
392
+
393
+ Args:
394
+ tool_name: Name of the tool.
395
+
396
+ Returns:
397
+ list[str]: Empty list (no longer used).
398
+ """
399
+ logger.warning(
400
+ f"get_no_auto_config_args() is deprecated for {tool_name}. "
401
+ "No longer needed with the tiered config model.",
402
+ )
403
+ return []
lintro/enums/__init__.py CHANGED
@@ -0,0 +1 @@
1
+ """Enumeration types used throughout the Lintro codebase."""
@@ -6,6 +6,8 @@ from enum import StrEnum, auto
6
6
 
7
7
 
8
8
  class DarglintStrictness(StrEnum):
9
+ """Strictness levels recognized by Darglint checks."""
10
+
9
11
  SHORT = auto()
10
12
  LONG = auto()
11
13
  FULL = auto()
@@ -14,6 +16,14 @@ class DarglintStrictness(StrEnum):
14
16
  def normalize_darglint_strictness(
15
17
  value: str | DarglintStrictness,
16
18
  ) -> DarglintStrictness:
19
+ """Normalize a strictness value, defaulting to FULL on error.
20
+
21
+ Args:
22
+ value: String or enum member representing strictness.
23
+
24
+ Returns:
25
+ DarglintStrictness: Normalized strictness enum value.
26
+ """
17
27
  if isinstance(value, DarglintStrictness):
18
28
  return value
19
29
  try:
@@ -6,6 +6,8 @@ from enum import StrEnum, auto
6
6
 
7
7
 
8
8
  class HadolintFormat(StrEnum):
9
+ """Supported output formats for Hadolint."""
10
+
9
11
  TTY = auto()
10
12
  JSON = auto()
11
13
  CHECKSTYLE = auto()
@@ -18,6 +20,8 @@ class HadolintFormat(StrEnum):
18
20
 
19
21
 
20
22
  class HadolintFailureThreshold(StrEnum):
23
+ """Hadolint failure thresholds used to gate exit status."""
24
+
21
25
  ERROR = auto()
22
26
  WARNING = auto()
23
27
  INFO = auto()
@@ -27,6 +31,15 @@ class HadolintFailureThreshold(StrEnum):
27
31
 
28
32
 
29
33
  def normalize_hadolint_format(value: str | HadolintFormat) -> HadolintFormat:
34
+ """Normalize user input to a HadolintFormat.
35
+
36
+ Args:
37
+ value: Existing enum member or string name of the format.
38
+
39
+ Returns:
40
+ HadolintFormat: Canonical enum value, defaulting to ``TTY`` when
41
+ parsing fails.
42
+ """
30
43
  if isinstance(value, HadolintFormat):
31
44
  return value
32
45
  try:
@@ -38,6 +51,15 @@ def normalize_hadolint_format(value: str | HadolintFormat) -> HadolintFormat:
38
51
  def normalize_hadolint_threshold(
39
52
  value: str | HadolintFailureThreshold,
40
53
  ) -> HadolintFailureThreshold:
54
+ """Normalize user input to a HadolintFailureThreshold.
55
+
56
+ Args:
57
+ value: Existing enum member or string name of the threshold.
58
+
59
+ Returns:
60
+ HadolintFailureThreshold: Canonical enum value, defaulting to ``INFO``
61
+ when parsing fails.
62
+ """
41
63
  if isinstance(value, HadolintFailureThreshold):
42
64
  return value
43
65
  try:
lintro/enums/tool_name.py CHANGED
@@ -12,7 +12,9 @@ class ToolName(StrEnum):
12
12
  """Supported tool identifiers in lower-case values."""
13
13
 
14
14
  DARGLINT = auto()
15
+ ESLINT = auto()
15
16
  HADOLINT = auto()
17
+ MARKDOWNLINT = auto()
16
18
  PRETTIER = auto()
17
19
  RUFF = auto()
18
20
  YAMLLINT = auto()
lintro/enums/tool_type.py CHANGED
@@ -17,6 +17,7 @@ class ToolType(Flag):
17
17
  DOCUMENTATION = Tool that checks documentation
18
18
  SECURITY = Tool that checks for security issues
19
19
  INFRASTRUCTURE = Tool that checks infrastructure code
20
+ TEST_RUNNER = Tool that runs tests
20
21
  """
21
22
 
22
23
  LINTER = auto()
@@ -25,3 +26,4 @@ class ToolType(Flag):
25
26
  DOCUMENTATION = auto()
26
27
  SECURITY = auto()
27
28
  INFRASTRUCTURE = auto()
29
+ TEST_RUNNER = auto()
@@ -6,6 +6,8 @@ from enum import StrEnum, auto
6
6
 
7
7
 
8
8
  class YamllintFormat(StrEnum):
9
+ """Output styles supported by Yamllint's CLI."""
10
+
9
11
  PARSABLE = auto()
10
12
  STANDARD = auto()
11
13
  COLORED = auto()
@@ -14,6 +16,15 @@ class YamllintFormat(StrEnum):
14
16
 
15
17
 
16
18
  def normalize_yamllint_format(value: str | YamllintFormat) -> YamllintFormat:
19
+ """Normalize a value to a YamllintFormat enum member.
20
+
21
+ Args:
22
+ value: Existing enum member or string name of the format.
23
+
24
+ Returns:
25
+ YamllintFormat: Canonical enum value, defaulting to ``PARSABLE`` when
26
+ parsing fails.
27
+ """
17
28
  if isinstance(value, YamllintFormat):
18
29
  return value
19
30
  try:
@@ -0,0 +1 @@
1
+ """Project-specific exception classes and error helpers."""
@@ -0,0 +1 @@
1
+ """Formatters for converting tool outputs into human-friendly tables."""
@@ -0,0 +1 @@
1
+ """Core formatting abstractions for tool-agnostic table rendering."""
@@ -1,8 +1,19 @@
1
+ """Output style abstraction for rendering tabular data.
2
+
3
+ Defines a minimal interface consumed by format-specific implementations.
4
+ """
5
+
1
6
  from abc import ABC, abstractmethod
2
7
  from typing import Any
3
8
 
4
9
 
5
10
  class OutputStyle(ABC):
11
+ """Abstract base class for output style renderers.
12
+
13
+ Implementations convert tabular data into a concrete textual
14
+ representation (e.g., grid, markdown, plain).
15
+ """
16
+
6
17
  @abstractmethod
7
18
  def format(
8
19
  self,
@@ -1,8 +1,16 @@
1
+ """Interfaces for describing table columns and rows for tool issues."""
2
+
1
3
  from abc import ABC, abstractmethod
2
4
  from typing import Any
3
5
 
4
6
 
5
7
  class TableDescriptor(ABC):
8
+ """Describe how to extract tabular data for a tool's issues.
9
+
10
+ Concrete implementations define column ordering and how to map issue
11
+ objects into a list of column values.
12
+ """
13
+
6
14
  @abstractmethod
7
15
  def get_columns(self) -> list[str]:
8
16
  """Return the list of column names in order."""
@@ -1,3 +1,5 @@
1
+ """CSV output style implementation."""
2
+
1
3
  import csv
2
4
  import io
3
5
  from typing import Any
@@ -1,3 +1,5 @@
1
+ """Grid output style implementation."""
2
+
1
3
  from typing import Any
2
4
 
3
5
  from lintro.formatters.core.output_style import OutputStyle
@@ -1,3 +1,5 @@
1
+ """HTML output style implementation."""
2
+
1
3
  from typing import Any
2
4
 
3
5
  from lintro.formatters.core.output_style import OutputStyle
@@ -1,3 +1,5 @@
1
+ """JSON output style implementation."""
2
+
1
3
  import json
2
4
  from datetime import datetime
3
5
  from typing import Any
@@ -1,3 +1,5 @@
1
+ """Markdown output style implementation."""
2
+
1
3
  from typing import Any
2
4
 
3
5
  from lintro.formatters.core.output_style import OutputStyle
@@ -1,3 +1,5 @@
1
+ """Plain text output style implementation."""
2
+
1
3
  from typing import Any
2
4
 
3
5
  from lintro.formatters.core.output_style import OutputStyle
@@ -12,10 +12,18 @@ from lintro.formatters.tools.darglint_formatter import (
12
12
  DarglintTableDescriptor,
13
13
  format_darglint_issues,
14
14
  )
15
+ from lintro.formatters.tools.eslint_formatter import (
16
+ EslintTableDescriptor,
17
+ format_eslint_issues,
18
+ )
15
19
  from lintro.formatters.tools.hadolint_formatter import (
16
20
  HadolintTableDescriptor,
17
21
  format_hadolint_issues,
18
22
  )
23
+ from lintro.formatters.tools.markdownlint_formatter import (
24
+ MarkdownlintTableDescriptor,
25
+ format_markdownlint_issues,
26
+ )
19
27
  from lintro.formatters.tools.prettier_formatter import (
20
28
  PrettierTableDescriptor,
21
29
  format_prettier_issues,
@@ -36,8 +44,12 @@ __all__ = [
36
44
  "format_bandit_issues",
37
45
  "DarglintTableDescriptor",
38
46
  "format_darglint_issues",
47
+ "EslintTableDescriptor",
48
+ "format_eslint_issues",
39
49
  "HadolintTableDescriptor",
40
50
  "format_hadolint_issues",
51
+ "MarkdownlintTableDescriptor",
52
+ "format_markdownlint_issues",
41
53
  "PrettierTableDescriptor",
42
54
  "format_prettier_issues",
43
55
  "RuffTableDescriptor",