Skip to content

Commit

Permalink
Add index_file config option
Browse files Browse the repository at this point in the history
The option allows to use a different page for the section page instead
of the first child.
  • Loading branch information
jannschu committed May 28, 2021
1 parent 6e7b33e commit 6646605
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 23 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions example/docs/z_noindex/a.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# a
Empty file added example/docs/z_noindex/foo.md
Empty file.
41 changes: 39 additions & 2 deletions mkdocs_section_index/plugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import collections
import logging
import os

import mkdocs.utils
from jinja2 import Environment
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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

Expand Down
86 changes: 65 additions & 21 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]


Expand Down

0 comments on commit 6646605

Please sign in to comment.