feat: docs can be built with both sphinx & mkdocs · python-validators/validators@b2240e7

@@ -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

11121213

# 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

)

2534263527-

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

45554656

@@ -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)

5565566657-

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")

721257312674127

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+

)