Skip to content

Commit acba0a3

Browse files
committed
Simplify part of, type and cleanup py2d
1 parent 303acfd commit acba0a3

1 file changed

Lines changed: 74 additions & 71 deletions

File tree

AutoDuck/py2d.py

Lines changed: 74 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,69 @@
1+
from __future__ import annotations
2+
13
import re
24
import sys
3-
import types
5+
from collections.abc import Iterable
6+
from functools import partial
7+
from types import FunctionType, MethodType
8+
from typing import TYPE_CHECKING, Generator, Generic, TypeVar
9+
10+
if TYPE_CHECKING:
11+
from _typeshed import SupportsWrite
12+
13+
_T = TypeVar("_T")
414

515

6-
def ad_escape(s):
16+
def ad_escape(s: str) -> str:
717
return re.sub(r"([^<]*)<([^>]*)>", r"\g<1>\\<\g<2>\\>", s)
818

919

10-
Print = __builtins__.__dict__["print"]
20+
Print = print
1121

1222

13-
class DocInfo:
14-
def __init__(self, name, ob):
23+
class DocInfo(Generic[_T]):
24+
def __init__(self, name: str, ob: _T) -> None:
25+
docstring = (ob.__doc__ or "").strip()
26+
27+
self.desc = docstring
28+
self.short_desc = docstring and docstring.splitlines()[0]
1529
self.name = name
1630
self.ob = ob
17-
self.short_desc = ""
18-
self.desc = ""
1931

2032

21-
def BuildArgInfos(ob):
22-
ret = []
23-
vars = list(ob.__code__.co_varnames[: ob.__code__.co_argcount])
24-
vars.reverse() # for easier default checking.
33+
class ArgInfo(DocInfo[FunctionType]):
34+
def __init__(self, name: str, ob: FunctionType, default: str) -> None:
35+
super().__init__(name, ob)
36+
self.desc = name
37+
self.short_desc = name
38+
self.default = default
39+
40+
41+
def BuildArgInfos(ob: FunctionType) -> list[ArgInfo]:
42+
ret: list[ArgInfo] = []
43+
# Reversed for easier default checking.
44+
# Since arguments w/ default can only be at the end of a function.
45+
vars = reversed(ob.__code__.co_varnames[: ob.__code__.co_argcount])
2546
defs = list(ob.__defaults__ or [])
26-
for i, n in enumerate(vars):
27-
info = DocInfo(n, ob)
28-
info.short_desc = info.desc = n
29-
info.default = ""
47+
for n in vars:
48+
default = ""
3049
if len(defs):
3150
default = repr(defs.pop())
32-
# the default may be an object, so the repr gives '<...>' - and
33-
# the angle brackets screw autoduck.
34-
info.default = default.replace("<", "").replace(">", "")
35-
ret.append(info)
51+
# the default may be an object, so the repr gives '<...>'
52+
# and the angle brackets screw AutoDuck.
53+
default = default.replace("<", "").replace(">", "")
54+
ret.append(ArgInfo(n, ob, default))
3655
ret.reverse()
3756
return ret
3857

3958

40-
def BuildInfo(name, ob):
41-
ret = DocInfo(name, ob)
42-
docstring = ob.__doc__ or ""
43-
ret.desc = ret.short_desc = docstring.strip()
44-
if ret.desc:
45-
ret.short_desc = ret.desc.splitlines()[0]
46-
return ret
47-
59+
def should_build_function(build_info: DocInfo[FunctionType]) -> bool:
60+
return bool(build_info.ob.__doc__) and not build_info.ob.__name__.startswith("_")
4861

49-
def should_build_function(build_info):
50-
return build_info.ob.__doc__ and not build_info.ob.__name__.startswith("_")
5162

52-
53-
# docstring aware paragraph generator. Isn't there something in docutils
54-
# we can use?
55-
def gen_paras(val):
56-
chunks = []
63+
# docstring aware paragraph generator.
64+
# Isn't there something in docutils we can use?
65+
def gen_paras(val: str) -> Generator[list[str]]:
66+
chunks: list[str] = []
5767
in_docstring = False
5868
for line in val.splitlines():
5969
line = ad_escape(line.strip())
@@ -69,13 +79,13 @@ def gen_paras(val):
6979
yield chunks or [""]
7080

7181

72-
def format_desc(desc):
82+
def format_desc(desc: str) -> str:
7383
# A little complicated! Given the docstring for a module, we want to:
7484
# write:
7585
# 'first_para_of_docstring'
7686
# '@comm next para of docstring'
7787
# '@comm next para of docstring' ... etc
78-
# BUT - also handling enbedded doctests, where we write
88+
# BUT - also handling embedded doctests, where we write
7989
# '@iex >>> etc.'
8090
if not desc:
8191
return ""
@@ -94,87 +104,80 @@ def format_desc(desc):
94104
return "\n".join(chunks)
95105

96106

97-
def build_module(fp, mod_name):
107+
def build_module(mod_name: str) -> None:
98108
__import__(mod_name)
99109
mod = sys.modules[mod_name]
100-
functions = []
101-
classes = []
102-
constants = []
110+
functions: list[DocInfo[FunctionType]] = []
111+
classes: list[DocInfo[type]] = []
112+
constants: list[tuple[str, int | str]] = []
103113
for name, ob in list(mod.__dict__.items()):
104114
if name.startswith("_"):
105115
continue
106116
if hasattr(ob, "__module__") and ob.__module__ != mod_name:
107117
continue
108118
if type(ob) == type:
109-
classes.append(BuildInfo(name, ob))
110-
elif isinstance(ob, types.FunctionType):
111-
functions.append(BuildInfo(name, ob))
119+
classes.append(DocInfo(name, ob))
120+
elif isinstance(ob, FunctionType):
121+
functions.append(DocInfo(name, ob))
112122
elif name.upper() == name and isinstance(ob, (int, str)):
113123
constants.append((name, ob))
114-
info = BuildInfo(mod_name, mod)
115-
Print(f"// @module {mod_name}|{format_desc(info.desc)}", file=fp)
124+
info = DocInfo(mod_name, mod)
125+
Print(f"// @module {mod_name}|{format_desc(info.desc)}")
116126
functions = [f for f in functions if should_build_function(f)]
117127
for ob in functions:
118-
Print(f"// @pymeth {ob.name}|{ob.short_desc}", file=fp)
128+
Print(f"// @pymeth {ob.name}|{ob.short_desc}")
119129
for ob in classes:
120130
# only classes with docstrings get printed.
121131
if not ob.ob.__doc__:
122132
continue
123133
ob_name = mod_name + "." + ob.name
124-
Print(f"// @pyclass {ob.name}|{ob.short_desc}", file=fp)
134+
Print(f"// @pyclass {ob.name}|{ob.short_desc}")
125135
for ob in functions:
126136
Print(
127137
f"// @pymethod |{mod_name}|{ob.name}|{format_desc(ob.desc)}",
128-
file=fp,
129138
)
130139
for ai in BuildArgInfos(ob.ob):
131-
Print(f"// @pyparm |{ai.name}|{ai.default}|{ai.short_desc}", file=fp)
140+
Print(f"// @pyparm |{ai.name}|{ai.default}|{ai.short_desc}")
132141

133142
for ob in classes:
134143
# only classes with docstrings get printed.
135144
if not ob.ob.__doc__:
136145
continue
137146
ob_name = mod_name + "." + ob.name
138-
Print(f"// @object {ob_name}|{format_desc(ob.desc)}", file=fp)
139-
func_infos = []
147+
Print(f"// @object {ob_name}|{format_desc(ob.desc)}")
148+
func_infos: list[DocInfo[FunctionType | MethodType]] = []
140149
# We need to iter the keys then to a getattr() so the funky descriptor
141150
# things work.
142151
for n in list(ob.ob.__dict__.keys()):
143152
o = getattr(ob.ob, n)
144-
if isinstance(o, (types.FunctionType, types.MethodType)):
145-
info = BuildInfo(n, o)
153+
if isinstance(o, (FunctionType, MethodType)):
154+
info = DocInfo(n, o)
146155
if should_build_function(info):
147156
func_infos.append(info)
148157
for fi in func_infos:
149-
Print(f"// @pymeth {fi.name}|{fi.short_desc}", file=fp)
158+
Print(f"// @pymeth {fi.name}|{fi.short_desc}")
150159
for fi in func_infos:
151-
Print(
152-
f"// @pymethod |{ob_name}|{fi.name}|{format_desc(fi.desc)}",
153-
file=fp,
154-
)
160+
Print(f"// @pymethod |{ob_name}|{fi.name}|{format_desc(fi.desc)}")
155161
if hasattr(fi.ob, "im_self") and fi.ob.im_self is ob.ob:
156-
Print("// @comm This is a @classmethod.", file=fp)
157-
Print(
158-
f"// @pymethod |{ob_name}|{fi.name}|{format_desc(fi.desc)}",
159-
file=fp,
160-
)
162+
Print("// @comm This is a @classmethod.")
163+
Print(f"// @pymethod |{ob_name}|{fi.name}|{format_desc(fi.desc)}")
161164
for ai in BuildArgInfos(fi.ob):
162-
Print(
163-
f"// @pyparm |{ai.name}|{ai.default}|{ai.short_desc}",
164-
file=fp,
165-
)
165+
Print(f"// @pyparm |{ai.name}|{ai.default}|{ai.short_desc}")
166166

167167
for name, val in constants:
168168
desc = f"{name} = {val!r}"
169169
if isinstance(val, int):
170170
desc += f" (0x{val:x})"
171-
Print(f"// @const {mod_name}|{name}|{desc}", file=fp)
171+
Print(f"// @const {mod_name}|{name}|{desc}")
172+
172173

174+
def main(fp: SupportsWrite[str], args: Iterable[str]) -> None:
175+
global Print
176+
Print = partial(print, file=fp)
173177

174-
def main(fp, args):
175-
Print("// @doc", file=fp)
178+
Print("// @doc")
176179
for arg in args:
177-
build_module(sys.stdout, arg)
180+
build_module(arg)
178181

179182

180183
if __name__ == "__main__":

0 commit comments

Comments
 (0)