Skip to content

Commit

Permalink
fix: taking into account custom_functions in py_signature
Browse files Browse the repository at this point in the history
  • Loading branch information
clatapie committed Oct 24, 2024
1 parent 41281ce commit 2e28187
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 59 deletions.
74 changes: 47 additions & 27 deletions src/pyconverter/xml2py/ast_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from inflect import engine
from lxml.etree import tostring
from lxml.html import fromstring
from pyconverter.xml2py.custom_functions import CustomFunctions
from pyconverter.xml2py.utils.utils import is_numeric, split_trail_alpha
import regex as re

Expand Down Expand Up @@ -198,6 +199,21 @@ def is_elipsis(name: str) -> bool:
return False


def str_types(types, join_str: str) -> str:
"""String representation of the parameter types."""
ptype_str = join_str.join([parm_type.__name__ for parm_type in types])
return ptype_str


def to_py_signature(py_arg_name, types) -> str:
"""Return the Python signature of the argument."""
if py_arg_name not in ["--", "–", ""]:
kwarg = f'{py_arg_name}: {str_types(types, " | ")} = ""'
else:
kwarg = None
return kwarg


# ############################################################################
# Element class
# ############################################################################
Expand Down Expand Up @@ -2317,11 +2333,6 @@ def types(self) -> List[type]:

return parm_types

def str_types(self, join_str: str) -> str:
"""String representation of the parameter types."""
ptype_str = join_str.join([parm_type.__name__ for parm_type in self.types])
return ptype_str

def resized_description(
self, description: str | None = None, max_length: int = 100, indent: str = ""
) -> List[str]:
Expand All @@ -2343,7 +2354,7 @@ def to_py_docstring(
) -> List[str]:
"""Return a list of string to enable converting the element to an RST format."""
if self.py_arg_name not in ["--", "–", ""]:
docstring = [f'{indent}{self.py_arg_name} : {self.str_types(" or ")}']
docstring = [f'{indent}{self.py_arg_name} : {str_types(self.types, " or ")}']
if isinstance(self._description, str):
rst_description = self._description
else:
Expand All @@ -2363,20 +2374,12 @@ def to_py_docstring(
rst_description = textwrap.indent(rst_description, description_indent)
list_description = rst_description.split("\n")

docstring = [f'{indent}{self.py_arg_name} : {self.str_types(" or ")}']
docstring = [f'{indent}{self.py_arg_name} : {str_types(self.types, " or ")}']
docstring.extend(list_description)
else:
docstring = []
return docstring

def to_py_signature(self) -> str:
"""Return the Python signature of the argument."""
if self.py_arg_name not in ["--", "–", ""]:
kwarg = f'{self.py_arg_name}: {self.str_types(" | ")} = ""'
else:
kwarg = None
return kwarg


class XMLCommand(Element):
"""Provides the XML command from the documentation."""
Expand Down Expand Up @@ -2513,19 +2516,30 @@ def group(self, group):
"""Set the group of the command."""
self._group = group

def py_signature(self, indent="") -> str:
def py_signature(self, custom_functions: CustomFunctions, indent="") -> str:
"""Beginning of the Python command's definition."""
args = ["self"]
arg_desc = self.arg_desc
if len(arg_desc) > 0:
for argument in arg_desc:
if argument.to_py_signature() is not None:
args.append(argument.to_py_signature())
if custom_functions is not None and (
self.py_name in custom_functions.py_names and self.py_name in custom_functions.py_args
):
for argument in custom_functions.py_args[self.py_name]:
new_arg = to_py_signature(
argument, [str]
) # checks are not done for custom functions
if new_arg is not None:
args.append(new_arg)
else:
arg_desc = self.arg_desc
if len(arg_desc) > 0:
for argument in arg_desc:
new_arg = to_py_signature(argument.py_arg_name, argument.types)
if new_arg is not None:
args.append(new_arg)

arg_sig = ", ".join(args)
return f"{indent}def {self.py_name}({arg_sig}, **kwargs):"

def py_docstring(self, custom_functions):
def py_docstring(self, custom_functions: CustomFunctions) -> str:
"""Python docstring of the command."""
xml_cmd = f"{self._terms['pn006p']} Command: `{self.name} <{self.url}>`_"

Expand Down Expand Up @@ -2775,9 +2789,13 @@ def py_notes(self):
fcache=self._fcache,
)
elif self.notes.tag in item_needing_links_base_url:
notes = self.notes.to_rst(max_length=self._max_length, links=self._links, base_url=self._base_url)
notes = self.notes.to_rst(
max_length=self._max_length, links=self._links, base_url=self._base_url
)
elif self.notes.tag in item_needing_fcache:
notes = self.notes.to_rst(max_length=self._max_length, links=self._links, fcache=self._fcache)
notes = self.notes.to_rst(
max_length=self._max_length, links=self._links, fcache=self._fcache
)
else:
notes = self.notes.to_rst()

Expand Down Expand Up @@ -2838,7 +2856,9 @@ def py_parm(self, indent="", links=None, base_url=None, fcache=None):
lines.append("Parameters")
lines.append("-" * 10)
for argument in arg_desc:
lines.extend(argument.to_py_docstring(self._max_length, indent, links, base_url, fcache))
lines.extend(
argument.to_py_docstring(self._max_length, indent, links, base_url, fcache)
)
lines.append("")
return lines

Expand Down Expand Up @@ -2894,13 +2914,13 @@ def to_python(self, custom_functions=None, indent=""):
imports = "\n".join(custom_functions.lib_import[self.py_name])
out = f"""
{imports}
{self.py_signature(indent)}
{self.py_signature(custom_functions, indent)}
{docstr}
{self.py_source(custom_functions, indent)}
"""
else:
out = f"""
{self.py_signature(indent)}
{self.py_signature(custom_functions, indent)}
{docstr}
{self.py_source(custom_functions, indent)}
"""
Expand Down
36 changes: 17 additions & 19 deletions src/pyconverter/xml2py/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,46 +134,44 @@ def version():
type=click.Path(exists=True),
help="Path to the directory that contains the XML documentation to convert.",
)
@click.option(
"-p",
"--targ-path",
type=click.Path(),
help="Path where to store the autogenerated package.",
)
@click.option(
"-t",
"--template-path",
type=click.Path(),
help="Path to the directory that contains the template to use.",
)
@click.option(
"-f",
"--func-path",
type=click.Path(exists=True),
help="Path to the directory that contains the functions that need to be customized.",
)
@click.option(
"-p",
"--pre-commit",
"-r",
"--run-pre-commit",
type=click.BOOL,
help="Whether to run the pre-commit hook on the autogenerated package source code.",
)
@click.option(
"-t",
"--template-path",
type=click.Path(),
help="Path to the directory that contains the template to use.",
)
@click.option(
"-l",
"--max-length",
type=click.INT,
default=100,
help="Maximum length of the generated docstrings.",
)
@click.option(
"-p",
"--run-pre-commit",
type=click.INT,
default=100,
help="Whether to run the pre-commit hook on the autogenerated package source code.",
)
def package(
xml_path: Path,
func_path: Path,
targ_path: Path,
template_path: Path,
run_black: bool,
func_path: Path,
run_pre_commit: bool,
max_length: int,
run_black
) -> None:
"""Create a Python package from your XML documentation."""
create_package(xml_path, targ_path, template_path, func_path, run_black, max_length)
create_package(xml_path, targ_path, template_path, func_path, run_pre_commit, max_length)
39 changes: 32 additions & 7 deletions src/pyconverter/xml2py/custom_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from pathlib import Path
from typing import Tuple

import regex as re


def get_docstring_lists(filename: str) -> Tuple[list[str], list[str], list[str], list[str]]:
"""
Expand Down Expand Up @@ -54,6 +56,7 @@ def get_docstring_lists(filename: str) -> Tuple[list[str], list[str], list[str],
bool_notes = False
begin_docstring = False
end_docstring = False
list_py_args = []
list_py_returns = []
list_py_examples = []
list_py_code = []
Expand All @@ -63,6 +66,10 @@ def get_docstring_lists(filename: str) -> Tuple[list[str], list[str], list[str],
list_import.append(line)
elif "def" in line and bool_def is False:
bool_def = True
split_def = line.split(",")
for split_arg in split_def:
if "=" in split_arg:
list_py_args.append(re.search(r"\w*(?=\=)", split_arg).group())
elif '"""' in line and begin_docstring is False:
begin_docstring = True
elif '"""' in line and begin_docstring is True:
Expand Down Expand Up @@ -98,7 +105,7 @@ def get_docstring_lists(filename: str) -> Tuple[list[str], list[str], list[str],
elif bool_notes is True:
pass # Notes are obtained from the converter

return list_py_returns, list_py_examples, list_py_code, list_import
return list_py_args, list_py_returns, list_py_examples, list_py_code, list_import


# ############################################################################
Expand All @@ -122,16 +129,23 @@ def __init__(self, path: Path) -> None:
if not Path(path).is_dir():
raise (FileExistsError, f"The path_functions {path} does not exist.")
self._py_names = []
self._py_args = {}
self._py_returns = {}
self._py_examples = {}
self._py_code = {}
self._lib_import = {}
for filename in Path(path).glob("*.py"):
py_name = filename.stem
self._py_names.append(py_name)
list_py_returns, list_py_examples, list_py_code, list_import = get_docstring_lists(
filename
)
(
list_py_args,
list_py_returns,
list_py_examples,
list_py_code,
list_import,
) = get_docstring_lists(filename)
if len(list_py_args) > 0:
self._py_args[py_name] = list_py_args
if len(list_py_returns) > 0:
self._py_returns[py_name] = list_py_returns
if len(list_py_examples) > 0:
Expand Down Expand Up @@ -159,9 +173,15 @@ def path(self, path: Path) -> None:
for filename in Path(path).glob("*.py"):
py_name = filename.stem
self._py_names.append(py_name)
list_py_returns, list_py_examples, list_py_code, list_import = get_docstring_lists(
filename
)
(
list_py_args,
list_py_returns,
list_py_examples,
list_py_code,
list_import,
) = get_docstring_lists(filename)
if len(list_py_args) > 0:
self._py_args[py_name] = list_py_args
if len(list_py_returns) > 0:
self._py_returns[py_name] = list_py_returns
if len(list_py_examples) > 0:
Expand All @@ -176,6 +196,11 @@ def py_names(self) -> list:
"""List with all customized functions located in the folder."""
return self._py_names

@property
def py_args(self) -> dict:
"""Dictionary containing the python arguments if any."""
return self._py_args

@property
def py_returns(self) -> dict:
"""Dictionary containing the ``Returns`` section if any."""
Expand Down
4 changes: 3 additions & 1 deletion src/pyconverter/xml2py/directory_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ def get_paths(
graph_path = path / "graphics"
if not graph_path.is_dir():
print(
f"WARNING: the path {graph_path} does not exist. Follow the predefined format or enter the graphic path manually." # noqa : E501
f"WARNING: the path {graph_path} does not exist.",
"Follow the predefined format or enter the graphic",
"path manually.", # noqa : E501
)

if link_path is None:
Expand Down
6 changes: 4 additions & 2 deletions src/pyconverter/xml2py/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ def run_pre_commit(package_path) -> None:
max_run = 10
while cur_run < max_run and output != 0:
cur_run += 1
output = os.system(f"pre-commit run --all-files --config {package_path}/.pre-commit-config.yaml")
output = os.system(
f"pre-commit run --all-files --config {package_path}/.pre-commit-config.yaml"
)
if output != 0:
raise RuntimeError("Pre-commit failed.")
else:
print("Pre-commit ran successfully.")
print("Pre-commit ran successfully.")
4 changes: 2 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ def test_cli_main_package_group():

assert "Create a Python package from your XML documentation." in result.output
assert "-x, --xml-path PATH" in result.output
assert "-f, --func-path PATH" in result.output
assert "-p, --targ-path PATH" in result.output
assert "-t, --template-path PATH" in result.output
assert "-b, --run-black BOOLEAN" in result.output
assert "-f, --func-path PATH" in result.output
assert "-r, --run-pre-commit BOOLEAN" in result.output
assert "-l, --max-length INTEGER" in result.output
1 change: 1 addition & 0 deletions tests/test_custom_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def test_customfunctions(custom_functions):
def test_get_docstring_lists(path_custom_functions):
path_custom_function = path_custom_functions / "kdist.py"
(
list_py_args,
list_py_returns,
list_py_examples,
list_py_code,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_convert(command_map, custom_functions):
command_map["E"].py_source(custom_functions)
== ' command = f"E,{i},{j},{k},{l},{m},{n},{o},{p}"\n return self.run(command, **kwargs)\n' # noqa : E501
)
assert 'def zoom(self, wn: str="", lab: str="", x1: str="", y1: str="", x2: str="", y2: str="", **kwargs):\n r"""Zooms a region of a display window.\n\n' in command_map[ # noqa : E501
assert 'def zoom(self, wn: str = "", lab: str = "", x1: str = "", y1: str = "", x2: str = "", y2: str = "", **kwargs):\n r"""Zooms a region of a display window.\n\n' in command_map[ # noqa : E501
"/ZOOM"
].to_python(
custom_functions
Expand Down

0 comments on commit 2e28187

Please sign in to comment.