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.

Files changed (85) hide show
  1. lintro/__init__.py +3 -0
  2. lintro/__main__.py +6 -0
  3. lintro/ascii-art/fail.txt +404 -0
  4. lintro/ascii-art/success.txt +484 -0
  5. lintro/cli.py +70 -0
  6. lintro/cli_utils/__init__.py +7 -0
  7. lintro/cli_utils/commands/__init__.py +7 -0
  8. lintro/cli_utils/commands/check.py +210 -0
  9. lintro/cli_utils/commands/format.py +167 -0
  10. lintro/cli_utils/commands/list_tools.py +114 -0
  11. lintro/enums/__init__.py +0 -0
  12. lintro/enums/action.py +29 -0
  13. lintro/enums/darglint_strictness.py +22 -0
  14. lintro/enums/group_by.py +31 -0
  15. lintro/enums/hadolint_enums.py +46 -0
  16. lintro/enums/output_format.py +40 -0
  17. lintro/enums/tool_name.py +36 -0
  18. lintro/enums/tool_type.py +27 -0
  19. lintro/enums/yamllint_format.py +22 -0
  20. lintro/exceptions/__init__.py +0 -0
  21. lintro/exceptions/errors.py +15 -0
  22. lintro/formatters/__init__.py +0 -0
  23. lintro/formatters/core/__init__.py +0 -0
  24. lintro/formatters/core/output_style.py +21 -0
  25. lintro/formatters/core/table_descriptor.py +24 -0
  26. lintro/formatters/styles/__init__.py +17 -0
  27. lintro/formatters/styles/csv.py +41 -0
  28. lintro/formatters/styles/grid.py +91 -0
  29. lintro/formatters/styles/html.py +48 -0
  30. lintro/formatters/styles/json.py +61 -0
  31. lintro/formatters/styles/markdown.py +41 -0
  32. lintro/formatters/styles/plain.py +39 -0
  33. lintro/formatters/tools/__init__.py +35 -0
  34. lintro/formatters/tools/darglint_formatter.py +72 -0
  35. lintro/formatters/tools/hadolint_formatter.py +84 -0
  36. lintro/formatters/tools/prettier_formatter.py +76 -0
  37. lintro/formatters/tools/ruff_formatter.py +116 -0
  38. lintro/formatters/tools/yamllint_formatter.py +87 -0
  39. lintro/models/__init__.py +0 -0
  40. lintro/models/core/__init__.py +0 -0
  41. lintro/models/core/tool.py +104 -0
  42. lintro/models/core/tool_config.py +23 -0
  43. lintro/models/core/tool_result.py +39 -0
  44. lintro/parsers/__init__.py +0 -0
  45. lintro/parsers/darglint/__init__.py +0 -0
  46. lintro/parsers/darglint/darglint_issue.py +9 -0
  47. lintro/parsers/darglint/darglint_parser.py +62 -0
  48. lintro/parsers/hadolint/__init__.py +1 -0
  49. lintro/parsers/hadolint/hadolint_issue.py +24 -0
  50. lintro/parsers/hadolint/hadolint_parser.py +65 -0
  51. lintro/parsers/prettier/__init__.py +0 -0
  52. lintro/parsers/prettier/prettier_issue.py +10 -0
  53. lintro/parsers/prettier/prettier_parser.py +60 -0
  54. lintro/parsers/ruff/__init__.py +1 -0
  55. lintro/parsers/ruff/ruff_issue.py +43 -0
  56. lintro/parsers/ruff/ruff_parser.py +89 -0
  57. lintro/parsers/yamllint/__init__.py +0 -0
  58. lintro/parsers/yamllint/yamllint_issue.py +24 -0
  59. lintro/parsers/yamllint/yamllint_parser.py +68 -0
  60. lintro/tools/__init__.py +40 -0
  61. lintro/tools/core/__init__.py +0 -0
  62. lintro/tools/core/tool_base.py +320 -0
  63. lintro/tools/core/tool_manager.py +167 -0
  64. lintro/tools/implementations/__init__.py +0 -0
  65. lintro/tools/implementations/tool_darglint.py +245 -0
  66. lintro/tools/implementations/tool_hadolint.py +302 -0
  67. lintro/tools/implementations/tool_prettier.py +270 -0
  68. lintro/tools/implementations/tool_ruff.py +618 -0
  69. lintro/tools/implementations/tool_yamllint.py +240 -0
  70. lintro/tools/tool_enum.py +17 -0
  71. lintro/utils/__init__.py +0 -0
  72. lintro/utils/ascii_normalize_cli.py +84 -0
  73. lintro/utils/config.py +39 -0
  74. lintro/utils/console_logger.py +783 -0
  75. lintro/utils/formatting.py +173 -0
  76. lintro/utils/output_manager.py +301 -0
  77. lintro/utils/path_utils.py +41 -0
  78. lintro/utils/tool_executor.py +443 -0
  79. lintro/utils/tool_utils.py +431 -0
  80. lintro-0.3.2.dist-info/METADATA +338 -0
  81. lintro-0.3.2.dist-info/RECORD +85 -0
  82. lintro-0.3.2.dist-info/WHEEL +5 -0
  83. lintro-0.3.2.dist-info/entry_points.txt +2 -0
  84. lintro-0.3.2.dist-info/licenses/LICENSE +21 -0
  85. lintro-0.3.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,72 @@
1
+ """Formatter for darglint issues."""
2
+
3
+ from lintro.formatters.core.table_descriptor import TableDescriptor
4
+ from lintro.formatters.styles.csv import CsvStyle
5
+ from lintro.formatters.styles.grid import GridStyle
6
+ from lintro.formatters.styles.html import HtmlStyle
7
+ from lintro.formatters.styles.json import JsonStyle
8
+ from lintro.formatters.styles.markdown import MarkdownStyle
9
+ from lintro.formatters.styles.plain import PlainStyle
10
+ from lintro.parsers.darglint.darglint_issue import DarglintIssue
11
+ from lintro.utils.path_utils import normalize_file_path_for_display
12
+
13
+ FORMAT_MAP = {
14
+ "plain": PlainStyle(),
15
+ "grid": GridStyle(),
16
+ "markdown": MarkdownStyle(),
17
+ "html": HtmlStyle(),
18
+ "json": JsonStyle(),
19
+ "csv": CsvStyle(),
20
+ }
21
+
22
+
23
+ class DarglintTableDescriptor(TableDescriptor):
24
+ def get_columns(self) -> list[str]:
25
+ return ["File", "Line", "Code", "Message"]
26
+
27
+ def get_rows(
28
+ self,
29
+ issues: list[DarglintIssue],
30
+ ) -> list[list[str]]:
31
+ rows = []
32
+ for issue in issues:
33
+ rows.append(
34
+ [
35
+ normalize_file_path_for_display(issue.file),
36
+ str(issue.line),
37
+ issue.code,
38
+ issue.message,
39
+ ],
40
+ )
41
+ return rows
42
+
43
+
44
+ def format_darglint_issues(
45
+ issues: list[DarglintIssue],
46
+ format: str = "grid",
47
+ ) -> str:
48
+ """Format a list of Darglint issues using the specified format.
49
+
50
+ Args:
51
+ issues: List of Darglint issues to format.
52
+ format: Output format (plain, grid, markdown, html, json, csv).
53
+
54
+ Returns:
55
+ Formatted string representation of the issues.
56
+ """
57
+ descriptor = DarglintTableDescriptor()
58
+ columns = descriptor.get_columns()
59
+ rows = descriptor.get_rows(issues)
60
+
61
+ formatter = FORMAT_MAP.get(format, GridStyle())
62
+
63
+ # For JSON format, pass tool name
64
+ if format == "json":
65
+ formatted_table = formatter.format(
66
+ columns=columns, rows=rows, tool_name="darglint"
67
+ )
68
+ else:
69
+ # For other formats, use standard formatting
70
+ formatted_table = formatter.format(columns=columns, rows=rows)
71
+
72
+ return formatted_table
@@ -0,0 +1,84 @@
1
+ """Hadolint table formatting utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from lintro.formatters.core.table_descriptor import TableDescriptor
8
+ from lintro.formatters.styles.csv import CsvStyle
9
+ from lintro.formatters.styles.grid import GridStyle
10
+ from lintro.formatters.styles.html import HtmlStyle
11
+ from lintro.formatters.styles.json import JsonStyle
12
+ from lintro.formatters.styles.markdown import MarkdownStyle
13
+ from lintro.formatters.styles.plain import PlainStyle
14
+ from lintro.parsers.hadolint.hadolint_issue import HadolintIssue
15
+ from lintro.utils.path_utils import normalize_file_path_for_display
16
+
17
+
18
+ class HadolintTableDescriptor(TableDescriptor):
19
+ """Describe hadolint issue columns and row extraction."""
20
+
21
+ def get_columns(self) -> list[str]:
22
+ return [
23
+ "File",
24
+ "Line",
25
+ "Column",
26
+ "Level",
27
+ "Code",
28
+ "Message",
29
+ ]
30
+
31
+ def get_rows(
32
+ self,
33
+ issues: list[HadolintIssue],
34
+ ) -> list[list[Any]]:
35
+ rows: list[list[Any]] = []
36
+ for issue in issues:
37
+ rows.append(
38
+ [
39
+ normalize_file_path_for_display(issue.file),
40
+ issue.line,
41
+ issue.column if getattr(issue, "column", None) is not None else "",
42
+ issue.level,
43
+ issue.code,
44
+ issue.message,
45
+ ],
46
+ )
47
+ return rows
48
+
49
+
50
+ def format_hadolint_issues(
51
+ issues: list[HadolintIssue],
52
+ format: str = "grid",
53
+ ) -> str:
54
+ """Format hadolint issues to the given style.
55
+
56
+ Args:
57
+ issues: List of HadolintIssue instances.
58
+ format: Output style identifier.
59
+
60
+ Returns:
61
+ Rendered string for the issues table.
62
+ """
63
+ descriptor = HadolintTableDescriptor()
64
+ columns = descriptor.get_columns()
65
+ rows = descriptor.get_rows(issues)
66
+
67
+ if not rows:
68
+ return "No issues found."
69
+
70
+ style = (format or "grid").lower()
71
+ if style == "grid":
72
+ return GridStyle().format(columns=columns, rows=rows)
73
+ if style == "plain":
74
+ return PlainStyle().format(columns=columns, rows=rows)
75
+ if style == "markdown":
76
+ return MarkdownStyle().format(columns=columns, rows=rows)
77
+ if style == "html":
78
+ return HtmlStyle().format(columns=columns, rows=rows)
79
+ if style == "json":
80
+ return JsonStyle().format(columns=columns, rows=rows, tool_name="hadolint")
81
+ if style == "csv":
82
+ return CsvStyle().format(columns=columns, rows=rows)
83
+
84
+ return GridStyle().format(columns=columns, rows=rows)
@@ -0,0 +1,76 @@
1
+ """Formatter for Prettier issues."""
2
+
3
+ from lintro.formatters.core.table_descriptor import TableDescriptor
4
+ from lintro.formatters.styles.csv import CsvStyle
5
+ from lintro.formatters.styles.grid import GridStyle
6
+ from lintro.formatters.styles.html import HtmlStyle
7
+ from lintro.formatters.styles.json import JsonStyle
8
+ from lintro.formatters.styles.markdown import MarkdownStyle
9
+ from lintro.formatters.styles.plain import PlainStyle
10
+ from lintro.parsers.prettier.prettier_issue import PrettierIssue
11
+ from lintro.utils.path_utils import normalize_file_path_for_display
12
+
13
+ FORMAT_MAP = {
14
+ "plain": PlainStyle(),
15
+ "grid": GridStyle(),
16
+ "markdown": MarkdownStyle(),
17
+ "html": HtmlStyle(),
18
+ "json": JsonStyle(),
19
+ "csv": CsvStyle(),
20
+ }
21
+
22
+
23
+ class PrettierTableDescriptor(TableDescriptor):
24
+ def get_columns(self) -> list[str]:
25
+ return ["File", "Line", "Column", "Code", "Message"]
26
+
27
+ def get_rows(
28
+ self,
29
+ issues: list[PrettierIssue],
30
+ ) -> list[list[str]]:
31
+ rows = []
32
+ for issue in issues:
33
+ rows.append(
34
+ [
35
+ normalize_file_path_for_display(issue.file),
36
+ str(issue.line) if issue.line is not None else "-",
37
+ str(issue.column) if issue.column is not None else "-",
38
+ issue.code,
39
+ issue.message,
40
+ ],
41
+ )
42
+ return rows
43
+
44
+
45
+ def format_prettier_issues(
46
+ issues: list[PrettierIssue],
47
+ format: str = "grid",
48
+ ) -> str:
49
+ """Format Prettier issues with auto-fixable labeling.
50
+
51
+ Args:
52
+ issues: List of PrettierIssue objects.
53
+ format: Output format identifier (e.g., "grid", "json").
54
+
55
+ Returns:
56
+ str: Formatted output string.
57
+
58
+ Notes:
59
+ All Prettier issues are auto-fixable by running `lintro format`.
60
+ For non-JSON formats, a single section is labeled as auto-fixable.
61
+ JSON returns the combined table for compatibility.
62
+ """
63
+ descriptor = PrettierTableDescriptor()
64
+ formatter = FORMAT_MAP.get(format, GridStyle())
65
+
66
+ if format == "json":
67
+ columns = descriptor.get_columns()
68
+ rows = descriptor.get_rows(issues)
69
+ return formatter.format(columns=columns, rows=rows, tool_name="prettier")
70
+
71
+ columns = descriptor.get_columns()
72
+ rows = descriptor.get_rows(issues)
73
+ table = formatter.format(columns=columns, rows=rows)
74
+ if not rows:
75
+ return table
76
+ return "Auto-fixable issues\n" + table
@@ -0,0 +1,116 @@
1
+ """Formatter for ruff issues."""
2
+
3
+ from lintro.formatters.core.table_descriptor import TableDescriptor
4
+ from lintro.formatters.styles.csv import CsvStyle
5
+ from lintro.formatters.styles.grid import GridStyle
6
+ from lintro.formatters.styles.html import HtmlStyle
7
+ from lintro.formatters.styles.json import JsonStyle
8
+ from lintro.formatters.styles.markdown import MarkdownStyle
9
+ from lintro.formatters.styles.plain import PlainStyle
10
+ from lintro.parsers.ruff.ruff_issue import RuffFormatIssue, RuffIssue
11
+ from lintro.utils.path_utils import normalize_file_path_for_display
12
+
13
+ FORMAT_MAP = {
14
+ "plain": PlainStyle(),
15
+ "grid": GridStyle(),
16
+ "markdown": MarkdownStyle(),
17
+ "html": HtmlStyle(),
18
+ "json": JsonStyle(),
19
+ "csv": CsvStyle(),
20
+ }
21
+
22
+
23
+ class RuffTableDescriptor(TableDescriptor):
24
+ def get_columns(self) -> list[str]:
25
+ return ["File", "Line", "Column", "Code", "Message"]
26
+
27
+ def get_rows(
28
+ self,
29
+ issues: list[RuffIssue | RuffFormatIssue],
30
+ ) -> list[list[str]]:
31
+ rows = []
32
+ for issue in issues:
33
+ if isinstance(issue, RuffIssue):
34
+ # Linting issue
35
+ rows.append(
36
+ [
37
+ normalize_file_path_for_display(issue.file),
38
+ str(issue.line),
39
+ str(issue.column),
40
+ issue.code,
41
+ issue.message,
42
+ ],
43
+ )
44
+ elif isinstance(issue, RuffFormatIssue):
45
+ # From Lintro's perspective, fmt applies both lint fixes and
46
+ # formatting, so formatting entries are auto-fixable by fmt.
47
+ rows.append(
48
+ [
49
+ normalize_file_path_for_display(issue.file),
50
+ "-",
51
+ "-",
52
+ "FORMAT",
53
+ "Would reformat file",
54
+ ],
55
+ )
56
+ return rows
57
+
58
+
59
+ def format_ruff_issues(
60
+ issues: list[RuffIssue | RuffFormatIssue],
61
+ format: str = "grid",
62
+ ) -> str:
63
+ """Format Ruff issues, split into auto-fixable and not auto-fixable tables.
64
+
65
+ For JSON format, return a single combined table (backwards compatible).
66
+
67
+ Args:
68
+ issues: List of Ruff issues to format.
69
+ format: Output format (plain, grid, markdown, html, json, csv).
70
+
71
+ Returns:
72
+ Formatted string (one or two tables depending on format).
73
+ """
74
+ descriptor = RuffTableDescriptor()
75
+ formatter = FORMAT_MAP.get(format, GridStyle())
76
+
77
+ # Partition issues
78
+ fixable_issues: list[RuffIssue | RuffFormatIssue] = []
79
+ non_fixable_issues: list[RuffIssue] = []
80
+
81
+ for issue in issues:
82
+ if isinstance(issue, RuffFormatIssue):
83
+ fixable_issues.append(issue)
84
+ elif isinstance(issue, RuffIssue) and issue.fixable:
85
+ fixable_issues.append(issue)
86
+ elif isinstance(issue, RuffIssue):
87
+ non_fixable_issues.append(issue)
88
+
89
+ # JSON: keep a single table for compatibility
90
+ if format == "json":
91
+ columns = descriptor.get_columns()
92
+ rows = descriptor.get_rows(issues)
93
+ return formatter.format(columns=columns, rows=rows, tool_name="ruff")
94
+
95
+ sections: list[str] = []
96
+
97
+ # Auto-fixable section
98
+ if fixable_issues:
99
+ columns_f = descriptor.get_columns()
100
+ rows_f = descriptor.get_rows(fixable_issues)
101
+ table_f = formatter.format(columns=columns_f, rows=rows_f)
102
+ sections.append("Auto-fixable issues\n" + table_f)
103
+
104
+ # Not auto-fixable section
105
+ if non_fixable_issues:
106
+ columns_u = descriptor.get_columns()
107
+ rows_u = descriptor.get_rows(non_fixable_issues)
108
+ table_u = formatter.format(columns=columns_u, rows=rows_u)
109
+ sections.append("Not auto-fixable issues\n" + table_u)
110
+
111
+ # If neither, return empty table structure
112
+ if not sections:
113
+ columns = descriptor.get_columns()
114
+ return formatter.format(columns=columns, rows=[])
115
+
116
+ return "\n\n".join(sections)
@@ -0,0 +1,87 @@
1
+ """Yamllint table formatting utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from lintro.formatters.core.table_descriptor import TableDescriptor
8
+ from lintro.formatters.styles.csv import CsvStyle
9
+ from lintro.formatters.styles.grid import GridStyle
10
+ from lintro.formatters.styles.html import HtmlStyle
11
+ from lintro.formatters.styles.json import JsonStyle
12
+ from lintro.formatters.styles.markdown import MarkdownStyle
13
+ from lintro.formatters.styles.plain import PlainStyle
14
+ from lintro.parsers.yamllint.yamllint_issue import YamllintIssue
15
+ from lintro.utils.path_utils import normalize_file_path_for_display
16
+
17
+
18
+ class YamllintTableDescriptor(TableDescriptor):
19
+ """Describe yamllint issue columns and row extraction."""
20
+
21
+ def get_columns(self) -> list[str]:
22
+ return [
23
+ "File",
24
+ "Line",
25
+ "Column",
26
+ "Level",
27
+ "Rule",
28
+ "Message",
29
+ ]
30
+
31
+ def get_rows(
32
+ self,
33
+ issues: list[YamllintIssue],
34
+ ) -> list[list[Any]]:
35
+ rows: list[list[Any]] = []
36
+ for issue in issues:
37
+ rows.append(
38
+ [
39
+ normalize_file_path_for_display(issue.file),
40
+ issue.line,
41
+ issue.column if getattr(issue, "column", None) is not None else "",
42
+ issue.level,
43
+ issue.rule or "",
44
+ issue.message,
45
+ ],
46
+ )
47
+ return rows
48
+
49
+
50
+ def format_yamllint_issues(
51
+ issues: list[YamllintIssue],
52
+ format: str = "grid",
53
+ *,
54
+ tool_name: str = "yamllint",
55
+ ) -> str:
56
+ """Format yamllint issues to the given style.
57
+
58
+ Args:
59
+ issues: List of YamllintIssue instances.
60
+ format: Output style identifier.
61
+ tool_name: Tool name for JSON metadata.
62
+
63
+ Returns:
64
+ Rendered string for the issues table.
65
+ """
66
+ descriptor = YamllintTableDescriptor()
67
+ columns = descriptor.get_columns()
68
+ rows = descriptor.get_rows(issues)
69
+
70
+ if not rows:
71
+ return "No issues found."
72
+
73
+ style = (format or "grid").lower()
74
+ if style == "grid":
75
+ return GridStyle().format(columns=columns, rows=rows)
76
+ if style == "plain":
77
+ return PlainStyle().format(columns=columns, rows=rows)
78
+ if style == "markdown":
79
+ return MarkdownStyle().format(columns=columns, rows=rows)
80
+ if style == "html":
81
+ return HtmlStyle().format(columns=columns, rows=rows)
82
+ if style == "json":
83
+ return JsonStyle().format(columns=columns, rows=rows, tool_name=tool_name)
84
+ if style == "csv":
85
+ return CsvStyle().format(columns=columns, rows=rows)
86
+
87
+ return GridStyle().format(columns=columns, rows=rows)
File without changes
File without changes
@@ -0,0 +1,104 @@
1
+ """Tool-related models for Lintro."""
2
+
3
+ from abc import abstractmethod
4
+ from typing import Protocol, runtime_checkable
5
+
6
+ from lintro.models.core.tool_config import ToolConfig
7
+ from lintro.models.core.tool_result import ToolResult
8
+
9
+
10
+ @runtime_checkable
11
+ class Tool(Protocol):
12
+ """Protocol for all linting and formatting tools.
13
+
14
+ This protocol defines the interface that all tools must implement.
15
+ Tools can be implemented as classes that inherit from this protocol.
16
+
17
+ Attributes:
18
+ name: Tool name
19
+ description: Tool description
20
+ can_fix: Whether the core can fix issues
21
+ config: Tool configuration
22
+ """
23
+
24
+ name: str
25
+ description: str
26
+ can_fix: bool
27
+ config: ToolConfig
28
+
29
+ def set_options(
30
+ self,
31
+ **kwargs,
32
+ ) -> None:
33
+ """Set core options.
34
+
35
+ Args:
36
+ **kwargs: Tool-specific options
37
+ """
38
+ ...
39
+
40
+ @abstractmethod
41
+ def check(
42
+ self,
43
+ paths: list[str],
44
+ ) -> ToolResult:
45
+ """Check files for issues.
46
+
47
+ Args:
48
+ paths: List of file paths to check.
49
+
50
+ Returns:
51
+ Result of the check operation.
52
+
53
+ Raises:
54
+ FileNotFoundError: If the core executable is not found
55
+ subprocess.TimeoutExpired: If the core execution times out
56
+ subprocess.CalledProcessError: If the core execution fails
57
+ """
58
+ ...
59
+
60
+ @abstractmethod
61
+ def fix(
62
+ self,
63
+ paths: list[str],
64
+ ) -> ToolResult:
65
+ """Fix issues in files.
66
+
67
+ Args:
68
+ paths: List of file paths to fix.
69
+
70
+ Returns:
71
+ Result of the fix operation.
72
+
73
+ Raises:
74
+ FileNotFoundError: If the core executable is not found
75
+ subprocess.TimeoutExpired: If the core execution times out
76
+ subprocess.CalledProcessError: If the core execution fails
77
+ NotImplementedError: If the core does not support fixing issues
78
+ """
79
+ ...
80
+
81
+ @staticmethod
82
+ def to_result(
83
+ name: str,
84
+ success: bool,
85
+ output: str,
86
+ issues_count: int,
87
+ ) -> ToolResult:
88
+ """Convert core operation result to a ToolResult.
89
+
90
+ Args:
91
+ name: Tool name
92
+ success: Whether the operation was successful
93
+ output: Output from the core
94
+ issues_count: Number of issues found or fixed
95
+
96
+ Returns:
97
+ Result object
98
+ """
99
+ return ToolResult(
100
+ name=name,
101
+ success=success,
102
+ output=output,
103
+ issues_count=issues_count,
104
+ )
@@ -0,0 +1,23 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from lintro.enums.tool_type import ToolType
4
+
5
+
6
+ @dataclass
7
+ class ToolConfig:
8
+ """Configuration for a core.
9
+
10
+ Attributes:
11
+ priority: int:
12
+ Priority level (higher number = higher priority when resolving conflicts)
13
+ conflicts_with: list[str]: List of tools this core conflicts with
14
+ file_patterns: list[str]: List of file patterns this core should be applied to
15
+ tool_type: ToolType: Type of core
16
+ options: dict[str, object]: Tool-specific configuration options
17
+ """
18
+
19
+ priority: int = 0
20
+ conflicts_with: list[str] = field(default_factory=list)
21
+ file_patterns: list[str] = field(default_factory=list)
22
+ tool_type: ToolType = ToolType.LINTER
23
+ options: dict[str, object] = field(default_factory=dict)
@@ -0,0 +1,39 @@
1
+ """Models for core tool execution results.
2
+
3
+ This module defines the canonical result object returned by all tools. It
4
+ supports both check and fix flows and includes standardized fields to report
5
+ fixed vs remaining counts for fix-capable tools.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass
12
+ class ToolResult:
13
+ """Result of running a tool.
14
+
15
+ For check operations:
16
+ - ``issues_count`` represents the number of issues found.
17
+
18
+ For fix/format operations:
19
+ - ``initial_issues_count`` is the number of issues detected before fixes
20
+ - ``fixed_issues_count`` is the number of issues the tool auto-fixed
21
+ - ``remaining_issues_count`` is the number of issues still remaining
22
+ - ``issues_count`` should mirror ``remaining_issues_count`` for
23
+ backward compatibility in format-mode summaries
24
+
25
+ The ``issues`` field can contain parsed issue objects (tool-specific) to
26
+ support unified table formatting.
27
+ """
28
+
29
+ name: str
30
+ success: bool
31
+ output: str | None = None
32
+ issues_count: int = 0
33
+ formatted_output: str | None = None
34
+ issues: list[object] | None = None
35
+
36
+ # Optional standardized counts for fix-capable tools
37
+ initial_issues_count: int | None = None
38
+ fixed_issues_count: int | None = None
39
+ remaining_issues_count: int | None = None
File without changes
File without changes
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class DarglintIssue:
6
+ file: str
7
+ line: int
8
+ code: str
9
+ message: str
@@ -0,0 +1,62 @@
1
+ """Parser for darglint output."""
2
+
3
+ import re
4
+
5
+ from lintro.parsers.darglint.darglint_issue import DarglintIssue
6
+
7
+
8
+ def parse_darglint_output(output: str) -> list[DarglintIssue]:
9
+ """Parse darglint output into a list of DarglintIssue objects.
10
+
11
+ Args:
12
+ output: The raw output from darglint
13
+
14
+ Returns:
15
+ List of DarglintIssue objects
16
+ """
17
+ issues: list[DarglintIssue] = []
18
+ # Patterns:
19
+ # 1. filename:function:line: CODE message
20
+ # 2. filename:line: CODE message (for module-level errors)
21
+ # Accept both "CODE message" and "CODE: message" variants from darglint
22
+ pattern: re.Pattern[str] = re.compile(r"^(.*?):(.*?):(\d+): (D[A-Z]*\d+):? (.*)$")
23
+ alt_pattern: re.Pattern[str] = re.compile(r"^(.*?):(\d+): (D[A-Z]*\d+):? (.*)$")
24
+ lines: list[str] = output.splitlines()
25
+ i: int = 0
26
+ while i < len(lines):
27
+ line: str = lines[i]
28
+ match: re.Match[str] | None = pattern.match(line)
29
+ if match:
30
+ file: str
31
+ obj: str
32
+ line_num: str
33
+ code: str
34
+ message: str
35
+ file, obj, line_num, code, message = match.groups()
36
+ else:
37
+ match = alt_pattern.match(line)
38
+ if match:
39
+ file, line_num, code, message = match.groups()
40
+ else:
41
+ i += 1
42
+ continue
43
+ # Capture all subsequent indented or colon-prefixed lines as part of the message
44
+ message_lines: list[str] = [message]
45
+ j: int = i + 1
46
+ while j < len(lines) and (
47
+ lines[j].strip().startswith(":") or lines[j].startswith(" ")
48
+ ):
49
+ # Remove leading colon and whitespace
50
+ message_lines.append(lines[j].strip().lstrip(": "))
51
+ j += 1
52
+ full_message: str = " ".join(message_lines)
53
+ issues.append(
54
+ DarglintIssue(
55
+ file=file,
56
+ line=int(line_num),
57
+ code=code,
58
+ message=full_message,
59
+ )
60
+ )
61
+ i = j
62
+ return issues
@@ -0,0 +1 @@
1
+ """Hadolint parser module."""