Skip to content

Commit

Permalink
imprv: Generator: Add docstring to functions
Browse files Browse the repository at this point in the history
  • Loading branch information
matperc committed Feb 11, 2024
1 parent 9ab752a commit fcfc78d
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 33 deletions.
87 changes: 73 additions & 14 deletions tools/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,14 @@ def _callable_get_arguments(
current_namespace: str,
needed_namespaces: set[str],
can_default: bool = False,
) -> Tuple[list[str], list[str], list[str]]:
) -> Tuple[list[str], list[str], list[str], list[str]]:
function_args = type.get_arguments()
accept_optional_args = False
optional_args_name = ""
dict_names: dict[int, str] = {}
dict_args: dict[int, GI.ArgInfo] = {}
str_args: list[str] = []
dict_return_names: dict[int, str] = {}
dict_return_args: dict[int, str] = {}
skip: list[int] = []

Expand All @@ -132,6 +133,7 @@ def _callable_get_arguments(
if len_arg < i:
dict_names.pop(len_arg, None)
dict_args.pop(len_arg, None)
dict_return_names.pop(len_arg, None)
dict_return_args.pop(len_arg, None)

# Need to check because user_data can be the first arg
Expand All @@ -142,6 +144,7 @@ def _callable_get_arguments(
arg.get_type(), current_namespace, needed_namespaces, True
)

dict_return_names[i] = arg.get_name()
dict_return_args[i] = t
elif direction == GI.Direction.IN or direction == GI.Direction.INOUT:
dict_names[i] = arg.get_name()
Expand Down Expand Up @@ -184,7 +187,7 @@ def _callable_get_arguments(
if return_type != "None" or len(return_args) == 0:
return_args.insert(0, return_type)

return (names, str_args, return_args)
return (names, str_args, return_args, list(dict_return_names.values()))


class TypeInfo:
Expand Down Expand Up @@ -295,7 +298,7 @@ def _type_to_python(
if tag == tags.INTERFACE:
interface = type.get_interface()
if isinstance(interface, GI.CallbackInfo):
(names, args, return_args) = _callable_get_arguments(
(names, args, return_args, _) = _callable_get_arguments(
interface, current_namespace, needed_namespaces
)

Expand Down Expand Up @@ -375,8 +378,53 @@ def _generate_full_name(prefix: str, name: str) -> str:
return full_name


def _normalize_string(s: str) -> str:
return re.sub(" +", " ", s)


def _build_function_info_doc(
fullname: str, args: list[str], return_args: list[str], return_names: list[str]
) -> str | None:
if not fullname in DOCS:
return None

args = [a.replace("*", "") for a in args]

doc = DOCS[fullname]
arg_doc: dict[str, str | None] = {}
for arg in args + return_names:
arg_doc[arg] = _normalize_string(DOCS.get(f"{fullname}.{arg}", ""))

return_doc = _normalize_string(DOCS.get(f"{fullname}.$return-value", ""))

# There are two conditions:
# 1: A return value and some / none out arguments,
# in this case the length will be different and we should ignore the first return_args
# 2: No return value, in this case the length will be equal
add_one = len(return_args) != len(return_names)
return_doc = (
f"{return_args[0]}: {return_doc or 'Not documented'}\n "
if add_one and not (len(return_args) == 1 and return_args[0] == "None")
else None
)
ret_args: dict[str, str] = {}
for i, arg in enumerate(return_names):
index = i + 1 if add_one else i
ret_args[arg] = return_args[index]

return f"""{doc}
Parameters:
{'\n '.join([f'{s}: {arg_doc[s]}' for s in args])}
Returns:
{return_doc or ""}{'\n '.join([f'{ret_args[s]}: {arg_doc[s]}' for s in return_names])}
"""


def _build_function_info(
current_namespace: str,
fullname: str,
name: str,
function: GI.FunctionInfo | GI.VFuncInfo,
in_class: Optional[Any],
Expand All @@ -400,7 +448,7 @@ def _build_function_info(
static = True

# Arguments
(names, args, return_args) = _callable_get_arguments(
(names, args, return_args, return_args_names) = _callable_get_arguments(
function, current_namespace, needed_namespaces, True
)
args_types = [f"{name}: {args[i]}" for (i, name) in enumerate(names)]
Expand All @@ -414,6 +462,8 @@ def _build_function_info(
return_type = f"{return_args[0]}"

# Generate string
doc = _build_function_info_doc(fullname, names, return_args, return_args_names)
doc = f' """\n{doc} """\n ...\n' if doc else ""
prepend = ""
if constructor:
args_types.insert(0, "cls")
Expand All @@ -428,13 +478,14 @@ def _build_function_info(
prepend = "@staticmethod\n"

if comment:
return f"{prepend}def {name}({', '.join(str(a) for a in args_types)}) -> {return_type}: ... # {comment}\n"
return f"{prepend}def {name}({', '.join(str(a) for a in args_types)}) -> {return_type}: {'...' if not doc else ''} # {comment}\n{doc}\n"
else:
return f"{prepend}def {name}({', '.join(str(a) for a in args_types)}) -> {return_type}: ...\n"
return f"{prepend}def {name}({', '.join(str(a) for a in args_types)}) -> {return_type}: {'...' if not doc else ''} \n{doc}\n"


def _wrapped_strip_boolean_result(
current_namespace: str,
fullname: str,
name: str,
function: Any,
in_class: Optional[Any],
Expand All @@ -443,7 +494,7 @@ def _wrapped_strip_boolean_result(
real_function = function.__wrapped__
fail_ret = inspect.getclosurevars(function).nonlocals.get("fail_ret")

(_, _, return_args) = _callable_get_arguments(
(_, _, return_args, _) = _callable_get_arguments(
real_function, current_namespace, needed_namespaces
)
return_args = return_args[1:] # Strip first return value
Expand All @@ -466,6 +517,7 @@ def _wrapped_strip_boolean_result(

return _build_function_info(
current_namespace,
fullname,
name,
real_function,
in_class,
Expand All @@ -477,6 +529,7 @@ def _wrapped_strip_boolean_result(

def _build_function(
current_namespace: str,
fullname: str,
name: str,
function: Any,
in_class: Optional[Any],
Expand All @@ -488,12 +541,12 @@ def _build_function(
if hasattr(function, "__wrapped__"):
if "strip_boolean_result" in str(function):
return _wrapped_strip_boolean_result(
current_namespace, name, function, in_class, needed_namespaces
current_namespace, fullname, name, function, in_class, needed_namespaces
)

if isinstance(function, GI.FunctionInfo) or isinstance(function, GI.VFuncInfo):
return _build_function_info(
current_namespace, name, function, in_class, needed_namespaces
current_namespace, fullname, name, function, in_class, needed_namespaces
)

signature: Optional[str] = None
Expand Down Expand Up @@ -611,10 +664,11 @@ def _gi_build_stub(

# Functions
for name in sorted(functions):
full_name = _generate_full_name(prefix_name, name)

if hasattr(functions[name], "is_deprecated"):
deprecated = functions[name].is_deprecated()
if deprecated:
full_name = _generate_full_name(prefix_name, name)
message = (
# Currently not implemented:
# https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/80
Expand All @@ -634,7 +688,12 @@ def _gi_build_stub(
continue

ret += _build_function(
current_namespace, name, functions[name], in_class, needed_namespaces
current_namespace,
full_name,
name,
functions[name],
in_class,
needed_namespaces,
)

if ret and functions:
Expand Down Expand Up @@ -877,7 +936,7 @@ def _gi_build_stub(
o = getattr(obj, key)
if isinstance(o, GI.FunctionInfo):
function_ret = _build_function(
current_namespace, key, o, obj, needed_namespaces
current_namespace, full_name, key, o, obj, needed_namespaces
)
for line in function_ret.splitlines():
ret += " " + line + "\n"
Expand Down Expand Up @@ -920,7 +979,7 @@ def _gi_build_stub(
o = getattr(obj, key)
if isinstance(o, GI.FunctionInfo):
function_ret = _build_function(
current_namespace, key, o, obj, needed_namespaces
current_namespace, full_name, key, o, obj, needed_namespaces
)
for line in function_ret.splitlines():
ret += " " + line + "\n"
Expand Down Expand Up @@ -983,7 +1042,7 @@ def start(module: str, version: str, overrides: dict[str, str]) -> str:

args = parser.parse_args()

DEPRECATION_DOCS = gir.load_gir(args.module, args.version)
DOCS, DEPRECATION_DOCS = gir.load_gir(args.module, args.version)

if args.output:
overrides: dict[str, str] = {}
Expand Down
53 changes: 34 additions & 19 deletions tools/gir.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
import gi
from gi.repository import GLib

NS = {
"core": "http://www.gtk.org/introspection/core/1.0",
"c": "http://www.gtk.org/introspection/c/1.0",
"glib": "http://www.gtk.org/introspection/glib/1.0",
}


def _get_gir_path(girname: str) -> str:
searchdirs: list[str] = []
Expand Down Expand Up @@ -35,28 +41,37 @@ def _get_gir_path(girname: str) -> str:
sys.exit(1)


def load_gir(module: str, version: str) -> dict[str, str]:
deprecation_docs: dict[str, str] = {}
ns = {
"core": "http://www.gtk.org/introspection/core/1.0",
"c": "http://www.gtk.org/introspection/c/1.0",
"glib": "http://www.gtk.org/introspection/glib/1.0",
}
gir_tree = ET.parse(_get_gir_path(f"{module}-{version}.gir"))
gir_root = gir_tree.getroot()
gir_parent_map = {c: p for p in gir_tree.iter() for c in p}
def _docs(
root: ET.Element,
parent_map: dict[ET.Element, ET.Element],
tag: str,
remove_newlines=False,
) -> dict[str, str]:
docs: dict[str, str] = {}

for child in gir_root.iterfind(".//core:doc-deprecated", ns):
for child in root.iterfind(f".//core:{tag}", NS):
parents: list[str] = []
parent = gir_parent_map[child]
while True:
parent = parent_map[child]
while parent:
try:
parents.insert(0, parent.attrib["name"])
except KeyError:
break
parent = gir_parent_map[parent]
deprecation_docs[".".join(parents[1:])] = (
cast(str, child.text).replace("\n", "").replace('"', '\\"')
)
if "return-value" in parent.tag:
parents.insert(0, "$return-value")
pass
parent = parent_map.get(parent, None)
text = cast(str, child.text).replace('"', '\\"')
if remove_newlines:
text = text.replace("\n", "")
docs[".".join(parents[1:])] = text
return docs


def load_gir(module: str, version: str) -> tuple[dict[str, str], dict[str, str]]:
gir_tree = ET.parse(_get_gir_path(f"{module}-{version}.gir"))
gir_root = gir_tree.getroot()
gir_parent_map = {c: p for p in gir_tree.iter() for c in p}

return deprecation_docs
return _docs(gir_root, gir_parent_map, "doc"), _docs(
gir_root, gir_parent_map, "doc-deprecated", True
)

0 comments on commit fcfc78d

Please sign in to comment.