diff --git a/mkdocs/build.py b/mkdocs/build.py index cf50b551..e1262d86 100644 --- a/mkdocs/build.py +++ b/mkdocs/build.py @@ -2,59 +2,22 @@ from __future__ import print_function from datetime import datetime - -from jinja2.exceptions import TemplateNotFound -import mkdocs -from mkdocs import nav, toc, utils -from mkdocs.compat import urljoin -from mkdocs.relative_path_ext import RelativePathExtension -import jinja2 import json -import markdown import os import logging from io import open +from jinja2.exceptions import TemplateNotFound +import jinja2 + +import mkdocs +from mkdocs import nav, utils +from mkdocs.compat import urljoin +from mkdocs.utils import convert_markdown + log = logging.getLogger('mkdocs') -def convert_markdown(markdown_source, site_navigation=None, extensions=(), strict=False): - """ - 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. - """ - - # 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 = set(builtin_extensions + mkdocs_extensions + user_extensions) - md = markdown.Markdown( - extensions=extensions, - extension_configs=extension_configs - ) - html_content = md.convert(markdown_source) - - # On completely blank markdown files, no Meta or tox properties are added - # to the generated document. - meta = getattr(md, 'Meta', {}) - toc_html = getattr(md, 'toc', '') - - # Post process the generated table of contents into a data structure - table_of_contents = toc.TableOfContents(toc_html) - - return (html_content, table_of_contents, meta) - - def get_global_context(nav, config): """ Given the SiteNavigation and config, generate the context which is relevant diff --git a/mkdocs/nav.py b/mkdocs/nav.py index 407c8bf2..85528e77 100644 --- a/mkdocs/nav.py +++ b/mkdocs/nav.py @@ -17,12 +17,13 @@ log = logging.getLogger(__name__) def file_to_tile(filename): """ Automatically generate a default title, given a filename. + + The method parses the file to check for a title, uses the filename + as a title otherwise. """ if utils.is_homepage(filename): return 'Home' - #todo check if there's a header in the doc first - title = os.path.splitext(filename)[0] title = title.replace('-', ' ').replace('_', ' ') # Capitalize if the filename was all lowercase, otherwise leave it as-is. diff --git a/mkdocs/tests/build_tests.py b/mkdocs/tests/build_tests.py index 8c0808d5..6e5da108 100644 --- a/mkdocs/tests/build_tests.py +++ b/mkdocs/tests/build_tests.py @@ -10,12 +10,13 @@ from mkdocs import build, nav, config from mkdocs.compat import zip from mkdocs.exceptions import MarkdownNotFound from mkdocs.tests.base import dedent +from mkdocs.utils import convert_markdown class BuildTests(unittest.TestCase): def test_empty_document(self): - html, toc, meta = build.convert_markdown("") + html, toc, meta = convert_markdown("") self.assertEqual(html, '') self.assertEqual(len(list(toc)), 0) @@ -26,7 +27,7 @@ class BuildTests(unittest.TestCase): Ensure that basic Markdown -> HTML and TOC works. """ - html, toc, meta = build.convert_markdown(dedent(""" + html, toc, meta = convert_markdown(dedent(""" page_title: custom title # Heading 1 @@ -59,25 +60,25 @@ class BuildTests(unittest.TestCase): def test_convert_internal_link(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 = convert_markdown(md_text) self.assertEqual(html.strip(), expected.strip()) def test_convert_multiple_internal_links(self): md_text = '[First link](first.md) [second link](second.md).' expected = '

First link second link.

' - html, toc, meta = build.convert_markdown(md_text) + html, toc, meta = convert_markdown(md_text) 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 = convert_markdown(md_text) 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 = convert_markdown(md_text) self.assertEqual(html.strip(), expected.strip()) def test_convert_internal_media(self): @@ -100,7 +101,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, _, _ = convert_markdown(md_text, site_navigation=site_navigation) self.assertEqual(html, template % expected) def test_convert_internal_asbolute_media(self): @@ -123,7 +124,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, _, _ = convert_markdown(md_text, site_navigation=site_navigation) self.assertEqual(html, template % expected) def test_dont_convert_code_block_urls(self): @@ -143,7 +144,7 @@ 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, _, _ = convert_markdown(markdown, site_navigation=site_navigation) self.assertEqual(dedent(html), expected) def test_anchor_only_link(self): @@ -158,13 +159,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, _, _ = convert_markdown(markdown, site_navigation=site_navigation) self.assertEqual(html, '

test

') 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 = convert_markdown(md_text) self.assertEqual(html.strip(), expected.strip()) def test_not_use_directory_urls(self): @@ -174,7 +175,7 @@ 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 = convert_markdown(md_text, site_navigation=site_navigation) self.assertEqual(html.strip(), expected.strip()) def test_markdown_table_extension(self): @@ -182,7 +183,7 @@ class BuildTests(unittest.TestCase): Ensure that the table extension is supported. """ - html, toc, meta = build.convert_markdown(dedent(""" + html, toc, meta = convert_markdown(dedent(""" First Header | Second Header -------------- | -------------- Content Cell 1 | Content Cell 2 @@ -217,7 +218,7 @@ class BuildTests(unittest.TestCase): Ensure that the fenced code extension is supported. """ - html, toc, meta = build.convert_markdown(dedent(""" + html, toc, meta = convert_markdown(dedent(""" ``` print 'foo' ``` @@ -238,12 +239,12 @@ class BuildTests(unittest.TestCase): # Check that the plugin is not active when not requested. expected_without_smartstrong = "

foobarbaz

" - html_base, _, _ = build.convert_markdown(md_input) + html_base, _, _ = convert_markdown(md_input) self.assertEqual(html_base.strip(), expected_without_smartstrong) # Check that the plugin is active when requested. expected_with_smartstrong = "

foo__bar__baz

" - html_ext, _, _ = build.convert_markdown(md_input, extensions=['smart_strong']) + html_ext, _, _ = convert_markdown(md_input, extensions=['smart_strong']) self.assertEqual(html_ext.strip(), expected_with_smartstrong) def test_markdown_duplicate_custom_extension(self): @@ -251,7 +252,7 @@ class BuildTests(unittest.TestCase): Duplicated extension names should not cause problems. """ md_input = "foo" - html_ext, _, _ = build.convert_markdown(md_input, ['toc']) + html_ext, _, _ = convert_markdown(md_input, ['toc']) self.assertEqual(html_ext.strip(), '

foo

') def test_copying_media(self): @@ -303,8 +304,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) + convert_markdown(valid, site_nav, strict=False) + convert_markdown(valid, site_nav, strict=True) def test_strict_mode_invalid(self): pages = [ @@ -315,11 +316,11 @@ class BuildTests(unittest.TestCase): site_nav = nav.SiteNavigation(pages) invalid = "[test](bad_link.md)" - build.convert_markdown(invalid, site_nav, strict=False) + convert_markdown(invalid, site_nav, strict=False) self.assertRaises( MarkdownNotFound, - build.convert_markdown, invalid, site_nav, strict=True) + convert_markdown, invalid, site_nav, strict=True) def test_extension_config(self): """ @@ -330,7 +331,7 @@ class BuildTests(unittest.TestCase): 'toc': {'permalink': True}, 'meta': None # This gets ignored as it is an invalid config } - html, toc, meta = build.convert_markdown(dedent(""" + html, toc, meta = convert_markdown(dedent(""" # A Header """), extensions=markdown_extensions) diff --git a/mkdocs/utils.py b/mkdocs/utils.py index 55c8d154..0890556c 100644 --- a/mkdocs/utils.py +++ b/mkdocs/utils.py @@ -9,8 +9,11 @@ and structure of the site and pages in the site. import os import shutil +import markdown +from mkdocs import toc from mkdocs.compat import urlparse, pathname2url +from mkdocs.relative_path_ext import RelativePathExtension def copy_file(source_path, output_path): @@ -226,3 +229,40 @@ def path_to_url(path): return path return pathname2url(path) + + +def convert_markdown(markdown_source, site_navigation=None, extensions=(), strict=False): + """ + 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. + """ + + # 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 = set(builtin_extensions + mkdocs_extensions + user_extensions) + md = markdown.Markdown( + extensions=extensions, + extension_configs=extension_configs + ) + html_content = md.convert(markdown_source) + + # On completely blank markdown files, no Meta or tox properties are added + # to the generated document. + meta = getattr(md, 'Meta', {}) + toc_html = getattr(md, 'toc', '') + + # Post process the generated table of contents into a data structure + table_of_contents = toc.TableOfContents(toc_html) + + return (html_content, table_of_contents, meta) \ No newline at end of file