Skip to content

Commit 6b5bdb3

Browse files
authored
Merge pull request #102 from melissawm/multiline
Enable multiline tags
2 parents b98d120 + 8d03d56 commit 6b5bdb3

File tree

5 files changed

+120
-34
lines changed

5 files changed

+120
-34
lines changed

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#
1313
import os
1414
import sys
15+
1516
from sphinx_tags import __version__
1617

1718
sys.path.insert(0, os.path.abspath("../src"))

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: 100 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
from sphinx.errors import ExtensionError
1313
from sphinx.util.docutils import SphinxDirective
1414
from sphinx.util.logging import getLogger
15+
from sphinx.util.matching import get_matching_files
1516
from sphinx.util.rst import textwidth
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,24 @@ 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_display_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+
[
70+
_normalize_display_tag(tag)
71+
for tag in ",".join(self.content).split(",")
72+
]
73+
)
74+
# Remove empty elements from page_tags
75+
# (can happen after _normalize_tag())
76+
page_tags = list(filter(None, page_tags))
5277

5378
tag_dir = Path(self.env.app.srcdir) / self.env.app.config.tags_output_dir
5479
result = nodes.paragraph()
@@ -59,7 +84,7 @@ def run(self):
5984
current_doc_dir = Path(self.env.doc2path(self.env.docname)).parent
6085
relative_tag_dir = Path(os.path.relpath(tag_dir, current_doc_dir))
6186

62-
for tag in tags:
87+
for tag in page_tags:
6388
count += 1
6489
# We want the link to be the path to the _tags folder, relative to
6590
# this document's path where
@@ -70,19 +95,19 @@ def run(self):
7095
# |
7196
# - current_doc_path
7297

73-
file_basename = _normalize_tag(tag)
98+
file_basename = _normalize_tag(tag, dashes=True)
7499

75100
if self.env.app.config.tags_create_badges:
76101
result += self._get_badge_node(tag, file_basename, relative_tag_dir)
77102
tag_separator = " "
78103
else:
79104
result += self._get_plaintext_node(tag, file_basename, relative_tag_dir)
80105
tag_separator = f"{self.separator} "
81-
if not count == len(tags):
106+
if not count == len(page_tags):
82107
result += nodes.inline(text=tag_separator)
83108

84109
# register tags to global metadata for document
85-
self.env.metadata[self.env.docname]["tags"] = tags
110+
self.env.metadata[self.env.docname]["tags"] = page_tags
86111

87112
return [result]
88113

@@ -131,8 +156,8 @@ class Tag:
131156

132157
def __init__(self, name):
133158
self.items = []
134-
self.name = name
135-
self.file_basename = _normalize_tag(name)
159+
self.name = _normalize_display_tag(name)
160+
self.file_basename = _normalize_tag(name, dashes=True)
136161

137162
def create_file(
138163
self,
@@ -214,9 +239,46 @@ def create_file(
214239
class Entry:
215240
"""Tags to pages map"""
216241

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

221283
def assign_to_tags(self, tag_dict):
222284
"""Append ourself to tags"""
@@ -230,13 +292,25 @@ def relpath(self, root_dir) -> str:
230292
return Path(os.path.relpath(self.filepath, root_dir)).as_posix()
231293

232294

233-
def _normalize_tag(tag: str) -> str:
295+
def _normalize_tag(tag: str, dashes: bool = False) -> str:
234296
"""Normalize a tag name to use in output filenames and tag URLs.
235297
Replace whitespace and other non-alphanumeric characters with dashes.
236298
237299
Example: 'Tag:with (special characters) ' -> 'tag-with-special-characters'
238300
"""
239-
return re.sub(r"[\s\W]+", "-", tag).lower().strip("-")
301+
char = " "
302+
if dashes:
303+
char = "-"
304+
return re.sub(r"[\s\W]+", char, tag).lower().strip(char)
305+
306+
307+
def _normalize_display_tag(tag: str) -> str:
308+
"""Strip extra whitespace from a tag name for display purposes.
309+
310+
Example: ' Tag:with (extra whitespace) ' -> 'Tag:with (extra whitespace)'
311+
"""
312+
tag = tag.replace("\\n", "\n").strip('"').strip()
313+
return re.sub(r"\s+", " ", tag)
240314

241315

242316
def tagpage(tags, outdir, title, extension, tags_index_head):
@@ -295,11 +369,15 @@ def assign_entries(app):
295369
pages = []
296370
tags = {}
297371

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)
372+
# Get document paths in the project that match specified file extensions
373+
doc_paths = get_matching_files(
374+
app.srcdir,
375+
include_patterns=[f"**.{extension}" for extension in app.config.tags_extension],
376+
exclude_patterns=app.config.exclude_patterns,
377+
)
378+
379+
for path in doc_paths:
380+
entry = Entry(Path(app.srcdir) / path)
303381
entry.assign_to_tags(tags)
304382
pages.append(entry)
305383

0 commit comments

Comments
 (0)