diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index 924e6207..4b9c5b8d 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -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 \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 930ccd95..e092f9ec 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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, Tom Christie. google_analytics: ['UA-27795084-5', 'mkdocs.org'] diff --git a/mkdocs/build.py b/mkdocs/build.py index 73d9a309..70f60e7a 100644 --- a/mkdocs/build.py +++ b/mkdocs/build.py @@ -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) diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py index c6ac2910..6c47197b 100644 --- a/mkdocs/config/config_options.py +++ b/mkdocs/config/config_options.py @@ -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 diff --git a/mkdocs/config/defaults.py b/mkdocs/config/defaults.py index 9d7bcd6d..2c2d929f 100644 --- a/mkdocs/config/defaults.py +++ b/mkdocs/config/defaults.py @@ -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. diff --git a/mkdocs/tests/build_tests.py b/mkdocs/tests/build_tests.py index 470dac46..d474925b 100644 --- a/mkdocs/tests/build_tests.py +++ b/mkdocs/tests/build_tests.py @@ -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("""
An internal link to another document.
' - 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 = '' - 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 = 'An internal link to another document.
' - 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 = 'An internal link to another document.
' - 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 My example link\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, '') def test_ignore_external_link(self): md_text = 'An [external link](http://example.com/external.md).' expected = 'An external link.
' - 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("""