Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…openMINDS_MATLAB into pipeline
  • Loading branch information
ehennestad committed Oct 28, 2024
2 parents ea94d5f + 6ed00f0 commit 987c6e4
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 36 deletions.
14 changes: 7 additions & 7 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import shutil

from pipeline.translator import MATLABSchemaBuilder
from pipeline.utils import clone_sources, SchemaLoader, initialise_jinja_template_environment, save_resource_files, save_enumeration_classes
from pipeline.utils import clone_sources, SchemaLoader, initialise_jinja_templates, save_resource_files, save_enumeration_classes

print("***************************************")
print(f"Triggering the generation of MATLAB-Classes for openMINDS")
Expand All @@ -14,10 +14,10 @@
if os.path.exists("target"):
shutil.rmtree("target")

for schema_version in schema_loader.get_schema_versions():
# Step 2 - Initialise the jinja templates
jinja_templates = initialise_jinja_templates()

# Step 2 - Initialise the jinja template environment
jinja_template_environment = initialise_jinja_template_environment()
for schema_version in schema_loader.get_schema_versions():

# Step 3 - find all involved schemas for the current version
schemas_file_paths = schema_loader.find_schemas(schema_version)
Expand All @@ -27,11 +27,11 @@
# Step 4 - translate and build each openMINDS schema as MATLAB class
schema_root_path = schema_loader.schemas_sources
try:
MATLABSchemaBuilder(schema_file_path, schema_root_path, jinja_template_environment).build()
MATLABSchemaBuilder(schema_file_path, schema_root_path, jinja_templates).build()
except Exception as e:
print(f"Error while building schema {schema_file_path}: {e}")

save_resource_files(schema_version, schemas_file_paths)

save_enumeration_classes("Types", schema_version, schema_loader, jinja_template_environment)
save_enumeration_classes("Models", schema_version, schema_loader, jinja_template_environment)
save_enumeration_classes("Types", schema_version, schema_loader, jinja_templates["types_enumeration"])
save_enumeration_classes("Models", schema_version, schema_loader, jinja_templates["models_enumeration"])
3 changes: 1 addition & 2 deletions pipeline/templates/controlledterm_class_template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ classdef {{ class_name }} < openminds.abstract.ControlledTerm
{%- endfor %}
]
end

methods
function obj = {{ class_name }}(varargin)
[email protected](varargin{:})
end
end

end
15 changes: 15 additions & 0 deletions pipeline/templates/mixedtype_class_template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
classdef {{ class_name }} < openminds.internal.abstract.LinkedCategory
properties (Constant, Hidden)
ALLOWED_TYPES = [ ...
{%- for full_type_name in allowed_types_list %}
{%- if not loop.last%}
"{{full_type_name}}", ...
{%- else%}
"{{full_type_name}}" ...
{%- endif -%}

{%- endfor %}
]
IS_SCALAR = {{is_scalar}}
end
end
3 changes: 1 addition & 2 deletions pipeline/templates/schema_class_template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,10 @@ classdef {{ class_name }} < {{base_class}}
[email protected](varargin{:})
end
end

methods (Access = protected)
function str = getDisplayLabel(obj)
{{display_label_method_expression}}
end
end

end
2 changes: 0 additions & 2 deletions pipeline/templates/types_enumeration_template.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
classdef Types < openminds.abstract.TypesEnumeration

enumeration
None('None')
{%- for type in types %}
{{type.name}}("{{type.class_name}}")
{%- endfor %}
end

end
35 changes: 20 additions & 15 deletions pipeline/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import math
import os
import re
from typing import List
from typing import List, Dict
from collections import defaultdict
import warnings

from jinja2 import Environment
from jinja2 import Template

from pipeline.constants import (
OPENMINDS_BASE_URI,
Expand Down Expand Up @@ -46,19 +46,19 @@
class MATLABSchemaBuilder(object):
""" Class for building MATLAB schema classes """

def __init__(self, schema_file_path:str, root_path:str, jinja_template_environment:Environment):
def __init__(self, schema_file_path:str, root_path:str, jinja_templates:Dict[str, Template]):

self._parse_source_file_path(schema_file_path, root_path)

with open(schema_file_path, "r") as schema_file:
self._schema_payload = json.load(schema_file)

if self._schema_model_name == "controlledTerms":
template_file_name = TEMPLATE_FILE_NAME_CT
self.class_template = jinja_templates["controlledterm_class"]
else:
template_file_name = TEMPLATE_FILE_NAME
self.class_template = jinja_templates["schema_class"]

self.schema_template = jinja_template_environment.get_template(template_file_name)
self.mixedtype_class_template = jinja_templates["mixedtype_class"]

def build(self):
"""Build and save the MATLAB schema class file"""
Expand Down Expand Up @@ -171,6 +171,8 @@ def _extract_template_variables(self):
# Resolve property validators in matlab
validators = _create_property_validator_functions(property_name, property_info)

mixed_types_list = sorted(possible_types)

if len(possible_types) == 1:
possible_types = possible_types[0]
possible_types_str = f'"{possible_types}"'
Expand All @@ -187,6 +189,7 @@ def _extract_template_variables(self):
"type": possible_types,
"type_doc": possible_types_docstr,
"type_list": possible_types_str,
"mixed_type_list": mixed_types_list,
"size": size_attribute,
"size_doc": size_attribute_doc,
"validators": "{{{}}}".format(', '.join(validators)) if validators else "",
Expand Down Expand Up @@ -241,7 +244,7 @@ def _expand_schema_template(self) -> str:
# print(f"Expanding template for {self._schema_file_name}")

template_variables = self._template_variables
result = self.schema_template.render(template_variables)
result = self.class_template.render(template_variables)
return _strip_trailing_whitespace(result)

def _generate_additional_files(self):
Expand Down Expand Up @@ -271,14 +274,16 @@ def _build_mixed_type_class(self, schema, prop):
file_name = property_name + ".m"
file_path = os.path.join(*path_parts, file_name)

# Write file content # Todo: use a template
with open(file_path, "w") as fp:
fp.write(f"classdef {property_name} < openminds.internal.abstract.LinkedCategory\n")
fp.write(f" properties (Constant, Hidden)\n")
fp.write(f" ALLOWED_TYPES = {prop['type_list']}\n")
fp.write(f" IS_SCALAR = {str(not(prop['allow_multiple'])).lower()}\n")
fp.write(f" end\n")
fp.write(f"end\n")
template_variables = {
"class_name": property_name,
"allowed_types_list": prop['mixed_type_list'],
"is_scalar": str(not(prop['allow_multiple'])).lower(),
}

mixedtype_classdef_str = self.mixedtype_class_template.render(template_variables)

with open(file_path, "w", encoding="utf-8") as target_file:
target_file.write(mixedtype_classdef_str)


# # # LOCAL UTILITY FUNCTIONS # # #
Expand Down
68 changes: 60 additions & 8 deletions pipeline/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from git import Repo, GitCommandError
from jinja2 import Environment, select_autoescape, FileSystemLoader
from jinja2 import Template

def clone_sources():

Expand Down Expand Up @@ -74,12 +75,67 @@ def get_instance_collection(self, version:str, schema_name:str) -> List[str]:

return instance_list

def initialise_jinja_template_environment(autoescape:bool=None):
return Environment(
loader=FileSystemLoader(os.path.dirname(os.path.realpath(__file__))),
def initialise_jinja_templates(autoescape:bool=None):
"""
Initializes a Jinja2 environment and preloads templates into a dictionary for reuse.
This function sets up a Jinja2 `Environment` for loading templates from the `templates`
subdirectory within the current script's directory. It then preloads a set of named templates
into a dictionary, making them accessible by key for efficient repeated rendering. This
approach avoids repeated environment initialization and template loading, optimizing
performance when rendering templates multiple times.
Parameters:
-----------
autoescape : bool, optional
Configures the autoescaping behavior for templates:
- `True` enables autoescaping for all templates.
- `False` disables autoescaping.
- `None` (default) uses Jinja2's `select_autoescape()` function to enable autoescaping for
specific file types (e.g., `.html`, `.xml`).
Returns:
--------
dict
A dictionary of preloaded Jinja2 template objects keyed by descriptive names, allowing
access to specific templates. The available templates are:
- `"schema_class"`: Template for schema class generation.
- `"controlledterm_class"`: Template for controlled term class generation.
- `"mixedtype_class"`: Template for mixed type class generation.
- `"models_enumeration"`: Template for models enumeration generation.
- `"types_enumeration"`: Template for types enumeration generation.
Notes:
------
- The function uses `os.path.dirname(os.path.realpath(__file__))` to locate the directory of
the current script, ensuring templates are loaded from the `templates` subdirectory.
- Autoescaping is configured to help prevent injection attacks for templates handling HTML or XML.
Example Usage:
--------------
templates = initialise_jinja_templates(autoescape=True)
rendered_schema = templates["schema_class"].render(data=schema_data)
"""

module_directory = os.path.dirname(os.path.realpath(__file__))
template_directory = os.path.join(module_directory, "templates")

jinja_environment = Environment(
loader=FileSystemLoader(template_directory),
autoescape=select_autoescape(autoescape) if autoescape is not None else select_autoescape()
)

jinja_templates = {
"schema_class": jinja_environment.get_template("schema_class_template.txt"),
"controlledterm_class": jinja_environment.get_template("controlledterm_class_template.txt"),
"mixedtype_class": jinja_environment.get_template("mixedtype_class_template.txt"),
"models_enumeration": jinja_environment.get_template("models_enumeration_template.txt"),
"types_enumeration": jinja_environment.get_template("types_enumeration_template.txt"),
}

return jinja_templates


def camel_case(text_string: str):
return text_string[0].lower() + text_string[1:]

Expand Down Expand Up @@ -156,12 +212,8 @@ def save_resource_files(version, schema_path_list):
with open(os.path.join(target_directory, "alias.json"), "w") as f:
json.dump(alias_json, f, indent=2)

def save_enumeration_classes(enum_type, version, schema_loader, jinja_template_environment):
def save_enumeration_classes(enum_type, version, schema_loader, enumeration_template:Template):

# Load templates
template_file_name = os.path.join("templates", enum_type.lower()+"_enumeration_template.txt")
enumeration_template = jinja_template_environment.get_template(template_file_name)

# Create target file directory
target_file_path = _create_enum_target_file_path(version, enum_type)
os.makedirs(os.path.dirname(target_file_path), exist_ok=True)
Expand Down

0 comments on commit 987c6e4

Please sign in to comment.