From f430d005b2b549c51c4ae5ae224677d72b391d5f Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Sat, 28 Sep 2024 12:49:00 +0000 Subject: [PATCH 1/5] #13 Improve yaml path parsing --- cppwg/info/base_info.py | 38 +++---- cppwg/info/module_info.py | 5 +- cppwg/parsers/package_info_parser.py | 115 +++++++++++++++++++++- cppwg/utils/constants.py | 4 +- cppwg/utils/utils.py | 65 ++++++------ cppwg/writers/class_writer.py | 4 + examples/shapes/wrapper/package_info.yaml | 7 +- 7 files changed, 166 insertions(+), 72 deletions(-) diff --git a/cppwg/info/base_info.py b/cppwg/info/base_info.py index dac9197..cd6999b 100644 --- a/cppwg/info/base_info.py +++ b/cppwg/info/base_info.py @@ -7,9 +7,6 @@ from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional -import cppwg.templates.custom as cppwg_custom -from cppwg.utils.constants import CPPWG_SOURCEROOT_STRING - class BaseInfo(ABC): """ @@ -47,7 +44,7 @@ class BaseInfo(ABC): pointer_call_policy : str The default pointer call policy. prefix_code : List[str] - Any wrapper code that precedes the feature. + Custom wrapper code that comes before the auto-generated feature code. prefix_text : str Text to add at the top of all wrappers. reference_call_policy : str @@ -60,6 +57,8 @@ class BaseInfo(ABC): A list of source files to be included with the feature. source_root : str The root directory of the C++ source code. + suffix_code : List[str] + Custom wrapper code that comes after the auto-generated feature code. template_substitutions : Dict[str, List[Any]] A list of template substitution sequences. @@ -123,7 +122,7 @@ def __init__(self, name: str, info_config: Optional[Dict[str, Any]] = None) -> N self.prefix_text: str = "" self.custom_generator: str = "" - self.custom_generator_instance: cppwg_custom.Custom = None + self.custom_generator_instance: "templates.custom.Custom" = None # noqa: F821 if info_config: for key in [ @@ -145,6 +144,7 @@ def __init__(self, name: str, info_config: Optional[Dict[str, Any]] = None) -> N "smart_ptr_type", "source_includes", "source_root", + "suffix_code", "template_substitutions", ]: if key in info_config: @@ -176,36 +176,20 @@ def load_custom_generator(self) -> None: return logger = logging.getLogger() - - # Replace the `CPPWG_SOURCEROOT` placeholder in the custom generator - # path if needed. For example, a custom generator might be specified - # as `custom_generator: CPPWG_SOURCEROOT/path/to/CustomGenerator.py` - filepath = self.custom_generator.replace( - CPPWG_SOURCEROOT_STRING, self.source_root - ) - filepath = os.path.abspath(filepath) - - # Verify that the custom generator file exists - if not os.path.isfile(filepath): - logger.error( - f"Could not find specified custom generator for {self.name}: {filepath}" - ) - raise FileNotFoundError() - - logger.info(f"Custom generator for {self.name}: {filepath}") + logger.info(f"Custom generator for {self.name}: {self.custom_generator}") # Load the custom generator as a module - module_name = os.path.splitext(filepath)[0] # /path/to/CustomGenerator - class_name = os.path.basename(module_name) # CustomGenerator + location = os.path.splitext(self.custom_generator)[0] # /path/to/FooGen + class_name = os.path.basename(location) # FooGen - spec = importlib.util.spec_from_file_location(module_name, filepath) + spec = importlib.util.spec_from_file_location(location, self.custom_generator) module = importlib.util.module_from_spec(spec) - sys.modules[module_name] = module + sys.modules[location] = module # location is the module name spec.loader.exec_module(module) # Get the custom generator class from the loaded module. # Note: The custom generator class name must match the filename. - CustomGeneratorClass: cppwg_custom.Custom = getattr(module, class_name) + CustomGeneratorClass = getattr(module, class_name) # Instantiate the custom generator from the provided class self.custom_generator_instance = CustomGeneratorClass() diff --git a/cppwg/info/module_info.py b/cppwg/info/module_info.py index 3060886..552ac28 100644 --- a/cppwg/info/module_info.py +++ b/cppwg/info/module_info.py @@ -123,9 +123,8 @@ def is_decl_in_source_path(self, decl: "declaration_t") -> bool: # noqa: F821 if not self.source_locations: return True - for source_location in self.source_locations: - full_path = os.path.join(self.package_info.source_root, source_location) - if Path(full_path) in Path(decl.location.file_name).parents: + for location in self.source_locations: + if Path(location) in Path(decl.location.file_name).parents: return True return False diff --git a/cppwg/parsers/package_info_parser.py b/cppwg/parsers/package_info_parser.py index 38b6dfc..0fa71ac 100644 --- a/cppwg/parsers/package_info_parser.py +++ b/cppwg/parsers/package_info_parser.py @@ -1,6 +1,7 @@ """Parser for input yaml.""" import logging +import os from typing import Any, Dict import yaml @@ -11,6 +12,7 @@ from cppwg.info.package_info import PackageInfo from cppwg.info.variable_info import CppVariableInfo from cppwg.utils import utils +from cppwg.utils.constants import CPPWG_SOURCEROOT_STRING class PackageInfoParser: @@ -68,6 +70,7 @@ def parse(self) -> PackageInfo: "smart_ptr_type": "", "source_includes": [], "source_root": self.source_root, + "suffix_code": [], "template_substitutions": [], } @@ -85,8 +88,15 @@ def parse(self) -> PackageInfo: package_config[key] = raw_package_info[key] # Replace boolean strings with booleans - utils.substitute_bool_for_string(package_config, "common_include_file") - utils.substitute_bool_for_string(package_config, "exclude_default_args") + package_config["common_include_file"] = utils.convert_to_bool( + package_config["common_include_file"] + ) + package_config["exclude_default_args"] = utils.convert_to_bool( + package_config["exclude_default_args"] + ) + + # Convert custom generator path to a full path + self.convert_custom_generator(package_config) # Create the PackageInfo object from the package config dict package_info = PackageInfo(package_config["name"], package_config) @@ -96,7 +106,7 @@ def parse(self) -> PackageInfo: # Get module config from the raw module info module_config = { "name": "cppwg_module", - "source_locations": "", + "source_locations": [], "use_all_classes": False, "use_all_free_functions": False, "use_all_variables": False, @@ -110,6 +120,18 @@ def parse(self) -> PackageInfo: if key in raw_module_info: module_config[key] = raw_module_info[key] + # Convert source locations to full paths + if module_config["source_locations"]: + locations = [] + for location in module_config["source_locations"]: + locations.append(self.full_path(location)) + self.verify_path(locations[-1]) + module_config["source_locations"] = locations + + # Convert custom generator path to a full path + self.convert_custom_generator(module_config) + + # Convert boolean options module_config["use_all_classes"] = utils.is_option_ALL( module_config["classes"] ) @@ -146,6 +168,15 @@ def parse(self) -> PackageInfo: if key in raw_class_info: class_config[key] = raw_class_info[key] + # Convert source file path to a full path + class_config["source_file_path"] = self.full_path( + class_config["source_file_path"] + ) + self.verify_path(class_config["source_file_path"]) + + # Convert custom generator path to a full path + self.convert_custom_generator(class_config) + # Create the CppClassInfo object from the class config dict class_info = CppClassInfo(raw_class_info["name"], class_config) @@ -170,6 +201,15 @@ def parse(self) -> PackageInfo: if key in raw_free_function_info: free_function_config[key] = raw_free_function_info[key] + # Convert source file path to a full path + free_function_config["source_file_path"] = self.full_path( + free_function_config["source_file_path"] + ) + self.verify_path(free_function_config["source_file_path"]) + + # Convert custom generator path to a full path + self.convert_custom_generator(free_function_config) + # Create the CppFreeFunctionInfo object from the free function config dict free_function_info = CppFreeFunctionInfo( free_function_config["name"], free_function_config @@ -194,6 +234,15 @@ def parse(self) -> PackageInfo: if key in raw_variable_info: variable_config[key] = raw_variable_info[key] + # Convert source file path to a full path + variable_config["source_file_path"] = self.full_path( + variable_config["source_file_path"] + ) + self.verify_path(variable_config["source_file_path"]) + + # Convert custom generator path to a full path + self.convert_custom_generator(variable_config) + # Create the CppVariableInfo object from the variable config dict variable_info = CppVariableInfo( variable_config["name"], variable_config @@ -203,3 +252,63 @@ def parse(self) -> PackageInfo: module_info.add_variable(variable_info) return package_info + + def convert_custom_generator(self, config: Dict[str, Any]) -> None: + """ + Convert the custom generator path to a full path if set in the config. + + Parameters + ---------- + config: Dict[str, Any] + The config dictionary. + """ + if not config["custom_generator"]: + return + + config["custom_generator"] = self.convert_path(config["custom_generator"]) + self.verify_path(config["custom_generator"]) + + def convert_path(self, raw_path: str) -> str: + """ + Convert a path which has a CPPWG_SOURCEROOT_STRING placeholder. + + Parameters + ---------- + raw_path: str + The path to convert. + """ + if not raw_path: + return "" + path = raw_path.replace(CPPWG_SOURCEROOT_STRING, self.source_root) + return os.path.abspath(path) + + def full_path(self, relative_path: str) -> str: + """ + Get the full path for a path specified relative to the source root. + + Parameters + ---------- + relative_path: str + The path relative to the source root. + """ + if not relative_path: + return "" + path = os.path.join(self.source_root, relative_path) + return os.path.abspath(path) + + def verify_path(self, path: str) -> None: + """ + Verify that the path exists if set. + + Parameters + ---------- + path: str + The path. + """ + if not path: + return + + logger = logging.getLogger() + if not os.path.exists(path): + logger.error(f"Could not find {path}") + raise FileNotFoundError() diff --git a/cppwg/utils/constants.py b/cppwg/utils/constants.py index 0c729bb..14ecc24 100644 --- a/cppwg/utils/constants.py +++ b/cppwg/utils/constants.py @@ -6,8 +6,8 @@ CPPWG_EXT = "cppwg" CPPWG_HEADER_COLLECTION_FILENAME = f"wrapper_header_collection.{CPPWG_EXT}.hpp" -CPPWG_TRUE_STRINGS = ["ON", "YES", "Y", "TRUE", "T"] -CPPWG_FALSE_STRINGS = ["OFF", "NO", "N", "FALSE", "F"] +CPPWG_TRUE_STRINGS = ["ON", "YES", "Y", "TRUE", "T", "1"] +CPPWG_FALSE_STRINGS = ["OFF", "NO", "N", "FALSE", "F", "0", ""] CPPWG_DEFAULT_WRAPPER_DIR = "cppwg_wrappers" diff --git a/cppwg/utils/utils.py b/cppwg/utils/utils.py index 7c7666d..4b48b62 100644 --- a/cppwg/utils/utils.py +++ b/cppwg/utils/utils.py @@ -1,16 +1,37 @@ """Utility functions for the cppwg package.""" import re -from typing import Any, Dict, List, Tuple +from typing import Any, List, Tuple -from cppwg.utils.constants import ( - CPPWG_ALL_STRING, - CPPWG_FALSE_STRINGS, - CPPWG_TRUE_STRINGS, -) +from cppwg.utils.constants import CPPWG_ALL_STRING, CPPWG_TRUE_STRINGS -def is_option_ALL(input_obj: Any, option_ALL_string: str = CPPWG_ALL_STRING) -> bool: +def convert_to_bool(value: Any) -> bool: + """ + Convert value to a boolean. + + Parameters + ---------- + value: Any + The value to convert. + + Returns + ------- + bool + True if value is any of the true strings e.g. "YES", "ON"... + False if value is a string but does not match any true string. + bool(value) if value is not a string. + """ + if isinstance(value, str): + caps_string = value.strip().upper() + if caps_string in CPPWG_TRUE_STRINGS: + return True + return False + + return bool(value) + + +def is_option_ALL(input_obj: Any) -> bool: """ Check if the input is a string that matches the "ALL" indicator e.g. "CPPWG_ALL". @@ -18,15 +39,13 @@ def is_option_ALL(input_obj: Any, option_ALL_string: str = CPPWG_ALL_STRING) -> ---------- input_obj : Any The object to check - option_ALL_string : str - The string to check against Returns ------- bool True if the input is a string that matches the "ALL" indicator """ - return isinstance(input_obj, str) and input_obj.upper() == option_ALL_string + return isinstance(input_obj, str) and input_obj.upper() == CPPWG_ALL_STRING def find_classes_in_source( @@ -246,29 +265,3 @@ def strip_source_whitespace(source: str) -> str: source = re.sub(r"\B\s+|\s+\B", "", source) return source - - -def substitute_bool_for_string(input_dict: Dict[Any, Any], key: Any) -> None: - """ - Convert a string in the input dictionary to a boolean. - - Substitute a string in the input dictionary with a boolean value if the - string is a boolean indicator e.g. "ON", "OFF", "YES", "NO", "TRUE", "FALSE" - - Parameters - ---------- - input_dict : Dict[Any, Any] - The input dictionary - key : Any - The key to check - """ - if not isinstance(input_dict[key], str): - return - - caps_string = input_dict[key].strip().upper() - - if caps_string in CPPWG_TRUE_STRINGS: - input_dict[key] = True - - elif caps_string in CPPWG_FALSE_STRINGS: - input_dict[key] = False diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 50889e3..9efd8cf 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -364,6 +364,10 @@ def write(self, work_dir: str) -> None: if generator: self.cpp_string += generator.get_class_cpp_def_code(class_py_name) + # Add any specified custom suffix code + for code_line in self.class_info.suffix_code: + self.cpp_string += code_line + "\n" + # Close the class definition self.cpp_string += " ;\n}\n" diff --git a/examples/shapes/wrapper/package_info.yaml b/examples/shapes/wrapper/package_info.yaml index e41e0c2..3557635 100644 --- a/examples/shapes/wrapper/package_info.yaml +++ b/examples/shapes/wrapper/package_info.yaml @@ -34,6 +34,7 @@ modules: - name: geometry source_locations: + # List of source directories for this module, relative to the source root. classes: - name: Point # Name of class source file. Not required if class name matches file name. @@ -58,12 +59,16 @@ modules: - [int, int, int] # Path to a custom script for generating wrappers for this class. - # CPPWG_SOURCEROOT points to the supplied source root directory. + # The custom generator must extend cppwg.templates.custom.Custom. + # CPPWG_SOURCEROOT can be used in the path for the source root directory. # custom_generator: "CPPWG_SOURCEROOT/point_generator.py" # Custom C++ code to place at the top of the class wrapper. # prefix_code: + # Custom C++ code to place at the bottom of the class wrapper. + # suffix_code: + - name: primitives source_locations: classes: From 814e959f54281e0a99ba5bcccc88605d15cdf3a4 Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Sat, 28 Sep 2024 13:19:01 +0000 Subject: [PATCH 2/5] #13 Add template syntax example --- .github/workflows/build-and-test.yml | 6 +++--- examples/shapes/CMakeLists.txt | 12 ++++++------ .../shapes/src/{ => cpp}/geometry/Point.cpp | 0 .../shapes/src/{ => cpp}/geometry/Point.hpp | 0 .../math_funcs/SimpleMathFunctions.hpp | 0 .../shapes/src/{ => cpp}/mesh/AbstractMesh.cpp | 0 .../shapes/src/{ => cpp}/mesh/AbstractMesh.hpp | 0 .../shapes/src/{ => cpp}/mesh/ConcreteMesh.cpp | 0 .../shapes/src/{ => cpp}/mesh/ConcreteMesh.hpp | 0 .../shapes/src/{ => cpp}/mesh/MeshFactory.cpp | 0 .../shapes/src/{ => cpp}/mesh/MeshFactory.hpp | 0 .../shapes/src/{ => cpp}/primitives/Cuboid.cpp | 0 .../shapes/src/{ => cpp}/primitives/Cuboid.hpp | 0 .../src/{ => cpp}/primitives/Rectangle.cpp | 0 .../src/{ => cpp}/primitives/Rectangle.hpp | 0 .../shapes/src/{ => cpp}/primitives/Shape.cpp | 0 .../shapes/src/{ => cpp}/primitives/Shape.hpp | 0 .../shapes/src/{ => cpp}/primitives/Square.cpp | 0 .../shapes/src/{ => cpp}/primitives/Square.hpp | 0 .../src/{ => cpp}/primitives/Triangle.cpp | 0 .../src/{ => cpp}/primitives/Triangle.hpp | 0 .../src/{python => py}/pyshapes/__init__.py | 0 examples/shapes/src/py/pyshapes/_syntax.py | 9 +++++++++ .../src/py/pyshapes/geometry/__init__.py | 11 +++++++++++ .../pyshapes/math_funcs/__init__.py | 0 .../shapes/src/py/pyshapes/mesh/__init__.py | 18 ++++++++++++++++++ .../src/py/pyshapes/primitives/__init__.py | 11 +++++++++++ .../src/{python => py}/test/test_classes.py | 11 +++++++++++ .../src/{python => py}/test/test_functions.py | 0 .../src/python/pyshapes/geometry/__init__.py | 2 -- .../src/python/pyshapes/mesh/__init__.py | 2 -- .../src/python/pyshapes/primitives/__init__.py | 2 -- ...st_shapes.py => test_wrapper_generation.py} | 2 +- 33 files changed, 70 insertions(+), 16 deletions(-) rename examples/shapes/src/{ => cpp}/geometry/Point.cpp (100%) rename examples/shapes/src/{ => cpp}/geometry/Point.hpp (100%) rename examples/shapes/src/{ => cpp}/math_funcs/SimpleMathFunctions.hpp (100%) rename examples/shapes/src/{ => cpp}/mesh/AbstractMesh.cpp (100%) rename examples/shapes/src/{ => cpp}/mesh/AbstractMesh.hpp (100%) rename examples/shapes/src/{ => cpp}/mesh/ConcreteMesh.cpp (100%) rename examples/shapes/src/{ => cpp}/mesh/ConcreteMesh.hpp (100%) rename examples/shapes/src/{ => cpp}/mesh/MeshFactory.cpp (100%) rename examples/shapes/src/{ => cpp}/mesh/MeshFactory.hpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Cuboid.cpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Cuboid.hpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Rectangle.cpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Rectangle.hpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Shape.cpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Shape.hpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Square.cpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Square.hpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Triangle.cpp (100%) rename examples/shapes/src/{ => cpp}/primitives/Triangle.hpp (100%) rename examples/shapes/src/{python => py}/pyshapes/__init__.py (100%) create mode 100644 examples/shapes/src/py/pyshapes/_syntax.py create mode 100644 examples/shapes/src/py/pyshapes/geometry/__init__.py rename examples/shapes/src/{python => py}/pyshapes/math_funcs/__init__.py (100%) create mode 100644 examples/shapes/src/py/pyshapes/mesh/__init__.py create mode 100644 examples/shapes/src/py/pyshapes/primitives/__init__.py rename examples/shapes/src/{python => py}/test/test_classes.py (79%) rename examples/shapes/src/{python => py}/test/test_functions.py (100%) delete mode 100644 examples/shapes/src/python/pyshapes/geometry/__init__.py delete mode 100644 examples/shapes/src/python/pyshapes/mesh/__init__.py delete mode 100644 examples/shapes/src/python/pyshapes/primitives/__init__.py rename tests/{test_shapes.py => test_wrapper_generation.py} (98%) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 384b05e..36af0e1 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,17 +42,17 @@ jobs: run: python -m flake8 - name: Test wrapper generation - run: python -m unittest tests/test_shapes.py + run: python -m unittest tests/test_wrapper_generation.py - name: Generate new wrappers run: | cd examples/shapes/wrapper rm -rf geometry math_funcs primitives cd .. - cppwg src/ \ + cppwg src/cpp \ --wrapper_root wrapper/ \ --package_info wrapper/package_info.yaml \ - --includes src/*/ extern/*/ \ + --includes src/cpp/*/ extern/*/ \ --std c++17 \ --logfile cppwg.log diff --git a/examples/shapes/CMakeLists.txt b/examples/shapes/CMakeLists.txt index f88661b..ab307e2 100644 --- a/examples/shapes/CMakeLists.txt +++ b/examples/shapes/CMakeLists.txt @@ -20,16 +20,16 @@ FetchContent_MakeAvailable(pybind11) add_subdirectory(extern/meshgen meshgen_build) # Add a shared library for the main C++ source -file(GLOB_RECURSE SHAPES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) +file(GLOB_RECURSE SHAPES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/*.cpp) add_library(shapes SHARED ${SHAPES_SOURCES}) target_include_directories( shapes PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src - ${CMAKE_CURRENT_SOURCE_DIR}/src/geometry - ${CMAKE_CURRENT_SOURCE_DIR}/src/math_funcs - ${CMAKE_CURRENT_SOURCE_DIR}/src/mesh - ${CMAKE_CURRENT_SOURCE_DIR}/src/primitives + ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/geometry + ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/math_funcs + ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/mesh + ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/primitives ) target_link_libraries(shapes PUBLIC meshgen::meshgen) diff --git a/examples/shapes/src/geometry/Point.cpp b/examples/shapes/src/cpp/geometry/Point.cpp similarity index 100% rename from examples/shapes/src/geometry/Point.cpp rename to examples/shapes/src/cpp/geometry/Point.cpp diff --git a/examples/shapes/src/geometry/Point.hpp b/examples/shapes/src/cpp/geometry/Point.hpp similarity index 100% rename from examples/shapes/src/geometry/Point.hpp rename to examples/shapes/src/cpp/geometry/Point.hpp diff --git a/examples/shapes/src/math_funcs/SimpleMathFunctions.hpp b/examples/shapes/src/cpp/math_funcs/SimpleMathFunctions.hpp similarity index 100% rename from examples/shapes/src/math_funcs/SimpleMathFunctions.hpp rename to examples/shapes/src/cpp/math_funcs/SimpleMathFunctions.hpp diff --git a/examples/shapes/src/mesh/AbstractMesh.cpp b/examples/shapes/src/cpp/mesh/AbstractMesh.cpp similarity index 100% rename from examples/shapes/src/mesh/AbstractMesh.cpp rename to examples/shapes/src/cpp/mesh/AbstractMesh.cpp diff --git a/examples/shapes/src/mesh/AbstractMesh.hpp b/examples/shapes/src/cpp/mesh/AbstractMesh.hpp similarity index 100% rename from examples/shapes/src/mesh/AbstractMesh.hpp rename to examples/shapes/src/cpp/mesh/AbstractMesh.hpp diff --git a/examples/shapes/src/mesh/ConcreteMesh.cpp b/examples/shapes/src/cpp/mesh/ConcreteMesh.cpp similarity index 100% rename from examples/shapes/src/mesh/ConcreteMesh.cpp rename to examples/shapes/src/cpp/mesh/ConcreteMesh.cpp diff --git a/examples/shapes/src/mesh/ConcreteMesh.hpp b/examples/shapes/src/cpp/mesh/ConcreteMesh.hpp similarity index 100% rename from examples/shapes/src/mesh/ConcreteMesh.hpp rename to examples/shapes/src/cpp/mesh/ConcreteMesh.hpp diff --git a/examples/shapes/src/mesh/MeshFactory.cpp b/examples/shapes/src/cpp/mesh/MeshFactory.cpp similarity index 100% rename from examples/shapes/src/mesh/MeshFactory.cpp rename to examples/shapes/src/cpp/mesh/MeshFactory.cpp diff --git a/examples/shapes/src/mesh/MeshFactory.hpp b/examples/shapes/src/cpp/mesh/MeshFactory.hpp similarity index 100% rename from examples/shapes/src/mesh/MeshFactory.hpp rename to examples/shapes/src/cpp/mesh/MeshFactory.hpp diff --git a/examples/shapes/src/primitives/Cuboid.cpp b/examples/shapes/src/cpp/primitives/Cuboid.cpp similarity index 100% rename from examples/shapes/src/primitives/Cuboid.cpp rename to examples/shapes/src/cpp/primitives/Cuboid.cpp diff --git a/examples/shapes/src/primitives/Cuboid.hpp b/examples/shapes/src/cpp/primitives/Cuboid.hpp similarity index 100% rename from examples/shapes/src/primitives/Cuboid.hpp rename to examples/shapes/src/cpp/primitives/Cuboid.hpp diff --git a/examples/shapes/src/primitives/Rectangle.cpp b/examples/shapes/src/cpp/primitives/Rectangle.cpp similarity index 100% rename from examples/shapes/src/primitives/Rectangle.cpp rename to examples/shapes/src/cpp/primitives/Rectangle.cpp diff --git a/examples/shapes/src/primitives/Rectangle.hpp b/examples/shapes/src/cpp/primitives/Rectangle.hpp similarity index 100% rename from examples/shapes/src/primitives/Rectangle.hpp rename to examples/shapes/src/cpp/primitives/Rectangle.hpp diff --git a/examples/shapes/src/primitives/Shape.cpp b/examples/shapes/src/cpp/primitives/Shape.cpp similarity index 100% rename from examples/shapes/src/primitives/Shape.cpp rename to examples/shapes/src/cpp/primitives/Shape.cpp diff --git a/examples/shapes/src/primitives/Shape.hpp b/examples/shapes/src/cpp/primitives/Shape.hpp similarity index 100% rename from examples/shapes/src/primitives/Shape.hpp rename to examples/shapes/src/cpp/primitives/Shape.hpp diff --git a/examples/shapes/src/primitives/Square.cpp b/examples/shapes/src/cpp/primitives/Square.cpp similarity index 100% rename from examples/shapes/src/primitives/Square.cpp rename to examples/shapes/src/cpp/primitives/Square.cpp diff --git a/examples/shapes/src/primitives/Square.hpp b/examples/shapes/src/cpp/primitives/Square.hpp similarity index 100% rename from examples/shapes/src/primitives/Square.hpp rename to examples/shapes/src/cpp/primitives/Square.hpp diff --git a/examples/shapes/src/primitives/Triangle.cpp b/examples/shapes/src/cpp/primitives/Triangle.cpp similarity index 100% rename from examples/shapes/src/primitives/Triangle.cpp rename to examples/shapes/src/cpp/primitives/Triangle.cpp diff --git a/examples/shapes/src/primitives/Triangle.hpp b/examples/shapes/src/cpp/primitives/Triangle.hpp similarity index 100% rename from examples/shapes/src/primitives/Triangle.hpp rename to examples/shapes/src/cpp/primitives/Triangle.hpp diff --git a/examples/shapes/src/python/pyshapes/__init__.py b/examples/shapes/src/py/pyshapes/__init__.py similarity index 100% rename from examples/shapes/src/python/pyshapes/__init__.py rename to examples/shapes/src/py/pyshapes/__init__.py diff --git a/examples/shapes/src/py/pyshapes/_syntax.py b/examples/shapes/src/py/pyshapes/_syntax.py new file mode 100644 index 0000000..a8f2b33 --- /dev/null +++ b/examples/shapes/src/py/pyshapes/_syntax.py @@ -0,0 +1,9 @@ +class ClassDict: + + def __init__(self, name, arg_tuples): + self._dict = {} + for arg_tuple in arg_tuples: + self._dict[arg_tuple] = name + "_" + "_".join(arg_tuple) + + def __getitem__(self, key): + return self._dict[key] diff --git a/examples/shapes/src/py/pyshapes/geometry/__init__.py b/examples/shapes/src/py/pyshapes/geometry/__init__.py new file mode 100644 index 0000000..89c7de0 --- /dev/null +++ b/examples/shapes/src/py/pyshapes/geometry/__init__.py @@ -0,0 +1,11 @@ +# Bring in everything from the shared module +from pyshapes._syntax import ClassDict +from pyshapes.geometry._pyshapes_geometry import * + +Point = ClassDict( + "Point", + [ + (2,), + (3,), + ], +) diff --git a/examples/shapes/src/python/pyshapes/math_funcs/__init__.py b/examples/shapes/src/py/pyshapes/math_funcs/__init__.py similarity index 100% rename from examples/shapes/src/python/pyshapes/math_funcs/__init__.py rename to examples/shapes/src/py/pyshapes/math_funcs/__init__.py diff --git a/examples/shapes/src/py/pyshapes/mesh/__init__.py b/examples/shapes/src/py/pyshapes/mesh/__init__.py new file mode 100644 index 0000000..eb4a950 --- /dev/null +++ b/examples/shapes/src/py/pyshapes/mesh/__init__.py @@ -0,0 +1,18 @@ +# Bring in everything from the shared module +from pyshapes._syntax import ClassDict +from pyshapes.mesh._pyshapes_mesh import * + +AbstractMesh = ClassDict( + "AbstractMesh", + [ + (2, 2), + (3, 3), + ], +) +ConcreteMesh = ClassDict( + "ConcreteMesh", + [ + (2,), + (3,), + ], +) diff --git a/examples/shapes/src/py/pyshapes/primitives/__init__.py b/examples/shapes/src/py/pyshapes/primitives/__init__.py new file mode 100644 index 0000000..c5ebd74 --- /dev/null +++ b/examples/shapes/src/py/pyshapes/primitives/__init__.py @@ -0,0 +1,11 @@ +# Bring in everything from the shared module +from pyshapes._syntax import ClassDict +from pyshapes.primitives._pyshapes_primitives import * + +Shape = ClassDict( + "Shape", + [ + (2,), + (3,), + ], +) diff --git a/examples/shapes/src/python/test/test_classes.py b/examples/shapes/src/py/test/test_classes.py similarity index 79% rename from examples/shapes/src/python/test/test_classes.py rename to examples/shapes/src/py/test/test_classes.py index 673f1fd..073f29e 100644 --- a/examples/shapes/src/python/test/test_classes.py +++ b/examples/shapes/src/py/test/test_classes.py @@ -44,6 +44,17 @@ def testMesh(self): cmesh.SetIndex(1) self.assertTrue(cmesh.GetIndex() == 1) + def testSyntax(self): + + self.assertEqual(pyshapes.geometry.Point[2], pyshapes.geometry.Point_2) + + point = pyshapes.geometry.Point[3](0.0, 1.0, 2.0) + self.assertTrue(point.GetLocation() == [0.0, 1.0, 2.0]) + + self.assertEqual( + pyshapes.mesh.AbstractMesh[2, 2], pyshapes.mesh.AbstractMesh_2_2 + ) + if __name__ == "__main__": unittest.main() diff --git a/examples/shapes/src/python/test/test_functions.py b/examples/shapes/src/py/test/test_functions.py similarity index 100% rename from examples/shapes/src/python/test/test_functions.py rename to examples/shapes/src/py/test/test_functions.py diff --git a/examples/shapes/src/python/pyshapes/geometry/__init__.py b/examples/shapes/src/python/pyshapes/geometry/__init__.py deleted file mode 100644 index 87a4392..0000000 --- a/examples/shapes/src/python/pyshapes/geometry/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Bring in everything from the shared module -from pyshapes.geometry._pyshapes_geometry import * diff --git a/examples/shapes/src/python/pyshapes/mesh/__init__.py b/examples/shapes/src/python/pyshapes/mesh/__init__.py deleted file mode 100644 index 2dc4b4e..0000000 --- a/examples/shapes/src/python/pyshapes/mesh/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Bring in everything from the shared module -from pyshapes.mesh._pyshapes_mesh import * diff --git a/examples/shapes/src/python/pyshapes/primitives/__init__.py b/examples/shapes/src/python/pyshapes/primitives/__init__.py deleted file mode 100644 index 490a0f2..0000000 --- a/examples/shapes/src/python/pyshapes/primitives/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Bring in everything from the shared module -from pyshapes.primitives._pyshapes_primitives import * diff --git a/tests/test_shapes.py b/tests/test_wrapper_generation.py similarity index 98% rename from tests/test_shapes.py rename to tests/test_wrapper_generation.py index 260b9aa..44fd939 100644 --- a/tests/test_shapes.py +++ b/tests/test_wrapper_generation.py @@ -32,7 +32,7 @@ def file_diff(file_a: str, file_b: str) -> bool: return "\n".join(context_diff(a, b)) -class TestShapes(unittest.TestCase): +class TestWrapperGeneration(unittest.TestCase): def test_wrapper_generation(self) -> None: """ From 0d60f4680ad58262f0da0fe0cdfa44b121517e60 Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Sat, 28 Sep 2024 13:59:06 +0000 Subject: [PATCH 3/5] #13 Fix ClassDict --- examples/shapes/CMakeLists.txt | 4 ++-- examples/shapes/src/py/pyshapes/_syntax.py | 17 +++++++++++++---- .../src/py/pyshapes/geometry/__init__.py | 9 ++++----- .../shapes/src/py/pyshapes/mesh/__init__.py | 19 +++++++++---------- .../src/py/pyshapes/primitives/__init__.py | 9 ++++----- examples/shapes/src/py/test/test_classes.py | 2 +- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/examples/shapes/CMakeLists.txt b/examples/shapes/CMakeLists.txt index ab307e2..3f72e81 100644 --- a/examples/shapes/CMakeLists.txt +++ b/examples/shapes/CMakeLists.txt @@ -35,11 +35,11 @@ target_link_libraries(shapes PUBLIC meshgen::meshgen) # Copy the Python source and test trees to the build location file( - COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/python/pyshapes + COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/py/pyshapes DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/ ) file( - COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/python/test/ + COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/py/test/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/ ) diff --git a/examples/shapes/src/py/pyshapes/_syntax.py b/examples/shapes/src/py/pyshapes/_syntax.py index a8f2b33..ef6da94 100644 --- a/examples/shapes/src/py/pyshapes/_syntax.py +++ b/examples/shapes/src/py/pyshapes/_syntax.py @@ -1,9 +1,18 @@ +from collections.abc import Iterable + + class ClassDict: - def __init__(self, name, arg_tuples): + def __init__(self, template_dict): self._dict = {} - for arg_tuple in arg_tuples: - self._dict[arg_tuple] = name + "_" + "_".join(arg_tuple) + for arg_tuple, clas in template_dict.items(): + if not isinstance(arg_tuple, Iterable): + arg_tuple = (arg_tuple,) + key = tuple(str(arg) for arg in arg_tuple) + self._dict[key] = clas - def __getitem__(self, key): + def __getitem__(self, arg_tuple): + if not isinstance(arg_tuple, Iterable): + arg_tuple = (arg_tuple,) + key = tuple(str(arg) for arg in arg_tuple) return self._dict[key] diff --git a/examples/shapes/src/py/pyshapes/geometry/__init__.py b/examples/shapes/src/py/pyshapes/geometry/__init__.py index 89c7de0..f2e6185 100644 --- a/examples/shapes/src/py/pyshapes/geometry/__init__.py +++ b/examples/shapes/src/py/pyshapes/geometry/__init__.py @@ -3,9 +3,8 @@ from pyshapes.geometry._pyshapes_geometry import * Point = ClassDict( - "Point", - [ - (2,), - (3,), - ], + { + 2: Point_2, + 3: Point_3, + } ) diff --git a/examples/shapes/src/py/pyshapes/mesh/__init__.py b/examples/shapes/src/py/pyshapes/mesh/__init__.py index eb4a950..1a0fa99 100644 --- a/examples/shapes/src/py/pyshapes/mesh/__init__.py +++ b/examples/shapes/src/py/pyshapes/mesh/__init__.py @@ -3,16 +3,15 @@ from pyshapes.mesh._pyshapes_mesh import * AbstractMesh = ClassDict( - "AbstractMesh", - [ - (2, 2), - (3, 3), - ], + { + (2, 2): AbstractMesh_2_2, + (3, 3): AbstractMesh_3_3, + } ) + ConcreteMesh = ClassDict( - "ConcreteMesh", - [ - (2,), - (3,), - ], + { + 2: ConcreteMesh_2, + 3: ConcreteMesh_3, + } ) diff --git a/examples/shapes/src/py/pyshapes/primitives/__init__.py b/examples/shapes/src/py/pyshapes/primitives/__init__.py index c5ebd74..a4c3311 100644 --- a/examples/shapes/src/py/pyshapes/primitives/__init__.py +++ b/examples/shapes/src/py/pyshapes/primitives/__init__.py @@ -3,9 +3,8 @@ from pyshapes.primitives._pyshapes_primitives import * Shape = ClassDict( - "Shape", - [ - (2,), - (3,), - ], + { + 2: Shape_2, + 3: Shape_3, + } ) diff --git a/examples/shapes/src/py/test/test_classes.py b/examples/shapes/src/py/test/test_classes.py index 073f29e..23ebe0f 100644 --- a/examples/shapes/src/py/test/test_classes.py +++ b/examples/shapes/src/py/test/test_classes.py @@ -45,7 +45,7 @@ def testMesh(self): self.assertTrue(cmesh.GetIndex() == 1) def testSyntax(self): - + self.assertEqual(pyshapes.geometry.Point[2], pyshapes.geometry.Point_2) point = pyshapes.geometry.Point[3](0.0, 1.0, 2.0) From 51aeacd9885f27f50190681d0bfb4e046212c631 Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Sat, 28 Sep 2024 14:04:51 +0000 Subject: [PATCH 4/5] #13 Fix test_wrapper_generation --- tests/test_wrapper_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_wrapper_generation.py b/tests/test_wrapper_generation.py index 44fd939..b91050f 100644 --- a/tests/test_wrapper_generation.py +++ b/tests/test_wrapper_generation.py @@ -41,7 +41,7 @@ def test_wrapper_generation(self) -> None: # Set paths to the shapes code, reference and generated wrappers, etc. shapes_root = os.path.abspath("examples/shapes") - shapes_src = os.path.join(shapes_root, "src") + shapes_src = os.path.join(shapes_root, "src/cpp") extern_src = os.path.join(shapes_root, "extern") wrapper_root_ref = os.path.join(shapes_root, "wrapper") From 002769e5c2ebfc15437474c902ab4c5ddf8bb07d Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Sat, 28 Sep 2024 14:25:30 +0000 Subject: [PATCH 5/5] #13 Fix hierarchy_attribute --- cppwg/info/base_info.py | 5 +++-- examples/shapes/wrapper/package_info.yaml | 24 +++++++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/cppwg/info/base_info.py b/cppwg/info/base_info.py index cd6999b..f777ace 100644 --- a/cppwg/info/base_info.py +++ b/cppwg/info/base_info.py @@ -5,6 +5,7 @@ import os import sys from abc import ABC, abstractmethod +from numbers import Number from typing import Any, Dict, List, Optional @@ -212,7 +213,7 @@ def hierarchy_attribute(self, attribute_name: str) -> Any: The attribute value, or None if not found. """ value = getattr(self, attribute_name, None) - if value: + if value or isinstance(value, bool) or isinstance(value, Number): return value if self.parent is None: @@ -241,7 +242,7 @@ def hierarchy_attribute_gather(self, attribute_name: str) -> List[Any]: value_list: List[Any] = [] value = getattr(self, attribute_name, None) - if value: + if value or isinstance(value, bool) or isinstance(value, Number): value_list.append(value) if self.parent is None: diff --git a/examples/shapes/wrapper/package_info.yaml b/examples/shapes/wrapper/package_info.yaml index 3557635..5b8d9d0 100644 --- a/examples/shapes/wrapper/package_info.yaml +++ b/examples/shapes/wrapper/package_info.yaml @@ -1,14 +1,18 @@ -name: pyshapes # Package name: prepended to all modules. +# Package name: prepended to all modules. +name: pyshapes # Smart pointer type for PYBIND11_DECLARE_HOLDER_TYPE in all wrappers. smart_ptr_type: std::shared_ptr + # Default value of pybind11::return_value_policy for pointers. pointer_call_policy: reference + # Default value of pybind11::return_value_policy for references. reference_call_policy: reference_internal # Set False to not include the common include file (all headers) in all wrappers. common_include_file: True + # Headers to include in all wrappers. source_includes: - @@ -24,24 +28,36 @@ template_substitutions: replacement: [[2, 2], [3, 3]] modules: - - name: math_funcs # Module name + # Module name + - name: math_funcs + + # List of source directories for this module relative to the source root. # Restrict to headers from these directories. Blank means unrestricted. source_locations: + # List of classes to wrap. Blank means none, CPPWG_ALL means discover all. classes: + # List of free functions to wrap. Blank means none, CPPWG_ALL means discover all. free_functions: CPPWG_ALL - name: geometry source_locations: - # List of source directories for this module, relative to the source root. + - geometry + classes: + # Name of class. - name: Point + # Name of class source file. Not required if class name matches file name. source_file: Point.hpp - # Additional headers to include in this class wrapper. + + # Optional path to the class source file, relative to the source root. + # source_file_path: geometry/Point.hpp + source_includes: - + # List of methods that should not be wrapped. excluded_methods: - ExcludedMethod