Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add use-page config option #5

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
48 changes: 46 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,36 @@
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.
"""
if first == second:
return
el_before_first = getattr(first, before)
if el_before_first == second:
# already correct, also invalid for later logic
return
el_before_second = getattr(second, before)
el_after_second = getattr(second, after)

# fix links where second was inserted
if el_before_first:
setattr(el_before_first, after, second)
setattr(second, before, el_before_first)
setattr(second, after, first)
setattr(first, before, second)

# fix links where second was removed
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 +54,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 +74,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":
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding actual files to the real example and branching all of this logic, I think the test should be separate and new.

If you need to use files from the real example, you can first create a directory for it then copy those files and add more.

shutil.copytree(example_dir / "docs", str(tmpdir))

noindex_dir = (tmpdir / "docs").mkdir("z_noindex")
(noindex_dir / "a.md").write_text("# a")

then change docs_dir=tmp_dir,

Or if you don't need the real example, just don't copy the files

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good

# 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