Skip to content

Commit 6646605

Browse files
committed
Add index_file config option
The option allows to use a different page for the section page instead of the first child.
1 parent 6e7b33e commit 6646605

File tree

5 files changed

+120
-23
lines changed

5 files changed

+120
-23
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ To make writing this kind of `nav` more natural ([in YAML there's no better opti
6363

6464
[literate-nav]: https://oprypin.github.io/mkdocs-literate-nav/
6565

66+
### Specifying the page to use for the section
67+
68+
By default the first child is used as the section page, even if there is no
69+
`index.md`. If you want to change the page to use add the `index_file` config
70+
option:
71+
72+
```yaml
73+
plugins:
74+
- section-index:
75+
index_file: index.md
76+
```
77+
78+
The value is the name of the page to use, in this case `index.md`. If a child
79+
with that name does not exist no section page is generated.
80+
6681
## [Implementation](https://github.com/oprypin/mkdocs-section-index/blob/master/mkdocs_section_index/plugin.py)
6782

6883
### "Protocol"

example/docs/z_noindex/a.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# a

example/docs/z_noindex/foo.md

Whitespace-only changes.

mkdocs_section_index/plugin.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import collections
22
import logging
3+
import os
34

45
import mkdocs.utils
56
from jinja2 import Environment
@@ -15,7 +16,29 @@
1516
log.addFilter(mkdocs.utils.warning_filter)
1617

1718

19+
def move_second_before_first(first, second, before="previous_page", after="next_page"):
20+
"""
21+
Move second element before first in a doubly linked list.
22+
"""
23+
el_before_second = getattr(second, before)
24+
el_after_second = getattr(second, after)
25+
el_before_first = getattr(first, before)
26+
27+
# now fix links from left to right
28+
if el_before_first:
29+
setattr(el_before_first, after, second)
30+
setattr(second, before, el_before_first)
31+
setattr(second, after, first)
32+
setattr(first, before, second)
33+
if el_before_second:
34+
setattr(el_before_second, after, el_after_second)
35+
if el_after_second:
36+
setattr(el_after_second, before, el_before_second)
37+
38+
1839
class SectionIndexPlugin(BasePlugin):
40+
config_scheme = (("index_file", mkdocs.config.config_options.Type(str, default=None)),)
41+
1942
def on_nav(self, nav: Navigation, config, files) -> Navigation:
2043
todo = collections.deque((nav.items,))
2144
while todo:
@@ -24,7 +47,17 @@ def on_nav(self, nav: Navigation, config, files) -> Navigation:
2447
if not isinstance(section, Section) or not section.children:
2548
continue
2649
todo.append(section.children)
27-
page = section.children[0]
50+
index_file = self.config["index_file"]
51+
if index_file is None:
52+
page_index = 0
53+
page = section.children[0]
54+
else:
55+
for page_index, child in enumerate(section.children):
56+
if os.path.basename(child.file.src_path) == index_file:
57+
page = child
58+
break
59+
else:
60+
continue
2861
if not isinstance(page, Page):
2962
continue
3063
assert not page.children
@@ -34,12 +67,16 @@ def on_nav(self, nav: Navigation, config, files) -> Navigation:
3467
page.is_section = page.is_page = True
3568
page.title = section.title
3669
# The page leaves the section but takes over children that used to be its peers.
37-
section.children.pop(0)
70+
section.children.pop(page_index)
3871
page.children = section.children
3972
for child in page.children:
4073
child.parent = page
74+
# Correct order if changed
75+
if page_index > 0:
76+
move_second_before_first(page.children[0], page)
4177
# The page replaces the section; the section will be garbage-collected.
4278
items[i] = page
79+
4380
self._nav = nav
4481
return nav
4582

tests/test_plugin.py

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,81 @@
1515

1616

1717
@pytest.mark.parametrize("directory_urls", ["use_directory_urls", "no_directory_urls"])
18-
@pytest.mark.parametrize("nav", ["explicit_nav", "derived_nav"])
19-
def test_real_example(tmpdir, directory_urls, nav):
18+
@pytest.mark.parametrize("nav_src", ["explicit_nav", "derived_nav"])
19+
@pytest.mark.parametrize("index_file", ["default_index_file", "index.md", "foo.md"])
20+
def test_real_example(tmpdir, directory_urls, nav_src, index_file):
2021
config = dict(
2122
docs_dir=str(example_dir / "docs"),
2223
site_dir=tmpdir,
2324
use_directory_urls=(directory_urls == "use_directory_urls"),
24-
nav=load_config(str(example_dir / "mkdocs.yml"))["nav"] if nav == "explicit_nav" else None,
25+
nav=load_config(str(example_dir / "mkdocs.yml"))["nav"]
26+
if nav_src == "explicit_nav"
27+
else None,
2528
)
29+
if nav_src == "derived_nav" and index_file != "default_index_file":
30+
# only test index_file for derived nav
31+
# if index_file is None we test the default value (which is first child)
32+
config["index_file"] = index_file
33+
else:
34+
index_file = None
2635
files = get_files(config)
2736
nav = get_navigation(files, config)
28-
nav = plugin.SectionIndexPlugin().on_nav(nav, config, files)
37+
instance = plugin.SectionIndexPlugin()
38+
instance.load_config(config)
39+
if index_file is None:
40+
assert instance.config["index_file"] is None
41+
nav = instance.on_nav(nav, instance.config, files)
2942

30-
assert len(nav.pages) == 5
31-
assert len(nav.items) == 3
43+
assert len(nav.pages) == (7 if nav_src == "derived_nav" else 5)
44+
assert len(nav.items) == (4 if nav_src == "derived_nav" else 3)
45+
46+
# items = index.md, baz.md, borgs/, z_noindex/
3247

3348
assert nav.items[1].is_page
3449
assert nav.items[1].file.name == "baz"
3550
assert not nav.items[1].is_section
3651

37-
sec = nav.items[2]
38-
assert isinstance(sec, SectionPage)
39-
assert sec.is_section
40-
assert sec.is_page
41-
assert sec.title == "Borgs"
42-
assert sec.url in ("borgs/", "borgs/index.html")
43-
assert sec.file.name == "index"
44-
45-
assert len(sec.children) == 2
46-
assert sec.children[0].is_page
47-
assert sec.children[0].file.name == "bar"
48-
49-
assert nav.items[1].next_page == sec
50-
assert sec.children[1].parent == sec
52+
assert nav.items[0].file.name == "index"
53+
assert not nav.items[0].is_section
54+
55+
borgs_sec = nav.items[2]
56+
assert isinstance(borgs_sec, SectionPage)
57+
assert borgs_sec.is_section
58+
assert borgs_sec.is_page
59+
assert borgs_sec.title == "Borgs"
60+
if index_file == "foo.md":
61+
assert borgs_sec.url in ("borgs/foo/", "borgs/foo.html")
62+
assert borgs_sec.file.name == "foo"
63+
else:
64+
assert borgs_sec.url in ("borgs/", "borgs/index.html")
65+
assert borgs_sec.file.name == "index"
66+
67+
assert len(borgs_sec.children) == 2
68+
assert borgs_sec.children[0].is_page
69+
if index_file == "foo.md":
70+
assert borgs_sec.children[0].file.name == "index"
71+
else:
72+
assert borgs_sec.children[0].file.name == "bar"
73+
74+
assert nav.items[1].next_page == borgs_sec
75+
assert borgs_sec.children[1].parent == borgs_sec
76+
77+
# check order
78+
if index_file == "foo.md":
79+
# new section page
80+
assert nav.items[3].previous_page == nav.items[2].children[-1]
81+
assert nav.items[3].next_page == nav.items[3].children[0]
82+
83+
# page previously before new section page
84+
assert nav.items[2].children[0].previous_page == nav.items[2]
85+
# page previously after new section page
86+
assert nav.items[2].children[1].previous_page == nav.items[2].children[0]
87+
88+
# first child
89+
assert nav.items[2].children[0].next_page == nav.items[2].children[1]
90+
91+
# previous page before child[0]
92+
assert nav.items[1].next_page == nav.items[2]
5193

5294

5395
@dataclasses.dataclass
@@ -74,7 +116,9 @@ def test_nav_repr(golden, tmpdir):
74116
config = dict(nav=golden["input"], use_directory_urls=use_directory_urls)
75117
files = FakeFiles(config)
76118
nav = get_navigation(files, config)
77-
nav = plugin.SectionIndexPlugin().on_nav(nav, config, files)
119+
instance = plugin.SectionIndexPlugin()
120+
instance.load_config(config)
121+
nav = instance.on_nav(nav, instance.config, files)
78122
assert str(nav) == golden.out[use_directory_urls]
79123

80124

0 commit comments

Comments
 (0)