From 15ee57b572297bdf7d6c074252918b8b862ec46b Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Sat, 30 Aug 2014 18:00:21 -0700 Subject: [PATCH] Allow checks to provide a extension matcher When a check is for a rst file vs a plaintext file and is not interoperable this change allows that check to declare that it only knows how to scan certain files. It also fixes the max line checker to use a similar routine to differentiate between rst files and non rst files and uses a different routine based on this decision. Change-Id: I1e3e5f9f1631ed5babe2df17c987e8e872938a38 --- doc8/checks.py | 30 +++++++++++++++++----- doc8/main.py | 10 ++++++++ doc8/parser.py | 5 ++++ doc8/tests/test_checks.py | 54 ++++++++++++++++++++++----------------- 4 files changed, 68 insertions(+), 31 deletions(-) diff --git a/doc8/checks.py b/doc8/checks.py index 30e994b..4d63fd4 100644 --- a/doc8/checks.py +++ b/doc8/checks.py @@ -75,6 +75,7 @@ class CheckCarriageReturn(LineCheck): class CheckValidity(ContentCheck): REPORTS = frozenset(["D000"]) + EXT_MATCHER = re.compile(r"(.*)[.]rst", re.I) # Only used when running in sphinx mode. SPHINX_IGNORES_REGEX = [ @@ -107,6 +108,11 @@ class CheckValidity(ContentCheck): class CheckMaxLineLength(ContentCheck): REPORTS = frozenset(["D001"]) + def __init__(self, cfg): + super(CheckMaxLineLength, self).__init__(cfg) + self._max_line_length = self._cfg['max_line_length'] + self._allow_long_titles = self._cfg['allow_long_titles'] + def _extract_node_lines(self, doc): def extract_lines(node, start_line): @@ -185,10 +191,14 @@ class CheckMaxLineLength(ContentCheck): directives.append((i, find_directive_end(i, lines))) return directives - def report_iter(self, parsed_file): - doc = parsed_file.document - lines = list(parsed_file.lines_iter()) + def _txt_checker(self, parsed_file): + for i, line in enumerate(parsed_file.lines_iter()): + if len(line) > self._max_line_length: + yield (i + 1, 'D001', 'Line too long') + def _rst_checker(self, parsed_file): + lines = list(parsed_file.lines_iter()) + doc = parsed_file.document nodes_lines, first_line = self._extract_node_lines(doc) directives = self._extract_directives(lines) @@ -225,10 +235,8 @@ class CheckMaxLineLength(ContentCheck): docutils_nodes.subtitle, docutils_nodes.section, ) - max_line_length = self._cfg['max_line_length'] - allow_long_titles = self._cfg['allow_long_titles'] for i, line in enumerate(lines): - if len(line) > max_line_length: + if len(line) > self._max_line_length: in_directive = False for (start, end) in directives: if i >= start and i <= end: @@ -245,6 +253,14 @@ class CheckMaxLineLength(ContentCheck): nodes = find_containing_nodes(i + 1) if any_types(nodes, skip_types): continue - if allow_long_titles and any_types(nodes, title_types): + if self._allow_long_titles and any_types(nodes, title_types): continue yield (i + 1, 'D001', 'Line too long') + + def report_iter(self, parsed_file): + if parsed_file.extension.lower() != '.rst': + checker_func = self._txt_checker + else: + checker_func = self._rst_checker + for issue in checker_func(parsed_file): + yield issue diff --git a/doc8/main.py b/doc8/main.py index b0cf811..6ae00a0 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -216,6 +216,16 @@ def main(): check_name = ".".join([c.__class__.__module__, c.__class__.__name__]) error_counts.setdefault(check_name, 0) + try: + extension_matcher = c.EXT_MATCHER + except AttributeError: + pass + else: + if not extension_matcher.match(f.extension): + print(" Skipping check '%s' since it does not understand" + " parsing a file with extension '%s'" + % (check_name, f.extension)) + continue try: reports = set(c.REPORTS) except AttributeError: diff --git a/doc8/parser.py b/doc8/parser.py index 9f7b82e..a971bf4 100644 --- a/doc8/parser.py +++ b/doc8/parser.py @@ -33,6 +33,7 @@ class ParsedFile(object): self._encoding = encoding self._doc = None self._errors = None + self._extension = os.path.splitext(filename)[1] @property def errors(self): @@ -76,6 +77,10 @@ class ParsedFile(object): line = line[0:-1] yield line + @property + def extension(self): + return self._extension + @property def filename(self): return self._filename diff --git a/doc8/tests/test_checks.py b/doc8/tests/test_checks.py index adccee0..1530fc5 100644 --- a/doc8/tests/test_checks.py +++ b/doc8/tests/test_checks.py @@ -73,20 +73,21 @@ test content += "\n\n" content += ("a" * 60) + " " + ("b" * 60) content += "\n" + conf = { + 'max_line_length': 79, + 'allow_long_titles': True, + } + for ext in ['.rst', '.txt']: + with tempfile.NamedTemporaryFile(suffix=ext) as fh: + fh.write(content) + fh.flush() - with tempfile.NamedTemporaryFile() as fh: - fh.write(content) - fh.flush() - parsed_file = parser.ParsedFile(fh.name) - conf = { - 'max_line_length': 79, - 'allow_long_titles': True, - } - check = checks.CheckMaxLineLength(conf) - errors = list(check.report_iter(parsed_file)) - self.assertEqual(1, len(errors)) - (line, code, msg) = errors[0] - self.assertIn(code, check.REPORTS) + parsed_file = parser.ParsedFile(fh.name) + check = checks.CheckMaxLineLength(conf) + errors = list(check.report_iter(parsed_file)) + self.assertEqual(1, len(errors)) + (line, code, msg) = errors[0] + self.assertIn(code, check.REPORTS) def test_unsplittable_length(self): content = """ @@ -102,15 +103,20 @@ test content += "\n\n" content += "a" * 100 content += "\n" + conf = { + 'max_line_length': 79, + 'allow_long_titles': True, + } + # This number is different since rst parsing is aware that titles + # are allowed to be over-length, while txt parsing is not aware of + # this fact (since it has no concept of title sections). + extensions = [(0, '.rst'), (1, '.txt')] + for expected_errors, ext in extensions: + with tempfile.NamedTemporaryFile(suffix=ext) as fh: + fh.write(content) + fh.flush() - with tempfile.NamedTemporaryFile() as fh: - fh.write(content) - fh.flush() - parsed_file = parser.ParsedFile(fh.name) - conf = { - 'max_line_length': 79, - 'allow_long_titles': True, - } - check = checks.CheckMaxLineLength(conf) - errors = list(check.report_iter(parsed_file)) - self.assertEqual(0, len(errors)) + parsed_file = parser.ParsedFile(fh.name) + check = checks.CheckMaxLineLength(conf) + errors = list(check.report_iter(parsed_file)) + self.assertEqual(expected_errors, len(errors))