diff --git a/.gitignore b/.gitignore index f37b84302..edc3b696a 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,4 @@ cython_debug/ helper/ package/ _autosummary/ +args.txt diff --git a/config.yaml b/config.yaml index f788f5df2..02dab56b1 100644 --- a/config.yaml +++ b/config.yaml @@ -1,8 +1,10 @@ +project_name: PyConverter-GeneratedCommands # Name of the project + library_name_structured: # Future name of the library - pyconverter - generatedcommands -subfolder: +subfolders: - subfolder - subsubfolder diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index a256314da..ccb159d8a 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -21,6 +21,7 @@ # SOFTWARE. import logging +from pathlib import Path import textwrap from typing import List import warnings @@ -82,6 +83,12 @@ NO_RESIZE_LIST = ["Variablelist"] +MISSING_ARGUMENT_DESCRIPTION = """The description of the argument is missing in the Python function. +Please, refer to the `command documentation `_ for further information.""" + +ADDITIONAL_ARGUMENT_DESCRIPTION = """Additional arguments can be passed to the initial command. +Please, refer to the `command documentation `_ for further information.""" + class NameMap: def __init__(self, name_map): @@ -134,9 +141,17 @@ def get_quant_iter_pos(name: str) -> tuple: def to_py_arg_name(name: str) -> str: """Python-compatible term""" - arg = str(name).lower().strip() - p = engine() + initial_arg = str(name).lower().strip() + arg = initial_arg + if arg in ["--", "–", ""]: + return "" + elif "--" in arg: + arg = arg.replace("--", "") + return arg + elif arg.isdigit(): + return "" if arg[0].isdigit(): + p = engine() if arg[1].isdigit(): raise ValueError(f"The code needs to be expanded to handle numbers") elif arg[1:3] not in superlatif: @@ -146,9 +161,6 @@ def to_py_arg_name(name: str) -> str: num_value = p.number_to_words(arg[:3]) arg = f"{num_value}{arg[3:]}" - if ("," in arg and "--" in arg) or arg == "–": - return "" - for key, value in PY_ARG_CLEANUP.items(): arg = arg.replace(key, value) arg = arg.strip() @@ -204,7 +216,7 @@ def str_types(types, join_str: str) -> str: def to_py_signature(py_arg_name, types) -> str: """Return the Python signature of the argument.""" - if py_arg_name not in ["--", "–", ""]: + if py_arg_name != "": kwarg = f'{py_arg_name}: {str_types(types, " | ")} = ""' else: kwarg = None @@ -1596,6 +1608,7 @@ def raw_args(self): cmd = cmd.replace("&fname2_arg;", self._terms["fname2_arg"]) cmd = cmd.replace("&pn006p;", self._terms["pn006p"]) cmd = cmd.replace("&ansysBrand;", self._terms["ansysBrand"]) + cmd = cmd.replace("``", "") split_args = cmd.split(",")[1:] return split_args @@ -1604,36 +1617,29 @@ def args(self): """Command arguments.""" args = [] for item in self.raw_args: - orig_arg = str(item).replace(",", "") - arg = orig_arg.lower().replace("--", "").replace("–", "").replace("-", "_").strip() - if arg == "": - continue - - if arg == "class": - arg = "class_" - elif arg == "type": - arg = "type_" + arg = to_py_arg_name(str(item)) # simply check if we can use this as a valid Python kwarg try: exec(f"{arg} = 1.0") except SyntaxError: - continue + arg = "" if "blank" in arg: - continue + arg = "" args.append(arg) # rename duplicate arguments if len(args) != len(set(args)): for arg in args: - i = 0 - if args.count(arg) > 1: - for j in range(len(args)): - if args[j] == arg: - args[j] = f"{arg}{i:d}" - i += 1 + if arg != "": + i = 0 + if args.count(arg) > 1: + for j in range(len(args)): + if args[j] == arg: + args[j] = f"{arg}{i:d}" + i += 1 return args @@ -2104,58 +2110,13 @@ class ProductName(Element): pass -class ArgumentList: - def __init__(self, list_entry: VarlistEntry, args: List) -> None: - - self._list_entry = list_entry - self._arguments = [] - self._initial_args = args - self._parse_list_entry() - - def _parse_list_entry(self): - for item in self._list_entry: - if isinstance(item, VarlistEntry): - argument_obj = Argument(item, self._initial_args) - additional_args = argument_obj.multiple_args - if len(additional_args) > 0: - for arg in additional_args: - if arg.py_arg_name != "" and arg.py_arg_name not in self.py_arg_names: - self._arguments.append(arg) - - else: - if argument_obj.py_arg_name != "": - self._arguments.append(argument_obj) - - def __iadd__(self, argument_list): - for arg in argument_list.arguments: - if arg.py_arg_name not in self.py_arg_names: - self._arguments.append(arg) - return self - - @property - def arguments(self): - return self._arguments - - @arguments.setter - def arguments(self, argument): - self._arguments.append(argument) - - @property - def initial_args(self): - return self._initial_args - - @property - def py_arg_names(self): - return [arg.py_arg_name for arg in self._arguments] - - class Argument: """Argument object.""" def __init__( self, element: str | Element, - initial_argument: List, + initial_arguments: List, description: Element | str | None = None, ) -> None: if description is None: @@ -2174,7 +2135,7 @@ def __init__( name = element self._name = name self._description = description - self._initial_argument = initial_argument + self._initial_arguments = initial_arguments @property def py_arg_name(self) -> str: @@ -2201,28 +2162,26 @@ def multiple_args(self): if not self.is_arg_elipsis: for item_name in split_name: arg_name = item_name.strip() - if arg_name not in ["--", ""]: - new_arg = Argument(arg_name, self._initial_argument, self._description) - if new_arg.py_arg_name != "": - additional_args.append(new_arg) + new_arg = Argument(arg_name, self._initial_arguments, self._description) + additional_args.append(new_arg) else: complete_args = get_complete_args_from_initial_arg( - elipsis_args=split_name, initial_args=self._initial_argument + elipsis_args=split_name, initial_args=self._initial_arguments ) if len(complete_args) > 0: for item in complete_args: - new_arg = Argument(item, self._initial_argument, self._description) - if new_arg.py_arg_name != "": - additional_args.append(new_arg) + new_arg = Argument(item, self._initial_arguments, self._description) + additional_args.append(new_arg) else: for i, item_name in enumerate(split_name): item_name = item_name.strip() if item_name == "": - continue + new_arg = Argument(arg_name, self._initial_arguments, self._description) + additional_args.append(new_arg) elif is_elipsis(item_name): if "+" in split_name[i + 1]: @@ -2242,7 +2201,7 @@ def multiple_args(self): arg_name = split_name[i + 1].strip() arg_name = f"{arg_name[:initial_pos_final]}{j}{arg_name[end_pos_final:]}" # noqa : E501 new_arg = Argument( - arg_name, self._initial_argument, self._description + arg_name, self._initial_arguments, self._description ) if new_arg.py_arg_name != "": additional_args.append(new_arg) @@ -2274,34 +2233,20 @@ def multiple_args(self): for j in range(number_iter_prev + 1, number_iter_next): arg_name = f"{name_iter_prev}{j}" new_arg = Argument( - arg_name, self._initial_argument, self._description + arg_name, self._initial_arguments, self._description ) - if new_arg.py_arg_name != "": - additional_args.append(new_arg) + additional_args.append(new_arg) else: additional_args.append( Argument( name_iter_next, - self._initial_argument, + self._initial_arguments, self._description, ) ) return additional_args - def rec_find(self, _type: str, terms=None) -> Element | None: - """Find the first type matching a given type string recursively.""" - for item in self: - if type(item).__name__ == _type: - if _type == "Refname" or _type == "Refnamediv": - item.terms = terms - return item - if isinstance(item, Element): - subitem = item.rec_find(_type) - if subitem is not None: - return subitem - return None - @property def types(self) -> List[type]: """One or more parameter types. @@ -2350,7 +2295,7 @@ def to_py_docstring( self, max_length=100, indent="", links=None, base_url=None, fcache=None ) -> List[str]: """Return a list of string to enable converting the element to an RST format.""" - if self.py_arg_name not in ["--", "–", ""]: + if self.py_arg_name != "": docstring = [f'{indent}{self.py_arg_name} : {str_types(self.types, " or ")}'] if isinstance(self._description, str): rst_description = self._description @@ -2373,11 +2318,141 @@ def to_py_docstring( docstring = [f'{indent}{self.py_arg_name} : {str_types(self.types, " or ")}'] docstring.extend(list_description) + else: docstring = [] return docstring +class ArgumentList: + def __init__(self, py_name: str, url: str, list_entry: VarlistEntry, args: List) -> None: + + self._py_name = py_name + self._url = url + self._list_entry = list_entry + self._arguments = [] + self._additional_args = [] + self._initial_args = args + self._parse_list_entry() + + def _parse_list_entry(self): + "Parse the list entry to get the main arguments and the additional ones." + temp_args = {} + for item in self._list_entry: + if isinstance(item, VarlistEntry): + argument_obj = Argument(item, self._initial_args) + additional_args = argument_obj.multiple_args + if len(additional_args) > 0: + for arg in additional_args: + arg_name = arg.py_arg_name + if (arg_name in self._initial_args) and ( + arg_name == "" or arg_name not in self.py_arg_names + ): + temp_args[arg_name] = arg + + else: + temp_args[argument_obj.py_arg_name] = argument_obj + + for initial_arg in self._initial_args: + if initial_arg in temp_args.keys(): + self._arguments.append(temp_args[initial_arg]) + else: + self._arguments.append( + Argument( + initial_arg, + self._initial_args, + MISSING_ARGUMENT_DESCRIPTION.replace("url", f"{self._url}"), + ) + ) # description is missing + + is_additional_arg = False + if len(temp_args) != len(self._initial_args): + for arg in temp_args: + if arg not in self.py_arg_names: + self._additional_args.append(temp_args[arg]) + is_additional_arg = True + + if is_additional_arg and "addional_command_arg" not in self.py_arg_names: + self._arguments.append( + Argument( + "addional_command_arg", + self._initial_args, + ADDITIONAL_ARGUMENT_DESCRIPTION.replace("url", f"{self._url}"), + ) + ) + + def __iadd__(self, argument_list): + temp_args = {} + for arg in argument_list.arguments: + arg_name = arg.py_arg_name + if (arg_name in self._initial_args) and ( + arg_name == "" or arg_name not in self.py_arg_names + ): + temp_args[arg_name] = arg + + for initial_arg in self._initial_args: + if initial_arg in temp_args.keys(): + if initial_arg not in self.py_arg_names: + self._arguments.append(temp_args[initial_arg]) + else: + self._arguments[self.py_arg_names.index(initial_arg)] = temp_args[initial_arg] + else: + if initial_arg not in self.py_arg_names: + self._arguments.append( + Argument( + initial_arg, + self._initial_args, + MISSING_ARGUMENT_DESCRIPTION.replace("url", f"{self._url}"), + ) + ) + + is_additional_arg = False + if len(temp_args) != len(self._initial_args): + for arg in temp_args: + if arg not in self.py_arg_names: + self._additional_args.append(temp_args[arg]) + is_additional_arg = True + + if is_additional_arg and "addional_command_arg" not in self.py_arg_names: + self._arguments.append( + Argument( + "addional_command_arg", + self._initial_args, + ADDITIONAL_ARGUMENT_DESCRIPTION.replace("url", f"{self._url}"), + ) + ) + + return self + + @property + def arguments(self) -> List[Argument]: + "Return a list of Argument objects." + return self._arguments + + @arguments.setter + def arguments(self, argument): + self._arguments.append(argument) + + @property + def py_name(self): + return self._py_name + + @property + def initial_args(self): + return self._initial_args + + @property + def py_arg_names(self): + return [arg.py_arg_name for arg in self._arguments] + + @property + def additional_args(self): + return self._additional_args + + def remove_last_arg(self): + self._arguments.pop() + + class XMLCommand(Element): """Provides the XML command from the documentation.""" @@ -2435,29 +2510,45 @@ def arg_desc(self) -> List[Argument]: if refsyn is None: refsections = self.find_all("RefSection") for elem in refsections: - if elem.id is not None and "argdescript" in elem.id: - for child in elem: - if isinstance(child, Variablelist): - if arguments is None: - arguments = ArgumentList(child, self.args) - else: - arguments += ArgumentList(child, self.args) + for child in elem: + if isinstance(child, Variablelist): + if arguments is None: + arguments = ArgumentList(self.py_name, self.url, child, self.args) + else: + arguments += ArgumentList(self.py_name, self.url, child, self.args) else: for elem in refsyn: if isinstance(elem, Variablelist): if arguments is None: - arguments = ArgumentList(elem, self.args) + arguments = ArgumentList(self.py_name, self.url, elem, self.args) else: - arguments += ArgumentList(elem, self.args) + arguments += ArgumentList(self.py_name, self.url, elem, self.args) + + arg_file = Path("args.txt") if arguments is not None: - if len(arguments.py_arg_names) < len(arguments.initial_args): - for arg in arguments.initial_args: - if arg not in arguments.py_arg_names: - new_arg = Argument(arg, arguments.initial_args, "") - if new_arg.py_arg_name != "": - arguments.arguments.append(new_arg) + # Remove last argument if it's empty + while arguments.py_arg_names[-1] == "": + arguments.remove_last_arg() + + if len(arguments.py_arg_names) != len(arguments.initial_args): + # This function needs a special treatment + if arg_file.exists(): + with open(arg_file, "r") as f: + for line in f: + pass + last_line = line + else: + last_line = "" + with open(arg_file, "a") as f: + if last_line != f"py_arg_name : {arguments.py_arg_names}\n": + f.write("--------------------------------------------------\n") + f.write(f"{self.py_name}: {self.group}\n") + f.write("initial_args : ") + f.write(f"{arguments.initial_args}\n") + f.write("py_arg_name : ") + f.write(f"{arguments.py_arg_names}\n") return arguments.arguments @@ -2899,11 +2990,14 @@ def py_source(self, custom_functions=None, indent=""): if len(self.arg_desc) > 0: command = 'command = f"' + self.name for arg in self.arg_desc: - command += ",{" - command += arg.py_arg_name - command += "}" + name = arg.py_arg_name + if name == "": + command += "," + else: + command += ",{" + command += arg.py_arg_name + command += "}" command += '"\n' - # ",{" + "},{".join(self.arg_desc.py_arg_name) + '}"\n' else: command = 'command = f"' + self.name + '"\n' return_command = "return self.run(command, **kwargs)\n" diff --git a/src/pyconverter/xml2py/cli.py b/src/pyconverter/xml2py/cli.py index 5f5ca5dd6..84ef760a2 100644 --- a/src/pyconverter/xml2py/cli.py +++ b/src/pyconverter/xml2py/cli.py @@ -105,6 +105,10 @@ def create_package( download.download_template() command_map, name_map = wr.convert(xml_path) + arg_file = Path("args.txt") + if arg_file.exists(): + # Delete the file if it exists + arg_file.unlink() package_structure = wr.write_source( command_map, name_map, xml_path, target_path, custom_functions_path ) diff --git a/src/pyconverter/xml2py/writer.py b/src/pyconverter/xml2py/writer.py index f95a00405..24123974d 100644 --- a/src/pyconverter/xml2py/writer.py +++ b/src/pyconverter/xml2py/writer.py @@ -220,6 +220,7 @@ def write_global__init__file(library_path: Path, config_path: Path) -> None: Path object of the directory containing the generated package. """ + project_name = get_config_data_value(config_path, "project_name") subfolder_values = get_config_data_value(config_path, "subfolders") if subfolder_values: @@ -244,7 +245,7 @@ def write_global__init__file(library_path: Path, config_path: Path) -> None: fid.write("except ModuleNotFoundError:\n") fid.write(" import importlib_metadata\n\n") fid.write("__version__ = importlib_metadata.version(__name__.replace('.', '-'))\n") - fid.write('"""PyConverter-GeneratedCommands version."""\n') + fid.write(f'"""{project_name} version."""\n') fid.close()