Refactor the pages configuration

The current pages configuration looks like this:

    pages:
    - ['index.md', 'Home']
    - ['writing-your-docs.md', 'User Guide', 'Writing your docs']
    - ['styling-your-docs.md', 'User Guide', 'Styling your docs']
    - ['configuration.md', 'User Guide', 'Configuration']
    - ['about/license.md', 'About', 'License']
    - ['about/release-notes.md', 'About', 'Release Notes']
    - ['about/contributing.md', 'About', 'Contributing']

This has a number of flaws:

- It isn't clear how to add second levels of navigation for
  newcomers. This is often queried and not easy to document.

- We are representing a tree structure as a set of flat items
  that need to be merged. This creates some interesting edge
  cases, for example:

    - ['writing-your-docs.md', 'User Guide']
    - ['styling-your-docs.md', 'User Guide', 'Styling your docs']

 Is the first entry a page with the title User Guide? or a page
 in the User Guide category with an automatic title.

- We are currently limited to two levels deep in the navigation.
  Changing this with the current structure isn't trivial.

This change adds a new format which makes the above configuration
look like this:

    pages:
    - Home: index.md
    - User Guide:
        - user-guide/writing-your-docs.md
        - user-guide/styling-your-docs.md
        - user-guide/configuration.md
    - About:
        - License: about/license.md
        - about/release-notes.md
        - Contributing: about/contributing.md

With this structure, we can more easily see the documentation
tree and it is far more obvious what the navigation will look
like. It also removes the ambiguous edge cases and opens up the
possibility of adding further levels to the navigation more
easily.

This change restructures the pages configuration, but doesn't yet
allow users to add further levels in the navigation.

Fixes #6
This commit is contained in:
Dougal Matthews
2015-04-26 10:28:42 +01:00
parent 1a0dfb5ee3
commit 8085c65c6a
13 changed files with 566 additions and 190 deletions

View File

@@ -105,8 +105,8 @@ We'd like our documentation site to include some navigation headers, so we'll ed
site_name: MkLorum
pages:
- [index.md, Home]
- [about.md, About]
- Home: index.md
- About: about.md
Refresh the browser and you'll now see a navigation bar with `Home` and `About` headers.
@@ -116,9 +116,10 @@ While we're here can also change the configuration file to alter how the documen
site_name: MkLorum
pages:
- [index.md, Home]
- [about.md, About]
```
- Home: index.md
- About: about.md
theme: readthedocs
Refresh the browser again, and you'll now see the ReadTheDocs theme being used.

View File

@@ -90,22 +90,23 @@ The setting should be a list. Each row in the list represents information about
Here's a simple example that would cause the build stage to create three pages:
pages:
- ['index.md', 'Introduction']
- ['user-guide.md', 'User Guide']
- ['about.md', 'About']
- 'Introduction': 'index.md'
- 'User Guide': 'user-guide.md'
- 'Abut': 'about.md'
Assuming the `docs_dir` setting was left with the default value of `docs`, the source files for this site's build process would be `docs/index.md`, `docs/user-guide.md` and `docs/about.md`.
If you have a lot of project documentation you might choose to use headings to break up your site navigation by category. You can do so by including an extra string in the page configuration for any pages that require a navigation heading, like so:
pages:
- ['index.md', 'Introduction']
- ['user-guide/creating.md', 'User Guide', 'Creating a new Mashmallow project']
- ['user-guide/api.md', 'User Guide', 'Mashmallow API guide']
- ['user-guide/configuration.md', 'User Guide', 'Configuring Mashmallow']
- ['about/license.md', 'About', 'License']
- 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'
- About:
- License: 'about/license.md'
See also the section on [configuring pages and navigation](/user-guide/writing-your-docs/#configure-pages-and-navigation) for a more detailed breakdown.

View File

@@ -17,19 +17,21 @@ A simple pages configuration looks like this:
With this example we will build two pages at the top level and they will automatically have their titles inferred from the filename. To provide a custom name for these pages, they can be added after the filename.
pages:
- ['index.md', 'Home']
- ['about.md', 'About MkDocs']
- Home: 'index.md'
- About: 'about.md'
### Multilevel documentation
To create a second level in the navigation and group topics, the category can be provided before the page title. This is best demonstrated in a documentation project with more pages and is slighlt more complicated.
pages:
- ['index.md', 'Home']
- ['user-guide/writing-your-docs.md', 'User Guide', 'Writing your docs']
- ['user-guide/styling-your-docs.md', 'User Guide', 'Styling your docs']
- ['about/license.md', 'About', 'License']
- ['about/release-notes.md', 'About', 'Release Notes']
- Home: 'index.md'
- User Guide:
- 'Writing your docs': 'user-guide/writing-your-docs.md'
- 'Styling your docs': 'user-guide/styling-your-docs.md'
- About:
- 'License': 'about/license.md'
- 'Release Notes': 'about/release-notes.md'
With the above configuration we have three top level sections Home, User Guide and About. Then under User Guide we have two pages, Writing your docs and Styling your docs. Under the About section we also have two pages, License and Release Notes

View File

@@ -5,13 +5,17 @@ site_description: Project documentation with Markdown.
repo_url: https://github.com/mkdocs/mkdocs/
pages:
- ['index.md', 'Home']
- ['user-guide/writing-your-docs.md', 'User Guide', 'Writing your docs']
- ['user-guide/styling-your-docs.md', 'User Guide', 'Styling your docs']
- ['user-guide/configuration.md', 'User Guide', 'Configuration']
- ['about/license.md', 'About', 'License']
- ['about/release-notes.md', 'About', 'Release Notes']
- ['about/contributing.md', 'About', 'Contributing']
- Home: index.md
- User Guide:
- Writing Your Docs: user-guide/writing-your-docs.md
- Styling Your Docs: user-guide/styling-your-docs.md
- Configuration: user-guide/configuration.md
- About:
- License: about/license.md
- Release Notes: about/release-notes.md
- Contributing: about/contributing.md
use_new_pages_structure: true
markdown_extensions:
toc:

View File

@@ -7,7 +7,7 @@ import os
from six.moves.urllib.parse import urlparse
from mkdocs import utils
from mkdocs import utils, legacy
from mkdocs.exceptions import ConfigurationError
log = logging.getLogger(__name__)
@@ -68,7 +68,7 @@ DEFAULT_CONFIG = {
# enabling strict mode causes MkDocs to stop the build when a problem is
# encountered rather than display an error.
'strict': False
'strict': False,
}
@@ -153,11 +153,27 @@ def validate_config(user_config):
check for Windows style paths. If they are found, output a warning
and continue.
"""
# TODO: Remove in 1.0
config_types = set(type(l) for l in config['pages'])
if list in config_types and dict not in config_types:
config['pages'] = legacy.pages_compat_shim(config['pages'])
elif list in config_types:
raise ConfigurationError("")
for page_config in config['pages']:
if isinstance(page_config, str):
path = page_config
elif isinstance(page_config, dict):
if len(page_config) > 1:
raise ConfigurationError(
"Invalid page config. Should have one key value only")
_, path = next(iter(page_config.items()))
elif len(page_config) in (1, 2, 3):
path = page_config[0]
else:
raise ConfigurationError("Unrecognised page config")
if ntpath.sep in path:
log.warning("The config path contains Windows style paths (\\ "

120
mkdocs/legacy.py Normal file
View File

@@ -0,0 +1,120 @@
import logging
from mkdocs.exceptions import ConfigurationError
log = logging.getLogger(__name__)
def pages_compat_shim(original_pages):
"""
Support legacy pages configuration
Re-write the pages config fron MkDocs <=0.12 to match the
new nested structure added in 0.13.
Given a pages configuration in the old style of:
pages:
- ['index.md', 'Home']
- ['user-guide/writing-your-docs.md', 'User Guide']
- ['user-guide/styling-your-docs.md', 'User Guide']
- ['about/license.md', 'About', 'License']
- ['about/release-notes.md', 'About']
- ['help/contributing.md', 'Help', 'Contributing']
- ['support.md']
- ['cli.md', 'CLI Guide']
Rewrite it to look like:
pages:
- Home: index.md
- User Guide:
- user-guide/writing-your-docs.md
- user-guide/styling-your-docs.md
- About:
- License: about/license.md
- about/release-notes.md
- Help:
- Contributing: about/contributing.md
- support.md
- CLI Guide: cli.md
TODO: Remove in 1.0
"""
log.warning("The pages config in the mkdocs.yml uses the deprecated "
"structure. This will be removed in the next release of "
"MkDocs. See for details on updating: "
"http://www.mkdocs.org/about/release-notes/")
new_pages = []
for config_line in original_pages:
if len(config_line) not in (1, 2, 3):
msg = (
"Line in 'page' config contained {0} items. In Line {1}. "
"Expected 1, 2 or 3 strings.".format(
config_line, len(config_line))
)
raise ConfigurationError(msg)
# First we need to pad out the config line as it could contain
# 1-3 items.
path, category, title = (list(config_line) + [None, None])[:3]
if len(new_pages) > 0:
# Get the previous top-level page so we can see if the category
# matches up with the one we have now.
prev_cat, subpages = next(iter(new_pages[-1].items()))
else:
# We are on the first page
prev_cat, subpages = None, []
# If the category is different, add a new top level category. If the
# previous category is None, the it's another top level one too.
if prev_cat is None or prev_cat != category:
subpages = []
new_pages.append({category: subpages})
# Add the current page to the determined category.
subpages.append({title: path})
# We need to do a bit of cleaning up to match the new structure. In the
# above example, pages can either be `- file.md` or `- Title: file.md`.
# For pages without a title we currently have `- None: file.md` - so we
# need to remove those Nones by changing from a dict to just a string with
# the path.
for i, category in enumerate(new_pages):
# Categories are a dictionary with one key as the name and the value
# is a list of pages. So, grab that from the dict.
category, pages = next(iter(category.items()))
# If we only have one page, then we can assume it is a top level
# category and no further nesting is required unless that single page
# has a title itself,
if len(pages) == 1:
title, path = pages.pop().popitem()
# If we have a title, it should be a sub page
if title is not None:
pages.append({title: path})
# if we have a category, but no title it should be a top-level page
elif category is not None:
new_pages[i] = {category: path}
# if we have no category or title, it must be a top level page with
# an atomatic title.
else:
new_pages[i] = path
else:
# We have more than one page, so the category is valid. We just
# need to iterate through and convert any {None: path} dicts to
# be just the path string.
for j, page in enumerate(pages):
title, path = page.popitem()
if title:
pages[j] = {title: path}
else:
pages[j] = path
return new_pages

View File

@@ -28,7 +28,8 @@ def file_to_title(filename):
try:
with open(filename, 'r') as f:
lines = f.read()
_, table_of_contents, meta = utils.convert_markdown(lines, ['meta', 'toc'])
_, table_of_contents, meta = utils.convert_markdown(
lines, ['meta', 'toc'])
if "title" in meta:
return meta["title"][0]
if len(table_of_contents.items) > 0:
@@ -53,8 +54,8 @@ class SiteNavigation(object):
def __init__(self, pages_config, use_directory_urls=True):
self.url_context = URLContext()
self.file_context = FileContext()
self.nav_items, self.pages = \
_generate_site_navigation(pages_config, self.url_context, use_directory_urls)
self.nav_items, self.pages = _generate_site_navigation(
pages_config, self.url_context, use_directory_urls)
self.homepage = self.pages[0] if self.pages else None
self.use_directory_urls = use_directory_urls
@@ -122,7 +123,8 @@ class URLContext(object):
return '.'
return url.lstrip('/')
# Under Python 2.6, relative_path adds an extra '/' at the end.
relative_path = os.path.relpath(url, start=self.base_path).rstrip('/') + suffix
relative_path = os.path.relpath(url, start=self.base_path)
relative_path = relative_path.rstrip('/') + suffix
return utils.path_to_url(relative_path)
@@ -154,6 +156,7 @@ class FileContext(object):
class Page(object):
def __init__(self, title, url, path, url_context):
self.title = title
self.abs_url = url
self.active = False
@@ -209,74 +212,100 @@ class Header(object):
return ret
def _generate_site_navigation(pages_config, url_context, use_directory_urls=True):
def _path_to_page(path, title, url_context, use_directory_urls):
if title is None:
title = file_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 _generate_site_navigation(pages_config, url_context, use_dir_urls=True):
"""
Returns a list of Page and Header instances that represent the
top level site navigation.
"""
nav_items = []
pages = []
previous = None
for config_line in pages_config:
if isinstance(config_line, str):
path = os.path.normpath(config_line)
title, child_title = None, None
elif len(config_line) in (1, 2, 3):
# Pad any items that don't exist with 'None'
padded_config = (list(config_line) + [None, None])[:3]
path, title, child_title = padded_config
path = os.path.normpath(path)
else:
msg = (
"Line in 'page' config contained %d items. "
"Expected 1, 2 or 3 strings." % len(config_line)
)
page = _path_to_page(path, None, url_context, use_dir_urls)
nav_items.append(page)
if previous:
page.previous_page = previous
previous.next_page = page
previous = page
pages.append(page)
continue
elif not isinstance(config_line, dict):
msg = ("Line in 'page' config is of type {0}, dict or string "
"expected. Line: {1}").format(type(config_line), config_line)
raise exceptions.ConfigurationError(msg)
# If both the title and child_title are None, then we
# have just been given a path. If that path contains a /
# then lets automatically nest it.
if title is None and child_title is None and os.path.sep in path:
filename = path.split(os.path.sep)[-1]
child_title = file_to_title(filename)
if len(config_line) > 1:
raise exceptions.ConfigurationError(
"Page configs should be in the format 'name: markdown.md'. The "
"config contains an invalid entry: {0}".format(config_line))
if title is None:
filename = path.split(os.path.sep)[0]
title = file_to_title(filename)
try:
category, subpages_or_path = next(iter(config_line.items()))
except StopIteration:
log.warning("Ignoring empty line in the pages config.")
continue
# If we don't have a child title but the other title is the same, we
# should be within a section and the child title needs to be inferred
# from the filename.
if len(nav_items) and title == nav_items[-1].title == title and child_title is None:
filename = path.split(os.path.sep)[-1]
child_title = file_to_title(filename)
url = utils.get_url_path(path, use_directory_urls)
if not child_title:
# New top level page.
page = Page(title=title, url=url, path=path, url_context=url_context)
if isinstance(subpages_or_path, str):
path = subpages_or_path
page = _path_to_page(path, category, url_context, use_dir_urls)
nav_items.append(page)
elif not nav_items or (nav_items[-1].title != title):
# New second level page.
page = Page(title=child_title, url=url, path=path, url_context=url_context)
header = Header(title=title, children=[page])
nav_items.append(header)
if previous:
page.previous_page = previous
previous.next_page = page
previous = page
pages.append(page)
continue
header = Header(title=category, children=[])
nav_items.append(header)
for subpage in subpages_or_path:
if isinstance(subpage, str):
path = subpage
page = _path_to_page(path, None, url_context, use_dir_urls)
elif isinstance(subpage, dict):
title, path = next(iter(subpage.items()))
page = _path_to_page(path, title, url_context, use_dir_urls)
else:
msg = ("Line in 'page' config is of type {0}, dict or string "
"expected. Line: {1}"
).format(type(config_line), config_line)
raise exceptions.ConfigurationError(msg)
page.ancestors = [header]
else:
# Additional second level page.
page = Page(title=child_title, url=url, path=path, url_context=url_context)
header = nav_items[-1]
header.children.append(page)
page.ancestors = [header]
# Add in previous and next information.
if previous:
page.previous_page = previous
previous.next_page = page
previous = page
if previous:
page.previous_page = previous
previous.next_page = page
previous = page
pages.append(page)
pages.append(page)
if len(pages) == 0:
raise exceptions.ConfigurationError(
"No pages found in the pages config. "
"Remove it entirely to enable automatic page discovery.")
return (nav_items, pages)

View File

@@ -84,9 +84,9 @@ class BuildTests(unittest.TestCase):
def test_convert_internal_media(self):
"""Test relative image URL's are the same for different base_urls"""
pages = [
('index.md',),
('internal.md',),
('sub/internal.md')
'index.md',
'internal.md',
'sub/internal.md',
]
site_navigation = nav.SiteNavigation(pages)
@@ -107,9 +107,9 @@ class BuildTests(unittest.TestCase):
def test_convert_internal_asbolute_media(self):
"""Test absolute image URL's are correct for different base_urls"""
pages = [
('index.md',),
('internal.md',),
('sub/internal.md')
'index.md',
'internal.md',
'sub/internal.md',
]
site_navigation = nav.SiteNavigation(pages)
@@ -129,9 +129,9 @@ class BuildTests(unittest.TestCase):
def test_dont_convert_code_block_urls(self):
pages = [
('index.md',),
('internal.md',),
('sub/internal.md')
'index.md',
'internal.md',
'sub/internal.md',
]
site_navigation = nav.SiteNavigation(pages)
@@ -150,9 +150,9 @@ class BuildTests(unittest.TestCase):
def test_anchor_only_link(self):
pages = [
('index.md',),
('internal.md',),
('sub/internal.md')
'index.md',
'internal.md',
'sub/internal.md',
]
site_navigation = nav.SiteNavigation(pages)
@@ -172,7 +172,7 @@ class BuildTests(unittest.TestCase):
md_text = 'An [internal link](internal.md) to another document.'
expected = '<p>An <a href="internal/index.html">internal link</a> to another document.</p>'
pages = [
('internal.md',)
'internal.md',
]
site_navigation = nav.SiteNavigation(pages, use_directory_urls=False)
html, toc, meta = build.convert_markdown(md_text, site_navigation=site_navigation)
@@ -297,9 +297,9 @@ class BuildTests(unittest.TestCase):
def test_strict_mode_valid(self):
pages = [
('index.md',),
('internal.md',),
('sub/internal.md')
'index.md',
'internal.md',
'sub/internal.md',
]
site_nav = nav.SiteNavigation(pages)
@@ -309,9 +309,9 @@ class BuildTests(unittest.TestCase):
def test_strict_mode_invalid(self):
pages = [
('index.md',),
('internal.md',),
('sub/internal.md')
'index.md',
'internal.md',
'sub/internal.md',
]
site_nav = nav.SiteNavigation(pages)

View File

@@ -72,7 +72,7 @@ class ConfigTests(unittest.TestCase):
expected_result = {
'site_name': 'Example',
'pages': [
['index.md', 'Introduction']
{'Introduction': 'index.md'}
],
}
file_contents = dedent("""

View File

@@ -0,0 +1,65 @@
import unittest
import yaml
from mkdocs import legacy
class TestCompatabilityShim(unittest.TestCase):
# TODO: Remove in 1.0
def test_convert(self):
self.maxDiff = None
pages_yaml_old = """
pages:
- ['index.md', 'Home']
- ['user-guide/writing-your-docs.md', 'User Guide']
- ['user-guide/styling-your-docs.md', 'User Guide']
- ['about/license.md', 'About', 'License']
- ['about/release-notes.md', 'About']
- ['about/contributing.md', 'About', 'Contributing']
- ['help/contributing.md', 'Help', 'Contributing']
- ['support.md']
- ['cli.md', 'CLI Guide']
"""
pages_yaml_new = """
pages:
- Home: index.md
- User Guide:
- user-guide/writing-your-docs.md
- user-guide/styling-your-docs.md
- About:
- License: about/license.md
- about/release-notes.md
- Contributing: about/contributing.md
- Help:
- Contributing: help/contributing.md
- support.md
- CLI Guide: cli.md
"""
self.assertEqual(
legacy.pages_compat_shim(yaml.load(pages_yaml_old)['pages']),
yaml.load(pages_yaml_new)['pages'])
def test_convert_no_home(self):
self.maxDiff = None
pages_yaml_old = """
pages:
- ['index.md']
- ['about.md', 'About']
"""
pages_yaml_new = """
pages:
- index.md
- About: about.md
"""
self.assertEqual(
legacy.pages_compat_shim(yaml.load(pages_yaml_old)['pages']),
yaml.load(pages_yaml_new)['pages'])

View File

@@ -5,7 +5,7 @@ import mock
import os
import unittest
from mkdocs import nav
from mkdocs import nav, legacy
from mkdocs.exceptions import ConfigurationError
from mkdocs.tests.base import dedent
@@ -13,8 +13,8 @@ from mkdocs.tests.base import dedent
class SiteNavigationTests(unittest.TestCase):
def test_simple_toc(self):
pages = [
('index.md', 'Home'),
('about.md', 'About')
{'Home': 'index.md'},
{'About': 'about.md'}
]
expected = dedent("""
Home - /
@@ -27,8 +27,8 @@ class SiteNavigationTests(unittest.TestCase):
def test_empty_toc_item(self):
pages = [
('index.md',),
('about.md', 'About')
'index.md',
{'About': 'about.md'}
]
expected = dedent("""
Home - /
@@ -41,36 +41,16 @@ class SiteNavigationTests(unittest.TestCase):
def test_indented_toc(self):
pages = [
('index.md', 'Home'),
('api-guide/running.md', 'API Guide', 'Running'),
('api-guide/testing.md', 'API Guide', 'Testing'),
('api-guide/debugging.md', 'API Guide', 'Debugging'),
('about/release-notes.md', 'About', 'Release notes'),
('about/license.md', 'About', 'License')
]
expected = dedent("""
Home - /
API Guide
Running - /api-guide/running/
Testing - /api-guide/testing/
Debugging - /api-guide/debugging/
About
Release notes - /about/release-notes/
License - /about/license/
""")
site_navigation = nav.SiteNavigation(pages)
self.assertEqual(str(site_navigation).strip(), expected)
self.assertEqual(len(site_navigation.nav_items), 3)
self.assertEqual(len(site_navigation.pages), 6)
def test_indented_toc_missing_child_title(self):
pages = [
('index.md', 'Home'),
('api-guide/running.md', 'API Guide', 'Running'),
('api-guide/testing.md', 'API Guide'),
('api-guide/debugging.md', 'API Guide', 'Debugging'),
('about/release-notes.md', 'About', 'Release notes'),
('about/license.md', 'About', 'License')
{'Home': 'index.md'},
{'API Guide': [
{'Running': 'api-guide/running.md'},
{'Testing': 'api-guide/testing.md'},
{'Debugging': 'api-guide/debugging.md'},
]},
{'About': [
{'Release notes': 'about/release-notes.md'},
{'License': 'about/license.md'}
]}
]
expected = dedent("""
Home - /
@@ -89,9 +69,9 @@ class SiteNavigationTests(unittest.TestCase):
def test_nested_ungrouped(self):
pages = [
('index.md', 'Home'),
('about/contact.md', 'Contact'),
('about/sub/license.md', 'License Title')
{'Home': 'index.md'},
{'Contact': 'about/contact.md'},
{'License Title': 'about/sub/license.md'},
]
expected = dedent("""
Home - /
@@ -105,45 +85,43 @@ class SiteNavigationTests(unittest.TestCase):
def test_nested_ungrouped_no_titles(self):
pages = [
('index.md',),
('about/contact.md'),
('about/sub/license.md')
'index.md',
'about/contact.md',
'about/sub/license.md'
]
expected = dedent("""
Home - /
About
Contact - /about/contact/
License - /about/sub/license/
Contact - /about/contact/
License - /about/sub/license/
""")
site_navigation = nav.SiteNavigation(pages)
self.assertEqual(str(site_navigation).strip(), expected)
self.assertEqual(len(site_navigation.nav_items), 2)
self.assertEqual(len(site_navigation.nav_items), 3)
self.assertEqual(len(site_navigation.pages), 3)
@mock.patch.object(os.path, 'sep', '\\')
def test_nested_ungrouped_no_titles_windows(self):
pages = [
('index.md',),
('about\\contact.md'),
('about\\sub\\license.md')
'index.md',
'about\\contact.md',
'about\\sub\\license.md',
]
expected = dedent("""
Home - /
About
Contact - /about/contact/
License - /about/sub/license/
Contact - /about/contact/
License - /about/sub/license/
""")
site_navigation = nav.SiteNavigation(pages)
self.assertEqual(str(site_navigation).strip(), expected)
self.assertEqual(len(site_navigation.nav_items), 2)
self.assertEqual(len(site_navigation.nav_items), 3)
self.assertEqual(len(site_navigation.pages), 3)
def test_walk_simple_toc(self):
pages = [
('index.md', 'Home'),
('about.md', 'About')
{'Home': 'index.md'},
{'About': 'about.md'}
]
expected = [
dedent("""
@@ -161,8 +139,8 @@ class SiteNavigationTests(unittest.TestCase):
def test_walk_empty_toc(self):
pages = [
('index.md',),
('about.md', 'About')
'index.md',
{'About': 'about.md'}
]
expected = [
dedent("""
@@ -180,12 +158,16 @@ class SiteNavigationTests(unittest.TestCase):
def test_walk_indented_toc(self):
pages = [
('index.md', 'Home'),
('api-guide/running.md', 'API Guide', 'Running'),
('api-guide/testing.md', 'API Guide', 'Testing'),
('api-guide/debugging.md', 'API Guide', 'Debugging'),
('about/release-notes.md', 'About', 'Release notes'),
('about/license.md', 'About', 'License')
{'Home': 'index.md'},
{'API Guide': [
{'Running': 'api-guide/running.md'},
{'Testing': 'api-guide/testing.md'},
{'Debugging': 'api-guide/debugging.md'},
]},
{'About': [
{'Release notes': 'about/release-notes.md'},
{'License': 'about/license.md'}
]}
]
expected = [
dedent("""
@@ -255,7 +237,7 @@ class SiteNavigationTests(unittest.TestCase):
def test_base_url(self):
pages = [
('index.md',)
'index.md'
]
site_navigation = nav.SiteNavigation(pages, use_directory_urls=False)
base_url = site_navigation.url_context.make_relative('/')
@@ -263,8 +245,8 @@ class SiteNavigationTests(unittest.TestCase):
def test_relative_md_links_have_slash(self):
pages = [
('index.md',),
('user-guide/styling-your-docs.md',)
'index.md',
'user-guide/styling-your-docs.md'
]
site_navigation = nav.SiteNavigation(pages, use_directory_urls=False)
site_navigation.url_context.base_path = "/user-guide/configuration"
@@ -277,17 +259,17 @@ class SiteNavigationTests(unittest.TestCase):
"""
pages = [
('index.md', ),
('api-guide/running.md', ),
('about/notes.md', ),
('about/sub/license.md', ),
'index.md',
'api-guide/running.md',
'about/notes.md',
'about/sub/license.md',
]
url_context = nav.URLContext()
nav_items, pages = nav._generate_site_navigation(pages, url_context)
self.assertEqual([n.title for n in nav_items],
['Home', 'Api guide', 'About'])
['Home', 'Running', 'Notes', 'License'])
self.assertEqual([p.title for p in pages],
['Home', 'Running', 'Notes', 'License'])
@@ -297,25 +279,25 @@ class SiteNavigationTests(unittest.TestCase):
Verify inferring page titles based on the filename with a windows path
"""
pages = [
('index.md', ),
('api-guide\\running.md', ),
('about\\notes.md', ),
('about\\sub\\license.md', ),
'index.md',
'api-guide\\running.md',
'about\\notes.md',
'about\\sub\\license.md',
]
url_context = nav.URLContext()
nav_items, pages = nav._generate_site_navigation(pages, url_context)
self.assertEqual([n.title for n in nav_items],
['Home', 'Api guide', 'About'])
['Home', 'Running', 'Notes', 'License'])
self.assertEqual([p.title for p in pages],
['Home', 'Running', 'Notes', 'License'])
def test_invalid_pages_config(self):
bad_pages = [
(), # too short
('this', 'is', 'too', 'long'),
set(), # should be dict or string only
{"a": "index.md", "b": "index.md"} # extra key
]
for bad_page in bad_pages:
@@ -325,15 +307,28 @@ class SiteNavigationTests(unittest.TestCase):
self.assertRaises(ConfigurationError, _test)
def test_pages_config(self):
bad_page = {} # empty
def _test():
return nav._generate_site_navigation((bad_page, ), None)
self.assertRaises(ConfigurationError, _test)
def test_ancestors(self):
pages = [
('index.md', 'Home'),
('api-guide/running.md', 'API Guide', 'Running'),
('api-guide/testing.md', 'API Guide', 'Testing'),
('api-guide/debugging.md', 'API Guide', 'Debugging'),
('about/release-notes.md', 'About', 'Release notes'),
('about/license.md', 'About', 'License')
{'Home': 'index.md'},
{'API Guide': [
{'Running': 'api-guide/running.md'},
{'Testing': 'api-guide/testing.md'},
{'Debugging': 'api-guide/debugging.md'},
]},
{'About': [
{'Release notes': 'about/release-notes.md'},
{'License': 'about/license.md'}
]}
]
site_navigation = nav.SiteNavigation(pages)
@@ -361,3 +356,146 @@ class SiteNavigationTests(unittest.TestCase):
title = nav.file_to_title("mkdocs/tests/resources/no_title_metadata.md")
self.assertEqual(title, "Title")
class TestLegacyPagesConfig(unittest.TestCase):
def test_walk_simple_toc(self):
pages = legacy.pages_compat_shim([
('index.md', 'Home'),
('about.md', 'About')
])
expected = [
dedent("""
Home - / [*]
About - /about/
"""),
dedent("""
Home - /
About - /about/ [*]
""")
]
site_navigation = nav.SiteNavigation(pages)
for index, page in enumerate(site_navigation.walk_pages()):
self.assertEqual(str(site_navigation).strip(), expected[index])
def test_walk_empty_toc(self):
pages = legacy.pages_compat_shim([
('index.md',),
('about.md', 'About')
])
print(pages)
expected = [
dedent("""
Home - / [*]
About - /about/
"""),
dedent("""
Home - /
About - /about/ [*]
""")
]
site_navigation = nav.SiteNavigation(pages)
for index, page in enumerate(site_navigation.walk_pages()):
self.assertEqual(str(site_navigation).strip(), expected[index])
def test_walk_indented_toc(self):
pages = legacy.pages_compat_shim([
('index.md', 'Home'),
('api-guide/running.md', 'API Guide', 'Running'),
('api-guide/testing.md', 'API Guide', 'Testing'),
('api-guide/debugging.md', 'API Guide', 'Debugging'),
('about/release-notes.md', 'About', 'Release notes'),
('about/license.md', 'About', 'License')
])
expected = [
dedent("""
Home - / [*]
API Guide
Running - /api-guide/running/
Testing - /api-guide/testing/
Debugging - /api-guide/debugging/
About
Release notes - /about/release-notes/
License - /about/license/
"""),
dedent("""
Home - /
API Guide [*]
Running - /api-guide/running/ [*]
Testing - /api-guide/testing/
Debugging - /api-guide/debugging/
About
Release notes - /about/release-notes/
License - /about/license/
"""),
dedent("""
Home - /
API Guide [*]
Running - /api-guide/running/
Testing - /api-guide/testing/ [*]
Debugging - /api-guide/debugging/
About
Release notes - /about/release-notes/
License - /about/license/
"""),
dedent("""
Home - /
API Guide [*]
Running - /api-guide/running/
Testing - /api-guide/testing/
Debugging - /api-guide/debugging/ [*]
About
Release notes - /about/release-notes/
License - /about/license/
"""),
dedent("""
Home - /
API Guide
Running - /api-guide/running/
Testing - /api-guide/testing/
Debugging - /api-guide/debugging/
About [*]
Release notes - /about/release-notes/ [*]
License - /about/license/
"""),
dedent("""
Home - /
API Guide
Running - /api-guide/running/
Testing - /api-guide/testing/
Debugging - /api-guide/debugging/
About [*]
Release notes - /about/release-notes/
License - /about/license/ [*]
""")
]
site_navigation = nav.SiteNavigation(pages)
for index, page in enumerate(site_navigation.walk_pages()):
self.assertEqual(str(site_navigation).strip(), expected[index])
def test_indented_toc_missing_child_title(self):
pages = legacy.pages_compat_shim([
('index.md', 'Home'),
('api-guide/running.md', 'API Guide', 'Running'),
('api-guide/testing.md', 'API Guide'),
('api-guide/debugging.md', 'API Guide', 'Debugging'),
('about/release-notes.md', 'About', 'Release notes'),
('about/license.md', 'About', 'License')
])
expected = dedent("""
Home - /
API Guide
Running - /api-guide/running/
Testing - /api-guide/testing/
Debugging - /api-guide/debugging/
About
Release notes - /about/release-notes/
License - /about/license/
""")
site_navigation = nav.SiteNavigation(pages)
self.assertEqual(str(site_navigation).strip(), expected)
self.assertEqual(len(site_navigation.nav_items), 3)
self.assertEqual(len(site_navigation.pages), 6)

View File

@@ -103,8 +103,8 @@ class SearchTests(unittest.TestCase):
"""
pages = [
('index.md', 'Home'),
('about.md', 'About')
{'Home': 'index.md'},
{'About': 'about.md'},
]
site_navigation = nav.SiteNavigation(pages)

View File

@@ -54,8 +54,8 @@ class UtilsTests(unittest.TestCase):
def test_create_media_urls(self):
pages = [
('index.md', 'Home'),
('about.md', 'About')
{'Home': 'index.md'},
{'About': 'about.md'}
]
expected_results = {
'https://media.cdn.org/jq.js': 'https://media.cdn.org/jq.js',