Skip to content

Commit 06b9c72

Browse files
committed
[SB05-065] lkql: add API doc generator & build API doc
Change-Id: I613d4ba5adbaa9b8a5f14c5880290118c40514ae
1 parent c02c634 commit 06b9c72

File tree

4 files changed

+175
-0
lines changed

4 files changed

+175
-0
lines changed

Diff for: user_manual/Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ help:
1414

1515
.PHONY: help Makefile
1616

17+
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
18+
1719
# Catch-all target: route all unknown targets to Sphinx using the new
1820
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
1921
%: Makefile
22+
LKQL_PATH=$(ROOT_DIR)/../lkql_checker/share/lkql python generate_apidoc.py --std -O generated stdlib
2023
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

Diff for: user_manual/apidoc_default.gpr

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
project Apidoc_Default is
2+
end Apidoc_Default;

Diff for: user_manual/generate_apidoc.py

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
"""
2+
APIdoc generation script for LKQL. It can be used to generate a sphinx file
3+
containing API documentation for given LKQL modules.
4+
5+
Limitations:
6+
7+
- For the moment it is only able to document functions and selectors.
8+
9+
- We don't have an LKQL sphinx domain, so this piggybacks on the python domain.
10+
This implies some limitations (like, for example, selectors are documented as
11+
functions for now).
12+
"""
13+
14+
import argparse
15+
import liblkqllang as L
16+
from os import path as P, makedirs
17+
import re
18+
19+
from contextlib import contextmanager
20+
21+
SCRIPT_PATH = P.dirname(P.realpath(__file__))
22+
23+
PROF_RE = re.compile(r"(?P<is_builtin>@builtin )?"
24+
r"(?P<kind>fun|selector)\s*(?P<profile>.*)")
25+
26+
27+
class App(object):
28+
29+
def __init__(self, *args, **kwargs):
30+
super().__init__(*args, **kwargs)
31+
self.eval_counter = 0
32+
self.output = []
33+
self._indent = 0
34+
self.ctx = L.AnalysisContext()
35+
self.parser = argparse.ArgumentParser()
36+
self.parser.add_argument(
37+
'modules', nargs='*', help="LKQL module names to document"
38+
)
39+
self.parser.add_argument(
40+
'--std', action="store_true",
41+
help='Generate apidoc for the prelude & builtin functions'
42+
)
43+
self.parser.add_argument(
44+
'-O', '--output-dir', type=str,
45+
default=".",
46+
help='Output directory for the generated rst files'
47+
)
48+
49+
def write(self, *chunks):
50+
"""
51+
Write chunks to the current file, taking current indentation into
52+
account.
53+
"""
54+
for chunk in chunks:
55+
if chunk == '':
56+
self.output.append('')
57+
continue
58+
59+
for line in chunk.splitlines():
60+
self.output.append((' ' * self ._indent) + line)
61+
62+
def get_output(self):
63+
"""
64+
Return current output formatted properly.
65+
"""
66+
return "\n".join(self.output)
67+
68+
@contextmanager
69+
def output_file(self, file_name):
70+
"""
71+
Context manager to set the output file. Writes the content of the
72+
current buffer to the file and flushes the buffer on exit.
73+
"""
74+
makedirs(self.args.output_dir, exist_ok=True)
75+
try:
76+
self.output_file_name = file_name
77+
yield
78+
finally:
79+
with open(
80+
P.join(self.args.output_dir, self.output_file_name), "w"
81+
) as f:
82+
f.write(self.get_output())
83+
self.output = []
84+
85+
@contextmanager
86+
def indent(self):
87+
"""
88+
Context manager to indent sphinx code emitted inside the with block.
89+
"""
90+
try:
91+
self._indent += 4
92+
yield
93+
finally:
94+
self._indent -= 4
95+
96+
def eval(self, cmd):
97+
"""
98+
Eval given LKQL command and returns the result as a string.
99+
"""
100+
cmd = self.ctx.get_from_buffer(f"<tmp_{self.eval_counter}", cmd)
101+
self.eval_counter += 1
102+
return cmd.root.p_interp_eval
103+
104+
def process_profile(self, prof):
105+
"""
106+
Given a string profile such as::
107+
108+
fun foo(a, b)
109+
110+
Returns a structured profile as a map::
111+
112+
{"is_builtin": bool, "kind": "fun"|"selector", profile: str}
113+
"""
114+
return PROF_RE.match(prof).groupdict()
115+
116+
def generate_module_doc(self, object_list, module_name=""):
117+
"""
118+
Generate documentation for the given object list and module name. If no
119+
module name is given, assume we're generating for the current
120+
namespace. This mode is used for the --std flag.
121+
"""
122+
if module_name:
123+
module_doc = eval(self.eval(f'doc({module_name})'))
124+
self.write(module_doc)
125+
self.write('')
126+
127+
for sym in object_list:
128+
name = sym if not module_name else f"{module_name}.{sym}"
129+
prof = eval(self.eval(f"profile({name})"))
130+
if not prof:
131+
return
132+
pprof = self.process_profile(prof)
133+
self.write(f'.. function:: {pprof["profile"]}')
134+
with self.indent():
135+
self.write('')
136+
doc = eval(self.eval(f"doc({name})"))
137+
self.write(doc)
138+
self.write('')
139+
140+
def main(self):
141+
self.args = self.parser.parse_args()
142+
dummy_unit = self.ctx.get_from_buffer('<dummy>', '12')
143+
dummy_unit.root.p_interp_init_from_project(
144+
P.join(SCRIPT_PATH, "apidoc_default.gpr")
145+
)
146+
147+
if self.args.std:
148+
local_symbols = eval(self.eval("get_symbols()"))
149+
with self.output_file('std.rst'):
150+
self.write('Standard library')
151+
self.write('----------------')
152+
self.write('')
153+
self.generate_module_doc(local_symbols)
154+
155+
for module_name in self.args.modules:
156+
self.eval(f"import {module_name}")
157+
module_symbols = eval(self.eval(
158+
f"get_symbols({module_name})")
159+
)
160+
with self.output_file(f'{module_name}.rst'):
161+
self.write(f'API doc for module {module_name}')
162+
self.write('--------------------------------')
163+
self.write('')
164+
self.generate_module_doc(module_symbols, module_name)
165+
print(self.get_output())
166+
167+
168+
if __name__ == '__main__':
169+
App().main()

Diff for: user_manual/source/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Welcome to LKQL's documentation!
1111
:caption: Contents:
1212

1313
language_reference
14+
lkql_api_doc
1415

1516

1617

0 commit comments

Comments
 (0)