Coverage for sphinxlint/sphinxlint.py: 85%

1 from collections import Counter

2 from dataclasses import dataclass

3 from os.path import splitext

4 from pathlib import Path

5 from typing import Optional

6

7 from sphinxlint.utils import PER_FILE_CACHES, hide_non_rst_blocks, po2rst

8

9

10@dataclass(frozen=True)

11 class LintError:

12 """A linting error found by one of the checkers"""

13

14 filename: str

15 line_no: int

16 msg: str

17 checker_name: str

18

19 def __str__(self):

20 return f"{self.filename}:{self.line_no}: {self.msg} ({self.checker_name})"

21

22

23 class CheckersOptions:

24 """Configuration options for checkers."""

25

26 max_line_length = 80

27

28 @classmethod

29 def from_argparse(cls, namespace):

30 options = cls()

31 options.max_line_length = namespace.max_line_length

32 return options

33

34

35 def check_text(filename, text, checkers, options=None):

36 if options is None:

37 options = CheckersOptions()

38 errors = []

39 ext = splitext(filename)[1]

40 checkers = {checker for checker in checkers if ext in checker.suffixes}

41 lines = tuple(text.splitlines(keepends=True))

42 if any(checker.rst_only for checker in checkers):

43 lines_with_rst_only = hide_non_rst_blocks(lines)

44 for check in checkers:

45 if ext not in check.suffixes:

46 continue

47 for lno, msg in check(

48 filename, lines_with_rst_only if check.rst_only else lines, options

49 ):

50 errors.append(LintError(filename, lno, msg, check.name))

51 return errors

52

53

54 def check_file(filename, checkers, options: Optional[CheckersOptions] = None):

55 try:

56 ext = splitext(filename)[1]

57 if not any(ext in checker.suffixes for checker in checkers):

58 return Counter()

59 try:

60 text = Path(filename).read_text(encoding="UTF-8")

61 if filename.endswith(".po"):

62 text = po2rst(text)

63 except OSError as err:

64 return [f"{filename}: cannot open: {err}"]

65 except UnicodeDecodeError as err:

66 return [f"{filename}: cannot decode as UTF-8: {err}"]

67 return check_text(filename, text, checkers, options)

68 finally:

69 for memoized_function in PER_FILE_CACHES:

70 memoized_function.cache_clear()