mirror of
https://github.com/mkdocs/mkdocs.git
synced 2026-03-27 09:58:31 +07:00
Refactor Markdown Extension Options Config.
Config validation now handles all extension processing: * Builtin extensions are defined and within the default scheme. * User extensions are defined only as a list in the config. Note, this is a backward incompatable change from the previous (short-lived) release. * The users extensions are added to the list of builtins. * Any duplicates are accounted for in validation. * Extension options are supported by a child key/value pair on the ext name. * All extension options are compiled into a format Markdown accepts within the validation process and are saved to the internal `mdx_configs` config setting. * The `mdx_configs` setting is private and raises an error if set by the user. * A whole suite of tests were added to test all aspects of ext validation. All relevant build tests were updated to pass the config to `build.convert_markdown` as the config now handles all extension data. The relevant documentation was updated to reflect the changes. While I was at it, I spellchecked the entire document and made a few additional formatting changes. This fixes #519 plus a lot more.
This commit is contained in:
@@ -8,7 +8,7 @@ Guide to all available configuration settings.
|
||||
|
||||
Project settings are always configured by using a YAML configuration file in the project directory named `mkdocs.yml`.
|
||||
|
||||
As a miniumum this configuration file must contain the `site_name` setting. All other settings are optional.
|
||||
As a minimum this configuration file must contain the `site_name` setting. All other settings are optional.
|
||||
|
||||
## Project information
|
||||
|
||||
@@ -16,7 +16,7 @@ As a miniumum this configuration file must contain the `site_name` setting. All
|
||||
|
||||
This is a **required setting**, and should be a string that is used as the main title for the project documentation. For example:
|
||||
|
||||
site_name: Mashmallow Generator
|
||||
site_name: Marshmallow Generator
|
||||
|
||||
When rendering the theme this setting will be passed as the `site_name` context variable.
|
||||
|
||||
@@ -109,9 +109,9 @@ If you have a lot of project documentation you might choose to use headings to b
|
||||
pages:
|
||||
- Introduction: 'index.md'
|
||||
- User Guide:
|
||||
- 'Creating a new Mashmallow project': 'user-guide/creating.md'
|
||||
- 'Mashmallow API guide': 'user-guide/api.md'
|
||||
- 'Configuring Mashmallow': 'user-guide/configuration.md'
|
||||
- 'Creating a new Marshmallow project': 'user-guide/creating.md'
|
||||
- 'Marshmallow API guide': 'user-guide/api.md'
|
||||
- 'Configuring Marshmallow': 'user-guide/configuration.md'
|
||||
- About:
|
||||
- License: 'about/license.md'
|
||||
|
||||
@@ -150,16 +150,21 @@ Lets you set the directory where the output HTML and other files are created. T
|
||||
|
||||
**default**: `'site'`
|
||||
|
||||
**Note**: If you are using source code control you will normally want to ensure that your *build output* files are not commited into the repository, and only keep the *source* files under version control. For example, if using `git` you might add the following line to your `.gitignore` file:
|
||||
!!! note "Note:"
|
||||
If you are using source code control you will normally want to ensure
|
||||
that your *build output* files are not committed into the repository, and only
|
||||
keep the *source* files under version control. For example, if using `git` you
|
||||
might add the following line to your `.gitignore` file:
|
||||
|
||||
site/
|
||||
site/
|
||||
|
||||
If you're using another source code control you'll want to check its documentation on how to ignore specific directories.
|
||||
If you're using another source code control you'll want to check its
|
||||
documentation on how to ignore specific directories.
|
||||
|
||||
|
||||
### extra_css
|
||||
|
||||
Set a list of css files to be included by the theme.
|
||||
Set a list of CSS files to be included by the theme.
|
||||
|
||||
**default**: By default `extra_css` will contain a list of all the CSS files found within the `docs_dir`, if none are found it will be `[]` (an empty list).
|
||||
|
||||
@@ -207,7 +212,7 @@ Determines if a broken link to a page within the documentation is considered a w
|
||||
|
||||
Determines the address used when running `mkdocs serve`. Setting this allows you to use another port, or allows you to make the service accessible over your local network by using the `0.0.0.0` address.
|
||||
|
||||
As with all settings, you can set this from the command line, which can be usful, for example:
|
||||
As with all settings, you can set this from the command line, which can be useful, for example:
|
||||
|
||||
mkdocs serve --dev-addr=0.0.0.0:80 # Run on port 80, accessible over the local network.
|
||||
|
||||
@@ -217,30 +222,62 @@ As with all settings, you can set this from the command line, which can be usful
|
||||
|
||||
### markdown_extensions
|
||||
|
||||
MkDocs uses the [Python Markdown][pymkd] library to translate Markdown files into HTML. Python Markdown supports a variety of [extensions][pymdk-extensions] that customize how pages are formatted. This setting lets you enable a list of extensions beyond the ones that MkDocs uses by default (`meta`, `toc`, `tables`, and `fenced_code`).
|
||||
MkDocs uses the [Python Markdown][pymkd] library to translate Markdown files
|
||||
into HTML. Python Markdown supports a variety of [extensions][pymdk-extensions]
|
||||
that customize how pages are formatted. This setting lets you enable a list of
|
||||
extensions beyond the ones that MkDocs uses by default (`meta`, `toc`, `tables`,
|
||||
and `fenced_code`).
|
||||
|
||||
For example, to enable the [SmartyPants typography extension][smarty], use:
|
||||
|
||||
markdown_extensions: [smartypants]
|
||||
markdown_extensions:
|
||||
- smarty
|
||||
|
||||
Some extensions provide configuration options of their own. If you would like to set any configuration options, then you can define `markdown_extensions` as a key/value mapping rather than a list. The key must be the name of the extension and the value must be a key/value pair (option name/option value) for the configuration option.
|
||||
Some extensions provide configuration options of their own. If you would like to
|
||||
set any configuration options, then you can nest a key/value mapping
|
||||
(`option_name: option value`) of any options that a given extension supports.
|
||||
See the documentation for the extension you are using to determine what options
|
||||
they support.
|
||||
|
||||
For example, to enable permalinks in the (included) `toc` extension, use:
|
||||
|
||||
markdown_extensions:
|
||||
toc:
|
||||
- toc:
|
||||
permalink: True
|
||||
|
||||
Add additonal items for each extension. If you have no configuration options to set for a specific extension, then you may leave that extensions options blank:
|
||||
Note that a colon (`:`) must follow the extension name (`toc`) and then on a new line
|
||||
the option name and value must be indented and seperated by a colon. If you would like
|
||||
to define multipe options for a single extension, each option must be defined on
|
||||
a seperate line:
|
||||
|
||||
markdown_extensions:
|
||||
smartypants:
|
||||
toc:
|
||||
- toc:
|
||||
permalink: True
|
||||
separator: "_"
|
||||
|
||||
Add an additional item to the list for each extension. If you have no
|
||||
configuration options to set for a specific extension, then simply omit options
|
||||
for that extension:
|
||||
|
||||
markdown_extensions:
|
||||
- smarty
|
||||
- toc:
|
||||
permalink: True
|
||||
- sane_lists
|
||||
|
||||
!!! note "See Also:"
|
||||
The Python-Markdown documentation provides a [list of extensions][exts]
|
||||
which are available out-of-the-box. For a list of configuration options
|
||||
available for a given extension, see the documentation for that extension.
|
||||
|
||||
You may also install and use various [third party extensions][3rd]. Consult the
|
||||
documentation provided by those extensions for installation instructions and
|
||||
available configuration options.
|
||||
|
||||
**default**: `[]`
|
||||
|
||||
[pymdk-extensions]: http://pythonhosted.org/Markdown/extensions/index.html
|
||||
[pymkd]: http://pythonhosted.org/Markdown/
|
||||
[smarty]: https://pypi.python.org/pypi/mdx_smartypants
|
||||
[smarty]: https://pythonhosted.org/Markdown/extensions/smarty.html
|
||||
[exts]:https://pythonhosted.org/Markdown/extensions/index.html
|
||||
[3rd]: https://github.com/waylan/Python-Markdown/wiki/Third-Party-Extensions
|
||||
@@ -5,9 +5,9 @@ site_description: Project documentation with Markdown.
|
||||
repo_url: https://github.com/mkdocs/mkdocs/
|
||||
|
||||
markdown_extensions:
|
||||
toc:
|
||||
- toc:
|
||||
permalink: ""
|
||||
admonition:
|
||||
- admonition:
|
||||
|
||||
copyright: Copyright © 2014, <a href="https://twitter.com/_tomchristie">Tom Christie</a>.
|
||||
google_analytics: ['UA-27795084-5', 'mkdocs.org']
|
||||
|
||||
@@ -17,30 +17,17 @@ import mkdocs
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def convert_markdown(markdown_source, site_navigation=None, extensions=(), strict=False):
|
||||
def convert_markdown(markdown_source, config, site_navigation=None):
|
||||
"""
|
||||
Convert the Markdown source file to HTML content, and additionally
|
||||
return the parsed table of contents, and a dictionary of any metadata
|
||||
that was specified in the Markdown file.
|
||||
|
||||
`extensions` is an optional sequence of Python Markdown extensions to add
|
||||
to the default set.
|
||||
Convert the Markdown source file to HTML as per the config and site_navigation.
|
||||
Return a tuple of the HTML as a string, the parsed table of contents,
|
||||
and a dictionary of any metadata that was specified in the Markdown file.
|
||||
"""
|
||||
|
||||
# Generate the HTML from the markdown source
|
||||
if isinstance(extensions, dict):
|
||||
user_extensions = list(extensions.keys())
|
||||
extension_configs = dict([(k, v) for k, v in extensions.items() if isinstance(v, dict)])
|
||||
else:
|
||||
user_extensions = list(extensions)
|
||||
extension_configs = {}
|
||||
builtin_extensions = ['meta', 'toc', 'tables', 'fenced_code']
|
||||
mkdocs_extensions = [RelativePathExtension(site_navigation, strict), ]
|
||||
extensions = utils.reduce_list(builtin_extensions + mkdocs_extensions + user_extensions)
|
||||
|
||||
html_content, table_of_contents, meta = utils.convert_markdown(markdown_source, extensions, extension_configs)
|
||||
|
||||
return (html_content, table_of_contents, meta)
|
||||
return utils.convert_markdown(
|
||||
markdown_source=markdown_source,
|
||||
extensions=[RelativePathExtension(site_navigation, config['strict'])] + config['markdown_extensions'],
|
||||
extension_configs=config['mdx_configs']
|
||||
)
|
||||
|
||||
|
||||
def get_global_context(nav, config):
|
||||
@@ -182,8 +169,9 @@ def _build_page(page, config, site_navigation, env, dump_json):
|
||||
|
||||
# Process the markdown text
|
||||
html_content, table_of_contents, meta = convert_markdown(
|
||||
input_content, site_navigation,
|
||||
extensions=config['markdown_extensions'], strict=config['strict']
|
||||
markdown_source=input_content,
|
||||
config=config,
|
||||
site_navigation=site_navigation
|
||||
)
|
||||
|
||||
context = get_global_context(site_navigation, config)
|
||||
|
||||
@@ -343,3 +343,56 @@ class NumPages(BaseConfigOption):
|
||||
config[key_name] = len(config['pages']) > self.at_lest
|
||||
except TypeError:
|
||||
config[key_name] = False
|
||||
|
||||
|
||||
class Private(BaseConfigOption):
|
||||
"""
|
||||
Private Config Option
|
||||
|
||||
A config option only for internal use. Raises an error if set by the user.
|
||||
"""
|
||||
|
||||
def run_validation(self, value):
|
||||
raise ValidationError('For internal use only.')
|
||||
|
||||
|
||||
class MarkdownExtensions(BaseConfigOption):
|
||||
"""
|
||||
Markdown Extensions Config Option
|
||||
|
||||
A list of extensions. If a list item contains extension configs,
|
||||
those are set on the private setting passed to `configkey`. The
|
||||
`builtins` keyword accepts a list of extensions which cannot be
|
||||
overriden by the user. However, builtins can be duplicated to define
|
||||
config options for them if desired.
|
||||
"""
|
||||
def __init__(self, builtins=None, configkey='mdx_configs', **kwargs):
|
||||
super(MarkdownExtensions, self).__init__(**kwargs)
|
||||
self.builtins = builtins or []
|
||||
self.configkey = configkey
|
||||
self.configdata = {}
|
||||
|
||||
def run_validation(self, value):
|
||||
if not isinstance(value, (list, tuple)):
|
||||
raise ValidationError('Invalid Markdown Extensions configuration')
|
||||
extensions = []
|
||||
for item in value:
|
||||
if isinstance(item, dict):
|
||||
if len(item) > 1:
|
||||
raise ValidationError('Invalid Markdown Extensions configuration')
|
||||
ext, cfg = item.popitem()
|
||||
extensions.append(ext)
|
||||
if cfg is None:
|
||||
continue
|
||||
if not isinstance(cfg, dict):
|
||||
raise ValidationError('Invalid config options for Markdown '
|
||||
"Extension '{0}'.".format(ext))
|
||||
self.configdata[ext] = cfg
|
||||
elif isinstance(item, six.string_types):
|
||||
extensions.append(item)
|
||||
else:
|
||||
raise ValidationError('Invalid Markdown Extensions configuration')
|
||||
return utils.reduce_list(self.builtins + extensions)
|
||||
|
||||
def post_validation(self, config, key_name):
|
||||
config[self.configkey] = self.configdata
|
||||
|
||||
@@ -90,8 +90,12 @@ DEFAULT_SCHEMA = (
|
||||
('include_next_prev', config_options.NumPages()),
|
||||
|
||||
# PyMarkdown extension names.
|
||||
('markdown_extensions', config_options.Type(
|
||||
(list, dict, tuple), default=())),
|
||||
('markdown_extensions', config_options.MarkdownExtensions(
|
||||
builtins=['meta', '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.
|
||||
|
||||
@@ -9,16 +9,26 @@ import unittest
|
||||
from six.moves import zip
|
||||
import mock
|
||||
|
||||
from mkdocs import build, nav
|
||||
from mkdocs.config import base as config_base, defaults as config_defaults
|
||||
from mkdocs import build, nav, config
|
||||
from mkdocs.exceptions import MarkdownNotFound
|
||||
from mkdocs.tests.base import dedent
|
||||
|
||||
|
||||
def load_config(cfg=None):
|
||||
""" Helper to build a simple config for testing. """
|
||||
cfg = cfg or {}
|
||||
if 'site_name' not in cfg:
|
||||
cfg['site_name'] = 'Example'
|
||||
conf = config.base.Config(schema=config.defaults.DEFAULT_SCHEMA)
|
||||
conf.load_dict(cfg)
|
||||
assert(conf.validate() == ([], []))
|
||||
return conf
|
||||
|
||||
|
||||
class BuildTests(unittest.TestCase):
|
||||
|
||||
def test_empty_document(self):
|
||||
html, toc, meta = build.convert_markdown("")
|
||||
html, toc, meta = build.convert_markdown("", load_config())
|
||||
|
||||
self.assertEqual(html, '')
|
||||
self.assertEqual(len(list(toc)), 0)
|
||||
@@ -28,7 +38,6 @@ class BuildTests(unittest.TestCase):
|
||||
"""
|
||||
Ensure that basic Markdown -> HTML and TOC works.
|
||||
"""
|
||||
|
||||
html, toc, meta = build.convert_markdown(dedent("""
|
||||
page_title: custom title
|
||||
|
||||
@@ -39,7 +48,7 @@ class BuildTests(unittest.TestCase):
|
||||
# Heading 2
|
||||
|
||||
And some more text.
|
||||
"""))
|
||||
"""), load_config())
|
||||
|
||||
expected_html = dedent("""
|
||||
<h1 id="heading-1">Heading 1</h1>
|
||||
@@ -62,25 +71,25 @@ class BuildTests(unittest.TestCase):
|
||||
def test_convert_internal_link(self):
|
||||
md_text = 'An [internal link](internal.md) to another document.'
|
||||
expected = '<p>An <a href="internal/">internal link</a> to another document.</p>'
|
||||
html, toc, meta = build.convert_markdown(md_text)
|
||||
html, toc, meta = build.convert_markdown(md_text, load_config())
|
||||
self.assertEqual(html.strip(), expected.strip())
|
||||
|
||||
def test_convert_multiple_internal_links(self):
|
||||
md_text = '[First link](first.md) [second link](second.md).'
|
||||
expected = '<p><a href="first/">First link</a> <a href="second/">second link</a>.</p>'
|
||||
html, toc, meta = build.convert_markdown(md_text)
|
||||
html, toc, meta = build.convert_markdown(md_text, load_config())
|
||||
self.assertEqual(html.strip(), expected.strip())
|
||||
|
||||
def test_convert_internal_link_differing_directory(self):
|
||||
md_text = 'An [internal link](../internal.md) to another document.'
|
||||
expected = '<p>An <a href="../internal/">internal link</a> to another document.</p>'
|
||||
html, toc, meta = build.convert_markdown(md_text)
|
||||
html, toc, meta = build.convert_markdown(md_text, load_config())
|
||||
self.assertEqual(html.strip(), expected.strip())
|
||||
|
||||
def test_convert_internal_link_with_anchor(self):
|
||||
md_text = 'An [internal link](internal.md#section1.1) to another document.'
|
||||
expected = '<p>An <a href="internal/#section1.1">internal link</a> to another document.</p>'
|
||||
html, toc, meta = build.convert_markdown(md_text)
|
||||
html, toc, meta = build.convert_markdown(md_text, load_config())
|
||||
self.assertEqual(html.strip(), expected.strip())
|
||||
|
||||
def test_convert_internal_media(self):
|
||||
@@ -103,7 +112,7 @@ class BuildTests(unittest.TestCase):
|
||||
|
||||
for (page, expected) in zip(site_navigation.walk_pages(), expected_results):
|
||||
md_text = ''
|
||||
html, _, _ = build.convert_markdown(md_text, site_navigation=site_navigation)
|
||||
html, _, _ = build.convert_markdown(md_text, load_config(), site_navigation=site_navigation)
|
||||
self.assertEqual(html, template % expected)
|
||||
|
||||
def test_convert_internal_asbolute_media(self):
|
||||
@@ -126,7 +135,7 @@ class BuildTests(unittest.TestCase):
|
||||
|
||||
for (page, expected) in zip(site_navigation.walk_pages(), expected_results):
|
||||
md_text = ''
|
||||
html, _, _ = build.convert_markdown(md_text, site_navigation=site_navigation)
|
||||
html, _, _ = build.convert_markdown(md_text, load_config(), site_navigation=site_navigation)
|
||||
self.assertEqual(html, template % expected)
|
||||
|
||||
def test_dont_convert_code_block_urls(self):
|
||||
@@ -146,11 +155,10 @@ class BuildTests(unittest.TestCase):
|
||||
|
||||
for page in site_navigation.walk_pages():
|
||||
markdown = 'An HTML Anchor::\n\n <a href="index.md">My example link</a>\n'
|
||||
html, _, _ = build.convert_markdown(markdown, site_navigation=site_navigation)
|
||||
html, _, _ = build.convert_markdown(markdown, load_config(), site_navigation=site_navigation)
|
||||
self.assertEqual(dedent(html), expected)
|
||||
|
||||
def test_anchor_only_link(self):
|
||||
|
||||
pages = [
|
||||
'index.md',
|
||||
'internal.md',
|
||||
@@ -161,13 +169,13 @@ class BuildTests(unittest.TestCase):
|
||||
|
||||
for page in site_navigation.walk_pages():
|
||||
markdown = '[test](#test)'
|
||||
html, _, _ = build.convert_markdown(markdown, site_navigation=site_navigation)
|
||||
html, _, _ = build.convert_markdown(markdown, load_config(), site_navigation=site_navigation)
|
||||
self.assertEqual(html, '<p><a href="#test">test</a></p>')
|
||||
|
||||
def test_ignore_external_link(self):
|
||||
md_text = 'An [external link](http://example.com/external.md).'
|
||||
expected = '<p>An <a href="http://example.com/external.md">external link</a>.</p>'
|
||||
html, toc, meta = build.convert_markdown(md_text)
|
||||
html, toc, meta = build.convert_markdown(md_text, load_config())
|
||||
self.assertEqual(html.strip(), expected.strip())
|
||||
|
||||
def test_not_use_directory_urls(self):
|
||||
@@ -177,20 +185,19 @@ class BuildTests(unittest.TestCase):
|
||||
'internal.md',
|
||||
]
|
||||
site_navigation = nav.SiteNavigation(pages, use_directory_urls=False)
|
||||
html, toc, meta = build.convert_markdown(md_text, site_navigation=site_navigation)
|
||||
html, toc, meta = build.convert_markdown(md_text, load_config(), site_navigation=site_navigation)
|
||||
self.assertEqual(html.strip(), expected.strip())
|
||||
|
||||
def test_markdown_table_extension(self):
|
||||
"""
|
||||
Ensure that the table extension is supported.
|
||||
"""
|
||||
|
||||
html, toc, meta = build.convert_markdown(dedent("""
|
||||
First Header | Second Header
|
||||
-------------- | --------------
|
||||
Content Cell 1 | Content Cell 2
|
||||
Content Cell 3 | Content Cell 4
|
||||
"""))
|
||||
"""), load_config())
|
||||
|
||||
expected_html = dedent("""
|
||||
<table>
|
||||
@@ -219,12 +226,11 @@ class BuildTests(unittest.TestCase):
|
||||
"""
|
||||
Ensure that the fenced code extension is supported.
|
||||
"""
|
||||
|
||||
html, toc, meta = build.convert_markdown(dedent("""
|
||||
```
|
||||
print 'foo'
|
||||
```
|
||||
"""))
|
||||
"""), load_config())
|
||||
|
||||
expected_html = dedent("""
|
||||
<pre><code>print 'foo'\n</code></pre>
|
||||
@@ -241,24 +247,29 @@ class BuildTests(unittest.TestCase):
|
||||
|
||||
# Check that the plugin is not active when not requested.
|
||||
expected_without_smartstrong = "<p>foo<strong>bar</strong>baz</p>"
|
||||
html_base, _, _ = build.convert_markdown(md_input)
|
||||
html_base, _, _ = build.convert_markdown(md_input, load_config())
|
||||
self.assertEqual(html_base.strip(), expected_without_smartstrong)
|
||||
|
||||
# Check that the plugin is active when requested.
|
||||
cfg = load_config({
|
||||
'markdown_extensions': ['smart_strong']
|
||||
})
|
||||
expected_with_smartstrong = "<p>foo__bar__baz</p>"
|
||||
html_ext, _, _ = build.convert_markdown(md_input, extensions=['smart_strong'])
|
||||
html_ext, _, _ = build.convert_markdown(md_input, cfg)
|
||||
self.assertEqual(html_ext.strip(), expected_with_smartstrong)
|
||||
|
||||
def test_markdown_duplicate_custom_extension(self):
|
||||
"""
|
||||
Duplicated extension names should not cause problems.
|
||||
"""
|
||||
cfg = load_config({
|
||||
'markdown_extensions': ['toc']
|
||||
})
|
||||
md_input = "foo"
|
||||
html_ext, _, _ = build.convert_markdown(md_input, ['toc'])
|
||||
html_ext, _, _ = build.convert_markdown(md_input, cfg)
|
||||
self.assertEqual(html_ext.strip(), '<p>foo</p>')
|
||||
|
||||
def test_copying_media(self):
|
||||
|
||||
docs_dir = tempfile.mkdtemp()
|
||||
site_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
@@ -281,14 +292,11 @@ class BuildTests(unittest.TestCase):
|
||||
os.mkdir(os.path.join(docs_dir, '.git'))
|
||||
open(os.path.join(docs_dir, '.git/hidden'), 'w').close()
|
||||
|
||||
conf = config_base.Config(schema=config_defaults.DEFAULT_SCHEMA)
|
||||
conf.load_dict({
|
||||
'site_name': 'Example',
|
||||
cfg = load_config({
|
||||
'docs_dir': docs_dir,
|
||||
'site_dir': site_dir
|
||||
})
|
||||
conf.validate()
|
||||
build.build(conf)
|
||||
build.build(cfg)
|
||||
|
||||
# Verify only the markdown (coverted to html) and the image are copied.
|
||||
self.assertTrue(os.path.isfile(os.path.join(site_dir, 'index.html')))
|
||||
@@ -308,8 +316,8 @@ class BuildTests(unittest.TestCase):
|
||||
site_nav = nav.SiteNavigation(pages)
|
||||
|
||||
valid = "[test](internal.md)"
|
||||
build.convert_markdown(valid, site_nav, strict=False)
|
||||
build.convert_markdown(valid, site_nav, strict=True)
|
||||
build.convert_markdown(valid, load_config({'strict': False}), site_nav)
|
||||
build.convert_markdown(valid, load_config({'strict': True}), site_nav)
|
||||
|
||||
def test_strict_mode_invalid(self):
|
||||
pages = [
|
||||
@@ -320,24 +328,24 @@ class BuildTests(unittest.TestCase):
|
||||
site_nav = nav.SiteNavigation(pages)
|
||||
|
||||
invalid = "[test](bad_link.md)"
|
||||
build.convert_markdown(invalid, site_nav, strict=False)
|
||||
build.convert_markdown(invalid, load_config({'strict': False}), site_nav)
|
||||
|
||||
self.assertRaises(
|
||||
MarkdownNotFound,
|
||||
build.convert_markdown, invalid, site_nav, strict=True)
|
||||
build.convert_markdown, invalid, load_config({'strict': True}), site_nav)
|
||||
|
||||
def test_extension_config(self):
|
||||
"""
|
||||
Test that a dictionary of 'markdown_extensions' is recognized as
|
||||
both a list of extensions and a dictionary of extnesion configs.
|
||||
"""
|
||||
markdown_extensions = {
|
||||
'toc': {'permalink': True},
|
||||
'meta': None # This gets ignored as it is an invalid config
|
||||
}
|
||||
cfg = load_config({
|
||||
'markdown_extensions': [{'toc': {'permalink': True}}]
|
||||
})
|
||||
|
||||
html, toc, meta = build.convert_markdown(dedent("""
|
||||
# A Header
|
||||
"""), extensions=markdown_extensions)
|
||||
"""), cfg)
|
||||
|
||||
expected_html = dedent("""
|
||||
<h1 id="a-header">A Header<a class="headerlink" href="#a-header" title="Permanent link">¶</a></h1>
|
||||
@@ -348,17 +356,14 @@ class BuildTests(unittest.TestCase):
|
||||
def test_extra_context(self):
|
||||
|
||||
# Same as the default schema, but don't verify the docs_dir exists.
|
||||
config = config_base.Config(schema=config_defaults.DEFAULT_SCHEMA)
|
||||
config.load_dict({
|
||||
cfg = load_config({
|
||||
'site_name': "Site",
|
||||
'extra': {
|
||||
'a': 1
|
||||
}
|
||||
})
|
||||
|
||||
self.assertEqual(config.validate(), ([], []))
|
||||
|
||||
context = build.get_global_context(mock.Mock(), config)
|
||||
context = build.get_global_context(mock.Mock(), cfg)
|
||||
|
||||
self.assertEqual(context['config']['extra'], {
|
||||
'a': 1
|
||||
|
||||
@@ -325,3 +325,170 @@ class NumPagesTest(unittest.TestCase):
|
||||
'key': True,
|
||||
'pages': None
|
||||
}, config)
|
||||
|
||||
|
||||
class PrivateTest(unittest.TestCase):
|
||||
|
||||
def test_defined(self):
|
||||
|
||||
option = config_options.Private()
|
||||
self.assertRaises(config_options.ValidationError,
|
||||
option.validate, 'somevalue')
|
||||
|
||||
|
||||
class MarkdownExtensionsTest(unittest.TestCase):
|
||||
|
||||
def test_simple_list(self):
|
||||
option = config_options.MarkdownExtensions()
|
||||
config = {
|
||||
'markdown_extensions': ['foo', 'bar']
|
||||
}
|
||||
config['markdown_extensions'] = option.validate(config['markdown_extensions'])
|
||||
option.post_validation(config, 'markdown_extensions')
|
||||
self.assertEqual({
|
||||
'markdown_extensions': ['foo', 'bar'],
|
||||
'mdx_configs': {}
|
||||
}, config)
|
||||
|
||||
def test_list_dicts(self):
|
||||
option = config_options.MarkdownExtensions()
|
||||
config = {
|
||||
'markdown_extensions': [
|
||||
{'foo': {'foo_option': 'foo value'}},
|
||||
{'bar': {'bar_option': 'bar value'}},
|
||||
{'baz': None}
|
||||
]
|
||||
}
|
||||
config['markdown_extensions'] = option.validate(config['markdown_extensions'])
|
||||
option.post_validation(config, 'markdown_extensions')
|
||||
self.assertEqual({
|
||||
'markdown_extensions': ['foo', 'bar', 'baz'],
|
||||
'mdx_configs': {
|
||||
'foo': {'foo_option': 'foo value'},
|
||||
'bar': {'bar_option': 'bar value'}
|
||||
}
|
||||
}, config)
|
||||
|
||||
def test_mixed_list(self):
|
||||
option = config_options.MarkdownExtensions()
|
||||
config = {
|
||||
'markdown_extensions': [
|
||||
'foo',
|
||||
{'bar': {'bar_option': 'bar value'}}
|
||||
]
|
||||
}
|
||||
config['markdown_extensions'] = option.validate(config['markdown_extensions'])
|
||||
option.post_validation(config, 'markdown_extensions')
|
||||
self.assertEqual({
|
||||
'markdown_extensions': ['foo', 'bar'],
|
||||
'mdx_configs': {
|
||||
'bar': {'bar_option': 'bar value'}
|
||||
}
|
||||
}, config)
|
||||
|
||||
def test_builtins(self):
|
||||
option = config_options.MarkdownExtensions(builtins=['meta', 'toc'])
|
||||
config = {
|
||||
'markdown_extensions': ['foo', 'bar']
|
||||
}
|
||||
config['markdown_extensions'] = option.validate(config['markdown_extensions'])
|
||||
option.post_validation(config, 'markdown_extensions')
|
||||
self.assertEqual({
|
||||
'markdown_extensions': ['meta', 'toc', 'foo', 'bar'],
|
||||
'mdx_configs': {}
|
||||
}, config)
|
||||
|
||||
def test_duplicates(self):
|
||||
option = config_options.MarkdownExtensions(builtins=['meta', 'toc'])
|
||||
config = {
|
||||
'markdown_extensions': ['meta', 'toc']
|
||||
}
|
||||
config['markdown_extensions'] = option.validate(config['markdown_extensions'])
|
||||
option.post_validation(config, 'markdown_extensions')
|
||||
self.assertEqual({
|
||||
'markdown_extensions': ['meta', 'toc'],
|
||||
'mdx_configs': {}
|
||||
}, config)
|
||||
|
||||
def test_builtins_config(self):
|
||||
option = config_options.MarkdownExtensions(builtins=['meta', 'toc'])
|
||||
config = {
|
||||
'markdown_extensions': [
|
||||
{'toc': {'permalink': True}}
|
||||
]
|
||||
}
|
||||
config['markdown_extensions'] = option.validate(config['markdown_extensions'])
|
||||
option.post_validation(config, 'markdown_extensions')
|
||||
self.assertEqual({
|
||||
'markdown_extensions': ['meta', 'toc'],
|
||||
'mdx_configs': {'toc': {'permalink': True}}
|
||||
}, config)
|
||||
|
||||
def test_configkey(self):
|
||||
option = config_options.MarkdownExtensions(configkey='bar')
|
||||
config = {
|
||||
'markdown_extensions': [
|
||||
{'foo': {'foo_option': 'foo value'}}
|
||||
]
|
||||
}
|
||||
config['markdown_extensions'] = option.validate(config['markdown_extensions'])
|
||||
option.post_validation(config, 'markdown_extensions')
|
||||
self.assertEqual({
|
||||
'markdown_extensions': ['foo'],
|
||||
'bar': {
|
||||
'foo': {'foo_option': 'foo value'}
|
||||
}
|
||||
}, config)
|
||||
|
||||
def test_none(self):
|
||||
option = config_options.MarkdownExtensions(default=[])
|
||||
config = {
|
||||
'markdown_extensions': None
|
||||
}
|
||||
config['markdown_extensions'] = option.validate(config['markdown_extensions'])
|
||||
option.post_validation(config, 'markdown_extensions')
|
||||
self.assertEqual({
|
||||
'markdown_extensions': [],
|
||||
'mdx_configs': {}
|
||||
}, config)
|
||||
|
||||
def test_not_list(self):
|
||||
option = config_options.MarkdownExtensions()
|
||||
self.assertRaises(config_options.ValidationError,
|
||||
option.validate, 'not a list')
|
||||
|
||||
def test_invalid_config_option(self):
|
||||
option = config_options.MarkdownExtensions()
|
||||
config = {
|
||||
'markdown_extensions': [
|
||||
{'foo': 'not a dict'}
|
||||
]
|
||||
}
|
||||
self.assertRaises(
|
||||
config_options.ValidationError,
|
||||
option.validate, config['markdown_extensions']
|
||||
)
|
||||
|
||||
def test_invalid_config_item(self):
|
||||
option = config_options.MarkdownExtensions()
|
||||
config = {
|
||||
'markdown_extensions': [
|
||||
['not a dict']
|
||||
]
|
||||
}
|
||||
self.assertRaises(
|
||||
config_options.ValidationError,
|
||||
option.validate, config['markdown_extensions']
|
||||
)
|
||||
|
||||
def test_invalid_dict_item(self):
|
||||
option = config_options.MarkdownExtensions()
|
||||
config = {
|
||||
'markdown_extensions': [
|
||||
{'key1': 'value', 'key2': 'too many keys'}
|
||||
]
|
||||
}
|
||||
self.assertRaises(
|
||||
config_options.ValidationError,
|
||||
option.validate, config['markdown_extensions']
|
||||
)
|
||||
|
||||
@@ -279,12 +279,9 @@ def convert_markdown(markdown_source, extensions=None, extension_configs=None):
|
||||
`extensions` is an optional sequence of Python Markdown extensions to add
|
||||
to the default set.
|
||||
"""
|
||||
extensions = extensions or []
|
||||
extension_configs = extension_configs or {}
|
||||
|
||||
md = markdown.Markdown(
|
||||
extensions=extensions,
|
||||
extension_configs=extension_configs
|
||||
extensions=extensions or [],
|
||||
extension_configs=extension_configs or {}
|
||||
)
|
||||
html_content = md.convert(markdown_source)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user