diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md index c0951359..c55b4248 100644 --- a/docs/about/release-notes.md +++ b/docs/about/release-notes.md @@ -87,6 +87,7 @@ and user created and third-party templates should be updated as outlined below: ### Other Changes and Additions to Version 1.0.0 +* Internal refactor of Markdown processing (#713) * Removed special error message for mkdocs-bootstrap and mkdocs-bootswatch themes (#1168) * The legacy pages config is no longer supported (#1168) diff --git a/mkdocs/commands/build.py b/mkdocs/commands/build.py index ef804e35..052f6b56 100644 --- a/mkdocs/commands/build.py +++ b/mkdocs/commands/build.py @@ -12,7 +12,6 @@ import jinja2 from mkdocs import nav, search, utils from mkdocs.utils import filters -from mkdocs.relative_path_ext import RelativePathExtension import mkdocs @@ -31,40 +30,15 @@ log = logging.getLogger(__name__) log.addFilter(DuplicateFilter()) -def get_complete_paths(config, page): - """ - Return the complete input/output paths for the supplied page. - """ - input_path = os.path.join(config['docs_dir'], page.input_path) - output_path = os.path.join(config['site_dir'], page.output_path) - return input_path, output_path - - -def convert_markdown(markdown_source, config, site_navigation=None): - """ - 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. - """ - - extensions = [ - RelativePathExtension(site_navigation, config['strict']) - ] + config['markdown_extensions'] - - return utils.convert_markdown( - markdown_source=markdown_source, - extensions=extensions, - extension_configs=config['mdx_configs'] - ) - - -def get_global_context(nav, config): +def get_context(nav, config, page=None): """ Given the SiteNavigation and config, generate the context which is relevant to app pages. """ + if nav is None: + return {'page', page} + extra_javascript = utils.create_media_urls(nav, config['extra_javascript']) extra_css = utils.create_media_urls(nav, config['extra_css']) @@ -85,27 +59,10 @@ def get_global_context(nav, config): 'build_date_utc': datetime.utcfromtimestamp(timestamp), 'config': config, + 'page': page, } -def get_page_context(page, content, toc, meta, config): - """ - Generate the page context by extending the global context and adding page - specific variables. - """ - if config['site_url']: - page.set_canonical_url(config['site_url']) - - if config['repo_url']: - page.set_edit_url(config['repo_url'], config['edit_uri']) - - page.content = content - page.toc = toc - page.meta = meta - - return {'page': page} - - def build_template(template_name, env, config, site_navigation=None): log.debug("Building template: %s", template_name) @@ -115,9 +72,7 @@ def build_template(template_name, env, config, site_navigation=None): except TemplateNotFound: return False - context = {'page': None} - if site_navigation is not None: - context.update(get_global_context(site_navigation, config)) + context = get_context(site_navigation, config) output_content = template.render(context) output_path = os.path.join(config['site_dir'], template_name) @@ -127,31 +82,15 @@ def build_template(template_name, env, config, site_navigation=None): def _build_page(page, config, site_navigation, env, dirty=False): - # Get the input/output paths - input_path, output_path = get_complete_paths(config, page) - - # Read the input file - try: - input_content = io.open(input_path, 'r', encoding='utf-8').read() - except IOError: - log.error('file not found: %s', input_path) - raise - # Process the markdown text - html_content, table_of_contents, meta = convert_markdown( - markdown_source=input_content, - config=config, - site_navigation=site_navigation - ) + page.load_markdown() + page.render(config, site_navigation) - context = get_global_context(site_navigation, config) - context.update(get_page_context( - page, html_content, table_of_contents, meta, config - )) + context = get_context(site_navigation, config, page) # Allow 'template:' override in md source files. - if 'template' in meta: - template = env.get_template(meta['template'][0]) + if 'template' in page.meta: + template = env.get_template(page.meta['template']) else: template = env.get_template('main.html') @@ -159,9 +98,7 @@ def _build_page(page, config, site_navigation, env, dirty=False): output_content = template.render(context) # Write the output file. - utils.write_file(output_content.encode('utf-8'), output_path) - - return html_content, table_of_contents, meta + utils.write_file(output_content.encode('utf-8'), page.abs_output_path) def build_extra_templates(extra_templates, config, site_navigation=None): @@ -175,9 +112,7 @@ def build_extra_templates(extra_templates, config, site_navigation=None): with io.open(input_path, 'r', encoding='utf-8') as template_file: template = jinja2.Template(template_file.read()) - context = {'page': None} - if site_navigation is not None: - context.update(get_global_context(site_navigation, config)) + context = get_context(site_navigation, config) output_content = template.render(context) output_path = os.path.join(config['site_dir'], extra_template) @@ -188,7 +123,7 @@ def build_pages(config, dirty=False): """ Builds all the pages and writes them into the build directory. """ - site_navigation = nav.SiteNavigation(config['pages'], config['use_directory_urls']) + site_navigation = nav.SiteNavigation(config) loader = jinja2.FileSystemLoader(config['theme_dir'] + [config['mkdocs_templates'], ]) env = jinja2.Environment(loader=loader) @@ -218,18 +153,14 @@ def build_pages(config, dirty=False): for page in site_navigation.walk_pages(): try: - # When --dirty is used, only build the page if the markdown has been modified since the # previous build of the output. - input_path, output_path = get_complete_paths(config, page) - if dirty and (utils.modified_time(input_path) < utils.modified_time(output_path)): + if dirty and (utils.modified_time(page.abs_input_path) < utils.modified_time(page.abs_output_path)): continue log.debug("Building page %s", page.input_path) - build_result = _build_page(page, config, site_navigation, env) - html_content, table_of_contents, _ = build_result - search_index.add_entry_from_context( - page, html_content, table_of_contents) + _build_page(page, config, site_navigation, env) + search_index.add_entry_from_context(page) except Exception: log.error("Error building page %s", page.input_path) raise diff --git a/mkdocs/config/defaults.py b/mkdocs/config/defaults.py index b93ff01f..8bcdbd7a 100644 --- a/mkdocs/config/defaults.py +++ b/mkdocs/config/defaults.py @@ -90,7 +90,7 @@ DEFAULT_SCHEMA = ( # PyMarkdown extension names. ('markdown_extensions', config_options.MarkdownExtensions( - builtins=['meta', 'toc', 'tables', 'fenced_code'], + builtins=['toc', 'tables', 'fenced_code'], configkey='mdx_configs', default=[])), # PyMarkdown Extension Configs. For internal use only. diff --git a/mkdocs/nav.py b/mkdocs/nav.py index 55d45a9f..db46c048 100644 --- a/mkdocs/nav.py +++ b/mkdocs/nav.py @@ -9,14 +9,18 @@ This consists of building a set of interlinked page and header objects. from __future__ import unicode_literals import datetime import logging +import markdown import os +import io -from mkdocs import utils, exceptions +from mkdocs import utils, exceptions, toc +from mkdocs.utils import meta +from mkdocs.relative_path_ext import RelativePathExtension log = logging.getLogger(__name__) -def filename_to_title(filename): +def _filename_to_title(filename): """ Automatically generate a default title, given a filename. """ @@ -26,14 +30,20 @@ def filename_to_title(filename): return utils.filename_to_title(filename) +@meta.transformer() +def default(value): + """ By default, return all meta values as strings. """ + return ' '.join(value) + + class SiteNavigation(object): - def __init__(self, pages_config, use_directory_urls=True): + def __init__(self, config): self.url_context = URLContext() self.file_context = FileContext() self.nav_items, self.pages = _generate_site_navigation( - pages_config, self.url_context, use_directory_urls) + config, self.url_context) self.homepage = self.pages[0] if self.pages else None - self.use_directory_urls = use_directory_urls + self.use_directory_urls = config['use_directory_urls'] def __str__(self): return ''.join([str(item) for item in self]) @@ -139,10 +149,10 @@ class FileContext(object): class Page(object): - def __init__(self, title, url, path, url_context): + def __init__(self, title, path, url_context, config): - self.title = title - self.abs_url = url + self._title = title + self.abs_url = utils.get_url_path(path, config['use_directory_urls']) self.active = False self.url_context = url_context @@ -155,22 +165,71 @@ class Page(object): else: self.update_date = datetime.datetime.now().strftime("%Y-%m-%d") - # Relative paths to the input markdown file and output html file. + # Relative and absolute paths to the input markdown file and output html file. self.input_path = path self.output_path = utils.get_html_path(path) + self.abs_input_path = os.path.join(config['docs_dir'], self.input_path) + self.abs_output_path = os.path.join(config['site_dir'], self.output_path) + + self.canonical_url = None + if config['site_url']: + self._set_canonical_url(config['site_url']) + + self.edit_url = None + if config['repo_url']: + self._set_edit_url(config['repo_url'], config['edit_uri']) + + # Placeholders to be filled in later in the build + # process when we have access to the config. + self.markdown = '' + self.meta = {} + self.content = None + self.toc = None - # Links to related pages self.previous_page = None self.next_page = None self.ancestors = [] - # Placeholders to be filled in later in the build - # process when we have access to the config. - self.canonical_url = None - self.edit_url = None - self.content = None - self.meta = None - self.toc = None + def __eq__(self, other): + + def sub_dict(d): + return dict((key, value) for key, value in d.items() + if key in ['title', 'input_path', 'abs_url']) + + return (isinstance(other, self.__class__) + and sub_dict(self.__dict__) == sub_dict(other.__dict__)) + + def __ne__(self, other): + return not self.__eq__(other) + + def __str__(self): + return self.indent_print() + + def __repr__(self): + return "nav.Page(title='{0}', input_path='{1}', url='{2}')".format( + self.title, self.input_path, self.abs_url) + + @property + def title(self): + """ + Get the title for a Markdown document + Check these in order and return the first that has a valid title: + - self._title which is populated from the mkdocs.yml + - self.meta['title'] which comes from the page metadata + - self.markdown - look for the first H1 + - self.input_path - create a title based on the filename + """ + if self._title is not None: + return self._title + elif 'title' in self.meta: + return self.meta['title'] + + title = utils.get_markdown_title(self.markdown) + + if title is not None: + return title + + return _filename_to_title(self.input_path.split(os.path.sep)[-1]) @property def url(self): @@ -184,8 +243,29 @@ class Page(object): def is_top_level(self): return len(self.ancestors) == 0 - def __str__(self): - return self.indent_print() + def load_markdown(self): + try: + input_content = io.open(self.abs_input_path, 'r', encoding='utf-8').read() + except IOError: + log.error('file not found: %s', self.abs_input_path) + raise + + self.markdown, self.meta = meta.get_data(input_content) + + def _set_canonical_url(self, base): + if not base.endswith('/'): + base += '/' + self.canonical_url = utils.urljoin(base, self.abs_url.lstrip('/')) + + def _set_edit_url(self, repo_url, edit_uri): + if not edit_uri: + self.edit_url = repo_url + else: + # Normalize URL from Windows path '\\' -> '/' + input_path_url = self.input_path.replace('\\', '/') + self.edit_url = utils.urljoin( + repo_url, + edit_uri + input_path_url) def indent_print(self, depth=0): indent = ' ' * depth @@ -198,20 +278,23 @@ class Page(object): for ancestor in self.ancestors: ancestor.set_active(active) - def set_canonical_url(self, base): - if not base.endswith('/'): - base += '/' - self.canonical_url = utils.urljoin(base, self.abs_url.lstrip('/')) + def render(self, config, site_navigation=None): + """ + Convert the Markdown source file to HTML as per the config and + site_navigation. - def set_edit_url(self, repo_url, edit_uri): - if not edit_uri: - self.edit_url = repo_url - else: - # Normalize URL from Windows path '\\' -> '/' - input_path_url = self.input_path.replace('\\', '/') - self.edit_url = utils.urljoin( - repo_url, - edit_uri + input_path_url) + """ + + extensions = [ + RelativePathExtension(site_navigation, config['strict']) + ] + config['markdown_extensions'] + + md = markdown.Markdown( + extensions=extensions, + extension_configs=config['mdx_configs'] or {} + ) + self.content = md.convert(self.markdown) + self.toc = toc.TableOfContents(getattr(md, 'toc', '')) class Header(object): @@ -241,19 +324,11 @@ class Header(object): ancestor.set_active(active) -def _path_to_page(path, title, url_context, use_directory_urls): - if title is None: - title = filename_to_title(path.split(os.path.sep)[-1]) - url = utils.get_url_path(path, use_directory_urls) - return Page(title=title, url=url, path=path, - url_context=url_context) - - -def _follow(config_line, url_context, use_dir_urls, header=None, title=None): +def _follow(config_line, url_context, config, header=None, title=None): if isinstance(config_line, utils.string_types): path = os.path.normpath(config_line) - page = _path_to_page(path, title, url_context, use_dir_urls) + page = Page(title, path, url_context, config) if header: page.ancestors = header.ancestors + [header, ] @@ -279,7 +354,7 @@ def _follow(config_line, url_context, use_dir_urls, header=None, title=None): if isinstance(subpages_or_path, utils.string_types): path = subpages_or_path - for sub in _follow(path, url_context, use_dir_urls, header=header, title=next_cat_or_title): + for sub in _follow(path, url_context, config, header=header, title=next_cat_or_title): yield sub raise StopIteration @@ -298,11 +373,11 @@ def _follow(config_line, url_context, use_dir_urls, header=None, title=None): subpages = subpages_or_path for subpage in subpages: - for sub in _follow(subpage, url_context, use_dir_urls, next_header): + for sub in _follow(subpage, url_context, config, next_header): yield sub -def _generate_site_navigation(pages_config, url_context, use_dir_urls=True): +def _generate_site_navigation(config, url_context): """ Returns a list of Page and Header instances that represent the top level site navigation. @@ -312,10 +387,10 @@ def _generate_site_navigation(pages_config, url_context, use_dir_urls=True): previous = None - for config_line in pages_config: + for config_line in config['pages']: for page_or_header in _follow( - config_line, url_context, use_dir_urls): + config_line, url_context, config): if isinstance(page_or_header, Header): diff --git a/mkdocs/search.py b/mkdocs/search.py index 10a92b8a..a4021919 100644 --- a/mkdocs/search.py +++ b/mkdocs/search.py @@ -41,7 +41,7 @@ class SearchIndex(object): 'location': loc }) - def add_entry_from_context(self, page, content, toc): + def add_entry_from_context(self, page): """ Create a set of entries in the index for a page. One for the page itself and then one for each of its' heading @@ -52,7 +52,7 @@ class SearchIndex(object): # full page. This handles all the parsing and prepares # us to iterate through it. parser = ContentParser() - parser.feed(content) + parser.feed(page.content) parser.close() # Get the absolute URL for the page, this is then @@ -62,12 +62,12 @@ class SearchIndex(object): # Create an entry for the full page. self._add_entry( title=page.title, - text=self.strip_tags(content).rstrip('\n'), + text=self.strip_tags(page.content).rstrip('\n'), loc=abs_url ) for section in parser.data: - self.create_entry_for_section(section, toc, abs_url) + self.create_entry_for_section(section, page.toc, abs_url) def create_entry_for_section(self, section, toc, abs_url): """ diff --git a/mkdocs/tests/base.py b/mkdocs/tests/base.py index 3a3d2f26..51cfee36 100644 --- a/mkdocs/tests/base.py +++ b/mkdocs/tests/base.py @@ -1,8 +1,10 @@ from __future__ import unicode_literals import textwrap import markdown +import os from mkdocs import toc +from mkdocs import config def dedent(text): @@ -14,3 +16,24 @@ def markdown_to_toc(markdown_source): md.convert(markdown_source) toc_output = md.toc return toc.TableOfContents(toc_output) + + +def load_config(**cfg): + """ Helper to build a simple config for testing. """ + path_base = os.path.join( + os.path.abspath(os.path.dirname(__file__)), 'integration', 'minimal' + ) + cfg = cfg or {} + if 'site_name' not in cfg: + cfg['site_name'] = 'Example' + if 'config_file_path' not in cfg: + cfg['config_file_path'] = os.path.join(path_base, 'mkdocs.yml') + if 'docs_dir' not in cfg: + # Point to an actual dir to avoid a 'does not exist' error on validation. + cfg['docs_dir'] = os.path.join(path_base, 'docs') + conf = config.Config(schema=config.DEFAULT_SCHEMA) + conf.load_dict(cfg) + + errors_warnings = conf.validate() + assert(errors_warnings == ([], [])), errors_warnings + return conf diff --git a/mkdocs/tests/build_tests.py b/mkdocs/tests/build_tests.py index 607d4244..e25c26ba 100644 --- a/mkdocs/tests/build_tests.py +++ b/mkdocs/tests/build_tests.py @@ -15,44 +15,42 @@ except ImportError: pass -from mkdocs import nav, config +from mkdocs import nav from mkdocs.commands import build from mkdocs.exceptions import MarkdownNotFound -from mkdocs.tests.base import dedent +from mkdocs.tests.base import dedent, load_config +from mkdocs.utils import meta -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' - if 'config_file_path' not in cfg: - cfg['config_file_path'] = os.path.join(os.path.abspath('.'), 'mkdocs.yml') - if 'extra_css' not in cfg: - cfg['extra_css'] = ['css/extra.css'] - conf = config.Config(schema=config.DEFAULT_SCHEMA) - conf.load_dict(cfg) +def build_page(title, path, config, md_src=None): + """ Helper which returns a Page object. """ - errors_warnings = conf.validate() - assert(errors_warnings == ([], [])), errors_warnings - return conf + sitenav = nav.SiteNavigation(config) + page = nav.Page(title, path, sitenav.url_context, config) + if md_src: + # Fake page.load_markdown() + page.markdown, page.meta = meta.get_data(md_src) + return page, sitenav class BuildTests(unittest.TestCase): def test_empty_document(self): - html, toc, meta = build.convert_markdown("", load_config()) + config = load_config(pages=[{'Home': 'index.md'}]) + page, nav = build_page(None, 'index.md', config) + page.render(config, nav) - self.assertEqual(html, '') - self.assertEqual(len(list(toc)), 0) - self.assertEqual(meta, {}) + self.assertEqual(page.content, '') + self.assertEqual(len(list(page.toc)), 0) + self.assertEqual(page.meta, {}) + self.assertEqual(page.title, 'Home') def test_convert_markdown(self): """ Ensure that basic Markdown -> HTML and TOC works. """ - html, toc, meta = build.convert_markdown(dedent(""" - page_title: custom title + md_text = dedent(""" + title: custom title # Heading 1 @@ -61,7 +59,11 @@ class BuildTests(unittest.TestCase): # Heading 2 And some more text. - """), load_config()) + """) + + config = load_config(pages=[{'Home': 'index.md'}]) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) expected_html = dedent("""
An internal link to another document.
' - html, toc, meta = build.convert_markdown(md_text, load_config()) - self.assertEqual(html.strip(), expected.strip()) + config = load_config(pages=['index.md', 'internal.md']) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + self.assertEqual(page.content.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, load_config()) - self.assertEqual(html.strip(), expected.strip()) + config = load_config(pages=['index.md', 'first.md', 'second.md']) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + self.assertEqual(page.content.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, load_config()) - self.assertEqual(html.strip(), expected.strip()) + config = load_config(pages=['foo/bar.md', 'internal.md']) + page, nav = build_page(None, 'foo/bar.md', config, md_text) + page.render(config) + self.assertEqual(page.content.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, load_config()) - self.assertEqual(html.strip(), expected.strip()) + config = load_config(pages=['index.md', 'internal.md']) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + self.assertEqual(page.content.strip(), expected.strip()) def test_convert_internal_media(self): """Test relative image URL's are the same for different base_urls""" @@ -113,7 +124,8 @@ class BuildTests(unittest.TestCase): 'sub/internal.md', ] - site_navigation = nav.SiteNavigation(pages) + config = load_config(pages=pages) + site_navigation = nav.SiteNavigation(config) expected_results = ( './img/initial-layout.png', @@ -124,9 +136,9 @@ class BuildTests(unittest.TestCase): template = 'An HTML Anchor::
@@ -167,9 +181,9 @@ 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, load_config(), site_navigation=site_navigation) - self.assertEqual(dedent(html), expected) + page.markdown = 'An HTML Anchor::\n\n My example link\n' + page.render(config, site_navigation) + self.assertEqual(page.content, expected) def test_anchor_only_link(self): pages = [ @@ -178,28 +192,29 @@ class BuildTests(unittest.TestCase): 'sub/internal.md', ] - site_navigation = nav.SiteNavigation(pages) + config = load_config(pages=pages) + site_navigation = nav.SiteNavigation(config) for page in site_navigation.walk_pages(): - markdown = '[test](#test)' - html, _, _ = build.convert_markdown(markdown, load_config(), site_navigation=site_navigation) - self.assertEqual(html, '') + page.markdown = '[test](#test)' + page.render(config, site_navigation) + self.assertEqual(page.content, '') 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, load_config()) - self.assertEqual(html.strip(), expected.strip()) + config = load_config(pages=[{'Home': 'index.md'}]) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + self.assertEqual(page.content.strip(), expected.strip()) def test_not_use_directory_urls(self): md_text = 'An [internal link](internal.md) to another document.' expected = 'An internal link to another document.
' - pages = [ - 'internal.md', - ] - site_navigation = nav.SiteNavigation(pages, use_directory_urls=False) - html, toc, meta = build.convert_markdown(md_text, load_config(), site_navigation=site_navigation) - self.assertEqual(html.strip(), expected.strip()) + config = load_config(pages=['index.md', 'internal.md'], use_directory_urls=False) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + self.assertEqual(page.content.strip(), expected.strip()) def test_ignore_email_links(self): md_text = 'Aprint 'foo'\n
""")
- self.assertEqual(html.strip(), expected_html)
+ config = load_config(pages=[{'Home': 'index.md'}])
+ page, nav = build_page(None, 'index.md', config, md_text)
+ page.render(config, nav)
+ self.assertEqual(page.content.strip(), expected_html)
def test_markdown_custom_extension(self):
"""
Check that an extension applies when requested in the arguments to
`convert_markdown`.
"""
- md_input = "foo__bar__baz"
+ md_text = "foo__bar__baz"
# Check that the plugin is not active when not requested.
expected_without_smartstrong = "foobarbaz
" - html_base, _, _ = build.convert_markdown(md_input, load_config()) - self.assertEqual(html_base.strip(), expected_without_smartstrong) + config = load_config(pages=[{'Home': 'index.md'}]) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + self.assertEqual(page.content.strip(), expected_without_smartstrong) # Check that the plugin is active when requested. - cfg = load_config({ - 'markdown_extensions': ['smart_strong'] - }) expected_with_smartstrong = "foo__bar__baz
" - html_ext, _, _ = build.convert_markdown(md_input, cfg) - self.assertEqual(html_ext.strip(), expected_with_smartstrong) + config = load_config(pages=[{'Home': 'index.md'}], markdown_extensions=['smart_strong']) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + self.assertEqual(page.content.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, cfg) - self.assertEqual(html_ext.strip(), 'foo
') + md_text = "foo" + config = load_config(pages=[{'Home': 'index.md'}], markdown_extensions=['toc']) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + self.assertEqual(page.content.strip(), 'foo
') def test_copying_media(self): docs_dir = tempfile.mkdtemp() @@ -318,10 +341,7 @@ class BuildTests(unittest.TestCase): os.mkdir(os.path.join(docs_dir, '.git')) open(os.path.join(docs_dir, '.git/hidden'), 'w').close() - cfg = load_config({ - 'docs_dir': docs_dir, - 'site_dir': site_dir - }) + cfg = load_config(docs_dir=docs_dir, site_dir=site_dir) build.build(cfg) # Verify only the markdown (coverted to html) and the image are copied. @@ -349,10 +369,7 @@ class BuildTests(unittest.TestCase): """)) f.close() - cfg = load_config({ - 'docs_dir': docs_dir, - 'site_dir': site_dir - }) + cfg = load_config(docs_dir=docs_dir, site_dir=site_dir) build.build(cfg) # Verify only theme media are copied, not templates or Python files. @@ -375,11 +392,16 @@ class BuildTests(unittest.TestCase): 'internal.md', 'sub/internal.md', ] - site_nav = nav.SiteNavigation(pages) - valid = "[test](internal.md)" - build.convert_markdown(valid, load_config({'strict': False}), site_nav) - build.convert_markdown(valid, load_config({'strict': True}), site_nav) + md_text = "[test](internal.md)" + + config = load_config(pages=pages, strict=False) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + + config = load_config(pages=pages, strict=True) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) def test_strict_mode_invalid(self): pages = [ @@ -387,55 +409,58 @@ class BuildTests(unittest.TestCase): 'internal.md', 'sub/internal.md', ] - site_nav = nav.SiteNavigation(pages) - invalid = "[test](bad_link.md)" - build.convert_markdown(invalid, load_config({'strict': False}), site_nav) + md_text = "[test](bad_link.md)" + config = load_config(pages=pages, strict=False) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, nav) + + config = load_config(pages=pages, strict=True) + page, nav = build_page(None, 'index.md', config, md_text) self.assertRaises( MarkdownNotFound, - build.convert_markdown, invalid, load_config({'strict': True}), site_nav) + page.render, config, nav) def test_absolute_link(self): pages = [ 'index.md', 'sub/index.md', ] - site_nav = nav.SiteNavigation(pages) - markdown = "[test 1](/index.md) [test 2](/sub/index.md)" - cfg = load_config({'strict': True}) - build.convert_markdown(markdown, cfg, site_nav) + md_text = "[test 1](/index.md) [test 2](/sub/index.md)" + config = load_config(pages=pages, strict=True) + page, nav = build_page(None, 'index.md', config, md_text) + page.render(config, 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. """ - cfg = load_config({ - 'markdown_extensions': [{'toc': {'permalink': True}}] - }) - - html, toc, meta = build.convert_markdown(dedent(""" + md_text = dedent(""" # A Header - """), cfg) + """) expected_html = dedent("""