diff --git a/src/ibek/gen_scripts.py b/src/ibek/gen_scripts.py index 616bed821..eecd2509d 100644 --- a/src/ibek/gen_scripts.py +++ b/src/ibek/gen_scripts.py @@ -9,6 +9,8 @@ from jinja2 import Template from ruamel.yaml.main import YAML +from ibek.utils import UTILS + from .globals import TEMPLATES from .ioc import IOC, Entity, clear_entity_model_ids, make_entity_models, make_ioc_model from .render import Render @@ -56,6 +58,10 @@ def ioc_deserialize(ioc_instance_yaml: Path, definition_yaml: List[Path]) -> IOC # extract the ioc instance yaml into a dict ioc_instance_dict = YAML(typ="safe").load(ioc_instance_yaml) + # extract the ioc name into UTILS for use in jinja renders + name = UTILS.render({}, ioc_instance_dict["ioc_name"]) + UTILS.set_ioc_name(name) + # Create an IOC instance from the instance dict and the model ioc_instance = ioc_model(**ioc_instance_dict) diff --git a/src/ibek/globals.py b/src/ibek/globals.py index 7dd227fbd..735beca2e 100644 --- a/src/ibek/globals.py +++ b/src/ibek/globals.py @@ -4,14 +4,10 @@ import os from pathlib import Path -from typing import Dict -from jinja2 import Template from pydantic import BaseModel, ConfigDict from typer.core import TyperGroup -from .utils import UTILS - # get the container paths from environment variables EPICS_BASE = Path(os.getenv("EPICS_BASE", "/epics/epics-base")) EPICS_ROOT = Path(os.getenv("EPICS_ROOT", "/epics/")) @@ -58,11 +54,3 @@ class BaseSettings(BaseModel): class NaturalOrderGroup(TyperGroup): def list_commands(self, ctx): return self.commands.keys() - - -def render_with_utils(context: Dict, template_text: str) -> str: - """ - Render a Jinja template with the global __utils__ object in the context - """ - jinja_template = Template(template_text) - return jinja_template.render(context, __utils__=UTILS) diff --git a/src/ibek/ioc.py b/src/ibek/ioc.py index fd50076f4..6b1c804f3 100644 --- a/src/ibek/ioc.py +++ b/src/ibek/ioc.py @@ -17,8 +17,9 @@ from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined -from .globals import BaseSettings, render_with_utils +from .globals import BaseSettings from .support import Definition, EnumArg, IdArg, ObjectArg, Support +from .utils import UTILS id_to_entity: Dict[str, Entity] = {} @@ -61,7 +62,7 @@ def add_ibek_attributes(cls, entity: Entity): id_to_entity[value] = entity elif isinstance(value, str): # Jinja expansion of any of the Entity's string args/values - setattr(entity, arg, render_with_utils(entity_dict, value)) + setattr(entity, arg, UTILS.render(entity_dict, value)) return entity def __str__(self): diff --git a/src/ibek/render.py b/src/ibek/render.py index c3a1ac605..bb8a4fc2a 100644 --- a/src/ibek/render.py +++ b/src/ibek/render.py @@ -4,9 +4,9 @@ from typing import Callable, List, Optional, Union -from .globals import render_with_utils from .ioc import IOC, Entity from .support import Comment, Script, Text, When +from .utils import UTILS class Render: @@ -45,7 +45,7 @@ def render_text( raise NotImplementedError("When.last not yet implemented") # Render Jinja entries in the text - result = render_with_utils(instance, text) # type: ignore + result = UTILS.render(instance, text) # type: ignore if result == "": return "" @@ -97,7 +97,10 @@ def render_environment_variables(self, instance: Entity) -> Optional[str]: for variable in variables: # Substitute the name and value of the environment variable from args env_template = f"epicsEnvSet {variable.name} {variable.value}" - env_var_txt += render_with_utils(instance, env_template) # type: ignore + env_var_txt += UTILS.render( + instance, + env_template, + ) # type: ignore return env_var_txt + "\n" def render_elements( diff --git a/src/ibek/render_db.py b/src/ibek/render_db.py index 55a499fb1..af9fa210d 100644 --- a/src/ibek/render_db.py +++ b/src/ibek/render_db.py @@ -6,9 +6,9 @@ from dataclasses import dataclass from typing import Any, Dict, List, Mapping, Optional, Tuple -from ibek.globals import render_with_utils from ibek.ioc import IOC, Entity from ibek.support import Database +from ibek.utils import UTILS class RenderDb: @@ -29,7 +29,7 @@ def add_row(self, filename: str, args: Mapping[str, Any], entity: Entity) -> Non Adding a new template file if it does not already exist. Convert all arguments to strings. """ - filename = render_with_utils(dict(entity), filename) + filename = UTILS.render(dict(entity), filename) if filename not in self.render_templates: # for new filenames create a new RenderDbTemplate entry @@ -47,7 +47,7 @@ def add_row(self, filename: str, args: Mapping[str, Any], entity: Entity) -> Non # render any Jinja fields in the arguments for i, line in enumerate(row): - row[i] = render_with_utils(dict(entity), row[i]) + row[i] = UTILS.render(dict(entity), row[i]) # save the new row self.render_templates[filename].rows.append(row) diff --git a/src/ibek/runtime_cmds/commands.py b/src/ibek/runtime_cmds/commands.py index f3a53d68d..84d8b6838 100644 --- a/src/ibek/runtime_cmds/commands.py +++ b/src/ibek/runtime_cmds/commands.py @@ -14,10 +14,10 @@ PVI_DEFS, RUNTIME_OUTPUT_PATH, NaturalOrderGroup, - render_with_utils, ) from ibek.ioc import IOC, Entity from ibek.support import Database +from ibek.utils import UTILS runtime_cli = typer.Typer(cls=NaturalOrderGroup) @@ -44,9 +44,12 @@ def generate( """ Build a startup script for an IOC instance """ + # the file name under of the instance definition provides the IOC name + UTILS.set_file_name(instance) + ioc_instance = ioc_deserialize(instance, definitions) - # Clear out generated files so developers know if something stops being generated + # Clear out generated files so developers know if something stop being generated shutil.rmtree(RUNTIME_OUTPUT_PATH, ignore_errors=True) RUNTIME_OUTPUT_PATH.mkdir(exist_ok=True) shutil.rmtree(OPI_OUTPUT_PATH, ignore_errors=True) @@ -100,9 +103,7 @@ def generate_pvi(ioc: IOC) -> Tuple[List[IndexEntry], List[Tuple[Database, Entit device.deserialize_parents([PVI_DEFS]) # Render the prefix value for the device from the instance parameters - macros = { - "prefix": render_with_utils(entity.model_dump(), entity_pvi.prefix) - } + macros = {"prefix": UTILS.render(entity.model_dump(), entity_pvi.prefix)} if entity_pvi.pva_template: # Create a template with the V4 structure defining a PVI interface diff --git a/src/ibek/utils.py b/src/ibek/utils.py index e7d7403b7..06a2be862 100644 --- a/src/ibek/utils.py +++ b/src/ibek/utils.py @@ -8,8 +8,11 @@ """ from dataclasses import dataclass +from pathlib import Path from typing import Any, Dict +from jinja2 import Template + @dataclass class Counter: @@ -35,6 +38,8 @@ class Utils: """ def __init__(self: "Utils"): + self.file_name: str = "" + self.ioc_name: str = "" self.__reset__() def __reset__(self: "Utils"): @@ -45,6 +50,18 @@ def __reset__(self: "Utils"): self.variables: Dict[str, Any] = {} self.counters: Dict[str, Counter] = {} + def set_file_name(self: "Utils", file: Path): + """ + Set the ioc name based on the file name of the instance definition + """ + self.file_name = file.stem + + def set_ioc_name(self: "Utils", name: str): + """ + Set the ioc name based on the file name of the instance definition + """ + self.ioc_name = name + def set_var(self, key: str, value: Any): """create a global variable for our jinja context""" self.variables[key] = value @@ -77,6 +94,18 @@ def counter( return result + def render(self, context: Any, template_text: str) -> str: + """ + Render a Jinja template with the global __utils__ object in the context + """ + jinja_template = Template(template_text) + return jinja_template.render( + context, + __utils__=self, + ioc_yaml_file_name=self.file_name, + ioc_name=self.ioc_name, + ) + # a singleton Utility object for sharing state across all Entity renders UTILS: Utils = Utils() diff --git a/tests/samples/outputs/all.ioc.subst b/tests/samples/outputs/all.ioc.subst index 3f64c0324..75760bc80 100644 --- a/tests/samples/outputs/all.ioc.subst +++ b/tests/samples/outputs/all.ioc.subst @@ -4,23 +4,23 @@ file "another_test.db" { pattern - { "name", "my_int_enum", "clock_rate", "db_calculated", "calculated_one" } - { "AllObject One", "2", "dummy", "HELLO AllObject One", "AllObject One.AllObject One String.1.1.0.True" } - { "AllObject Two", "1", "1", "HELLO AllObject Two", "AllObject Two.AllObject Two String.1.1.0.True" } + { "name", "my_int_enum", "clock_rate", "db_calculated", "calculated_one" } + { "ioc_name={{ ioc_name }}", "2", "dummy", "HELLO ioc_name={{ ioc_name }}", "ioc_name={{ ioc_name }}.AllObject One String.1.1.0.True" } + { "AllObject Two", "1", "1", "HELLO AllObject Two", "AllObject Two.AllObject Two String.1.1.0.True" } } file "yet_another.db" { pattern - { "name", "my_object", "my_float", "my_bool" } - { "AllObject One", "Ref1", "1.0", "True" } - { "AllObject Two", "Ref1", "1.0", "True" } + { "name", "my_object", "my_float", "my_bool" } + { "ioc_name={{ ioc_name }}", "Ref1", "1.0", "True" } + { "AllObject Two", "Ref1", "1.0", "True" } } file "jinjified1.db" { pattern - { "name" } - { "AllObject One" } - { "AllObject Two" } + { "name" } + { "ioc_name={{ ioc_name }}" } + { "AllObject Two" } } file "test.db" { @@ -31,7 +31,7 @@ pattern file "simple.pvi.template" { pattern - { "prefix" } - { "AllObject One" } - { "AllObject Two" } + { "prefix" } + { "ioc_name=all.ibek.ioc" } + { "AllObject Two" } } diff --git a/tests/samples/outputs/all.st.cmd b/tests/samples/outputs/all.st.cmd index bada55bbe..1b3652a14 100644 --- a/tests/samples/outputs/all.st.cmd +++ b/tests/samples/outputs/all.st.cmd @@ -13,7 +13,7 @@ TestValues Ref1.127.0.0.1 # this is a comment # that spans multiple lines # -testPreInit "AllObject One" "'AllObject One String'" +testPreInit "ioc_name={{ ioc_name }}" "'AllObject One String'" my_str = AllObject One String my_inferred_enum = third clock_rate = dummy @@ -36,7 +36,7 @@ dbLoadRecords $(RUNTIME_DIR)/ioc.db iocInit -testPostInit "AllObject One" test_value: +testPostInit "ioc_name={{ ioc_name }}" test_value: this should appear once only in the post_init section # post init comment testPostInit "AllObject Two" test_value: diff --git a/tests/samples/outputs/index.bob b/tests/samples/outputs/index.bob index fedd23c30..8645015d3 100644 --- a/tests/samples/outputs/index.bob +++ b/tests/samples/outputs/index.bob @@ -9,7 +9,7 @@ Title TITLE - test-multiple-ioc - Index + {{ ioc_yaml_file_name }} - Index 0 0 273 @@ -68,7 +68,7 @@ tab Open Display - AllObject One + ioc_name={{ ioc_name }} diff --git a/tests/samples/yaml/all.ibek.ioc.yaml b/tests/samples/yaml/all.ibek.ioc.yaml index 8417196c1..ae4a21ad8 100644 --- a/tests/samples/yaml/all.ibek.ioc.yaml +++ b/tests/samples/yaml/all.ibek.ioc.yaml @@ -1,6 +1,6 @@ # yaml-language-server: $schema=../schemas/multiple.ibek.ioc.schema.json -ioc_name: test-multiple-ioc +ioc_name: "{{ ioc_yaml_file_name }}" description: a basic example for testing multiple support definitions entities: @@ -8,7 +8,7 @@ entities: name: Ref1 - type: module.AllObject - name: AllObject One + name: ioc_name={{ ioc_name }} my_object: Ref1 my_int_enum: full_speed my_inferred_enum: third