Skip to content

Commit

Permalink
Merge pull request #84 from epics-containers/script-schema
Browse files Browse the repository at this point in the history
Fix for #81
  • Loading branch information
gilesknap authored Jul 6, 2023
2 parents acebf27 + 8f2aace commit b05d34f
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 185 deletions.
2 changes: 1 addition & 1 deletion ibek-defs
2 changes: 1 addition & 1 deletion src/ibek/gen_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ def create_boot_script(ioc_instance: IOC) -> str:

return template.render(
env_var_elements=renderer.render_environment_variable_elements(ioc_instance),
script_elements=renderer.render_script_elements(ioc_instance),
script_elements=renderer.render_pre_ioc_init_elements(ioc_instance),
post_ioc_init_elements=renderer.render_post_ioc_init_elements(ioc_instance),
)
87 changes: 50 additions & 37 deletions src/ibek/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from jinja2 import Template

from .ioc import IOC, Entity
from .support import Function, Once
from .support import Comment, Function, Script, Text, When


class Render:
Expand All @@ -20,7 +20,9 @@ class Render:
def __init__(self: "Render"):
self.once_done: List[str] = []

def render_text(self, instance: Entity, text: str, once=False, suffix="") -> str:
def render_text(
self, instance: Entity, text: str, when=When.every, suffix=""
) -> str:
"""
Add in the next line of text, honouring the ``once`` flag which will
only add the line once per IOC.
Expand All @@ -34,12 +36,14 @@ def render_text(self, instance: Entity, text: str, once=False, suffix="") -> str
Entity has more than one element to render once (e.g. functions)
"""

if once:
if when == When.first:
name = instance.__definition__.name + suffix
if name not in self.once_done:
self.once_done.append(name)
else:
return ""
elif when == When.last:
raise NotImplementedError("When.last not yet implemented")

# Render Jinja entries in the text
jinja_template = Template(text)
Expand All @@ -64,30 +68,50 @@ def render_function(self, instance: Entity, function: Function) -> str:
call += f"{value} "

text = (
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.strip(" "))
self.render_text(
instance, comment.strip(" "), when=When.first, suffix="func"
)
+ self.render_text(
instance, function.header, when=When.first, suffix="func_hdr"
)
+ self.render_text(instance, call.strip(" "), when=function.when)
)

return text

def render_script(self, instance: Entity) -> Optional[str]:
def render_script(self, instance: Entity, script_items: Script) -> Optional[str]:
script = ""

for item in script_items:
if isinstance(item, Comment):
comments = "\n".join(["# " + line for line in item.value.split("\n")])
script += self.render_text(
instance, comments, item.when, suffix="comment"
)
elif isinstance(item, Text):
script += self.render_text(
instance, item.value, item.when, suffix="text"
)
elif isinstance(item, Function):
script += self.render_function(instance, item)

return script

def render_pre_ioc_init(self, instance: Entity) -> Optional[str]:
"""
render the startup script by combining the jinja template from
an entity with the arguments from an Entity
"""
pre_init = instance.__definition__.pre_init
return self.render_script(instance, pre_init)

script = ""
script_items = getattr(instance.__definition__, "script")
for line in script_items:
if isinstance(line, str):
script += self.render_text(instance, line)
elif isinstance(line, Once):
script += self.render_text(instance, line.value, True)
elif isinstance(line, Function):
script += self.render_function(instance, line)

return script
def render_post_ioc_init(self, instance: Entity) -> Optional[str]:
"""
render the post-iocInit entries by combining the jinja template
from an entity with the arguments from an Entity
"""
post_init = instance.__definition__.post_init
return self.render_script(instance, post_init)

def render_database(self, instance: Entity) -> Optional[str]:
"""
Expand Down Expand Up @@ -143,17 +167,6 @@ def render_environment_variables(self, instance: Entity) -> Optional[str]:
env_var_txt += env_template.render(instance.__dict__)
return env_var_txt + "\n"

def render_post_ioc_init(self, instance: Entity) -> Optional[str]:
"""
render the post-iocInit entries by combining the jinja template
from an entity with the arguments from an Entity
"""
script = ""
for line in getattr(instance.__definition__, "post_ioc_init"):
script += self.render_text(instance, line)

return script

def render_elements(
self, ioc: IOC, method: Callable[[Entity], Union[str, None]]
) -> str:
Expand All @@ -168,11 +181,17 @@ def render_elements(
elements += element
return elements

def render_script_elements(self, ioc: IOC) -> str:
def render_pre_ioc_init_elements(self, ioc: IOC) -> str:
"""
Render all of the startup script entries for a given IOC instance
"""
return self.render_elements(ioc, self.render_script)
return self.render_elements(ioc, self.render_pre_ioc_init)

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, self.render_post_ioc_init)

def render_database_elements(self, ioc: IOC) -> str:
"""
Expand All @@ -185,9 +204,3 @@ 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, 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, self.render_post_ioc_init)
51 changes: 39 additions & 12 deletions src/ibek/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Any, Dict, Mapping, Optional, Sequence, Type, Union

from apischema import Undefined, UndefinedType, deserialize, deserializer, identity
Expand All @@ -15,6 +16,12 @@
from .globals import T, desc


class When(Enum):
first = "first"
every = "every"
last = "last"


@dataclass
class Arg:
"""Base class for all Argument Types"""
Expand Down Expand Up @@ -126,20 +133,36 @@ class Function:
"""

name: A[str, desc("Name of the function to call")]
args: A[Dict[str, Any], desc("The arguments IOC instance should supply")]
args: A[Dict[str, Any], desc("The arguments to pass to the function")]
header: A[str, desc("commands/comments to appear before the function")] = ""
once: A[bool, desc("If true, only call the function once")] = False
# TODO will be an enum
when: A[str, desc("one of first / every / last")] = "every"
type: Literal["function"] = "function"


@dataclass
class Once:
class Comment:
"""
A script snippet that will have '# ' prepended to every line
for insertion into the startup script
"""

type: Literal["comment"] = "comment"
# TODO will be an enum
when: A[str, desc("One of first / every / last")] = "every"
value: A[str, desc("A comment to add into the startup script")] = ""


@dataclass
class Text:
"""
A script snippet that should for the first occurrence only
A script snippet to insert into the startup script
"""

type: Literal["once"] = "once"
value: A[str, desc("Startup script snippets defined as Jinja template")] = ""
type: Literal["text"] = "text"
# TODO will be an enum
when: A[str, desc("One of first / every / last")] = "every"
value: A[str, desc("raw text to add to the startup script")] = ""


@dataclass
Expand All @@ -154,6 +177,9 @@ def __str__(self):
return self.value


Script = Sequence[Union[Function, Comment, Text]]


@dataclass
class Definition:
"""
Expand All @@ -165,17 +191,18 @@ class 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]],
desc("Startup script snippets defined as Jinja template or function"),
pre_init: A[
Script,
desc("Startup script snippets to add before iocInit()"),
] = ()
post_init: A[
Script,
desc("Startup script snippets to add post iocInit(), such as dbpf"),
] = ()
env_vars: A[
Sequence[EnvironmentVariable],
desc("Environment variables to set in the boot script"),
] = ()
post_ioc_init: A[
Sequence[str], desc("Entries to add post iocInit(), such as dbpf")
] = ()


@dataclass
Expand Down
10 changes: 7 additions & 3 deletions tests/samples/boot_scripts/st.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ cd "/repos/epics/ioc"
dbLoadDatabase dbd/ioc.dbd
ioc_registerRecordDeviceDriver pdbbase

pmacAsynIPConfigure(BRICK1port, 192.168.0.12:1112:1025)
pmacCreateController(BL45P-MO-BRICK-01, BRICK1port, 0, 8, 500, 100)
pmacCreateAxes(BL45P-MO-BRICK-01, 8)

# pmacAsynIPConfigure AsynPortName IPAddress
pmacAsynIPConfigure BRICK1port 192.168.0.12:1112:1025

# pmacCreateController AsynPortName PmacAsynPort Address NumAxes MovingPollPeriod IdlePollPeriod
pmacCreateController BL45P-MO-BRICK-01 BRICK1port 0 8 500 100
pmacCreateAxes BL45P-MO-BRICK-01 8

dbLoadRecords /tmp/ioc.db
iocInit
Expand Down
18 changes: 12 additions & 6 deletions tests/samples/boot_scripts/stbl45p-mo-ioc-03
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ cd "/repos/epics/ioc"
dbLoadDatabase dbd/ioc.dbd
ioc_registerRecordDeviceDriver pdbbase

pmacAsynIPConfigure(BRICK1port, 192.168.0.12:1112:1025)
pmacCreateController(BL45P-MO-BRICK-01, BRICK1port, 0, 8, 500, 100)
pmacCreateAxes(BL45P-MO-BRICK-01, 8)
TODO provide Jinja to generate Startup Entries
note this is interesting because builder.py has a few if clauses
for generating the necessary script

# pmacAsynIPConfigure AsynPortName IPAddress
pmacAsynIPConfigure BRICK1port 192.168.0.12:1112:1025

# pmacCreateController AsynPortName PmacAsynPort Address NumAxes MovingPollPeriod IdlePollPeriod
pmacCreateController BL45P-MO-BRICK-01 BRICK1port 0 8 500 100
pmacCreateAxes BL45P-MO-BRICK-01 8
# ASYNSerial Startup ENTRY
# TODO provide Jinja to generate ASYN Startup Entries
# note this is interesting because builder.py has a few if clauses
# for generating the necessary script
#

dbLoadRecords /tmp/ioc.db
iocInit
Expand Down
4 changes: 3 additions & 1 deletion tests/samples/boot_scripts/stbl45p-mo-ioc-04
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ dbLoadRecords /tmp/ioc.db
iocInit


dbpf "BL45P-MO-THIN-01:Y1.TWV", "0.5"

# dbpf pv value
dbpf BL45P-MO-THIN-01:Y1.TWV 0.5

4 changes: 2 additions & 2 deletions tests/samples/generate_samples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ ibek build-startup ${SAMPLES_DIR}/example-srrfioc08/SR-RF-IOC-08.ibek.ioc.yaml $
cp /tmp/ioc/st.cmd ${SAMPLES_DIR}/example-srrfioc08
cp /tmp/ioc/make_db.sh ${SAMPLES_DIR}/example-srrfioc08

echo makgin values_test IOC
ibek build-startup ${SAMPLES_DIR}/values_test/values.ibek.ioc.yaml ${SAMPLES_DIR}/values_test/*.support.yaml --out /tmp/ioc/st.cmd --db-out /tmp/ioc/make_db.sh
echo making values_test IOC
ibek build-startup ${SAMPLES_DIR}/values_test/values.ibek.ioc.yaml ${DEFS}/*/*.support.yaml --out /tmp/ioc/st.cmd --db-out /tmp/ioc/make_db.sh
cp /tmp/ioc/st.cmd ${SAMPLES_DIR}/values_test


Expand Down
Loading

0 comments on commit b05d34f

Please sign in to comment.