lintro 0.7.0__py3-none-any.whl → 0.9.0__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 (51) hide show
  1. lintro/__init__.py +1 -1
  2. lintro/cli.py +6 -0
  3. lintro/cli_utils/commands/list_tools.py +1 -1
  4. lintro/enums/__init__.py +1 -0
  5. lintro/enums/darglint_strictness.py +10 -0
  6. lintro/enums/hadolint_enums.py +22 -0
  7. lintro/enums/yamllint_format.py +11 -0
  8. lintro/exceptions/__init__.py +1 -0
  9. lintro/formatters/__init__.py +1 -0
  10. lintro/formatters/core/__init__.py +1 -0
  11. lintro/formatters/core/output_style.py +11 -0
  12. lintro/formatters/core/table_descriptor.py +8 -0
  13. lintro/formatters/styles/csv.py +2 -0
  14. lintro/formatters/styles/grid.py +2 -0
  15. lintro/formatters/styles/html.py +2 -0
  16. lintro/formatters/styles/json.py +2 -0
  17. lintro/formatters/styles/markdown.py +2 -0
  18. lintro/formatters/styles/plain.py +2 -0
  19. lintro/formatters/tools/black_formatter.py +27 -5
  20. lintro/formatters/tools/darglint_formatter.py +16 -1
  21. lintro/formatters/tools/hadolint_formatter.py +13 -0
  22. lintro/formatters/tools/prettier_formatter.py +15 -0
  23. lintro/formatters/tools/ruff_formatter.py +16 -1
  24. lintro/formatters/tools/yamllint_formatter.py +14 -1
  25. lintro/models/__init__.py +1 -0
  26. lintro/models/core/__init__.py +1 -0
  27. lintro/models/core/tool_config.py +11 -7
  28. lintro/parsers/actionlint/actionlint_parser.py +1 -1
  29. lintro/parsers/darglint/__init__.py +1 -0
  30. lintro/parsers/darglint/darglint_issue.py +11 -0
  31. lintro/parsers/prettier/__init__.py +1 -0
  32. lintro/parsers/prettier/prettier_issue.py +12 -0
  33. lintro/parsers/ruff/ruff_parser.py +6 -2
  34. lintro/parsers/yamllint/__init__.py +1 -0
  35. lintro/tools/core/__init__.py +1 -0
  36. lintro/tools/core/tool_base.py +33 -7
  37. lintro/tools/implementations/__init__.py +1 -0
  38. lintro/tools/implementations/tool_bandit.py +11 -22
  39. lintro/tools/implementations/tool_darglint.py +3 -1
  40. lintro/tools/implementations/tool_ruff.py +1 -1
  41. lintro/tools/tool_enum.py +2 -0
  42. lintro/utils/__init__.py +1 -0
  43. lintro/utils/ascii_normalize_cli.py +5 -0
  44. lintro/utils/tool_utils.py +6 -10
  45. {lintro-0.7.0.dist-info → lintro-0.9.0.dist-info}/METADATA +1 -1
  46. lintro-0.9.0.dist-info/RECORD +96 -0
  47. lintro-0.7.0.dist-info/RECORD +0 -96
  48. {lintro-0.7.0.dist-info → lintro-0.9.0.dist-info}/WHEEL +0 -0
  49. {lintro-0.7.0.dist-info → lintro-0.9.0.dist-info}/entry_points.txt +0 -0
  50. {lintro-0.7.0.dist-info → lintro-0.9.0.dist-info}/licenses/LICENSE +0 -0
  51. {lintro-0.7.0.dist-info → lintro-0.9.0.dist-info}/top_level.txt +0 -0
lintro/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Lintro - A unified CLI core for code formatting, linting, and quality assurance."""
2
2
 
3
- __version__ = "0.7.0"
3
+ __version__ = "0.9.0"
lintro/cli.py CHANGED
@@ -9,6 +9,12 @@ from lintro.cli_utils.commands.list_tools import list_tools_command
9
9
 
10
10
 
11
11
  class LintroGroup(click.Group):
12
+ """Custom Click group with enhanced help rendering.
13
+
14
+ This group prints command aliases alongside their canonical names to make
15
+ the CLI help output more discoverable.
16
+ """
17
+
12
18
  def format_commands(
13
19
  self,
14
20
  ctx: click.Context,
@@ -109,6 +109,6 @@ def list_tools(
109
109
  f.write(output_text + "\n")
110
110
  success_msg = f"Output written to: {output}"
111
111
  click.echo(success_msg)
112
- except IOError as e:
112
+ except OSError as e:
113
113
  error_msg = f"Error writing to file {output}: {e}"
114
114
  click.echo(error_msg, err=True)
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:
@@ -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
@@ -2,14 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from lintro.formatters.core.table_descriptor import TableDescriptor
6
5
  from lintro.formatters.styles.csv import CsvStyle
7
6
  from lintro.formatters.styles.grid import GridStyle
8
7
  from lintro.formatters.styles.html import HtmlStyle
9
8
  from lintro.formatters.styles.json import JsonStyle
10
9
  from lintro.formatters.styles.markdown import MarkdownStyle
11
10
  from lintro.formatters.styles.plain import PlainStyle
12
- from lintro.parsers.black.black_issue import BlackIssue
13
11
  from lintro.utils.path_utils import normalize_file_path_for_display
14
12
 
15
13
  FORMAT_MAP = {
@@ -22,11 +20,26 @@ FORMAT_MAP = {
22
20
  }
23
21
 
24
22
 
25
- class BlackTableDescriptor(TableDescriptor):
23
+ class BlackTableDescriptor:
24
+ """Column layout for Black issues in tabular output."""
25
+
26
26
  def get_columns(self) -> list[str]:
27
+ """Return ordered column headers for Black output rows.
28
+
29
+ Returns:
30
+ list[str]: Column names for the formatted table.
31
+ """
27
32
  return ["File", "Message"]
28
33
 
29
- def get_rows(self, issues: list[BlackIssue]) -> list[list[str]]:
34
+ def get_rows(self, issues: list) -> list[list[str]]:
35
+ """Return formatted rows for Black issues.
36
+
37
+ Args:
38
+ issues: Parsed Black issues to render.
39
+
40
+ Returns:
41
+ list[list[str]]: Table rows with normalized file paths and messages.
42
+ """
30
43
  rows: list[list[str]] = []
31
44
  for issue in issues:
32
45
  rows.append(
@@ -38,7 +51,16 @@ class BlackTableDescriptor(TableDescriptor):
38
51
  return rows
39
52
 
40
53
 
41
- def format_black_issues(issues: list[BlackIssue], format: str = "grid") -> str:
54
+ def format_black_issues(issues, format: str) -> str:
55
+ """Format Black issues according to the chosen style.
56
+
57
+ Args:
58
+ issues: Parsed Black issues.
59
+ format: Output style identifier.
60
+
61
+ Returns:
62
+ str: Rendered table string.
63
+ """
42
64
  descriptor = BlackTableDescriptor()
43
65
  formatter = FORMAT_MAP.get(format, GridStyle())
44
66
  columns = descriptor.get_columns()
@@ -21,13 +21,28 @@ FORMAT_MAP = {
21
21
 
22
22
 
23
23
  class DarglintTableDescriptor(TableDescriptor):
24
+ """Describe columns and rows for Darglint issues."""
25
+
24
26
  def get_columns(self) -> list[str]:
27
+ """Return column headers for the Darglint issues table.
28
+
29
+ Returns:
30
+ list[str]: Column names for the formatted table.
31
+ """
25
32
  return ["File", "Line", "Code", "Message"]
26
33
 
27
34
  def get_rows(
28
35
  self,
29
36
  issues: list[DarglintIssue],
30
37
  ) -> list[list[str]]:
38
+ """Return rows for the Darglint table.
39
+
40
+ Args:
41
+ issues: Parsed Darglint issues to format.
42
+
43
+ Returns:
44
+ list[list[str]]: Table rows with normalized path, line, code, message.
45
+ """
31
46
  rows = []
32
47
  for issue in issues:
33
48
  rows.append(
@@ -52,7 +67,7 @@ def format_darglint_issues(
52
67
  format: Output format (plain, grid, markdown, html, json, csv).
53
68
 
54
69
  Returns:
55
- Formatted string representation of the issues.
70
+ str: Formatted string representation of the issues.
56
71
  """
57
72
  descriptor = DarglintTableDescriptor()
58
73
  columns = descriptor.get_columns()
@@ -19,6 +19,11 @@ class HadolintTableDescriptor(TableDescriptor):
19
19
  """Describe hadolint issue columns and row extraction."""
20
20
 
21
21
  def get_columns(self) -> list[str]:
22
+ """Return ordered column headers for the Hadolint table.
23
+
24
+ Returns:
25
+ list[str]: Column names for the formatted table.
26
+ """
22
27
  return [
23
28
  "File",
24
29
  "Line",
@@ -32,6 +37,14 @@ class HadolintTableDescriptor(TableDescriptor):
32
37
  self,
33
38
  issues: list[HadolintIssue],
34
39
  ) -> list[list[Any]]:
40
+ """Return rows for the Hadolint issues table.
41
+
42
+ Args:
43
+ issues: Parsed Hadolint issues to render.
44
+
45
+ Returns:
46
+ list[list[Any]]: Table rows with normalized file path and fields.
47
+ """
35
48
  rows: list[list[Any]] = []
36
49
  for issue in issues:
37
50
  rows.append(
@@ -21,13 +21,28 @@ FORMAT_MAP = {
21
21
 
22
22
 
23
23
  class PrettierTableDescriptor(TableDescriptor):
24
+ """Describe columns and rows for Prettier issues."""
25
+
24
26
  def get_columns(self) -> list[str]:
27
+ """Return ordered column headers for the Prettier table.
28
+
29
+ Returns:
30
+ list[str]: Column names for the formatted table.
31
+ """
25
32
  return ["File", "Line", "Column", "Code", "Message"]
26
33
 
27
34
  def get_rows(
28
35
  self,
29
36
  issues: list[PrettierIssue],
30
37
  ) -> list[list[str]]:
38
+ """Return rows for the Prettier issues table.
39
+
40
+ Args:
41
+ issues: Parsed Prettier issues to render.
42
+
43
+ Returns:
44
+ list[list[str]]: Table rows with normalized file path and fields.
45
+ """
31
46
  rows = []
32
47
  for issue in issues:
33
48
  rows.append(
@@ -21,13 +21,28 @@ FORMAT_MAP = {
21
21
 
22
22
 
23
23
  class RuffTableDescriptor(TableDescriptor):
24
+ """Describe columns and rows for Ruff issues."""
25
+
24
26
  def get_columns(self) -> list[str]:
27
+ """Return ordered column headers for the Ruff table.
28
+
29
+ Returns:
30
+ list[str]: Column names for the formatted table.
31
+ """
25
32
  return ["File", "Line", "Column", "Code", "Message"]
26
33
 
27
34
  def get_rows(
28
35
  self,
29
36
  issues: list[RuffIssue | RuffFormatIssue],
30
37
  ) -> list[list[str]]:
38
+ """Return rows for the Ruff issues table.
39
+
40
+ Args:
41
+ issues: Parsed Ruff issues to render.
42
+
43
+ Returns:
44
+ list[list[str]]: Table rows with normalized file path and fields.
45
+ """
31
46
  rows = []
32
47
  for issue in issues:
33
48
  if isinstance(issue, RuffIssue):
@@ -69,7 +84,7 @@ def format_ruff_issues(
69
84
  format: Output format (plain, grid, markdown, html, json, csv).
70
85
 
71
86
  Returns:
72
- Formatted string (one or two tables depending on format).
87
+ str: Formatted string (one or two tables depending on format).
73
88
  """
74
89
  descriptor = RuffTableDescriptor()
75
90
  formatter = FORMAT_MAP.get(format, GridStyle())
@@ -19,6 +19,11 @@ class YamllintTableDescriptor(TableDescriptor):
19
19
  """Describe yamllint issue columns and row extraction."""
20
20
 
21
21
  def get_columns(self) -> list[str]:
22
+ """Return ordered column headers for the Yamllint table.
23
+
24
+ Returns:
25
+ list[str]: Column names for the formatted table.
26
+ """
22
27
  return [
23
28
  "File",
24
29
  "Line",
@@ -32,6 +37,14 @@ class YamllintTableDescriptor(TableDescriptor):
32
37
  self,
33
38
  issues: list[YamllintIssue],
34
39
  ) -> list[list[Any]]:
40
+ """Return rows for the Yamllint issues table.
41
+
42
+ Args:
43
+ issues: Parsed Yamllint issues to render.
44
+
45
+ Returns:
46
+ list[list[Any]]: Table rows with normalized file path and fields.
47
+ """
35
48
  rows: list[list[Any]] = []
36
49
  for issue in issues:
37
50
  rows.append(
@@ -61,7 +74,7 @@ def format_yamllint_issues(
61
74
  tool_name: Tool name for JSON metadata.
62
75
 
63
76
  Returns:
64
- Rendered string for the issues table.
77
+ str: Rendered string for the issues table.
65
78
  """
66
79
  descriptor = YamllintTableDescriptor()
67
80
  columns = descriptor.get_columns()
lintro/models/__init__.py CHANGED
@@ -0,0 +1 @@
1
+ """Model package containing issue types and configuration structures."""
@@ -0,0 +1 @@
1
+ """Core data models used across tool integrations."""
@@ -1,3 +1,5 @@
1
+ """Core model for tool configuration used by Lintro tools."""
2
+
1
3
  from dataclasses import dataclass, field
2
4
 
3
5
  from lintro.enums.tool_type import ToolType
@@ -5,15 +7,17 @@ from lintro.enums.tool_type import ToolType
5
7
 
6
8
  @dataclass
7
9
  class ToolConfig:
8
- """Configuration for a core.
10
+ """Configuration container for a tool.
11
+
12
+ This dataclass defines the static configuration associated with a tool,
13
+ including its priority, file targeting, type flags, and default options.
9
14
 
10
15
  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
16
+ priority: int: Priority used when ordering tools.
17
+ conflicts_with: list[str]: Names of tools that conflict with this one.
18
+ file_patterns: list[str]: Glob patterns to select applicable files.
19
+ tool_type: ToolType: Bitmask describing tool capabilities.
20
+ options: dict[str, object]: Default tool options applied at runtime.
17
21
  """
18
22
 
19
23
  priority: int = 0
@@ -8,7 +8,7 @@ uniform tables and reports across styles.
8
8
  from __future__ import annotations
9
9
 
10
10
  import re
11
- from typing import Iterable
11
+ from collections.abc import Iterable
12
12
 
13
13
  from lintro.parsers.actionlint.actionlint_issue import ActionlintIssue
14
14
 
@@ -0,0 +1 @@
1
+ """Parsing utilities and types for Darglint output."""
@@ -1,8 +1,19 @@
1
+ """Typed structure representing a single Darglint issue."""
2
+
1
3
  from dataclasses import dataclass
2
4
 
3
5
 
4
6
  @dataclass
5
7
  class DarglintIssue:
8
+ """Simple container for Darglint findings.
9
+
10
+ Attributes:
11
+ file: File path where the issue occurred.
12
+ line: Line number of the issue.
13
+ code: Darglint error code.
14
+ message: Human-readable description of the issue.
15
+ """
16
+
6
17
  file: str
7
18
  line: int
8
19
  code: str
@@ -0,0 +1 @@
1
+ """Parsing utilities and types for Prettier output."""
@@ -1,8 +1,20 @@
1
+ """Typed structure representing a single Prettier issue."""
2
+
1
3
  from dataclasses import dataclass
2
4
 
3
5
 
4
6
  @dataclass
5
7
  class PrettierIssue:
8
+ """Simple container for Prettier findings.
9
+
10
+ Attributes:
11
+ file: File path where the issue occurred.
12
+ line: Line number, if provided by Prettier.
13
+ code: Tool-specific code identifying the rule.
14
+ message: Human-readable description of the issue.
15
+ column: Column number, if provided by Prettier.
16
+ """
17
+
6
18
  file: str
7
19
  line: int | None
8
20
  code: str
@@ -129,8 +129,12 @@ def parse_ruff_format_check_output(output: str) -> list[str]:
129
129
  if not output:
130
130
  return []
131
131
  files = []
132
- for line in output.splitlines():
133
- line = line.strip()
132
+ import re
133
+
134
+ ansi_re = re.compile(r"\x1b\[[0-9;]*m")
135
+ for raw in output.splitlines():
136
+ # Strip ANSI color codes for stable parsing across environments
137
+ line = ansi_re.sub("", raw).strip()
134
138
  # Ruff format --check output: 'Would reformat: path/to/file.py' or
135
139
  # 'Would reformat path/to/file.py'
136
140
  if line.startswith("Would reformat: "):
@@ -0,0 +1 @@
1
+ """Parsing utilities and types for Yamllint output."""
@@ -0,0 +1 @@
1
+ """Base classes and utilities for tool implementations."""
@@ -102,7 +102,7 @@ class BaseTool(ABC):
102
102
  try:
103
103
  lintro_ignore_path = os.path.abspath(".lintro-ignore")
104
104
  if os.path.exists(lintro_ignore_path):
105
- with open(lintro_ignore_path, "r", encoding="utf-8") as f:
105
+ with open(lintro_ignore_path, encoding="utf-8") as f:
106
106
  for line in f:
107
107
  line_stripped = line.strip()
108
108
  if not line_stripped or line_stripped.startswith("#"):
@@ -147,6 +147,9 @@ class BaseTool(ABC):
147
147
  TimeoutExpired: If command times out.
148
148
  FileNotFoundError: If command executable is not found.
149
149
  """
150
+ # Validate command arguments for safety prior to execution
151
+ self._validate_subprocess_command(cmd=cmd)
152
+
150
153
  try:
151
154
  result = subprocess.run( # nosec B603 - args list, shell=False
152
155
  cmd,
@@ -157,7 +160,6 @@ class BaseTool(ABC):
157
160
  "timeout",
158
161
  self._default_timeout,
159
162
  ),
160
- check=False,
161
163
  cwd=cwd,
162
164
  )
163
165
  return result.returncode == 0, result.stdout + result.stderr
@@ -184,6 +186,31 @@ class BaseTool(ABC):
184
186
  f"Please ensure it is installed and in your PATH.",
185
187
  ) from e
186
188
 
189
+ def _validate_subprocess_command(self, cmd: list[str]) -> None:
190
+ """Validate a subprocess command argument list for safety.
191
+
192
+ Ensures that the command is a non-empty list of strings and that no
193
+ argument contains shell metacharacters that could enable command
194
+ injection when passed to subprocess (even with ``shell=False``).
195
+
196
+ Args:
197
+ cmd: list[str]: Command and arguments to validate.
198
+
199
+ Raises:
200
+ ValueError: If the command list is empty, contains non-strings,
201
+ or contains unsafe characters.
202
+ """
203
+ if not cmd or not isinstance(cmd, list):
204
+ raise ValueError("Command must be a non-empty list of strings")
205
+
206
+ unsafe_chars: set[str] = {";", "&", "|", ">", "<", "`", "$", "\\", "\n", "\r"}
207
+
208
+ for arg in cmd:
209
+ if not isinstance(arg, str):
210
+ raise ValueError("All command arguments must be strings")
211
+ if any(ch in arg for ch in unsafe_chars):
212
+ raise ValueError("Unsafe character detected in command argument")
213
+
187
214
  def set_options(self, **kwargs) -> None:
188
215
  """Set core options.
189
216
 
@@ -233,11 +260,10 @@ class BaseTool(ABC):
233
260
  self,
234
261
  paths: list[str],
235
262
  ) -> str | None:
236
- """Return the common parent directory for the given paths, or None if not
237
- applicable.
263
+ """Return the common parent directory for the given paths.
238
264
 
239
265
  Args:
240
- paths: list[str]: List of file paths to find common parent directory for.
266
+ paths: list[str]: Paths to compute a common parent directory for.
241
267
 
242
268
  Returns:
243
269
  str | None: Common parent directory path, or None if not applicable.
@@ -300,10 +326,10 @@ class BaseTool(ABC):
300
326
  if tool_name in python_tools_prefer_uv:
301
327
  if shutil.which(tool_name):
302
328
  return [tool_name]
303
- if shutil.which("uv"):
304
- return ["uv", "run", tool_name]
305
329
  if shutil.which("uvx"):
306
330
  return ["uvx", tool_name]
331
+ if shutil.which("uv"):
332
+ return ["uv", "run", tool_name]
307
333
  return [tool_name]
308
334
 
309
335
  # Default: prefer direct system executable (node/binary tools like
@@ -0,0 +1 @@
1
+ """Concrete tool integrations (Ruff, Black, Prettier, etc.)."""
@@ -2,7 +2,6 @@
2
2
 
3
3
  import json
4
4
  import os
5
- import shutil
6
5
  import subprocess # nosec B404 - deliberate, shell disabled
7
6
  import tomllib
8
7
  from dataclasses import dataclass, field
@@ -243,15 +242,10 @@ class BanditTool(BaseTool):
243
242
  Returns:
244
243
  list[str]: List of command arguments.
245
244
  """
246
- # Prefer the Bandit CLI directly; avoid module execution which can fail
247
- # when Bandit isn't installed in the current venv. Fall back to uvx
248
- # (which can run ephemeral tools), then finally to plain name.
249
- if shutil.which("bandit"):
250
- exec_cmd: list[str] = ["bandit"]
251
- elif shutil.which("uvx"):
252
- exec_cmd = ["uvx", "bandit"]
253
- else:
254
- exec_cmd = ["bandit"]
245
+ # Resolve executable via BaseTool preferences to ensure reliable
246
+ # execution inside the active environment (prefers 'uv run bandit' when
247
+ # available), falling back to a direct executable.
248
+ exec_cmd: list[str] = self._get_executable_command("bandit")
255
249
 
256
250
  cmd: list[str] = exec_cmd + ["-r"]
257
251
 
@@ -367,21 +361,16 @@ class BanditTool(BaseTool):
367
361
  cmd: list[str] = self._build_check_command(files=rel_files)
368
362
 
369
363
  output: str
370
- # Run Bandit and capture both stdout and stderr; Bandit may emit logs
371
- # and JSON to different streams depending on version/settings.
364
+ # Run Bandit via the shared safe runner in BaseTool. This enforces
365
+ # argument validation and consistent subprocess handling across tools.
372
366
  try:
373
- result = subprocess.run(
374
- cmd,
375
- capture_output=True,
376
- text=True,
367
+ success, combined = self._run_subprocess(
368
+ cmd=cmd,
377
369
  timeout=timeout,
378
370
  cwd=cwd,
379
- ) # nosec B603 - cmd args list; no shell
380
- # Combine streams for robust JSON extraction
381
- stdout_text: str = result.stdout or ""
382
- stderr_text: str = result.stderr or ""
383
- output = (stdout_text + "\n" + stderr_text).strip()
384
- rc: int = result.returncode
371
+ )
372
+ output = (combined or "").strip()
373
+ rc: int = 0 if success else 1
385
374
  except subprocess.TimeoutExpired:
386
375
  raise
387
376
  except Exception as e:
@@ -136,7 +136,9 @@ class DarglintTool(BaseTool):
136
136
  Returns:
137
137
  list[str]: List of command arguments.
138
138
  """
139
- cmd: list[str] = ["darglint"]
139
+ # Prefer running via the active environment (uv run) if available,
140
+ # falling back to a direct executable when necessary.
141
+ cmd: list[str] = self._get_executable_command("darglint")
140
142
 
141
143
  # Add configuration options
142
144
  if self.options.get("ignore"):
@@ -59,7 +59,7 @@ def _load_lintro_ignore() -> list[str]:
59
59
 
60
60
  if lintro_ignore_path.exists():
61
61
  try:
62
- with open(lintro_ignore_path, "r", encoding="utf-8") as f:
62
+ with open(lintro_ignore_path, encoding="utf-8") as f:
63
63
  for line in f:
64
64
  line = line.strip()
65
65
  # Skip empty lines and comments
lintro/tools/tool_enum.py CHANGED
@@ -13,6 +13,8 @@ from lintro.tools.implementations.tool_yamllint import YamllintTool
13
13
 
14
14
 
15
15
  class ToolEnum(Enum):
16
+ """Enumeration mapping tool names to their implementation classes."""
17
+
16
18
  BLACK = BlackTool
17
19
  DARGLINT = DarglintTool
18
20
  HADOLINT = HadolintTool
lintro/utils/__init__.py CHANGED
@@ -0,0 +1 @@
1
+ """Utility package providing helpers for formatting and execution."""
@@ -32,6 +32,11 @@ def _write_sections(
32
32
 
33
33
 
34
34
  def main() -> int:
35
+ """Normalize ASCII art files based on width/height and alignment.
36
+
37
+ Returns:
38
+ int: Zero on success, non-zero when the base directory is missing.
39
+ """
35
40
  parser = argparse.ArgumentParser(description="Normalize ASCII art files.")
36
41
  parser.add_argument("files", nargs="*", help="Specific ASCII art files to process")
37
42
  parser.add_argument("--width", type=int, default=80)
@@ -394,21 +394,17 @@ def walk_files_with_excludes(
394
394
  exclude_patterns: list[str],
395
395
  include_venv: bool = False,
396
396
  ) -> list[str]:
397
- """Walk through directories and find files matching patterns, excluding
398
- specified patterns.
397
+ """Return files under ``paths`` matching patterns and not excluded.
399
398
 
400
399
  Args:
401
- paths: list[str]: List of file or directory paths to search.
402
- file_patterns: list[str]: List of file patterns to include (e.g.,
403
- ["*.py", "*.js"]).
404
- exclude_patterns: list[str]: List of patterns to exclude (e.g.,
405
- ["__pycache__", "*.pyc"]).
406
- include_venv: bool: Whether to include virtual environment directories.
400
+ paths: list[str]: Files or directories to search.
401
+ file_patterns: list[str]: Glob patterns to include.
402
+ exclude_patterns: list[str]: Glob patterns to exclude.
403
+ include_venv: bool: Include virtual environment directories when True.
407
404
 
408
405
  Returns:
409
- list[str]: List of file paths that match the patterns and are not excluded.
406
+ list[str]: Sorted file paths matching include filters and not excluded.
410
407
  """
411
-
412
408
  all_files: list[str] = []
413
409
 
414
410
  for path in paths:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lintro
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: A unified CLI tool for code formatting, linting, and quality assurance
5
5
  Author-email: TurboCoder13 <turbocoder13@gmail.com>
6
6
  License: MIT License
@@ -0,0 +1,96 @@
1
+ lintro/__init__.py,sha256=GMSRftN4nyCplC-dnP6I_BowcxRi_DKZ5aCyPEAyKuQ,110
2
+ lintro/__main__.py,sha256=McxM6wEcEeCLBHZs0F8xzT1PxVqJYkjtaPq1_-Nug-0,106
3
+ lintro/cli.py,sha256=0apxBcqVxBZdCeXGMyaSdJYSntMIiCFwPwan1jUmPzM,2520
4
+ lintro/ascii-art/fail.txt,sha256=gshKngSrz5FvJdP6g7lPygpLsLsZZh4etBU_wR7PXOw,56396
5
+ lintro/ascii-art/success.txt,sha256=xhteIXcBvl_1QcFKMIS9CkjjCY8czdf9znP9AiB8oPU,90151
6
+ lintro/cli_utils/__init__.py,sha256=F5P_fY_s-gwNZrnV1bzUOgHGb73QeZG1i-K_kfyJ8Yg,250
7
+ lintro/cli_utils/commands/__init__.py,sha256=GGT-uG0gQLlHAqkrwfwqfCkD6DUMP4Tq1rgDgaYe5Ng,239
8
+ lintro/cli_utils/commands/check.py,sha256=YnpuiX8-kTADnwxS_mZUPf_SkmqdyqTZFJLTDf7QSQ8,6229
9
+ lintro/cli_utils/commands/format.py,sha256=nEU0Hv28O0RCIp6l-pFcKSzzyLE6fajQCiUYpRyHfzU,5175
10
+ lintro/cli_utils/commands/list_tools.py,sha256=zGs5m3Dehk6HhS5WbBGTe_-GFrE-OsaAFQLZcm0MwMw,3495
11
+ lintro/enums/__init__.py,sha256=ZwCDematwfGNPdZJN9j-yoGE5SkkoddRhoHz3F9pLMY,61
12
+ lintro/enums/action.py,sha256=3gln8HUNpZzAyAAjQH-cOyW1nKCx9MT6S4rFDAVirdc,597
13
+ lintro/enums/darglint_strictness.py,sha256=uzAznuAsEbOAJR4bY4v4pDQOyBVRkLGr6t-B7I-zvcs,762
14
+ lintro/enums/group_by.py,sha256=Eb2rJpeTsPhCK-1xnsyogXrcH_pmNvDZ0dHRK1h4kzM,647
15
+ lintro/enums/hadolint_enums.py,sha256=L7Srle5_wEhL5yooawkwbg-u-koBGkECcZiqx2gy24Q,1705
16
+ lintro/enums/output_format.py,sha256=lw3omfjUJUdnrqFjUWVBRSBmWdrxIveS2z2JxFxYPlU,954
17
+ lintro/enums/tool_name.py,sha256=LV09Wpuh2hkEo7Ei5BaIzSwqH8hLGZQ8NmmQOzwqqtI,796
18
+ lintro/enums/tool_type.py,sha256=aa01LmewNr1DRuiHSJ0t5ZH3k933ZLbNYUC9gZXJDfI,829
19
+ lintro/enums/yamllint_format.py,sha256=XzN9S2lHfVz2OKJgcamlXhR6wK9dW3tSCG-s8iiyjoI,802
20
+ lintro/exceptions/__init__.py,sha256=IZclngSiIhDcihyx_-QhTZ__ISIGI1enJu7ytprcLPc,60
21
+ lintro/exceptions/errors.py,sha256=QnELwMd6yTZVMN7nKRElhxLmxo4JeKtb-HDmVNcZ72M,374
22
+ lintro/formatters/__init__.py,sha256=-G0RkbV5aTkHqGufasVYDwye8DH0t3iB_6p0Lh_05Ik,73
23
+ lintro/formatters/core/__init__.py,sha256=Rgvun_rjCoP8mKpF6x6kaneai8un_81ViDiKLAW94EQ,70
24
+ lintro/formatters/core/output_style.py,sha256=CuUp3BgL4YhVofKaXWuFtTn0QPHuQJQy1sd-7tbcBNg,795
25
+ lintro/formatters/core/table_descriptor.py,sha256=MJvFU3xDVsl7v0_HCCAtxGKom1dw1faZW_86fOk5aEQ,849
26
+ lintro/formatters/styles/__init__.py,sha256=rRGvbHhZpu09PJs0IbraFn3-yAYBxGVFv81hsnKkjiU,337
27
+ lintro/formatters/styles/csv.py,sha256=toQ-XmT-A9T2uwELQAdMPY_lzJbh_WQsmmHQXbEQ2kI,1066
28
+ lintro/formatters/styles/grid.py,sha256=54ZtwHsjitkyEjA6aoHCf3GWkMmBXp4fE3c39bjJITw,2908
29
+ lintro/formatters/styles/html.py,sha256=FVjucFJJ3tSoh9nI1NvjJYThv_oWgiBLk6iM7Z-kyoA,1556
30
+ lintro/formatters/styles/json.py,sha256=bVLyHnoUwEcCD-X8GPdIlg_J2FDhOdJRtMr6XcZC0B8,1964
31
+ lintro/formatters/styles/markdown.py,sha256=OsOjq7bNl0P4j0Pf8eu4PiCLPCoD80rK7vZE27r-LQY,1354
32
+ lintro/formatters/styles/plain.py,sha256=lRBnAFpww9Jscw1_cEehAE1W8wTXU-dSCI0auuhvAbU,1172
33
+ lintro/formatters/tools/__init__.py,sha256=5HxvLscHFl-dYv0h_X6_-rEaszdVU-XPNtL-ZkliGvU,1289
34
+ lintro/formatters/tools/actionlint_formatter.py,sha256=GiUlD7DSW375seiPNLwdX3mOjtcn56T7z7ho4nQzEz8,2812
35
+ lintro/formatters/tools/bandit_formatter.py,sha256=WP7haIWGjIPuGZf1FQ5Ixi5DKIySHGdX-L7hZcKnFuw,3093
36
+ lintro/formatters/tools/black_formatter.py,sha256=YTe0Zc82p8cWwlq03bRaSJKOT8iHqXuGe2pR7aFhuB4,2092
37
+ lintro/formatters/tools/darglint_formatter.py,sha256=owYIP3QxtTantBwfd4w1GVfaRiaHFXSynxAntSKw7IU,2649
38
+ lintro/formatters/tools/hadolint_formatter.py,sha256=OWc2BYae31U2TdNcxXkGl0W6fTjVWkkaJV57X4l4jRw,2995
39
+ lintro/formatters/tools/prettier_formatter.py,sha256=GYAyHppot5aG_pWF8QHz5GmWqmqgZwE6UmcUQZFesWE,2915
40
+ lintro/formatters/tools/ruff_formatter.py,sha256=DzH-gs-3IhUFQ6cT6rgVxW1RVNVkGo2jrrEZ4Kx-MS8,4445
41
+ lintro/formatters/tools/yamllint_formatter.py,sha256=oEwkU-I4Upfc8--KAgfVAPSyYbeqkdsDXucxj3OwjIA,3093
42
+ lintro/models/__init__.py,sha256=E6tU5BnnaRWyMEf2cdiN_C3nvbRlWDfblKHrK0is7IU,73
43
+ lintro/models/core/__init__.py,sha256=mevoEG547VTotUsgyocEB72OknnGhExNWcfel7ZPLnI,54
44
+ lintro/models/core/tool.py,sha256=TWkVV_KOYziqCdq_f3Goft3PKWKByitsQ2VFiX6MVHM,2594
45
+ lintro/models/core/tool_config.py,sha256=FCfKQ_bz2yBg_FJ5xEud7TdF0PZ3fPp1Yjty02rbHR8,1004
46
+ lintro/models/core/tool_result.py,sha256=z5fexuCBt1ir0hvzojxVX-Abt6MNIT7_JvH5oyW_FlI,1319
47
+ lintro/parsers/__init__.py,sha256=53UA1LuS21BL4xj8i81Qb59dKXe3jCFLBrDVqvvNHVQ,288
48
+ lintro/parsers/actionlint/__init__.py,sha256=N953d0kbJEOZE9cG4eq9BjiSErdk10GMrngV_tKjLvo,33
49
+ lintro/parsers/actionlint/actionlint_issue.py,sha256=H_BQr04KAGHpVp1qKqETcWLi3i-u6atl7ccDae-jqX8,636
50
+ lintro/parsers/actionlint/actionlint_parser.py,sha256=tvpPOi1dQrnpf7V2-3LMANH_UfDdecI4_AwDQpZOyU0,1898
51
+ lintro/parsers/black/black_issue.py,sha256=RwrT7n8aw4Nybakv83eXoeUxZlDtHwicWKNfrHYIYOg,507
52
+ lintro/parsers/black/black_parser.py,sha256=mCbEy4aTRtxC_SKKPNK6hThGEu8C0m9EnliFW__Sa3Y,2958
53
+ lintro/parsers/darglint/__init__.py,sha256=r2ilRjf8WdHOvXIGsg4SmtYkRUR76DowWLA_n9sIOV0,55
54
+ lintro/parsers/darglint/darglint_issue.py,sha256=Gc07-PGvv4Kn4X85tnACLYB5FygNPthEe3fYodOaFoA,444
55
+ lintro/parsers/darglint/darglint_parser.py,sha256=pbIGnmHVKjf8OaxvhQbDWWXAFZEictb3FfuhoGv_I00,2046
56
+ lintro/parsers/hadolint/__init__.py,sha256=vU33M_cj6fyg7aqZthz8MFUMfBIshX_Nx3DNuQmbHD4,30
57
+ lintro/parsers/hadolint/hadolint_issue.py,sha256=VlTzTPHcluFZihFL-Tkg7UuXp1fQcD03eDCH0sr1qHU,582
58
+ lintro/parsers/hadolint/hadolint_parser.py,sha256=Q7Hf6sL45s38O3C9dbBYzOpDlb0KGFj0U3Yry_ixdK8,1810
59
+ lintro/parsers/prettier/__init__.py,sha256=jH8yEsh_X6TpwJs1V1cN1AmbDuQwQ9qWA-_dk0R9jOA,55
60
+ lintro/parsers/prettier/prettier_issue.py,sha256=DKxV9LXxAB-CxvOsbWVEAmfpXQJvd4ikHKGyMrg1Y60,569
61
+ lintro/parsers/prettier/prettier_parser.py,sha256=lH50rhFHbWWLH50CiTlyIvjgbx9vz1hiIdtxEXi7uJw,2043
62
+ lintro/parsers/ruff/__init__.py,sha256=1oT00c82qKfveRqa4FnSedAEQjQVrb9BspJEUQJn-Kk,26
63
+ lintro/parsers/ruff/ruff_issue.py,sha256=96ht6YWImOBiRTo0ATlM1PvVhgIwFiuN8810emm9nXo,1142
64
+ lintro/parsers/ruff/ruff_parser.py,sha256=DRGzpf55OnNJCTO01RSFK4GXr6Lav-_JzACdqDesuj0,4722
65
+ lintro/parsers/yamllint/__init__.py,sha256=ZZtw7Ref1EAbY-IwSz2UxJauGak6sKO10pfYsdE_sV8,55
66
+ lintro/parsers/yamllint/yamllint_issue.py,sha256=jWawdGUiTI1HADbhg8yEdZfC6yHcmk9FDzpr3PBUPBg,608
67
+ lintro/parsers/yamllint/yamllint_parser.py,sha256=ol44xhJcspnGIsEta8vVv1xi-sIbpB12CovmT94QFmQ,1947
68
+ lintro/tools/__init__.py,sha256=S-L_x7Hzp-Ja-QezupEUwF6-uhRl4ZXSIpRx59jvsaA,1410
69
+ lintro/tools/tool_enum.py,sha256=RkJcfEpM_lFWp2y1rFqZdr3qOzYVcSoVBt3XVbtO4Sg,926
70
+ lintro/tools/core/__init__.py,sha256=eUbc-DlpOcFaG_gKzy-veaOQAOkC5-OuItxI--p6I9c,59
71
+ lintro/tools/core/tool_base.py,sha256=yOEvReleksIV_t_aB4PZv6-PgXZTrHTnz4SDPSr_MSk,13480
72
+ lintro/tools/core/tool_manager.py,sha256=yKQONy17t7-fq1k9dkrMk6lZaAvaSwp7-JcvJFZMoYc,5103
73
+ lintro/tools/implementations/__init__.py,sha256=7DX7vWmRUVGYgrB2Rx1IbKpLWfXfVDLiOfz-UX2Crzs,64
74
+ lintro/tools/implementations/tool_actionlint.py,sha256=7b1pUxs9Ln6KmoLiRQBmyDeq9SmgZ08e0DxleM9NEt8,5466
75
+ lintro/tools/implementations/tool_bandit.py,sha256=5HhwwHTmXiOlxK9UlfmnGqEtHJnMkD8ryiVHI_AN3s0,15523
76
+ lintro/tools/implementations/tool_black.py,sha256=x1U6NhdRxxYE5Tmvh-LoxcWtIkwHceeJWMUROa9tAJ4,9680
77
+ lintro/tools/implementations/tool_darglint.py,sha256=1G_gxjhkFNNCVCSkMDAgicYEZJ6JcI_NGX47Sg3a1vM,9561
78
+ lintro/tools/implementations/tool_hadolint.py,sha256=NfHLoThp23V-n5chSfrBZetleXsRR4oYxkLxOkJxU0g,11479
79
+ lintro/tools/implementations/tool_prettier.py,sha256=tTw3yEn6RR2rCBoZdeDWy6r06Sfd_jCR2fLCBj4MDoY,9783
80
+ lintro/tools/implementations/tool_ruff.py,sha256=YkVwvbBE-yxzcufnk8bnewBOfHqGsbzVsrKYEUX3mkQ,25356
81
+ lintro/tools/implementations/tool_yamllint.py,sha256=0powR9F3FkIq-b7PI0-XWdh7nx7rR3HVMtvEaz_NWeA,8831
82
+ lintro/utils/__init__.py,sha256=mUAkJXnTb91theOR_ue6FfGHtuSm3C466T_SH_KoWRE,70
83
+ lintro/utils/ascii_normalize_cli.py,sha256=Rlasrsy9FShCKGJ_9ti4VxJGP-I05vAtTmNMENbo_a4,2575
84
+ lintro/utils/config.py,sha256=3AVwNAteT-enN6Ica8jJbPwEFO94i4F7uWiEA1HSpJY,1427
85
+ lintro/utils/console_logger.py,sha256=N0n5LomG6MDqu4tSPbmhBsIB6lzpRl5M53tZx6EoF4k,33104
86
+ lintro/utils/formatting.py,sha256=khC9hYBva5xqBV1IqNcivRH9gRdvWw6u6mh2TZLTy7E,5346
87
+ lintro/utils/output_manager.py,sha256=oIE0g8LNVQcWSQOb1MbDVqGYPrOjBGuUZ4R80M1RevQ,10213
88
+ lintro/utils/path_utils.py,sha256=NJP3vjVDcRBgUHtYNrpL0tRa0Sc3oQhGX3_2WWzdZE4,1395
89
+ lintro/utils/tool_executor.py,sha256=8-m7nQKLRlKHBTvzT7iLmaHYZ3-2s0t4zUbC-py8-H8,26204
90
+ lintro/utils/tool_utils.py,sha256=DIdebZxdUBhomuBKSezfojNOmEZyP7KtGifz1EX1qZo,15746
91
+ lintro-0.9.0.dist-info/licenses/LICENSE,sha256=CwaAnyD2psonDBBJjbqFUz00W8nQw-FGDlEGZReUV6A,1069
92
+ lintro-0.9.0.dist-info/METADATA,sha256=LQYE3UdaHkMpp9JIYxjWYTGDdSpXuX6hF_n57XOw-Dw,16029
93
+ lintro-0.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
94
+ lintro-0.9.0.dist-info/entry_points.txt,sha256=SYSk35jFyNLEHyrofSJsRv4qFN9NsT4VWSbvnTS9ov0,43
95
+ lintro-0.9.0.dist-info/top_level.txt,sha256=_D-7eyV6gNBOoIwHuf_h60wN_RWiw8GxB430Il9VKhU,7
96
+ lintro-0.9.0.dist-info/RECORD,,
@@ -1,96 +0,0 @@
1
- lintro/__init__.py,sha256=fEulqe3o_DokVWV3Slw2lQCAekhEoA_Rs6cTtfKhOF8,110
2
- lintro/__main__.py,sha256=McxM6wEcEeCLBHZs0F8xzT1PxVqJYkjtaPq1_-Nug-0,106
3
- lintro/cli.py,sha256=Fy5nqdSg4id0gjGbPy82wRTK3aZMWbRdRDnwAbIv-vs,2333
4
- lintro/ascii-art/fail.txt,sha256=gshKngSrz5FvJdP6g7lPygpLsLsZZh4etBU_wR7PXOw,56396
5
- lintro/ascii-art/success.txt,sha256=xhteIXcBvl_1QcFKMIS9CkjjCY8czdf9znP9AiB8oPU,90151
6
- lintro/cli_utils/__init__.py,sha256=F5P_fY_s-gwNZrnV1bzUOgHGb73QeZG1i-K_kfyJ8Yg,250
7
- lintro/cli_utils/commands/__init__.py,sha256=GGT-uG0gQLlHAqkrwfwqfCkD6DUMP4Tq1rgDgaYe5Ng,239
8
- lintro/cli_utils/commands/check.py,sha256=YnpuiX8-kTADnwxS_mZUPf_SkmqdyqTZFJLTDf7QSQ8,6229
9
- lintro/cli_utils/commands/format.py,sha256=nEU0Hv28O0RCIp6l-pFcKSzzyLE6fajQCiUYpRyHfzU,5175
10
- lintro/cli_utils/commands/list_tools.py,sha256=Q-ZXwkkDDZ5pdLFLyxYcKMmWAPNF_vv7N4r8yRCEn3M,3495
11
- lintro/enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- lintro/enums/action.py,sha256=3gln8HUNpZzAyAAjQH-cOyW1nKCx9MT6S4rFDAVirdc,597
13
- lintro/enums/darglint_strictness.py,sha256=_mKMbGtqzQd8dtRkzP_QHI_3OgIPiW9LvHIUbK7WtvE,479
14
- lintro/enums/group_by.py,sha256=Eb2rJpeTsPhCK-1xnsyogXrcH_pmNvDZ0dHRK1h4kzM,647
15
- lintro/enums/hadolint_enums.py,sha256=CY4ucR3CSQkHKGEFptZ6j3RU_saWmimPM9921Q4HgtI,1078
16
- lintro/enums/output_format.py,sha256=lw3omfjUJUdnrqFjUWVBRSBmWdrxIveS2z2JxFxYPlU,954
17
- lintro/enums/tool_name.py,sha256=LV09Wpuh2hkEo7Ei5BaIzSwqH8hLGZQ8NmmQOzwqqtI,796
18
- lintro/enums/tool_type.py,sha256=aa01LmewNr1DRuiHSJ0t5ZH3k933ZLbNYUC9gZXJDfI,829
19
- lintro/enums/yamllint_format.py,sha256=yKOeM10SBWKKAqsU0OohOpmZXp8M1lkbdUM0Pk20SPw,490
20
- lintro/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- lintro/exceptions/errors.py,sha256=QnELwMd6yTZVMN7nKRElhxLmxo4JeKtb-HDmVNcZ72M,374
22
- lintro/formatters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- lintro/formatters/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- lintro/formatters/core/output_style.py,sha256=kr5kAWULozdj2dmHjbyferXuHMF4fx5hCTLIPWu3XRU,480
25
- lintro/formatters/core/table_descriptor.py,sha256=gLEhoGO0QQoPXM6ye3VxQ3oYG746j1EBbliCGmvpAos,586
26
- lintro/formatters/styles/__init__.py,sha256=rRGvbHhZpu09PJs0IbraFn3-yAYBxGVFv81hsnKkjiU,337
27
- lintro/formatters/styles/csv.py,sha256=Ghm-GElUeLwZFCRqZJvC2KxqQZqiD8jtIlY8Mz-PL5c,1026
28
- lintro/formatters/styles/grid.py,sha256=t1T1e4UzOxd9Rh3M58AJjud-DdmiugC_R0HvNpytQmk,2867
29
- lintro/formatters/styles/html.py,sha256=M1YVlzCKsV8_zaOearYwkPOHEVLcbV-tueXMJJcJbN8,1515
30
- lintro/formatters/styles/json.py,sha256=aZobZxntAiyo92_elI1VznrOd8jzEPrU7MUQBQHdM8Q,1923
31
- lintro/formatters/styles/markdown.py,sha256=proUn2UEhNceTw6mVv_KSBYinLekgATx58ZWiu_PT1U,1309
32
- lintro/formatters/styles/plain.py,sha256=_-4ziK6vBtvxI1wk-qLJHqKTUMs0SGWaBJPz1zV4Ngc,1125
33
- lintro/formatters/tools/__init__.py,sha256=5HxvLscHFl-dYv0h_X6_-rEaszdVU-XPNtL-ZkliGvU,1289
34
- lintro/formatters/tools/actionlint_formatter.py,sha256=GiUlD7DSW375seiPNLwdX3mOjtcn56T7z7ho4nQzEz8,2812
35
- lintro/formatters/tools/bandit_formatter.py,sha256=WP7haIWGjIPuGZf1FQ5Ixi5DKIySHGdX-L7hZcKnFuw,3093
36
- lintro/formatters/tools/black_formatter.py,sha256=xFmcmdfgQributNqQRBtp7MwPfR6QKFCsBo6urk0tDY,1623
37
- lintro/formatters/tools/darglint_formatter.py,sha256=NG5V32PX7PnxOccisyXYy02TVUp15r1dzwZzC9KQRsw,2202
38
- lintro/formatters/tools/hadolint_formatter.py,sha256=enugwgR571IFv-75HJ-RCrsK9GBZfH-Bwb-WjoJGd2Y,2608
39
- lintro/formatters/tools/prettier_formatter.py,sha256=VtV3uSoNhUtatqQa18QtQgcBAmzMPyjS2xDIAzpI0IY,2470
40
- lintro/formatters/tools/ruff_formatter.py,sha256=3R3ByBDN5YzR71cLOyNQMWLDkVU8qif_PweoTQXskkM,4011
41
- lintro/formatters/tools/yamllint_formatter.py,sha256=8IkW5gHcsQPn--1yj7Q2AAHnrSVM9BynAevZWq2vmDo,2701
42
- lintro/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
- lintro/models/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- lintro/models/core/tool.py,sha256=TWkVV_KOYziqCdq_f3Goft3PKWKByitsQ2VFiX6MVHM,2594
45
- lintro/models/core/tool_config.py,sha256=_VHxiUc0V3e9e2VIVJCpoEQ66cijuNMtsiz5hQmVIus,804
46
- lintro/models/core/tool_result.py,sha256=z5fexuCBt1ir0hvzojxVX-Abt6MNIT7_JvH5oyW_FlI,1319
47
- lintro/parsers/__init__.py,sha256=53UA1LuS21BL4xj8i81Qb59dKXe3jCFLBrDVqvvNHVQ,288
48
- lintro/parsers/actionlint/__init__.py,sha256=N953d0kbJEOZE9cG4eq9BjiSErdk10GMrngV_tKjLvo,33
49
- lintro/parsers/actionlint/actionlint_issue.py,sha256=H_BQr04KAGHpVp1qKqETcWLi3i-u6atl7ccDae-jqX8,636
50
- lintro/parsers/actionlint/actionlint_parser.py,sha256=FAovzY877UeaDj7ZHb0zy6Y_hsidWS6UR1O8KCYVISU,1889
51
- lintro/parsers/black/black_issue.py,sha256=RwrT7n8aw4Nybakv83eXoeUxZlDtHwicWKNfrHYIYOg,507
52
- lintro/parsers/black/black_parser.py,sha256=mCbEy4aTRtxC_SKKPNK6hThGEu8C0m9EnliFW__Sa3Y,2958
53
- lintro/parsers/darglint/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- lintro/parsers/darglint/darglint_issue.py,sha256=f43blLSM13r1VyyGwk-bjtC7raBCjRqwlnPw7Nmx9ic,127
55
- lintro/parsers/darglint/darglint_parser.py,sha256=pbIGnmHVKjf8OaxvhQbDWWXAFZEictb3FfuhoGv_I00,2046
56
- lintro/parsers/hadolint/__init__.py,sha256=vU33M_cj6fyg7aqZthz8MFUMfBIshX_Nx3DNuQmbHD4,30
57
- lintro/parsers/hadolint/hadolint_issue.py,sha256=VlTzTPHcluFZihFL-Tkg7UuXp1fQcD03eDCH0sr1qHU,582
58
- lintro/parsers/hadolint/hadolint_parser.py,sha256=Q7Hf6sL45s38O3C9dbBYzOpDlb0KGFj0U3Yry_ixdK8,1810
59
- lintro/parsers/prettier/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- lintro/parsers/prettier/prettier_issue.py,sha256=yeOUmkb79mhYmidcx-iZgxvsNl_jkjT3ZCqT_Vo27rw,164
61
- lintro/parsers/prettier/prettier_parser.py,sha256=lH50rhFHbWWLH50CiTlyIvjgbx9vz1hiIdtxEXi7uJw,2043
62
- lintro/parsers/ruff/__init__.py,sha256=1oT00c82qKfveRqa4FnSedAEQjQVrb9BspJEUQJn-Kk,26
63
- lintro/parsers/ruff/ruff_issue.py,sha256=96ht6YWImOBiRTo0ATlM1PvVhgIwFiuN8810emm9nXo,1142
64
- lintro/parsers/ruff/ruff_parser.py,sha256=dwNaDgb-96DPuIiAgtHa0E1lTtzr2FLeWdZD_LVZhxE,4576
65
- lintro/parsers/yamllint/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
- lintro/parsers/yamllint/yamllint_issue.py,sha256=jWawdGUiTI1HADbhg8yEdZfC6yHcmk9FDzpr3PBUPBg,608
67
- lintro/parsers/yamllint/yamllint_parser.py,sha256=ol44xhJcspnGIsEta8vVv1xi-sIbpB12CovmT94QFmQ,1947
68
- lintro/tools/__init__.py,sha256=S-L_x7Hzp-Ja-QezupEUwF6-uhRl4ZXSIpRx59jvsaA,1410
69
- lintro/tools/tool_enum.py,sha256=ECok554KLqeLuU2xyLl1fcqzh77x9ojc-6Z7CRBRQUU,851
70
- lintro/tools/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
- lintro/tools/core/tool_base.py,sha256=pnu1sMwPuICCOy_CZRip6HhvHVSYfs7q5Sfpi9zUX9E,12360
72
- lintro/tools/core/tool_manager.py,sha256=yKQONy17t7-fq1k9dkrMk6lZaAvaSwp7-JcvJFZMoYc,5103
73
- lintro/tools/implementations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
- lintro/tools/implementations/tool_actionlint.py,sha256=7b1pUxs9Ln6KmoLiRQBmyDeq9SmgZ08e0DxleM9NEt8,5466
75
- lintro/tools/implementations/tool_bandit.py,sha256=Ck1fT8I9CSRTJ8zYqXYsVMQ_-HSp_VzUcB6GWuTahvI,15939
76
- lintro/tools/implementations/tool_black.py,sha256=x1U6NhdRxxYE5Tmvh-LoxcWtIkwHceeJWMUROa9tAJ4,9680
77
- lintro/tools/implementations/tool_darglint.py,sha256=zj8hQ8bVW3fwO8dsK9tdXzyL1yzZvqiEetVGvxhGe4g,9396
78
- lintro/tools/implementations/tool_hadolint.py,sha256=NfHLoThp23V-n5chSfrBZetleXsRR4oYxkLxOkJxU0g,11479
79
- lintro/tools/implementations/tool_prettier.py,sha256=tTw3yEn6RR2rCBoZdeDWy6r06Sfd_jCR2fLCBj4MDoY,9783
80
- lintro/tools/implementations/tool_ruff.py,sha256=8WIpOKSc6HmuvEFCWgTqBX9X4T3MSD-RKesv4bs2lpQ,25361
81
- lintro/tools/implementations/tool_yamllint.py,sha256=0powR9F3FkIq-b7PI0-XWdh7nx7rR3HVMtvEaz_NWeA,8831
82
- lintro/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
- lintro/utils/ascii_normalize_cli.py,sha256=8Pi9XHIIIYCyPCq-JOAPnj5tYbz6XLmFLO7KypCv3EA,2408
84
- lintro/utils/config.py,sha256=3AVwNAteT-enN6Ica8jJbPwEFO94i4F7uWiEA1HSpJY,1427
85
- lintro/utils/console_logger.py,sha256=N0n5LomG6MDqu4tSPbmhBsIB6lzpRl5M53tZx6EoF4k,33104
86
- lintro/utils/formatting.py,sha256=khC9hYBva5xqBV1IqNcivRH9gRdvWw6u6mh2TZLTy7E,5346
87
- lintro/utils/output_manager.py,sha256=oIE0g8LNVQcWSQOb1MbDVqGYPrOjBGuUZ4R80M1RevQ,10213
88
- lintro/utils/path_utils.py,sha256=NJP3vjVDcRBgUHtYNrpL0tRa0Sc3oQhGX3_2WWzdZE4,1395
89
- lintro/utils/tool_executor.py,sha256=8-m7nQKLRlKHBTvzT7iLmaHYZ3-2s0t4zUbC-py8-H8,26204
90
- lintro/utils/tool_utils.py,sha256=zobweCIANBxXxHUERajFDshQyZGlEnJPQ52NleOrDdQ,15884
91
- lintro-0.7.0.dist-info/licenses/LICENSE,sha256=CwaAnyD2psonDBBJjbqFUz00W8nQw-FGDlEGZReUV6A,1069
92
- lintro-0.7.0.dist-info/METADATA,sha256=wkHfSzxSATr6CPOifCS2ScP0hpWL3lZYP6HXbCv6JVw,16029
93
- lintro-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
94
- lintro-0.7.0.dist-info/entry_points.txt,sha256=SYSk35jFyNLEHyrofSJsRv4qFN9NsT4VWSbvnTS9ov0,43
95
- lintro-0.7.0.dist-info/top_level.txt,sha256=_D-7eyV6gNBOoIwHuf_h60wN_RWiw8GxB430Il9VKhU,7
96
- lintro-0.7.0.dist-info/RECORD,,
File without changes