From 2e78c013b9f1d007880362c2141a4885cecbc6f3 Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:35:08 +0200 Subject: [PATCH 01/20] feat: attempt for docstring length improvement --- src/pyconverter/xml2py/ast_tree.py | 369 ++++++++++++++++++++--------- 1 file changed, 260 insertions(+), 109 deletions(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 37be93833..736adabbf 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -28,6 +28,7 @@ from lxml.html import fromstring from pyconverter.xml2py.utils.utils import is_numeric, split_trail_alpha import regex as re +from typing import List CONV_EQN = False @@ -67,7 +68,7 @@ # XML commands to skip SKIP = {"*IF", "*ELSE", "C***", "*RETURN"} - +NO_RESIZE_LIST = ["Variablelist"] class NameMap: def __init__(self, name_map): self.name_map = name_map @@ -202,7 +203,7 @@ def id(self): """ID of the element.""" return self._element.get("id") - def to_rst(self, indent="", links=None, base_url=None, fcache=None): + def to_rst(self, indent="", max_length= 100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" items = [] for item in self: @@ -211,19 +212,20 @@ def to_rst(self, indent="", links=None, base_url=None, fcache=None): items.append( item.to_rst( indent, + max_length=max_length, links=links, base_url=base_url, fcache=fcache, ) ) elif item.tag in item_needing_links_base_url: - items.append(item.to_rst(indent, links=links, base_url=base_url)) + items.append(item.to_rst(indent, max_length=max_length, links=links, base_url=base_url)) elif item.tag in item_needing_fcache: - items.append(item.to_rst(indent=indent, fcache=fcache)) + items.append(item.to_rst(indent=indent, max_length=max_length, fcache=fcache)) else: - items.append(item.to_rst(indent=indent)) + items.append(item.to_rst(indent=indent, max_length=max_length)) else: - items.append(indent + str(item)) + items.append(resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) return " ".join(items) def rec_find(self, _type, terms=None): @@ -281,7 +283,7 @@ def tag(self): return self._element.tag -def resize_length(text, initial_indent, subsequent_indent, max_length=100): +def resize_length(text, max_length=100, initial_indent="", subsequent_indent="", list=False): """Resize the length of a text.""" wrapper = textwrap.TextWrapper( @@ -291,7 +293,10 @@ def resize_length(text, initial_indent, subsequent_indent, max_length=100): subsequent_indent=subsequent_indent, drop_whitespace=False, ) - return wrapper.fill(text=text) + if list is False: + return wrapper.fill(text=text) + else: + return wrapper.wrap(text=text) class ItemizedList(Element): @@ -300,7 +305,7 @@ class ItemizedList(Element): def __repr__(self): return "\n".join([f"* {str(item).strip()}" for item in self]) - def to_rst(self, indent="", links=None, base_url=None, fcache=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" lines = [] for item in self: @@ -314,7 +319,7 @@ def to_rst(self, indent="", links=None, base_url=None, fcache=None): elif item.tag in item_needing_fcache: item_lines = item.to_rst(indent=indent, fcache=fcache).splitlines() else: - item_lines = item.to_rst(indent) + item_lines = item.to_rst(indent).splitlines() else: item_lines = str(item).splitlines() @@ -335,16 +340,17 @@ def to_rst(self, indent="", links=None, base_url=None, fcache=None): # rst_list = resize_length( # rst_list, + # max_length=max_length # initial_indent="", # subsequent_indent="", - # max_length=max_length # ) - for i in range(len(rst_list)): - rst_list[i] = ponctuaction_whitespace(rst_list[i], ".") - rst_list[i] = ponctuaction_whitespace(rst_list[i], ",") - rst_list[i] = resize_length(rst_list[i], initial_indent="", subsequent_indent="") + new_rst_list = [] + for line in rst_list: + line = ponctuaction_whitespace(line, ".") + line = ponctuaction_whitespace(line, ",") + new_rst_list.extend(resize_length(line, max_length=max_length, initial_indent=indent, subsequent_indent=indent, list=True)) - lines.extend(rst_list) + lines.extend(new_rst_list) # lists must have at least one line proceeding lines = ["", ""] + lines + [""] @@ -395,7 +401,7 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None): rst_item, "," ) # Remove extra whitespace before comma resized_item = resize_length( - rst_item, initial_indent="", subsequent_indent="", max_length=max_length + rst_item, max_length=max_length, initial_indent="", subsequent_indent="" ) ordered_list.append(resized_item) return "\n\n".join(ordered_list) @@ -404,7 +410,7 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None): class ListItem(Element): """Provides the list item element.""" - def to_rst(self, indent="", links=None, base_url=None, fcache=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" items = [] for item in self: @@ -417,13 +423,13 @@ def to_rst(self, indent="", links=None, base_url=None, fcache=None): fcache=fcache, ) elif item.tag in item_needing_links_base_url: - rst_item = item.to_rst(indent=indent, links=links, base_url=base_url) + rst_item = item.to_rst(indent=indent, max_length=max_length, links=links, base_url=base_url) elif item.tag in item_needing_fcache: - rst_item = item.to_rst(indent=indent, fcache=fcache) + rst_item = item.to_rst(indent=indent, max_length=max_length, fcache=fcache) else: - rst_item = item.to_rst(indent=indent) + rst_item = item.to_rst(indent=indent, max_length=max_length) else: - rst_item = str(item) + rst_item = resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent) items.append(rst_item) return "\n".join(items) @@ -432,7 +438,7 @@ def to_rst(self, indent="", links=None, base_url=None, fcache=None): class FileName(Element): """Provides the filename element.""" - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" return f"``{self[0]}`` {self.tail}" @@ -453,7 +459,7 @@ def targetdoc(self): """Value for the ``targetdoc`` parameter contained in the OLink element.""" return self.get("targetdoc") - def to_rst(self, indent="", links=None, base_url=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None): """Return a string to enable converting the element to an RST format.""" key = f"{self.targetptr}" if (links or base_url) is None: @@ -501,6 +507,7 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No "\n\n" + item.to_rst( indent=indent, + max_length=max_length, links=links, base_url=base_url, fcache=fcache, @@ -511,19 +518,20 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No items.append( item.to_rst( indent=indent, + max_length=max_length, links=links, base_url=base_url, fcache=fcache, ) ) elif item.tag in item_needing_links_base_url: - items.append(item.to_rst(indent=indent, links=links, base_url=base_url)) + items.append(item.to_rst(indent=indent, max_length=max_length, links=links, base_url=base_url)) elif item.tag in item_needing_fcache: - items.append(item.to_rst(indent=indent, fcache=fcache)) + items.append(item.to_rst(indent=indent, max_length=max_length, fcache=fcache)) else: - items.append(item.to_rst(indent=indent)) + items.append(item.to_rst(indent=indent, max_length=max_length)) else: - items.append(str(item)) + items.append(resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) rst_item = " ".join(items) + "\n" @@ -558,7 +566,7 @@ def role(self): """Return the role parameter value contained in the Emphasis element.""" return self._element.get("role") - def to_rst(self, indent="", links=None, base_url=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None): """Return a string to enable converting the element to an RST format.""" if self.role == "bold": @@ -580,7 +588,7 @@ def to_rst(self, indent="", links=None, base_url=None): else: items.append(item.to_rst(indent)) else: - items.append(str(item)) + items.append(resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) return content + " ".join(items) @@ -654,7 +662,7 @@ def content_equals(self): return f"``{self.content[0].lower()}={parm}`` {tail}" - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" if isinstance(self.prev_elem, Command): if any([self.content[0] in arg for arg in self.prev_elem.args]): @@ -681,9 +689,9 @@ def to_rst(self, indent="", max_length=100): rst_item = header + textwrap.indent(source_code, prefix=indent + " " * 3) + "\n" # rst_item = resize_length( # rst_item, + # max_length=max_length, # initial_indent="", # subsequent_indent=" !", - # max_length=max_length, # replace_whitespace=False # ) return rst_item @@ -696,12 +704,15 @@ def resize_list_text(text, max_length=100): n_line = len(re.match(r"^\s*", line).group()) if line.strip() and line.strip()[0] == "*": n_line += 2 - new_text.append(resize_length(line, "", " " * n_line, max_length)) + new_text.append(resize_length(line, max_length, initial_indent="", subsequent_indent=" " * n_line)) return "\n".join(new_text) class Variablelist(Element): """Provides the variable list.""" + + def __init__(self, element): + super().__init__(element) def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" @@ -740,7 +751,7 @@ def terms(self): class RefSection(Element): """Provides the reference section element.""" - def to_rst(self, indent="", links=None, base_url=None, fcache=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" items = [] for item in self[1:]: @@ -880,18 +891,16 @@ def py_text(self, links=None, base_url=None, fcache=None): rst = ". ".join(valid) return rst - def to_rst(self, indent="", links=None, base_url=None, fcache=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" indent += " " # if this is a parameter arg if self.is_arg: + # This is what needs to be modified in order to have the arg class lines = [f"{self.py_term(links=links, base_url=base_url)}"] - lines.append( - textwrap.indent( - self.py_text(links=links, base_url=base_url, fcache=fcache), - prefix=indent, - ) - ) + text = self.py_text(links=links, base_url=base_url, fcache=fcache) + text_list = resize_length(text, max_length=max_length, initial_indent=indent, subsequent_indent=indent, list=True) + lines.extend(text_list) return "\n".join(lines) # term_text = [line.strip() for line in self.py_text.splitlines()] @@ -900,14 +909,13 @@ def to_rst(self, indent="", links=None, base_url=None, fcache=None): if "``" in py_term: py_term = py_term.replace("``", "") lines = [ - f"* ``{py_term}`` - {textwrap.indent(self.py_text(links=links, base_url=base_url, fcache=fcache), indent)}" # noqa : E501 + f"* ``{py_term}`` - {self.py_text(links=links, base_url=base_url, fcache=fcache)}" ] text = "\n".join(lines) # if 'ID number to which this tip belongs' in text: # breakpoint() return text - - + class Term(Element): """Provides the term element.""" @@ -951,7 +959,7 @@ class GuiMenuItem(Element): class SuperScript(Element): """Provides the superscript element.""" - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" return f":sup:`{self.content[0]}` {self.tail}" @@ -1017,7 +1025,7 @@ def tail(self): """Return the tail of the element as a string.""" return self.raw.split("")[-1].replace("\n", "") - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" return f":math:`{self.equation.strip()}` {self.tail}" @@ -1068,7 +1076,7 @@ def linkend(self): def __repr__(self): return str(self.linkend) - def to_rst(self, indent="", links=None, base_url=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None): """Return a string to enable converting the element to an RST format.""" if (links or base_url) is None: logging.error( @@ -1094,7 +1102,7 @@ def tail(self): """Tail of the element as a string.""" return " ".join([str(item) for item in self._content]) - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" # disabled at the moment # return f':ref:`{self.linkend}`{self.tail}' @@ -1116,7 +1124,7 @@ class Screen(Element): class Literal(Element): """Provides the literal output element.""" - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" return f"``{self.content[0]}`` {self.tail}" @@ -1124,10 +1132,11 @@ def to_rst(self, indent=""): class Caution(Element): """Provides the caution element.""" - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" lines = ["", "", ".. warning::"] - lines.append(textwrap.indent(str(self), prefix=indent + " ")) + indent = indent + " " + lines.append(resize_length(str(self), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) return "\n".join(lines) @@ -1142,7 +1151,7 @@ def entityref(self): entityref = entityref.strip() return entityref - def to_rst(self, fcache, indent=""): + def to_rst(self, fcache, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" if self.entityref is None: @@ -1177,7 +1186,7 @@ class Note(Element): class BlockQuote(Element): """Provides the block quote element.""" - def to_rst(self, indent="", links=None, base_url=None, fcache=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" items = [] for item in self: @@ -1186,19 +1195,20 @@ def to_rst(self, indent="", links=None, base_url=None, fcache=None): items.append( item.to_rst( indent, + max_length=max_length, links=links, base_url=base_url, fcache=fcache, ) ) elif item.tag in item_needing_links_base_url: - items.append(item.to_rst(indent, links=links, base_url=base_url)) + items.append(item.to_rst(indent, max_length=max_length, links=links, base_url=base_url)) elif item.tag in item_needing_fcache: - items.append(item.to_rst(indent=indent, fcache=fcache)) + items.append(item.to_rst(indent=indent, max_length=max_length, fcache=fcache)) else: - items.append(item.to_rst(indent)) + items.append(item.to_rst(indent, max_length=max_length)) else: - items.append(indent + str(item)) + items.append(resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) return "\n\n" + " ".join(items) + "\n\n" @@ -1321,8 +1331,9 @@ def tgroup(self): """TGroup.""" return self.find("TGroup") - def to_rst(self, indent="", links=None, base_url=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None): """Return a string to enable converting the element to an RST format.""" + # For now, Tables don't support ``max_length`` lines = [] if self.title is not None: lines.append(f"{self.title}".strip()) @@ -1561,7 +1572,7 @@ def sphinx_cmd(self): ref = f":ref:`{self.py_cmd}`" return ref - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" if self.args and self.args[0] != "": return f"{self.sphinx_cmd} {self.tail_no_args}" @@ -1572,7 +1583,7 @@ def to_rst(self, indent=""): class ComputerOutput(Element): """Provides the computer output element.""" - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" return f"``{self[0]}`` {self[1]}" @@ -1590,7 +1601,7 @@ def graphic(self): """First graphic element found in the figure element.""" return self.rec_find("Graphic") - def to_rst(self, indent="", fcache=None): + def to_rst(self, indent="", max_length=100, fcache=None): """Return a string to enable converting the element to an RST format.""" graphic = self.graphic if graphic is not None and graphic.entityref is not None: @@ -1919,6 +1930,113 @@ class ProductName(Element): pass +# class Argument: + +# def __init__(self, list_entry: VarlistEntry) -> None: + +# self._name = list_entry[0] +# self._type = "" +# self._description = list_entry[1] +# self._types = list_entry.py_term + +# print(self._types) + + +# def to_rst(self, max_length=100): +# pass +# # print(resize_length(self._description.to_rst(), max_length)) + + +class Argument: + """Argument object.""" + + def __init__(self, element): + self._name = element[0] + self._description = element[1] + + 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. + + This is guessed from any subvarlists. If unavailable, then it's guessed + from the description of the variable. + + This is either a string, float, or integer (or some combination thereof). + + """ + varlist = self._description.rec_find("Variablelist") + + parm_types = [str] + if varlist is not None: + terms = varlist.terms + if terms: + terms_numeric = [is_numeric(term) for term in terms] + if all(terms_numeric): + parm_types = [int] + elif any(terms_numeric): + parm_types = [int, str] + else: + parm_types = [str] + + # consider checking for bool + # terms_numeric = set(terms) == set(['1', '0']) + + 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 + + @property + def py_arg_name(self) -> str: + """Python-compatible term.""" + arg = str(self._name).lower() + + if arg == "type": + arg = "type_" + + return f"{arg}" + + def resized_description(self, description: str|None=None, max_length: int =100, indent: str="") -> List[str]: + """Resize the description to a maximum length.""" + indent = " "*4 + indent + if description is None: + description = self._description + return resize_length(description, max_length, initial_indent=indent, subsequent_indent=indent, list=True) + + def to_py_docstring(self, max_length=100, indent="", links=None, base_url=None) -> List[str]: + """Return a list of string to enable converting the element to an RST format.""" + + docstring = [f"{indent}{self.py_arg_name} : {self.str_types(" or ")}"] + rst_description = self._description.to_rst(indent=indent, max_length = max_length, links=links, base_url=base_url) + if not "* " in rst_description: + list_description = self.resized_description(rst_description, max_length, indent) + else: + list_description = rst_description.split("\n") + + docstring = [f"{indent}{self.py_arg_name} : {self.str_types(" or ")}"] + docstring.extend(list_description) + return docstring + + def to_py_signature(self) -> str: + """Return the Python signature of the argument.""" + return f"{self.py_arg_name}: {self.str_types(" | ")}" + class XMLCommand(Element): """Provides the XML command from the documentation.""" @@ -1968,24 +2086,43 @@ def default(self): return item @property - def arg_desc(self): - """Each argument's description.""" + def arg_desc(self) -> List[Argument]: + """Argument object list of the command.""" refsyn = self.rec_find("Refsynopsisdiv") - + arguments = None # search by ID if refsyn is None: - arg_desc = [] - for elem in self.find_all("RefSection"): + arguments = [] + refsections = self.find_all("RefSection") + for elem in refsections: if ( elem.id is not None and "argdescript" in elem.id - ): # need to be modified for extended docstrings - arg_desc.append(elem) - if len(arg_desc) > 0: - return arg_desc - else: - return None - - return refsyn + ): + for child in elem[1]: + arguments.append(Argument(child)) + return arguments + else: + # print("NUMBER OF CHILDREN : ", len(refsyn.children)) + # for child in refsyn.children: + # print("| TYPE : ", type(child)) + # print("| CHILD : ", child) + # print("| NUMBER OF GRANDCHILDREN : ", len(child.children)) + # for gc in child.children: + # print("|| TYPE : ", type(gc)) + # print("|| CHILD : ", gc) + # print("|| NUMBER OF GREAT GRANDCHILDREN : ", len(gc.children)) + # for ggc in gc.children: + # print("||| TYPE : ", type(ggc)) + # print("||| CHILD : ", ggc) + # print("||| NUMBER OF GREAT GREAT GRANDCHILDREN : ", len(ggc.children)) + # for gggc in ggc.children: + # print("|||| TYPE : ", type(gggc)) + # print("|||| CHILD : ", gggc) + # print("|||| NUMBER OF GREAT GREAT GREAT GRANDCHILDREN : ", len(gggc.children)) + + + arguments = [refsyn] + return arguments @property def short_desc(self): @@ -2041,10 +2178,9 @@ def py_signature(self, indent=""): args = ["self"] kwargs = [f'{arg}=""' for arg in self.py_args if "--" not in arg] arg_sig = ", ".join(args + kwargs) - # print(self.py_parm) return f"{indent}def {self.py_name}({arg_sig}, **kwargs):" - def py_docstring(self, custom_functions): + def py_docstring(self, custom_functions, max_length=100): """Python docstring of the command.""" xml_cmd = f"{self._terms['pn006p']} Command: `{self.name} <{self.url}>`_" @@ -2058,7 +2194,7 @@ def py_docstring(self, custom_functions): else: items += [""] + textwrap.wrap("Default: " + self.default.to_rst()) if self.args is not None: - items += [""] + self.py_parm + items += [""] + self.py_parm(max_length, links=self._links, base_url=self._base_url, fcache=self._fcache) if custom_functions is not None and ( self.py_name in custom_functions.py_names and self.py_name in custom_functions.py_returns @@ -2301,35 +2437,6 @@ def py_notes(self): return lines - @property - def py_parm(self): - """Python parameter's string.""" - if self.arg_desc is not None: - lines = ["Parameters", "-" * 10] - for item in self.arg_desc: - if not isinstance(item, Variablelist): - if isinstance(item, Element) and "Command Default" in str(item.title): - continue - if isinstance(item, Element): - if item.tag in item_needing_all: - lines.append( - item.to_rst( - links=self._links, - base_url=self._base_url, - fcache=self._fcache, - ) - ) - elif item.tag in item_needing_links_base_url: - lines.append(item.to_rst(links=self._links, base_url=self._base_url)) - elif item.tag in item_needing_fcache: - lines.append(item.to_rst(fcache=self._fcache)) - else: - lines.append(item.to_rst()) - else: - lines.append(str(item)) - return lines - return [] - @property def url(self): """URL to the Ansys command documentation.""" @@ -2365,12 +2472,57 @@ def __repr__(self): lines.append("Function signature:") lines.append(", ".join([f"{self.name}"] + self.args)) lines.append("") - lines.append(str(self.arg_desc)) + lines.append(str(self.arg_desc)) #TO DO: modify this lines.append("") lines.append(str(self.notes)) return "\n".join(lines) + def py_parm(self, max_length=100, indent="", links=None, base_url=None, fcache=None): + """Python parameter's string.""" + lines = [] + if self.arg_desc is not None: + lines.append("Parameters") + lines.append("-" * 10) + print("COMMAND NAME : ", self.name) + for argument in self.arg_desc: + if isinstance(argument, Argument): + lines.extend(argument.to_py_docstring(max_length, indent, links, base_url)) + elif isinstance(argument, Refsynopsisdiv): + lines.extend(argument.to_rst(max_length=max_length, links=links, base_url=base_url, fcache=fcache).split("\n")) + lines.append("") + print(lines) + return lines + + # def py_parm(self, max_length=100): + # """Python parameter's string.""" + # if self.arg_desc is not None: + # lines = ["Parameters", "-" * 10] + # for item in self.arg_desc: + # if not isinstance(item, Variablelist): + # if isinstance(item, Element) and "Command Default" in str(item.title): + # continue + # if isinstance(item, Element): + # if item.tag in item_needing_all: + # rst_item = ( + # item.to_rst( + # links=self._links, + # base_url=self._base_url, + # fcache=self._fcache, + # ) + # ) + # elif item.tag in item_needing_links_base_url: + # rst_item = item.to_rst(links=self._links, base_url=self._base_url) + # elif item.tag in item_needing_fcache: + # rst_item = item.to_rst(fcache=self._fcache) + # else: + # rst_item = item.to_rst() + # else: + # rst_item = str(item) + # lines.append(rst_item) + # return lines + # return [] + def py_source(self, custom_functions=None, indent=""): """ Return the Python source. @@ -2429,11 +2581,10 @@ def to_python(self, custom_functions=None, indent=""): """ return out - class InformalTable(Element): """Provides the informal table element.""" - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" return "InformalTables need to be added." From f77b505f53828c7faa18fd42750aefd2a726f9bb Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Fri, 13 Sep 2024 18:52:05 +0200 Subject: [PATCH 02/20] feat: adding latest enhancements --- src/pyconverter/xml2py/ast_tree.py | 159 ++++++++++++++++++----------- 1 file changed, 100 insertions(+), 59 deletions(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 736adabbf..04026375a 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -736,8 +736,13 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No rst_item = item.to_rst(indent=indent) else: rst_item = str(item) - - rst_item = resize_list_text(rst_item, max_length) + + if type(item) != str and len(item.children)>1 and type(item[1]) != str: + intersection_types = set(NO_RESIZE_LIST).intersection(set(item[1].children_types)) + if len(intersection_types)==0: + rst_item = resize_list_text(rst_item, max_length) + else: + rst_item = resize_list_text(rst_item, max_length) active_items.append(rst_item) return "\n".join(active_items) + "\n" @@ -919,7 +924,7 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No class Term(Element): """Provides the term element.""" - def to_rst(self, indent="", links=None, base_url=None, fcache=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" items = [] @@ -1946,14 +1951,57 @@ class ProductName(Element): # pass # # print(resize_length(self._description.to_rst(), max_length)) +class ArgumentList: + + def __init__(self, list_entry: VarlistEntry) -> None: + + self._list_entry = list_entry + self._arguments = [] + self._parse_list_entry() + + def _parse_list_entry(self): + for item in self._list_entry: + if isinstance(item, VarlistEntry): + if len(Argument(item).multiple_args)>0: + print("IT'S TRUE : ") + print(Argument(item).multiple_args) + for arg in Argument(item).multiple_args: + self._arguments.append(arg) + else: + self._arguments.append(Argument(item)) + + @property + def arguments(self): + return self._arguments + + @arguments.setter + def arguments(self, argument): + self._arguments.append(argument) class Argument: """Argument object.""" - def __init__(self, element): - self._name = element[0] - self._description = element[1] - + def __init__(self, element:str | Element, description:Element | None=None) -> None: + if description is None: + name = element[0] + description = element[1] + else: + name = element + self._name = name + self._description = description + + @property + def multiple_args(self): + additional_args = [] + if "," in str(self._name): + for item_name in str(self._name).split(","): + print(item_name) + if item_name.strip() == "": + continue + arg_name = item_name.strip() + additional_args.append(Argument(arg_name, 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: @@ -1985,9 +2033,7 @@ def types(self) -> List[type]: terms = varlist.terms if terms: terms_numeric = [is_numeric(term) for term in terms] - if all(terms_numeric): - parm_types = [int] - elif any(terms_numeric): + if any(terms_numeric): parm_types = [int, str] else: parm_types = [str] @@ -2009,6 +2055,15 @@ def py_arg_name(self) -> str: if arg == "type": arg = "type_" + + elif "," in arg: + + for item_name in arg.split(","): + if item_name.strip() == "": + continue + arg_name = item_name.strip() + Argument(arg_name, self._description) + return f"{arg}" @@ -2019,11 +2074,11 @@ def resized_description(self, description: str|None=None, max_length: int =100, description = self._description return resize_length(description, max_length, initial_indent=indent, subsequent_indent=indent, list=True) - def to_py_docstring(self, max_length=100, indent="", links=None, base_url=None) -> List[str]: + 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.""" docstring = [f"{indent}{self.py_arg_name} : {self.str_types(" or ")}"] - rst_description = self._description.to_rst(indent=indent, max_length = max_length, links=links, base_url=base_url) + rst_description = self._description.to_rst(indent=indent, max_length = max_length, links=links, base_url=base_url, fcache=fcache) if not "* " in rst_description: list_description = self.resized_description(rst_description, max_length, indent) else: @@ -2034,8 +2089,12 @@ def to_py_docstring(self, max_length=100, indent="", links=None, base_url=None) return docstring def to_py_signature(self) -> str: - """Return the Python signature of the argument.""" - return f"{self.py_arg_name}: {self.str_types(" | ")}" + """Return the Python signature of the argument.""" + if self.py_arg_name != "--" and self.py_arg_name != "–": + kwarg = f'{self.py_arg_name}: {self.str_types(" | ")}=""' + else: + kwarg = None + return kwarg class XMLCommand(Element): @@ -2089,39 +2148,23 @@ def default(self): def arg_desc(self) -> List[Argument]: """Argument object list of the command.""" refsyn = self.rec_find("Refsynopsisdiv") - arguments = None # search by ID + arguments = [] if refsyn is None: - arguments = [] refsections = self.find_all("RefSection") for elem in refsections: if ( elem.id is not None and "argdescript" in elem.id ): - for child in elem[1]: - arguments.append(Argument(child)) - return arguments + for child in elem: + if isinstance(child, Variablelist): + arguments = ArgumentList(child).arguments + continue else: - # print("NUMBER OF CHILDREN : ", len(refsyn.children)) - # for child in refsyn.children: - # print("| TYPE : ", type(child)) - # print("| CHILD : ", child) - # print("| NUMBER OF GRANDCHILDREN : ", len(child.children)) - # for gc in child.children: - # print("|| TYPE : ", type(gc)) - # print("|| CHILD : ", gc) - # print("|| NUMBER OF GREAT GRANDCHILDREN : ", len(gc.children)) - # for ggc in gc.children: - # print("||| TYPE : ", type(ggc)) - # print("||| CHILD : ", ggc) - # print("||| NUMBER OF GREAT GREAT GRANDCHILDREN : ", len(ggc.children)) - # for gggc in ggc.children: - # print("|||| TYPE : ", type(gggc)) - # print("|||| CHILD : ", gggc) - # print("|||| NUMBER OF GREAT GREAT GREAT GRANDCHILDREN : ", len(gggc.children)) - - - arguments = [refsyn] + for elem in refsyn: + if isinstance(elem, Variablelist): + arguments = ArgumentList(elem).arguments + continue return arguments @property @@ -2173,11 +2216,15 @@ def group(self, group): """Set the group of the command.""" self._group = group - def py_signature(self, indent=""): + def py_signature(self, indent="") -> str: """Beginning of the Python command's definition.""" args = ["self"] - kwargs = [f'{arg}=""' for arg in self.py_args if "--" not in arg] - arg_sig = ", ".join(args + kwargs) + if len(self.arg_desc) > 0: + for argument in self.arg_desc: + if argument.to_py_signature() is not None: + args.append(argument.to_py_signature()) + + arg_sig = ", ".join(args) return f"{indent}def {self.py_name}({arg_sig}, **kwargs):" def py_docstring(self, custom_functions, max_length=100): @@ -2201,7 +2248,7 @@ def py_docstring(self, custom_functions, max_length=100): ): items += [""] + custom_functions.py_returns[self.py_name] if self.notes is not None: - items += [""] + self.py_notes + items += [""] + self.py_notes(max_length) if custom_functions is not None and ( self.py_name in custom_functions.py_names and self.py_name in custom_functions.py_examples @@ -2416,25 +2463,24 @@ def term_replacer(match): return docstr - @property - def py_notes(self): + def py_notes(self, max_length=100): """Python-formatted notes string.""" lines = ["Notes", "-" * 5] if self.notes.tag in item_needing_all: - lines.append( - self.notes.to_rst( + notes = self.notes.to_rst( links=self._links, base_url=self._base_url, fcache=self._fcache, - ) - ) + ) elif self.notes.tag in item_needing_links_base_url: - lines.append(self.notes.to_rst(links=self._links, base_url=self._base_url)) + notes = self.notes.to_rst(links=self._links, base_url=self._base_url) elif self.notes.tag in item_needing_fcache: - lines.append(self.notes.to_rst(links=self._links, fcache=self._fcache)) + notes = self.notes.to_rst(links=self._links, fcache=self._fcache) else: - lines.append(self.notes.to_rst()) + notes = self.notes.to_rst() + notes = resize_length(notes, 100, list=True) + lines.extend(notes) return lines @property @@ -2481,17 +2527,12 @@ def __repr__(self): def py_parm(self, max_length=100, indent="", links=None, base_url=None, fcache=None): """Python parameter's string.""" lines = [] - if self.arg_desc is not None: + if len(self.arg_desc) > 0: lines.append("Parameters") lines.append("-" * 10) - print("COMMAND NAME : ", self.name) for argument in self.arg_desc: - if isinstance(argument, Argument): - lines.extend(argument.to_py_docstring(max_length, indent, links, base_url)) - elif isinstance(argument, Refsynopsisdiv): - lines.extend(argument.to_rst(max_length=max_length, links=links, base_url=base_url, fcache=fcache).split("\n")) + lines.extend(argument.to_py_docstring(max_length, indent, links, base_url, fcache)) lines.append("") - print(lines) return lines # def py_parm(self, max_length=100): From 88e4bbe274ce1989a7652a5fcae2417cf0619cc3 Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:32:49 +0200 Subject: [PATCH 03/20] fix: most of the type hint issues --- src/pyconverter/xml2py/ast_tree.py | 164 ++++++++++++++++++++++++----- 1 file changed, 135 insertions(+), 29 deletions(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 04026375a..399ced62d 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -51,6 +51,35 @@ '``"``': "``", } +word_digit = [ + 'zero', + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', +] + +superlatif = ["st", "nd", "rd", "th"] + +superlatif_digit = [ + '', + 'first', + 'second', + 'third', + 'fourth', + 'fifth', + 'sixth', + 'seventh', + 'eighth', + 'ninth', +] + + CLEANUP = { ",, ": ", ", ", , ": ", ", @@ -86,6 +115,31 @@ def to_py_name(name, name_map=None): else: return name +def get_iter_values(name: str): + """Get the values of an iterator.""" + output = re.search(r"([a-zA-Z_]*)(\d*)", name.strip()) + groups = output.groups() + return groups[0], int(groups[1]) + +def get_quant_iter_pos(name: str) -> tuple: + """ + Get the values of a quantity iterator. + + Parameters + ---------- + name : str + Name of the parameter containing the iterator. + + Returns + ------- + tuple + Tuple containing the iteration value and the position of the iterator. + """ + output = re.search(r"(?<=\+)\d*", name.strip()) # find the number after the '+' + iter = output.group() + position = output.span() + return int(iter), position + # ############################################################################ # Element class @@ -1963,8 +2017,6 @@ def _parse_list_entry(self): for item in self._list_entry: if isinstance(item, VarlistEntry): if len(Argument(item).multiple_args)>0: - print("IT'S TRUE : ") - print(Argument(item).multiple_args) for arg in Argument(item).multiple_args: self._arguments.append(arg) else: @@ -1994,12 +2046,54 @@ def __init__(self, element:str | Element, description:Element | None=None) -> No def multiple_args(self): additional_args = [] if "," in str(self._name): - for item_name in str(self._name).split(","): - print(item_name) - if item_name.strip() == "": - continue - arg_name = item_name.strip() - additional_args.append(Argument(arg_name, self._description)) + split_name = str(self._name).split(",") + if ". . ." not in str(self._name) or "..." not in str(self._name): + for item_name in split_name: + if item_name.strip() == "": + continue + arg_name = item_name.strip() + additional_args.append(Argument(arg_name, self._description)) + else: + for i, item_name in enumerate(split_name): + item_name = item_name.strip() + if item_name == "": + continue + elif ". . ." in item_name or "..." in item_name: + if "+" in split_name[i+1]: + number_final_iter, (initial_pos_final, end_pos_final) = get_quant_iter_pos(split_name[i+1]) + if "+" in split_name[i-1]: + number_prev_iter, (initial_pos_prev, end_pos_prev) = get_quant_iter_pos(split_name[i-1]) + else : + number_prev_iter = 0 + + for j in range(number_prev_iter+1, number_final_iter): + arg_name = split_name[i+1].strip() + arg_name = f"{arg_name[:initial_pos_final]}{j}{arg_name[end_pos_final:]}" + additional_args.append(Argument(arg_name, self._description)) + else: + print(repr(split_name[i-1]), repr(split_name[i+1])) + k = i + while split_name[k-1].strip() == "" and k-1 >= 0: + k -= 1 + print(k, "SPLIT NAME : ", split_name[k-1].strip()) + if split_name[k-1].strip() == "": + raise ValueError("The argument name is not consistent.") + name_iter_prev, number_iter_prev = get_iter_values(split_name[k-1]) + name_iter_next, number_iter_next = get_iter_values(split_name[i+1]) + if name_iter_prev != name_iter_next: + print(name_iter_prev, name_iter_next) + logging.warning(f"The argument name is not consistent : {name_iter_prev} != {name_iter_next}") + logging.info("Applying the longest name for the argument list as it's probably coming from a typography.") + if len(name_iter_prev) > len(name_iter_next): + name_iter_next = name_iter_prev + else: + name_iter_prev = name_iter_next + else: + for j in range(number_iter_prev+1, number_iter_next): + arg_name = f"{name_iter_prev}{j}" + additional_args.append(Argument(arg_name, self._description)) + else: + additional_args.append(Argument(item_name, self._description)) return additional_args def rec_find(self, _type: str, terms=None) -> Element | None: @@ -2051,20 +2145,28 @@ def str_types(self, join_str: str) -> str: @property def py_arg_name(self) -> str: """Python-compatible term.""" - arg = str(self._name).lower() - + arg = str(self._name).lower().strip() + + if arg[0].isdigit(): + if arg[1].isdigit(): + raise ValueError(f"The code needs to be expanded to handle numbers") + elif arg[1:3] not in superlatif: + arg = f"{word_digit[int(arg[0])]}{arg[1:]}" + else: + arg = f"{superlatif_digit[int(arg[0])]}{arg[3:]}" + + arg = arg.replace("(" , "_").replace(")", "_").replace("+", "plus").replace("blank", "").replace("-", "_").replace("'", "") + arg = arg.strip() + + while len(arg) > 0 and arg[-1] == "_": + arg = arg[:-1] + if arg == "type": arg = "type_" - elif "," in arg: - - for item_name in arg.split(","): - if item_name.strip() == "": - continue - arg_name = item_name.strip() - Argument(arg_name, self._description) - - + elif arg == "class": + arg = "class_" + return f"{arg}" def resized_description(self, description: str|None=None, max_length: int =100, indent: str="") -> List[str]: @@ -2076,21 +2178,23 @@ def resized_description(self, description: str|None=None, max_length: int =100, 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.""" - - docstring = [f"{indent}{self.py_arg_name} : {self.str_types(" or ")}"] - rst_description = self._description.to_rst(indent=indent, max_length = max_length, links=links, base_url=base_url, fcache=fcache) - if not "* " in rst_description: - list_description = self.resized_description(rst_description, max_length, indent) + if self.py_arg_name not in ["--","–",""]: + docstring = [f"{indent}{self.py_arg_name} : {self.str_types(" or ")}"] + rst_description = self._description.to_rst(indent=indent, max_length = max_length, links=links, base_url=base_url, fcache=fcache) + if not "* " in rst_description: + list_description = self.resized_description(rst_description, max_length, indent) + else: + list_description = rst_description.split("\n") + + docstring = [f"{indent}{self.py_arg_name} : {self.str_types(" or ")}"] + docstring.extend(list_description) else: - list_description = rst_description.split("\n") - - docstring = [f"{indent}{self.py_arg_name} : {self.str_types(" or ")}"] - docstring.extend(list_description) + docstring = [] return docstring def to_py_signature(self) -> str: """Return the Python signature of the argument.""" - if self.py_arg_name != "--" and self.py_arg_name != "–": + if self.py_arg_name not in ["--","–",""]: kwarg = f'{self.py_arg_name}: {self.str_types(" | ")}=""' else: kwarg = None @@ -2158,11 +2262,13 @@ def arg_desc(self) -> List[Argument]: ): for child in elem: if isinstance(child, Variablelist): + print("COMMAND : ", self.name) arguments = ArgumentList(child).arguments continue else: for elem in refsyn: if isinstance(elem, Variablelist): + print("COMMAND : ", self.name) arguments = ArgumentList(elem).arguments continue return arguments From 47f0cfc8bc74a5f9a5345d5efca831d065add59b Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:23:04 +0200 Subject: [PATCH 04/20] feat: adapting docstring_length --- src/pyconverter/xml2py/ast_tree.py | 618 +++++++++++++++++++---------- 1 file changed, 413 insertions(+), 205 deletions(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 399ced62d..fb64ae9b1 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -22,13 +22,13 @@ import logging import textwrap +from typing import List import warnings from lxml.etree import tostring from lxml.html import fromstring from pyconverter.xml2py.utils.utils import is_numeric, split_trail_alpha import regex as re -from typing import List CONV_EQN = False @@ -52,31 +52,31 @@ } word_digit = [ - 'zero', - 'one', - 'two', - 'three', - 'four', - 'five', - 'six', - 'seven', - 'eight', - 'nine', + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", ] superlatif = ["st", "nd", "rd", "th"] superlatif_digit = [ - '', - 'first', - 'second', - 'third', - 'fourth', - 'fifth', - 'sixth', - 'seventh', - 'eighth', - 'ninth', + "", + "first", + "second", + "third", + "fourth", + "fifth", + "sixth", + "seventh", + "eighth", + "ninth", ] @@ -91,6 +91,16 @@ "`` %": "%``", # same } +PY_ARG_CLEANUP = { + "(": "_", + ")": "_", + "+": "plus", + "blank": "", + "-": "_", + "'": "", + "caret1?": "", +} + # Map XML command to pycommand function NAME_MAP_GLOB = {} @@ -98,6 +108,8 @@ SKIP = {"*IF", "*ELSE", "C***", "*RETURN"} NO_RESIZE_LIST = ["Variablelist"] + + class NameMap: def __init__(self, name_map): self.name_map = name_map @@ -115,32 +127,100 @@ def to_py_name(name, name_map=None): else: return name + def get_iter_values(name: str): """Get the values of an iterator.""" output = re.search(r"([a-zA-Z_]*)(\d*)", name.strip()) groups = output.groups() - return groups[0], int(groups[1]) + name = groups[0] + iter = groups[1] + if iter == "": + iter = 0 + return name, int(iter) + def get_quant_iter_pos(name: str) -> tuple: """ Get the values of a quantity iterator. - + Parameters ---------- name : str Name of the parameter containing the iterator. - + Returns ------- tuple Tuple containing the iteration value and the position of the iterator. """ - output = re.search(r"(?<=\+)\d*", name.strip()) # find the number after the '+' + output = re.search(r"(?<=\+)\d*", name.strip()) # find the number after the '+' iter = output.group() position = output.span() return int(iter), position +def to_py_arg_name(name: str) -> str: + """Python-compatible term""" + arg = str(name).lower().strip() + + if arg[0].isdigit(): + if arg[1].isdigit(): + raise ValueError(f"The code needs to be expanded to handle numbers") + elif arg[1:3] not in superlatif: + arg = f"{word_digit[int(arg[0])]}{arg[1:]}" + else: + arg = f"{superlatif_digit[int(arg[0])]}{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() + + while len(arg) > 0 and arg[-1] == "_": + arg = arg[:-1] + + if arg == "type": + arg = "type_" + + elif arg == "class": + arg = "class_" + + return f"{arg}" + + +def get_complete_args_from_initial_arg( + initial_args: List[str], elipsis_args: List[str] +) -> List[str]: + # elipsis_args = ['Cname1', ' Cname2',' …'] or ['Cname1', '...', 'Cname6'] + # initial_args = ['energytype', 'cname1', 'cname2', 'cname3', 'cname4', 'cname5', 'cname6'] + + first_arg_name = to_py_arg_name(elipsis_args[0]) + name_without_iter, first_num = get_iter_values(first_arg_name) + + complete_args = [] + for i, arg in enumerate(initial_args): + if name_without_iter in arg: + complete_args.append(arg) + + return complete_args + + +def is_elipsis(name: str) -> bool: + """ + Check if a name is an elipsis. + + Returns + ------- + bool + True if the argument is an elipsis, False otherwise. + """ + if any(elips in name for elips in [". . .", "...", "…"]): + return True + return False + + # ############################################################################ # Element class # ############################################################################ @@ -257,7 +337,7 @@ def id(self): """ID of the element.""" return self._element.get("id") - def to_rst(self, indent="", max_length= 100, links=None, base_url=None, fcache=None): + def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" items = [] for item in self: @@ -266,20 +346,29 @@ def to_rst(self, indent="", max_length= 100, links=None, base_url=None, fcache=N items.append( item.to_rst( indent, - max_length=max_length, + max_length=max_length, links=links, base_url=base_url, fcache=fcache, ) ) elif item.tag in item_needing_links_base_url: - items.append(item.to_rst(indent, max_length=max_length, links=links, base_url=base_url)) + items.append( + item.to_rst(indent, max_length=max_length, links=links, base_url=base_url) + ) elif item.tag in item_needing_fcache: items.append(item.to_rst(indent=indent, max_length=max_length, fcache=fcache)) else: items.append(item.to_rst(indent=indent, max_length=max_length)) else: - items.append(resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) + items.append( + resize_length( + str(item), + max_length=max_length, + initial_indent=indent, + subsequent_indent=indent, + ) + ) return " ".join(items) def rec_find(self, _type, terms=None): @@ -339,13 +428,12 @@ def tag(self): def resize_length(text, max_length=100, initial_indent="", subsequent_indent="", list=False): """Resize the length of a text.""" - + text = text.replace(" .", ".") wrapper = textwrap.TextWrapper( width=max_length, break_long_words=False, initial_indent=initial_indent, subsequent_indent=subsequent_indent, - drop_whitespace=False, ) if list is False: return wrapper.fill(text=text) @@ -387,22 +475,24 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No rst_list.append(line) for line in item_lines[1:]: text = line.to_rst(indent) if isinstance(line, Element) else str(line) - rst_list.append(textwrap.indent(text, prefix=indent + " ")) + rst_list.append(textwrap.indent(text, prefix=indent)) else: rst_list = item_lines - # rst_list = resize_length( - # rst_list, - # max_length=max_length - # initial_indent="", - # subsequent_indent="", - # ) new_rst_list = [] for line in rst_list: line = ponctuaction_whitespace(line, ".") line = ponctuaction_whitespace(line, ",") - new_rst_list.extend(resize_length(line, max_length=max_length, initial_indent=indent, subsequent_indent=indent, list=True)) + new_rst_list.extend( + resize_length( + line, + max_length=max_length, + initial_indent=indent, + subsequent_indent=indent, + list=True, + ) + ) lines.extend(new_rst_list) @@ -440,14 +530,14 @@ class OrderedList(Element): def to_rst(self, indent="", max_length=100, links=None, base_url=None): """Return a string to enable converting the element to an RST format.""" - indent += " " + # indent += " " * 4 ordered_list = [] for item in self: if item.tag in item_needing_links_base_url: rst_item = item.to_rst(indent, links=links, base_url=base_url) else: rst_item = item.to_rst(indent) - rst_item = re.sub(r"\s+", " ", rst_item) # Remove extra whitespaces + rst_item = re.sub(r"\s+", " ", rst_item.lstrip()) # Remove extra whitespaces rst_item = ponctuaction_whitespace( rst_item, "." ) # Remove extra whitespace before period @@ -477,13 +567,20 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No fcache=fcache, ) elif item.tag in item_needing_links_base_url: - rst_item = item.to_rst(indent=indent, max_length=max_length, links=links, base_url=base_url) + rst_item = item.to_rst( + indent=indent, max_length=max_length, links=links, base_url=base_url + ) elif item.tag in item_needing_fcache: rst_item = item.to_rst(indent=indent, max_length=max_length, fcache=fcache) else: rst_item = item.to_rst(indent=indent, max_length=max_length) else: - rst_item = resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent) + rst_item = resize_length( + str(item), + max_length=max_length, + initial_indent=indent, + subsequent_indent=indent, + ) items.append(rst_item) return "\n".join(items) @@ -579,13 +676,25 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No ) ) elif item.tag in item_needing_links_base_url: - items.append(item.to_rst(indent=indent, max_length=max_length, links=links, base_url=base_url)) + items.append( + item.to_rst( + indent=indent, max_length=max_length, links=links, base_url=base_url + ) + ) elif item.tag in item_needing_fcache: - items.append(item.to_rst(indent=indent, max_length=max_length, fcache=fcache)) + items.append( + item.to_rst(indent=indent, max_length=max_length, fcache=fcache) + ) else: items.append(item.to_rst(indent=indent, max_length=max_length)) else: - items.append(resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) + str_item = resize_length( + str(item), + max_length=max_length, + initial_indent=indent, + subsequent_indent=indent, + ) + items.append(str_item) rst_item = " ".join(items) + "\n" @@ -642,7 +751,14 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None): else: items.append(item.to_rst(indent)) else: - items.append(resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) + items.append( + resize_length( + str(item), + max_length=max_length, + initial_indent=indent, + subsequent_indent=indent, + ) + ) return content + " ".join(items) @@ -741,13 +857,6 @@ def to_rst(self, indent="", max_length=100): header = f"\n\n{indent}.. code::\n\n" source_code = re.sub(r"[^\S\r\n]", " ", self.source) # Remove extra whitespaces rst_item = header + textwrap.indent(source_code, prefix=indent + " " * 3) + "\n" - # rst_item = resize_length( - # rst_item, - # max_length=max_length, - # initial_indent="", - # subsequent_indent=" !", - # replace_whitespace=False - # ) return rst_item @@ -758,13 +867,15 @@ def resize_list_text(text, max_length=100): n_line = len(re.match(r"^\s*", line).group()) if line.strip() and line.strip()[0] == "*": n_line += 2 - new_text.append(resize_length(line, max_length, initial_indent="", subsequent_indent=" " * n_line)) + new_text.append( + resize_length(line, max_length, initial_indent="", subsequent_indent=" " * n_line) + ) return "\n".join(new_text) class Variablelist(Element): """Provides the variable list.""" - + def __init__(self, element): super().__init__(element) @@ -790,11 +901,12 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No rst_item = item.to_rst(indent=indent) else: rst_item = str(item) - - if type(item) != str and len(item.children)>1 and type(item[1]) != str: + + if type(item) != str and len(item.children) > 1 and type(item[1]) != str: intersection_types = set(NO_RESIZE_LIST).intersection(set(item[1].children_types)) - if len(intersection_types)==0: + if len(intersection_types) == 0: rst_item = resize_list_text(rst_item, max_length) + else: rst_item = resize_list_text(rst_item, max_length) active_items.append(rst_item) @@ -952,29 +1064,32 @@ def py_text(self, links=None, base_url=None, fcache=None): def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): """Return a string to enable converting the element to an RST format.""" - indent += " " + indent += " " * 4 # if this is a parameter arg if self.is_arg: # This is what needs to be modified in order to have the arg class lines = [f"{self.py_term(links=links, base_url=base_url)}"] text = self.py_text(links=links, base_url=base_url, fcache=fcache) - text_list = resize_length(text, max_length=max_length, initial_indent=indent, subsequent_indent=indent, list=True) + text_list = resize_length( + text, + max_length=max_length, + initial_indent=indent, + subsequent_indent=indent, + list=True, + ) lines.extend(text_list) return "\n".join(lines) - # term_text = [line.strip() for line in self.py_text.splitlines()] - # term_text = term_text[0] + '\n' + textwrap.indent('\n'.join(term_text[1:]), ) py_term = self.py_term(links=links, base_url=base_url) if "``" in py_term: py_term = py_term.replace("``", "") - lines = [ - f"* ``{py_term}`` - {self.py_text(links=links, base_url=base_url, fcache=fcache)}" - ] + lines = [f"* ``{py_term}`` - {self.py_text(links=links, base_url=base_url, fcache=fcache)}"] text = "\n".join(lines) # if 'ID number to which this tip belongs' in text: # breakpoint() return text - + + class Term(Element): """Provides the term element.""" @@ -1194,8 +1309,12 @@ class Caution(Element): def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" lines = ["", "", ".. warning::"] - indent = indent + " " - lines.append(resize_length(str(self), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) + indent = indent + " " * 4 + lines.append( + resize_length( + str(self), max_length=max_length, initial_indent=indent, subsequent_indent=indent + ) + ) return "\n".join(lines) @@ -1261,13 +1380,22 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No ) ) elif item.tag in item_needing_links_base_url: - items.append(item.to_rst(indent, max_length=max_length, links=links, base_url=base_url)) + items.append( + item.to_rst(indent, max_length=max_length, links=links, base_url=base_url) + ) elif item.tag in item_needing_fcache: items.append(item.to_rst(indent=indent, max_length=max_length, fcache=fcache)) else: items.append(item.to_rst(indent, max_length=max_length)) else: - items.append(resize_length(str(item), max_length=max_length, initial_indent=indent, subsequent_indent=indent)) + items.append( + resize_length( + str(item), + max_length=max_length, + initial_indent=indent, + subsequent_indent=indent, + ) + ) return "\n\n" + " ".join(items) + "\n\n" @@ -1808,7 +1936,7 @@ class TBody(Element): @property def rows(self): - """ "Return all the row elements found in the TBody element.""" + """Return all the row elements found in the TBody element.""" return self.find_all("Row") def to_rst(self, l_head, indent="", links=None, base_url=None): @@ -1888,7 +2016,7 @@ def to_rst_list(self, indent="", links=None, base_url=None): """Return a list to enable converting the element to an RST format.""" row = [] for entry in self.entry: - content = entry.to_rst(links=links, base_url=base_url) + content = entry.to_rst(links=links, base_url=base_url, indent="") content = content.replace("\n", "") content = content.replace("\r", "") row.append(content) @@ -1931,7 +2059,6 @@ def to_rst(self, indent="", links=None, base_url=None): ini = 0 row.append("") - for i in range(ini, len(self.rows)): rst_list = self.rows[i].to_rst_list(links=links, base_url=base_url) l_rst_list = len(rst_list) @@ -1989,113 +2116,184 @@ class ProductName(Element): pass -# class Argument: - -# def __init__(self, list_entry: VarlistEntry) -> None: - -# self._name = list_entry[0] -# self._type = "" -# self._description = list_entry[1] -# self._types = list_entry.py_term - -# print(self._types) - - -# def to_rst(self, max_length=100): -# pass -# # print(resize_length(self._description.to_rst(), max_length)) class ArgumentList: - - def __init__(self, list_entry: VarlistEntry) -> None: - + 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): - if len(Argument(item).multiple_args)>0: - for arg in Argument(item).multiple_args: - self._arguments.append(arg) + 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: - self._arguments.append(Argument(item)) - + 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, description:Element | None=None) -> None: + def __init__( + self, element: str | Element, initial_argument: List, description: Element | None = None + ) -> None: if description is None: - name = element[0] - description = element[1] + if isinstance(element[0], Term): + name = element[0] + description = element[1] + + else: + i = 0 + while not isinstance(element[i], Term) and i < len(element): + i += 1 + name = element[i] + description = element[i + 1] + else: name = element self._name = name self._description = description + self._initial_argument = initial_argument + + @property + def is_arg_elipsis(self): + """ + Check if the argument is an elipsis. + + Returns + ------- + bool + True if the argument is an elipsis, False otherwise. + """ + return is_elipsis(str(self._name)) @property def multiple_args(self): additional_args = [] if "," in str(self._name): split_name = str(self._name).split(",") - if ". . ." not in str(self._name) or "..." not in str(self._name): + if not self.is_arg_elipsis: for item_name in split_name: - if item_name.strip() == "": - continue arg_name = item_name.strip() - additional_args.append(Argument(arg_name, self._description)) + 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) else: - for i, item_name in enumerate(split_name): - item_name = item_name.strip() - if item_name == "": - continue - elif ". . ." in item_name or "..." in item_name: - if "+" in split_name[i+1]: - number_final_iter, (initial_pos_final, end_pos_final) = get_quant_iter_pos(split_name[i+1]) - if "+" in split_name[i-1]: - number_prev_iter, (initial_pos_prev, end_pos_prev) = get_quant_iter_pos(split_name[i-1]) - else : - number_prev_iter = 0 - - for j in range(number_prev_iter+1, number_final_iter): - arg_name = split_name[i+1].strip() - arg_name = f"{arg_name[:initial_pos_final]}{j}{arg_name[end_pos_final:]}" - additional_args.append(Argument(arg_name, self._description)) - else: - print(repr(split_name[i-1]), repr(split_name[i+1])) - k = i - while split_name[k-1].strip() == "" and k-1 >= 0: - k -= 1 - print(k, "SPLIT NAME : ", split_name[k-1].strip()) - if split_name[k-1].strip() == "": - raise ValueError("The argument name is not consistent.") - name_iter_prev, number_iter_prev = get_iter_values(split_name[k-1]) - name_iter_next, number_iter_next = get_iter_values(split_name[i+1]) - if name_iter_prev != name_iter_next: - print(name_iter_prev, name_iter_next) - logging.warning(f"The argument name is not consistent : {name_iter_prev} != {name_iter_next}") - logging.info("Applying the longest name for the argument list as it's probably coming from a typography.") - if len(name_iter_prev) > len(name_iter_next): - name_iter_next = name_iter_prev + + complete_args = get_complete_args_from_initial_arg( + elipsis_args=split_name, initial_args=self._initial_argument + ) + + 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) + + else: + + for i, item_name in enumerate(split_name): + item_name = item_name.strip() + if item_name == "": + continue + elif is_elipsis(item_name): + + if "+" in split_name[i + 1]: + number_final_iter, ( + initial_pos_final, + end_pos_final, + ) = get_quant_iter_pos(split_name[i + 1]) + if "+" in split_name[i - 1]: + number_prev_iter, ( + initial_pos_prev, + end_pos_prev, + ) = get_quant_iter_pos(split_name[i - 1]) else: - name_iter_prev = name_iter_next + number_prev_iter = 0 + + for j in range(number_prev_iter + 1, number_final_iter): + 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 + ) + if new_arg.py_arg_name != "": + additional_args.append(new_arg) else: - for j in range(number_iter_prev+1, number_iter_next): - arg_name = f"{name_iter_prev}{j}" - additional_args.append(Argument(arg_name, self._description)) - else: - additional_args.append(Argument(item_name, self._description)) + k = i + while split_name[k - 1].strip() == "" and k - 1 >= 0: + k -= 1 + if split_name[k - 1].strip() == "": + raise ValueError("The argument name is not consistent.") + name_iter_prev, number_iter_prev = get_iter_values( + split_name[k - 1] + ) + name_iter_next, number_iter_next = get_iter_values( + split_name[k + 1] + ) + if name_iter_prev != name_iter_next: + logging.warning( + f"The argument name is not consistent : {name_iter_prev} != {name_iter_next}" # noqa : E501 + ) + logging.info( + "Applying the longest name for the argument list as it's probably coming from a typography." # noqa : E501 + ) + if len(name_iter_prev) > len(name_iter_next): + name_iter_next = name_iter_prev + else: + name_iter_prev = name_iter_next + else: + if number_iter_next > 0: + 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 + ) + if new_arg.py_arg_name != "": + additional_args.append(new_arg) + else: + additional_args.append( + Argument( + name_iter_next, + self._initial_argument, + 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: @@ -2108,7 +2306,6 @@ def rec_find(self, _type: str, terms=None) -> Element | None: if subitem is not None: return subitem return None - @property def types(self) -> List[type]: @@ -2145,45 +2342,40 @@ def str_types(self, join_str: str) -> str: @property def py_arg_name(self) -> str: """Python-compatible term.""" - arg = str(self._name).lower().strip() - - if arg[0].isdigit(): - if arg[1].isdigit(): - raise ValueError(f"The code needs to be expanded to handle numbers") - elif arg[1:3] not in superlatif: - arg = f"{word_digit[int(arg[0])]}{arg[1:]}" - else: - arg = f"{superlatif_digit[int(arg[0])]}{arg[3:]}" - - arg = arg.replace("(" , "_").replace(")", "_").replace("+", "plus").replace("blank", "").replace("-", "_").replace("'", "") - arg = arg.strip() - - while len(arg) > 0 and arg[-1] == "_": - arg = arg[:-1] - - if arg == "type": - arg = "type_" - - elif arg == "class": - arg = "class_" - - return f"{arg}" - - def resized_description(self, description: str|None=None, max_length: int =100, indent: str="") -> List[str]: + return to_py_arg_name(self._name) + + def resized_description( + self, description: str | None = None, max_length: int = 100, indent: str = "" + ) -> List[str]: """Resize the description to a maximum length.""" - indent = " "*4 + indent if description is None: description = self._description - return resize_length(description, max_length, initial_indent=indent, subsequent_indent=indent, list=True) - def to_py_docstring(self, max_length=100, indent="", links=None, base_url=None, fcache=None) -> List[str]: + if "* " in description: + output = description.split("\n") + else: + output = resize_length( + description, max_length, initial_indent=indent, subsequent_indent=indent, list=True + ) + + return output + + 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 not in ["--", "–", ""]: docstring = [f"{indent}{self.py_arg_name} : {self.str_types(" or ")}"] - rst_description = self._description.to_rst(indent=indent, max_length = max_length, links=links, base_url=base_url, fcache=fcache) + rst_description = self._description.to_rst( + indent=indent, max_length=max_length, links=links, base_url=base_url, fcache=fcache + ) + description_indent = " " * 4 + indent if not "* " in rst_description: - list_description = self.resized_description(rst_description, max_length, indent) + list_description = self.resized_description( + rst_description, max_length, description_indent + ) else: + 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 ")}"] @@ -2193,11 +2385,11 @@ def to_py_docstring(self, max_length=100, indent="", links=None, base_url=None, return docstring def to_py_signature(self) -> str: - """Return the Python signature of the argument.""" - if self.py_arg_name not in ["--","–",""]: + """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 + kwarg = None return kwarg @@ -2253,25 +2445,31 @@ def arg_desc(self) -> List[Argument]: """Argument object list of the command.""" refsyn = self.rec_find("Refsynopsisdiv") # search by ID - arguments = [] + arguments = None if refsyn is None: refsections = self.find_all("RefSection") for elem in refsections: - if ( - elem.id is not None and "argdescript" in elem.id - ): + if elem.id is not None and "argdescript" in elem.id: for child in elem: if isinstance(child, Variablelist): - print("COMMAND : ", self.name) - arguments = ArgumentList(child).arguments - continue + if arguments is None: + arguments = ArgumentList(child, self.args) + else: + arguments += ArgumentList(child, self.args) + else: for elem in refsyn: if isinstance(elem, Variablelist): - print("COMMAND : ", self.name) - arguments = ArgumentList(elem).arguments - continue - return arguments + if arguments is None: + arguments = ArgumentList(elem, self.args) + else: + arguments += ArgumentList(elem, self.args) + + if arguments is not None: + return arguments.arguments + + else: + return [] @property def short_desc(self): @@ -2325,11 +2523,12 @@ def group(self, group): def py_signature(self, indent="") -> str: """Beginning of the Python command's definition.""" args = ["self"] - if len(self.arg_desc) > 0: - for argument in self.arg_desc: + 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()) - + arg_sig = ", ".join(args) return f"{indent}def {self.py_name}({arg_sig}, **kwargs):" @@ -2347,7 +2546,9 @@ def py_docstring(self, custom_functions, max_length=100): else: items += [""] + textwrap.wrap("Default: " + self.default.to_rst()) if self.args is not None: - items += [""] + self.py_parm(max_length, links=self._links, base_url=self._base_url, fcache=self._fcache) + items += [""] + self.py_parm( + max_length, links=self._links, base_url=self._base_url, fcache=self._fcache + ) if custom_functions is not None and ( self.py_name in custom_functions.py_names and self.py_name in custom_functions.py_returns @@ -2556,6 +2757,7 @@ def term_replacer(match): while "\n\n\n" in docstr: docstr = docstr.replace("\n\n\n", "\n\n") + docstr = re.sub(r"bgcolor=\S\S\S\S\S\S\S\S\S\S? ", "", docstr) docstr = re.sub(r"bgcolor=\S\S\S\S\S\S\S\S\S\S?", "", docstr) docstr = re.sub(r"_cellfont Shading=\S\S\S\S\S\S\S\S", "", docstr) @@ -2574,10 +2776,10 @@ def py_notes(self, max_length=100): lines = ["Notes", "-" * 5] if self.notes.tag in item_needing_all: notes = self.notes.to_rst( - links=self._links, - base_url=self._base_url, - fcache=self._fcache, - ) + links=self._links, + base_url=self._base_url, + fcache=self._fcache, + ) elif self.notes.tag in item_needing_links_base_url: notes = self.notes.to_rst(links=self._links, base_url=self._base_url) elif self.notes.tag in item_needing_fcache: @@ -2585,8 +2787,12 @@ def py_notes(self, max_length=100): else: notes = self.notes.to_rst() - notes = resize_length(notes, 100, list=True) - lines.extend(notes) + if "flat-table" not in "".join(notes) and ".. code::" not in "".join(notes): + notes = resize_length(notes, 100, list=True) + lines.extend(notes) + else: + lines.append(notes) + return lines @property @@ -2624,7 +2830,7 @@ def __repr__(self): lines.append("Function signature:") lines.append(", ".join([f"{self.name}"] + self.args)) lines.append("") - lines.append(str(self.arg_desc)) #TO DO: modify this + lines.append(str(self.arg_desc)) # TO DO: modify this lines.append("") lines.append(str(self.notes)) @@ -2633,14 +2839,15 @@ def __repr__(self): def py_parm(self, max_length=100, indent="", links=None, base_url=None, fcache=None): """Python parameter's string.""" lines = [] - if len(self.arg_desc) > 0: + arg_desc = self.arg_desc + if len(arg_desc) > 0: lines.append("Parameters") lines.append("-" * 10) - for argument in self.arg_desc: + for argument in arg_desc: lines.extend(argument.to_py_docstring(max_length, indent, links, base_url, fcache)) lines.append("") return lines - + # def py_parm(self, max_length=100): # """Python parameter's string.""" # if self.arg_desc is not None: @@ -2728,6 +2935,7 @@ def to_python(self, custom_functions=None, indent=""): """ return out + class InformalTable(Element): """Provides the informal table element.""" From 7eda7c1178be8b3d5b6de9a3ded2a5b4cce51cf1 Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:39:31 +0200 Subject: [PATCH 05/20] maint: updating cicd --- .github/workflows/ci_cd.yml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 8ca148a88..8e11befd3 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest steps: - name: PyAnsys code style checks - uses: ansys/actions/code-style@v7 + uses: ansys/actions/code-style@v8 with: python-version: ${{ env.MAIN_PYTHON_VERSION }} @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - name: PyAnsys documentation style checks - uses: ansys/actions/doc-style@v7 + uses: ansys/actions/doc-style@v8 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -60,7 +60,7 @@ jobs: steps: - name: Build wheelhouse and perform smoke test - uses: ansys/actions/build-wheelhouse@v7 + uses: ansys/actions/build-wheelhouse@v8 with: library-name: ${{ env.PACKAGE_NAME }} library-namespace: ${{ env.PACKAGE_NAMESPACE }} @@ -81,7 +81,7 @@ jobs: steps: - name: Build wheelhouse and perform smoke test - uses: ansys/actions/build-wheelhouse@v7 + uses: ansys/actions/build-wheelhouse@v8 with: library-name: ${{ env.PACKAGE_NAME }} library-namespace: ${{ env.PACKAGE_NAMESPACE }} @@ -197,7 +197,7 @@ jobs: autosummary-pyconverter-xml2py-v${{ env.RESET_AUTOSUMMARY_CACHE }}-${{ env.PYCONVERTER_VERSION }} - name: "Run Ansys documentation building action" - uses: ansys/actions/doc-build@v7 + uses: ansys/actions/doc-build@v8 with: python-version: ${{ env.MAIN_PYTHON_VERSION }} checkout: false @@ -211,7 +211,7 @@ jobs: needs: [doc-build, build-test] steps: - name: Build library source and wheel artifacts - uses: ansys/actions/build-library@v7 + uses: ansys/actions/build-library@v8 with: library-name: ${{ env.PACKAGE_NAME }} python-version: ${{ env.MAIN_PYTHON_VERSION }} @@ -225,10 +225,12 @@ jobs: needs: [package] steps: - name: "Deploy the latest documentation" - uses: ansys/actions/doc-deploy-dev@v7 + uses: ansys/actions/doc-deploy-dev@v8 with: cname: ${{ env.DOCUMENTATION_CNAME }} token: ${{ secrets.GITHUB_TOKEN }} + bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} release: @@ -238,14 +240,14 @@ jobs: runs-on: ubuntu-latest steps: - name: "Release to the public PyPI repository" - uses: ansys/actions/release-pypi-public@v7 + uses: ansys/actions/release-pypi-public@v8 with: library-name: ${{ env.PACKAGE_NAME }} twine-username: "__token__" twine-token: ${{ secrets.PYPI_TOKEN }} - name: "Release to GitHub" - uses: ansys/actions/release-github@v7 + uses: ansys/actions/release-github@v8 with: library-name: ${{ env.PACKAGE_NAME }} @@ -258,8 +260,9 @@ jobs: needs: [release] steps: - name: "Deploy the stable documentation" - uses: ansys/actions/doc-deploy-stable@v7 + uses: ansys/actions/doc-deploy-stable@v8 with: cname: ${{ env.DOCUMENTATION_CNAME }} token: ${{ secrets.GITHUB_TOKEN }} - python-version: ${{ env.MAIN_PYTHON_VERSION }} + bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} From 4c4b96f11c9b0ef4c320784ca89388949267ff15 Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:51:23 +0200 Subject: [PATCH 06/20] maint: updating cicd - 2 --- .github/workflows/ci_cd.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 8e11befd3..141d97e6a 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -14,13 +14,13 @@ on: - main env: - MAIN_PYTHON_VERSION: '3.10' + MAIN_PYTHON_VERSION: '3.12' PACKAGE_NAME: 'pyconverter-xml2py' PACKAGE_NAMESPACE: 'pyconverter.xml2py' DOCUMENTATION_CNAME: 'pyconverter-xml2py.docs.pyansys.com' - RESET_PIP_CACHE: 2 - RESET_AUTOSUMMARY_CACHE: 2 - RESET_DOC_BUILD_CACHE: 2 + RESET_PIP_CACHE: 0 + RESET_AUTOSUMMARY_CACHE: 0 + RESET_DOC_BUILD_CACHE: 0 ON_CI: true concurrency: @@ -48,7 +48,6 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} - smoke-tests: name: Build and Smoke tests (Linux) runs-on: ubuntu-latest From a7aed2fee2a68cd26112d0e93a51bc0b2bf7cdeb Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:55:12 +0200 Subject: [PATCH 07/20] fix: ``to_py_docstring`` --- src/pyconverter/xml2py/ast_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index fb64ae9b1..bdb2e39e6 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -2365,7 +2365,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} : {self.str_types(" or ")}'] rst_description = self._description.to_rst( indent=indent, max_length=max_length, links=links, base_url=base_url, fcache=fcache ) From 913f673e71ea16eca54984a8c4fd361aa5b416ab Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:56:37 +0200 Subject: [PATCH 08/20] fix: resetting cache --- .github/workflows/ci_cd.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 141d97e6a..8bb7936e7 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -18,9 +18,9 @@ env: PACKAGE_NAME: 'pyconverter-xml2py' PACKAGE_NAMESPACE: 'pyconverter.xml2py' DOCUMENTATION_CNAME: 'pyconverter-xml2py.docs.pyansys.com' - RESET_PIP_CACHE: 0 - RESET_AUTOSUMMARY_CACHE: 0 - RESET_DOC_BUILD_CACHE: 0 + RESET_PIP_CACHE: 10 + RESET_AUTOSUMMARY_CACHE: 10 + RESET_DOC_BUILD_CACHE: 10 ON_CI: true concurrency: From 78c66f41295066cdc514a874a8d07422b3c16038 Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:58:25 +0200 Subject: [PATCH 09/20] fix: ``to_py_docstring`` - 2 --- src/pyconverter/xml2py/ast_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index bdb2e39e6..0606fb790 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -2378,7 +2378,7 @@ 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} : {self.str_types(" or ")}'] docstring.extend(list_description) else: docstring = [] From c93272a696eca3b3c3af0a4fbe87bf1d253c78eb Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:17:04 +0200 Subject: [PATCH 10/20] fix: tests and missing arguments --- src/pyconverter/xml2py/ast_tree.py | 61 ++++++++++++++++++------------ tests/test_writer.py | 2 +- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 0606fb790..1739bea09 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -145,7 +145,7 @@ def get_quant_iter_pos(name: str) -> tuple: Parameters ---------- - name : str + name: str Name of the parameter containing the iterator. Returns @@ -1000,7 +1000,7 @@ def py_term(self, links=None, base_url=None): if self.parm_types is not None: ptype_str = " or ".join([parm_type.__name__ for parm_type in self.parm_types]) - return f"{arg} : {ptype_str}" + return f"{arg}: {ptype_str}" return f"{arg}" if self.term.tag in item_needing_links_base_url: @@ -2166,7 +2166,7 @@ class Argument: """Argument object.""" def __init__( - self, element: str | Element, initial_argument: List, description: Element | None = None + self, element: str | Element, initial_argument: List, description: Element | str | None = None ) -> None: if description is None: if isinstance(element[0], Term): @@ -2265,7 +2265,7 @@ def multiple_args(self): ) if name_iter_prev != name_iter_next: logging.warning( - f"The argument name is not consistent : {name_iter_prev} != {name_iter_next}" # noqa : E501 + f"The argument name is not consistent: {name_iter_prev} != {name_iter_next}" # noqa : E501 ) logging.info( "Applying the longest name for the argument list as it's probably coming from a typography." # noqa : E501 @@ -2317,20 +2317,21 @@ def types(self) -> List[type]: This is either a string, float, or integer (or some combination thereof). """ - varlist = self._description.rec_find("Variablelist") - parm_types = [str] - if varlist is not None: - terms = varlist.terms - if terms: - terms_numeric = [is_numeric(term) for term in terms] - if any(terms_numeric): - parm_types = [int, str] - else: - parm_types = [str] + if isinstance(self._description, Element): + varlist = self._description.rec_find("Variablelist") + + if varlist is not None: + terms = varlist.terms + if terms: + terms_numeric = [is_numeric(term) for term in terms] + if any(terms_numeric): + parm_types = [int, str] + else: + parm_types = [str] - # consider checking for bool - # terms_numeric = set(terms) == set(['1', '0']) + # consider checking for bool + # terms_numeric = set(terms) == set(['1', '0']) return parm_types @@ -2365,10 +2366,13 @@ 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 ")}'] - rst_description = self._description.to_rst( - indent=indent, max_length=max_length, links=links, base_url=base_url, fcache=fcache - ) + docstring = [f'{indent}{self.py_arg_name}: {self.str_types(" or ")}'] + if isinstance(self._description, str): + rst_description = self._description + else: + rst_description = self._description.to_rst( + indent=indent, max_length=max_length, links=links, base_url=base_url, fcache=fcache + ) description_indent = " " * 4 + indent if not "* " in rst_description: list_description = self.resized_description( @@ -2378,7 +2382,7 @@ 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}: {self.str_types(" or ")}'] docstring.extend(list_description) else: docstring = [] @@ -2464,8 +2468,15 @@ def arg_desc(self) -> List[Argument]: arguments = ArgumentList(elem, self.args) else: arguments += ArgumentList(elem, self.args) - + 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) + return arguments.arguments else: @@ -2883,7 +2894,7 @@ def py_source(self, custom_functions=None, indent=""): Parameters ---------- - custom_functions : CustomFunctions, optional + custom_functions: CustomFunctions, optional Custom functions to add to the command. The default is ``None``. """ if custom_functions is None or self.py_name not in custom_functions.py_names: @@ -2905,9 +2916,9 @@ def to_python(self, custom_functions=None, indent=""): Parameters ---------- - custom_functions : CustomFunctions, optional + custom_functions: CustomFunctions, optional Custom functions to add to the command. The default is ``None``. - indent : str, optional + indent: str, optional Indentation of the Python function. The default is ``""``. Returns diff --git a/tests/test_writer.py b/tests/test_writer.py index b5e69dcfc..e152eac22 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -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="", lab="", x1="", y1="", x2="", y2="", **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 From 93e7c0e46ef911e90c826272e1aeeba161b28f47 Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:17:55 +0200 Subject: [PATCH 11/20] docs: using ``: `` syntax instead of `` : `` --- doc/source/user_guide/source_code.rst | 12 ++--- src/pyconverter/xml2py/ast_tree.py | 15 ++++-- src/pyconverter/xml2py/custom_functions.py | 2 +- src/pyconverter/xml2py/directory_format.py | 10 ++-- src/pyconverter/xml2py/load_xml_doc.py | 22 ++++----- src/pyconverter/xml2py/utils/utils.py | 22 ++++----- src/pyconverter/xml2py/writer.py | 56 +++++++++++----------- 7 files changed, 73 insertions(+), 66 deletions(-) diff --git a/doc/source/user_guide/source_code.rst b/doc/source/user_guide/source_code.rst index 5959c0e29..9ae9e3931 100644 --- a/doc/source/user_guide/source_code.rst +++ b/doc/source/user_guide/source_code.rst @@ -146,21 +146,21 @@ into account and added to the Python docstring: Parameters ---------- - nl1 : str + nl1: str Number of the first line to be hit (touched by the end of the new line). If negative, assume ``P1`` (see below) is the second keypoint of the line instead of the first - nl2 : str + nl2: str Number of the second line to be hit. If negative, assume ``P3`` is the second keypoint of the line instead of the first. - ang1 : str + ang1: str Angle of intersection (usually zero or 180) of generated line with tangent to first line. - ang2 : str + ang2: str Angle of intersection (usually zero or 180) of generated line with tangent to second line. - phit1 : str + phit1: str Number to be assigned to keypoint generated at hit location on first line (defaults to lowest available keypoint number ( :ref:`numstr` )). - phit2 : str + phit2: str Number to be assigned to keypoint generated at hit location on second line (defaults to lowest available keypoint number ( :ref:`numstr` )). Returns diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 1739bea09..0aacdf500 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -2166,7 +2166,10 @@ class Argument: """Argument object.""" def __init__( - self, element: str | Element, initial_argument: List, description: Element | str | None = None + self, + element: str | Element, + initial_argument: List, + description: Element | str | None = None, ) -> None: if description is None: if isinstance(element[0], Term): @@ -2371,7 +2374,11 @@ def to_py_docstring( rst_description = self._description else: rst_description = self._description.to_rst( - indent=indent, max_length=max_length, links=links, base_url=base_url, fcache=fcache + indent=indent, + max_length=max_length, + links=links, + base_url=base_url, + fcache=fcache, ) description_indent = " " * 4 + indent if not "* " in rst_description: @@ -2468,7 +2475,7 @@ def arg_desc(self) -> List[Argument]: arguments = ArgumentList(elem, self.args) else: arguments += ArgumentList(elem, self.args) - + if arguments is not None: if len(arguments.py_arg_names) < len(arguments.initial_args): for arg in arguments.initial_args: @@ -2476,7 +2483,7 @@ def arg_desc(self) -> List[Argument]: new_arg = Argument(arg, arguments.initial_args, "") if new_arg.py_arg_name != "": arguments.arguments.append(new_arg) - + return arguments.arguments else: diff --git a/src/pyconverter/xml2py/custom_functions.py b/src/pyconverter/xml2py/custom_functions.py index d3322f3a1..c255582e5 100644 --- a/src/pyconverter/xml2py/custom_functions.py +++ b/src/pyconverter/xml2py/custom_functions.py @@ -31,7 +31,7 @@ def get_docstring_lists(filename: str) -> Tuple[list[str], list[str], list[str], Parameters ---------- - filename : str + filename: str Path containing the Python file. Returns diff --git a/src/pyconverter/xml2py/directory_format.py b/src/pyconverter/xml2py/directory_format.py index fc152e79d..30adc3841 100644 --- a/src/pyconverter/xml2py/directory_format.py +++ b/src/pyconverter/xml2py/directory_format.py @@ -61,18 +61,18 @@ def get_paths( Parameters ---------- - path : Path + path: Path Path object of the directory with the predefined format. - graph_path : Path, optional + graph_path: Path, optional Path object of the directory containing the graphics. The default is ``None``, in which case the XML predefined directory format is used. - link_path : Path, optional + link_path: Path, optional Path object of the directory containing the links. The default is ``None``, in which case the XML predefined directory format is used. - term_path : Path, optional + term_path: Path, optional Path object of the directory containing the terms. The default is ``None``, in which case the XML predefined directory format is used. - xml_path : Path, optional + xml_path: Path, optional Path object of the directory containing the XML files. The default is ``None``, in which case the XML predefined directory format is used. diff --git a/src/pyconverter/xml2py/load_xml_doc.py b/src/pyconverter/xml2py/load_xml_doc.py index b4a6b72d7..da55a2646 100644 --- a/src/pyconverter/xml2py/load_xml_doc.py +++ b/src/pyconverter/xml2py/load_xml_doc.py @@ -37,7 +37,7 @@ def load_links(link_path: Path) -> dict: Parameters ---------- - link_path : Path + link_path: Path Path to the links directory. Returns @@ -85,7 +85,7 @@ def load_fcache(graph_path: Path) -> dict: Parameters ---------- - graph_path : Path + graph_path: Path Path object of the graphic directory. Returns @@ -109,7 +109,7 @@ def load_docu_global(term_path: Path) -> dict: Parameters ---------- - term_path : Path + term_path: Path Path object of the terms directory. Returns @@ -160,24 +160,24 @@ def load_terms( Parameters ---------- - term_path : Path + term_path: Path Path object of the terms directory. - docu_global : dict + docu_global: dict Dictionary containing the entity names from the documentation and their path. - links : dict + links: dict Dictionary containing the link names and the needed information to render the links. - fcache : dict + fcache: dict Dictionary containing the base names of the graphics and their path. - variable_file : str, optional + variable_file: str, optional Name of the file containing the variable terms to import. The default value is ``"build_variables.ent"``. - global_terms_file : str, optional + global_terms_file: str, optional Name of the file containing the global terms to import. The default is ``"terms_global.ent"``. - manual_file : str, optional + manual_file: str, optional Name of the file containing the manual entities to import. The default is ``"manuals.ent"``. - character_directory : str, optional + character_directory: str, optional Name of the directory containg the entities for the special characters. The default is ``"ent"``. diff --git a/src/pyconverter/xml2py/utils/utils.py b/src/pyconverter/xml2py/utils/utils.py index ccc835caa..4ef35a269 100644 --- a/src/pyconverter/xml2py/utils/utils.py +++ b/src/pyconverter/xml2py/utils/utils.py @@ -33,7 +33,7 @@ def parse_yaml(yaml_path: Path) -> dict: Parameters ---------- - yaml_path : Path + yaml_path: Path Path object of the YAML file. Returns @@ -55,9 +55,9 @@ def get_config_data_value(yaml_path: Path, value: str) -> Union[str, dict, list, Parameters ---------- - yaml_path : Path + yaml_path: Path Path object of the YAML file. - value : str + value: str Key to search for in the YAML file. """ config_data = parse_yaml(yaml_path) @@ -70,9 +70,9 @@ def create_name_map(meta_command: list[str], yaml_file_path: Path) -> dict: Parameters ---------- - meta_command : list[str] + meta_command: list[str] List of command names. - yaml_file_path : Path + yaml_file_path: Path Path object of the YAML file. Returns @@ -141,11 +141,11 @@ def import_handler( Parameters ---------- - filename : Path + filename: Path Path object of the Python file. - additional_content : str + additional_content: str Additional content to add to the Python file. - str_before_def : str + str_before_def: str String before the function definition. """ @@ -177,7 +177,7 @@ def split_trail_alpha(text: str) -> Tuple[str, str]: Parameters ---------- - text : str + text: str String to split. """ for ii, char in enumerate(text): @@ -195,7 +195,7 @@ def is_numeric(text: str) -> bool: Parameters ---------- - text : str + text: str String to check. Returns @@ -216,7 +216,7 @@ def get_refentry(filename: Path) -> list: Parameters ---------- - filename : Path + filename: Path Path object of an XML file. """ root = fromstring(open(filename, "rb").read()) diff --git a/src/pyconverter/xml2py/writer.py b/src/pyconverter/xml2py/writer.py index 73c371704..823929cf1 100644 --- a/src/pyconverter/xml2py/writer.py +++ b/src/pyconverter/xml2py/writer.py @@ -59,7 +59,7 @@ def convert(directory_path): Parameters ---------- - directory_path : Path + directory_path: Path Path to the directory containing the XML files to convert. Returns @@ -84,7 +84,7 @@ def load_commands( Parameters ---------- - xml_path : Path + xml_path: Path Path object of the directory containing the XML files to convert. Examples @@ -159,7 +159,7 @@ def load_commands( name_map = create_name_map(meta_command, Path("config.yaml")) ast.NameMap(name_map) - # TODO : accept conversion of a single command + # TODO: accept conversion of a single command # convert a single command # if command is not None: @@ -181,13 +181,13 @@ def copy_template_package(template_path: Path, new_package_path: Path, clean: bo Parameters ---------- - template_path : Path + template_path: Path Path object containing the directory to copy. - new_package_path : Path + new_package_path: Path Path object containing the directory where the new files and directorys are to be added. - clean : bool, optional + clean: bool, optional Whether the directories in the path for the new package must be cleared before adding new files. The default value is ``False``. @@ -219,7 +219,7 @@ def write_global__init__file(library_path: Path) -> None: Parameters ---------- - library_path : Path + library_path: Path Path object of the directory containing the generated package. """ mod_file = library_path / "__init__.py" @@ -245,7 +245,7 @@ def write__init__file(library_path: Path) -> None: Parameters ---------- - library_path : Path + library_path: Path Path object of the directory containing the generated package. """ @@ -273,9 +273,9 @@ def get_library_path(new_package_path: Path, config_path: Path) -> Path: Parameters ---------- - new_package_path : Path + new_package_path: Path Path objecy of the new package directory. - config_path : str + config_path: str Path to the configuration file. Returns @@ -296,9 +296,9 @@ def get_module_info(library_path: Path, command: ast.XMLCommand) -> Tuple[str, s Parameters ---------- - library_path : Path + library_path: Path Path object to the library directory. - command : ast.XMLCommand + command: ast.XMLCommand Command object. Returns @@ -323,9 +323,9 @@ def get_class_info(initial_class_name: str, module_path: Path) -> Tuple[str, str Parameters ---------- - initial_class_name : str + initial_class_name: str Initial class name. - module_path : Path + module_path: Path Path object of the module directory. Returns @@ -360,28 +360,28 @@ def write_source( Parameters ---------- - command_map : dict + command_map: dict Dictionary with the following format: ``{"initial_command_name": command_obj}``. - name_map : dict + name_map: dict Dictionary with the following format: ``{"initial_command_name": "python_name"}``. - xml_doc_path : Path + xml_doc_path: Path Path object containing the XML directory to convert. - target_path : Path + target_path: Path Path object to generate the new package to. - path_custom_functions : Path, optional + path_custom_functions: Path, optional Path object containing the customized functions. The default value is ``None``. - template_path : Path, optional + template_path: Path, optional Path object of the template to use. If no path is provided, the default template is used. - config_path : Path, optional + config_path: Path, optional Path object of the configuration file. The default value is ``Path(config.yaml)``.`. - clean : bool, optional + clean: bool, optional Whether the directories in the new package path must be cleared before adding new files. The default value is ``True``. - structured : bool, optional + structured: bool, optional Whether the package should be structured. The default value is ``True``. - check_structure_map : bool, optional + check_structure_map: bool, optional Whether the structure map must be checked. The default value is ``False``. - check_files : bool, optional + check_files: bool, optional Whether the files must be checked. The default value is ``False``. Returns @@ -518,12 +518,12 @@ def write_docs( Parameters ---------- - package_path : Path + package_path: Path Path object of the new package folder. - package_structure : dict, optional + package_structure: dict, optional Dictionary with the following format: ``{'python_module_name': [{'python_class_name': python_names_list}]}``. - config_path : Path, optional + config_path: Path, optional Path object of the configuration file. The default value is ``Path(config.yaml)``. Returns From 15dcba3b10e525c624c2ae5c2874b8936833c3cf Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:30:59 +0200 Subject: [PATCH 12/20] fix: ``max_length`` argument in ``Math`` class --- src/pyconverter/xml2py/ast_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 0aacdf500..eb1439a63 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -1178,7 +1178,7 @@ def equation(self): class Math(_Math): """Provides the math element.""" - def to_rst(self, indent=""): + def to_rst(self, indent="", max_length=100): """Return a string to enable converting the element to an RST format.""" lines = ["", "", f"{indent}.. math::\n"] lines.append(textwrap.indent(self.equation, prefix=indent + " " * 4)) From 73c0fc6bf36ffe6d4d4178913dfb18cba6abb461 Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:24:23 +0200 Subject: [PATCH 13/20] fix: removing dead code --- src/pyconverter/xml2py/ast_tree.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index eb1439a63..57b661fbc 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -2866,35 +2866,6 @@ def py_parm(self, max_length=100, indent="", links=None, base_url=None, fcache=N lines.append("") return lines - # def py_parm(self, max_length=100): - # """Python parameter's string.""" - # if self.arg_desc is not None: - # lines = ["Parameters", "-" * 10] - # for item in self.arg_desc: - # if not isinstance(item, Variablelist): - # if isinstance(item, Element) and "Command Default" in str(item.title): - # continue - # if isinstance(item, Element): - # if item.tag in item_needing_all: - # rst_item = ( - # item.to_rst( - # links=self._links, - # base_url=self._base_url, - # fcache=self._fcache, - # ) - # ) - # elif item.tag in item_needing_links_base_url: - # rst_item = item.to_rst(links=self._links, base_url=self._base_url) - # elif item.tag in item_needing_fcache: - # rst_item = item.to_rst(fcache=self._fcache) - # else: - # rst_item = item.to_rst() - # else: - # rst_item = str(item) - # lines.append(rst_item) - # return lines - # return [] - def py_source(self, custom_functions=None, indent=""): """ Return the Python source. From 9d313e5259f225c5db595ec544734f1d38a7876a Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:41:04 +0200 Subject: [PATCH 14/20] feat: using ``"num2words`` and fixing ``command =`` output --- pyproject.toml | 3 ++ src/pyconverter/xml2py/ast_tree.py | 52 ++++++++++-------------------- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8850e3a3a..ba1238641 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "importlib-metadata>=4.0", "pygithub>=1.59.1", "lxml>=4.9.3", + "num2words>=0.5.0", "numpy>=1.14.0,<1.25.0; python_version<'3.9'", "numpy>=1.14.0; python_version>='3.9'", "py-asciimath==0.3.0", @@ -46,6 +47,7 @@ tests = [ "click==8.1.7", "pygithub==2.4.0", "lxml==5.3.0", + "num2words==0.5.13", "numpy==2.1.2", "parse==1.20.2", "pytest==8.3.2", @@ -60,6 +62,7 @@ doc = [ "build>= 0.10.0", "flit>=3.8.0", "jupyter_sphinx==0.5.3", + "num2words==0.5.13", "numpy==2.1.2", "numpydoc==1.8.0", "pandas==2.2.3", diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 57b661fbc..68f2ec4ec 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -22,6 +22,7 @@ import logging import textwrap +from num2words import num2words as num from typing import List import warnings @@ -51,34 +52,8 @@ '``"``': "``", } -word_digit = [ - "zero", - "one", - "two", - "three", - "four", - "five", - "six", - "seven", - "eight", - "nine", -] - superlatif = ["st", "nd", "rd", "th"] -superlatif_digit = [ - "", - "first", - "second", - "third", - "fourth", - "fifth", - "sixth", - "seventh", - "eighth", - "ninth", -] - CLEANUP = { ",, ": ", ", @@ -167,9 +142,10 @@ def to_py_arg_name(name: str) -> str: if arg[1].isdigit(): raise ValueError(f"The code needs to be expanded to handle numbers") elif arg[1:3] not in superlatif: - arg = f"{word_digit[int(arg[0])]}{arg[1:]}" + arg = f"{num(arg[0])}{arg[1:]}" else: - arg = f"{superlatif_digit[int(arg[0])]}{arg[3:]}" + arg = f"{num(arg[0], to="ordinal")}{arg[3:]}" + print(arg) if ("," in arg and "--" in arg) or arg == "–": return "" @@ -2189,6 +2165,11 @@ def __init__( self._description = description self._initial_argument = initial_argument + @property + def py_arg_name(self) -> str: + """Python-compatible term.""" + return to_py_arg_name(self._name) + @property def is_arg_elipsis(self): """ @@ -2343,11 +2324,6 @@ def str_types(self, join_str: str) -> str: ptype_str = join_str.join([parm_type.__name__ for parm_type in self.types]) return ptype_str - @property - def py_arg_name(self) -> str: - """Python-compatible term.""" - return to_py_arg_name(self._name) - def resized_description( self, description: str | None = None, max_length: int = 100, indent: str = "" ) -> List[str]: @@ -2877,8 +2853,14 @@ def py_source(self, custom_functions=None, indent=""): """ if custom_functions is None or self.py_name not in custom_functions.py_names: - if len(self.py_args) > 0: - command = 'command = f"' + self.name + ",{" + "},{".join(self.py_args) + '}"\n' + if len(self.arg_desc) > 0: + command = 'command = f"' + self.name + for arg in self.arg_desc: + 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" From 5370ad96d577aa0f68ea47f3a8f648c0ece7b14d Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:45:58 +0200 Subject: [PATCH 15/20] fix: reformatting the code for ``pre-commit`` --- src/pyconverter/xml2py/ast_tree.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 68f2ec4ec..dba5deae6 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -22,12 +22,12 @@ import logging import textwrap -from num2words import num2words as num from typing import List import warnings from lxml.etree import tostring from lxml.html import fromstring +from num2words import num2words as num from pyconverter.xml2py.utils.utils import is_numeric, split_trail_alpha import regex as re @@ -142,9 +142,11 @@ def to_py_arg_name(name: str) -> str: if arg[1].isdigit(): raise ValueError(f"The code needs to be expanded to handle numbers") elif arg[1:3] not in superlatif: - arg = f"{num(arg[0])}{arg[1:]}" + num_value = num(arg[0]) + arg = f"{num_value}{arg[1:]}" else: - arg = f"{num(arg[0], to="ordinal")}{arg[3:]}" + num_value = num(arg[0], to="ordinal") + arg = f"{num_value}{arg[3:]}" print(arg) if ("," in arg and "--" in arg) or arg == "–": @@ -2854,11 +2856,11 @@ def py_source(self, custom_functions=None, indent=""): if custom_functions is None or self.py_name not in custom_functions.py_names: if len(self.arg_desc) > 0: - command = 'command = f"' + self.name + command = 'command = f"' + self.name for arg in self.arg_desc: - command += ',{' + command += ",{" command += arg.py_arg_name - command += '}' + command += "}" command += '"\n' # ",{" + "},{".join(self.arg_desc.py_arg_name) + '}"\n' else: From 266c3facb28f752eb686766b023e1074e517ab3f Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:42:27 +0200 Subject: [PATCH 16/20] fix: fixing some indent errors --- src/pyconverter/xml2py/ast_tree.py | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index dba5deae6..2447bfe3d 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -147,7 +147,6 @@ def to_py_arg_name(name: str) -> str: else: num_value = num(arg[0], to="ordinal") arg = f"{num_value}{arg[3:]}" - print(arg) if ("," in arg and "--" in arg) or arg == "–": return "" @@ -675,6 +674,11 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No items.append(str_item) rst_item = " ".join(items) + "\n" + + if "Terminates the analysis if the" in rst_item: + print("RST in Paragraph : ") + print(rst_item) + print(type(item)) return rst_item @@ -837,19 +841,11 @@ def to_rst(self, indent="", max_length=100): rst_item = header + textwrap.indent(source_code, prefix=indent + " " * 3) + "\n" return rst_item - -def resize_list_text(text, max_length=100): - lines = text.split("\n") - new_text = [] - for line in lines: - n_line = len(re.match(r"^\s*", line).group()) - if line.strip() and line.strip()[0] == "*": - n_line += 2 - new_text.append( - resize_length(line, max_length, initial_indent="", subsequent_indent=" " * n_line) - ) - return "\n".join(new_text) - +def resize_element_list(text, max_length=100): + element_list = re.finditer(r"^\* ", text) + subsequent_indent = " " * 2 + element_list =resize_length(text, max_length, initial_indent="", subsequent_indent=subsequent_indent) + return element_list class Variablelist(Element): """Provides the variable list.""" @@ -883,10 +879,10 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No if type(item) != str and len(item.children) > 1 and type(item[1]) != str: intersection_types = set(NO_RESIZE_LIST).intersection(set(item[1].children_types)) if len(intersection_types) == 0: - rst_item = resize_list_text(rst_item, max_length) + rst_item = resize_element_list(rst_item, max_length) else: - rst_item = resize_list_text(rst_item, max_length) + rst_item = resize_element_list(rst_item, max_length) active_items.append(rst_item) return "\n".join(active_items) + "\n" @@ -1038,6 +1034,10 @@ def py_text(self, links=None, base_url=None, fcache=None): if "GUI" not in sentence: valid.append(sentence) rst = ". ".join(valid) + if "Terminates the analysis if the" in rst: + print("RST : ") + print(rst) + return rst def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): @@ -2657,10 +2657,10 @@ def term_replacer(match): is_dash_sign = False i = 0 while i < len(lines): - if lines[i].lstrip().startswith("-"): + if lines[i].lstrip().startswith("--"): if is_dash_sign == False: is_dash_sign = True - elif lines[i].lstrip().startswith("="): + elif lines[i].lstrip().startswith("=="): if is_equal_sign or is_dash_sign: lines[i - 1] = "**" + lines[i - 1] + "**" lines.pop(i) From b4fba3005b19662ab0579147ca85e744e1f8897b Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:09:36 +0200 Subject: [PATCH 17/20] fix: using ``inflect`` instead of ``num2words`` and fixing parameter type rendering --- pyproject.toml | 5 +++-- src/pyconverter/xml2py/ast_tree.py | 26 +++++++++----------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d2d9ec025..6dc8de630 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ dependencies = [ "click>=7.0,<9.0.0", "importlib-metadata>=4.0", "pygithub>=1.59.1", + "inflect>=7.0.0", "lxml>=4.9.3", "num2words>=0.5.0", "numpy>=1.14.0,<1.25.0; python_version<'3.9'", @@ -45,9 +46,9 @@ dependencies = [ tests = [ "black>=24.2.0", "click==8.1.7", + "inflect==7.4.0", "pygithub==2.4.0", "lxml==5.3.0", - "num2words==0.5.13", "numpy==2.1.2", "parse==1.20.2", "pytest==8.3.3", @@ -61,8 +62,8 @@ doc = [ "ansys-sphinx-theme[autoapi]==1.1.5", "build>= 0.10.0", "flit>=3.8.0", + "inflect==7.4.0", "jupyter_sphinx==0.5.3", - "num2words==0.5.13", "numpy==2.1.2", "numpydoc==1.8.0", "pandas==2.2.3", diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 2447bfe3d..09a9878c4 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -27,7 +27,7 @@ from lxml.etree import tostring from lxml.html import fromstring -from num2words import num2words as num +from inflect import engine from pyconverter.xml2py.utils.utils import is_numeric, split_trail_alpha import regex as re @@ -137,17 +137,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() if arg[0].isdigit(): if arg[1].isdigit(): raise ValueError(f"The code needs to be expanded to handle numbers") elif arg[1:3] not in superlatif: - num_value = num(arg[0]) + num_value = p.number_to_words(arg[0]) arg = f"{num_value}{arg[1:]}" else: - num_value = num(arg[0], to="ordinal") + num_value = p.number_to_words(arg[:3]) arg = f"{num_value}{arg[3:]}" - + if ("," in arg and "--" in arg) or arg == "–": return "" @@ -674,11 +674,6 @@ def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=No items.append(str_item) rst_item = " ".join(items) + "\n" - - if "Terminates the analysis if the" in rst_item: - print("RST in Paragraph : ") - print(rst_item) - print(type(item)) return rst_item @@ -974,7 +969,7 @@ def py_term(self, links=None, base_url=None): if self.parm_types is not None: ptype_str = " or ".join([parm_type.__name__ for parm_type in self.parm_types]) - return f"{arg}: {ptype_str}" + return f"{arg} : {ptype_str}" return f"{arg}" if self.term.tag in item_needing_links_base_url: @@ -1034,9 +1029,6 @@ def py_text(self, links=None, base_url=None, fcache=None): if "GUI" not in sentence: valid.append(sentence) rst = ". ".join(valid) - if "Terminates the analysis if the" in rst: - print("RST : ") - print(rst) return rst @@ -2347,7 +2339,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} : {self.str_types(" or ")}'] if isinstance(self._description, str): rst_description = self._description else: @@ -2367,7 +2359,7 @@ 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} : {self.str_types(" or ")}'] docstring.extend(list_description) else: docstring = [] @@ -2376,7 +2368,7 @@ def to_py_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(" | ")}=""' + kwarg = f'{self.py_arg_name} : {self.str_types(" | ")}=""' else: kwarg = None return kwarg From 603f52d6acde4135bb184e634e5b1510aa28f13c Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:17:46 +0200 Subject: [PATCH 18/20] fix: pre-commit --- src/pyconverter/xml2py/ast_tree.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index 09a9878c4..cdb20e39f 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -25,9 +25,9 @@ from typing import List import warnings +from inflect import engine from lxml.etree import tostring from lxml.html import fromstring -from inflect import engine from pyconverter.xml2py.utils.utils import is_numeric, split_trail_alpha import regex as re @@ -147,7 +147,7 @@ def to_py_arg_name(name: str) -> str: else: num_value = p.number_to_words(arg[:3]) arg = f"{num_value}{arg[3:]}" - + if ("," in arg and "--" in arg) or arg == "–": return "" @@ -836,12 +836,16 @@ def to_rst(self, indent="", max_length=100): rst_item = header + textwrap.indent(source_code, prefix=indent + " " * 3) + "\n" return rst_item + def resize_element_list(text, max_length=100): element_list = re.finditer(r"^\* ", text) subsequent_indent = " " * 2 - element_list =resize_length(text, max_length, initial_indent="", subsequent_indent=subsequent_indent) + element_list = resize_length( + text, max_length, initial_indent="", subsequent_indent=subsequent_indent + ) return element_list + class Variablelist(Element): """Provides the variable list.""" @@ -1029,7 +1033,7 @@ def py_text(self, links=None, base_url=None, fcache=None): if "GUI" not in sentence: valid.append(sentence) rst = ". ".join(valid) - + return rst def to_rst(self, indent="", max_length=100, links=None, base_url=None, fcache=None): From 63435a66586c49e1b496db86f813d10019c40722 Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:32:04 +0200 Subject: [PATCH 19/20] fix: `to_py_signature`` --- src/pyconverter/xml2py/ast_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyconverter/xml2py/ast_tree.py b/src/pyconverter/xml2py/ast_tree.py index cdb20e39f..6c9fe3083 100644 --- a/src/pyconverter/xml2py/ast_tree.py +++ b/src/pyconverter/xml2py/ast_tree.py @@ -2372,7 +2372,7 @@ def to_py_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(" | ")}=""' + kwarg = f'{self.py_arg_name}: {self.str_types(" | ")}=""' else: kwarg = None return kwarg From 44afa1a7770fba7a85027948638e8c92347cd3d0 Mon Sep 17 00:00:00 2001 From: Camille <78221213+clatapie@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:45:23 +0200 Subject: [PATCH 20/20] fix: removing ``num`words`` as a dependency --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6dc8de630..55907a91f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,6 @@ dependencies = [ "pygithub>=1.59.1", "inflect>=7.0.0", "lxml>=4.9.3", - "num2words>=0.5.0", "numpy>=1.14.0,<1.25.0; python_version<'3.9'", "numpy>=1.14.0; python_version>='3.9'", "py-asciimath==0.3.0",