Skip to content

Commit bf8d0fd

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 bf8d0fd

File tree

5 files changed

+114
-23
lines changed

5 files changed

+114
-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: 33 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
@@ -16,6 +17,8 @@
1617

1718

1819
class SectionIndexPlugin(BasePlugin):
20+
config_scheme = (("index_file", mkdocs.config.config_options.Type(str, default=None)),)
21+
1922
def on_nav(self, nav: Navigation, config, files) -> Navigation:
2023
todo = collections.deque((nav.items,))
2124
while todo:
@@ -24,7 +27,17 @@ def on_nav(self, nav: Navigation, config, files) -> Navigation:
2427
if not isinstance(section, Section) or not section.children:
2528
continue
2629
todo.append(section.children)
27-
page = section.children[0]
30+
index_file = self.config["index_file"]
31+
if index_file is None:
32+
page_index = 0
33+
page = section.children[0]
34+
else:
35+
for page_index, child in enumerate(section.children):
36+
if os.path.basename(child.file.src_path) == index_file:
37+
page = child
38+
break
39+
else:
40+
continue
2841
if not isinstance(page, Page):
2942
continue
3043
assert not page.children
@@ -34,12 +47,30 @@ def on_nav(self, nav: Navigation, config, files) -> Navigation:
3447
page.is_section = page.is_page = True
3548
page.title = section.title
3649
# The page leaves the section but takes over children that used to be its peers.
37-
section.children.pop(0)
50+
section.children.pop(page_index)
3851
page.children = section.children
3952
for child in page.children:
4053
child.parent = page
54+
# Correct order if changed
55+
if page_index > 0:
56+
# Child previously before new section page
57+
page.children[page_index - 1].next_page = page.next_page
58+
# Page previously after new section page
59+
if page.next_page:
60+
page.next_page.previous_page = page.children[page_index - 1]
61+
62+
# New section page itself
63+
page.next_page = page.children[0]
64+
page.previous_page = page.children[0].previous_page
65+
66+
# Page before first child now points to new section page
67+
if page.children[0].previous_page:
68+
page.children[0].previous_page.next_page = page
69+
# First child
70+
page.children[0].previous_page = page
4171
# The page replaces the section; the section will be garbage-collected.
4272
items[i] = page
73+
4374
self._nav = nav
4475
return nav
4576

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)