Skip to content

Commit 3af7e52

Browse files
authored
Command line to generate api (#38)
* Command line to generate api * fix a couple of bugs
1 parent 15e3afa commit 3af7e52

File tree

4 files changed

+222
-2
lines changed

4 files changed

+222
-2
lines changed

_doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
}
107107

108108
epkg_dictionary = {
109+
"automodule": "https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-automodule",
109110
"black": "https://black.readthedocs.io/en/stable/index.html",
110111
"dot": "https://en.wikipedia.org/wiki/DOT_(graph_description_language)",
111112
"DOT": "https://en.wikipedia.org/wiki/DOT_(graph_description_language)",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import os
2+
import unittest
3+
from sphinx_runpython.ext_test_case import ExtTestCase
4+
from sphinx_runpython.tools.sphinx_api import sphinx_api
5+
6+
7+
class TestSphinxApi(ExtTestCase):
8+
9+
def test_this_doc_simulate(self):
10+
doc = os.path.join(os.path.dirname(__file__), "..", "..", "sphinx_runpython")
11+
res = sphinx_api(doc, simulate=True, verbose=1)
12+
self.assertEmpty(res)
13+
14+
def test_this_doc_write(self):
15+
doc = os.path.join(os.path.dirname(__file__), "..", "..", "sphinx_runpython")
16+
output = os.path.join(os.path.dirname(__file__), "temp_this_doc")
17+
res = sphinx_api(doc, simulate=False, verbose=1, output_folder=output)
18+
print(res)
19+
20+
21+
if __name__ == "__main__":
22+
unittest.main(verbosity=2)

sphinx_runpython/_cmd_helper.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import glob
22
import os
3-
from argparse import ArgumentParser
3+
from argparse import ArgumentParser, RawTextHelpFormatter
44
from tempfile import TemporaryDirectory
55

66

77
def get_parser():
88
parser = ArgumentParser(
99
prog="sphinx-runpython command line",
1010
description="A collection of quick tools.",
11+
formatter_class=RawTextHelpFormatter,
1112
epilog="",
1213
)
1314
parser.add_argument(
1415
"command",
15-
help="Command to run, only 'nb2py', 'readme', 'img2pdf' are available",
16+
help="Command to run, only 'nb2py', 'readme', 'img2pdf', 'api' are available\n"
17+
"- nb2py - converts notebooks into python\n"
18+
"- readme - checks readme syntax\n"
19+
"- img2pdf - converts impage to pdf\n"
20+
"- api - generates sphinx documentation api",
1621
)
1722
parser.add_argument(
1823
"-p", "--path", help="Folder or file which contains the files to process"
@@ -23,6 +28,11 @@ def get_parser():
2328
help="Recursive search.",
2429
action="store_true",
2530
)
31+
parser.add_argument(
32+
"--hidden",
33+
help="shows hidden submodules as well",
34+
action="store_true",
35+
)
2636
parser.add_argument(
2737
"-o",
2838
"--output",
@@ -59,11 +69,32 @@ def nb2py(infolder: str, recursive: bool = False, verbose: int = 0):
5969
convert_ipynb_to_gallery(name, outfile=out)
6070

6171

72+
def sphinx_api(
73+
infolder: str,
74+
output: str,
75+
recursive: bool = False,
76+
hidden: bool = False,
77+
verbose: int = 0,
78+
):
79+
from .tools.sphinx_api import sphinx_api as f
80+
81+
f(infolder, output, verbose=verbose, hidden=hidden)
82+
83+
6284
def process_args(args):
6385
cmd = args.command
6486
if cmd == "nb2py":
6587
nb2py(args.path, recursive=args.recursive, verbose=args.verbose)
6688
return
89+
if cmd == "api":
90+
sphinx_api(
91+
args.path,
92+
recursive=args.recursive,
93+
verbose=args.verbose,
94+
output=args.output,
95+
hidden=args.hidden,
96+
)
97+
return
6798
if cmd == "img2pdf":
6899
from .tools.img_export import images2pdf
69100

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import os
2+
import textwrap
3+
from typing import Dict, List, Optional
4+
5+
6+
def _write_doc_folder(
7+
folder: str,
8+
pyfiles: List[str],
9+
hidden: bool = False,
10+
prefix: str = "",
11+
subfolders: Optional[List[str]] = None,
12+
) -> Dict[str, str]:
13+
"""
14+
Creates all the file in a dictionary.
15+
"""
16+
template = textwrap.dedent(
17+
"""
18+
<full_module_name>
19+
<line>
20+
21+
.. automodule:: <full_module_name>
22+
:members:
23+
:no-undoc-members:
24+
"""
25+
)
26+
27+
index = textwrap.dedent(
28+
"""
29+
<full_module_name>
30+
<line>
31+
"""
32+
)
33+
34+
submodule = ".".join(os.path.splitext(folder)[0].replace("\\", "/").split("/"))
35+
fullsubmodule = f"{prefix}.{submodule}" if prefix else submodule
36+
rows = [
37+
index.replace("<submodule>", submodule)
38+
.replace("<full_module_name>", fullsubmodule)
39+
.replace("<line>", "=" * len(fullsubmodule)),
40+
]
41+
if subfolders:
42+
rows.append(
43+
textwrap.dedent(
44+
"""
45+
.. toctree::
46+
:maxdepth: 1
47+
:caption: submodules
48+
49+
"""
50+
)
51+
)
52+
for sub in subfolders:
53+
rows.append(f" {sub}/index")
54+
res = {}
55+
has_module = False
56+
for name in sorted(pyfiles):
57+
if not name:
58+
continue
59+
module_name = ".".join(os.path.splitext(name)[0].replace("\\", "/").split("/"))
60+
last = module_name.split(".")[-1]
61+
if not hidden and last[0] == "_" and last != "__init__":
62+
continue
63+
if not module_name or module_name in ("__main__", "__init__"):
64+
continue
65+
key = f"{module_name}.rst"
66+
if module_name.endswith("__init__"):
67+
module_name = ".".join(module_name.split(".")[:-1])
68+
full_module_name = f"{submodule}.{module_name}"
69+
line = "=" * len(full_module_name)
70+
text = (
71+
template.replace("<module_name>", module_name)
72+
.replace("<full_module_name>", full_module_name)
73+
.replace("<line>", line)
74+
)
75+
res[key] = text
76+
if not has_module:
77+
has_module = True
78+
rows.append(
79+
textwrap.dedent(
80+
"""
81+
82+
.. toctree::
83+
:maxdepth: 1
84+
:caption: modules
85+
86+
"""
87+
)
88+
)
89+
rows.append(f" {last}")
90+
91+
rows.append(
92+
textwrap.dedent(
93+
f"""
94+
95+
.. automodule:: {submodule}
96+
:members:
97+
:no-undoc-members:
98+
"""
99+
)
100+
)
101+
res["index.rst"] = "\n".join(rows)
102+
return res
103+
104+
105+
def sphinx_api(
106+
folder: str,
107+
output_folder: Optional[str] = None,
108+
simulate: bool = False,
109+
hidden: bool = False,
110+
verbose: int = 0,
111+
):
112+
"""
113+
Creates simple pages to document a package.
114+
Relies on :epkg:`automodule`.
115+
116+
:param folder: folder to document
117+
:param output_folder: where to write the result
118+
:param simulate: prints out what the function will do
119+
:param hidden: document file starting with `_`
120+
:param verbose: verbosity
121+
:return: list of written file
122+
"""
123+
folder = folder.rstrip("/\\")
124+
root, package_name = os.path.split(folder)
125+
files = []
126+
if verbose:
127+
print(f"[sphinx_api] start creating API for {folder!r}")
128+
129+
for racine, dossiers, fichiers in os.walk(folder):
130+
pyfiles = [f for f in fichiers if f.endswith(".py")]
131+
if not pyfiles:
132+
continue
133+
mname = racine[len(root) + 1 :] if root else racine
134+
selected = [
135+
d
136+
for d in dossiers
137+
if os.path.exists(os.path.join(racine, d, "__init__.py"))
138+
]
139+
if verbose:
140+
print(f"[sphinx_api] open {mname!r}")
141+
if selected:
142+
print(f"[sphinx_api] submodules {selected!r}")
143+
content = _write_doc_folder(
144+
mname, pyfiles, hidden=hidden, prefix="", subfolders=selected
145+
)
146+
if verbose:
147+
print(f"[sphinx_api] close {mname!r}")
148+
if simulate:
149+
print(f"--+ {mname}")
150+
for k, v in content.items():
151+
print(f" | {k}")
152+
else:
153+
assert output_folder, "output_folder is empty"
154+
subfolder = os.path.join(output_folder, *mname.split("/")[1:])
155+
if verbose:
156+
print(f"[sphinx_api] create {subfolder!r}")
157+
if not os.path.exists(subfolder):
158+
os.makedirs(subfolder)
159+
for k, v in content.items():
160+
n = os.path.join(subfolder, k)
161+
if verbose:
162+
print(f"[sphinx_api] write {n!r}")
163+
with open(n, "w") as f:
164+
f.write(v)
165+
files.append(n)
166+
return files

0 commit comments

Comments
 (0)