Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add values support (completed) #68

Merged
merged 16 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ibek-defs
7 changes: 2 additions & 5 deletions src/ibek/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from .gen_scripts import create_boot_script, create_db_script, ioc_deserialize
from .ioc import IOC, make_entity_classes
from .support import Support
from .utils import Utils

cli = typer.Typer()
yaml = YAML()
Expand Down Expand Up @@ -100,16 +99,14 @@ def build_startup(

ioc_instance = ioc_deserialize(instance, definitions)

utility = Utils(ioc_instance.ioc_name)

script_txt = create_boot_script(ioc_instance, utility)
script_txt = create_boot_script(ioc_instance)

out.parent.mkdir(parents=True, exist_ok=True)

with out.open("w") as stream:
stream.write(script_txt)

db_txt = create_db_script(ioc_instance, utility)
db_txt = create_db_script(ioc_instance)

with db_out.open("w") as stream:
stream.write(db_txt)
Expand Down
12 changes: 5 additions & 7 deletions src/ibek/gen_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from .ioc import IOC, make_entity_classes
from .render import Render
from .support import Support
from .utils import Utils

log = logging.getLogger(__name__)

Expand All @@ -28,6 +27,7 @@ def ioc_deserialize(ioc_instance_yaml: Path, definition_yaml: List[Path]) -> IOC

Returns an in memory object graph of the resulting ioc instance
"""

# Read and load the support module definitions
for yaml in definition_yaml:
support = Support.deserialize(YAML(typ="safe").load(yaml))
Expand All @@ -37,32 +37,30 @@ def ioc_deserialize(ioc_instance_yaml: Path, definition_yaml: List[Path]) -> IOC
return IOC.deserialize(YAML(typ="safe").load(ioc_instance_yaml))


def create_db_script(ioc_instance: IOC, utility: Utils) -> str:
def create_db_script(ioc_instance: IOC) -> str:
"""
Create make_db.sh script for expanding the database templates
"""
with open(TEMPLATES / "make_db.jinja", "r") as f:
template = Template(f.read())

renderer = Render(utility)
renderer = Render()

return template.render(
__util__=utility,
database_elements=renderer.render_database_elements(ioc_instance),
)


def create_boot_script(ioc_instance: IOC, utility: Utils) -> str:
def create_boot_script(ioc_instance: IOC) -> str:
"""
Create the boot script for an IOC
"""
with open(TEMPLATES / "st.cmd.jinja", "r") as f:
template = Template(f.read())

renderer = Render(utility)
renderer = Render()

return template.render(
__util__=utility,
env_var_elements=renderer.render_environment_variable_elements(ioc_instance),
script_elements=renderer.render_script_elements(ioc_instance),
post_ioc_init_elements=renderer.render_post_ioc_init_elements(ioc_instance),
Expand Down
25 changes: 23 additions & 2 deletions src/ibek/ioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import builtins
import types
from dataclasses import Field, dataclass, field, make_dataclass
from dataclasses import Field, asdict, dataclass, field, make_dataclass
from typing import Any, Dict, List, Mapping, Sequence, Tuple, Type, cast

from apischema import (
Expand All @@ -20,12 +20,14 @@
)
from apischema.conversions import Conversion, reset_deserializers
from apischema.metadata import conversion
from jinja2 import Template
from typing_extensions import Annotated as A
from typing_extensions import Literal

from . import modules
from .globals import T, desc
from .support import Definition, IdArg, ObjectArg, Support
from .utils import Utils


class Entity:
Expand All @@ -36,10 +38,12 @@ class Entity:

# a link back to the Definition Object that generated this Definition
__definition__: Definition
# a singleton Utility object for sharing state across all Entity renders
__utils__: Utils = Utils()
gilesknap marked this conversation as resolved.
Show resolved Hide resolved
gilesknap marked this conversation as resolved.
Show resolved Hide resolved

entity_enabled: bool

def __post_init__(self):
def __post_init__(self: "Entity"):
# If there is an argument which is an id then allow deserialization by that
args = self.__definition__.args
ids = set(a.name for a in args if isinstance(a, IdArg))
Expand All @@ -50,6 +54,23 @@ def __post_init__(self):
assert inst_id not in id_to_entity, f"Already got an instance {inst_id}"
id_to_entity[inst_id] = self

# create a context dictionary for use in Jinja expansion of this Entity
context: Dict[str, Any] = asdict(self) # type: ignore
for value in self.__definition__.values:
context[value.name] = value.value

# add in the global __utils__ object for state sharing
context["__utils__"] = self.__utils__

# Do Jinja expansion of any string args/values in the context
for arg, value in context.items():
if isinstance(value, str):
jinja_template = Template(value)
rendered = jinja_template.render(context)
context[arg] = rendered

self.__context__ = context


id_to_entity: Dict[str, Entity] = {}

Expand Down
54 changes: 20 additions & 34 deletions src/ibek/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
Functions for rendering lines in the boot script using Jinja2
"""

from dataclasses import asdict
from typing import Any, Dict, List, Optional
from typing import Callable, List, Optional

from jinja2 import Template

from .ioc import IOC, Entity
from .support import Function, Once
from .utils import Utils


class Render:
Expand All @@ -19,25 +17,17 @@ class Render:
definition yaml with substitution values supplied in ioc entity yaml
"""

def __init__(self, utils: Utils):
self.utils = utils
def __init__(self: "Render"):
self.once_done: List[str] = []

def _to_dict(self, instance: Any) -> Dict[str, Any]:
"""
add the global utils object to the instance so we can use them in the
jinja templates
"""
result = asdict(instance)
result["__utils__"] = self.utils

return result

def render_text(self, instance: Entity, text: str, once=False, suffix="") -> str:
"""
render a line of jinja template in ``text`` using the values supplied
in the ``instance`` object. Supports the ``once`` flag to only render
the line once per definitions file.
Add in the next line of text, honouring the ``once`` flag which will
only add the line once per IOC.

Jinja rendering of values/args has already been done in Entity.__post_init__
but we pass all strings though jinja again to render any other jinja
in the IOC (e.g. database and function entries)

``once`` uses the name of the definition + suffix to track which lines
have been rendered already. The suffix can be used where a given
Expand All @@ -51,9 +41,6 @@ def render_text(self, instance: Entity, text: str, once=False, suffix="") -> str
else:
return ""

jinja_template = Template(text)
result = jinja_template.render(self._to_dict(instance)) # type: ignore

# run the result through jinja again so we can refer to args for arg defaults
gilesknap marked this conversation as resolved.
Show resolved Hide resolved
# e.g.
#
Expand All @@ -62,8 +49,8 @@ def render_text(self, instance: Entity, text: str, once=False, suffix="") -> str
# description: IPAC identifier
# default: "IPAC{{ slot }}"

jinja_template = Template(result)
result = jinja_template.render(self._to_dict(instance)) # type: ignore
jinja_template = Template(text)
result = jinja_template.render(instance.__context__) # type: ignore

if result == "":
return ""
Expand All @@ -84,9 +71,9 @@ def render_function(self, instance: Entity, function: Function) -> str:
call += f"{value} "

text = (
self.render_text(instance, comment, once=True, suffix="func")
self.render_text(instance, comment.strip(" "), once=True, suffix="func")
+ self.render_text(instance, function.header, once=True, suffix="func_hdr")
+ self.render_text(instance, call)
+ self.render_text(instance, call.strip(" "))
)

return text
Expand Down Expand Up @@ -134,12 +121,12 @@ def render_database(self, instance: Entity) -> Optional[str]:
)

jinja_template = Template(jinja_txt)
db_txt = jinja_template.render(self._to_dict(instance)) # type: ignore
db_txt = jinja_template.render(instance.__context__) # type: ignore

# run the result through jinja again so we can refer to args for arg defaults

db_template = Template(db_txt)
db_txt = db_template.render(self._to_dict(instance)) # type: ignore
db_txt = db_template.render(instance.__context__) # type: ignore

return db_txt + "\n"

Expand All @@ -156,7 +143,7 @@ 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 = Template(f"epicsEnvSet {variable.name} {variable.value}")
env_var_txt += env_template.render(self._to_dict(instance))
env_var_txt += env_template.render(instance.__context__)
return env_var_txt + "\n"

def render_post_ioc_init(self, instance: Entity) -> Optional[str]:
Expand All @@ -170,11 +157,10 @@ def render_post_ioc_init(self, instance: Entity) -> Optional[str]:

return script

def render_elements(self, ioc: IOC, element: str) -> str:
def render_elements(self, ioc: IOC, method: Callable) -> str:
gilesknap marked this conversation as resolved.
Show resolved Hide resolved
"""
Render elements of a given IOC instance based on calling the correct method
"""
method = getattr(self, element)
elements = ""
for instance in ioc.entities:
if instance.entity_enabled:
Expand All @@ -187,22 +173,22 @@ def render_script_elements(self, ioc: IOC) -> str:
"""
Render all of the startup script entries for a given IOC instance
"""
return self.render_elements(ioc, "render_script")
return self.render_elements(ioc, self.render_script)

def render_database_elements(self, ioc: IOC) -> str:
"""
Render all of the DBLoadRecords entries for a given IOC instance
"""
return self.render_elements(ioc, "render_database")
return self.render_elements(ioc, self.render_database)

def render_environment_variable_elements(self, ioc: IOC) -> str:
"""
Render all of the environment variable entries for a given IOC instance
"""
return self.render_elements(ioc, "render_environment_variables")
return self.render_elements(ioc, self.render_environment_variables)

def render_post_ioc_init_elements(self, ioc: IOC) -> str:
"""
Render all of the post-iocInit elements for a given IOC instance
"""
return self.render_elements(ioc, "render_post_ioc_init")
return self.render_elements(ioc, self.render_post_ioc_init)
13 changes: 13 additions & 0 deletions src/ibek/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,18 @@ class Once:
value: A[str, desc("Startup script snippets defined as Jinja template")] = ""


@dataclass
class Value:
"""A calculated string value for a definition"""

name: A[str, desc("Name of the value that the IOC instance will expose")]
description: A[str, desc("Description of what the value will be used for")]
value: A[str, desc("The contents of the value")]

def __str__(self):
return self.value


@dataclass
class Definition:
"""
Expand All @@ -148,6 +160,7 @@ class Definition:
name: A[str, desc("Publish Definition as type <module>.<name> for IOC instances")]
description: A[str, desc("Describes the purpose of the definition")]
args: A[Sequence[Arg], desc("The arguments IOC instance should supply")] = ()
values: A[Sequence[Value], desc("The values IOC instance should supply")] = ()
databases: A[Sequence[Database], desc("Databases to instantiate")] = ()
script: A[
Sequence[Union[str, Function, Once]],
Expand Down
10 changes: 8 additions & 2 deletions src/ibek/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@ class Utils:
A Utility class for adding functions to the Jinja context
"""

def __init__(self, ioc_name: str):
self.ioc_name = ioc_name
def __init__(self: "Utils"):
self.__reset__()

def __reset__(self: "Utils"):
"""
Reset all saved state. For use in testing where more than one
IOC is rendered in a single session
"""
self.variables: Dict[str, Any] = {}
self.counters: Dict[str, Counter] = {}

Expand Down
16 changes: 14 additions & 2 deletions tests/samples/example-srrfioc08/SR-RF-IOC-08.ibek.ioc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,31 @@ entities:
name: Vec0
- type: epics.InterruptVectorVME
name: Vec1
- type: epics.InterruptVectorVME
name: Vec2

- type: ipac.Hy8002
name: IPAC4
slot: 4

- type: ipac.Hy8002
name: IPAC5
slot: 5

- type: Hy8401ip.Hy8401ip
name: SlotA
name: SlotA_Card4
carrier: IPAC4
ip_site_number: 0
vector: Vec0

- type: Hy8401ip.Hy8401ip
name: SlotC
name: SlotC_Card4
carrier: IPAC4
ip_site_number: 2
vector: Vec1

- type: Hy8401ip.Hy8401ip
name: SlotA_Card5
carrier: IPAC5
ip_site_number: 0
vector: Vec2
Loading