From a363ad2d95c3bd3b065533b7a0c0017b02f400bc Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Sun, 22 Sep 2024 13:26:35 +0000 Subject: [PATCH] #12 Remove info_helper --- .flake8 | 8 ++- cppwg/generators.py | 49 +++++++------ cppwg/input/class_info.py | 83 ++++++++++++++++++++- cppwg/input/free_function_info.py | 3 +- cppwg/input/info_helper.py | 115 ------------------------------ cppwg/input/module_info.py | 8 +++ 6 files changed, 126 insertions(+), 140 deletions(-) delete mode 100644 cppwg/input/info_helper.py diff --git a/.flake8 b/.flake8 index 21f7699..95b9d39 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,8 @@ [flake8] max_line_length = 120 -exclude= + +exclude = + __pycache__, .git, .github, build, @@ -9,4 +11,8 @@ exclude= cppwg/templates, tests, venv, + docstring-convention=numpy + +# D200 One-line docstring should fit on one line with quotes +extend-ignore = D200 diff --git a/cppwg/generators.py b/cppwg/generators.py index 7ce416a..0191df3 100644 --- a/cppwg/generators.py +++ b/cppwg/generators.py @@ -11,7 +11,6 @@ import pygccxml -from cppwg.input.info_helper import CppInfoHelper from cppwg.input.package_info import PackageInfo from cppwg.parsers.package_info_parser import PackageInfoParser from cppwg.parsers.source_parser import CppSourceParser @@ -196,19 +195,10 @@ def collect_source_hpp_files(self) -> None: logger.error(f"No header files found in source root: {self.source_root}") raise FileNotFoundError() - def extract_templates_from_source(self) -> None: - """Extract template arguments for each class from the associated source file.""" - for module_info in self.package_info.module_info_collection: - info_helper = CppInfoHelper(module_info) - for class_info in module_info.class_info_collection: - # Skip excluded classes - if class_info.excluded: - continue - info_helper.extract_templates_from_source(class_info) - class_info.update_names() - def log_unknown_classes(self) -> None: - """Get unwrapped classes.""" + """ + Log unwrapped classes. + """ logger = logging.getLogger() all_class_decls = self.source_ns.classes(allow_empty=True) @@ -277,7 +267,9 @@ def parse_header_collection(self) -> None: self.source_ns = source_parser.parse() def parse_package_info(self) -> None: - """Parse the package info file to create a PackageInfo object.""" + """ + Parse the package info file to create a PackageInfo object. + """ if self.package_info_path: # If a package info file exists, parse it to create a PackageInfo object info_parser = PackageInfoParser(self.package_info_path, self.source_root) @@ -288,12 +280,23 @@ def parse_package_info(self) -> None: self.package_info = PackageInfo("cppwg_package", self.source_root) def update_from_ns(self) -> None: - """Update modules with information from the source namespace.""" + """ + Update modules with information from the parsed source namespace. + """ for module_info in self.package_info.module_info_collection: module_info.update_from_ns(self.source_ns) + def update_from_source(self) -> None: + """ + Update modules with information from the source headers. + """ + for module_info in self.package_info.module_info_collection: + module_info.update_from_source() + def write_header_collection(self) -> None: - """Write the header collection to file.""" + """ + Write the header collection to file. + """ header_collection_writer = CppHeaderCollectionWriter( self.package_info, self.wrapper_root, @@ -302,7 +305,9 @@ def write_header_collection(self) -> None: header_collection_writer.write() def write_wrappers(self) -> None: - """Write all the wrappers required for the package.""" + """ + Write all the wrappers required for the package. + """ for module_info in self.package_info.module_info_collection: module_writer = CppModuleWrapperWriter( module_info, @@ -312,7 +317,9 @@ def write_wrappers(self) -> None: module_writer.write() def generate_wrapper(self) -> None: - """Parse input yaml and C++ source to generate Python wrappers.""" + """ + Parse input yaml and C++ source to generate Python wrappers. + """ # Parse the input yaml for package, module, and class information self.parse_package_info() @@ -322,8 +329,8 @@ def generate_wrapper(self) -> None: # Map each class to a header file self.map_classes_to_hpp_files() - # Attempt to extract templates for each class from the source files - self.extract_templates_from_source() + # Update modules with information from the source headers + self.update_from_source() # Write the header collection to file self.write_header_collection() @@ -331,7 +338,7 @@ def generate_wrapper(self) -> None: # Parse the headers with pygccxml and castxml self.parse_header_collection() - # Update modules with information from the source namespace + # Update modules with information from the parsed source namespace self.update_from_ns() # Log list of unknown classes in the source root diff --git a/cppwg/input/class_info.py b/cppwg/input/class_info.py index 358a1f8..07b30c2 100644 --- a/cppwg/input/class_info.py +++ b/cppwg/input/class_info.py @@ -8,6 +8,7 @@ from pygccxml.declarations.runtime_errors import declaration_not_found_t from cppwg.input.cpp_type_info import CppTypeInfo +from cppwg.utils import utils class CppClassInfo(CppTypeInfo): @@ -32,6 +33,69 @@ def __init__(self, name: str, class_config: Optional[Dict[str, Any]] = None): self.py_names: List[str] = None self.base_decls: Optional[List["declaration_t"]] = None # noqa: F821 + def extract_templates_from_source(self) -> None: + """ + Extract template args from the associated source file. + + Search the source file for a class signature matching one of the + template signatures defined in `template_substitutions`. If a match + is found, set the corresponding template arg replacements for the class. + """ + # Skip if there are template args attached directly to the class + if self.template_arg_lists: + return + + # Skip if there is no source file + source_path = self.source_file_full_path + if not source_path: + return + + # Get list of template substitutions applicable to this class + # e.g. [ {"signature":"", "replacement":[[2,2], [3,3]]} ] + substitutions = self.hierarchy_attribute_gather("template_substitutions") + + # Skip if there are no applicable template substitutions + if not substitutions: + return + + source = utils.read_source_file( + source_path, + strip_comments=True, + strip_preprocessor=True, + strip_whitespace=True, + ) + + # Search for template signatures in the source file + for substitution in substitutions: + # Signature e.g. + signature = substitution["signature"].strip() + + class_list = utils.find_classes_in_source( + source, + class_name=self.name, + template_signature=signature, + ) + + if class_list: + self.template_signature = signature + + # Replacement e.g. [[2,2], [3,3]] + self.template_arg_lists = substitution["replacement"] + + # Extract parameters ["A", "B"] from "" + self.template_params = [] + for part in signature.split(","): + param = ( + part.strip() + .replace("<", "") + .replace(">", "") + .split(" ")[1] + .split("=")[0] + .strip() + ) + self.template_params.append(param) + break + def is_child_of(self, other: "ClassInfo") -> bool: # noqa: F821 """ Check if the class is a child of the specified class. @@ -151,6 +215,17 @@ def update_from_ns(self, source_ns: "namespace_t") -> None: # noqa: F821 base.related_class for decl in self.decls for base in decl.bases ] + def update_from_source(self) -> None: + """ + Update class with information from the source headers. + """ + # Skip excluded classes + if self.excluded: + return + + self.extract_templates_from_source() + self.update_names() + def update_py_names(self) -> None: """ Set the Python names for the class, accounting for template args. @@ -243,11 +318,15 @@ def update_cpp_names(self) -> None: self.cpp_names.append(self.name + template_string) def update_names(self) -> None: - """Update the C++ and Python names for the class.""" + """ + Update the C++ and Python names for the class. + """ self.update_cpp_names() self.update_py_names() @property def parent(self) -> "ModuleInfo": # noqa: F821 - """Returns the parent module info object.""" + """ + Returns the parent module info object. + """ return self.module_info diff --git a/cppwg/input/free_function_info.py b/cppwg/input/free_function_info.py index 89e3e72..5f69134 100644 --- a/cppwg/input/free_function_info.py +++ b/cppwg/input/free_function_info.py @@ -30,4 +30,5 @@ def update_from_ns(self, source_ns: "namespace_t") -> None: # noqa: F821 source_ns : pygccxml.declarations.namespace_t The source namespace """ - self.decls = source_ns.free_functions(self.name, allow_empty=True)[0] + ff_decls = source_ns.free_functions(self.name, allow_empty=True) + self.decls = [ff_decls[0]] diff --git a/cppwg/input/info_helper.py b/cppwg/input/info_helper.py deleted file mode 100644 index 71b00bc..0000000 --- a/cppwg/input/info_helper.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Helper utilities for info structures.""" - -import logging -import os -from typing import Any, Dict, List - -from cppwg.input.base_info import BaseInfo -from cppwg.input.class_info import CppClassInfo -from cppwg.input.module_info import ModuleInfo -from cppwg.utils import utils - - -class CppInfoHelper: - """ - Adds information extracted from C++ source code to info objects. - - Helper class that attempts to automatically fill in extra feature - information based on simple analysis of the source tree. - - __________ - Attributes - __________ - module_info : ModuleInfo - The module info object that this helper is working with. - class_dict : dict - A dictionary of class info objects keyed by class name. - """ - - def __init__(self, module_info: ModuleInfo): - - self.module_info: ModuleInfo = module_info - - # For convenience, collect class info in a dict keyed by name - self.class_dict: Dict[str, CppClassInfo] = { - class_info.name: class_info - for class_info in module_info.class_info_collection - } - - def extract_templates_from_source(self, feature_info: BaseInfo) -> None: - """ - Get template args from the source file associated with an info object. - - __________ - Parameters - __________ - feature_info : BaseInfo - The feature info object to expand. - """ - logger = logging.getLogger() - - if not isinstance(feature_info, CppClassInfo): - logger.error(f"Unsupported feature type: {type(feature_info)}") - raise TypeError() - - # Skip if there are pre-defined template args - if feature_info.template_arg_lists: - return - - # Skip if there is no source file - source_path = feature_info.source_file_full_path - if not source_path: - return - - if not os.path.isfile(source_path): - logger.error(f"Could not find source file: {source_path}") - raise FileNotFoundError() - - # Get list of template substitutions from this feature and its parents - # e.g. {"signature":"","replacement":[[2,2], [3,3]]} - template_substitutions: List[Dict[str, Any]] = ( - feature_info.hierarchy_attribute_gather("template_substitutions") - ) - - # Skip if there are no template substitutions - if len(template_substitutions) == 0: - return - - source = utils.read_source_file( - source_path, - strip_comments=True, - strip_preprocessor=True, - strip_whitespace=True, - ) - - # Search for template signatures in the source file - for template_substitution in template_substitutions: - # Signature e.g. - signature = template_substitution["signature"].strip() - - class_list = utils.find_classes_in_source( - source, - class_name=feature_info.name, - template_signature=signature, - ) - - if class_list: - # Replacement e.g. [[2,2], [3,3]] - replacement = template_substitution["replacement"] - - feature_info.template_arg_lists = replacement - feature_info.template_signature = signature - - # Extract ["DIM_A", "DIM_B"] from "" - template_params = [] - for tp in signature.split(","): - template_params.append( - tp.strip() - .replace("<", "") - .replace(">", "") - .split(" ")[1] - .split("=")[0] - .strip() - ) - feature_info.template_params = template_params - break diff --git a/cppwg/input/module_info.py b/cppwg/input/module_info.py index f1f0195..4867b4c 100644 --- a/cppwg/input/module_info.py +++ b/cppwg/input/module_info.py @@ -161,3 +161,11 @@ def update_from_ns(self, source_ns: "namespace_t") -> None: # noqa: F821 # Update free functions with information from source namespace. for ff_info in self.free_function_info_collection: ff_info.update_from_ns(source_ns) + + def update_from_source(self) -> None: + """ + Update module with information from the source headers. + + """ + for class_info in self.class_info_collection: + class_info.update_from_source()