1+ from __future__ import annotations
2+
13import re
24import 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
180183if __name__ == "__main__" :
0 commit comments