From 7f5b58d0ffe09901146f4395448d8ef8c034b36a Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 11:49:43 +0100 Subject: [PATCH 01/12] modernize packaging --- pyproject.toml | 53 +++++++++++++++++++++++++++++++++++--------- requirements-dev.txt | 1 - setup.cfg | 45 ------------------------------------- setup.py | 8 ++----- 4 files changed, 45 insertions(+), 62 deletions(-) delete mode 100644 requirements-dev.txt delete mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index 551e212..239862a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,48 @@ [build-system] -requires = [ - "setuptools>=45", - "setuptools_scm[toml]>=6.2", - "wheel", -] +requires = ["setuptools>=61.2", "wheel"] build-backend = "setuptools.build_meta" -[tool.setuptools_scm] +[project] +name = "sphinxcontrib-email" +version = "0.3.6" +description = "Sphinx email obfuscation extension" +python_requires = ">=3.7" +dependencies = ["sphinx", "lxml>=4.5.2"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Environment :: Web Environment", + "Framework :: Sphinx :: Extension", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Documentation :: Sphinx", + "Topic :: Utilities", +] + +[[project.authors]] +name = "Christian Knittl-Frank" +email = "lcnittl@gmail.com" + +[project.license] +text = "BSD-3-Clause" + +[project.readme] +file = "README.rst" +content-type = "text/x-rst" + +[project.urls] +repository = "https://github.com/sphinx-contrib/email" -[tool.black] -target-version = ["py38", "py39", "py310"] +[project.optional-dependencies] +dev = ["nox", "pre-commit", "mypy"] -[tool.isort] -profile = "black" +[tool.setuptools] +include-package-data = false +license-files = ["LICENSE"] +packages = ["sphinxcontrib"] diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 416634f..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1 +0,0 @@ -pre-commit diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 69a3121..0000000 --- a/setup.cfg +++ /dev/null @@ -1,45 +0,0 @@ -[metadata] -name = sphinxcontrib_email -description = Sphinx email obfuscation extension -long_description = file: README.rst -long_description_content_type = text/x-rst -url = https://github.com/sphinx-contrib/email -author = Christian Knittl-Frank -author_email = lcnittl@gmail.com -license = BSD-3-Clause -license_file = LICENSE -license_files = LICENSE -classifiers = - Development Status :: 3 - Alpha - Environment :: Console - Environment :: Web Environment - Framework :: Sphinx :: Extension - Intended Audience :: Developers - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Topic :: Documentation :: Sphinx - Topic :: Utilities -download_url = http://pypi.python.org/pypi/sphinxcontrib-email -project_urls = - GitHub: repo = https://github.com/sphinx-contrib/email - GitHub: issues = https://github.com/sphinx-contrib/email/issues - -[options] -packages = find_namespace: -install_requires = - Sphinx>=1.8 - lxml>=4.5.2 -python_requires = >=3.7 -include_package_data = True -package_dir = - = src -platforms = any -zip_safe = False - -[options.packages.find] -where = src - -[aliases] -release = check -rs sdist bdist_wheel diff --git a/setup.py b/setup.py index 4fdfbbb..6068493 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,3 @@ -import setuptools +from setuptools import setup -if __name__ == "__main__": - setuptools.setup( - use_scm_version=True, - setup_requires="setuptools-scm>=4.1", - ) +setup() From 2fb95a5fccb98d0d562bfce5bde9ae3efc2cf98a Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 11:51:42 +0100 Subject: [PATCH 02/12] fix: pre-commit whhere inconsistent with lib content --- .pre-commit-config.yaml | 77 ++++++++--------------------------------- 1 file changed, 15 insertions(+), 62 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 410e266..2615847 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,67 +1,20 @@ +default_install_hook_types: [pre-commit] + repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + - repo: "https://github.com/psf/black" + rev: "22.3.0" hooks: - - id: check-case-conflict - - id: check-merge-conflict - - id: trailing-whitespace - args: [--markdown-linebreak-ext=md] - - id: end-of-file-fixer - - id: check-json - - id: check-toml - - id: check-yaml - - id: requirements-txt-fixer - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 + - id: black + stages: [commit] + + - repo: "https://github.com/pre-commit/mirrors-prettier" + rev: "v2.7.1" hooks: - id: prettier - - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.2.0 + stages: [commit] + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.0.215" hooks: - - id: setup-cfg-fmt - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 - hooks: - - id: pyupgrade - args: [--py37-plus] - ## <<< darker ## - ## - repo: https://github.com/akaihola/darker - ## rev: 1.4.0 - ## hooks: - ## - id: darker - ## args: ["--isort"] # TODO: Move to pyproject.toml - ## additional_dependencies: - ## - isort - ## === ## - - repo: https://github.com/PyCQA/isort - rev: 5.11.4 - hooks: - - id: isort - - repo: https://github.com/psf/black - rev: 22.12.0 - hooks: - - id: black # black-jupyter - ## >>> black and isort ## - - repo: https://github.com/PyCQA/bandit - rev: 1.7.4 - hooks: - - id: bandit - args: [--recursive, --quiet] - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 # E***, W***, F*** - additional_dependencies: - # - dlint # DUO*** - - flake8-2020 # YTT*** - - flake8-bugbear # B*** - - flake8-builtins # A*** - - flake8-comprehensions # C4** - - flake8-deprecated # D*** - - flake8-variables-names # VNE*** - - mccabe # C9** - - pep8-naming # N8** - # - repo: https://github.com/pre-commit/mirrors-mypy - # rev: v0.790 - # hooks: - # - id: mypy + - id: ruff + stages: [commit] From 577a52c5fd18253dc7a5d3ec218dbdbbac3fae70 Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 12:23:16 +0100 Subject: [PATCH 03/12] solve pre-commit issues --- pyproject.toml | 25 +++++++++++++++++++++++++ src/sphinxcontrib/email/__init__.py | 3 +++ src/sphinxcontrib/email/handlers.py | 6 ++++-- src/sphinxcontrib/email/roles.py | 14 ++++++++------ src/sphinxcontrib/email/utils.py | 15 +++++++++------ 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 239862a..856ce56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,3 +46,28 @@ dev = ["nox", "pre-commit", "mypy"] include-package-data = false license-files = ["LICENSE"] packages = ["sphinxcontrib"] + +[tool.ruff] +ignore-init-module-imports = true +fix = true +select = ["E", "F", "W", "I", "D", "RUF"] +line-length = 88 + +[tool.ruff.flake8-quotes] +docstring-quotes = "double" + +[tool.ruff.pydocstyle] +convention = "google" + +[tool.ruff.per-file-ignores] +"setup.py" = ["D100"] # nothing to see there + +[tool.coverage.run] +source = ["sphinxcontrib.email"] + +[tool.mypy] +scripts_are_modules = true +ignore_missing_imports = true +install_types = true +non_interactive = true +warn_redundant_casts = true diff --git a/src/sphinxcontrib/email/__init__.py b/src/sphinxcontrib/email/__init__.py index 4166ad8..954ad92 100644 --- a/src/sphinxcontrib/email/__init__.py +++ b/src/sphinxcontrib/email/__init__.py @@ -1,3 +1,5 @@ +"""Provide an email obfuscator for Sphinx-based documentation.""" + from typing import Any, Dict from sphinx.application import Sphinx @@ -20,6 +22,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: + """Setup email role parameters.""" app.add_config_value(name="email_automode", default=False, rebuild="env") app.connect(event="html-page-context", callback=html_page_context_handler) app.add_role(name="email", role=EmailRole()) diff --git a/src/sphinxcontrib/email/handlers.py b/src/sphinxcontrib/email/handlers.py index 9b39a25..3550c42 100644 --- a/src/sphinxcontrib/email/handlers.py +++ b/src/sphinxcontrib/email/handlers.py @@ -1,6 +1,8 @@ +"""Custom handlers for the email role.""" + from typing import Dict -import lxml.html # nosec # noqa DUO107 +import lxml.html # nosec from sphinx.application import Sphinx from sphinx.util import logging @@ -12,7 +14,7 @@ def html_page_context_handler( app: Sphinx, pagename: str, templatename: str, context: Dict, doctree: bool ): - """Search html for 'mailto' links and obfuscate them""" + """Search html for 'mailto' links and obfuscate them.""" if not app.config["email_automode"]: return if not doctree: diff --git a/src/sphinxcontrib/email/roles.py b/src/sphinxcontrib/email/roles.py index d77bdb4..a2015ed 100644 --- a/src/sphinxcontrib/email/roles.py +++ b/src/sphinxcontrib/email/roles.py @@ -1,3 +1,5 @@ +"""Email role.""" + import re from typing import List, Tuple @@ -12,13 +14,13 @@ class EmailRole(SphinxRole): - def run(self) -> Tuple[List[Node], List[system_message]]: - """Role to obfuscate e-mail addresses. + """Role to obfuscate e-mail addresses. - Handle addresses of the form - "name@domain.org" - "Name Surname " - """ + Handle addresses of the form "name@domain.org" and "Name Surname " + """ + + def run(self) -> Tuple[List[Node], List[system_message]]: + """Setup the role in the builder context.""" pattern = ( r"^(?:(?P.*?)\s*<)?(?P\b[-.\w]+@[-.\w]+\.[a-z]{2,4}\b)>?$" ) diff --git a/src/sphinxcontrib/email/utils.py b/src/sphinxcontrib/email/utils.py index ac14589..584705b 100644 --- a/src/sphinxcontrib/email/utils.py +++ b/src/sphinxcontrib/email/utils.py @@ -1,7 +1,9 @@ +"""Set of helpers to build the email role.""" + import re import textwrap import xml.sax.saxutils # nosec -from xml.etree import ElementTree as ET # nosec # noqa DUO107 +from xml.etree import ElementTree as ET # nosec from sphinx.util import logging @@ -9,7 +11,10 @@ class Obfuscator: + """Obfuscator for html output.""" + def __init__(self): + """Obfuscator for html output.""" self.rot_13_trans = str.maketrans( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm", @@ -26,7 +31,7 @@ def rot_13_encrypt(self, line: str) -> str: return line def xml_to_unesc_string(self, node: ET.Element) -> str: - """Return unescaped xml string""" + """Return unescaped xml string.""" text = xml.sax.saxutils.unescape( ET.tostring(node, encoding="unicode", method="html"), {"'": "'", """: '"'}, @@ -34,9 +39,7 @@ def xml_to_unesc_string(self, node: ET.Element) -> str: return text def js_obfuscated_text(self, text: str) -> str: - """ROT 13 encryption with embedded in Javascript code to decrypt in the - browser. - """ + """ROT 13 encryption embedded in Javascript code to decrypt in the browser.""" xml_node = ET.Element("script") xml_node.attrib["type"] = "text/javascript" js_script = textwrap.dedent( @@ -56,7 +59,7 @@ def js_obfuscated_text(self, text: str) -> str: return self.xml_to_unesc_string(xml_node) def js_obfuscated_mailto(self, email: str, displayname: str = None) -> str: - """ROT 13 encryption within an Anchor tag w/ a mailto: attribute""" + """ROT 13 encryption within an Anchor tag w/ a mailto: attribute.""" xml_node = ET.Element("a") xml_node.attrib["class"] = "reference external" xml_node.attrib["href"] = f"mailto:{email}" From 6dd535ec546ae3594d4f257ff946bf5de261fa1f Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 12:29:04 +0100 Subject: [PATCH 04/12] test: introduce nox --- .gitignore | 10 +++------ noxfile.py | 21 +++++++++++++++++++ pyproject.toml | 4 ++-- .../email/__init__.py | 0 .../email/handlers.py | 0 .../email/roles.py | 0 .../email/utils.py | 0 7 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 noxfile.py rename {src/sphinxcontrib => sphinxcontrib}/email/__init__.py (100%) rename {src/sphinxcontrib => sphinxcontrib}/email/handlers.py (100%) rename {src/sphinxcontrib => sphinxcontrib}/email/roles.py (100%) rename {src/sphinxcontrib => sphinxcontrib}/email/utils.py (100%) diff --git a/.gitignore b/.gitignore index 042c8f7..c394f6f 100644 --- a/.gitignore +++ b/.gitignore @@ -88,13 +88,6 @@ ipython_config.py # pyenv .python-version -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ @@ -135,4 +128,7 @@ dmypy.json # pytype static type analyzer .pytype/ +# ruff +.ruff_cache + # End of https://www.toptal.com/developers/gitignore/api/python diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..4c04aca --- /dev/null +++ b/noxfile.py @@ -0,0 +1,21 @@ +"""All the process that can be run using nox. + +The nox run are build in isolated environment that will be stored in .nox. to force the venv update, remove the .nox/xxx folder. +""" + +import nox + + +@nox.session(reuse_venv=True) +def lint(session): + """Apply the pre-commits.""" + session.install("pre-commit") + session.run("pre-commit", "run", "--a", *session.posargs) + + +@nox.session(reuse_venv=True) +def mypy(session): + """Run a mypy check of the lib.""" + session.install(".[dev]") + test_files = session.posargs or ["sphinxcontrib"] + session.run("mypy", *test_files) diff --git a/pyproject.toml b/pyproject.toml index 856ce56..06e6f63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "sphinxcontrib-email" version = "0.3.6" description = "Sphinx email obfuscation extension" -python_requires = ">=3.7" +requires-python = ">=3.7" dependencies = ["sphinx", "lxml>=4.5.2"] classifiers = [ "Development Status :: 3 - Alpha", @@ -51,7 +51,7 @@ packages = ["sphinxcontrib"] ignore-init-module-imports = true fix = true select = ["E", "F", "W", "I", "D", "RUF"] -line-length = 88 +ignore = ["E501"] # line too long | Black take care of it [tool.ruff.flake8-quotes] docstring-quotes = "double" diff --git a/src/sphinxcontrib/email/__init__.py b/sphinxcontrib/email/__init__.py similarity index 100% rename from src/sphinxcontrib/email/__init__.py rename to sphinxcontrib/email/__init__.py diff --git a/src/sphinxcontrib/email/handlers.py b/sphinxcontrib/email/handlers.py similarity index 100% rename from src/sphinxcontrib/email/handlers.py rename to sphinxcontrib/email/handlers.py diff --git a/src/sphinxcontrib/email/roles.py b/sphinxcontrib/email/roles.py similarity index 100% rename from src/sphinxcontrib/email/roles.py rename to sphinxcontrib/email/roles.py diff --git a/src/sphinxcontrib/email/utils.py b/sphinxcontrib/email/utils.py similarity index 100% rename from src/sphinxcontrib/email/utils.py rename to sphinxcontrib/email/utils.py From 5852197202a97b7ce7e7e1dc9635015601619962 Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 12:52:46 +0100 Subject: [PATCH 05/12] fix: solve mypy issues --- sphinxcontrib/email/__init__.py | 21 ++++++--------------- sphinxcontrib/email/handlers.py | 6 ++---- sphinxcontrib/email/roles.py | 16 ++++++++-------- sphinxcontrib/email/utils.py | 6 +++--- 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/sphinxcontrib/email/__init__.py b/sphinxcontrib/email/__init__.py index 954ad92..60e62f1 100644 --- a/sphinxcontrib/email/__init__.py +++ b/sphinxcontrib/email/__init__.py @@ -1,22 +1,13 @@ """Provide an email obfuscator for Sphinx-based documentation.""" +from importlib import metadata from typing import Any, Dict from sphinx.application import Sphinx from sphinx.util import logging from .handlers import html_page_context_handler -from .roles import EmailRole - -try: - from importlib import metadata -except ImportError: - import importlib_metadata as metadata - -try: - __version__ = metadata.version("sphinxcontrib-email") -except metadata.PackageNotFoundError: - pass +from .roles import Email logger = logging.getLogger("sphinxcontrib-email") @@ -25,10 +16,10 @@ def setup(app: Sphinx) -> Dict[str, Any]: """Setup email role parameters.""" app.add_config_value(name="email_automode", default=False, rebuild="env") app.connect(event="html-page-context", callback=html_page_context_handler) - app.add_role(name="email", role=EmailRole()) + app.add_role(name="email", role=Email) - metadata = { - "version": ".".join(__version__.split(".")[:3]), + return { + "version": metadata.version("sphinxcontrib-email"), "parallel_read_safe": True, + "parallel_write_safe": True, } - return metadata diff --git a/sphinxcontrib/email/handlers.py b/sphinxcontrib/email/handlers.py index 3550c42..1a408fe 100644 --- a/sphinxcontrib/email/handlers.py +++ b/sphinxcontrib/email/handlers.py @@ -2,7 +2,7 @@ from typing import Dict -import lxml.html # nosec +import lxml.html from sphinx.application import Sphinx from sphinx.util import logging @@ -15,9 +15,7 @@ def html_page_context_handler( app: Sphinx, pagename: str, templatename: str, context: Dict, doctree: bool ): """Search html for 'mailto' links and obfuscate them.""" - if not app.config["email_automode"]: - return - if not doctree: + if not app.config["email_automode"] or not doctree: return tree = lxml.html.fragment_fromstring(context["body"], create_parent="body") diff --git a/sphinxcontrib/email/roles.py b/sphinxcontrib/email/roles.py index a2015ed..3a5bec9 100644 --- a/sphinxcontrib/email/roles.py +++ b/sphinxcontrib/email/roles.py @@ -4,7 +4,7 @@ from typing import List, Tuple from docutils import nodes -from docutils.nodes import Node, system_message +from docutils.nodes import Node from sphinx.util import logging from sphinx.util.docutils import SphinxRole @@ -12,19 +12,19 @@ logger = logging.getLogger(f"sphinxcontrib-email.{__name__}") +PATTERN = r"^(?:(?P.*?)\s*<)?(?P\b[-.\w]+@[-.\w]+\.[a-z]{2,4}\b)>?$" +"email adress pattern" -class EmailRole(SphinxRole): + +class Email(SphinxRole): """Role to obfuscate e-mail addresses. Handle addresses of the form "name@domain.org" and "Name Surname " """ - def run(self) -> Tuple[List[Node], List[system_message]]: + def run(self) -> Tuple[List[Node], List[str]]: """Setup the role in the builder context.""" - pattern = ( - r"^(?:(?P.*?)\s*<)?(?P\b[-.\w]+@[-.\w]+\.[a-z]{2,4}\b)>?$" - ) - match = re.search(pattern, self.text) + match = re.search(PATTERN, self.text) if not match: return [], [] data = match.groupdict() @@ -32,5 +32,5 @@ def run(self) -> Tuple[List[Node], List[system_message]]: obfuscated = Obfuscator().js_obfuscated_mailto( email=data["email"], displayname=data["name"] ) - node = nodes.raw("", obfuscated, format="html") + node = nodes.raw("", obfuscated, format="html") # type: ignore return [node], [] diff --git a/sphinxcontrib/email/utils.py b/sphinxcontrib/email/utils.py index 584705b..56a2a54 100644 --- a/sphinxcontrib/email/utils.py +++ b/sphinxcontrib/email/utils.py @@ -2,8 +2,8 @@ import re import textwrap -import xml.sax.saxutils # nosec -from xml.etree import ElementTree as ET # nosec +import xml.sax.saxutils +from xml.etree import ElementTree as ET from sphinx.util import logging @@ -58,7 +58,7 @@ def js_obfuscated_text(self, text: str) -> str: return self.xml_to_unesc_string(xml_node) - def js_obfuscated_mailto(self, email: str, displayname: str = None) -> str: + def js_obfuscated_mailto(self, email: str, displayname: str = "") -> str: """ROT 13 encryption within an Anchor tag w/ a mailto: attribute.""" xml_node = ET.Element("a") xml_node.attrib["class"] = "reference external" From 22bdbb2cb78aee33e1c6fc5a4288c33310e46084 Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 13:35:54 +0100 Subject: [PATCH 06/12] refactor: remove unused files --- .flake8 | 7 ------- .prettierrc.yaml | 1 - 2 files changed, 8 deletions(-) delete mode 100644 .flake8 delete mode 100644 .prettierrc.yaml diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 414c491..0000000 --- a/.flake8 +++ /dev/null @@ -1,7 +0,0 @@ -[flake8] -max-line-length = 88 -extend-ignore = - E203 # "Whitespace before ':'" - not PEP-8 compliant - E501 # "Line too long (82 >= 79 characters)" -per-file-ignores = - __init__.py:F401 diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index ca09957..0000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1 +0,0 @@ -printWidth: 88 From aa3f5efc02c1e484702c68b59d96b18d4879417c Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 14:09:20 +0100 Subject: [PATCH 07/12] test: add 2 simple test for email role --- noxfile.py | 8 +++++++ pyproject.toml | 1 + sphinxcontrib/email/__init__.py | 2 +- sphinxcontrib/email/utils.py | 1 + tests/__init__.py | 1 + tests/conftest.py | 12 ++++++++++ tests/roots/test-email/conf.py | 5 +++++ tests/roots/test-email/index.rst | 7 ++++++ tests/roots/test-email/named_email.rst | 4 ++++ tests/roots/test-email/simple_email.rst | 4 ++++ tests/test_build.py | 29 +++++++++++++++++++++++++ tests/test_build/email.html | 12 ++++++++++ tests/test_build/named-email.html | 12 ++++++++++ 13 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/roots/test-email/conf.py create mode 100644 tests/roots/test-email/index.rst create mode 100644 tests/roots/test-email/named_email.rst create mode 100644 tests/roots/test-email/simple_email.rst create mode 100644 tests/test_build.py create mode 100644 tests/test_build/email.html create mode 100644 tests/test_build/named-email.html diff --git a/noxfile.py b/noxfile.py index 4c04aca..77547bf 100644 --- a/noxfile.py +++ b/noxfile.py @@ -19,3 +19,11 @@ def mypy(session): session.install(".[dev]") test_files = session.posargs or ["sphinxcontrib"] session.run("mypy", *test_files) + + +@nox.session(reuse_venv=True) +def test(session): + """Run all the test using the environment varialbe of the running machine.""" + session.install(".[test]") + test_files = session.posargs or ["tests"] + session.run("pytest", "--color=yes", "--cov", "--cov-report=html", *test_files) diff --git a/pyproject.toml b/pyproject.toml index 06e6f63..5f6dc67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ repository = "https://github.com/sphinx-contrib/email" [project.optional-dependencies] dev = ["nox", "pre-commit", "mypy"] +test = ["pytest", "beautifulsoup4", "pytest-regressions", "pytest-cov"] [tool.setuptools] include-package-data = false diff --git a/sphinxcontrib/email/__init__.py b/sphinxcontrib/email/__init__.py index 60e62f1..d6d6457 100644 --- a/sphinxcontrib/email/__init__.py +++ b/sphinxcontrib/email/__init__.py @@ -16,7 +16,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: """Setup email role parameters.""" app.add_config_value(name="email_automode", default=False, rebuild="env") app.connect(event="html-page-context", callback=html_page_context_handler) - app.add_role(name="email", role=Email) + app.add_role(name="email", role=Email()) return { "version": metadata.version("sphinxcontrib-email"), diff --git a/sphinxcontrib/email/utils.py b/sphinxcontrib/email/utils.py index 56a2a54..530f8b7 100644 --- a/sphinxcontrib/email/utils.py +++ b/sphinxcontrib/email/utils.py @@ -42,6 +42,7 @@ def js_obfuscated_text(self, text: str) -> str: """ROT 13 encryption embedded in Javascript code to decrypt in the browser.""" xml_node = ET.Element("script") xml_node.attrib["type"] = "text/javascript" + xml_node.attrib["class"] = "obfuscated-email" js_script = textwrap.dedent( """\ document.write( diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..190373f --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Make the test folder a package for coverage reports.""" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..db50b26 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,12 @@ +"""Pytest configuration.""" + +import pytest +from sphinx.testing.path import path + +pytest_plugins = "sphinx.testing.fixtures" + + +@pytest.fixture(scope="session") +def rootdir(): + """Get the root directory for the whole test session.""" + return path(__file__).parent.abspath() / "roots" diff --git a/tests/roots/test-email/conf.py b/tests/roots/test-email/conf.py new file mode 100644 index 0000000..b36a051 --- /dev/null +++ b/tests/roots/test-email/conf.py @@ -0,0 +1,5 @@ +"""Configuration file for the Sphinx documentation builder.""" + +extensions = ["sphinxcontrib.email"] +exclude_patterns = ["_build"] +html_theme = "basic" diff --git a/tests/roots/test-email/index.rst b/tests/roots/test-email/index.rst new file mode 100644 index 0000000..0ce2b23 --- /dev/null +++ b/tests/roots/test-email/index.rst @@ -0,0 +1,7 @@ +email +===== + +.. toctree:: + + simple_email + named_email \ No newline at end of file diff --git a/tests/roots/test-email/named_email.rst b/tests/roots/test-email/named_email.rst new file mode 100644 index 0000000..e44db41 --- /dev/null +++ b/tests/roots/test-email/named_email.rst @@ -0,0 +1,4 @@ +email +===== + +send an email to :email:`toto `. \ No newline at end of file diff --git a/tests/roots/test-email/simple_email.rst b/tests/roots/test-email/simple_email.rst new file mode 100644 index 0000000..cae56d6 --- /dev/null +++ b/tests/roots/test-email/simple_email.rst @@ -0,0 +1,4 @@ +email +===== + +send an email to :email:`toto@gmail.com`. \ No newline at end of file diff --git a/tests/test_build.py b/tests/test_build.py new file mode 100644 index 0000000..c771ccc --- /dev/null +++ b/tests/test_build.py @@ -0,0 +1,29 @@ +"""Test sphinxcontrib.video extension.""" + + +import pytest +from bs4 import BeautifulSoup, formatter + +fmt = formatter.HTMLFormatter(indent=2, void_element_close_prefix=" /") + + +@pytest.mark.sphinx(testroot="email") +def test_email(app, status, warning, file_regression): + """Build a video without options.""" + app.builder.build_all() + + raw_html = (app.outdir / "simple_email.html").read_text(encoding="utf8") + html = BeautifulSoup(raw_html, "html.parser") + script = html.select("script.obfuscated-email")[0].prettify(formatter=fmt) + file_regression.check(script, basename="email", extension=".html") + + +@pytest.mark.sphinx(testroot="email") +def test_named_email(app, status, warning, file_regression): + """Build a video without options.""" + app.builder.build_all() + + raw_html = (app.outdir / "named_email.html").read_text(encoding="utf8") + html = BeautifulSoup(raw_html, "html.parser") + script = html.select("script.obfuscated-email")[0].prettify(formatter=fmt) + file_regression.check(script, basename="named-email", extension=".html") diff --git a/tests/test_build/email.html b/tests/test_build/email.html new file mode 100644 index 0000000..b4a3ebf --- /dev/null +++ b/tests/test_build/email.html @@ -0,0 +1,12 @@ + diff --git a/tests/test_build/named-email.html b/tests/test_build/named-email.html new file mode 100644 index 0000000..292a7cd --- /dev/null +++ b/tests/test_build/named-email.html @@ -0,0 +1,12 @@ + From 32d206b0eef10d25c355abfbc6683196bb298712 Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 14:12:15 +0100 Subject: [PATCH 08/12] build: add unit testing --- .github/workflows/unit.yml | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/unit.yml diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml new file mode 100644 index 0000000..77e2292 --- /dev/null +++ b/.github/workflows/unit.yml @@ -0,0 +1,48 @@ +name: Unit tests + +on: + - push + - pull-request + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + - uses: pre-commit/action@v3.0.0 + + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: pip install .[test,dev] + + - name: Check static typing + run: mypy sphinxcontrib + + - name: test with pytest + run: pytest --color=yes --cov --cov-report=xml tests + + #- name: coverage + # run: coverage xml + # + #- name: codecov + # uses: codecov/codecov-action@v3 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + # verbose: true From cedb85812e1bd0afbcfcd5f76dfd7c13b11056b8 Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 14:32:30 +0100 Subject: [PATCH 09/12] setup a __version__ --- sphinxcontrib/email/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinxcontrib/email/__init__.py b/sphinxcontrib/email/__init__.py index d6d6457..ddfbf49 100644 --- a/sphinxcontrib/email/__init__.py +++ b/sphinxcontrib/email/__init__.py @@ -9,6 +9,8 @@ from .handlers import html_page_context_handler from .roles import Email +__version__ = metadata.version("sphinxcontrib-email") + logger = logging.getLogger("sphinxcontrib-email") @@ -19,7 +21,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_role(name="email", role=Email()) return { - "version": metadata.version("sphinxcontrib-email"), + "version": __version__, "parallel_read_safe": True, "parallel_write_safe": True, } From 76d1577ca6e57c608d2634cfb887e349fda227d7 Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 14:36:29 +0100 Subject: [PATCH 10/12] typo --- .github/workflows/unit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 77e2292..5c11aa1 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -2,7 +2,7 @@ name: Unit tests on: - push - - pull-request + - pull_request jobs: lint: From e11dab6138cb597ed7ac52d855ab4fb9d6ea00ac Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 16:51:28 +0100 Subject: [PATCH 11/12] fix: don't prettify generated content from the test --- .pre-commit-config.yaml | 1 + sphinxcontrib/email/utils.py | 21 +++++++++++---------- test.txt | 12 ++++++++++++ tests/test_build.py | 2 ++ tests/test_build/email.html | 18 +++++++++--------- tests/test_build/named-email.html | 18 +++++++++--------- 6 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 test.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2615847..dd900e8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,7 @@ repos: hooks: - id: prettier stages: [commit] + exclude: tests\/test_build\/.+\.html$ - repo: https://github.com/charliermarsh/ruff-pre-commit rev: "v0.0.215" diff --git a/sphinxcontrib/email/utils.py b/sphinxcontrib/email/utils.py index 530f8b7..c313c63 100644 --- a/sphinxcontrib/email/utils.py +++ b/sphinxcontrib/email/utils.py @@ -23,7 +23,6 @@ def __init__(self): def rot_13_encrypt(self, line: str) -> str: """Rotate 13 encryption.""" line = line.translate(self.rot_13_trans) - line = re.sub(r"(?=[\"])", r"\\", line) line = re.sub("\n", r"\n", line) line = re.sub(r"@", r"\\100", line) line = re.sub(r"\.", r"\\056", line) @@ -44,16 +43,18 @@ def js_obfuscated_text(self, text: str) -> str: xml_node.attrib["type"] = "text/javascript" xml_node.attrib["class"] = "obfuscated-email" js_script = textwrap.dedent( - """\ + """ document.write( - "{text}".replace(/[a-zA-Z]/g, - function(c){{ - return String.fromCharCode( - (c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26 - ); - }} - ) - );""" + '{text}'.replace( + /[a-zA-Z]/g, + function (c) {{ + return String.fromCharCode( + (c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26 + ); + }} + ) + ); + """ ) xml_node.text = js_script.format(text=self.rot_13_encrypt(text)) diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..e743ccf --- /dev/null +++ b/test.txt @@ -0,0 +1,12 @@ + diff --git a/tests/test_build.py b/tests/test_build.py index c771ccc..e0c00b7 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,5 +1,6 @@ """Test sphinxcontrib.video extension.""" +from pathlib import Path import pytest from bs4 import BeautifulSoup, formatter @@ -15,6 +16,7 @@ def test_email(app, status, warning, file_regression): raw_html = (app.outdir / "simple_email.html").read_text(encoding="utf8") html = BeautifulSoup(raw_html, "html.parser") script = html.select("script.obfuscated-email")[0].prettify(formatter=fmt) + Path("test.txt").write_text(script) file_regression.check(script, basename="email", extension=".html") diff --git a/tests/test_build/email.html b/tests/test_build/email.html index b4a3ebf..e743ccf 100644 --- a/tests/test_build/email.html +++ b/tests/test_build/email.html @@ -1,12 +1,12 @@ diff --git a/tests/test_build/named-email.html b/tests/test_build/named-email.html index 292a7cd..9031775 100644 --- a/tests/test_build/named-email.html +++ b/tests/test_build/named-email.html @@ -1,12 +1,12 @@ From 8d4bbf5acd9544ba4477a1e0239ac2a97c849080 Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Thu, 23 Feb 2023 16:55:46 +0100 Subject: [PATCH 12/12] support for python 3.7 --- pyproject.toml | 2 +- sphinxcontrib/email/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5f6dc67..9bec45c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "sphinxcontrib-email" version = "0.3.6" description = "Sphinx email obfuscation extension" requires-python = ">=3.7" -dependencies = ["sphinx", "lxml>=4.5.2"] +dependencies = ["sphinx", "lxml>=4.5.2", "importlib_metadata"] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", diff --git a/sphinxcontrib/email/__init__.py b/sphinxcontrib/email/__init__.py index ddfbf49..f2cd2c3 100644 --- a/sphinxcontrib/email/__init__.py +++ b/sphinxcontrib/email/__init__.py @@ -1,15 +1,15 @@ """Provide an email obfuscator for Sphinx-based documentation.""" -from importlib import metadata from typing import Any, Dict +from importlib_metadata import version from sphinx.application import Sphinx from sphinx.util import logging from .handlers import html_page_context_handler from .roles import Email -__version__ = metadata.version("sphinxcontrib-email") +__version__ = version("sphinxcontrib-email") logger = logging.getLogger("sphinxcontrib-email")