1212from sphinx .errors import ExtensionError
1313from sphinx .util .docutils import SphinxDirective
1414from sphinx .util .logging import getLogger
15+ from sphinx .util .matching import get_matching_files
1516from sphinx .util .rst import textwidth
1617
17- __version__ = "0.3.1 "
18+ __version__ = "0.4dev "
1819
1920logger = 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(
214239class 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
242316def 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