class SearchPlugin(BasePlugin): """ Add a search feature to MkDocs. """ config_scheme = ( ('lang', LangOption()), ('separator', config_options.Type(str, default=r'[\s\-]+')), ('min_search_length', config_options.Type(int, default=3)), ('prebuild_index', config_options.Choice((False, True, 'node', 'python'), default=False)), ('indexing', config_options.Choice(('full', 'sections', 'titles'), default='full')) ) def on_config(self, config, **kwargs): "Add plugin templates and scripts to config." if 'include_search_page' in config['theme'] and config['theme']['include_search_page']: config['theme'].static_templates.add('search.html') if not ('search_index_only' in config['theme'] and config['theme']['search_index_only']): path = os.path.join(base_path, 'templates') config['theme'].dirs.append(path) if 'search/main.js' not in config['extra_javascript']: config['extra_javascript'].append('search/main.js') if self.config['lang'] is None: # lang setting undefined. Set default based on theme locale validate = self.config_scheme[0][1].run_validation self.config['lang'] = validate(config['theme']['locale'].language) return config def on_pre_build(self, config, **kwargs): "Create search index instance for later use." self.search_index = SearchIndex(**self.config) def on_page_context(self, context, **kwargs): "Add page to search index." self.search_index.add_entry_from_context(context['page']) def on_post_build(self, config, **kwargs): "Build search index." output_base_path = os.path.join(config['site_dir'], 'search') search_index = self.search_index.generate_search_index() json_output_path = os.path.join(output_base_path, 'search_index.json') utils.write_file(search_index.encode('utf-8'), json_output_path) if not ('search_index_only' in config['theme'] and config['theme']['search_index_only']): # Include language support files in output. Copy them directly # so that only the needed files are included. files = [] if len(self.config['lang']) > 1 or 'en' not in self.config['lang']: files.append('lunr.stemmer.support.js') if len(self.config['lang']) > 1: files.append('lunr.multi.js') if ('ja' in self.config['lang'] or 'jp' in self.config['lang']): files.append('tinyseg.js') for lang in self.config['lang']: if (lang != 'en'): files.append('lunr.{}.js'.format(lang)) for filename in files: from_path = os.path.join(base_path, 'lunr-language', filename) to_path = os.path.join(output_base_path, filename) utils.copy_file(from_path, to_path)
def test_invalid_choice(self): option = config_options.Choice(('python', 'node')) self.assertRaises(config_options.ValidationError, option.validate, 'go')
def test_valid_choice(self): option = config_options.Choice(('python', 'node')) value = option.validate('python') self.assertEqual(value, 'python')
class SearchPlugin(BasePlugin): """ Add a search feature to MkDocs. """ config_scheme = (('lang', LangOption(default=['en'])), ('separator', config_options.Type(utils.string_types, default=r'[\s\-]+')), ('prebuild_index', config_options.Choice((False, True, 'node', 'python'), default=False)), ('local_search_shim', config_options.Type(bool, default=False))) def on_config(self, config, **kwargs): "Add plugin templates and scripts to config." if 'include_search_page' in config['theme'] and config['theme'][ 'include_search_page']: config['theme'].static_templates.add('search.html') if not ('search_index_only' in config['theme'] and config['theme']['search_index_only']): path = os.path.join(base_path, 'templates') config['theme'].dirs.append(path) if 'search/main.js' not in config['extra_javascript']: config['extra_javascript'].append('search/main.js') return config def on_pre_build(self, config, **kwargs): "Create search index instance for later use." self.search_index = SearchIndex(**self.config) def on_page_context(self, context, **kwargs): "Add page to search index." self.search_index.add_entry_from_context(context['page']) def on_post_build(self, config, **kwargs): "Build search index." output_base_path = os.path.join(config['site_dir'], 'search') if self.config['local_search_shim']: print( "INFO - local_search_shim Enabled! Make sure you've added shims/fetch_shim.js to your docs folder." ) # Change the search_index from being pure JSON to being JavaScript containing a global searchIndex variable with the JSON object we want # Also write it in the traditional format, so that either way works search_index = self.search_index.generate_search_index() search_index_shimmed = "shim_localSearchIndex = " + search_index json_output_path = os.path.join(output_base_path, 'search_index.json') json_output_path_shimmed = os.path.join(output_base_path, 'search_index.js') utils.write_file(search_index.encode('utf-8'), json_output_path) utils.write_file(search_index_shimmed.encode('utf-8'), json_output_path_shimmed) else: # Write the search index only in the traditional way print( "INFO - local_search_shim disabled. Generating only traditional JSON search index..." ) search_index = self.search_index.generate_search_index() json_output_path = os.path.join(output_base_path, 'search_index.json') utils.write_file(search_index.encode('utf-8'), json_output_path) if not ('search_index_only' in config['theme'] and config['theme']['search_index_only']): # Include language support files in output. Copy them directly # so that only the needed files are included. files = [] if len(self.config['lang']) > 1 or 'en' not in self.config['lang']: files.append('lunr.stemmer.support.js') if len(self.config['lang']) > 1: files.append('lunr.multi.js') for lang in self.config['lang']: if (lang != 'en'): files.append('lunr.{}.js'.format(lang)) for filename in files: from_path = os.path.join(base_path, 'lunr-language', filename) to_path = os.path.join(output_base_path, filename) utils.copy_file(from_path, to_path)
class GitAuthorsPlugin(BasePlugin): config_scheme = (('show_contribution', config_options.Type(bool, default=False)), ('show_line_count', config_options.Type(bool, default=False)), ('count_empty_lines', config_options.Type(bool, default=True)), ('sort_authors_by', config_options.Choice(['name', 'contribution'], default='name')), ('sort_reverse', config_options.Type(bool, default=False))) def __init__(self): self._repo = Repo() def on_config(self, config, **kwargs): """ Store the plugin configuration in the Repo object. The config event is the first event called on build and is run immediately after the user configuration is loaded and validated. Any alterations to the config should be made here. https://www.mkdocs.org/user-guide/plugins/#on_config NOTE: This is only the dictionary with the plugin configuration, not the global config which is passed to the various event handlers. Args: config: global configuration object Returns: (updated) configuration object """ self.repo().set_config(self.config) def on_files(self, files, config, **kwargs): """ Preprocess all markdown pages in the project. The files event is called after the files collection is populated from the docs_dir. Use this event to add, remove, or alter files in the collection. Note that Page objects have not yet been associated with the file objects in the collection. Use Page Events to manipulate page specific data. https://www.mkdocs.org/user-guide/plugins/#on_files This populates all the lines and total_lines properties of the pages and the repository. The event is executed after on_config, but before all other events. When any page or template event is called, all pages have already been parsed and their statistics been aggregated. So in any on_page_XXX event the contributions of an author to the current page *and* the repository as a whole are available. Args: files: global files collection config: global configuration object Returns: global files collection """ for file in files: path = file.abs_src_path if path.endswith('.md'): _ = self.repo().page(path) def on_page_content(self, html, page, config, files, **kwargs): """ Replace jinja tag {{ git_authors_list }} in HTML. The page_content event is called after the Markdown text is rendered to HTML (but before being passed to a template) and can be used to alter the HTML body of the page. https://www.mkdocs.org/user-guide/plugins/#on_page_content We replace the authors list in this event in order to be able to replace it with arbitrary HTML content (which might otherwise end up in styled HTML in a code block). Args: html: the processed HTML of the page page: mkdocs.nav.Page instance config: global configuration object site_navigation: global navigation object Returns: str: HTML text of page as string """ list_pattern = re.compile(r"\{\{\s*git_authors_list\s*\}\}", flags=re.IGNORECASE) if list_pattern.search(html): html = list_pattern.sub( util.repo_authors_summary(self.repo().get_authors(), self.config), html) return html def on_page_markdown(self, markdown, page, config, files): """ Replace jinja tag {{ git_authors_summary }} in markdown. The page_markdown event is called after the page's markdown is loaded from file and can be used to alter the Markdown source text. The meta- data has been stripped off and is available as page.meta at this point. https://www.mkdocs.org/user-guide/plugins/#on_page_markdown Args: markdown (str): Markdown source text of page as string page: mkdocs.nav.Page instance config: global configuration object site_navigation: global navigation object Returns: str: Markdown source text of page as string """ summary_pattern = re.compile(r"\{\{\s*git_authors_summary\s*\}\}", flags=re.IGNORECASE) if not summary_pattern.search(markdown): return markdown page_obj = self.repo().page(page.file.abs_src_path) return summary_pattern.sub(page_obj.authors_summary(), markdown) def on_page_context(self, context, page, config, nav, **kwargs): """ Add 'git_authors' and 'git_authors_summary' variables to template context. The page_context event is called after the context for a page is created and can be used to alter the context for that specific page only. https://www.mkdocs.org/user-guide/plugins/#on_page_context Note this is called *after* on_page_markdown() Args: context (dict): template context variables page (class): mkdocs.nav.Page instance config: global configuration object nav: global navigation object Returns: dict: template context variables """ path = page.file.abs_src_path page_obj = self.repo().page(path) authors = page_obj.get_authors() # NOTE: last_datetime is currently given as a # string in the format # '2020-02-24 17:49:14 +0100' # omitting the 'str' argument would result in a # datetime.datetime object with tzinfo instead. # Should this be formatted differently? context['git_authors'] = [{ 'name': author.name(), 'email': author.email(), 'last_datetime': author.datetime(path, str), 'lines': author.lines(path), 'lines_all_pages': author.lines(), 'contribution': author.contribution(path, str), 'contribution_all_pages': author.contribution(None, str) } for author in authors] context['git_authors_summary'] = page_obj.authors_summary() return context def repo(self): """ Reference to the Repo object of the current project. """ return self._repo