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:
Waylan Limberg
2015-05-15 00:07:55 -04:00
parent 93a181a208
commit a6fc4f9420
8 changed files with 345 additions and 94 deletions

View File

@@ -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

View File

@@ -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 &copy; 2014, <a href="https://twitter.com/_tomchristie">Tom Christie</a>.
google_analytics: ['UA-27795084-5', 'mkdocs.org']

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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 = '![The initial MkDocs layout](img/initial-layout.png)'
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 = '![The initial MkDocs layout](/img/initial-layout.png)'
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">&para;</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

View File

@@ -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']
)

View File

@@ -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)