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 mkdocs-literate-nav to handle ordering and recursive navs #45

Merged
merged 2 commits into from
May 29, 2024
Merged
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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies = [
"mkdocstrings-python>=1.9.1",
"mkdocs-macros-plugin",
"mkdocs-site-urls",
"mkdocs-literate-nav",
"importlib_resources",
"httpx",
"rich",
Expand Down
2 changes: 2 additions & 0 deletions src/pulp_docs/data/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ plugins:
module_name: '../mkdocs_macros'
- tags:
tags_file: pulp-docs/docs/tags.md
- literate-nav:
nav_file: _SUMMARY.md
- mkdocstrings:
handlers:
python:
Expand Down
112 changes: 25 additions & 87 deletions src/pulp_docs/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,103 +57,41 @@ def grouped_by_persona(tmpdir: Path, repos: Repos):
{content-type}
"""
f = AgregationUtils(tmpdir, repos)
usage_section = [
{"Overview": f.section_file("user/index.md")},
{
"Pulpcore": [
f.section(
Names.PULPCORE_TUTORIAL,
f.get_children,
"pulpcore/docs/user/tutorials",
),
f.section(Names.LEARN, f.get_children, "pulpcore/docs/user/learn"),
f.section(Names.GUIDES, f.get_children, "pulpcore/docs/user/guides"),
]
},
{
"Plugins": f.repo_grouping(
"{repo}/docs/user/{content}", repo_types=["content"]
)
},
{"Extras": f.repo_grouping("{repo}/docs/user/{content}", repo_types=["other"])},
]
admin_section = [
{"Overview": f.section_file("admin/index.md")},
{
"Pulpcore": [
f.section(
Names.PULPCORE_TUTORIAL,
f.get_children,
"pulpcore/docs/admin/tutorials",
),
f.section(Names.LEARN, f.get_children, "pulpcore/docs/admin/learn"),
f.section(Names.GUIDES, f.get_children, "pulpcore/docs/admin/guides"),
]
},
f.section(
"Plugins",
f.repo_grouping,
"{repo}/docs/admin/{content}",
repo_types=["content"],
hide_empty_section=False,
),
f.section(
"Extras",
f.repo_grouping,
"{repo}/docs/admin/{content}",
repo_types=["other"],
hide_empty_section=False,
),
]
development_section = [
{"Overview": f.section_file("dev/index.md")},
{
"Pulpcore": [
f.section(
Names.PULPCORE_TUTORIAL,
f.get_children,
"pulpcore/docs/dev/tutorials",
),
f.section(Names.LEARN, f.get_children, "pulpcore/docs/dev/learn"),
f.section(Names.GUIDES, f.get_children, "pulpcore/docs/dev/guides"),
]
},
{
"Plugins": f.repo_grouping(
"{repo}/docs/dev/{content}", repo_types=["content"]
)
},
{"Extras": f.repo_grouping("{repo}/docs/dev/{content}", repo_types=["other"])},
]
SECTION_HOST = "pulp-docs"
CHANGES_PATH = "{repo}/changes/changelog.md"

# Manual section for each persona
manual_nav = {}
for section in ("user", "admin", "dev"):
TEMPLATE_STRING = "{repo}/docs/%s/{content}" % section
section_nav = [
{"Overview": f"{SECTION_HOST}/docs/sections/{section}/index.md"},
{"Core": f.repo_grouping(TEMPLATE_STRING, repo_types=["core"])},
{"Plugins": f.repo_grouping(TEMPLATE_STRING, repo_types=["content"])},
{"Extras": f.repo_grouping(TEMPLATE_STRING, repo_types=["other"])},
]
manual_nav[section] = section_nav

# Custom help section
help_section = [
{"Overview": f.section_file("help/index.md")},
{"Community": f.get_children("pulp-docs/docs/sections/help/community")},
{"More": f.get_children("pulp-docs/docs/sections/help/more")},
{"Overview": f"{SECTION_HOST}/docs/sections/help/index.md"},
{"Community": f"{SECTION_HOST}/docs/sections/help/community/"},
{"More": f"{SECTION_HOST}/docs/sections/help/more/"},
{
"Changelogs": [
{"Pulpcore": "pulpcore/changes/changelog.md"},
{
"Plugins": sorted(
f.repo_grouping(
"{repo}/changes", repo_types=["content"]
).items()
)
},
{
"Extra": sorted(
f.repo_grouping("{repo}/changes", repo_types=["other"]).items()
)
},
{"Core": f.changes_grouping(CHANGES_PATH, repo_types=["core"])},
{"Plugins": f.changes_grouping(CHANGES_PATH, repo_types=["content"])},
{"Extra": f.changes_grouping(CHANGES_PATH, repo_types=["other"])},
]
},
]

# Main Section
navigation = [
{"Home": "index.md"},
{"User Manual": usage_section},
{"Admin Manual": admin_section},
{"Developer Manual": development_section},
{"User Manual": manual_nav["user"]},
{"Admin Manual": manual_nav["admin"]},
{"Developer Manual": manual_nav["dev"]},
{"Help": help_section},
]
return navigation
186 changes: 66 additions & 120 deletions src/pulp_docs/utils/aggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,6 @@ def section(self, name: str, fn: t.Callable, *args, **kwargs) -> dict:
return {name: section}
return {name: section} if section else {"": ""}

def get_children(self, path: t.Union[str, Path]) -> t.List[str]:
"""
Get all markdown files contained in @path recursively.

Uses the dirname.title() as the subsection name if there are subdirs.
"""
basepath = self.tmpdir / path if isinstance(path, str) else path
if not basepath.exists():
return []

def _get_tree(_path):
"""Recursive scandir"""
with os.scandir(_path) as it:
children = []
for entry in sorted(it, key=lambda x: x.name):
if entry.is_file() and entry.name.endswith(".md"):
filename = str(Path(entry.path).relative_to(self.tmpdir))
children.append(filename)
elif entry.is_dir():
dir_title = self.normalize_title(entry.name)
sub_section = {dir_title: _get_tree(entry)}
children.append(sub_section)
return children

result = _get_tree(basepath)
return result

def normalize_title(self, raw_title: str):
return raw_title.replace("_", " ").title()

Expand Down Expand Up @@ -89,57 +62,80 @@ def repo_grouping(
}
```
"""
_nav = {}
_expand_content_types = "{content}" in template_str

# Selected a set of repos
selected_repos = []
selected_content = content_types or [
"tutorials",
"guides",
"learn",
"reference",
]
if not repo_types: # default case
selected_repos = self.repos.all
else:
selected_repos.extend(self.repos.get_repos(repo_types=repo_types))

# Dont expand content-types
if not _expand_content_types:
for repo in selected_repos:
lookup_path = self._parse_template_str(template_str, repo.name)
_repo_content = self.get_children(lookup_path)

selected_repos = self.repos.all
if repo_types:
selected_repos = self.repos.get_repos(repo_types=repo_types)

group_nav = []
for repo in selected_repos:
repo_nav = []
# Include index.md if present in staging_docs/{persona}/index.md
persona_basepath = self._parse_template_str(
template_str, repo.name, "placeholder"
).parent
index_path = persona_basepath / "index.md"
if index_path.exists():
repo_nav.append({"Overview": str(index_path.relative_to(self.tmpdir))})

for content_type in selected_content:
# Get repo files from content-type and persona
lookup_path = self._parse_template_str(
template_str, repo.name, content_type
)
_repo_content = self._add_literate_nav_dir(lookup_path)

# Prevent rendering content-type section if there are no files
if _repo_content:
_nav[repo.title] = (
_repo_content if len(_repo_content) > 1 else _repo_content[0]
)
# Expand content-types
else:
for repo in selected_repos:
repo_nav = {}
# Include index.md if present in staging_docs/{persona}/index.md
persona_basepath = self._parse_template_str(
template_str, repo.name, "placeholder"
).parent
index_path = persona_basepath / "index.md"
if index_path.exists():
repo_nav["Overview"] = str(index_path.relative_to(self.tmpdir))

for content_type in selected_content:
# Get repo files from content-type and persona
lookup_path = self._parse_template_str(
template_str, repo.name, content_type
)
_repo_content = self.get_children(lookup_path)

# Prevent rendering content-type section if there are no files
if _repo_content:
content_type_name = Names.get(content_type)
repo_nav[content_type_name] = _repo_content # type: ignore
if repo_nav:
_nav[repo.title] = repo_nav
return _nav or ["#"]
content_type_title = Names.get(content_type)
repo_nav.append({content_type_title: _repo_content}) # type: ignore
if repo_nav:
group_nav.append({repo.title: repo_nav})
return group_nav or ["#"]

def changes_grouping(
self, changes_path_template: str, repo_types: t.Optional[t.List[str]] = None
):
selected_repos = self.repos.all
if repo_types:
selected_repos = self.repos.get_repos(repo_types=repo_types)

group_nav = [
{repo.title: changes_path_template.format(repo=repo.name)}
for repo in selected_repos
]
return group_nav or ["#"] # type: ignore

def _add_literate_nav_dir(self, lookup_path: Path) -> t.Optional[str]:
"""
Take a path and return a path-str or None.

The path-str is expanded by mkdocs-literate-nav. E.g:

For "foo/bar/eggs/", it will:
1. Try to find "foo/bar/eggs/_SUMMARY.md"
2. If cant find, generate recursive nav for "foo/bar/eggs"

If the @lookup_path is non-existent, non-dir or doesnt contain .md, returns None.
"""
if not lookup_path.exists():
return None

if not lookup_path.is_dir():
return None

if len(list(lookup_path.rglob("*.md"))) == 0:
return None

path_str = str(lookup_path.relative_to(self.tmpdir)) + "/"
return path_str

def _parse_template_str(
self, template_str: str, repo_name: str, content_type: t.Optional[str] = None
Expand All @@ -163,53 +159,3 @@ def _parse_template_str(

return self.tmpdir / template_str.format(**kwargs)

def _pop_quickstart_from(self, pathlist: t.List[str]) -> t.Optional[str]:
"""Get quickstart.md file from filelist with case and variations tolerance"""
for path in pathlist:
if not isinstance(path, str):
continue

filename = path.split("/")[-1]
if filename.lower() in ("quickstart.md", "quick-start.md"):
pathlist.remove(path)
return path
return None

def repo_reference_grouping(self):
"""
Create reference section by aggregating some specific files.

Group according to the pattern:
{repo}/
Readme.md
Rest Api.md
Code Api/
Module A.md
Module B.md
Changelog.md

"""
template_str = "{repo}/docs/reference"
_nav = {}
for repo in self.repos.all:
lookup_path = self.tmpdir / template_str.format(repo=repo.name)
_repo_content = self.get_children(lookup_path)
reference_section = [
{"REST API": f"{repo.name}/docs/rest_api.md"},
{"Readme": f"{repo.name}/README.md"},
{"Code API": _repo_content},
{"Changelog": f"{repo.name}/CHANGELOG.md"},
]
_nav[repo.title] = reference_section
return _nav

def section_file(self, section_and_filename: str):
"""Get a markdown file from the website section folder."""
basepath = "pulp-docs/docs/sections"
return f"{basepath}/{section_and_filename}"

def section_children(self, section_name: str):
"""Get children markdown files from the website section folder."""
basepath = "pulpcore/docs/sections"
section = self.get_children(f"{basepath}/{section_name}")
return section
Loading