Пример #1
0
    def test_dir_unicode(self):
        cfg = Config(
            [('dir', config_options.Dir())],
            config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
        )

        test_config = {
            'dir': 'юникод'
        }

        cfg.load_dict(test_config)

        fails, warns = cfg.validate()

        self.assertEqual(len(fails), 0)
        self.assertEqual(len(warns), 0)
        self.assertIsInstance(cfg['dir'], str)
Пример #2
0
    def test_dir_filesystemencoding(self):
        cfg = Config(
            [('dir', config_options.Dir())],
            config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
        )

        test_config = {
            'dir': 'Übersicht'.encode(encoding=sys.getfilesystemencoding())
        }

        cfg.load_dict(test_config)

        fails, warns = cfg.validate()

        # str does not include byte strings so validation fails
        self.assertEqual(len(fails), 1)
        self.assertEqual(len(warns), 0)
Пример #3
0
    def test_config_dir_prepended(self):
        base_path = os.path.abspath('.')
        cfg = Config(
            [('dir', config_options.Dir())],
            config_file_path=os.path.join(base_path, 'mkdocs.yml'),
        )

        test_config = {'dir': 'foo'}

        cfg.load_dict(test_config)

        fails, warns = cfg.validate()

        self.assertEqual(len(fails), 0)
        self.assertEqual(len(warns), 0)
        self.assertIsInstance(cfg['dir'], str)
        self.assertEqual(cfg['dir'], os.path.join(base_path, 'foo'))
    def test_doc_dir_in_site_dir(self):

        j = os.path.join
        option = config_options.SiteDir()
        docs_dir = config_options.Dir()
        # The parent dir is not the same on every system, so use the actual dir name
        parent_dir = mkdocs.__file__.split(os.sep)[-3]

        test_configs = (
            {
                'docs_dir': j('site', 'docs'),
                'site_dir': 'site'
            },
            {
                'docs_dir': 'docs',
                'site_dir': '.'
            },
            {
                'docs_dir': '.',
                'site_dir': '.'
            },
            {
                'docs_dir': 'docs',
                'site_dir': ''
            },
            {
                'docs_dir': '',
                'site_dir': ''
            },
            {
                'docs_dir': j('..', parent_dir, 'docs'),
                'site_dir': 'docs'
            },
        )

        for test_config in test_configs:

            test_config['docs_dir'] = docs_dir.validate(
                test_config['docs_dir'])
            test_config['site_dir'] = option.validate(test_config['site_dir'])

            self.assertRaises(config_options.ValidationError,
                              option.post_validation, test_config, 'key')
Пример #5
0
    def test_dir_bad_encoding_fails(self):
        cfg = Config(
            [('dir', config_options.Dir())],
            config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
        )

        test_config = {'dir': 'юникод'.encode(encoding='ISO 8859-5')}

        cfg.load_dict(test_config)

        fails, warns = cfg.validate()

        if sys.platform.startswith('win') and not utils.PY3:
            # PY2 on Windows seems to be able to decode anything we give it.
            # But that just means less possable errors for those users so we allow it.
            self.assertEqual(len(fails), 0)
        else:
            self.assertEqual(len(fails), 1)
        self.assertEqual(len(warns), 0)
Пример #6
0
    def test_doc_dir_in_site_dir(self):

        j = os.path.join
        option = config_options.SiteDir()
        docs_dir = config_options.Dir()

        test_configs = (
            {
                'docs_dir': j('site', 'docs'),
                'site_dir': 'site'
            },
            {
                'docs_dir': 'docs',
                'site_dir': '.'
            },
            {
                'docs_dir': '.',
                'site_dir': '.'
            },
            {
                'docs_dir': 'docs',
                'site_dir': ''
            },
            {
                'docs_dir': '',
                'site_dir': ''
            },
            {
                'docs_dir': j('..', 'mkdocs', 'docs'),
                'site_dir': 'docs'
            },
        )

        for test_config in test_configs:

            test_config['docs_dir'] = docs_dir.validate(
                test_config['docs_dir'])
            test_config['site_dir'] = option.validate(test_config['site_dir'])

            self.assertRaises(config_options.ValidationError,
                              option.post_validation, test_config, 'key')
    def test_site_dir_in_docs_dir(self):

        j = os.path.join

        test_configs = (
            {'docs_dir': 'docs', 'site_dir': j('docs', 'site')},
            {'docs_dir': '.', 'site_dir': 'site'},
            {'docs_dir': '', 'site_dir': 'site'},
        )

        for test_config in test_configs:
            test_config['config_file_path'] = j(os.path.abspath('..'), 'mkdocs.yml')

            docs_dir = config_options.Dir()
            option = config_options.SiteDir()

            test_config['docs_dir'] = docs_dir.validate(test_config['docs_dir'])
            test_config['site_dir'] = option.validate(test_config['site_dir'])

            self.assertRaises(config_options.ValidationError,
                              option.post_validation, test_config, 'site_dir')
Пример #8
0
class DummyPlugin(plugins.BasePlugin):
    config_scheme = (
        ('foo', config_options.Type(str, default='default foo')),
        ('bar', config_options.Type(int, default=0)),
        ('dir', config_options.Dir(exists=False)),
    )

    def on_pre_page(self, content, **kwargs):
        """ modify page content by prepending `foo` config value. """
        return f'{self.config["foo"]} {content}'

    def on_nav(self, item, **kwargs):
        """ do nothing (return None) to not modify item. """
        return None

    def on_page_read_source(self, **kwargs):
        """ create new source by prepending `foo` config value to 'source'. """
        return f'{self.config["foo"]} source'

    def on_pre_build(self, **kwargs):
        """ do nothing (return None). """
        return None
Пример #9
0
    def validate_config(self, config):
        """ Given a config with values for site_dir and doc_dir, run site_dir post_validation. """
        site_dir = config_options.SiteDir()
        docs_dir = config_options.Dir()

        fname = os.path.join(os.path.abspath('..'), 'mkdocs.yml')

        config['docs_dir'] = docs_dir.validate(config['docs_dir'])
        config['site_dir'] = site_dir.validate(config['site_dir'])

        schema = [
            ('site_dir', site_dir),
            ('docs_dir', docs_dir),
        ]
        cfg = Config(schema, fname)
        cfg.load_dict(config)
        failed, warned = cfg.validate()

        if failed:
            raise config_options.ValidationError(failed)

        return True
Пример #10
0
    def test_site_dir_in_docs_dir(self):

        j = os.path.join

        test_configs = (
            {'docs_dir': 'docs', 'site_dir': j('docs', 'site')},
            {'docs_dir': '.', 'site_dir': 'site'},
            {'docs_dir': '', 'site_dir': 'site'},
        )

        for test_config in test_configs:

            docs_dir = config_options.Dir()
            option = config_options.SiteDir()

            test_config['docs_dir'] = docs_dir.validate(test_config['docs_dir'])
            test_config['site_dir'] = option.validate(test_config['site_dir'])

            option.post_validation(test_config, 'key')
            self.assertEqual(len(option.warnings), 1)
            self.assertEqual(
                option.warnings[0][:50],
                "The 'site_dir' should not be within the 'docs_dir'")
Пример #11
0
    def test_dir_filesystemencoding(self):
        cfg = Config(
            [('dir', config_options.Dir())],
            config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
        )

        test_config = {
            'dir': 'Übersicht'.encode(encoding=sys.getfilesystemencoding())
        }

        cfg.load_dict(test_config)

        fails, warns = cfg.validate()

        if utils.PY3:
            # In PY3 string_types does not include byte strings so validation fails
            self.assertEqual(len(fails), 1)
            self.assertEqual(len(warns), 0)
        else:
            # In PY2 string_types includes byte strings so validation passes
            # This test confirms that the byte string is properly decoded
            self.assertEqual(len(fails), 0)
            self.assertEqual(len(warns), 0)
            self.assertIsInstance(cfg['dir'], utils.text_type)
 def test_incorrect_type_type_error(self):
     option = config_options.Dir()
     self.assertRaises(config_options.ValidationError, option.validate, [])
    def test_missing_dir_but_required(self):

        d = os.path.join("not", "a", "real", "path", "I", "hope")
        option = config_options.Dir(exists=True)
        self.assertRaises(config_options.ValidationError, option.validate, d)
 def test_file(self):
     d = __file__
     option = config_options.Dir(exists=True)
     self.assertRaises(config_options.ValidationError, option.validate, d)
    def test_missing_dir(self):

        d = os.path.join("not", "a", "real", "path", "I", "hope")
        option = config_options.Dir()
        value = option.validate(d)
        self.assertEqual(os.path.abspath(d), value)
    def test_valid_dir(self):

        d = os.path.dirname(__file__)
        option = config_options.Dir(exists=True)
        value = option.validate(d)
        self.assertEqual(d, value)
Пример #17
0
class BibTexPlugin(BasePlugin):
    """
    Allows the use of bibtex in markdown content for MKDocs.

    Options:
        bib_file (string): path or url to a single bibtex file for entries,
                           url example: https://api.zotero.org/*/items?format=bibtex
        bib_dir (string): path to a directory of bibtex files for entries
        bib_command (string): command to place a bibliography relevant to just that file
                              defaults to \bibliography
        bib_by_default (bool): automatically appends bib_command to markdown pages
                               by default, defaults to true
        full_bib_command (string): command to place a full bibliography of all references
        csl_file (string, optional): path or url to a CSL file, relative to mkdocs.yml.
        cite_inline (bool): Whether or not to render inline citations, requires CSL, defaults to false
    """

    config_scheme = [
        ("bib_file", config_options.Type(str, required=False)),
        ("bib_dir", config_options.Dir(exists=True, required=False)),
        ("bib_command", config_options.Type(str, default="\\bibliography")),
        ("bib_by_default", config_options.Type(bool, default=True)),
        ("full_bib_command",
         config_options.Type(str, default="\\full_bibliography")),
        ("csl_file", config_options.Type(str, default='')),
        ("cite_inline", config_options.Type(bool, default=False)),
    ]

    def __init__(self):
        self.bib_data = None
        self.all_references = OrderedDict()
        self.unescape_for_arithmatex = False

    def on_config(self, config):
        """
        Loads bibliography on load of config
        """

        bibfiles = []

        # Set bib_file from either url or path
        if self.config.get("bib_file", None) is not None:
            is_url = validators.url(self.config["bib_file"])
            # if bib_file is a valid URL, cache it with tempfile
            if is_url:
                bibfiles.append(
                    tempfile_from_url(self.config["bib_file"], '.bib'))
            else:
                bibfiles.append(self.config["bib_file"])
        elif self.config.get("bib_dir", None) is not None:
            bibfiles.extend(Path(self.config["bib_dir"]).glob("*.bib"))
        else:
            raise Exception(
                "Must supply a bibtex file or directory for bibtex files")

        # load bibliography data
        refs = {}
        for bibfile in bibfiles:
            bibdata = parse_file(bibfile)
            refs.update(bibdata.entries)

        self.bib_data = BibliographyData(entries=refs)

        # Set CSL from either url or path (or empty)
        is_url = validators.url(self.config["csl_file"])
        if is_url:
            self.csl_file = tempfile_from_url(self.config["csl_file"], '.csl')
        else:
            self.csl_file = self.config.get("csl_file", None)

        # Toggle whether or not to render citations inline (Requires CSL)
        self.cite_inline = self.config.get("cite_inline", False)
        if self.cite_inline and not self.csl_file:
            raise Exception(
                "Must supply a CSL file in order to use cite_inline")

        return config

    def on_page_markdown(self, markdown, page, config, files):
        """
        Parses the markdown for each page, extracting the bibtex references
        If a local reference list is requested, this will render that list where requested

        1. Finds all cite keys (may include multiple citation references)
        2. Convert all cite keys to citation quads:
            (full cite key,
            induvidual cite key,
            citation key in corresponding style,
            citation for induvidual cite key)
        3. Insert formatted cite keys into text
        4. Insert the bibliography into the markdown
        5. Insert the full bibliograph into the markdown
        """

        # 1. Grab all the cited keys in the markdown
        cite_keys = find_cite_blocks(markdown)

        # 2. Convert all the citations to text references
        citation_quads = self.format_citations(cite_keys)

        # 3. Convert cited keys to citation,
        # or a footnote reference if inline_cite is false.
        if self.cite_inline:
            markdown = insert_citation_keys(citation_quads, markdown,
                                            self.csl_file,
                                            self.bib_data.to_string("bibtex"))
        else:
            markdown = insert_citation_keys(citation_quads, markdown)

        # 4. Insert in the bibliopgrahy text into the markdown
        bib_command = self.config.get("bib_command", "\\bibliography")

        if self.config.get("bib_by_default"):
            markdown += f"\n{bib_command}"

        bibliography = format_bibliography(citation_quads)
        markdown = re.sub(
            re.escape(bib_command),
            bibliography,
            markdown,
        )

        # 5. Build the full Bibliography and insert into the text
        full_bib_command = self.config.get("full_bib_command",
                                           "\\full_bibliography")

        markdown = re.sub(
            re.escape(full_bib_command),
            self.full_bibliography,
            markdown,
        )

        return markdown

    def format_citations(self, cite_keys):
        """
        Formats references into citation quads and adds them to the global registry

        Args:
            cite_keys (list): List of full cite_keys that maybe compound keys

        Returns:
            citation_quads: quad tuples of the citation inforamtion
        """

        # Deal with arithmatex fix at some point

        # 1. Extract the keys from the keyset
        entries = OrderedDict()
        pairs = [[cite_block, key] for cite_block in cite_keys
                 for key in extract_cite_keys(cite_block)]
        keys = list(OrderedDict.fromkeys([k for _, k in pairs]).keys())
        numbers = {k: str(n + 1) for n, k in enumerate(keys)}

        # Remove non-existant keys from pairs
        pairs = [p for p in pairs if p[1] in self.bib_data.entries]

        # 2. Collect any unformatted reference keys
        for _, key in pairs:
            if key not in self.all_references:
                entries[key] = self.bib_data.entries[key]

        # 3. Format entries
        if self.csl_file:
            self.all_references.update(format_pandoc(entries, self.csl_file))
        else:
            self.all_references.update(format_simple(entries))

        # 4. Construct quads
        quads = [(cite_block, key, numbers[key], self.all_references[key])
                 for cite_block, key in pairs]

        # List the quads in order to remove duplicate entries
        return list(dict.fromkeys(quads))

    @property
    def full_bibliography(self):
        """
        Returns the full bibliography text
        """

        bibliography = []
        for number, (key, citation) in enumerate(self.all_references.items()):
            bibliography_text = "[^{}]: {}".format(number, citation)
            bibliography.append(bibliography_text)

        return "\n".join(bibliography)
Пример #18
0
def get_schema():
    return (

        # Reserved for internal use, stores the mkdocs.yml config file.
        ('config_file_path', config_options.Type(str)),

        # The title to use for the documentation
        ('site_name', config_options.Type(str, required=True)),

        # Defines the structure of the navigation.
        ('nav', config_options.Nav()),
        # TODO: remove this when the `pages` config setting is fully deprecated.
        ('pages', config_options.Nav()),

        # The full URL to where the documentation will be hosted
        ('site_url', config_options.URL(is_dir=True)),

        # A description for the documentation project that will be added to the
        # HTML meta tags.
        ('site_description', config_options.Type(str)),
        # The name of the author to add to the HTML meta tags
        ('site_author', config_options.Type(str)),

        # The MkDocs theme for the documentation.
        ('theme', config_options.Theme(default='mkdocs')),

        # The directory containing the documentation markdown.
        ('docs_dir', config_options.Dir(default='docs', exists=True)),

        # The directory where the site will be built to
        ('site_dir', config_options.SiteDir(default='site')),

        # A copyright notice to add to the footer of documentation.
        ('copyright', config_options.Type(str)),

        # set of values for Google analytics containing the account IO and domain,
        # this should look like, ['UA-27795084-5', 'mkdocs.org']
        ('google_analytics', config_options.Type(list, length=2)),

        # The address on which to serve the live reloading docs server.
        ('dev_addr', config_options.IpAddress(default='127.0.0.1:8000')),

        # If `True`, use `<page_name>/index.hmtl` style files with hyperlinks to
        # the directory.If `False`, use `<page_name>.html style file with
        # hyperlinks to the file.
        # True generates nicer URLs, but False is useful if browsing the output on
        # a filesystem.
        ('use_directory_urls', config_options.Type(bool, default=True)),

        # Specify a link to the project source repo to be included
        # in the documentation pages.
        ('repo_url', config_options.RepoURL()),

        # A name to use for the link to the project source repo.
        # Default, If repo_url is unset then None, otherwise
        # "GitHub", "Bitbucket" or "GitLab" for known url or Hostname
        # for unknown urls.
        ('repo_name', config_options.Type(str)),

        # Specify a URI to the docs dir in the project source repo, relative to the
        # repo_url. When set, a link directly to the page in the source repo will
        # be added to the generated HTML. If repo_url is not set also, this option
        # is ignored.
        ('edit_uri', config_options.Type(str)),

        # Specify which css or javascript files from the docs directory should be
        # additionally included in the site.
        ('extra_css', config_options.Type(list, default=[])),
        ('extra_javascript', config_options.Type(list, default=[])),

        # Similar to the above, but each template (HTML or XML) will be build with
        # Jinja2 and the global context.
        ('extra_templates', config_options.Type(list, default=[])),

        # PyMarkdown extension names.
        ('markdown_extensions', config_options.MarkdownExtensions(
            builtins=['toc', 'tables', 'fenced_code'],
            configkey='mdx_configs', default=[])),

        # PyMarkdown Extension Configs. For internal use only.
        ('mdx_configs', config_options.Private()),

        # enabling strict mode causes MkDocs to stop the build when a problem is
        # encountered rather than display an error.
        ('strict', config_options.Type(bool, default=False)),

        # the remote branch to commit to when using gh-deploy
        ('remote_branch', config_options.Type(
            str, default='gh-pages')),

        # the remote name to push to when using gh-deploy
        ('remote_name', config_options.Type(str, default='origin')),

        # extra is a mapping/dictionary of data that is passed to the template.
        # This allows template authors to require extra configuration that not
        # relevant to all themes and doesn't need to be explicitly supported by
        # MkDocs itself. A good example here would be including the current
        # project version.
        ('extra', config_options.SubConfig()),

        # a list of plugins. Each item may contain a string name or a key value pair.
        # A key value pair should be the string name (as the key) and a dict of config
        # options (as the value).
        ('plugins', config_options.Plugins(default=['search'])),
    )
Пример #19
0
 def test_incorrect_type_attribute_error(self):
     option = config_options.Dir()
     with self.assertRaises(config_options.ValidationError):
         option.validate(1)
Пример #20
0
    ('site_url', config_options.URL()),

    # A description for the documentation project that will be added to the
    # HTML meta tags.
    # The name of the keywords to add to the HTML meta tags
    ('site_keywords', config_options.Type(str)),
    # The name of the keywords to add to the HTML meta tags
    ('site_description', config_options.Type(str)),
    # The name of the author to add to the HTML meta tags
    ('site_author', config_options.Type(str)),

    # The MkDocs theme for the documentation.
    ('theme', config_options.Theme(default='mkdocs')),

    # The directory containing the documentation markdown.
    ('docs_dir', config_options.Dir(default='docs', exists=True)),

    # The directory where the site will be built to
    ('site_dir', config_options.SiteDir(default='site')),

    # A copyright notice to add to the footer of documentation.
    ('copyright', config_options.Type(str)),

    # set of values for Google analytics containing the account IO and domain,
    # this should look like, ['UA-27795084-5', 'mkdocs.org']
    ('google_analytics', config_options.Type(list, length=2)),

    # The address on which to serve the live reloading docs server.
    ('dev_addr', config_options.IpAddress(default='127.0.0.1:8000')),

    # If `True`, use `<page_name>/index.hmtl` style files with hyperlinks to
Пример #21
0
    ('pages', config_options.Nav()),

    # The full URL to where the documentation will be hosted
    ('site_url', config_options.URL()),

    # A description for the documentation project that will be added to the
    # HTML meta tags.
    ('site_description', config_options.Type(utils.string_types)),
    # The name of the author to add to the HTML meta tags
    ('site_author', config_options.Type(utils.string_types)),

    # The MkDocs theme for the documentation.
    ('theme', config_options.Theme(default='mkdocs')),

    # The directory containing the documentation markdown.
    ('docs_dir', config_options.Dir(default='source', exists=True)),
    ('source_dir', config_options.Dir(default='source', exists=True)),

    # The directory where the site will be built to
    ('site_dir', config_options.SiteDir(default='site')),

    # A copyright notice to add to the footer of documentation.
    ('copyright', config_options.Type(utils.string_types)),

    # set of values for Google analytics containing the account IO and domain,
    # this should look like, ['UA-27795084-5', 'mkdocs.org']
    ('google_analytics', config_options.Type(list, length=2)),

    # The address on which to serve the live reloading docs server.
    ('dev_addr', config_options.IpAddress(default='127.0.0.1:8000')),