From 66466059ddd4936a2648c7b202991b5d0ccdcaab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannik=20Sch=C3=BCrg?= Date: Fri, 28 May 2021 09:50:18 +0200 Subject: [PATCH] Add index_file config option The option allows to use a different page for the section page instead of the first child. --- README.md | 15 ++++++ example/docs/z_noindex/a.md | 1 + example/docs/z_noindex/foo.md | 0 mkdocs_section_index/plugin.py | 41 +++++++++++++++- tests/test_plugin.py | 86 +++++++++++++++++++++++++--------- 5 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 example/docs/z_noindex/a.md create mode 100644 example/docs/z_noindex/foo.md diff --git a/README.md b/README.md index 414faea..34f23ad 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,21 @@ To make writing this kind of `nav` more natural ([in YAML there's no better opti [literate-nav]: https://oprypin.github.io/mkdocs-literate-nav/ +### Specifying the page to use for the section + +By default the first child is used as the section page, even if there is no +`index.md`. If you want to change the page to use add the `index_file` config +option: + +```yaml +plugins: + - section-index: + index_file: index.md +``` + +The value is the name of the page to use, in this case `index.md`. If a child +with that name does not exist no section page is generated. + ## [Implementation](https://github.com/oprypin/mkdocs-section-index/blob/master/mkdocs_section_index/plugin.py) ### "Protocol" diff --git a/example/docs/z_noindex/a.md b/example/docs/z_noindex/a.md new file mode 100644 index 0000000..d229daa --- /dev/null +++ b/example/docs/z_noindex/a.md @@ -0,0 +1 @@ +# a \ No newline at end of file diff --git a/example/docs/z_noindex/foo.md b/example/docs/z_noindex/foo.md new file mode 100644 index 0000000..e69de29 diff --git a/mkdocs_section_index/plugin.py b/mkdocs_section_index/plugin.py index 956aab0..e69ed34 100644 --- a/mkdocs_section_index/plugin.py +++ b/mkdocs_section_index/plugin.py @@ -1,5 +1,6 @@ import collections import logging +import os import mkdocs.utils from jinja2 import Environment @@ -15,7 +16,29 @@ log.addFilter(mkdocs.utils.warning_filter) +def move_second_before_first(first, second, before="previous_page", after="next_page"): + """ + Move second element before first in a doubly linked list. + """ + el_before_second = getattr(second, before) + el_after_second = getattr(second, after) + el_before_first = getattr(first, before) + + # now fix links from left to right + if el_before_first: + setattr(el_before_first, after, second) + setattr(second, before, el_before_first) + setattr(second, after, first) + setattr(first, before, second) + if el_before_second: + setattr(el_before_second, after, el_after_second) + if el_after_second: + setattr(el_after_second, before, el_before_second) + + class SectionIndexPlugin(BasePlugin): + config_scheme = (("index_file", mkdocs.config.config_options.Type(str, default=None)),) + def on_nav(self, nav: Navigation, config, files) -> Navigation: todo = collections.deque((nav.items,)) while todo: @@ -24,7 +47,17 @@ def on_nav(self, nav: Navigation, config, files) -> Navigation: if not isinstance(section, Section) or not section.children: continue todo.append(section.children) - page = section.children[0] + index_file = self.config["index_file"] + if index_file is None: + page_index = 0 + page = section.children[0] + else: + for page_index, child in enumerate(section.children): + if os.path.basename(child.file.src_path) == index_file: + page = child + break + else: + continue if not isinstance(page, Page): continue assert not page.children @@ -34,12 +67,16 @@ def on_nav(self, nav: Navigation, config, files) -> Navigation: page.is_section = page.is_page = True page.title = section.title # The page leaves the section but takes over children that used to be its peers. - section.children.pop(0) + section.children.pop(page_index) page.children = section.children for child in page.children: child.parent = page + # Correct order if changed + if page_index > 0: + move_second_before_first(page.children[0], page) # The page replaces the section; the section will be garbage-collected. items[i] = page + self._nav = nav return nav diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 6d3284a..9475944 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -15,39 +15,81 @@ @pytest.mark.parametrize("directory_urls", ["use_directory_urls", "no_directory_urls"]) -@pytest.mark.parametrize("nav", ["explicit_nav", "derived_nav"]) -def test_real_example(tmpdir, directory_urls, nav): +@pytest.mark.parametrize("nav_src", ["explicit_nav", "derived_nav"]) +@pytest.mark.parametrize("index_file", ["default_index_file", "index.md", "foo.md"]) +def test_real_example(tmpdir, directory_urls, nav_src, index_file): config = dict( docs_dir=str(example_dir / "docs"), site_dir=tmpdir, use_directory_urls=(directory_urls == "use_directory_urls"), - nav=load_config(str(example_dir / "mkdocs.yml"))["nav"] if nav == "explicit_nav" else None, + nav=load_config(str(example_dir / "mkdocs.yml"))["nav"] + if nav_src == "explicit_nav" + else None, ) + if nav_src == "derived_nav" and index_file != "default_index_file": + # only test index_file for derived nav + # if index_file is None we test the default value (which is first child) + config["index_file"] = index_file + else: + index_file = None files = get_files(config) nav = get_navigation(files, config) - nav = plugin.SectionIndexPlugin().on_nav(nav, config, files) + instance = plugin.SectionIndexPlugin() + instance.load_config(config) + if index_file is None: + assert instance.config["index_file"] is None + nav = instance.on_nav(nav, instance.config, files) - assert len(nav.pages) == 5 - assert len(nav.items) == 3 + assert len(nav.pages) == (7 if nav_src == "derived_nav" else 5) + assert len(nav.items) == (4 if nav_src == "derived_nav" else 3) + + # items = index.md, baz.md, borgs/, z_noindex/ assert nav.items[1].is_page assert nav.items[1].file.name == "baz" assert not nav.items[1].is_section - sec = nav.items[2] - assert isinstance(sec, SectionPage) - assert sec.is_section - assert sec.is_page - assert sec.title == "Borgs" - assert sec.url in ("borgs/", "borgs/index.html") - assert sec.file.name == "index" - - assert len(sec.children) == 2 - assert sec.children[0].is_page - assert sec.children[0].file.name == "bar" - - assert nav.items[1].next_page == sec - assert sec.children[1].parent == sec + assert nav.items[0].file.name == "index" + assert not nav.items[0].is_section + + borgs_sec = nav.items[2] + assert isinstance(borgs_sec, SectionPage) + assert borgs_sec.is_section + assert borgs_sec.is_page + assert borgs_sec.title == "Borgs" + if index_file == "foo.md": + assert borgs_sec.url in ("borgs/foo/", "borgs/foo.html") + assert borgs_sec.file.name == "foo" + else: + assert borgs_sec.url in ("borgs/", "borgs/index.html") + assert borgs_sec.file.name == "index" + + assert len(borgs_sec.children) == 2 + assert borgs_sec.children[0].is_page + if index_file == "foo.md": + assert borgs_sec.children[0].file.name == "index" + else: + assert borgs_sec.children[0].file.name == "bar" + + assert nav.items[1].next_page == borgs_sec + assert borgs_sec.children[1].parent == borgs_sec + + # check order + if index_file == "foo.md": + # new section page + assert nav.items[3].previous_page == nav.items[2].children[-1] + assert nav.items[3].next_page == nav.items[3].children[0] + + # page previously before new section page + assert nav.items[2].children[0].previous_page == nav.items[2] + # page previously after new section page + assert nav.items[2].children[1].previous_page == nav.items[2].children[0] + + # first child + assert nav.items[2].children[0].next_page == nav.items[2].children[1] + + # previous page before child[0] + assert nav.items[1].next_page == nav.items[2] @dataclasses.dataclass @@ -74,7 +116,9 @@ def test_nav_repr(golden, tmpdir): config = dict(nav=golden["input"], use_directory_urls=use_directory_urls) files = FakeFiles(config) nav = get_navigation(files, config) - nav = plugin.SectionIndexPlugin().on_nav(nav, config, files) + instance = plugin.SectionIndexPlugin() + instance.load_config(config) + nav = instance.on_nav(nav, instance.config, files) assert str(nav) == golden.out[use_directory_urls]