13
13
from sphinx .util .docutils import SphinxDirective
14
14
from sphinx .util .logging import getLogger
15
15
from sphinx .util .rst import textwidth
16
+ from sphinx .util .matching import get_matching_files
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,21 @@ 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_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
+ [_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 ))
52
74
53
75
tag_dir = Path (self .env .app .srcdir ) / self .env .app .config .tags_output_dir
54
76
result = nodes .paragraph ()
@@ -59,7 +81,7 @@ def run(self):
59
81
current_doc_dir = Path (self .env .doc2path (self .env .docname )).parent
60
82
relative_tag_dir = Path (os .path .relpath (tag_dir , current_doc_dir ))
61
83
62
- for tag in tags :
84
+ for tag in page_tags :
63
85
count += 1
64
86
# We want the link to be the path to the _tags folder, relative to
65
87
# this document's path where
@@ -70,19 +92,19 @@ def run(self):
70
92
# |
71
93
# - current_doc_path
72
94
73
- file_basename = _normalize_tag (tag )
95
+ file_basename = _normalize_tag (tag , dashes = True )
74
96
75
97
if self .env .app .config .tags_create_badges :
76
98
result += self ._get_badge_node (tag , file_basename , relative_tag_dir )
77
99
tag_separator = " "
78
100
else :
79
101
result += self ._get_plaintext_node (tag , file_basename , relative_tag_dir )
80
102
tag_separator = f"{ self .separator } "
81
- if not count == len (tags ):
103
+ if not count == len (page_tags ):
82
104
result += nodes .inline (text = tag_separator )
83
105
84
106
# 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
86
108
87
109
return [result ]
88
110
@@ -132,7 +154,7 @@ class Tag:
132
154
def __init__ (self , name ):
133
155
self .items = []
134
156
self .name = name
135
- self .file_basename = _normalize_tag (name )
157
+ self .file_basename = _normalize_tag (name , dashes = True )
136
158
137
159
def create_file (
138
160
self ,
@@ -214,9 +236,45 @@ def create_file(
214
236
class Entry :
215
237
"""Tags to pages map"""
216
238
217
- def __init__ (self , entrypath : Path , tags : list ):
239
+ def __init__ (self , entrypath : Path ):
218
240
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 != "" ]
220
278
221
279
def assign_to_tags (self , tag_dict ):
222
280
"""Append ourself to tags"""
@@ -230,13 +288,16 @@ def relpath(self, root_dir) -> str:
230
288
return Path (os .path .relpath (self .filepath , root_dir )).as_posix ()
231
289
232
290
233
- def _normalize_tag (tag : str ) -> str :
291
+ def _normalize_tag (tag : str , dashes : bool = False ) -> str :
234
292
"""Normalize a tag name to use in output filenames and tag URLs.
235
293
Replace whitespace and other non-alphanumeric characters with dashes.
236
294
237
295
Example: 'Tag:with (special characters) ' -> 'tag-with-special-characters'
238
296
"""
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 )
240
301
241
302
242
303
def tagpage (tags , outdir , title , extension , tags_index_head ):
@@ -295,11 +356,15 @@ def assign_entries(app):
295
356
pages = []
296
357
tags = {}
297
358
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 )
303
368
entry .assign_to_tags (tags )
304
369
pages .append (entry )
305
370
0 commit comments