diff --git a/README.rst b/README.rst index 7ce3fd4..7801649 100644 --- a/README.rst +++ b/README.rst @@ -91,15 +91,15 @@ extensions if you like:: [django_coverage_plugin] template_extensions = html, txt, tex, email -If you use ``pyproject.toml`` for tool configuration use:: +Block tags can be excluded using regexes to match the block content; +for example, to exclude a custom template tag ``{% my_tag ... %}``, use:: - [tool.coverage.run] - plugins = [ - 'django_coverage_plugin', - ] + [run] + plugins = django_coverage_plugin + + [django_coverage_plugin] + exclude_blocks = ["my_tag.+"] - [tool.coverage.django_coverage_plugin] - template_extensions = 'html, txt, tex, email' Caveats ~~~~~~~ diff --git a/django_coverage_plugin/plugin.py b/django_coverage_plugin/plugin.py index 158ee9a..51a92e2 100644 --- a/django_coverage_plugin/plugin.py +++ b/django_coverage_plugin/plugin.py @@ -6,6 +6,8 @@ import os.path import re +from coverage.misc import join_regex + try: from coverage.exceptions import NoSource except ImportError: @@ -149,6 +151,8 @@ class DjangoTemplatePlugin( def __init__(self, options): extensions = options.get("template_extensions", "html,htm,txt") self.extensions = [e.strip() for e in extensions.split(",")] + + self.exclude_blocks = options.get("exclude_blocks") self.debug_checked = False @@ -184,7 +188,7 @@ def file_tracer(self, filename): return None def file_reporter(self, filename): - return FileReporter(filename) + return FileReporter(filename, self.exclude_blocks) def find_executable_files(self, src_dir): # We're only interested in files that look like reasonable HTML @@ -292,10 +296,16 @@ def get_line_map(self, filename): class FileReporter(coverage.plugin.FileReporter): - def __init__(self, filename): + def __init__(self, filename, exclude_blocks): super().__init__(filename) # TODO: html filenames are absolute. + if exclude_blocks: + self.exclude_blocks_regex = re.compile(join_regex(exclude_blocks)) + else: + self.exclude_blocks_regex = None + self._excluded = set() + self._source = None def source(self): @@ -351,6 +361,12 @@ def lines(self): # In an inheriting template, ignore all tags outside of # blocks. continue + + # Ignore any block token content that has been explcitly + # excluded in config + if self.exclude_block_token(token): + self._excluded.add(token.lineno) + continue if token.contents == "comment": comment = True @@ -389,6 +405,12 @@ def lines(self): return source_lines + def excluded_lines(self): + return self._excluded + + def exclude_block_token(self, token): + if self.exclude_blocks_regex: + return self.exclude_blocks_regex.search(token.contents) def running_sum(seq): total = 0 diff --git a/tests/test_simple.py b/tests/test_simple.py index a09ecb7..1034b25 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -264,3 +264,30 @@ def test_with_branch_enabled(self): ) self.assertEqual(text, 'Hello\nWorld\n\nGoodbye') self.assert_analysis([1, 2, 3, 4]) + + +class ExcludeTest(DjangoPluginTestCase): + """Tests of excluding block tokens by regex.""" + + def test_exclude_block(self): + self.make_template("""\ + First + {% with foo='bar' %} + {{ foo }} + {% endwith %} + Last + """) + text = self.run_django_coverage() + self.assertEqual(text, "First\n\n bar\n\nLast\n") + self.assert_analysis([1, 2, 3, 5]) + + self.make_file(".coveragerc", """\ + [run] + plugins = django_coverage_plugin + [django_coverage_plugin] + exclude_blocks = [".+foo.+"] + """) + + text = self.run_django_coverage() + self.assertEqual(text, "First\n\n bar\n\nLast\n") + self.assert_analysis([1, 3, 5])