Skip to content

Commit 668f9f4

Browse files
authored
Merge pull request #262 from joe733/workshop
feat: docs can be built with both sphinx & mkdocs
2 parents 68227f2 + b2240e7 commit 668f9f4

File tree

8 files changed

+503
-90
lines changed

8 files changed

+503
-90
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ instance/
6969
.scrapy
7070

7171
# Sphinx documentation
72+
docs/reference/
7273
docs/_build/
74+
docs/*.rst
7375

7476
# PyBuilder
7577
.pybuilder/

build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
if __name__ == "__main__":
1212
project_dir = Path(__file__).parent
13-
generate_documentation(project_dir, discard_refs=False)
13+
generate_documentation(project_dir, only_md=True, discard_refs=False)
1414
process = run(("poetry", "build"), capture_output=True)
1515
print(process.stderr.decode() + process.stdout.decode())
1616
rmtree(project_dir / "docs/reference", ignore_errors=True)

docs/Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line, and also
5+
# from the environment for the first two.
6+
SPHINXOPTS ?=
7+
SPHINXBUILD ?= sphinx-build
8+
SOURCEDIR = .
9+
BUILDDIR = _build
10+
11+
# Put it first so that "make" without argument is like "make help".
12+
help:
13+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14+
15+
.PHONY: help Makefile
16+
17+
# Catch-all target: route all unknown targets to Sphinx using the new
18+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19+
%: Makefile
20+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

docs/conf.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Configuration file for the Sphinx documentation builder.
2+
3+
For the full list of built-in configuration values, see the documentation:
4+
https://www.sphinx-doc.org/en/master/usage/configuration.html
5+
"""
6+
7+
# standard
8+
from importlib.metadata import metadata
9+
from datetime import datetime
10+
11+
# -- Project information ----------------------------------------------------------
12+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
13+
_metadata = metadata("validators")
14+
15+
project: str = _metadata["name"]
16+
author: str = _metadata["author"]
17+
project_copyright = f"2013 - {datetime.now().year}, {_metadata['author']}"
18+
version: str = _metadata["version"]
19+
release = version
20+
21+
# -- General configuration ---------------------------------------------------
22+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
23+
extensions = [
24+
"sphinx.ext.autodoc",
25+
"sphinx.ext.napoleon",
26+
"myst_parser",
27+
]
28+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "*.md"]
29+
30+
31+
# -- Options for HTML output -------------------------------------------------
32+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
33+
html_theme = "alabaster"
34+
35+
# -- Options for manpage generation -------------------------------------------------
36+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-man_pages
37+
man_pages = [("index", project, _metadata["summary"], [author], 1)]
38+
39+
# -- Options for docstring parsing -------------------------------------------------
40+
# https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html
41+
napoleon_numpy_docstring = False

docs/gen_docs.py

Lines changed: 83 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from os.path import getsize
99
from subprocess import run
1010
from pathlib import Path
11+
from sys import argv
1112

1213
# external
1314
from yaml import safe_load, safe_dump
@@ -19,28 +20,37 @@ def _write_ref_content(source: Path, module_name: str, func_name: str):
1920
"""Write content."""
2021
with open(source, "at") as ref:
2122
ref.write(
22-
(f"# {module_name}\n\n" if getsize(source) == 0 else "")
23-
+ f"::: validators.{module_name}.{func_name}\n"
23+
(
24+
(f"# {module_name}\n\n" if getsize(source) == 0 else "")
25+
+ f"::: validators.{module_name}.{func_name}\n"
26+
)
27+
if f"{source}".endswith(".md")
28+
else (
29+
(f"{module_name}\n{len(module_name) * '-'}\n\n" if getsize(source) == 0 else "")
30+
+ f".. module:: validators.{module_name}\n"
31+
+ f".. autofunction:: {func_name}\n"
32+
)
2433
)
2534

2635

27-
def _generate_reference(source: Path, destination: Path):
28-
"""Generate reference."""
29-
nav_items: Dict[str, List[str]] = {"Code Reference": []}
30-
# clean destination
31-
if destination.exists() and destination.is_dir():
32-
rmtree(destination)
33-
destination.mkdir(exist_ok=True)
34-
# parse source
36+
def _parse_package(source: Path):
37+
"""Parse validators package."""
3538
v_ast = parse(source.read_text(), source)
36-
# generate reference content
3739
for namespace in (node for node in v_ast.body if isinstance(node, ImportFrom)):
3840
if not namespace.module:
3941
continue
40-
for alias in namespace.names:
41-
ref_module = destination / f"{namespace.module}.md"
42-
_write_ref_content(ref_module, namespace.module, alias.name)
43-
nav_items["Code Reference"].append(f"reference/{namespace.module}.md")
42+
yield (namespace.module, namespace.names)
43+
44+
45+
def _generate_reference(source: Path, destination: Path, ext: str):
46+
"""Generate reference."""
47+
nav_items: Dict[str, List[str]] = {"Code Reference": []}
48+
# generate reference content
49+
for module_name, aliases in _parse_package(source):
50+
for alias in aliases:
51+
_write_ref_content(destination / f"{module_name}.{ext}", module_name, alias.name)
52+
if ext == "md":
53+
nav_items["Code Reference"].append(f"reference/{module_name}.md")
4454
return nav_items
4555

4656

@@ -54,27 +64,71 @@ def _update_mkdocs_config(source: Path, destination: Path, nav_items: Dict[str,
5464
safe_dump(mkdocs_conf, mkf, sort_keys=False)
5565

5666

57-
def generate_documentation(source: Path, discard_refs: bool = True):
58-
"""Generate documentation."""
59-
# copy readme as docs index file
60-
copy(source / "README.md", source / "docs/index.md")
61-
# generate reference documentation
62-
nav_items = _generate_reference(source / "validators/__init__.py", source / "docs/reference")
67+
def _gen_md_docs(source: Path, refs_path: Path):
68+
"""Generate Markdown docs."""
69+
nav_items = _generate_reference(source / "validators/__init__.py", refs_path, "md")
6370
# backup mkdocs config
64-
_update_mkdocs_config(source / "mkdocs.yaml", source / "mkdocs.bak.yml", nav_items)
65-
# build docs as subprocess
71+
_update_mkdocs_config(source / "mkdocs.yaml", source / "mkdocs.bak.yaml", nav_items)
72+
# build mkdocs as subprocess
6673
print(run(("mkdocs", "build"), capture_output=True).stderr.decode())
6774
# restore mkdocs config
68-
move(str(source / "mkdocs.bak.yml"), source / "mkdocs.yaml")
75+
move(str(source / "mkdocs.bak.yaml"), source / "mkdocs.yaml")
76+
77+
78+
def _gen_rst_docs(source: Path, refs_path: Path):
79+
"""Generate reStructuredText docs."""
80+
# external
81+
from pypandoc import convert_file # type: ignore
82+
83+
# generate index.rst
84+
with open(source / "docs/index.rst", "wt") as idx_f:
85+
idx_f.write(
86+
convert_file(source_file=source / "docs/index.md", format="md", to="rst")
87+
+ "\n\n.. toctree::"
88+
+ "\n :hidden:"
89+
+ "\n :maxdepth: 2"
90+
+ "\n :caption: Reference:"
91+
+ "\n :glob:\n"
92+
+ "\n reference/*\n"
93+
)
94+
# generate RST reference documentation
95+
_generate_reference(source / "validators/__init__.py", refs_path, "rst")
96+
# build sphinx web pages as subprocess
97+
web_build = run(("sphinx-build", "docs", "docs/_build/web"), capture_output=True)
98+
print(web_build.stderr.decode(), "\n", web_build.stdout.decode(), sep="")
99+
# build sphinx man pages as subprocess
100+
man_build = run(("sphinx-build", "-b", "man", "docs", "docs/_build/man"), capture_output=True)
101+
print(man_build.stderr.decode(), "\n", man_build.stdout.decode(), sep="")
102+
103+
104+
def generate_documentation(
105+
source: Path, only_md: bool = False, only_rst: bool = False, discard_refs: bool = True
106+
):
107+
"""Generate documentation."""
108+
if only_md and only_rst:
109+
return
110+
# copy readme as docs index file
111+
copy(source / "README.md", source / "docs/index.md")
112+
# clean destination
113+
refs_path = source / "docs/reference"
114+
if refs_path.exists() and refs_path.is_dir():
115+
rmtree(refs_path)
116+
refs_path.mkdir(exist_ok=True)
117+
# documentation for each kind
118+
if not only_rst:
119+
_gen_md_docs(source, refs_path)
120+
if not only_md:
121+
_gen_rst_docs(source, refs_path)
69122
# optionally discard reference folder
70123
if discard_refs:
71124
rmtree(source / "docs/reference")
72125

73126

74127
if __name__ == "__main__":
75128
project_root = Path(__file__).parent.parent
76-
generate_documentation(project_root)
77-
# NOTE: use following lines only for testing/debugging
78-
# generate_documentation(project_root, discard_refs=False)
79-
# from sys import argv
80-
# generate_documentation(project_root, len(argv) > 1 and argv[1] == "--keep")
129+
generate_documentation(
130+
project_root,
131+
only_md=True,
132+
only_rst=False,
133+
discard_refs=len(argv) <= 1 or argv[1] != "--keep",
134+
)

docs/make.bat

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
@ECHO OFF
2+
3+
pushd %~dp0
4+
5+
REM Command file for Sphinx documentation
6+
7+
if "%SPHINXBUILD%" == "" (
8+
set SPHINXBUILD=sphinx-build
9+
)
10+
set SOURCEDIR=.
11+
set BUILDDIR=_build
12+
13+
%SPHINXBUILD% >NUL 2>NUL
14+
if errorlevel 9009 (
15+
echo.
16+
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17+
echo.installed, then set the SPHINXBUILD environment variable to point
18+
echo.to the full path of the 'sphinx-build' executable. Alternatively you
19+
echo.may add the Sphinx directory to PATH.
20+
echo.
21+
echo.If you don't have Sphinx installed, grab it from
22+
echo.https://www.sphinx-doc.org/
23+
exit /b 1
24+
)
25+
26+
if "%1" == "" goto help
27+
28+
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29+
goto end
30+
31+
:help
32+
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33+
34+
:end
35+
popd

0 commit comments

Comments
 (0)