plugin.py (5148B)
1 # Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com> 2 3 # Permission is hereby granted, free of charge, to any person obtaining a copy 4 # of this software and associated documentation files (the "Software"), to 5 # deal in the Software without restriction, including without limitation the 6 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 # sell copies of the Software, and to permit persons to whom the Software is 8 # furnished to do so, subject to the following conditions: 9 10 # The above copyright notice and this permission notice shall be included in 11 # all copies or substantial portions of the Software. 12 13 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 # IN THE SOFTWARE. 20 21 import logging 22 import os 23 import sys 24 25 from collections import defaultdict 26 from markdown.extensions.toc import slugify 27 from mkdocs import utils 28 from mkdocs.commands.build import DuplicateFilter 29 from mkdocs.config.config_options import Type 30 from mkdocs.plugins import BasePlugin 31 32 # ----------------------------------------------------------------------------- 33 # Class 34 # ----------------------------------------------------------------------------- 35 36 # Tags plugin 37 class TagsPlugin(BasePlugin): 38 39 # Configuration scheme 40 config_scheme = ( 41 ("tags_file", Type(str, required = False)), 42 ) 43 44 # Initialize plugin 45 def __init__(self): 46 self.tags = defaultdict(list) 47 self.tags_file = None 48 self.slugify = None 49 50 # Retrieve configuration for anchor generation 51 def on_config(self, config): 52 if "toc" in config["markdown_extensions"]: 53 toc = { "slugify": slugify, "separator": "-" } 54 if "toc" in config["mdx_configs"]: 55 toc = { **toc, **config["mdx_configs"]["toc"] } 56 57 # Partially apply slugify function 58 self.slugify = lambda value: ( 59 toc["slugify"](str(value), toc["separator"]) 60 ) 61 62 # Hack: 2nd pass for tags index page 63 def on_nav(self, nav, files, **kwargs): 64 file = self.config.get("tags_file") 65 if file: 66 self.tags_file = files.get_file_from_path(file) 67 if not self.tags_file: 68 log.error(f"Configuration error: {file} doesn't exist.") 69 sys.exit() 70 71 # Add tags file to files 72 files.append(self.tags_file) 73 74 # Build and render tags index page 75 def on_page_markdown(self, markdown, page, **kwargs): 76 if page.file == self.tags_file: 77 return self.__render_tag_index(markdown) 78 79 # Add page to tags index 80 for tag in page.meta.get("tags", []): 81 self.tags[tag].append(page) 82 83 # Inject tags into page (after search and before minification) 84 def on_page_context(self, context, page, **kwargs): 85 if "tags" in page.meta: 86 context["tags"] = [ 87 self.__render_tag(tag) 88 for tag in page.meta["tags"] 89 ] 90 91 # ------------------------------------------------------------------------- 92 93 # Render tags index 94 def __render_tag_index(self, markdown): 95 if not "[TAGS]" in markdown: 96 markdown += "\n[TAGS]" 97 98 # Replace placeholder in Markdown with rendered tags index 99 return markdown.replace("[TAGS]", "\n".join([ 100 self.__render_tag_links(*args) 101 for args in sorted(self.tags.items()) 102 ])) 103 104 # Render the given tag and links to all pages with occurrences 105 def __render_tag_links(self, tag, pages): 106 content = [f"## <span class=\"md-tag\">{tag}</span>", ""] 107 for page in pages: 108 url = utils.get_relative_url( 109 page.file.src_path.replace(os.path.sep, "/"), 110 self.tags_file.src_path.replace(os.path.sep, "/") 111 ) 112 113 # Ensure forward slashes, as we have to use the path of the source 114 # file which contains the operating system's path separator. 115 content.append("- [{}]({})".format( 116 page.meta.get("title", page.title), 117 url 118 )) 119 120 # Return rendered tag links 121 return "\n".join(content) 122 123 # Render the given tag, linking to the tags index (if enabled) 124 def __render_tag(self, tag): 125 if not self.tags_file or not self.slugify: 126 return dict(name = tag) 127 else: 128 url = self.tags_file.url 129 url += f"#{self.slugify(tag)}" 130 return dict(name = tag, url = url) 131 132 # ----------------------------------------------------------------------------- 133 # Data 134 # ----------------------------------------------------------------------------- 135 136 # Set up logging 137 log = logging.getLogger("mkdocs") 138 log.addFilter(DuplicateFilter())