From a1d16f8c7e52611867ca598297cd8ec08e950e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Sat, 27 Jan 2024 12:59:01 -0300 Subject: [PATCH 1/5] Fix pickup of tags in code-block directive --- docs/quickstart.rst | 2 +- src/sphinx_tags/__init__.py | 71 ++++++++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9549bf2..a0e3bc0 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -3,7 +3,7 @@ Quickstart The ``sphinx-tags`` package enables the use of blog-style tags with Sphinx. -.. tags:: tag documentation, tag installation +.. tags:: tag documentation, tag installation Tags are created using the custom directive ``.. tags::`` with the tag titles as arguments. diff --git a/src/sphinx_tags/__init__.py b/src/sphinx_tags/__init__.py index 6c9ca5c..7fe2d49 100644 --- a/src/sphinx_tags/__init__.py +++ b/src/sphinx_tags/__init__.py @@ -9,6 +9,8 @@ from typing import List from docutils import nodes +from docutils.parsers.rst import Parser +from docutils import frontend, utils from sphinx.errors import ExtensionError from sphinx.util.docutils import SphinxDirective from sphinx.util.logging import getLogger @@ -49,7 +51,6 @@ class TagLinks(SphinxDirective): final_argument_whitespace = True has_content = True final_argument_whitespace = True - # Custom attributes separator = "," def run(self): @@ -75,12 +76,22 @@ def run(self): # (can happen after _normalize_tag()) page_tags = list(filter(None, page_tags)) + # Leftover code from previous attempt: + #settings = frontend.get_default_settings(Parser) + #document = utils.new_document("tags", settings) + #tags = Parser().parse(tagline, document) + #print(f"Tags: {tags}") + + tag_dir = Path(self.env.app.srcdir) / self.env.app.config.tags_output_dir result = nodes.paragraph() result["classes"] = ["tags"] result += nodes.inline(text=f"{self.env.app.config.tags_intro_text} ") count = 0 + # Parse the directive contents. + self.state.nested_parse(self.content, self.content_offset, result) + current_doc_dir = Path(self.env.doc2path(self.env.docname)).parent relative_tag_dir = Path(os.path.relpath(tag_dir, current_doc_dir)) @@ -159,6 +170,9 @@ def __init__(self, name): self.name = _normalize_display_tag(name) self.file_basename = _normalize_tag(name, dashes=True) + def __repr__(self): + return f"Tag({self.name}), {len(self.items)} items: {self.items}" + def create_file( self, items, @@ -283,14 +297,32 @@ def __init__(self, entrypath: Path): def assign_to_tags(self, tag_dict): """Append ourself to tags""" for tag in self.tags: - if tag not in tag_dict: - tag_dict[tag] = Tag(tag) - tag_dict[tag].items.append(self) + if tag: + if tag not in tag_dict: + tag_dict[tag] = Tag(tag) + tag_dict[tag].items.append(self) def relpath(self, root_dir) -> str: """Get this entry's path relative to the given root directory""" return Path(os.path.relpath(self.filepath, root_dir)).as_posix() +def read_tags(lines, tagstart, tagend): + """Read tags from a list of lines in a file. + + """ + tagline = [line for line in lines if tagstart in line] + # Custom attributes + separator = "," + + tags = [] + if tagline: + # TODO: This is matching the .. tags:: example inside a code-block + # in configuration.rst + tagline = tagline[0].replace(tagstart, "").rstrip(tagend) + tags = [" ".join(tag.strip().split()) for tag in tagline.split(separator)] + tags = [tag for tag in tags if tag != ""] + return tags + def _normalize_tag(tag: str, dashes: bool = False) -> str: """Normalize a tag name to use in output filenames and tag URLs. @@ -365,7 +397,15 @@ def tagpage(tags, outdir, title, extension, tags_index_head): def assign_entries(app): - """Assign all found entries to their tag.""" + """Assign all found entries to their tag. + + Returns + ------- + tags : dict + Dictionary of tags, keyed by tag name + pages : list + List of Entry objects + """ pages = [] tags = {} @@ -380,6 +420,10 @@ def assign_entries(app): entry = Entry(Path(app.srcdir) / path) entry.assign_to_tags(tags) pages.append(entry) + # docname is path without the file extension + # docname = path.split(".", 1)[0] + # register tags to global metadata for document + # app.env.metadata[docname]["tags"] = tags return tags, pages @@ -400,14 +444,15 @@ def update_tags(app): tags, pages = assign_entries(app) for tag in tags.values(): - tag.create_file( - [item for item in pages if tag.name in item.tags], - app.config.tags_extension, - tags_output_dir, - app.srcdir, - app.config.tags_page_title, - app.config.tags_page_header, - ) + if tag: + tag.create_file( + [item for item in pages if tag.name in item.tags], + app.config.tags_extension, + tags_output_dir, + app.srcdir, + app.config.tags_page_title, + app.config.tags_page_header, + ) # Create tags overview page tagpage( From 854fdcfe49e7993f9ea877227acbfabbe81bf7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Sat, 29 Jun 2024 18:23:24 -0300 Subject: [PATCH 2/5] Implemented domain --- docs/conf.py | 2 +- docs/configuration.rst | 2 + docs/index.rst | 2 +- src/sphinx_tags/__init__.py | 377 +++++++++++++++--------------------- 4 files changed, 162 insertions(+), 221 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 79a769c..5bce781 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,7 +36,7 @@ extensions = ["sphinx_design", "sphinx_tags", "nbsphinx", "myst_parser"] tags_create_tags = True -tags_create_badges = True +tags_create_badges = False # tags_output_dir = "_tags" # default tags_overview_title = "All tags" # default: "Tags overview" tags_extension = ["rst", "md", "ipynb"] # default: ["rst"] diff --git a/docs/configuration.rst b/docs/configuration.rst index fcc00de..91faed3 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -120,3 +120,5 @@ files so it doesn't get stuck in a loop. Example: sphinx-autobuild docs docs/_build/html --ignore '**/_tags/*' If you have set ``tags_output_dir`` to a different path, use that instead of ``_tags``. + +.. tags:: tag documentation \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index e07ede1..c21792c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,4 +26,4 @@ Check out the `list of projects that use this extension >>>>>> 7f17eec (Implemented domain) for tag in page_tags: count += 1 # We want the link to be the path to the _tags folder, relative to @@ -162,166 +163,130 @@ def _get_tag_color(self, tag: str) -> str: return "primary" -class Tag: - """A tag contains entries""" - - def __init__(self, name): - self.items = [] - self.name = _normalize_display_tag(name) - self.file_basename = _normalize_tag(name, dashes=True) - - def __repr__(self): - return f"Tag({self.name}), {len(self.items)} items: {self.items}" - - def create_file( - self, - items, - extension, - tags_output_dir, - srcdir, - tags_page_title, - tags_page_header, - ): - """Create file with list of documents associated with a given tag in - toctree format. - - This file is reached as a link from the tag name in each documentation - file, or from the tag overview page. - - If we are using md files, generate and md file; otherwise, go with rst. - - Parameters - ---------- - - tags_output_dir : Path - path where the file for this tag will be created - items : list - list of files associated with this tag (instance of Entry) - extension : {["rst"], ["md"], ["rst", "md"]} - list of file extensions used. - srcdir : str - root folder for the documentation (usually, project/docs) - tags_page_title: str - the title of the tag page, after which the tag is listed (e.g. "Tag: programming") - tags_page_header: str - the words after which the pages with the tag are listed (e.g. "With this tag: Hello World") - tag_intro_text: str - the words after which the tags of a given page are listed (e.g. "Tags: programming, python") +class TagsDomain(Domain): + name = "tags" + label = "Tags" - """ - # Get sorted file paths for tag pages, relative to /docs/_tags - tag_page_paths = sorted([i.relpath(srcdir) for i in items]) - ref_label = f"sphx_tag_{self.file_basename}" + roles = {} - content = [] - if "md" in extension: - filename = f"{self.file_basename}.md" - content.append(f"({ref_label})=") - content.append(f"# {tags_page_title}: {self.name}") - content.append("") - content.append("```{toctree}") - content.append("---") - content.append("maxdepth: 1") - content.append(f"caption: {tags_page_header}") - content.append("---") - for path in tag_page_paths: - content.append(f"../{path}") - content.append("```") - else: - filename = f"{self.file_basename}.rst" - header = f"{tags_page_title}: {self.name}" - content.append(f".. _{ref_label}:") - content.append("") - content.append(header) - content.append("#" * textwidth(header)) - content.append("") - content.append(".. toctree::") - content.append(" :maxdepth: 1") - content.append(f" :caption: {tags_page_header}") - content.append("") - for path in tag_page_paths: - content.append(f" ../{path}") + directives = { + "tags": TagLinks, + } - content.append("") - with open( - os.path.join(srcdir, tags_output_dir, filename), "w", encoding="utf8" - ) as f: - f.write("\n".join(content)) - - -class Entry: - """Tags to pages map""" - - def __init__(self, entrypath: Path): - self.filepath = entrypath - # self.tags = tags - # Read tags (for the first time) to create the tag pages - self.lines = self.filepath.read_text(encoding="utf8").split("\n") - if self.filepath.suffix == ".rst": - tagstart = ".. tags::" - tagend = "" # empty line - 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." - ) + # The values defined in initial_data will be copied to + # env.domaindata[domain_name] as the initial data of the domain, and domain + # instances can access it via self.data. + initial_data = { + "tags": [], + "entries": {}, + } - # tagline = [line for line in self.lines if tagstart in line] - # tagblock is all content until the next new empty line - tagblock = [] - reading = False - for line in self.lines: - line = line.strip() - if tagstart in line: - reading = True - line = line.split(tagstart)[1] - tagblock.extend(line.split(",")) - else: - if reading and line == tagend: - # tagblock now contains at least one tag - if tagblock != [""]: - break - if reading: - tagblock.extend(line.split(",")) + def get_full_qualified_name(self, node): + print(f"Node: {node}") + return f"tags.{node.arguments[0]}" + + def get_objects(self): + yield from self.data["tags"] + + def add_tag(self, tagname, page): + """Add a new tag to the domain.""" + anchor = f"{tagname}" + + # Add this page to the list of pages with this tag + if self.data["entries"].get(tagname) is None: + self.data["entries"][tagname] = [page] + else: + self.data["entries"][tagname].append(page) + + # Add this tag to the global list of tags + # name, dispname, type, docname, anchor, priority + self.data["tags"].append((tagname, tagname, "Tag", page, anchor, 0)) + + +def create_file( + app, + tag: tuple, + extension: List[str], + tags_output_dir: Path, + srcdir: str, + tags_page_title: str, + tags_page_header: str, + tag_intro_text: str, +): + """Create file with list of documents associated with a given tag in + toctree format. + + This file is reached as a link from the tag name in each documentation + file, or from the tag overview page. + + If we are using md files, generate and md file; otherwise, go with rst. + + Parameters + ---------- + + tag : tuple + tag name and list of pages associated with this tag + extension : {["rst"], ["md"], ["rst", "md"]} + list of file extensions used. + tags_output_dir : Path + path where the file for this tag will be created + srcdir : str + root folder for the documentation (usually, project/docs) + tags_page_title: str + the title of the tag page, after which the tag is listed (e.g. "Tag: programming") + tags_page_header: str + the words after which the pages with the tag are listed (e.g. "With this tag: Hello World") + tag_intro_text: str + the words after which the tags of a given page are listed (e.g. "Tags: programming, python") self.tags = [] if tagblock: self.tags = [_normalize_display_tag(tag) for tag in tagblock if tag] - def assign_to_tags(self, tag_dict): - """Append ourself to tags""" - for tag in self.tags: - if tag: - if tag not in tag_dict: - tag_dict[tag] = Tag(tag) - tag_dict[tag].items.append(self) + """ - def relpath(self, root_dir) -> str: - """Get this entry's path relative to the given root directory""" - return Path(os.path.relpath(self.filepath, root_dir)).as_posix() + name = tag[0] + file_basename = _normalize_tag(tag[0], dashes=True) -def read_tags(lines, tagstart, tagend): - """Read tags from a list of lines in a file. + # Get sorted file paths for tag pages, relative to /docs/_tags + tag_page_paths = sorted([os.path.relpath(i, srcdir) for i in tag[1]]) + ref_label = f"sphx_tag_{file_basename}" - """ - tagline = [line for line in lines if tagstart in line] - # Custom attributes - separator = "," + content = [] + if "md" in extension: + filename = f"{file_basename}.md" + content.append(f"({ref_label})=") + content.append(f"# {tags_page_title}: {name}") + content.append("") + content.append("```{toctree}") + content.append("---") + content.append("maxdepth: 1") + content.append(f"caption: {tags_page_header}") + content.append("---") + for path in tag_page_paths: + content.append(f"../{path}") + content.append("```") + else: + filename = f"{file_basename}.rst" + header = f"{tags_page_title}: {name}" + content.append(f".. _{ref_label}:") + content.append("") + content.append(header) + content.append("#" * textwidth(header)) + content.append("") + content.append(".. toctree::") + content.append(" :maxdepth: 1") + content.append(f" :caption: {tags_page_header}") + content.append("") + for path in tag_page_paths: + content.append(f" ../{path}") - tags = [] - if tagline: - # TODO: This is matching the .. tags:: example inside a code-block - # in configuration.rst - tagline = tagline[0].replace(tagstart, "").rstrip(tagend) - tags = [" ".join(tag.strip().split()) for tag in tagline.split(separator)] - tags = [tag for tag in tags if tag != ""] - return tags + content.append("") + with open( + os.path.join(srcdir, tags_output_dir, filename), "w", encoding="utf8" + ) as f: + f.write("\n".join(content)) def _normalize_tag(tag: str, dashes: bool = False) -> str: @@ -352,7 +317,8 @@ def tagpage(tags, outdir, title, extension, tags_index_head): """ - tags = list(tags.values()) + print(f"Tags: {tags=}") + print(f"outdir: {outdir=}") if "md" in extension: content = [] @@ -366,8 +332,9 @@ def tagpage(tags, outdir, title, extension, tags_index_head): content.append(f"caption: {tags_index_head}") content.append("maxdepth: 1") content.append("---") - for tag in sorted(tags, key=lambda t: t.name): - content.append(f"{tag.name} ({len(tag.items)}) <{tag.file_basename}>") + for name, pages in tags.items(): + file_basename = _normalize_tag(name, dashes=True) + content.append(f"{name} ({len(pages)}) <{file_basename}>") content.append("```") content.append("") filename = os.path.join(outdir, "tagsindex.md") @@ -385,10 +352,9 @@ def tagpage(tags, outdir, title, extension, tags_index_head): content.append(f" :caption: {tags_index_head}") content.append(" :maxdepth: 1") content.append("") - for tag in sorted(tags, key=lambda t: t.name): - content.append( - f" {tag.name} ({len(tag.items)}) <{tag.file_basename}.rst>" - ) + for name, pages in tags.items(): + file_basename = _normalize_tag(name, dashes=True) + content.append(f" {name} ({len(pages)}) <{file_basename}.rst>") content.append("") filename = os.path.join(outdir, "tagsindex.rst") @@ -396,38 +362,6 @@ def tagpage(tags, outdir, title, extension, tags_index_head): f.write("\n".join(content)) -def assign_entries(app): - """Assign all found entries to their tag. - - Returns - ------- - tags : dict - Dictionary of tags, keyed by tag name - pages : list - List of Entry objects - """ - 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) - entry.assign_to_tags(tags) - pages.append(entry) - # docname is path without the file extension - # docname = path.split(".", 1)[0] - # register tags to global metadata for document - # app.env.metadata[docname]["tags"] = tags - - return tags, pages - - def update_tags(app): """Update tags according to pages found""" if app.config.tags_create_tags: @@ -441,22 +375,24 @@ def update_tags(app): os.remove(os.path.join(app.srcdir, tags_output_dir, file)) # Create pages for each tag - tags, pages = assign_entries(app) - - for tag in tags.values(): - if tag: - tag.create_file( - [item for item in pages if tag.name in item.tags], - app.config.tags_extension, - tags_output_dir, - app.srcdir, - app.config.tags_page_title, - app.config.tags_page_header, - ) + global_tags = env.get_domain("tags").data["entries"] + logger.info(f"Global tags: {global_tags=}", color="green") + + for tag in global_tags.items(): + create_file( + app, + tag, + app.config.tags_extension, + tags_output_dir, + app.srcdir, + app.config.tags_page_title, + app.config.tags_page_header, + app.config.tags_intro_text, + ) # Create tags overview page tagpage( - tags, + global_tags, os.path.join(app.srcdir, tags_output_dir), app.config.tags_overview_title, app.config.tags_extension, @@ -468,6 +404,9 @@ def update_tags(app): "Tags were not created (tags_create_tags=False in conf.py)", color="white" ) + # Return iterable of docnames to re-read + return os.listdir(os.path.join(app.srcdir, tags_output_dir)) + def setup(app): """Setup for Sphinx.""" @@ -496,11 +435,11 @@ def setup(app): ) # Update tags - # TODO: tags should be updated after sphinx-gallery is generated, and the - # gallery is also connected to builder-inited. Are there situations when - # this will not work? - app.connect("builder-inited", update_tags) + # Tags should be updated after sphinx-gallery is generated, on + # builder-inited + app.connect("source-read", update_tags) app.add_directive("tags", TagLinks) + app.add_domain(TagsDomain) return { "version": __version__, From a18efca42d38ab0b9dc76570722229a3b9506a75 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 21:46:00 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 91faed3..fb20b78 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -121,4 +121,4 @@ files so it doesn't get stuck in a loop. Example: If you have set ``tags_output_dir`` to a different path, use that instead of ``_tags``. -.. tags:: tag documentation \ No newline at end of file +.. tags:: tag documentation From dcebcd1ce53f42239a60e0452fff57c502a3699a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Mon, 1 Jul 2024 19:40:24 -0300 Subject: [PATCH 4/5] Add index for tags pages Co-authored-by: hannah --- src/sphinx_tags/__init__.py | 61 ++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/src/sphinx_tags/__init__.py b/src/sphinx_tags/__init__.py index a082c89..cad22cb 100644 --- a/src/sphinx_tags/__init__.py +++ b/src/sphinx_tags/__init__.py @@ -2,7 +2,7 @@ """ -from sphinx.domains import Domain +from sphinx.domains import Domain, Index, IndexEntry from sphinx.util.logging import getLogger from sphinx.util.docutils import SphinxDirective from sphinx.errors import ExtensionError @@ -14,6 +14,8 @@ from typing import List from fnmatch import fnmatch import re +from collections.abc import Iterable +from collections import defaultdict __version__ = "1.0dev" @@ -92,10 +94,6 @@ def run(self): current_doc_dir = Path(self.env.doc2path(self.env.docname)).parent relative_tag_dir = Path(os.path.relpath(tag_dir, current_doc_dir)) -<<<<<<< HEAD - -======= ->>>>>>> 7f17eec (Implemented domain) for tag in page_tags: count += 1 # We want the link to be the path to the _tags folder, relative to @@ -118,8 +116,8 @@ def run(self): if not count == len(page_tags): result += nodes.inline(text=tag_separator) - # register tags to global metadata for document - self.env.metadata[self.env.docname]["tags"] = page_tags + td = self.env.get_domain("tags") + td.add_tagpage(self.env.docname, page_tags) return [result] @@ -163,6 +161,33 @@ def _get_tag_color(self, tag: str) -> str: return "primary" +class PagesIndex(Index): + """A custom index that creates a pages matrix.""" + + name = "tagpage" + localname = "Page Index" + shortname = "TagPage" + + def generate( + self, docnames: Iterable[str] | None = None + ) -> tuple[list[tuple[str, list[IndexEntry]]], bool]: + content = defaultdict(list) + + # sort the list of pages + pages = sorted(self.domain.get_objects(), key=lambda page: page[0]) + + # name, subtype, docname, anchor, extra, qualifier, description + + for _name, dispname, typ, docname, anchor, _priority in pages: + content[dispname[0].lower()].append( + (dispname, 0, docname, anchor, docname, "", typ) + ) + + # convert the dict to the sorted list of tuples expected + + return sorted(content.items()), True + + class TagsDomain(Domain): name = "tags" @@ -174,12 +199,15 @@ class TagsDomain(Domain): "tags": TagLinks, } + indices = [PagesIndex] + # The values defined in initial_data will be copied to # env.domaindata[domain_name] as the initial data of the domain, and domain # instances can access it via self.data. initial_data = { - "tags": [], - "entries": {}, + "tags": [], # list of tags + "entries": {}, # list of pages with tags + "pages": [], # list of tag pages } def get_full_qualified_name(self, node): @@ -203,6 +231,17 @@ def add_tag(self, tagname, page): # name, dispname, type, docname, anchor, priority self.data["tags"].append((tagname, tagname, "Tag", page, anchor, 0)) + def add_tagpage(self, docname, tags): + """Add a new page of tags to domain""" + name = f"tagpage.{docname}" + anchor = f"tagpage-{docname}" + + print(f"{self.data['tags']=}") + # name, dispname, type, docname, anchor, priority + self.data["pages"].append( + (name, docname, "TagPage", self.env.docname, anchor, 0) + ) + def create_file( app, @@ -375,7 +414,7 @@ def update_tags(app): os.remove(os.path.join(app.srcdir, tags_output_dir, file)) # Create pages for each tag - global_tags = env.get_domain("tags").data["entries"] + global_tags = app.env.get_domain("tags").data["entries"] logger.info(f"Global tags: {global_tags=}", color="green") for tag in global_tags.items(): @@ -437,7 +476,7 @@ def setup(app): # Update tags # Tags should be updated after sphinx-gallery is generated, on # builder-inited - app.connect("source-read", update_tags) + app.connect("builder-inited", update_tags) app.add_directive("tags", TagLinks) app.add_domain(TagsDomain) From c3912842ff82b743f3c3150d54d7d51531cdbb40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Sat, 6 Jul 2024 08:38:39 -0300 Subject: [PATCH 5/5] Force re-read of newly added pages to build This generates a couple of build warnings, because some references are missing on the first pass. This is expected, but we should either silence these errors or defer resolution of these references until later. This also doesn't include badges for similar reasons: because the references in the badges can't be resolved in the first pass, the badges are not linked to any pages. --- docs/index.rst | 4 + src/sphinx_tags/__init__.py | 156 ++++++++++++++++++++---------------- 2 files changed, 91 insertions(+), 69 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index c21792c..bf11e9f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,3 +27,7 @@ Check out the `list of projects that use this extension List[nodes.Node]: """Get a plaintext reference link for the given tag""" link = relative_tag_dir / f"{file_basename}.html" + # return pending_xref(refuri=str(link), text=tag) return nodes.reference(refuri=str(link), text=tag) def _get_badge_node( @@ -139,7 +139,7 @@ def _get_badge_node( text_nodes, messages = self.state.inline_text("", self.lineno) # Ref paths always use forward slashes, even on Windows - tag_ref = f"{tag} <{relative_tag_dir.as_posix()}/{file_basename}>" + tag_ref = f"{tag} <{relative_tag_dir.as_posix()}/{file_basename}.html>" tag_color = self._get_tag_color(tag) tag_badge = XRefBadgeRole(tag_color) return tag_badge( @@ -161,12 +161,14 @@ def _get_tag_color(self, tag: str) -> str: return "primary" -class PagesIndex(Index): - """A custom index that creates a pages matrix.""" +class TagsIndex(Index): + """A custom index that creates a page with all tags and corresponding pages + where they are defined. + """ - name = "tagpage" - localname = "Page Index" - shortname = "TagPage" + name = "index" + localname = "All tags" + shortname = "TagsIndex" def generate( self, docnames: Iterable[str] | None = None @@ -177,14 +179,12 @@ def generate( pages = sorted(self.domain.get_objects(), key=lambda page: page[0]) # name, subtype, docname, anchor, extra, qualifier, description - for _name, dispname, typ, docname, anchor, _priority in pages: content[dispname[0].lower()].append( (dispname, 0, docname, anchor, docname, "", typ) ) # convert the dict to the sorted list of tuples expected - return sorted(content.items()), True @@ -199,7 +199,7 @@ class TagsDomain(Domain): "tags": TagLinks, } - indices = [PagesIndex] + indices = [TagsIndex] # The values defined in initial_data will be copied to # env.domaindata[domain_name] as the initial data of the domain, and domain @@ -211,7 +211,7 @@ class TagsDomain(Domain): } def get_full_qualified_name(self, node): - print(f"Node: {node}") + # print(f"Node: {node}") return f"tags.{node.arguments[0]}" def get_objects(self): @@ -230,21 +230,35 @@ def add_tag(self, tagname, page): # Add this tag to the global list of tags # name, dispname, type, docname, anchor, priority self.data["tags"].append((tagname, tagname, "Tag", page, anchor, 0)) + # Create pages for each tag + create_file( + (tagname, self.data["entries"][tagname]), + # self.app.config.tags_extension, + ".md", + # Path(self.app.config.tags_output_dir), + Path("_tags"), + # self.app.srcdir, + Path("docs"), + # self.app.config.tags_page_title, + "My tags", + # self.app.config.tags_page_header, + "With this tag", + # self.app.config.tags_intro_text + "Tags:", + ) def add_tagpage(self, docname, tags): """Add a new page of tags to domain""" - name = f"tagpage.{docname}" - anchor = f"tagpage-{docname}" + name = f"index.{docname}" + anchor = f"index-{docname}" - print(f"{self.data['tags']=}") # name, dispname, type, docname, anchor, priority self.data["pages"].append( - (name, docname, "TagPage", self.env.docname, anchor, 0) + (name, docname, "TagsIndex", self.env.docname, anchor, 0) ) def create_file( - app, tag: tuple, extension: List[str], tags_output_dir: Path, @@ -278,17 +292,11 @@ def create_file( the words after which the pages with the tag are listed (e.g. "With this tag: Hello World") tag_intro_text: str the words after which the tags of a given page are listed (e.g. "Tags: programming, python") - - self.tags = [] - if tagblock: - self.tags = [_normalize_display_tag(tag) for tag in tagblock if tag] - """ - name = tag[0] - file_basename = _normalize_tag(tag[0], dashes=True) - # Get sorted file paths for tag pages, relative to /docs/_tags + file_basename = _normalize_tag(tag[0], dashes=True) + name = _normalize_display_tag(tag[0]) tag_page_paths = sorted([os.path.relpath(i, srcdir) for i in tag[1]]) ref_label = f"sphx_tag_{file_basename}" @@ -355,10 +363,6 @@ def tagpage(tags, outdir, title, extension, tags_index_head): This page contains a list of all available tags. """ - - print(f"Tags: {tags=}") - print(f"outdir: {outdir=}") - if "md" in extension: content = [] content.append("(tagoverview)=") @@ -371,9 +375,7 @@ def tagpage(tags, outdir, title, extension, tags_index_head): content.append(f"caption: {tags_index_head}") content.append("maxdepth: 1") content.append("---") - for name, pages in tags.items(): - file_basename = _normalize_tag(name, dashes=True) - content.append(f"{name} ({len(pages)}) <{file_basename}>") + content.append("PLACEHOLDER") content.append("```") content.append("") filename = os.path.join(outdir, "tagsindex.md") @@ -391,9 +393,7 @@ def tagpage(tags, outdir, title, extension, tags_index_head): content.append(f" :caption: {tags_index_head}") content.append(" :maxdepth: 1") content.append("") - for name, pages in tags.items(): - file_basename = _normalize_tag(name, dashes=True) - content.append(f" {name} ({len(pages)}) <{file_basename}.rst>") + content.append("") # placeholder content.append("") filename = os.path.join(outdir, "tagsindex.rst") @@ -401,51 +401,69 @@ def tagpage(tags, outdir, title, extension, tags_index_head): f.write("\n".join(content)) -def update_tags(app): +def update_tags(app, env): """Update tags according to pages found""" if app.config.tags_create_tags: tags_output_dir = Path(app.config.tags_output_dir) - - if not os.path.exists(os.path.join(app.srcdir, tags_output_dir)): - os.makedirs(os.path.join(app.srcdir, tags_output_dir)) - - for file in os.listdir(os.path.join(app.srcdir, tags_output_dir)): - if file.endswith("md") or file.endswith("rst"): - os.remove(os.path.join(app.srcdir, tags_output_dir, file)) - # Create pages for each tag global_tags = app.env.get_domain("tags").data["entries"] - logger.info(f"Global tags: {global_tags=}", color="green") - - for tag in global_tags.items(): - create_file( - app, - tag, - app.config.tags_extension, - tags_output_dir, - app.srcdir, - app.config.tags_page_title, - app.config.tags_page_header, - app.config.tags_intro_text, + # Inject these into found_docs + for docname, tags in global_tags.items(): + env.found_docs.add(docname) + # Inject these into the tagsindex + tags_output_dir = Path(app.config.tags_output_dir) + outdir = os.path.join(app.srcdir, tags_output_dir) + newcontent = [] + if "md" in app.config.tags_extension: + filename = os.path.join(outdir, "tagsindex.md") + taglist = "{name} ({len}) <{file_basename}>" + else: + filename = os.path.join(outdir, "tagsindex.rst") + taglist = " {name} ({len}) <{file_basename}.rst>" + + for name, pages in global_tags.items(): + file_basename = _normalize_tag(name, dashes=True) + newcontent.append( + taglist.format(name=name, len=len(pages), file_basename=file_basename) ) + with open(filename, "r", encoding="utf8") as f: + content = f.read() + with open(filename, "w", encoding="utf8") as f: + f.write(content.replace("PLACEHOLDER", "\n".join(newcontent))) + else: + logger.info( + "Tags were not created (tags_create_tags=False in conf.py)", color="white" + ) + logger.info("Tags updated", color="white") + + # Re-read files, create doctrees and add to env.found_docs and return + # iterable of docnames to re-read for env-get-updated + app.builder.read() + return env.found_docs + + +def prepare_tags(app): + if app.config.tags_create_tags: + tags_output_dir = Path(app.config.tags_output_dir) + + if not os.path.exists(os.path.join(app.srcdir, tags_output_dir)): + os.makedirs(os.path.join(app.srcdir, tags_output_dir)) # Create tags overview page tagpage( - global_tags, + [], os.path.join(app.srcdir, tags_output_dir), app.config.tags_overview_title, app.config.tags_extension, app.config.tags_index_head, ) - logger.info("Tags updated", color="white") + + logger.info("Tags output dir created", color="green") else: logger.info( - "Tags were not created (tags_create_tags=False in conf.py)", color="white" + "Tags were not created (tags_create_tags=False in conf.py)", color="red" ) - # Return iterable of docnames to re-read - return os.listdir(os.path.join(app.srcdir, tags_output_dir)) - def setup(app): """Setup for Sphinx.""" @@ -473,10 +491,10 @@ def setup(app): "html", ) - # Update tags # Tags should be updated after sphinx-gallery is generated, on # builder-inited - app.connect("builder-inited", update_tags) + app.connect("builder-inited", prepare_tags) + app.connect("env-get-updated", update_tags) app.add_directive("tags", TagLinks) app.add_domain(TagsDomain)