Skip to content

Commit

Permalink
Lint with black and flake8
Browse files Browse the repository at this point in the history
  • Loading branch information
toastwaffle committed Feb 7, 2025
1 parent e861289 commit 565a426
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 104 deletions.
35 changes: 16 additions & 19 deletions asciidoc_linter/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,35 @@
from .linter import AsciiDocLinter
from .reporter import ConsoleReporter, JsonReporter, HtmlReporter, Reporter


def create_parser() -> argparse.ArgumentParser:
"""Create the command line parser"""
parser = argparse.ArgumentParser(
description='Lint AsciiDoc files for common issues and style violations'
)
parser.add_argument(
'files',
nargs='+',
help='One or more AsciiDoc files to check'
)
parser.add_argument(
'--config',
help='Path to configuration file'
description="Lint AsciiDoc files for common issues and style violations"
)
parser.add_argument("files", nargs="+", help="One or more AsciiDoc files to check")
parser.add_argument("--config", help="Path to configuration file")
parser.add_argument(
'--format',
choices=['console', 'plain', 'json', 'html'],
default='console',
help='Output format (default: console)'
"--format",
choices=["console", "plain", "json", "html"],
default="console",
help="Output format (default: console)",
)
return parser


def get_reporter(format: str) -> Reporter:
if format == 'json':
if format == "json":
return JsonReporter()
if format == 'html':
if format == "html":
return HtmlReporter()
if format == 'plain':
if format == "plain":
return ConsoleReporter(enable_color=False)
if format == 'console':
if format == "console":
return ConsoleReporter(enable_color=True)
raise ValueError(f"Unrecognised format {format}")


def main(args: Optional[List[str]] = None) -> int:
"""Main entry point for the linter"""
if args is None:
Expand All @@ -57,5 +53,6 @@ def main(args: Optional[List[str]] = None) -> int:

return report.exit_code

if __name__ == '__main__':

if __name__ == "__main__":
sys.exit(main())
13 changes: 6 additions & 7 deletions asciidoc_linter/linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,15 @@
from .rules.heading_rules import (
HeadingFormatRule,
HeadingHierarchyRule,
MultipleTopLevelHeadingsRule
)
from .rules.block_rules import (
UnterminatedBlockRule,
BlockSpacingRule
MultipleTopLevelHeadingsRule,
)
from .rules.block_rules import UnterminatedBlockRule, BlockSpacingRule
from .rules.whitespace_rules import WhitespaceRule
from .rules.image_rules import ImageAttributesRule
from .parser import AsciiDocParser
from .reporter import LintReport


class AsciiDocLinter:
"""Main linter class that coordinates parsing and rule checking"""

Expand All @@ -33,7 +31,7 @@ def __init__(self):
UnterminatedBlockRule(),
BlockSpacingRule(),
WhitespaceRule(),
ImageAttributesRule()
ImageAttributesRule(),
]

def lint(self, file_paths: List[str]) -> LintReport:
Expand All @@ -59,7 +57,8 @@ def lint_file(self, file_path: Path) -> List[Finding]:
Finding(
message=f"Error linting file: {e}",
severity=Severity.ERROR,
file=str(file_path))
file=str(file_path),
)
]

def lint_string(self, content: str) -> List[Finding]:
Expand Down
56 changes: 33 additions & 23 deletions asciidoc_linter/reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
from abc import ABC, abstractmethod
from collections import defaultdict
from dataclasses import dataclass
from typing import List, Dict, Optional
from typing import List, Dict
import json

from .rules.base import Finding


@dataclass
class LintReport:
"""Contains all lint findings for a document"""

findings: List[Finding]

def grouped_findings(self) -> Dict[str, List[Finding]]:
Expand All @@ -32,13 +34,15 @@ def __bool__(self):
def __len__(self):
return len(self.findings)


class Reporter(ABC):
"""Base class for lint report formatters."""

@abstractmethod
def format_report(self, report: LintReport) -> str:
pass


class ConsoleReporter(Reporter):
"""Base class for formatting lint reports"""

Expand All @@ -54,7 +58,7 @@ def _red(self, text):
if not self.enable_color:
return text
return f"\033[31m{text}\033[0m"

def format_report(self, report: LintReport) -> str:
"""Format the report as string"""
if not report:
Expand All @@ -65,46 +69,52 @@ def format_report(self, report: LintReport) -> str:
if file:
output.append(f"Results for {file}:")
else:
output.append(f"Results without file:")
output.append("Results without file:")

for finding in findings:
location = finding.location
if location:
output.append(f"{self._red('✗')} {finding.location}: {finding.message}")
output.append(
f"{self._red('✗')} {finding.location}: {finding.message}"
)
else:
output.append(f"{self._red('✗')} {finding.message}")
output.append("\n")

return "\n".join(output)


class JsonReporter(Reporter):
"""Reports findings in JSON format"""

def format_report(self, report: LintReport) -> str:
return json.dumps({
"findings": [
finding.to_json_object()
for finding in report.findings
],
}, indent=2)
return json.dumps(
{
"findings": [finding.to_json_object() for finding in report.findings],
},
indent=2,
)


class HtmlReporter(Reporter):
"""Reports findings in HTML format"""

def format_report(self, report: LintReport) -> str:
rows = []
for finding in report.findings:
rows.extend([
f'<tr>',
f'<td>{finding.severity}</td>',
f'<td>{finding.rule_id or ""}</td>',
f'<td>{finding.location}</td>',
f'<td>{finding.message}</td>',
f'</tr>',
])
rows.extend(
[
"<tr>",
f"<td>{finding.severity}</td>",
f'<td>{finding.rule_id or ""}</td>',
f"<td>{finding.location}</td>",
f"<td>{finding.message}</td>",
"</tr>",
]
)

rows = "\n".join(rows)

return f"""<!DOCTYPE html>
<html>
<head>
Expand All @@ -130,4 +140,4 @@ def format_report(self, report: LintReport) -> str:
{rows}
</table>
</body>
</html>"""
</html>"""
29 changes: 15 additions & 14 deletions asciidoc_linter/rules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""

from typing import Type, Dict, List, Optional, Any
from enum import Enum, auto
from enum import Enum
from dataclasses import dataclass

class Severity(str, Enum):
Expand Down Expand Up @@ -44,13 +44,14 @@ def __str__(self) -> str:
@dataclass
class Finding:
"""Represents a rule violation finding"""

message: str
severity: Severity
position: Optional[Position] = None
rule_id: Optional[str] = None
context: Optional[Any] = None
file: Optional[str] = None

@property
def line_number(self) -> int:
"""Backward compatibility for line number access"""
Expand All @@ -61,27 +62,27 @@ def location(self) -> str:
"""A string representation of the finding's location."""
if not self.position:
return self.file or ""
l = str(self.position)
location = str(self.position)
if self.file:
l = f"{self.file}, {l}"
return l
location = f"{self.file}, {location}"
return location

def to_json_object(self) -> Dict[str, Any]:
"""An object which can be serialised to JSON."""
return {
'file': self.file,
'line': self.position.line if self.position else None,
'column': self.position.column if self.position else None,
'message': self.message,
'severity': str(self.severity),
'rule_id': self.rule_id,
'context': self.context,
"file": self.file,
"line": self.position.line if self.position else None,
"column": self.position.column if self.position else None,
"message": self.message,
"severity": str(self.severity),
"rule_id": self.rule_id,
"context": self.context,
}

def set_file(self, file: str) -> 'Finding':
def set_file(self, file: str) -> "Finding":
self.file = file
return self

def __post_init__(self):
"""
Ensure severity is always a Severity enum instance.
Expand Down
Loading

0 comments on commit 565a426

Please sign in to comment.