mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
Fix https://github.com/IfcOpenShell/IfcOpenShell/issues/1663 : Extract Docs is bundled with IfcPatch module
This commit is contained in:
@@ -1,101 +0,0 @@
|
||||
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import typing
|
||||
import inspect
|
||||
import collections
|
||||
import importlib
|
||||
from types import ModuleType
|
||||
|
||||
def extract_docs(
|
||||
module: ModuleType,
|
||||
submodule_name: str,
|
||||
cls_name: str,
|
||||
method_name: str,
|
||||
boilerplate_args : typing.Iterable[str]=None):
|
||||
"""Extract class docstrings and method arguments
|
||||
|
||||
:param module: Parent module from which to extract the submodule class
|
||||
:param submodule_name: Submodule from which to extract the class
|
||||
:param cls_name: Class from which to extract the docstring and method arguments
|
||||
:param method_name: Class Method name from which to extract arguments
|
||||
:param boilerplate_args: String iterable containing arguments that shall not be parsed
|
||||
"""
|
||||
spec = importlib.util.spec_from_file_location(submodule_name, f"{module.__path__[0]}/recipes/{submodule_name}.py")
|
||||
submodule = importlib.util.module_from_spec(spec)
|
||||
try:
|
||||
spec.loader.exec_module(submodule)
|
||||
try:
|
||||
return _extract_docs(getattr(submodule, cls_name), method_name, boilerplate_args)
|
||||
except AttributeError as e:
|
||||
print(e)
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Error : IFCPatch {str(submodule)} could not complete because : {str(e)}")
|
||||
except:
|
||||
print(f"Error : IFCPatch {str(submodule)} could not load")
|
||||
|
||||
def _extract_docs(cls, method_name, boilerplate_args):
|
||||
inputs = collections.OrderedDict()
|
||||
method = getattr(cls, method_name)
|
||||
node_data = {"class": cls}
|
||||
|
||||
signature = inspect.signature(method)
|
||||
for name, parameter in signature.parameters.items():
|
||||
if name == "self":
|
||||
continue
|
||||
inputs[name] = {"name": name}
|
||||
if isinstance(parameter.default, (str, float, int, bool)):
|
||||
inputs[name]["default"] = parameter.default
|
||||
|
||||
type_hints = typing.get_type_hints(method)
|
||||
for name, socket_data in inputs.items():
|
||||
type_hint = type_hints.get(name, None)
|
||||
if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??)
|
||||
continue
|
||||
if isinstance(type_hint, typing._UnionGenericAlias):
|
||||
inputs[name]["type"] = [t.__name__ for t in typing.get_args(type_hint)]
|
||||
else:
|
||||
inputs[name]["type"] = type_hint.__name__
|
||||
|
||||
description = ""
|
||||
doc = method.__doc__
|
||||
if doc is not None:
|
||||
for i, line in enumerate(doc.split("\n")):
|
||||
line = line.strip()
|
||||
if i == 0:
|
||||
node_data["name"] = line
|
||||
elif line.startswith(":return:"):
|
||||
node_data["output"] = {"name": line.split(":")[2].strip(), "description": line.split(":")[3].strip()}
|
||||
elif line.startswith(":param"):
|
||||
param_name = line.split(":")[1].strip().replace("param ", "")
|
||||
if param_name in inputs:
|
||||
inputs[param_name]["description"] = line.split(":")[2].strip()
|
||||
elif i == 2:
|
||||
description += line
|
||||
elif i > 2:
|
||||
description += "\n" + line
|
||||
|
||||
node_data["description"] = description.strip()
|
||||
node_data["inputs"] = inputs
|
||||
|
||||
if boilerplate_args is not None:
|
||||
for arg in boilerplate_args: # Remove boilerplate arguments
|
||||
node_data["inputs"].pop(arg, None)
|
||||
return node_data
|
||||
|
||||
@@ -26,8 +26,6 @@ try:
|
||||
except:
|
||||
print("IfcPatch not available")
|
||||
|
||||
from .helper import extract_docs
|
||||
|
||||
|
||||
class SelectIfcPatchInput(bpy.types.Operator):
|
||||
bl_idname = "bim.select_ifc_patch_input"
|
||||
@@ -102,7 +100,7 @@ class UpdateIfcPatchArguments(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
patch_args = context.scene.BIMPatchProperties.ifc_patch_args_attr
|
||||
patch_args.clear()
|
||||
docs = extract_docs(ifcpatch, self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args"))
|
||||
docs = ifcpatch.extract_docs(self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args"))
|
||||
if docs and "inputs" in docs:
|
||||
inputs = docs["inputs"]
|
||||
for arg_name in inputs:
|
||||
|
||||
@@ -33,8 +33,6 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from .helper import extract_docs
|
||||
from .operator import UpdateIfcPatchArguments
|
||||
|
||||
|
||||
ifcpatchrecipes_enum = []
|
||||
@@ -54,7 +52,7 @@ def getIfcPatchRecipes(self, context):
|
||||
f = str(filename.stem)
|
||||
if f == "__init__":
|
||||
continue
|
||||
docs = extract_docs(ifcpatch, f, "Patcher", "__init__", ("src", "file", "logger", "args"))
|
||||
docs = ifcpatch.extract_docs(f, "Patcher", "__init__", ("src", "file", "logger", "args"))
|
||||
ifcpatchrecipes_enum.append((f, f, docs.get("description","") if docs else ""))
|
||||
return ifcpatchrecipes_enum
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
|
||||
import ifcopenshell
|
||||
import logging
|
||||
import os
|
||||
import typing
|
||||
import inspect
|
||||
import collections
|
||||
import importlib
|
||||
|
||||
|
||||
def execute(args, is_library=None):
|
||||
@@ -51,3 +56,78 @@ def execute(args, is_library=None):
|
||||
else:
|
||||
ifc_file.write(args["output"])
|
||||
print("# All tasks are complete :-)")
|
||||
|
||||
|
||||
def extract_docs(
|
||||
submodule_name: str,
|
||||
cls_name: str,
|
||||
method_name: str="__init__",
|
||||
boilerplate_args : typing.Iterable[str]=None):
|
||||
"""Extract class docstrings and method arguments
|
||||
|
||||
:param module: Parent module from which to extract the submodule class
|
||||
:param submodule_name: Submodule from which to extract the class
|
||||
:param cls_name: Class from which to extract the docstring and method arguments
|
||||
:param method_name: Class Method name from which to extract arguments
|
||||
:param boilerplate_args: String iterable containing arguments that shall not be parsed
|
||||
"""
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
submodule_name,
|
||||
f"{os.path.dirname(inspect.getabsfile(inspect.currentframe()))}/recipes/{submodule_name}.py")
|
||||
submodule = importlib.util.module_from_spec(spec)
|
||||
try:
|
||||
spec.loader.exec_module(submodule)
|
||||
try:
|
||||
return _extract_docs(getattr(submodule, cls_name), method_name, boilerplate_args)
|
||||
except AttributeError as e:
|
||||
print(e)
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Error : IFCPatch {str(submodule)} could not load because : {str(e)}")
|
||||
|
||||
|
||||
def _extract_docs(cls, method_name, boilerplate_args):
|
||||
inputs = collections.OrderedDict()
|
||||
method = getattr(cls, method_name)
|
||||
docs = {"class": cls}
|
||||
if boilerplate_args is None:
|
||||
boilerplate_args = []
|
||||
|
||||
signature = inspect.signature(method)
|
||||
for name, parameter in signature.parameters.items():
|
||||
if name == "self" or name in boilerplate_args:
|
||||
continue
|
||||
inputs[name] = {"name": name}
|
||||
if isinstance(parameter.default, (str, float, int, bool)):
|
||||
inputs[name]["default"] = parameter.default
|
||||
|
||||
type_hints = typing.get_type_hints(method)
|
||||
for input_name in inputs.keys():
|
||||
type_hint = type_hints.get(input_name, None)
|
||||
if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??)
|
||||
continue
|
||||
if isinstance(type_hint, typing._UnionGenericAlias):
|
||||
inputs[input_name]["type"] = [t.__name__ for t in typing.get_args(type_hint)]
|
||||
else:
|
||||
inputs[input_name]["type"] = type_hint.__name__
|
||||
|
||||
description = ""
|
||||
doc = method.__doc__
|
||||
if doc is not None:
|
||||
for i, line in enumerate(doc.split("\n")):
|
||||
line = line.strip()
|
||||
if i == 0:
|
||||
docs["name"] = line
|
||||
elif line.startswith(":return:"):
|
||||
docs["output"] = {"name": line.split(":")[2].strip(), "description": line.split(":")[3].strip()}
|
||||
elif line.startswith(":param"):
|
||||
param_name = line.split(":")[1].strip().replace("param ", "")
|
||||
if param_name in inputs:
|
||||
inputs[param_name]["description"] = line.split(":")[2].strip()
|
||||
elif i == 2:
|
||||
description += line
|
||||
elif i > 2:
|
||||
description += "\n" + line
|
||||
|
||||
docs["description"] = description.strip()
|
||||
docs["inputs"] = inputs
|
||||
return docs
|
||||
|
||||
Reference in New Issue
Block a user