Пример #1
0
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)
Пример #2
0
 def test_invalid_choice(self):
     option = config_options.Choice(('python', 'node'))
     self.assertRaises(config_options.ValidationError, option.validate,
                       'go')
Пример #3
0
 def test_valid_choice(self):
     option = config_options.Choice(('python', 'node'))
     value = option.validate('python')
     self.assertEqual(value, 'python')
Пример #4
0
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)
Пример #5
0
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