12
12
from sphinx .errors import ExtensionError
13
13
from sphinx .util .docutils import SphinxDirective
14
14
from sphinx .util .logging import getLogger
15
+ from sphinx .util .matching import get_matching_files
15
16
from sphinx .util .rst import textwidth
16
17
17
- __version__ = "0.3.1 "
18
+ __version__ = "0.4dev "
18
19
19
20
logger = getLogger ("sphinx-tags" )
20
21
@@ -26,11 +27,25 @@ class TagLinks(SphinxDirective):
26
27
27
28
See also https://docutils.sourceforge.io/docs/howto/rst-directives.html
28
29
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
29
44
"""
30
45
31
46
# Sphinx directive class attributes
32
47
required_arguments = 0
33
- optional_arguments = 1 # Arbitrary, split on seperator
48
+ optional_arguments = 1 # Arbitrary, split on separator
34
49
final_argument_whitespace = True
35
50
has_content = True
36
51
final_argument_whitespace = True
@@ -41,14 +56,24 @@ def run(self):
41
56
if not (self .arguments or self .content ):
42
57
raise ExtensionError ("No tags passed to 'tags' directive." )
43
58
44
- tagline = []
59
+ page_tags = []
45
60
# normalize white space and remove "\n"
46
61
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
+ )
48
65
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 ))
52
77
53
78
tag_dir = Path (self .env .app .srcdir ) / self .env .app .config .tags_output_dir
54
79
result = nodes .paragraph ()
@@ -59,7 +84,7 @@ def run(self):
59
84
current_doc_dir = Path (self .env .doc2path (self .env .docname )).parent
60
85
relative_tag_dir = Path (os .path .relpath (tag_dir , current_doc_dir ))
61
86
62
- for tag in tags :
87
+ for tag in page_tags :
63
88
count += 1
64
89
# We want the link to be the path to the _tags folder, relative to
65
90
# this document's path where
@@ -70,19 +95,19 @@ def run(self):
70
95
# |
71
96
# - current_doc_path
72
97
73
- file_basename = _normalize_tag (tag )
98
+ file_basename = _normalize_tag (tag , dashes = True )
74
99
75
100
if self .env .app .config .tags_create_badges :
76
101
result += self ._get_badge_node (tag , file_basename , relative_tag_dir )
77
102
tag_separator = " "
78
103
else :
79
104
result += self ._get_plaintext_node (tag , file_basename , relative_tag_dir )
80
105
tag_separator = f"{ self .separator } "
81
- if not count == len (tags ):
106
+ if not count == len (page_tags ):
82
107
result += nodes .inline (text = tag_separator )
83
108
84
109
# 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
86
111
87
112
return [result ]
88
113
@@ -131,8 +156,8 @@ class Tag:
131
156
132
157
def __init__ (self , name ):
133
158
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 )
136
161
137
162
def create_file (
138
163
self ,
@@ -214,9 +239,46 @@ def create_file(
214
239
class Entry :
215
240
"""Tags to pages map"""
216
241
217
- def __init__ (self , entrypath : Path , tags : list ):
242
+ def __init__ (self , entrypath : Path ):
218
243
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 ]
220
282
221
283
def assign_to_tags (self , tag_dict ):
222
284
"""Append ourself to tags"""
@@ -230,13 +292,25 @@ def relpath(self, root_dir) -> str:
230
292
return Path (os .path .relpath (self .filepath , root_dir )).as_posix ()
231
293
232
294
233
- def _normalize_tag (tag : str ) -> str :
295
+ def _normalize_tag (tag : str , dashes : bool = False ) -> str :
234
296
"""Normalize a tag name to use in output filenames and tag URLs.
235
297
Replace whitespace and other non-alphanumeric characters with dashes.
236
298
237
299
Example: 'Tag:with (special characters) ' -> 'tag-with-special-characters'
238
300
"""
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 )
240
314
241
315
242
316
def tagpage (tags , outdir , title , extension , tags_index_head ):
@@ -295,11 +369,15 @@ def assign_entries(app):
295
369
pages = []
296
370
tags = {}
297
371
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 )
303
381
entry .assign_to_tags (tags )
304
382
pages .append (entry )
305
383
0 commit comments