Skip to content

Commit ecf6752

Browse files
committed
feat: docs can be built with both sphinx & mkdocs
- adds back sphinx configs - refactors `docs/gen_docs.py` to generate both sphinx and mkdocs based docs as needed - ignores dynamically generated doc files - updates development dependencies **Related items** *Issues* - Closes #258
1 parent 68227f2 commit ecf6752

File tree

8 files changed

+501
-89
lines changed

8 files changed

+501
-89
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: 82 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,70 @@ 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+
with open(source / "docs/index.rst", "wt") as idx_f:
84+
idx_f.write(
85+
convert_file(source_file=source / "docs/index.md", format="md", to="rst")
86+
+ "\n\n.. toctree::"
87+
+ "\n :hidden:"
88+
+ "\n :maxdepth: 2"
89+
+ "\n :caption: Reference:"
90+
+ "\n :glob:\n"
91+
+ "\n reference/*\n"
92+
)
93+
# generate RST reference documentation
94+
_generate_reference(source / "validators/__init__.py", refs_path, "rst")
95+
# build sphinx web pages as subprocess
96+
web_build = run(("sphinx-build", "docs", "docs/_build/web"), capture_output=True)
97+
print(web_build.stderr.decode(), "\n", web_build.stdout.decode(), sep="")
98+
# build sphinx man pages as subprocess
99+
man_build = run(("sphinx-build", "-b", "man", "docs", "docs/_build/man"), capture_output=True)
100+
print(man_build.stderr.decode(), "\n", man_build.stdout.decode(), sep="")
101+
102+
103+
def generate_documentation(
104+
source: Path, only_md: bool = False, only_rst: bool = False, discard_refs: bool = True
105+
):
106+
"""Generate documentation."""
107+
if only_md and only_rst:
108+
return
109+
# copy readme as docs index file
110+
copy(source / "README.md", source / "docs/index.md")
111+
# clean destination
112+
refs_path = source / "docs/reference"
113+
if refs_path.exists() and refs_path.is_dir():
114+
rmtree(refs_path)
115+
refs_path.mkdir(exist_ok=True)
116+
# documentation for each kind
117+
if not only_rst:
118+
_gen_md_docs(source, refs_path)
119+
if not only_md:
120+
_gen_rst_docs(source, refs_path)
69121
# optionally discard reference folder
70122
if discard_refs:
71123
rmtree(source / "docs/reference")
72124

73125

74126
if __name__ == "__main__":
75127
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")
128+
generate_documentation(
129+
project_root,
130+
only_md=True,
131+
only_rst=False,
132+
discard_refs=len(argv) <= 1 or argv[1] != "--keep",
133+
)

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)