"""
https://github.com/barseghyanartur/sphinx-no-pragma/
"""
import re
from copy import deepcopy
from docutils import nodes
from sphinx.directives.code import LiteralInclude
__title__ = "sphinx-no-pragma"
__version__ = "0.1.3"
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2023-2025 Artur Barseghyan"
__license__ = "MIT"
__all__ = (
"DEFAULT_IGNORE_COMMENTS_ENDINGS",
"NoPragmaLiteralInclude",
"setup",
)
DEFAULT_IGNORE_COMMENTS_ENDINGS = [
"# type: ignore",
"# noqa",
"# pragma: no cover",
"# pragma: no branch",
"# fmt: off",
"# fmt: on",
"# fmt: skip",
"# yapf: disable",
"# yapf: enable",
"# pylint: disable",
"# pylint: enable",
"# flake8: noqa",
"# noinspection",
"# pragma: allowlist secret",
"# pragma: NOSONAR",
]
[docs]
class NoPragmaLiteralInclude(LiteralInclude):
[docs]
def run(self):
# Retrieve the global configuration set in conf.py
config = self.state.document.settings.env.config
# Default ignore endings
ignore_endings = deepcopy(config.ignore_comments_endings)
if not isinstance(ignore_endings, list):
ignore_endings = [ignore_endings]
# Additional user-defined ignore endings
user_ignore_endings = deepcopy(config.user_ignore_comments_endings)
if not isinstance(user_ignore_endings, list):
user_ignore_endings = [user_ignore_endings]
# Merge the two
ignore_endings.extend(user_ignore_endings)
# Get the original content generated by LiteralInclude
original_content = super().run()
new_content = []
for node in original_content:
if isinstance(node, nodes.literal_block):
# Modify lines by removing specified endings
lines = node.rawsource.splitlines()
filtered_lines = [
self.remove_endings(line, ignore_endings) for line in lines
]
# Update the node content
node.rawsource = "\n".join(filtered_lines)
node[:] = [nodes.Text("\n".join(filtered_lines))]
new_content.append(node)
return new_content
[docs]
def remove_endings(self, line, endings):
"""Remove from the line any trailing ignore markers.
For each ending provided, this function uses a regex pattern to match
the literal ending, optionally followed by a colon and error codes,
and any extra whitespace until the end of the line.
It repeatedly removes the matched pattern for each ending.
"""
for ending in endings:
# Build a regex pattern: escape the given ending and allow an
# optional colon and error codes, plus trailing whitespace until
# the end of the line.
pattern = re.escape(ending) + r"(?:\s*[:=]\s*\S+|\s+\S+)?\s*$"
# Keep removing the marker until no match is found.
while re.search(pattern, line):
line = re.sub(pattern, "", line).rstrip()
return line
[docs]
def setup(app):
app.add_directive("literalinclude", NoPragmaLiteralInclude, override=True)
app.add_config_value(
"ignore_comments_endings",
DEFAULT_IGNORE_COMMENTS_ENDINGS,
"env",
)
app.add_config_value("user_ignore_comments_endings", [], "env")
return {
"version": "0.1",
"parallel_read_safe": True,
"parallel_write_safe": True,
}