diff --git a/README.rst b/README.rst index d6ef68f..559269c 100644 --- a/README.rst +++ b/README.rst @@ -97,15 +97,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 13e9c41..5603cb2 100644 --- a/django_coverage_plugin/plugin.py +++ b/django_coverage_plugin/plugin.py @@ -10,6 +10,7 @@ import django import django.template from coverage.exceptions import NoSource +from coverage.misc import join_regex from django.template.base import Lexer, NodeList, Template, TextNode, TokenType from django.template.defaulttags import VerbatimNode from django.templatetags.i18n import BlockTranslateNode @@ -117,6 +118,10 @@ 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.exclude_blocks = options.get("exclude_blocks") + self.debug_checked = False self.django_template_dir = os.path.normcase(os.path.realpath( @@ -151,7 +156,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 @@ -259,10 +264,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): @@ -319,6 +330,18 @@ def lines(self): # 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 + + # 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 if token.contents.startswith("end"): @@ -356,6 +379,13 @@ 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 c4e326d..ccb3e84 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])