diff --git a/.gitignore b/.gitignore index b7faf40..4aef921 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Output in examples +examples/output/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] diff --git a/Makefile b/Makefile index 640171b..b3cf4ce 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ PYTHON ?= python3 all: check-format check install: - pipx install . + pipx install --force . check: $(MAKE) -C tests check diff --git a/data/ecss-template/ecss-e-st-40c_4_1_software_static_architecture.tmplt b/data/ecss-template/ecss-e-st-40c_4_1_software_static_architecture.tmplt new file mode 100644 index 0000000..a8274c2 --- /dev/null +++ b/data/ecss-template/ecss-e-st-40c_4_1_software_static_architecture.tmplt @@ -0,0 +1,82 @@ +<% +## Data Initialization +# Get all functions + +funcs = [] +def get_function_children(func): + result = [] + if func.nested_functions: + for nested in func.nested_functions: + result.append(nested) + result.extend(get_function_children(nested)) + return result + +for func in interface_view.functions: + funcs.append(func) + funcs.extend(get_function_children(func)) + +funcs.sort(key=lambda f: f.name.lower()) + +# Get functions deployed to the target partition +target_partition_name = values["TARGET"] + +deployed_funcs = [] +target_partition = None +target_node = None +for node in deployment_view.nodes: + for partition in node.partitions: + if partition.name == target_partition_name: + target_node = node + target_partition = partition + +deployed_func_names = [f.name for f in target_partition.functions] +for fun in funcs: + if fun.name in deployed_func_names: + deployed_funcs.append(fun) + +# Only leaf functions are deployed, so a correction for parents must be applied +for func in funcs: + if func.nested_functions: + for nested in func.nested_functions: + if nested in deployed_funcs and not func in deployed_funcs: + deployed_funcs.append(func) + deployed_func_names.append(func.name) + +# Get used implementations +languages = set() +for func in deployed_funcs: + languages.add(func.language) + +%> + +The software architecture of ${target_partition_name} consists of ${len(deployed_funcs)} functions deployed onto ${target_node.name} node. +The functions use the following implementation technologies: ${",".join([l.value for l in languages])}. +The top-level components are as follows: +% for func in interface_view.functions: +<% +if not func.name in deployed_func_names: + continue +is_composite = func.nested_functions and len(func.nested_functions) > 0 +implementation_text = "[COMPOSITE] " if is_composite else func.language.value +%> + +- ${func.name} [${implementation_text}] - ${func.comment} +% endfor +## Print the level 2 functions +% for func in interface_view.functions: +<% +if not func.nested_functions or len(func.nested_functions) == 0: + continue +%> + +Function ${func.name} is a composite, containing the following sub-functions: +% for nested in func.nested_functions: +<% +is_composite = nested.nested_functions and len(nested.nested_functions) > 0 +implementation_text = "[COMPOSITE] " if is_composite else nested.language.value +%> + +- ${nested.name} [${implementation_text}] - ${nested.comment} +% endfor + +% endfor \ No newline at end of file diff --git a/data/ecss-template/ecss-e-st-40c_4_2_software_dynamic_architecture.tmplt b/data/ecss-template/ecss-e-st-40c_4_2_software_dynamic_architecture.tmplt new file mode 100644 index 0000000..adbfb7b --- /dev/null +++ b/data/ecss-template/ecss-e-st-40c_4_2_software_dynamic_architecture.tmplt @@ -0,0 +1,74 @@ +<% +## Data Initialization +# Get all functions + +funcs = [] +def get_function_children(func): + result = [] + if func.nested_functions: + for nested in func.nested_functions: + result.append(nested) + result.extend(get_function_children(nested)) + return result + +for func in interface_view.functions: + funcs.append(func) + funcs.extend(get_function_children(func)) + +funcs.sort(key=lambda f: f.name.lower()) + +# Get functions deployed to the target partition +target_partition_name = values["TARGET"] + +deployed_funcs = [] +target_partition = None +for node in deployment_view.nodes: + for partition in node.partitions: + if partition.name == target_partition_name: + target_partition = partition + +deployed_func_names = [f.name for f in target_partition.functions] +for fun in funcs: + if fun.name in deployed_func_names: + deployed_funcs.append(fun) + +# Get all threads +threads = [] +for func in deployed_funcs: + for pi in func.provided_interfaces: + if pi.kind.value == "Sporadic" or pi.kind.value == "Cyclic": + threads.append((func, pi)) + +%> + + +The list below summarizes all threads of the user components. +% for (func, pi) in threads: + +${"##"} [${pi.kind.value}] ${func.name}::${pi.name} + +- Description: ${pi.comment} + +- Stack size: ${pi.stack_size} + +% if pi.kind.value == "Sporadic": +- Queue size: ${pi.queue_size} + +% endif +- Priority: ${pi.priority} + +% if pi.kind.value == "Cyclic": +- Period: ${pi.period} + +- Dispatch offset : ${pi.dispatch_offset} +%endif + +% if pi.wcet is not None and pi.wcet != 0: +- WCET: ${pi.wcet} + +%endif +% if pi.miat is not None and pi.miat != 0: +- MIAT: ${pi.miat} + +% endif +% endfor diff --git a/data/ecss-template/ecss-e-st-40c_4_4_interfaces_context.tmplt b/data/ecss-template/ecss-e-st-40c_4_4_interfaces_context.tmplt new file mode 100644 index 0000000..bfc8968 --- /dev/null +++ b/data/ecss-template/ecss-e-st-40c_4_4_interfaces_context.tmplt @@ -0,0 +1,165 @@ +<% +## Data Initialization +# Get all functions + +def find_by_name(all, name): + for f in all: + if f.name == name: + return f + return None + +funcs = [] +def get_function_children(func): + result = [] + if func.nested_functions: + for nested in func.nested_functions: + result.append(nested) + result.extend(get_function_children(nested)) + return result + +for func in interface_view.functions: + funcs.append(func) + funcs.extend(get_function_children(func)) + +funcs.sort(key=lambda f: f.name.lower()) + +# Get functions deployed to the target partition +target_partition_name = values["TARGET"] + +deployed_funcs = [] +target_partition = None +for node in deployment_view.nodes: + for partition in node.partitions: + if partition.name == target_partition_name: + target_partition = partition + +deployed_func_names = [f.name for f in target_partition.functions] +for fun in funcs: + if fun.name in deployed_func_names: + deployed_funcs.append(fun) + +# Only leaf functions are deployed, so a correction for parents must be applied +for func in funcs: + if func.nested_functions: + for nested in func.nested_functions: + if nested in deployed_funcs and not func in deployed_funcs: + deployed_funcs.append(func) + deployed_func_names.append(func.name) + +# Find and crossreference all connections +def get_function_connections(func): + result = [] + if func.nested_connections: + result.extend(func.nested_connections) + if func.nested_functions: + for nested in func.nested_functions: + result.extend(get_function_connections(nested)) + return result + +connections = [] +connections.extend(interface_view.connections) +for func in interface_view.functions: + connections.extend(get_function_connections(func)) + +external_connections = [] + +for connection in connections: + if connection.source is None or connection.source.function_name is None: + continue + if connection.target is None or connection.target.function_name is None: + continue + target_inside = connection.target.function_name in deployed_func_names + source_inside = connection.source.function_name in deployed_func_names + if (target_inside and not source_inside) or (not target_inside and source_inside): + external_connections.append(connection) + +iface_source_map = {} +iface_target_map = {} + +for connection in external_connections: + meta = {} + meta["connection"] = connection + meta["source_function"] = find_by_name(funcs, connection.source.function_name) + if meta["source_function"] is None: + print(f"Source function {connection.source.function_name} not found") + continue + meta["source_iface"] = find_by_name(meta["source_function"].provided_interfaces + meta["source_function"].required_interfaces, connection.source.iface_name) + meta["target_function"] = find_by_name(funcs, connection.target.function_name) + if meta["target_function"] is None: + print(f"Target function {connection.target.function_name} not found") + continue + meta["target_iface"] = find_by_name(meta["target_function"].provided_interfaces + meta["target_function"].required_interfaces, connection.source.iface_name) + source_handle = f"{meta["source_function"].name}__{meta["source_iface"].name}" + target_handle = f"{meta["target_function"].name}__{meta["target_iface"].name}" + if not source_handle in iface_source_map.keys(): + iface_source_map[source_handle] = [] + iface_source_map[source_handle].append((meta["target_function"], meta["target_iface"])) + if not target_handle in iface_target_map.keys(): + iface_target_map[target_handle] = [] + iface_target_map[target_handle].append((meta["source_function"], meta["source_iface"])) + +%> + +The list below summarizes all external interfaces. + +% for func in deployed_funcs: +% for iface in func.provided_interfaces + func.required_interfaces: +<% +iface_handle = f"{func.name}__{iface.name}" +if not iface_handle in iface_source_map and not iface_handle in iface_target_map: + continue +%> +${"#"} \ +% if iface in func.provided_interfaces: +[PROVIDED] \ +% else: +[REQUIRED] \ +% endif +[${iface.kind.value}] ${func.name}::${iface.name} + +- Description: ${iface.comment} + +% if iface.kind.value != "Cyclic": +- Parameters: +% for param in iface.input_parameters + iface.output_parameters: + + - \ +% if param in iface.input_parameters: +[IN] \ +% else: +[OUT] \ +% endif +${param.name} ${param.type} (with ${param.encoding.value} encoding) +% endfor +% endif + +% if iface_handle in iface_source_map: +- Connects to: +% for (other_function, other_iface) in iface_source_map[iface_handle]: + + - ${other_function.name}::${other_iface.name} +% endfor +% endif + +% if iface_handle in iface_target_map: +- Is connected from: +% for (other_function, other_iface) in iface_target_map[iface_handle]: + + - ${other_function.name}::${other_iface.name} +% endfor +% endif + +% endfor +% endfor + +The list below summarizes all external connections: +% for connection in external_connections: +<% + if connection.source is None or connection.source.function_name is None: + continue + if connection.target is None or connection.target.function_name is None: + continue +%> \ + +- Connection from ${connection.source.function_name}::${connection.source.iface_name} to ${connection.target.function_name}::${connection.target.iface_name} +% endfor \ No newline at end of file diff --git a/data/ecss-template/ecss-e-st-40c_5_2_overall_architecture.tmplt b/data/ecss-template/ecss-e-st-40c_5_2_overall_architecture.tmplt new file mode 100644 index 0000000..7ad8a05 --- /dev/null +++ b/data/ecss-template/ecss-e-st-40c_5_2_overall_architecture.tmplt @@ -0,0 +1,102 @@ +<% +## Data Initialization +# Get all functions + +funcs = [] +def get_function_children(func): + result = [] + if func.nested_functions: + for nested in func.nested_functions: + result.append(nested) + result.extend(get_function_children(nested)) + return result + +for func in interface_view.functions: + funcs.append(func) + funcs.extend(get_function_children(func)) + +funcs.sort(key=lambda f: f.name.lower()) + +# Get functions deployed to the target partition +target_partition_name = values["TARGET"] + +deployed_funcs = [] +target_partition = None +target_node = None +for node in deployment_view.nodes: + for partition in node.partitions: + if partition.name == target_partition_name: + target_node = node + target_partition = partition + +deployed_func_names = [f.name for f in target_partition.functions] +for fun in funcs: + if fun.name in deployed_func_names: + deployed_funcs.append(fun) + +# Only leaf functions are deployed, so a correction for parents must be applied +for func in funcs: + if func.nested_functions: + for nested in func.nested_functions: + if nested in deployed_funcs and not func in deployed_funcs: + deployed_funcs.append(func) + deployed_func_names.append(func.name) + +%> + +Components of ${target_partition_name} are enumerated in 4.1 Software Static Architecture, while threads are de described in 4.2 Software Dynamic Architecture. +Details of each of the components are provided in 5.4 Aspects of each Component. Scheduling is pre-emptive, multithreaded, provided by ${node.type}. +<% +mutex_function_names = set() +thread_function_names = set() +for func in deployed_funcs: + if len(func.nested_functions) > 0: + continue + for pi in func.provided_interfaces: + if pi.kind.value != "Unprotected": + mutex_function_names.add(func.name) + if pi.kind.value == "Cyclic" or pi.kind.value == "Sporadic": + thread_function_names.add(func.name) +%> +The following functions host threads for cyclic or sporadic interfaces (described in Chapter 4.2): +% for func in deployed_funcs: +<% +if not func.name in thread_function_names: + continue +%> + +- ${func.name} +% endfor + +The following functions host semaphores, due to providing at least once cyclic, sporadic or protected interface: +% for func in deployed_funcs: +<% +if not func.name in mutex_function_names: + continue +%> + +- ${func.name} +% endfor + +Table below lists the queues that the components interact through. Connections are described in detail in Chapter 5.5. + +| Queue | Item Type | Size | +| - | - | - | +% for func in deployed_funcs: +<% +if len(func.nested_functions) > 0: + continue +%>\ +% for pi in func.provided_interfaces: +<% +if pi.kind.value != "Sporadic": + continue + +param = "Dummy Item" +if len(pi.input_parameters) > 0: + param = pi.input_parameters[0].type +%>\ +| ${func.name}::${pi.name} | ${param} | ${pi.queue_size} | +% endfor +% endfor + diff --git a/data/ecss-template/ecss-e-st-40c_5_3_software_components_design.tmplt b/data/ecss-template/ecss-e-st-40c_5_3_software_components_design.tmplt new file mode 100644 index 0000000..467b979 --- /dev/null +++ b/data/ecss-template/ecss-e-st-40c_5_3_software_components_design.tmplt @@ -0,0 +1,51 @@ +<% +## Data Initialization +# Get all functions + +funcs = [] +def get_function_children(func): + result = [] + if func.nested_functions: + for nested in func.nested_functions: + result.append(nested) + result.extend(get_function_children(nested)) + return result + +for func in interface_view.functions: + funcs.append(func) + funcs.extend(get_function_children(func)) + +funcs.sort(key=lambda f: f.name.lower()) + +# Get functions deployed to the target partition +target_partition_name = values["TARGET"] + +deployed_funcs = [] +target_partition = None +for node in deployment_view.nodes: + for partition in node.partitions: + if partition.name == target_partition_name: + target_partition = partition + +deployed_func_names = [f.name for f in target_partition.functions] +for fun in funcs: + if fun.name in deployed_func_names: + deployed_funcs.append(fun) + +# Only leaf functions are deployed, so a correction for parents must be applied +for func in funcs: + if func.nested_functions: + for nested in func.nested_functions: + if nested in deployed_funcs and not func in deployed_funcs: + deployed_funcs.append(func) + deployed_func_names.append(func.name) + +%> + +The below table lists all components of ${target_partition_name}. + +| Function | Type | Description | +| -| - | - | +% for func in deployed_funcs: +| ${func.name} | ${func.language.value if len(func.nested_functions) == 0 else "COMPOSITE" } | ${func.comment} | +% endfor \ No newline at end of file diff --git a/data/ecss-template/ecss-e-st-40c_5_4_aspects_of_each_component.tmplt b/data/ecss-template/ecss-e-st-40c_5_4_aspects_of_each_component.tmplt new file mode 100644 index 0000000..d878524 --- /dev/null +++ b/data/ecss-template/ecss-e-st-40c_5_4_aspects_of_each_component.tmplt @@ -0,0 +1,84 @@ +<% +## Data Initialization +# Get all functions + +funcs = [] +def get_function_children(func): + result = [] + if func.nested_functions: + for nested in func.nested_functions: + result.append(nested) + result.extend(get_function_children(nested)) + return result + +for func in interface_view.functions: + funcs.append(func) + funcs.extend(get_function_children(func)) + +funcs.sort(key=lambda f: f.name.lower()) + +# Get functions deployed to the target partition +target_partition_name = values["TARGET"] + +deployed_funcs = [] +target_partition = None +for node in deployment_view.nodes: + for partition in node.partitions: + if partition.name == target_partition_name: + target_partition = partition + +deployed_func_names = [f.name for f in target_partition.functions] +for fun in funcs: + if fun.name in deployed_func_names: + deployed_funcs.append(fun) + +# Only leaf functions are deployed, so a correction for parents must be applied +for func in funcs: + if func.nested_functions: + for nested in func.nested_functions: + if nested in deployed_funcs and not func in deployed_funcs: + deployed_funcs.append(func) + deployed_func_names.append(func.name) + +%> + +The below chapters summarize aspects of each component. + +% for func in deployed_funcs: + +${"#"} ${func.name} + +Component Identifier: ${func.name} + +Type: TASTE ${func.language.value} Function + +Purpose: ${func.comment} + +Subordinates: ${",".join([child.name for child in func.nested_functions])} + +Dependencies: N/A + +Required Interfaces: + +% for ri in func.required_interfaces: + +- [${str(ri.kind.value).lower()}] ${ri.name} +% endfor + +Provided Interfaces: +% for pi in func.provided_interfaces: + +- [${str(pi.kind.value).lower()}] ${pi.name} +% endfor + +Resources: N/A + +References: N/A + +Data: N/A + +Backward Requirement Trace: ${", ".join(func.requirement_ids) } + +Forward Requirement Trace: Described in Chapter 6 + +% endfor diff --git a/data/ecss-template/ecss-e-st-40c_5_5_internal_interface_design.tmplt b/data/ecss-template/ecss-e-st-40c_5_5_internal_interface_design.tmplt new file mode 100644 index 0000000..dd2ecac --- /dev/null +++ b/data/ecss-template/ecss-e-st-40c_5_5_internal_interface_design.tmplt @@ -0,0 +1,166 @@ +<% +## Data Initialization +# Get all functions + +def find_by_name(all, name): + for f in all: + if f.name == name: + return f + return None + +funcs = [] +def get_function_children(func): + result = [] + if func.nested_functions: + for nested in func.nested_functions: + result.append(nested) + result.extend(get_function_children(nested)) + return result + +for func in interface_view.functions: + funcs.append(func) + funcs.extend(get_function_children(func)) + +funcs.sort(key=lambda f: f.name.lower()) + +# Get functions deployed to the target partition +target_partition_name = values["TARGET"] + +deployed_funcs = [] +target_partition = None +for node in deployment_view.nodes: + for partition in node.partitions: + if partition.name == target_partition_name: + target_partition = partition + +deployed_func_names = [f.name for f in target_partition.functions] +for fun in funcs: + if fun.name in deployed_func_names: + deployed_funcs.append(fun) + +# Only leaf functions are deployed, so a correction for parents must be applied +for func in funcs: + if func.nested_functions: + for nested in func.nested_functions: + if nested in deployed_funcs and not func in deployed_funcs: + deployed_funcs.append(func) + deployed_func_names.append(func.name) + + + +# Find and crossreference all connections +def get_function_connections(func): + result = [] + if func.nested_connections: + result.extend(func.nested_connections) + if func.nested_functions: + for nested in func.nested_functions: + result.extend(get_function_connections(nested)) + return result + +connections = [] +connections.extend(interface_view.connections) +for func in interface_view.functions: + connections.extend(get_function_connections(func)) + +internal_connections = [] + +for connection in connections: + if connection.source is None or connection.source.function_name is None: + continue + if connection.target is None or connection.target.function_name is None: + continue + if connection.target.function_name in deployed_func_names and connection.source.function_name in deployed_func_names: + internal_connections.append(connection) + +iface_source_map = {} +iface_target_map = {} + +for connection in internal_connections: + meta = {} + meta["connection"] = connection + meta["source_function"] = find_by_name(funcs, connection.source.function_name) + if meta["source_function"] is None: + print(f"Source function {connection.source.function_name} not found") + continue + meta["source_iface"] = find_by_name(meta["source_function"].provided_interfaces + meta["source_function"].required_interfaces, connection.source.iface_name) + meta["target_function"] = find_by_name(funcs, connection.target.function_name) + if meta["target_function"] is None: + print(f"Target function {connection.target.function_name} not found") + continue + meta["target_iface"] = find_by_name(meta["target_function"].provided_interfaces + meta["target_function"].required_interfaces, connection.source.iface_name) + source_handle = f"{meta["source_function"].name}__{meta["source_iface"].name}" + target_handle = f"{meta["target_function"].name}__{meta["target_iface"].name}" + if not source_handle in iface_source_map.keys(): + iface_source_map[source_handle] = [] + iface_source_map[source_handle].append((meta["target_function"], meta["target_iface"])) + if not target_handle in iface_target_map.keys(): + iface_target_map[target_handle] = [] + iface_target_map[target_handle].append((meta["source_function"], meta["source_iface"])) + +%> + +The list below summarizes all internal interfaces between user components. + +% for func in deployed_funcs: +% for iface in func.provided_interfaces + func.required_interfaces: +<% +iface_handle = f"{func.name}__{iface.name}" +if not iface_handle in iface_source_map and not iface_handle in iface_target_map: + continue +%> + +${"#"} \ +% if iface in func.provided_interfaces: +[PROVIDED] \ +% else: +[REQUIRED] \ +% endif +[${iface.kind.value}] ${func.name}::${iface.name} + +- Description: ${iface.comment} + +% if iface.kind.value != "Cyclic": +- Parameters: +% for param in iface.input_parameters + iface.output_parameters: + + - \ +% if param in iface.input_parameters: +[IN] \ +% else: +[OUT] \ +% endif +${param.name} ${param.type} (with ${param.encoding.value} encoding) +% endfor +% endif + +% if iface_handle in iface_source_map: +- Connects to: +% for (other_function, other_iface) in iface_source_map[iface_handle]: + + - ${other_function.name}::${other_iface.name} +% endfor +% endif + +% if iface_handle in iface_target_map: +- Is connected from: +% for (other_function, other_iface) in iface_target_map[iface_handle]: + + - ${other_function.name}::${other_iface.name} +% endfor +% endif + +% endfor +% endfor + +The list below summarizes all internal connections between the interfaces: +% for connection in internal_connections: +<% + if connection.source is None or connection.source.function_name is None: + continue + if connection.target is None or connection.target.function_name is None: + continue +%> \ + +- Connection from ${connection.source.function_name}::${connection.source.iface_name} to ${connection.target.function_name}::${connection.target.iface_name} +% endfor \ No newline at end of file diff --git a/data/ecss-template/ecss-e-st-40c_6_requirement_traceability.tmplt b/data/ecss-template/ecss-e-st-40c_6_requirement_traceability.tmplt new file mode 100644 index 0000000..816aee3 --- /dev/null +++ b/data/ecss-template/ecss-e-st-40c_6_requirement_traceability.tmplt @@ -0,0 +1,63 @@ +<% +## Data Initialization +# Get all functions + +funcs = [] +def get_function_children(func): + result = [] + if func.nested_functions: + for nested in func.nested_functions: + result.append(nested) + result.extend(get_function_children(nested)) + return result + +for func in interface_view.functions: + funcs.append(func) + funcs.extend(get_function_children(func)) + +# Get functions deployed to the target partition +target_partition_name = values["TARGET"] + +deployed_funcs = [] +target_partition = None +for node in deployment_view.nodes: + for partition in node.partitions: + if partition.name == target_partition_name: + target_partition = partition + +deployed_func_names = [f.name for f in target_partition.functions] +for fun in funcs: + if fun.name in deployed_func_names: + deployed_funcs.append(fun) + +# Get all requirements +req_map = {} +for func in deployed_funcs: + for req_id in func.requirement_ids: + if not req_id in req_map: + req_map[req_id] = [] + req_map[req_id].append(func) + +req_ids = sorted(req_map.keys()) # This can be sourced from an external (e.g., JSON) file, if needed + +%> + +${"#"} Forward traceability matrix + +| Requirement ID | Components | +| - | - | +% for id in req_ids: +<% +if not id: + continue +%> \ +| ${id} | ${",".join([func.name for func in req_map[id]])} | +% endfor + +${"#"} Backward traceability matrix + +| Component | Requirement IDs | +| - | - | +% for func in deployed_funcs: +| ${func.name} | ${",".join(func.requirement_ids)} | +% endfor diff --git a/data/requirements.iv.xml b/data/requirements.iv.xml new file mode 100644 index 0000000..43f932e --- /dev/null +++ b/data/requirements.iv.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/demo-project/.gitignore b/examples/demo-project/.gitignore new file mode 100644 index 0000000..30c34ba --- /dev/null +++ b/examples/demo-project/.gitignore @@ -0,0 +1,6 @@ +*.aadl +*.pro.user +*.pro.user.* +.qtc* +# We don't need the details to compile the project, only ASN.1/ACN, IV and DV are of interest +work/ \ No newline at end of file diff --git a/examples/demo-project/Makefile b/examples/demo-project/Makefile new file mode 100644 index 0000000..c9e6d9b --- /dev/null +++ b/examples/demo-project/Makefile @@ -0,0 +1,144 @@ +KAZOO?=kazoo +SPACECREATOR?=spacecreator.AppImage + +# Here you can specify custom compiler/linker flags, and add folders containing +# external code you want to compile and link for a specific partition. +# Use upper case for the partition name: +# +# export _USER_CFLAGS=... +# export _USER_LDFLAGS=... +# export _EXTERNAL_SOURCE_PATH=... +# +# NOTE: this can also be done in the Deployment View directly + +# If you need to reset this Makefile to its original state, run: +# $ taste reset + +# Disable the progress bar from taste-update-data-view when building systems +export NO_PROGRESS_BAR=1 + +# Get the list of ASN.1 files from Space Creator project file: +DISTFILES=$(shell qmake demo-project.pro -o /tmp/null 2>&1) +# Exclude system.asn here as the types inside do not changes - only the values in the PID +# enumeration change, but this does not affect the content of DataView.aadl +ASN1_FILES=$(shell find ${DISTFILES} 2>/dev/null | egrep '\.asn$$|\.asn1$$' | grep -v system.asn) + +all: release + +include Makefile.modelcheck + +release: work/glue_release + rm -rf work/glue_debug + rm -rf work/glue_coverage + $(MAKE) -C work check_targets + $(MAKE) -C work + +debug: work/glue_debug + rm -rf work/glue_release + rm -rf work/glue_coverage + $(MAKE) -C work check_targets + $(MAKE) -C work + +coverage: work/glue_coverage + rm -rf work/glue_release + rm -rf work/glue_debug + $(MAKE) -C work check_targets + $(MAKE) -C work + +# To build and run the system type e.g. 'make debug run' +run: + $(MAKE) -C work run + +# To run Cheddar/Marzhin for scheduling analysis, type 'make edit_cv' +edit_cv: + $(MAKE) -C work run_cv + +# Simulation target (experimental - for systems made of SDL functions only) +simu: + if [ -f work/glue_debug ] || [ -f work/glue_release ] || [ -f work/glue_coverage ]; then $(MAKE) clean; fi + $(MAKE) interfaceview work/glue_simu + $(MAKE) -C work + $(MAKE) -C work/simulation -f Makefile.Simulation simu + +# Simulation and model checking: shortcut to create observer.asn +observer_dataview: + $(MAKE) DataView.aadl + $(MAKE) InterfaceView.aadl + $(MAKE) interfaceview + $(KAZOO) --glue -t SIMU + $(MAKE) -C work dataview/dataview-uniq.asn + $(MAKE) -C work/build -f Makefile.taste observer.asn + +skeletons: + $(MAKE) work/skeletons_built + +work/skeletons_built: InterfaceView.aadl DataView.aadl + $(KAZOO) --gw -o work + $(MAKE) -C work dataview simulink_skeletons + touch DataView.aadl # to avoid rebuilds due to new system.asn + touch $@ + +work/glue_simu: InterfaceView.aadl DataView.aadl + $(KAZOO) -t SIMU --glue --gw + $(MAKE) -C work dataview + touch DataView.aadl + touch $@ + +work/glue_release: InterfaceView.aadl DeploymentView.aadl DataView.aadl + sed -i 's/CoverageEnabled => true/CoverageEnabled => false/g' DeploymentView.aadl || : + $(KAZOO) -p --glue --gw -o work + touch DataView.aadl + touch $@ + +work/glue_debug: InterfaceView.aadl DeploymentView.aadl DataView.aadl + sed -i 's/CoverageEnabled => true/CoverageEnabled => false/g' DeploymentView.aadl || : + $(KAZOO) --debug -p --glue --gw -o work + touch DataView.aadl + touch $@ + +work/glue_coverage: InterfaceView.aadl DeploymentView.aadl DataView.aadl + sed -i 's/CoverageEnabled => false/CoverageEnabled => true/g' DeploymentView.aadl || : + $(KAZOO) --debug -p --glue --gw -o work + touch DataView.aadl + touch $@ + +InterfaceView.aadl: interfaceview.xml + $(SPACECREATOR) --aadlconverter -o $^ -t $(shell taste-config --prefix)/share/xml2aadl/interfaceview.tmplt -x $@ + +%: %.dv.xml Default_Deployment.aadl + # Build using deployment view $^ + @# We must update the .aadl only if the dv.xml file has changed (more recent timestamp) + if [ $< -nt $@.aadl ]; then $(SPACECREATOR) --dvconverter -o $< -t $(shell taste-config --prefix)/share/dv2aadl/deploymentview.tmplt -x $@.aadl; fi; + rsync --checksum $@.aadl DeploymentView.aadl + +interfaceview: Default_Deployment.aadl + # Build when no deployment view is open - use default + rsync --checksum $< DeploymentView.aadl + +Default_Deployment.aadl: interfaceview.xml + # Create/update a default deployment view for Linux target, if none other is provided + $(SPACECREATOR) --aadlconverter -o $^ -t $(shell taste-config --prefix)/share/xml2dv/interfaceview.tmplt -x $@ || exit 1 + rsync --checksum $@ DeploymentView.aadl + +DeploymentView.aadl: Default_Deployment.aadl + +DataView.aadl: ${ASN1_FILES} + $(info Generating/Updating DataView.aadl) + taste-update-data-view $^ work/system.asn + +clean: + rm -rf work/build work/dataview work/glue_simu + rm -f *.aadl # Interface and Deployment views in AADL are generated + rm -f work/glue_release work/glue_debug work/glue_coverage work/skeletons_built + find work -type d -name "wrappers" -exec rm -rf {} + || : + find work -type d -name "*_GUI" -exec rm -rf {} + || : + find work -type d -path "*/QGenC/xmi" -exec rm -rf {} + || : + find work -type d -path "*/QGenC/src/.qgeninfo" -exec rm -rf {} + || : + find work -type d -path "*/QGenC/src/slprj" -exec rm -rf {} + || : + find work -type f -path "*/QGenC/src/built" -exec rm -f {} + || : + find work -type f -path "*/QGenC/src/*.slxc" -exec rm -f {} + || : + find work -type f -path "*/QGenC/src/*.h" -not -name "simulink_definition_of_types.h" -not -name "*_invoke_ri.h" -exec rm -f {} + || : + find work -type f -path "*/QGenC/src/*.c" -exec rm -f {} + || : + +.PHONY: clean release debug coverage skeletons simu run simulink_skeletons + diff --git a/examples/demo-project/Makefile.modelcheck b/examples/demo-project/Makefile.modelcheck new file mode 100644 index 0000000..f07a8f9 --- /dev/null +++ b/examples/demo-project/Makefile.modelcheck @@ -0,0 +1,36 @@ +model-check: InterfaceView.aadl DeploymentView.aadl DataView.aadl + $(KAZOO) -gw --glue -t MOCHECK + $(MAKE) -C work model-check + +create-obs: work/modelchecking/properties work/modelchecking observer_dataview + mkdir -p work/modelchecking/properties/$(NAME) + make -C work obs-skeleton NAME=$(NAME) + +create-msc: work/modelchecking/properties work/modelchecking observer_dataview + mkdir -p work/modelchecking/properties/$(NAME) + make -C work msc-skeleton NAME=$(NAME) + +create-bsc: work/modelchecking/properties work/modelchecking observer_dataview + mkdir -p work/modelchecking/properties/$(NAME) + make -C work bsc-skeleton NAME=$(NAME) + +work/modelchecking/properties: + mkdir -p work/modelchecking/properties + +work/modelchecking: + mkdir -p work/modelchecking + +create-subtype: work/modelchecking/subtypes work/modelchecking + find work/ -path work/binaries -prune -o -name subtype_*.asn -exec cat {} \; > work/modelchecking/subtypes/$(NAME).asn + +work/modelchecking/subtypes: + mkdir -p work/modelchecking/subtypes + +# Native model cheker target (experimental - for systems made of SDL functions only) +native_modelchecker: + if [ -f work/glue_debug ] || [ -f work/glue_release ] || [ -f work/glue_coverage ]; then $(MAKE) clean; fi + $(MAKE) interfaceview work/glue_simu + $(MAKE) -C work + $(MAKE) -C work/simulation -f Makefile.Simulation modelcheck + cd work/simulation && ./modelcheck + diff --git a/examples/demo-project/demo-project.acn b/examples/demo-project/demo-project.acn new file mode 100644 index 0000000..b87156b --- /dev/null +++ b/examples/demo-project/demo-project.acn @@ -0,0 +1,4 @@ +DEMO-PROJECT-DATAVIEW DEFINITIONS ::= BEGIN + +END + diff --git a/examples/demo-project/demo-project.asn b/examples/demo-project/demo-project.asn new file mode 100644 index 0000000..83057fa --- /dev/null +++ b/examples/demo-project/demo-project.asn @@ -0,0 +1,21 @@ +DEMO-PROJECT-DATAVIEW DEFINITIONS ::= +BEGIN + + MyInteger ::= INTEGER (0 .. 10000) + +-- Dummy types with different names for documentation purposes + + TC ::= INTEGER (0..10) + TM ::= INTEGER (0..10) + + Request ::= INTEGER (0..10) + Response ::= INTEGER (0..10) + + ParameterValue ::= INTEGER (0..10) + + Cmd ::= INTEGER (0..10) + + RawData ::= INTEGER (0..10) + +END + diff --git a/examples/demo-project/demo-project.pro b/examples/demo-project/demo-project.pro new file mode 100644 index 0000000..2978d60 --- /dev/null +++ b/examples/demo-project/demo-project.pro @@ -0,0 +1,18 @@ +TEMPLATE = lib +CONFIG -= qt +CONFIG += generateC + +DISTFILES += $(HOME)/tool-inst/share/taste-types/taste-types.asn \ + deploymentview.dv.xml +DISTFILES += demo-project.msc +DISTFILES += interfaceview.xml +DISTFILES += work/binaries/*.msc +DISTFILES += work/binaries/coverage/index.html +DISTFILES += work/binaries/filters +DISTFILES += work/system.asn + +DISTFILES += demo-project.asn +DISTFILES += demo-project.acn +include(work/taste.pro) +message($$DISTFILES) + diff --git a/examples/demo-project/deploymentview.dv.xml b/examples/demo-project/deploymentview.dv.xml new file mode 100755 index 0000000..8946e23 --- /dev/null +++ b/examples/demo-project/deploymentview.dv.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/demo-project/deploymentview.ui.xml b/examples/demo-project/deploymentview.ui.xml new file mode 100644 index 0000000..b3385c4 --- /dev/null +++ b/examples/demo-project/deploymentview.ui.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/demo-project/interfaceview.ui.xml b/examples/demo-project/interfaceview.ui.xml new file mode 100644 index 0000000..32ec9c8 --- /dev/null +++ b/examples/demo-project/interfaceview.ui.xml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/demo-project/interfaceview.xml b/examples/demo-project/interfaceview.xml new file mode 100644 index 0000000..4e7d949 --- /dev/null +++ b/examples/demo-project/interfaceview.xml @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/dv.tmplt b/examples/dv.tmplt new file mode 100644 index 0000000..fe01d50 --- /dev/null +++ b/examples/dv.tmplt @@ -0,0 +1,20 @@ +${"#"} Deployment View +% for node in deployment_view.nodes: +% for partition in node.partitions: + +${"##"} ${partition.name}: +% for func in partition.functions: +- ${func.name} +% endfor +% endfor +% endfor + +${"##"} Connections: + +% for connection in deployment_view.connections: +${connection.from_node} <-> ${connection.to_node} +% for message in connection.messages: + +- ${message.from_function}.${message.from_interface} -> ${message.to_function}.${message.to_interface} +% endfor +% endfor diff --git a/examples/generate_dv.sh b/examples/generate_dv.sh new file mode 100755 index 0000000..f61098d --- /dev/null +++ b/examples/generate_dv.sh @@ -0,0 +1,4 @@ +#!/bin/bash +mkdir -p output +template-processor --verbosity info --dv ../data/deploymentview.dv.xml -o output -t dv.tmplt +pandoc --pdf-engine=pdfroff --output=output/dv.pdf output/dv.md \ No newline at end of file diff --git a/examples/generate_ecss_demo.sh b/examples/generate_ecss_demo.sh new file mode 100755 index 0000000..ba562eb --- /dev/null +++ b/examples/generate_ecss_demo.sh @@ -0,0 +1,26 @@ +#!/bin/bash +mkdir -p output + +template-processor --verbosity info --value TARGET=ASW --iv demo-project/interfaceview.xml --dv demo-project/deploymentview.dv.xml -o output -t ../data/ecss-template/ecss-e-st-40c_4_1_software_static_architecture.tmplt +pandoc --pdf-engine=pdfroff --output=output/ecss-e-st-40c_4_1_software_static_architecture.pdf output/ecss-e-st-40c_4_1_software_static_architecture.md + +template-processor --verbosity info --value TARGET=ASW --iv demo-project/interfaceview.xml --dv demo-project/deploymentview.dv.xml -o output -t ../data/ecss-template/ecss-e-st-40c_4_2_software_dynamic_architecture.tmplt +pandoc --pdf-engine=pdfroff --output=output/ecss-e-st-40c_4_2_software_dynamic_architecture.pdf output/ecss-e-st-40c_4_2_software_dynamic_architecture.md + +template-processor --verbosity info --value TARGET=ASW --iv demo-project/interfaceview.xml --dv demo-project/deploymentview.dv.xml -o output -t ../data/ecss-template/ecss-e-st-40c_4_4_interfaces_context.tmplt +pandoc --pdf-engine=pdfroff --output=output/ecss-e-st-40c_4_4_interfaces_context.pdf output/ecss-e-st-40c_4_4_interfaces_context.md + +template-processor --verbosity info --value TARGET=ASW --iv demo-project/interfaceview.xml --dv demo-project/deploymentview.dv.xml -o output -t ../data/ecss-template/ecss-e-st-40c_5_2_overall_architecture.tmplt +pandoc --pdf-engine=pdfroff --output=output/ecss-e-st-40c_5_2_overall_architecture.pdf output/ecss-e-st-40c_5_2_overall_architecture.md + +template-processor --verbosity info --value TARGET=ASW --iv demo-project/interfaceview.xml --dv demo-project/deploymentview.dv.xml -o output -t ../data/ecss-template/ecss-e-st-40c_5_3_software_components_design.tmplt +pandoc --pdf-engine=pdfroff --output=output/ecss-e-st-40c_5_3_software_components_design.pdf output/ecss-e-st-40c_5_3_software_components_design.md + +template-processor --verbosity info --value TARGET=ASW --iv demo-project/interfaceview.xml --dv demo-project/deploymentview.dv.xml -o output -t ../data/ecss-template/ecss-e-st-40c_5_4_aspects_of_each_component.tmplt +pandoc --pdf-engine=pdfroff --output=output/ecss-e-st-40c_5_4_aspects_of_each_component.pdf output/ecss-e-st-40c_5_4_aspects_of_each_component.md + +template-processor --verbosity info --value TARGET=ASW --iv demo-project/interfaceview.xml --dv demo-project/deploymentview.dv.xml -o output -t ../data/ecss-template/ecss-e-st-40c_5_5_internal_interface_design.tmplt +pandoc --pdf-engine=pdfroff --output=output/ecss-e-st-40c_5_5_internal_interface_design.pdf output/ecss-e-st-40c_5_5_internal_interface_design.md + +template-processor --verbosity info --value TARGET=ASW --iv demo-project/interfaceview.xml --dv demo-project/deploymentview.dv.xml -o output -t ../data/ecss-template/ecss-e-st-40c_6_requirement_traceability.tmplt +pandoc --pdf-engine=pdfroff --output=output/ecss-e-st-40c_6_requirement_traceability.pdf output/ecss-e-st-40c_6_requirement_traceability.md \ No newline at end of file diff --git a/examples/generate_so_list.sh b/examples/generate_so_list.sh new file mode 100755 index 0000000..49ad0eb --- /dev/null +++ b/examples/generate_so_list.sh @@ -0,0 +1,4 @@ +#!/bin/bash +mkdir -p output +template-processor --verbosity info --system-objects ../data/events.csv -o output -t so_list.tmplt +pandoc --pdf-engine=pdfroff --output=output/so_list.pdf output/so_list.md \ No newline at end of file diff --git a/examples/generate_traces_list.sh b/examples/generate_traces_list.sh new file mode 100755 index 0000000..eac76be --- /dev/null +++ b/examples/generate_traces_list.sh @@ -0,0 +1,4 @@ +#!/bin/bash +mkdir -p output +template-processor --verbosity info --iv ../data/requirements.iv.xml -o output -t requirements.tmplt +pandoc --pdf-engine=pdfroff --output=output/requirements.pdf output/requirements.md \ No newline at end of file diff --git a/examples/requirements.tmplt b/examples/requirements.tmplt new file mode 100644 index 0000000..d51881a --- /dev/null +++ b/examples/requirements.tmplt @@ -0,0 +1,69 @@ +<% +# Build requirement ID to component mapping +req_to_comp = {} +for func in interface_view.functions: + # Add function-level requirements + for req_id in func.requirement_ids: + if req_id not in req_to_comp: + req_to_comp[req_id] = [] + req_to_comp[req_id].append((func.name, None)) + + # Add provided interface requirements + for pi in func.provided_interfaces: + for req_id in pi.requirement_ids: + if req_id not in req_to_comp: + req_to_comp[req_id] = [] + req_to_comp[req_id].append((func.name, f"{pi.name} (PI)")) + + # Add required interface requirements + for ri in func.required_interfaces: + for req_id in ri.requirement_ids: + if req_id not in req_to_comp: + req_to_comp[req_id] = [] + req_to_comp[req_id].append((func.name, f"{ri.name} (RI)")) + +# Sort by requirement ID +sorted_req_ids = sorted(req_to_comp.keys()) +%> + +${'##'} Forward trace ${'##'} + +${'###'} Requirement to Component Traces + +| Requirement ID | Component | Interface | +|----------------|----------------------|-----------| +% for req_id in sorted_req_ids: +<% +if not req_id: + continue +%> \ +% for comp_name, iface_name in req_to_comp[req_id]: +<% +iface_display = iface_name if iface_name else "-" +%> \ +| ${req_id or "-"} | ${comp_name} | ${iface_display} | +% endfor +% endfor + +${'##'} Backward trace ${'##'} + +${'###'} Component to Requirement Traces + +| Component | Interface | Requirement IDs | +|----------------------|-----------|-----------------| +% for func in interface_view.functions: +<% + # Collect function-level requirements + func_reqs = ', '.join(sorted(func.requirement_ids)) if func.requirement_ids else "-" +%>| ${func.name} | - | ${func_reqs} | +% for pi in func.provided_interfaces: +<% + pi_reqs = ', '.join(sorted(pi.requirement_ids)) if pi.requirement_ids else "-" +%>| ${func.name} | ${pi.name} (PI) | ${pi_reqs} | +% endfor +% for ri in func.required_interfaces: +<% + ri_reqs = ', '.join(sorted(ri.requirement_ids)) if ri.requirement_ids else "-" +%>| ${func.name} | ${ri.name} (RI) | ${ri_reqs} | +% endfor +% endfor \ No newline at end of file diff --git a/examples/so_list.tmplt b/examples/so_list.tmplt new file mode 100644 index 0000000..e80082c --- /dev/null +++ b/examples/so_list.tmplt @@ -0,0 +1,10 @@ +### List of all System Objects +% for name, so_type in system_object_types.items(): + +# [${name}] +Properties: ${', '.join(so_type.property_names)} +% for idx, instance in enumerate(so_type.instances): + +- Instance ${idx}: ${', '.join(instance.values.values())} +% endfor +% endfor \ No newline at end of file diff --git a/setup.py b/setup.py index ec90987..55d60ff 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ include_package_data=True, python_requires='>=3.8', install_requires=[ - # Add project dependencies here + "mako==1.3.10" ], extras_require={ 'dev': [ diff --git a/templateprocessor/cli.py b/templateprocessor/cli.py index 6884b35..e8a18ea 100644 --- a/templateprocessor/cli.py +++ b/templateprocessor/cli.py @@ -2,13 +2,21 @@ Command Line Interface for Template Processor """ +import logging import argparse +from pathlib import Path import sys from templateprocessor import __version__ +from templateprocessor.iv import InterfaceView +from templateprocessor.dv import DeploymentView +from templateprocessor.templateinstantiator import TemplateInstantiator +from templateprocessor.ivreader import IVReader +from templateprocessor.soreader import SOReader +from templateprocessor.dvreader import DVReader +from templateprocessor.so import SystemObjectType -def main(): - """Main entry point for the Template Processor CLI.""" +def parse_arguments() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Template Processor - Template processing engine for TASTE Document Generator", formatter_class=argparse.RawDescriptionHelpFormatter, @@ -20,23 +28,180 @@ def main(): parser.add_argument( "-i", - "--input", - help="Input data file (e.g., TASTE Interface View data)", + "--iv", + help="Input Interface View", + metavar="FILE", + ) + + parser.add_argument( + "-d", + "--dv", + help="Input Deployment View", + metavar="FILE", + ) + + parser.add_argument( + "-s", + "--system-objects", + help="Input System Objects provided as CSV files (each as a separate argument)", + metavar="FILE", + action="append", + ) + + parser.add_argument( + "-v", + "--value", + help="Input values (formatted as name=value pair, e.g., target=ASW)", + action="append", + ) + + parser.add_argument( + "-t", + "--template", + help="Template file to process (each as a separate argument)", metavar="FILE", + action="append", + ) + + parser.add_argument( + "-m", + "--module-directory", + help="Module directory for Mako templating engine", + metavar="FILE", + ) + + parser.add_argument( + "-o", + "--output", + help="Output directory for processed templates", + metavar="DIR", + required=True, ) parser.add_argument( - "-t", "--template", help="Template file to process", metavar="FILE" + "--verbosity", + choices=["info", "debug", "warning", "error"], + default="warning", + help="Logging verbosity", ) parser.add_argument( - "-o", "--output", help="Output file for processed template", metavar="FILE" + "-p", + "--postprocess", + choices=["none", "md2docx"], + help="Output postprocessing", + default="none", ) - args = parser.parse_args() + return parser.parse_args() + + +def get_log_level(level_str: str) -> int: + log_levels = { + "info": logging.INFO, + "debug": logging.DEBUG, + "warning": logging.WARNING, + "error": logging.ERROR, + } + + return log_levels.get(level_str.lower(), logging.WARNING) + + +def get_values_dictionary(values: list[str]) -> dict[str, str]: + if not values or not isinstance(values, list): + return {} + result = {} + for pair in values: + if pair.count("=") != 1: + raise ValueError( + f"Pair [{pair}] contains incorrect number of name/value separators (=)" + ) + split = pair.split("=") + name = split[0].strip() + value = split[1].strip() + if len(name) == 0: + raise ValueError(f"Name in [{pair}] is empty") + # value can be empty + result[name] = value + return result + + +def read_sots(file_names: list[str]) -> dict[str, SystemObjectType]: + sots = {} + so_reader = SOReader() + for sot_file in file_names: + try: + logging.info(f"Reading System Objects from {sot_file}") + name = Path(sot_file).stem + logging.debug(f"-SOT name: {name}") + sos = so_reader.read(sot_file) + sots[name] = sos + except Exception as e: + logging.error(f"Could not read System Objects from {sot_file}") + return sots + + +def instantiate( + instantiator: TemplateInstantiator, + template_file: str, + module_directory: str, + output_directory: str, +): + try: + logging.info(f"Processing template {template_file}") + name = Path(template_file).stem + logging.debug(f"Base name: {name}") + logging.debug(f"Reading template {template_file}") + with open(template_file, "r") as f: + template = f.read() + logging.debug(f"Instantiating template:\n {template}") + instantiated_template = instantiator.instantiate(template, module_directory) + logging.debug(f"Instantiation:\n {instantiated_template}") + output = Path(output_directory) / f"{name}.md" + logging.debug(f"Saving to {output}") + with open(output, "w") as f: + f.write(instantiated_template) + except FileNotFoundError as e: + logging.error(f"File not found: {e.filename}") + except Exception as e: + logging.error(f"Error processing template {template_file}: {e}") + + +def main(): + """Main entry point for the Template Processor CLI.""" + + args = parse_arguments() + logging_level = get_log_level(args.verbosity) + logging.basicConfig(level=logging_level) + + logging.info("Template Processor") + logging.debug(f"Interface View: {args.iv}") + logging.debug(f"Deployment View: {args.dv}") + logging.debug(f"System Objects: {args.system_objects}") + logging.debug(f"Values: {args.value}") + logging.debug(f"Templates: {args.template}") + logging.debug(f"Output Directory: {args.output}") + logging.debug(f"Module directory: {args.module_directory}") + + logging.info(f"Reading Interface View from {args.iv}") + iv = IVReader().read(args.iv) if args.iv else InterfaceView() + + logging.info(f"Reading Deployment View from {args.dv}") + dv = DVReader().read(args.dv) if args.dv else DeploymentView() + + logging.info(f"Reading provided System Objects") + sots = read_sots(args.system_objects) if args.system_objects else {} + + logging.info(f"Parsing values from {args.value}") + values = get_values_dictionary(args.value) + + logging.info(f"Instantiating the TemplateInstantiator") + instantiator = TemplateInstantiator(iv, dv, sots, values) - print("Template Processor - Not yet implemented") - print(f"Version: {__version__}") + if args.template: + logging.info(f"Instantiating templates") + for template_file in args.template: + instantiate(instantiator, template_file, args.module_directory, args.output) return 0 diff --git a/templateprocessor/iv.py b/templateprocessor/iv.py index 734e9a1..0bffeb5 100644 --- a/templateprocessor/iv.py +++ b/templateprocessor/iv.py @@ -37,6 +37,8 @@ class Language(str, Enum): CPP = "C++" SIMULINK = "Simulink" QGenc = "QGenc" + GUI = "GUI" + BLACKBOX_C = "Blackbox_C" @dataclass @@ -76,6 +78,7 @@ class FunctionInterface: id: str name: str + comment: str kind: InterfaceKind enable_multicast: bool = True layer: str = "default" @@ -92,6 +95,7 @@ class FunctionInterface: input_parameters: List[InputParameter] = field(default_factory=list) output_parameters: List[OutputParameter] = field(default_factory=list) properties: List[Property] = field(default_factory=list) + requirement_ids: List[str] = field(default_factory=list) @dataclass @@ -122,6 +126,7 @@ class Function: id: str name: str + comment: str is_type: bool language: Optional[Language] = None default_implementation: str = "default" @@ -138,6 +143,7 @@ class Function: properties: List[Property] = field(default_factory=list) nested_functions: List["Function"] = field(default_factory=list) nested_connections: List["Connection"] = field(default_factory=list) + requirement_ids: List[str] = field(default_factory=list) @dataclass @@ -200,10 +206,10 @@ class InterfaceView: and other elements that define a TASTE system's interface architecture. """ - version: str - asn1file: str - uiFile: str - modifierHash: str + version: str = "" + asn1file: str = "" + uiFile: str = "" + modifierHash: str = "" functions: List[Function] = field(default_factory=list) connections: List[Connection] = field(default_factory=list) comments: List[Comment] = field(default_factory=list) diff --git a/templateprocessor/ivreader.py b/templateprocessor/ivreader.py index 493cda7..293abed 100644 --- a/templateprocessor/ivreader.py +++ b/templateprocessor/ivreader.py @@ -118,6 +118,7 @@ def _parse_function(self, elem: ET.Element) -> Function: function = Function( id=elem.get("id", ""), name=elem.get("name", ""), + comment=elem.get("Comment", ""), is_type=elem.get("is_type", "NO") == "YES", language=( Language(elem.get("language", "")) if elem.get("language") else None @@ -134,6 +135,9 @@ def _parse_function(self, elem: ET.Element) -> Function: if elem.get("type_language") else None ), + requirement_ids=[ + rid for rid in elem.get("requirement_ids", "").split(",") if rid + ], ) # Parse properties @@ -175,6 +179,7 @@ def _parse_interface(self, elem: ET.Element) -> FunctionInterface: iface = FunctionInterface( id=elem.get("id", ""), name=elem.get("name", ""), + comment=elem.get("Comment", ""), kind=InterfaceKind(elem.get("kind", "")), enable_multicast=elem.get("enable_multicast", "true") == "true", layer=elem.get("layer", "default"), @@ -192,6 +197,9 @@ def _parse_interface(self, elem: ET.Element) -> FunctionInterface: else None ), priority=int(elem.get("priority")) if elem.get("priority") else None, + requirement_ids=[ + rid for rid in elem.get("requirement_ids", "").split(",") if rid + ], ) # Parse input parameters diff --git a/templateprocessor/templateinstantiator.py b/templateprocessor/templateinstantiator.py new file mode 100644 index 0000000..25f0c62 --- /dev/null +++ b/templateprocessor/templateinstantiator.py @@ -0,0 +1,47 @@ +""" +Template Instantiator. + +This module is responsible for instantiating Mako templates using the provided data. +""" + +from templateprocessor.iv import InterfaceView +from templateprocessor.dv import DeploymentView +from templateprocessor.so import SystemObjectType +from typing import Dict +from mako.template import Template + + +class TemplateInstantiator: + """ + Instantiator of Mako templates + """ + + system_object_types: Dict[str, SystemObjectType] + values: Dict[str, str] + interface_view: InterfaceView + deployment_view: DeploymentView + + def __init__( + self, + interface_view: InterfaceView, + deployment_view: DeploymentView, + system_object_types: Dict[str, SystemObjectType], + values: Dict[str, str], + ): + self.system_object_types = system_object_types + self.interface_view = interface_view + self.deployment_view = deployment_view + self.values = values + + def instantiate(self, template: str, context_directory: str) -> str: + mako_template = Template(text=template, module_directory=context_directory) + + context = { + "system_object_types": self.system_object_types, + "interface_view": self.interface_view, + "deployment_view": self.deployment_view, + "values": self.values, + } + + instantiated_text = str(mako_template.render(**context)) + return instantiated_text diff --git a/tests/Makefile b/tests/Makefile index bd4d2ba..6eff3a1 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -4,7 +4,8 @@ PYTHON ?= python3 TESTS = \ test_ivreader.py \ test_dvreader.py \ - test_soreader.py + test_soreader.py \ + test_templateinstantiator.py .PHONY: \ check diff --git a/tests/test_ivreader.py b/tests/test_ivreader.py index 4377a16..dd33d4d 100644 --- a/tests/test_ivreader.py +++ b/tests/test_ivreader.py @@ -249,3 +249,33 @@ def test_read_string(self): assert len(iv.functions) == 1 assert iv.functions[0].name == "test_func" assert len(iv.layers) == 1 + + def test_read_requirements(self): + """Test parsing interface with requirement IDs.""" + # Prepare + reader = IVReader() + iv_file = self.get_test_data_file("requirements.iv.xml") + assert iv_file.exists() + + # Read + iv = reader.read(iv_file) + + # Find Function_1 function + function1 = next((f for f in iv.functions if f.name == "Function_1"), None) + assert function1 is not None + + assert len(function1.requirement_ids) == 2 + assert "r1" in function1.requirement_ids + assert "r2" in function1.requirement_ids + + # Find Function_2 function + function2 = next((f for f in iv.functions if f.name == "Function_2"), None) + assert function2 is not None + + # Find do_smth interface + do_smth = next( + (pi for pi in function2.provided_interfaces if pi.name == "do_smth"), None + ) + assert do_smth is not None + assert len(do_smth.requirement_ids) == 1 + assert "r5" in do_smth.requirement_ids diff --git a/tests/test_templateinstantiator.py b/tests/test_templateinstantiator.py new file mode 100644 index 0000000..e11ca52 --- /dev/null +++ b/tests/test_templateinstantiator.py @@ -0,0 +1,556 @@ +""" +Tests for TemplateInstantiator class +""" + +import pytest +import tempfile +from typing import Dict +from templateprocessor.templateinstantiator import TemplateInstantiator +from templateprocessor.iv import ( + InterfaceView, + Function, + Language, + ProvidedInterface, + RequiredInterface, + InterfaceKind, + InputParameter, + OutputParameter, + Encoding, +) +from templateprocessor.so import SystemObjectType, SystemObject +from templateprocessor.dv import ( + DeploymentView, + Node, + Partition, + DeploymentFunction, + Device, + Connection, + Message, +) + + +class TestTemplateInstantiator: + """Test cases for TemplateInstantiator class.""" + + @staticmethod + def create_sample_interface_view() -> InterfaceView: + """Create a sample InterfaceView for testing.""" + iv = InterfaceView( + version="1.3", + asn1file="test.acn", + uiFile="test.ui.xml", + modifierHash="test_hash", + ) + + # Create a sample function + func = Function( + id="func_1", + comment="No Comment", + name="TestFunction", + is_type=False, + language=Language.C, + ) + + # Add a provided interface + pi = ProvidedInterface( + id="pi_1", + name="test_pi", + comment="No Comment", + kind=InterfaceKind.SPORADIC, + ) + pi.input_parameters = [ + InputParameter(name="input1", type="MyInt", encoding=Encoding.NATIVE) + ] + func.provided_interfaces = [pi] + + # Add a required interface + ri = RequiredInterface( + id="ri_1", + name="test_ri", + comment="No Comment", + kind=InterfaceKind.CYCLIC, + ) + ri.output_parameters = [ + OutputParameter(name="output1", type="MyFloat", encoding=Encoding.UPER) + ] + func.required_interfaces = [ri] + + iv.functions = [func] + return iv + + @staticmethod + def create_sample_system_object_types() -> Dict[str, SystemObjectType]: + """Create sample SystemObjectTypes for testing.""" + # Create events system object type + events = SystemObjectType() + events.property_names = ["ID", "Name", "Severity"] + + event1 = SystemObject() + event1.values = {"ID": "1", "Name": "Error Event", "Severity": "high"} + event2 = SystemObject() + event2.values = {"ID": "2", "Name": "Info Event", "Severity": "low"} + + events.instances = [event1, event2] + + # Create parameters system object type + params = SystemObjectType() + params.property_names = ["ID", "Name", "Default"] + + param1 = SystemObject() + param1.values = {"ID": "1", "Name": "Timeout", "Default": "100"} + param2 = SystemObject() + param2.values = {"ID": "2", "Name": "MaxRetries", "Default": "3"} + + params.instances = [param1, param2] + + sots = dict() + sots["events"] = events + sots["params"] = params + + return sots + + @staticmethod + def create_sample_deployment_view() -> DeploymentView: + """Create a sample DeploymentView for testing.""" + dv = DeploymentView( + version="1.2", + ui_file="test_deployment.ui.xml", + creator_hash="test_creator", + modifier_hash="test_modifier", + ) + + # Create first node (x86 Linux) + node1 = Node( + id="n1", + name="x86 Linux C++_1", + type="ocarina_processors_x86::x86.generic_linux", + node_label="Node_1", + namespace="ocarina_processors_x86", + requirement_ids=["r20", "r21"], + ) + + # Add partition to node1 + partition1 = Partition( + id="p1", + name="test_partition_1", + ) + + # Add functions to partition + func1 = DeploymentFunction( + id="f1", + name="TestFunction", + path="/test/path/function1", + ) + partition1.functions.append(func1) + node1.partitions.append(partition1) + + # Add device to node1 + device1 = Device( + id="d1", + name="uart0", + requires_bus_access="UART", + port="uart0", + asn1file="test.asn", + asn1type="TestType", + asn1module="TestModule", + namespace="test_namespace", + extends="BaseDevice", + impl_extends="BaseImpl", + bus_namespace="uart_namespace", + ) + node1.devices.append(device1) + + # Create second node (ARM RTEMS) + node2 = Node( + id="n2", + name="SAM V71 RTEMS N7S_1", + type="ocarina_processors_arm::samv71.rtems", + node_label="Node_2", + namespace="ocarina_processors_arm", + ) + + # Add partition to node2 + partition2 = Partition( + id="p2", + name="test_partition_2", + ) + + func2 = DeploymentFunction( + id="f2", + name="SensorFunction", + path="/test/path/function2", + ) + partition2.functions.append(func2) + node2.partitions.append(partition2) + + # Add nodes to deployment view + dv.nodes.append(node1) + dv.nodes.append(node2) + + # Create connection between nodes + connection = Connection( + id="c1", + name="UartLink", + from_node="n1", + from_port="uart0", + to_bus="UART", + to_node="n2", + to_port="uart0", + ) + + # Add messages to connection + msg1 = Message( + id="m1", + name="DataMessage", + from_function="TestFunction", + from_interface="test_pi", + to_function="SensorFunction", + to_interface="sensor_ri", + ) + connection.messages.append(msg1) + + dv.connections.append(connection) + + return dv + + def test_instantiator_initialization(self): + """Test TemplateInstantiator initialization.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + assert instantiator.interface_view == iv + assert instantiator.system_object_types == so_types + assert len(instantiator.system_object_types) == 2 + + def test_instantiate_simple_template(self): + """Test instantiating a simple template.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = "Hello World!" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert result == "Hello World!" + + def test_instantiate_template_with_interface_view(self): + """Test instantiating a template that uses Interface View.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """Interface View version: ${interface_view.version} +ASN1 file: ${interface_view.asn1file} +Number of functions: ${len(interface_view.functions)}""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Interface View version: 1.3" in result + assert "ASN1 file: test.acn" in result + assert "Number of functions: 1" in result + + def test_instantiate_template_with_function_details(self): + """Test instantiating a template that accesses Function details.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """% for func in interface_view.functions: +Function: ${func.name} +Language: ${func.language.value} +Provided Interfaces: ${len(func.provided_interfaces)} +Required Interfaces: ${len(func.required_interfaces)} +% endfor""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Function: TestFunction" in result + assert "Language: C" in result + assert "Provided Interfaces: 1" in result + assert "Required Interfaces: 1" in result + + def test_instantiate_template_with_system_object_types(self): + """Test instantiating a template that uses System Object Types.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """Number of System Object Types: ${len(system_object_types)} +% for name, so_type in system_object_types.items(): + [${name}] Properties: ${', '.join(so_type.property_names)} + [${name}] Instances: ${len(so_type.instances)} +% endfor""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Number of System Object Types: 2" in result + assert "[events] Properties: ID, Name, Severity" in result + assert "[params] Properties: ID, Name, Default" in result + assert "Instances: 2" in result + + def test_instantiate_template_with_system_object_instances(self): + """Test instantiating a template that accesses System Object Type instances.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """% for name, so_type in system_object_types.items(): +[${name}] +% for instance in so_type.instances: +% if 'Name' in instance.values: + - ID: ${instance.values['ID']} - ${instance.values['Name']} +% endif +% endfor +% endfor""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + assert ( + """[events] + - ID: 1 - Error Event + - ID: 2 - Info Event +[params] + - ID: 1 - Timeout + - ID: 2 - MaxRetries +""" + == result + ) + + def test_instantiate_template_with_empty_data(self): + """Test instantiating a template with empty Interface View and no System Objects.""" + iv = InterfaceView(version="1.0", asn1file="", uiFile="", modifierHash="") + dv = self.create_sample_deployment_view() + + so_types = {} + + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """Version: ${interface_view.version} +Functions: ${len(interface_view.functions)} +System Object Types: ${len(system_object_types)}""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Version: 1.0" in result + assert "Functions: 0" in result + assert "System Object Types: 0" in result + + def test_instantiate_template_with_python_expressions(self): + """Test instantiating a template with Python expressions.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """<% +total_interfaces = sum(len(f.provided_interfaces) + len(f.required_interfaces) for f in interface_view.functions) +total_instances = sum(len(so.instances) for so in system_object_types.values()) +%> +Total Interfaces: ${total_interfaces} +Total System Object Instances: ${total_instances}""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Total Interfaces: 2" in result + assert "Total System Object Instances: 4" in result + + def test_instantiate_template_with_deployment_view(self): + """Test instantiating a template that uses Deployment View.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """Deployment View version: ${deployment_view.version} +UI file: ${deployment_view.ui_file} +Number of nodes: ${len(deployment_view.nodes)} +Number of connections: ${len(deployment_view.connections)}""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Deployment View version: 1.2" in result + assert "UI file: test_deployment.ui.xml" in result + assert "Number of nodes: 2" in result + assert "Number of connections: 1" in result + + def test_instantiate_template_with_deployment_nodes(self): + """Test instantiating a template that accesses Deployment View nodes.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """% for node in deployment_view.nodes: +Node: ${node.name} +Type: ${node.type} +Label: ${node.node_label} +Namespace: ${node.namespace} +Partitions: ${len(node.partitions)} +Devices: ${len(node.devices)} +% endfor""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Node: x86 Linux C++_1" in result + assert "Type: ocarina_processors_x86::x86.generic_linux" in result + assert "Node: SAM V71 RTEMS N7S_1" in result + assert "Type: ocarina_processors_arm::samv71.rtems" in result + assert "Partitions: 1" in result + assert "Devices: 1" in result + + def test_instantiate_template_with_partitions_and_functions(self): + """Test instantiating a template that accesses partitions and deployed functions.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """% for node in deployment_view.nodes: +${node.name} +% for partition in node.partitions: +Partition: ${partition.name} +% for func in partition.functions: +- Function: ${func.name} (${func.id}) + Path: ${func.path} +% endfor +% endfor +% endfor""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "x86 Linux C++_1" in result + assert "Partition: test_partition_1" in result + assert "- Function: TestFunction (f1)" in result + assert "Path: /test/path/function1" in result + assert "SAM V71 RTEMS N7S_1" in result + assert "Partition: test_partition_2" in result + assert "- Function: SensorFunction (f2)" in result + assert "Path: /test/path/function2" in result + + def test_instantiate_template_with_devices(self): + """Test instantiating a template that accesses node devices.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """% for node in deployment_view.nodes: +% if node.devices: +Node: ${node.name} +% for device in node.devices: + Device: ${device.name} + - Port: ${device.port} + - Bus: ${device.requires_bus_access} + - ASN1 Type: ${device.asn1type} +% endfor +% endif +% endfor""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Node: x86 Linux C++_1" in result + assert "Device: uart0" in result + assert "- Port: uart0" in result + assert "- Bus: UART" in result + assert "- ASN1 Type: TestType" in result + + def test_instantiate_template_with_connections(self): + """Test instantiating a template that accesses connections and messages.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """% for conn in deployment_view.connections: +Connection: ${conn.name} + From: ${conn.from_node}:${conn.from_port} + To: ${conn.to_node}:${conn.to_port} + Bus: ${conn.to_bus} + Messages: ${len(conn.messages)} +% for msg in conn.messages: + - ${msg.name}: ${msg.from_function}.${msg.from_interface} -> ${msg.to_function}.${msg.to_interface} +% endfor +% endfor""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Connection: UartLink" in result + assert "From: n1:uart0" in result + assert "To: n2:uart0" in result + assert "Bus: UART" in result + assert "Messages: 1" in result + assert ( + "- DataMessage: TestFunction.test_pi -> SensorFunction.sensor_ri" in result + ) + + def test_instantiate_template_with_node_requirements(self): + """Test instantiating a template that accesses node requirement IDs.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + instantiator = TemplateInstantiator(iv, dv, so_types, {}) + + template = """% for node in deployment_view.nodes: +Node: ${node.name} +% if node.requirement_ids: + Requirements: ${', '.join(node.requirement_ids)} +% else: + Requirements: None +% endif +% endfor""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Node: x86 Linux C++_1" in result + assert "Requirements: r20, r21" in result + assert "Node: SAM V71 RTEMS N7S_1" in result + assert "Requirements: None" in result + + def test_instantiate_template_with_values(self): + """Test instantiating a template that uses provided values.""" + iv = self.create_sample_interface_view() + dv = self.create_sample_deployment_view() + so_types = self.create_sample_system_object_types() + + values = { + "project_name": "MyProject", + "version": "2.1.0", + "author": "Test Author", + "description": "Test project description", + } + + instantiator = TemplateInstantiator(iv, dv, so_types, values) + + template = """Project: ${values['project_name']} +Version: ${values['version']} +Author: ${values['author']} +Description: ${values['description']}""" + + with tempfile.TemporaryDirectory() as tmpdir: + result = instantiator.instantiate(template, tmpdir) + + assert "Project: MyProject" in result + assert "Version: 2.1.0" in result + assert "Author: Test Author" in result + assert "Description: Test project description" in result