Skip to content

Commit c8d0119

Browse files
committed
Enable multiline tags
1 parent b98d120 commit c8d0119

File tree

5 files changed

+106
-34
lines changed

5 files changed

+106
-34
lines changed

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
extensions = ["sphinx_design", "sphinx_tags", "nbsphinx", "myst_parser"]
3636

3737
tags_create_tags = True
38-
tags_create_badges = True
38+
tags_create_badges = False
3939
# tags_output_dir = "_tags" # default
4040
tags_overview_title = "All tags" # default: "Tags overview"
4141
tags_extension = ["rst", "md", "ipynb"] # default: ["rst"]

docs/configuration.rst

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,6 @@ Special characters
106106
Tags can contain spaces and special characters such as emoji. In that case, the
107107
tag will be normalized when processed. See our :doc:`examples/examples` for more details.
108108

109-
Multiple lines of tags
110-
----------------------
111-
112-
Tags can be passed in either as arguments or in the body of the directive:
113-
114-
.. code-block:: rst
115-
116-
.. tags::
117-
118-
tag1, tag2, tag3,
119-
tag4, tag5, tag6,
120-
121109
Usage with sphinx-autobuild
122110
---------------------------
123111

docs/examples/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ Usage examples
55

66
examples
77
raw-cells
8+
multiline

docs/examples/multiline.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Multiline tags
2+
==============
3+
4+
Since sphinx-tags 0.4.0, we now support multiline tag blocks. This means you can write
5+
6+
.. code-block:: rst
7+
8+
.. tags::
9+
10+
several, different, tags, can be, added,
11+
as long as, they are, separated
12+
13+
to obtain
14+
15+
.. tags::
16+
17+
several, different, tags, can be, added,
18+
as long as, they are, separated

src/sphinx_tags/__init__.py

Lines changed: 86 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
from sphinx.util.docutils import SphinxDirective
1414
from sphinx.util.logging import getLogger
1515
from sphinx.util.rst import textwidth
16+
from sphinx.util.matching import get_matching_files
1617

17-
__version__ = "0.3.1"
18+
__version__ = "0.4dev"
1819

1920
logger = getLogger("sphinx-tags")
2021

@@ -26,11 +27,25 @@ class TagLinks(SphinxDirective):
2627
2728
See also https://docutils.sourceforge.io/docs/howto/rst-directives.html
2829
30+
This directive can be used with arguments and with content.
31+
32+
1. With arguments:
33+
34+
.. raw:: rst
35+
.. tags:: tag1, tag2, tag3
36+
37+
2. With (multiline) content:
38+
39+
.. raw:: rst
40+
.. tags::
41+
42+
tag1, tag2,
43+
tag3
2944
"""
3045

3146
# Sphinx directive class attributes
3247
required_arguments = 0
33-
optional_arguments = 1 # Arbitrary, split on seperator
48+
optional_arguments = 1 # Arbitrary, split on separator
3449
final_argument_whitespace = True
3550
has_content = True
3651
final_argument_whitespace = True
@@ -41,14 +56,21 @@ def run(self):
4156
if not (self.arguments or self.content):
4257
raise ExtensionError("No tags passed to 'tags' directive.")
4358

44-
tagline = []
59+
page_tags = []
4560
# normalize white space and remove "\n"
4661
if self.arguments:
47-
tagline.extend(self.arguments[0].split())
62+
page_tags.extend(
63+
[_normalize_tag(tag) for tag in self.arguments[0].split(",")]
64+
)
4865
if self.content:
49-
tagline.extend((" ".join(self.content)).strip().split())
50-
51-
tags = [tag.strip() for tag in (" ".join(tagline)).split(self.separator)]
66+
# self.content: StringList(['different, tags,', 'separated'],
67+
# items=[(path, lineno), (path, lineno)])
68+
page_tags.extend(
69+
[_normalize_tag(tag) for tag in ",".join(self.content).split(",")]
70+
)
71+
# Remove empty elements from page_tags
72+
# (can happen after _normalize_tag())
73+
page_tags = list(filter(None, page_tags))
5274

5375
tag_dir = Path(self.env.app.srcdir) / self.env.app.config.tags_output_dir
5476
result = nodes.paragraph()
@@ -59,7 +81,7 @@ def run(self):
5981
current_doc_dir = Path(self.env.doc2path(self.env.docname)).parent
6082
relative_tag_dir = Path(os.path.relpath(tag_dir, current_doc_dir))
6183

62-
for tag in tags:
84+
for tag in page_tags:
6385
count += 1
6486
# We want the link to be the path to the _tags folder, relative to
6587
# this document's path where
@@ -70,19 +92,19 @@ def run(self):
7092
# |
7193
# - current_doc_path
7294

73-
file_basename = _normalize_tag(tag)
95+
file_basename = _normalize_tag(tag, dashes=True)
7496

7597
if self.env.app.config.tags_create_badges:
7698
result += self._get_badge_node(tag, file_basename, relative_tag_dir)
7799
tag_separator = " "
78100
else:
79101
result += self._get_plaintext_node(tag, file_basename, relative_tag_dir)
80102
tag_separator = f"{self.separator} "
81-
if not count == len(tags):
103+
if not count == len(page_tags):
82104
result += nodes.inline(text=tag_separator)
83105

84106
# register tags to global metadata for document
85-
self.env.metadata[self.env.docname]["tags"] = tags
107+
self.env.metadata[self.env.docname]["tags"] = page_tags
86108

87109
return [result]
88110

@@ -132,7 +154,7 @@ class Tag:
132154
def __init__(self, name):
133155
self.items = []
134156
self.name = name
135-
self.file_basename = _normalize_tag(name)
157+
self.file_basename = _normalize_tag(name, dashes=True)
136158

137159
def create_file(
138160
self,
@@ -214,9 +236,45 @@ def create_file(
214236
class Entry:
215237
"""Tags to pages map"""
216238

217-
def __init__(self, entrypath: Path, tags: list):
239+
def __init__(self, entrypath: Path):
218240
self.filepath = entrypath
219-
self.tags = tags
241+
# self.tags = tags
242+
# Read tags (for the first time) to create the tag pages
243+
self.lines = self.filepath.read_text(encoding="utf8").split("\n")
244+
if self.filepath.suffix == ".rst":
245+
tagstart = ".. tags::"
246+
tagend = "" # empty line
247+
elif self.filepath.suffix == ".md":
248+
tagstart = "```{tags}"
249+
tagend = "```"
250+
elif self.filepath.suffix == ".ipynb":
251+
tagstart = '".. tags::'
252+
tagend = "]"
253+
else:
254+
raise ValueError(
255+
"Unknown file extension. Currently, only .rst, .md .ipynb are supported."
256+
)
257+
258+
# tagline = [line for line in self.lines if tagstart in line]
259+
# tagblock is all content until the next new empty line
260+
tagblock = []
261+
reading = False
262+
for line in self.lines:
263+
if tagstart in line:
264+
reading = True
265+
line = line.split(tagstart)[1]
266+
tagblock.extend(line.split(","))
267+
else:
268+
if reading and line.strip() == tagend:
269+
# tagblock now contains at least one tag
270+
if tagblock != [""]:
271+
break
272+
if reading:
273+
tagblock.extend(line.split(","))
274+
275+
self.tags = []
276+
if tagblock:
277+
self.tags = [tag.strip().rstrip('"') for tag in tagblock if tag != ""]
220278

221279
def assign_to_tags(self, tag_dict):
222280
"""Append ourself to tags"""
@@ -230,13 +288,16 @@ def relpath(self, root_dir) -> str:
230288
return Path(os.path.relpath(self.filepath, root_dir)).as_posix()
231289

232290

233-
def _normalize_tag(tag: str) -> str:
291+
def _normalize_tag(tag: str, dashes: bool = False) -> str:
234292
"""Normalize a tag name to use in output filenames and tag URLs.
235293
Replace whitespace and other non-alphanumeric characters with dashes.
236294
237295
Example: 'Tag:with (special characters) ' -> 'tag-with-special-characters'
238296
"""
239-
return re.sub(r"[\s\W]+", "-", tag).lower().strip("-")
297+
char = " "
298+
if dashes:
299+
char = "-"
300+
return re.sub(r"[\s\W]+", char, tag).lower().strip(char)
240301

241302

242303
def tagpage(tags, outdir, title, extension, tags_index_head):
@@ -295,11 +356,15 @@ def assign_entries(app):
295356
pages = []
296357
tags = {}
297358

298-
for docname in app.env.found_docs:
299-
doctags = app.env.metadata[docname].get("tags", None)
300-
if doctags is None:
301-
continue # skip if no tags
302-
entry = Entry(app.env.doc2path(docname), doctags)
359+
# Get document paths in the project that match specified file extensions
360+
doc_paths = get_matching_files(
361+
app.srcdir,
362+
include_patterns=[f"**.{extension}" for extension in app.config.tags_extension],
363+
exclude_patterns=app.config.exclude_patterns,
364+
)
365+
366+
for path in doc_paths:
367+
entry = Entry(Path(app.srcdir) / path)
303368
entry.assign_to_tags(tags)
304369
pages.append(entry)
305370

0 commit comments

Comments
 (0)