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,24 @@
1
+ """Hadolint issue model."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class HadolintIssue:
8
+ """Represents an issue found by hadolint.
9
+
10
+ Attributes:
11
+ file: File path where the issue was found
12
+ line: Line number where the issue occurs
13
+ column: Column number where the issue occurs (if available)
14
+ level: Severity level (error, warning, info, style)
15
+ code: Rule code (e.g., DL3006, SC2086)
16
+ message: Description of the issue
17
+ """
18
+
19
+ file: str
20
+ line: int
21
+ column: int | None
22
+ level: str
23
+ code: str
24
+ message: str
@@ -0,0 +1,65 @@
1
+ """Parser for hadolint output."""
2
+
3
+ import re
4
+
5
+ from lintro.parsers.hadolint.hadolint_issue import HadolintIssue
6
+
7
+
8
+ def parse_hadolint_output(output: str) -> list[HadolintIssue]:
9
+ """Parse hadolint output into a list of HadolintIssue objects.
10
+
11
+ Hadolint outputs in the format:
12
+ filename:line code level: message
13
+
14
+ Example outputs:
15
+ Dockerfile:1 DL3006 error: Always tag the version of an image explicitly
16
+ Dockerfile:3 DL3009 warning: Delete the apt-get lists after installing
17
+ something
18
+ Dockerfile:5 DL3015 info: Avoid additional packages by specifying
19
+ `--no-install-recommends`
20
+
21
+ Args:
22
+ output: The raw output from hadolint
23
+
24
+ Returns:
25
+ List of HadolintIssue objects
26
+ """
27
+ issues: list[HadolintIssue] = []
28
+
29
+ # Skip empty output
30
+ if not output.strip():
31
+ return issues
32
+
33
+ # Pattern for hadolint output: filename:line code level: message
34
+ pattern: re.Pattern[str] = re.compile(
35
+ r"^(.+?):(\d+)\s+([A-Z]+\d+)\s+(error|warning|info|style):\s+(.+)$"
36
+ )
37
+
38
+ lines: list[str] = output.splitlines()
39
+
40
+ for line in lines:
41
+ line = line.strip()
42
+ if not line:
43
+ continue
44
+
45
+ match: re.Match[str] | None = pattern.match(line)
46
+ if match:
47
+ file: str
48
+ line_num: str
49
+ code: str
50
+ level: str
51
+ message: str
52
+ file, line_num, code, level, message = match.groups()
53
+
54
+ issues.append(
55
+ HadolintIssue(
56
+ file=file,
57
+ line=int(line_num),
58
+ column=None, # hadolint doesn't provide column in this format
59
+ level=level,
60
+ code=code,
61
+ message=message.strip(),
62
+ )
63
+ )
64
+
65
+ return issues
File without changes
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class PrettierIssue:
6
+ file: str
7
+ line: int | None
8
+ code: str
9
+ message: str
10
+ column: int | None = None
@@ -0,0 +1,60 @@
1
+ """Parser for prettier output.
2
+
3
+ Handles typical Prettier CLI output for --check and --write modes,
4
+ including ANSI-colored lines produced in CI environments.
5
+ """
6
+
7
+ import re
8
+
9
+ from lintro.parsers.prettier.prettier_issue import PrettierIssue
10
+
11
+
12
+ def parse_prettier_output(output: str) -> list[PrettierIssue]:
13
+ """Parse prettier output into a list of PrettierIssue objects.
14
+
15
+ Args:
16
+ output: The raw output from prettier
17
+
18
+ Returns:
19
+ List of PrettierIssue objects
20
+ """
21
+ issues: list[PrettierIssue] = []
22
+
23
+ if not output:
24
+ return issues
25
+
26
+ # Prettier output format when issues are found:
27
+ # "Checking formatting..."
28
+ # "[warn] path/to/file.js"
29
+ # "[warn] Code style issues found in the above file. Run Prettier with --write \
30
+ # to fix."
31
+ # Normalize output by stripping ANSI escape sequences to make matching robust
32
+ # across different terminals and CI runners.
33
+ # Example: "[\x1b[33mwarn\x1b[39m] file.js" -> "[warn] file.js"
34
+ ansi_escape = re.compile(r"\x1b\[[0-9;]*m")
35
+ normalized_output = ansi_escape.sub("", output)
36
+
37
+ lines = normalized_output.splitlines()
38
+
39
+ for i, line in enumerate(lines):
40
+ line = line.strip()
41
+ if not line:
42
+ continue
43
+
44
+ # Look for [warn] lines that contain file paths
45
+ if line.startswith("[warn]") and not line.endswith("fix."):
46
+ # Extract the file path from the [warn] line
47
+ file_path = line[6:].strip() # Remove "[warn] " prefix
48
+ if file_path and not file_path.startswith("Code style issues"):
49
+ # Create a generic issue for the file
50
+ issues.append(
51
+ PrettierIssue(
52
+ file=file_path,
53
+ line=1, # Prettier doesn't provide specific line numbers
54
+ code="FORMAT",
55
+ message="Code style issues found",
56
+ column=1, # Prettier doesn't provide specific column numbers
57
+ ),
58
+ )
59
+
60
+ return issues
@@ -0,0 +1 @@
1
+ """Ruff parser module."""
@@ -0,0 +1,43 @@
1
+ """Models for ruff issues."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class RuffIssue:
8
+ """Represents a ruff linting issue.
9
+
10
+ Attributes:
11
+ file: File path where the issue was found.
12
+ line: Line number where the issue was found.
13
+ column: Column number where the issue was found.
14
+ code: Ruff error code (e.g., E401, F401).
15
+ message: Human-readable error message.
16
+ url: Optional URL to documentation for this error.
17
+ end_line: End line number for multi-line issues.
18
+ end_column: End column number for multi-line issues.
19
+ fixable: Whether this issue can be auto-fixed.
20
+ fix_applicability: Whether the fix is safe or unsafe (safe, unsafe, or None).
21
+ """
22
+
23
+ file: str
24
+ line: int
25
+ column: int
26
+ code: str
27
+ message: str
28
+ url: str | None = None
29
+ end_line: int | None = None
30
+ end_column: int | None = None
31
+ fixable: bool = False
32
+ fix_applicability: str | None = None
33
+
34
+
35
+ @dataclass
36
+ class RuffFormatIssue:
37
+ """Represents a ruff formatting issue.
38
+
39
+ Attributes:
40
+ file: File path that would be reformatted.
41
+ """
42
+
43
+ file: str
@@ -0,0 +1,89 @@
1
+ """Parser for ruff output (lint and format).
2
+
3
+ This module provides functions to parse both:
4
+ - ruff check --output-format json (linting issues)
5
+ - ruff format --check (plain text: files needing formatting)
6
+ """
7
+
8
+ import json
9
+
10
+ from lintro.parsers.ruff.ruff_issue import RuffIssue
11
+
12
+
13
+ def parse_ruff_output(output: str) -> list[RuffIssue]:
14
+ """Parse ruff JSON output into a list of RuffIssue objects.
15
+
16
+ Args:
17
+ output: The raw JSON output from ruff
18
+
19
+ Returns:
20
+ List of RuffIssue objects
21
+ """
22
+ issues: list[RuffIssue] = []
23
+
24
+ if not output or output.strip() == "[]":
25
+ return issues
26
+
27
+ try:
28
+ # Ruff outputs JSON array of issue objects, but may have warnings
29
+ # after. Find the end of the JSON array by looking for the
30
+ # closing
31
+ # bracket
32
+ json_end = output.rfind("]")
33
+ if json_end == -1:
34
+ # No closing bracket found, try to parse the whole output
35
+ ruff_data = json.loads(output)
36
+ else:
37
+ # Extract just the JSON part (up to and including the closing bracket)
38
+ json_part = output[: json_end + 1]
39
+ ruff_data = json.loads(json_part)
40
+
41
+ for item in ruff_data:
42
+ # Extract fix applicability if available
43
+ fix_applicability = None
44
+ if item.get("fix"):
45
+ fix_applicability = item["fix"].get("applicability")
46
+
47
+ issues.append(
48
+ RuffIssue(
49
+ file=item["filename"],
50
+ line=item["location"]["row"],
51
+ column=item["location"]["column"],
52
+ code=item["code"],
53
+ message=item["message"],
54
+ url=item.get("url"),
55
+ end_line=item["end_location"]["row"],
56
+ end_column=item["end_location"]["column"],
57
+ fixable=bool(item.get("fix")),
58
+ fix_applicability=fix_applicability,
59
+ ),
60
+ )
61
+ except (json.JSONDecodeError, KeyError, TypeError):
62
+ # If JSON parsing fails, return empty list
63
+ # Could also log the error for debugging
64
+ pass
65
+
66
+ return issues
67
+
68
+
69
+ def parse_ruff_format_check_output(output: str) -> list[str]:
70
+ """Parse the output of `ruff format --check` to get files needing formatting.
71
+
72
+ Args:
73
+ output: The raw output from `ruff format --check`
74
+
75
+ Returns:
76
+ List of file paths that would be reformatted
77
+ """
78
+ if not output:
79
+ return []
80
+ files = []
81
+ for line in output.splitlines():
82
+ line = line.strip()
83
+ # Ruff format --check output: 'Would reformat: path/to/file.py' or
84
+ # 'Would reformat path/to/file.py'
85
+ if line.startswith("Would reformat: "):
86
+ files.append(line[len("Would reformat: ") :])
87
+ elif line.startswith("Would reformat "):
88
+ files.append(line[len("Would reformat ") :])
89
+ return files
File without changes
@@ -0,0 +1,24 @@
1
+ """Yamllint issue model."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class YamllintIssue:
8
+ """Represents an issue found by yamllint.
9
+
10
+ Attributes:
11
+ file: File path where the issue was found
12
+ line: Line number where the issue occurs
13
+ column: Column number where the issue occurs (if available)
14
+ level: Severity level (error, warning)
15
+ rule: Rule name that was violated (e.g., line-length, trailing-spaces)
16
+ message: Description of the issue
17
+ """
18
+
19
+ file: str
20
+ line: int
21
+ column: int | None
22
+ level: str
23
+ rule: str | None
24
+ message: str
@@ -0,0 +1,68 @@
1
+ """Parser for yamllint output."""
2
+
3
+ import re
4
+
5
+ from lintro.parsers.yamllint.yamllint_issue import YamllintIssue
6
+
7
+
8
+ def parse_yamllint_output(output: str) -> list[YamllintIssue]:
9
+ """Parse yamllint output into a list of YamllintIssue objects.
10
+
11
+ Yamllint outputs in parsable format as:
12
+ filename:line:column: [level] message (rule)
13
+
14
+ Example outputs:
15
+ test_samples/yaml_violations.yml:3:1: [warning] missing document start
16
+ "---" (document-start)
17
+ test_samples/yaml_violations.yml:6:32: [error] trailing spaces
18
+ (trailing-spaces)
19
+ test_samples/yaml_violations.yml:11:81: [error] line too long (149 > 80
20
+ characters) (line-length)
21
+
22
+ Args:
23
+ output: The raw output from yamllint
24
+
25
+ Returns:
26
+ List of YamllintIssue objects
27
+ """
28
+ issues: list[YamllintIssue] = []
29
+
30
+ # Skip empty output
31
+ if not output.strip():
32
+ return issues
33
+
34
+ lines: list[str] = output.splitlines()
35
+
36
+ for line in lines:
37
+ line = line.strip()
38
+ if not line:
39
+ continue
40
+
41
+ # Pattern for yamllint parsable format: "filename:line:column: [level]
42
+ # message (rule)"
43
+ pattern: re.Pattern[str] = re.compile(
44
+ r"^([^:]+):(\d+):(\d+):\s*\[(error|warning)\]\s+(.+?)(?:\s+\(([^)]+)\))?$"
45
+ )
46
+
47
+ match: re.Match[str] | None = pattern.match(line)
48
+ if match:
49
+ filename: str
50
+ line_num: str
51
+ column: str
52
+ level: str
53
+ message: str
54
+ rule: str | None
55
+ filename, line_num, column, level, message, rule = match.groups()
56
+
57
+ issues.append(
58
+ YamllintIssue(
59
+ file=filename,
60
+ line=int(line_num),
61
+ column=int(column) if column else None,
62
+ level=level,
63
+ rule=rule,
64
+ message=message.strip(),
65
+ )
66
+ )
67
+
68
+ return issues
@@ -0,0 +1,40 @@
1
+ """Tool implementations for Lintro."""
2
+
3
+ from lintro.enums.tool_type import ToolType
4
+ from lintro.models.core.tool import Tool
5
+ from lintro.models.core.tool_config import ToolConfig
6
+ from lintro.tools.core.tool_manager import ToolManager
7
+
8
+ # Import core implementations after Tool class definition to avoid circular imports
9
+ from lintro.tools.implementations.tool_darglint import DarglintTool
10
+ from lintro.tools.implementations.tool_hadolint import HadolintTool
11
+ from lintro.tools.implementations.tool_prettier import PrettierTool
12
+ from lintro.tools.implementations.tool_ruff import RuffTool
13
+ from lintro.tools.implementations.tool_yamllint import YamllintTool
14
+ from lintro.tools.tool_enum import ToolEnum
15
+
16
+ # Create global core manager instance
17
+ tool_manager = ToolManager()
18
+
19
+ # Register all available tools using ToolEnum
20
+ AVAILABLE_TOOLS = {tool_enum: tool_enum.value for tool_enum in ToolEnum}
21
+
22
+
23
+ for tool_enum, tool_class in AVAILABLE_TOOLS.items():
24
+ tool_manager.register_tool(tool_class)
25
+
26
+ # Consolidated exports
27
+ __all__ = [
28
+ "Tool",
29
+ "ToolConfig",
30
+ "ToolType",
31
+ "ToolManager",
32
+ "ToolEnum",
33
+ "tool_manager",
34
+ "AVAILABLE_TOOLS",
35
+ "DarglintTool",
36
+ "HadolintTool",
37
+ "PrettierTool",
38
+ "RuffTool",
39
+ "YamllintTool",
40
+ ]
File without changes