From 5c56b6e1bbfececca594567e9a64eb5f06f3137b Mon Sep 17 00:00:00 2001 From: hannah Date: Wed, 13 Dec 2023 02:27:10 -0500 Subject: [PATCH] tags stashed on env and using arguments, which normalizes spaces --- src/sphinx_tags/__init__.py | 61 +++++++++--------------- test/conftest.py | 13 +++-- test/outputs/general/_tags/tag-4.txt | 4 +- test/outputs/general/_tags/tagsindex.txt | 2 +- test/outputs/general/index.txt | 2 +- test/sources/test-badges/conf.py | 2 +- test/test_badges.py | 23 ++++++--- test/test_general_tags.py | 3 +- 8 files changed, 53 insertions(+), 57 deletions(-) diff --git a/src/sphinx_tags/__init__.py b/src/sphinx_tags/__init__.py index 0414f2c..793ff28 100644 --- a/src/sphinx_tags/__init__.py +++ b/src/sphinx_tags/__init__.py @@ -3,6 +3,7 @@ """ import os import re +from collections import defaultdict from fnmatch import fnmatch from pathlib import Path from typing import List @@ -36,9 +37,8 @@ class TagLinks(SphinxDirective): separator = "," def run(self): - # Undo splitting args by whitespace, and use our own separator (to support tags with spaces) - tags = " ".join(self.arguments).split(self.separator) - tags = [t.strip() for t in tags] + tagline = " ".join(self.arguments).split(self.separator) + tags = [tag.strip() for tag in tagline] tag_dir = Path(self.env.app.srcdir) / self.env.app.config.tags_output_dir result = nodes.paragraph() @@ -46,6 +46,9 @@ def run(self): result += nodes.inline(text=f"{self.env.app.config.tags_intro_text} ") count = 0 + current_doc_dir = Path(self.env.doc2path(self.env.docname)).parent + relative_tag_dir = Path(os.path.relpath(tag_dir, current_doc_dir)) + for tag in tags: count += 1 # We want the link to be the path to the _tags folder, relative to @@ -56,10 +59,8 @@ def run(self): # - subfolder # | # - current_doc_path - current_doc_dir = Path(self.env.doc2path(self.env.docname)).parent - relative_tag_dir = Path(os.path.relpath(tag_dir, current_doc_dir)) - file_basename = _normalize_tag(tag) + file_basename = _normalize_tag(tag) if self.env.app.config.tags_create_badges: result += self._get_badge_node(tag, file_basename, relative_tag_dir) tag_separator = " " @@ -68,6 +69,10 @@ def run(self): tag_separator = f"{self.separator} " if not count == len(tags): result += nodes.inline(text=tag_separator) + + # register tags to global metadata for document + self.env.metadata[self.env.docname]["tags"] = tags + return [result] def _get_plaintext_node( @@ -193,30 +198,11 @@ def create_file( class Entry: - """Extracted info from source file (*.rst/*.md/*.ipynb)""" + """Tags to pages map""" - def __init__(self, entrypath: Path): + def __init__(self, entrypath: Path, tags: list): self.filepath = entrypath - self.lines = self.filepath.read_text(encoding="utf8").split("\n") - if self.filepath.suffix == ".rst": - tagstart = ".. tags::" - tagend = "" - elif self.filepath.suffix == ".md": - tagstart = "```{tags}" - tagend = "```" - elif self.filepath.suffix == ".ipynb": - tagstart = '".. tags::' - tagend = '"' - else: - raise ValueError( - "Unknown file extension. Currently, only .rst, .md .ipynb are supported." - ) - tagline = [line for line in self.lines if tagstart in line] - self.tags = [] - if tagline: - tagline = tagline[0].replace(tagstart, "").rstrip(tagend) - self.tags = tagline.split(",") - self.tags = [tag.strip() for tag in self.tags] + self.tags = tags def assign_to_tags(self, tag_dict): """Append ourself to tags""" @@ -245,6 +231,7 @@ def tagpage(tags, outdir, title, extension, tags_index_head): This page contains a list of all available tags. """ + tags = list(tags.values()) if "md" in extension: @@ -294,24 +281,18 @@ def assign_entries(app): pages = [] tags = {} - # Get document paths in the project that match specified file extensions - doc_paths = get_matching_files( - app.srcdir, - include_patterns=[f"**.{extension}" for extension in app.config.tags_extension], - exclude_patterns=app.config.exclude_patterns, - ) - - for path in doc_paths: - entry = Entry(Path(app.srcdir) / path) + for docname in app.env.found_docs: + doctags = app.env.metadata[docname].get("tags", None) + if doctags is None: + continue # skip if no tags + entry = Entry(app.env.doc2path(docname), doctags) entry.assign_to_tags(tags) pages.append(entry) - return tags, pages def update_tags(app): """Update tags according to pages found""" - if app.config.tags_create_tags: tags_output_dir = Path(app.config.tags_output_dir) @@ -324,6 +305,7 @@ def update_tags(app): # Create pages for each tag tags, pages = assign_entries(app) + for tag in tags.values(): tag.create_file( [item for item in pages if tag.name in item.tags], @@ -386,4 +368,5 @@ def setup(app): "version": __version__, "parallel_read_safe": True, "parallel_write_safe": True, + "env_version": 1, } diff --git a/test/conftest.py b/test/conftest.py index 0812e35..55b2c2b 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -15,7 +15,6 @@ import pytest import sphinx.testing -import sphinx.testing.path collect_ignore = ["sources", "outputs"] pytest_plugins = "sphinx.testing.fixtures" @@ -31,11 +30,15 @@ def rootdir(): resolved and copied as files. """ - def copytree(src, dest): - shutil.copytree(src, dest, symlinks=True) + # patch copytree into Path objects to have sphinx 5 compatibility and + class PatchedPath(type(Path())): + def __new__(cls, *pathsegments): + return super().__new__(cls, *pathsegments) - with patch.object(sphinx.testing.path.path, "copytree", copytree): - yield sphinx.testing.path.path(SOURCE_ROOT_DIR) + def copytree(src, dest): + shutil.copytree(src, dest, symlinks=True) + + yield PatchedPath(SOURCE_ROOT_DIR) @pytest.fixture(scope="session", autouse=True) diff --git a/test/outputs/general/_tags/tag-4.txt b/test/outputs/general/_tags/tag-4.txt index a44fdac..065580c 100644 --- a/test/outputs/general/_tags/tag-4.txt +++ b/test/outputs/general/_tags/tag-4.txt @@ -1,5 +1,5 @@ -My tags: [{(tag 4)}] -********************** +My tags: [{(tag 4)}] +******************** With this tag diff --git a/test/outputs/general/_tags/tagsindex.txt b/test/outputs/general/_tags/tagsindex.txt index 968e8bb..0672d1e 100644 --- a/test/outputs/general/_tags/tagsindex.txt +++ b/test/outputs/general/_tags/tagsindex.txt @@ -5,7 +5,7 @@ Tags overview Tags ^^^^ -* [{(tag 4)}] (1) +* [{(tag 4)}] (1) * tag 3 (2) diff --git a/test/outputs/general/index.txt b/test/outputs/general/index.txt index 90b0f1d..faf7abc 100644 --- a/test/outputs/general/index.txt +++ b/test/outputs/general/index.txt @@ -13,7 +13,7 @@ Test document * Tags overview - * [{(tag 4)}] (1) + * [{(tag 4)}] (1) * Page 1 diff --git a/test/sources/test-badges/conf.py b/test/sources/test-badges/conf.py index 664af23..7a217c2 100644 --- a/test/sources/test-badges/conf.py +++ b/test/sources/test-badges/conf.py @@ -1,4 +1,4 @@ -extensions = ["sphinx_design", "sphinx_tags", "myst_parser"] +extensions = ["sphinx_tags", "myst_parser", "sphinx_design"] tags_create_tags = True tags_extension = ["md"] tags_create_badges = True diff --git a/test/test_badges.py b/test/test_badges.py index 850e3e8..3f53508 100644 --- a/test/test_badges.py +++ b/test/test_badges.py @@ -11,11 +11,17 @@ EXPECTED_CLASSES = { "tag-1": "sd-bg-primary", "tag-2": "sd-bg-secondary", - "prefix-tag-3": "sd-bg-info", - "tag-4": "sd-bg-dark", + "prefix:tag-3": "sd-bg-info", + "tag 4": "sd-bg-dark", } +@pytest.mark.sphinx("html", testroot="badges") +def test_build(app: SphinxTestApp, status: StringIO, warning: StringIO): + app.build() + assert "build succeeded" in status.getvalue() + + @pytest.mark.sphinx("html", testroot="badges") def test_badges(app: SphinxTestApp, status: StringIO, warning: StringIO): """Parse output HTML for a page with badges, find badge links, and check for CSS classes for @@ -27,11 +33,14 @@ def test_badges(app: SphinxTestApp, status: StringIO, warning: StringIO): build_dir = Path(app.srcdir) / "_build" / "html" page_1 = (build_dir / "page_1.html").read_text() + assert page_1 is not None soup = BeautifulSoup(page_1, "html.parser") # print(soup.prettify()) + assert soup is not None + print(soup) + badge_links = soup.find_all("span", class_="sd-badge") + print("badge: ", badge_links) - badge_links = soup.find_all("a", class_="sd-badge") - classes_by_tag = {Path(a.get("href")).stem: a.get("class") for a in badge_links} - - for tag, cls in EXPECTED_CLASSES.items(): - assert cls in classes_by_tag[tag] + for (tag, class_), span in zip(EXPECTED_CLASSES.items(), badge_links): + assert tag in span.text # hard to test tag 4 b/c of the icon + assert class_ in span["class"] diff --git a/test/test_general_tags.py b/test/test_general_tags.py index ff0e3e5..5ba72bd 100644 --- a/test/test_general_tags.py +++ b/test/test_general_tags.py @@ -19,6 +19,7 @@ def run_all_formats(): pytest.param(marks=pytest.mark.sphinx("text", testroot="symlink")), pytest.param(marks=pytest.mark.sphinx("text", testroot="ipynb")), ], + ids=["myst", "rst", "symlink", "ipynb"], ) @@ -29,7 +30,7 @@ def test_build(app: SphinxTestApp, status: StringIO, warning: StringIO): # Build with notebooks and text output results in spurious errors "File Not Found: _tags/*.html" # For all other formats, ensure no warnings are raised - if not app.srcdir.endswith("ipynb"): + if not str(app.srcdir).endswith("ipynb"): assert not warning.getvalue().strip()