aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJongwook Choi <wookayin@gmail.com>2023-12-28 14:11:13 -0500
committerJongwook Choi <wookayin@gmail.com>2024-01-02 11:15:36 -0500
commit1a31d4cf2b88e9f677d7a2f23b10e4dacf803a9d (patch)
treed2e747135163693d343d9658e581d989829112cc
parent5e2d4b3c4dbd56342cabb6993d354f690e0a1575 (diff)
downloadrneovim-1a31d4cf2b88e9f677d7a2f23b10e4dacf803a9d.tar.gz
rneovim-1a31d4cf2b88e9f677d7a2f23b10e4dacf803a9d.tar.bz2
rneovim-1a31d4cf2b88e9f677d7a2f23b10e4dacf803a9d.zip
refactor(gen_vimdoc): use typing for function API vimdoc generation
-rwxr-xr-xscripts/gen_vimdoc.py143
1 files changed, 102 insertions, 41 deletions
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index f22d11ed6b..40ece01ba3 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -112,6 +112,11 @@ lua2dox = os.path.join(base_dir, 'scripts', 'lua2dox.lua')
SectionName = str
+Docstring = str # Represents (formatted) vimdoc string
+
+FunctionName = str
+
+
@dataclasses.dataclass
class Config:
"""Config for documentation."""
@@ -881,6 +886,44 @@ def is_program_listing(para):
return len(children) == 1 and children[0].nodeName == 'programlisting'
+FunctionParam = Tuple[
+ str, # type
+ str, # parameter name
+]
+
+@dataclasses.dataclass
+class FunctionDoc:
+ """Data structure for function documentation. Also exported as msgpack."""
+
+ annotations: List[str]
+ """Attributes, e.g., FUNC_API_REMOTE_ONLY. See annotation_map"""
+
+ signature: str
+ """Function signature with *tags*."""
+
+ parameters: List[FunctionParam]
+ """Parameters: (type, name)"""
+
+ parameters_doc: Dict[str, Docstring]
+ """Parameters documentation. Key is parameter name, value is doc."""
+
+ doc: List[Docstring]
+ """Main description for the function. Separated by paragraph."""
+
+ return_: List[Docstring]
+ """Return:, or Return (multiple): (@return strings)"""
+
+ seealso: List[Docstring]
+ """See also: (@see strings)"""
+
+ # for fmt_node_as_vimhelp
+ desc_node: Element | None = None
+ brief_desc_node: Element | None = None
+
+ # for INCLUDE_C_DECL
+ c_decl: str | None = None
+
+
def fmt_node_as_vimhelp(parent: Element, width=text_width - indentation, indent='',
fmt_vimhelp=False):
"""Renders (nested) Doxygen <para> nodes as Vim :help text.
@@ -946,7 +989,10 @@ def fmt_node_as_vimhelp(parent: Element, width=text_width - indentation, indent=
return clean_lines('\n'.join(rendered_blocks).strip())
-def extract_from_xml(filename, target, width, fmt_vimhelp):
+def extract_from_xml(filename, target, width, fmt_vimhelp) -> Tuple[
+ Dict[FunctionName, FunctionDoc],
+ Dict[FunctionName, FunctionDoc],
+]:
"""Extracts Doxygen info as maps without formatting the text.
Returns two maps:
@@ -958,8 +1004,8 @@ def extract_from_xml(filename, target, width, fmt_vimhelp):
"""
config: Config = CONFIG[target]
- fns = {} # Map of func_name:docstring.
- deprecated_fns = {} # Map of func_name:docstring.
+ fns: Dict[FunctionName, FunctionDoc] = {}
+ deprecated_fns: Dict[FunctionName, FunctionDoc] = {}
dom = minidom.parse(filename)
compoundname = get_text(dom.getElementsByTagName('compoundname')[0])
@@ -1084,7 +1130,7 @@ def extract_from_xml(filename, target, width, fmt_vimhelp):
# Tracks `xrefsect` titles. As of this writing, used only for separating
# deprecated functions.
xrefs_all = set()
- paras = []
+ paras: List[Dict[str, Any]] = []
brief_desc = find_first(member, 'briefdescription')
if brief_desc:
for child in brief_desc.childNodes:
@@ -1103,47 +1149,48 @@ def extract_from_xml(filename, target, width, fmt_vimhelp):
desc.toprettyxml(indent=' ', newl='\n')),
' ' * indentation))
- fn = {
- 'annotations': list(annotations),
- 'signature': signature,
- 'parameters': params,
- 'parameters_doc': collections.OrderedDict(),
- 'doc': [],
- 'return': [],
- 'seealso': [],
- }
+ fn = FunctionDoc(
+ annotations=list(annotations),
+ signature=signature,
+ parameters=params,
+ parameters_doc=collections.OrderedDict(),
+ doc=[],
+ return_=[],
+ seealso=[],
+ )
if fmt_vimhelp:
- fn['desc_node'] = desc
- fn['brief_desc_node'] = brief_desc
+ fn.desc_node = desc
+ fn.brief_desc_node = brief_desc
for m in paras:
- if 'text' in m:
- if not m['text'] == '':
- fn['doc'].append(m['text'])
+ if m.get('text', ''):
+ fn.doc.append(m['text'])
if 'params' in m:
# Merge OrderedDicts.
- fn['parameters_doc'].update(m['params'])
+ fn.parameters_doc.update(m['params'])
if 'return' in m and len(m['return']) > 0:
- fn['return'] += m['return']
+ fn.return_ += m['return']
if 'seealso' in m and len(m['seealso']) > 0:
- fn['seealso'] += m['seealso']
+ fn.seealso += m['seealso']
if INCLUDE_C_DECL:
- fn['c_decl'] = c_decl
+ fn.c_decl = c_decl
if 'Deprecated' in str(xrefs_all):
deprecated_fns[name] = fn
elif name.startswith(config.fn_name_prefix):
fns[name] = fn
+ # sort functions by name (lexicographically)
fns = collections.OrderedDict(sorted(
fns.items(),
- key=lambda key_item_tuple: key_item_tuple[0].lower()))
+ key=lambda key_item_tuple: key_item_tuple[0].lower(),
+ ))
deprecated_fns = collections.OrderedDict(sorted(deprecated_fns.items()))
return fns, deprecated_fns
-def fmt_doxygen_xml_as_vimhelp(filename, target):
+def fmt_doxygen_xml_as_vimhelp(filename, target) -> Tuple[Docstring, Docstring]:
"""Entrypoint for generating Vim :help from from Doxygen XML.
Returns 2 items:
@@ -1154,20 +1201,26 @@ def fmt_doxygen_xml_as_vimhelp(filename, target):
fns_txt = {} # Map of func_name:vim-help-text.
deprecated_fns_txt = {} # Map of func_name:vim-help-text.
+
+ fns: Dict[FunctionName, FunctionDoc]
fns, _ = extract_from_xml(filename, target, text_width, True)
- for name, fn in fns.items():
+ for fn_name, fn in fns.items():
# Generate Vim :help for parameters.
- if fn['desc_node']:
- doc = fmt_node_as_vimhelp(fn['desc_node'], fmt_vimhelp=True)
- if not doc and fn['brief_desc_node']:
- doc = fmt_node_as_vimhelp(fn['brief_desc_node'])
- if not doc and name.startswith("nvim__"):
+
+ # Generate body.
+ doc = ''
+ if fn.desc_node:
+ doc = fmt_node_as_vimhelp(fn.desc_node, fmt_vimhelp=True)
+ if not doc and fn.brief_desc_node:
+ doc = fmt_node_as_vimhelp(fn.brief_desc_node)
+ if not doc and fn_name.startswith("nvim__"):
continue
if not doc:
doc = 'TODO: Documentation'
- annotations = '\n'.join(fn['annotations'])
+ # Annotations: put before Parameters
+ annotations: str = '\n'.join(fn.annotations)
if annotations:
annotations = ('\n\nAttributes: ~\n' +
textwrap.indent(annotations, ' '))
@@ -1177,18 +1230,22 @@ def fmt_doxygen_xml_as_vimhelp(filename, target):
else:
doc = doc[:i] + annotations + '\n\n' + doc[i:]
+ # C Declaration: (debug only)
if INCLUDE_C_DECL:
doc += '\n\nC Declaration: ~\n>\n'
- doc += fn['c_decl']
+ assert fn.c_decl is not None
+ doc += fn.c_decl
doc += '\n<'
- func_doc = fn['signature'] + '\n'
+ # Start of function documentations. e.g.,
+ # nvim_cmd({*cmd}, {*opts}) *nvim_cmd()*
+ func_doc = fn.signature + '\n'
func_doc += textwrap.indent(clean_lines(doc), ' ' * indentation)
# Verbatim handling.
func_doc = re.sub(r'^\s+([<>])$', r'\1', func_doc, flags=re.M)
- split_lines = func_doc.split('\n')
+ split_lines: List[str] = func_doc.split('\n')
start = 0
while True:
try:
@@ -1214,12 +1271,14 @@ def fmt_doxygen_xml_as_vimhelp(filename, target):
func_doc = "\n".join(map(align_tags, split_lines))
- if (name.startswith(config.fn_name_prefix)
- and name != "nvim_error_event"):
- fns_txt[name] = func_doc
+ if (fn_name.startswith(config.fn_name_prefix)
+ and fn_name != "nvim_error_event"):
+ fns_txt[fn_name] = func_doc
- return ('\n\n'.join(list(fns_txt.values())),
- '\n\n'.join(list(deprecated_fns_txt.values())))
+ return (
+ '\n\n'.join(list(fns_txt.values())),
+ '\n\n'.join(list(deprecated_fns_txt.values())),
+ )
def delete_lines_below(filename, tokenstr):
@@ -1402,9 +1461,11 @@ def main(doxygen_config, args):
with open(doc_file, 'ab') as fp:
fp.write(docs.encode('utf8'))
- fn_map_full = collections.OrderedDict(sorted(fn_map_full.items()))
+ fn_map_full = collections.OrderedDict(sorted(
+ (name, fn_doc.__dict__) for (name, fn_doc) in fn_map_full.items()
+ ))
with open(mpack_file, 'wb') as fp:
- fp.write(msgpack.packb(fn_map_full, use_bin_type=True))
+ fp.write(msgpack.packb(fn_map_full, use_bin_type=True)) # type: ignore
if not args.keep_tmpfiles:
shutil.rmtree(output_dir)