9
9
from typing import List
10
10
11
11
from docutils import nodes
12
+ from docutils .parsers .rst import Parser
13
+ from docutils import frontend , utils
12
14
from sphinx .errors import ExtensionError
13
15
from sphinx .util .docutils import SphinxDirective
14
16
from sphinx .util .logging import getLogger
17
+ from sphinx .util .matching import get_matching_files
15
18
from sphinx .util .rst import textwidth
16
19
17
20
__version__ = "0.3.1"
@@ -30,35 +33,42 @@ class TagLinks(SphinxDirective):
30
33
31
34
# Sphinx directive class attributes
32
35
required_arguments = 0
33
- optional_arguments = 1 # Arbitrary, split on seperator
36
+ optional_arguments = 1 # Arbitrary, split on separator
34
37
final_argument_whitespace = True
35
38
has_content = True
36
39
final_argument_whitespace = True
37
- # Custom attributes
38
40
separator = ","
39
41
40
42
def run (self ):
41
43
if not (self .arguments or self .content ):
42
44
raise ExtensionError ("No tags passed to 'tags' directive." )
43
45
44
46
tagline = []
45
- # normalize white space and remove "\n"
47
+
48
+ # normalize white space and remove "\n"
46
49
if self .arguments :
47
50
tagline .extend (self .arguments [0 ].split ())
48
51
if self .content :
49
52
tagline .extend ((" " .join (self .content )).strip ().split ())
53
+ #tags = read_tags(tagline, ".. tags::", "")
54
+ #tags = [tag.strip() for tag in (" ".join(tagline)).split(self.separator)]
50
55
51
- tags = [tag .strip () for tag in (" " .join (tagline )).split (self .separator )]
56
+ settings = frontend .get_default_settings (Parser )
57
+ document = utils .new_document ("tags" , settings )
58
+ tags = Parser ().parse (tagline , document )
59
+ print (f"Tags: { tags } " )
52
60
53
61
tag_dir = Path (self .env .app .srcdir ) / self .env .app .config .tags_output_dir
54
62
result = nodes .paragraph ()
55
63
result ["classes" ] = ["tags" ]
56
64
result += nodes .inline (text = f"{ self .env .app .config .tags_intro_text } " )
57
65
count = 0
58
66
67
+ # Parse the directive contents.
68
+ self .state .nested_parse (self .content , self .content_offset , result )
69
+
59
70
current_doc_dir = Path (self .env .doc2path (self .env .docname )).parent
60
71
relative_tag_dir = Path (os .path .relpath (tag_dir , current_doc_dir ))
61
-
62
72
for tag in tags :
63
73
count += 1
64
74
# We want the link to be the path to the _tags folder, relative to
@@ -81,9 +91,6 @@ def run(self):
81
91
if not count == len (tags ):
82
92
result += nodes .inline (text = tag_separator )
83
93
84
- # register tags to global metadata for document
85
- self .env .metadata [self .env .docname ]["tags" ] = tags
86
-
87
94
return [result ]
88
95
89
96
def _get_plaintext_node (
@@ -134,6 +141,9 @@ def __init__(self, name):
134
141
self .name = name
135
142
self .file_basename = _normalize_tag (name )
136
143
144
+ def __repr__ (self ):
145
+ return f"Tag({ self .name } ), { len (self .items )} items: { self .items } "
146
+
137
147
def create_file (
138
148
self ,
139
149
items ,
@@ -214,21 +224,60 @@ def create_file(
214
224
class Entry :
215
225
"""Tags to pages map"""
216
226
217
- def __init__ (self , entrypath : Path , tags : list ):
227
+ # def __init__(self, entrypath: Path, tags: list):
228
+ def __init__ (self , entrypath : Path ):
218
229
self .filepath = entrypath
219
- self .tags = tags
230
+ self .lines = self .filepath .read_text (encoding = "utf8" ).split ("\n " )
231
+ if self .filepath .suffix == ".rst" :
232
+ tagstart = ".. tags::"
233
+ tagend = ""
234
+ elif self .filepath .suffix == ".md" :
235
+ tagstart = "```{tags}"
236
+ tagend = "```"
237
+ elif self .filepath .suffix == ".ipynb" :
238
+ tagstart = '".. tags::'
239
+ tagend = '"'
240
+ else :
241
+ raise ValueError (
242
+ "Unknown file extension. Currently, only .rst, .md and .ipynb are supported."
243
+ )
244
+
245
+ self .tags = read_tags (self .lines , tagstart , tagend )
246
+
247
+
248
+ def __repr__ (self ):
249
+ return f"Entry({ self .filepath } ), { self .tags } "
250
+
220
251
221
252
def assign_to_tags (self , tag_dict ):
222
253
"""Append ourself to tags"""
223
254
for tag in self .tags :
224
- if tag not in tag_dict :
225
- tag_dict [tag ] = Tag (tag )
226
- tag_dict [tag ].items .append (self )
255
+ if tag :
256
+ if tag not in tag_dict :
257
+ tag_dict [tag ] = Tag (tag )
258
+ tag_dict [tag ].items .append (self )
227
259
228
260
def relpath (self , root_dir ) -> str :
229
261
"""Get this entry's path relative to the given root directory"""
230
262
return Path (os .path .relpath (self .filepath , root_dir )).as_posix ()
231
263
264
+ def read_tags (lines , tagstart , tagend ):
265
+ """Read tags from a list of lines in a file.
266
+
267
+ """
268
+ tagline = [line for line in lines if tagstart in line ]
269
+ # Custom attributes
270
+ separator = ","
271
+
272
+ tags = []
273
+ if tagline :
274
+ # TODO: This is matching the .. tags:: example inside a code-block
275
+ # in configuration.rst
276
+ tagline = tagline [0 ].replace (tagstart , "" ).rstrip (tagend )
277
+ tags = [" " .join (tag .strip ().split ()) for tag in tagline .split (separator )]
278
+ tags = [tag for tag in tags if tag != "" ]
279
+ return tags
280
+
232
281
233
282
def _normalize_tag (tag : str ) -> str :
234
283
"""Normalize a tag name to use in output filenames and tag URLs.
@@ -291,17 +340,32 @@ def tagpage(tags, outdir, title, extension, tags_index_head):
291
340
292
341
293
342
def assign_entries (app ):
294
- """Assign all found entries to their tag."""
343
+ """Assign all found entries to their tag.
344
+
345
+ Returns
346
+ -------
347
+ tags : dict
348
+ Dictionary of tags, keyed by tag name
349
+ pages : list
350
+ List of Entry objects
351
+ """
295
352
pages = []
296
353
tags = {}
297
354
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 )
355
+ doc_paths = get_matching_files (
356
+ app .srcdir ,
357
+ include_patterns = [f"**.{ extension } " for extension in app .config .tags_extension ],
358
+ exclude_patterns = app .config .exclude_patterns ,
359
+ )
360
+
361
+ for path in doc_paths :
362
+ entry = Entry (Path (app .srcdir ) / path )
303
363
entry .assign_to_tags (tags )
304
364
pages .append (entry )
365
+ # docname is path without the file extension
366
+ # docname = path.split(".", 1)[0]
367
+ # register tags to global metadata for document
368
+ # app.env.metadata[docname]["tags"] = tags
305
369
306
370
return tags , pages
307
371
@@ -322,14 +386,15 @@ def update_tags(app):
322
386
tags , pages = assign_entries (app )
323
387
324
388
for tag in tags .values ():
325
- tag .create_file (
326
- [item for item in pages if tag .name in item .tags ],
327
- app .config .tags_extension ,
328
- tags_output_dir ,
329
- app .srcdir ,
330
- app .config .tags_page_title ,
331
- app .config .tags_page_header ,
332
- )
389
+ if tag :
390
+ tag .create_file (
391
+ [item for item in pages if tag .name in item .tags ],
392
+ app .config .tags_extension ,
393
+ tags_output_dir ,
394
+ app .srcdir ,
395
+ app .config .tags_page_title ,
396
+ app .config .tags_page_header ,
397
+ )
333
398
334
399
# Create tags overview page
335
400
tagpage (
0 commit comments