Introduction to the PLCnext CLI Template System

This template system is a powerful and flexible tool designed to simplify the generation of files based on user-defined templates containing placeholders. These placeholders are automatically evaluated and filled by the PLCnext CLI, streamlining the process of creating files with dynamic content. The template system is ideal for use cases such as code generation, configuration file management, and document templating, where the goal is to produce consistently formatted files with varying content. By leveraging the expressive syntax of the PLCnext CLI Template System, users can focus on defining the structure and logic of their templates, while the system takes care of the rest.

Placeholder syntax

In the PLCnext CLI Template System, placeholders are denoted using the $(path.to.value) syntax. This notation allows the user to specify the path to the desired value in a hierarchical data structure. The path is evaluated by the PLCnext CLI, and the corresponding value is inserted into the output file at the position of the placeholder.

For example, consider a template with the following content:

This is $(file.name)!
It was generated in $(file.path).

The generated file could be:

This is Program.hpp!
It was generated in path/to/Program.cpp.

Getting started

This documentation will guide you through the process of creating, managing, and using templates in the PLCnext CLI Template System. The following sections are included:

Template syntax: Learn about the syntax and features supported by the PLCnext CLI Template System, including conditionals, loops, and more.

Template management: Discover how to create, edit, and manage your templates.

Data sources: Understand how to provide data to the PLCnext CLI for use in evaluating placeholders.

Template syntax

The PLCnext CLI Template System offers a rich syntax that allows you to create dynamic and complex templates with ease. As mentioned earlier, the general format for accessing values within the template is $(path.to.value). This section provides an in-depth look at the available control sequences and their usage.

Control sequences in the template follow the format:

&([control-sequence-name]parameters) 
Content 
&([end-control-sequence-name])

Available control sequences

no-duplicate-lines

This control sequence removes duplicate lines from the content. It has no parameters. The syntax is:

&([no-duplicate-lines]) 
Content 
&([end-no-duplicate-lines])

if-exist

This control sequence checks if a value exists in the current context. If the value exists, the content before the $([else]) sequence is generated. If the value does not exist, the content after the $([else]) sequence is generated. It has one parameter, which is the name of the value. The syntax is:

&([if-exist]path.to.value) 
Content if value exists 
$([else])
Content if value does not exist 
&([end-if-exist])

if-specified

This control sequence checks if a value is specified (is set) in the current context.

  • If the value is specified, the content before the $([else]) sequence is generated.
  • If the value is not specified, the content after the $([else]) sequence is generated.

It has one parameter which is the name of the value. The syntax is:

&([if-specified]path.to.value) 
Content if value is specified
$([else])
Content if value is not specified
&([end-if-specified])

if

Similar to if-exist, this control sequence evaluates a value as boolean. If the value evaluates to true, the content before the $([else]) sequence is generated. If the value evaluates to false, the content after the $([else]) sequence is generated. The syntax is:

&([if]path.to.value) 
Content if value is true
`$([else])` 
Content if value is false
&([end-if])

foreach

The foreach control sequence iterates over a collection, executing the content for each element in the collection within the context of the current element. The syntax is:

&([foreach]element[in]path.to.collection) 
Content for each element 
&([end-foreach])

plain

The plain control sequence allows to prevent text to be interpreted by the template mechanism. The syntax is:

&([plain]) 
Uninterpreted text with e.g. $(some) &(some-other) text which is not altered by the template system.
&([end-plain])

Example

Consider the following example to better understand the template syntax:

#include <iostream>
#include "$(file.name).h"
$([no-duplicate-lines])
$([foreach]struct[in]portStructs)
#include "$(struct.file.format.include)"
$([end-foreach])
#include "$(file.format.include)"
$([end-no-duplicate-lines])
&([if-exist]file.namespace)
namespace $(file.namespace) {
&([end-if-exist])
&([foreach]function[in]file.functions)
void $(function.name)() {
  &([if]function.hasImplementation)
  $(function.implementation)
  &([else])
  // TODO: Implement $(function.name)
  &([end-if])
}
&([end-foreach])
&([if-exist]file.namespace)
} // namespace $(file.namespace)
&([end-if-exist])

In this example, we use various control sequences to generate a C++ source file based on the provided data. The no-duplicate-lines sequence ensures that each included file is unique, while the if-exist, if, and foreach sequences provide conditional generation and iteration over elements in the context. This is only an example. Not all values do exist.

Template management

In the PLCnext CLI Template System, templates are managed through template description files. These files define the content, documentation, and arguments required to invoke the template using the PLCnext CLI. In this section, we will cover the creation and activation of templates.

Creating a template

To create a template, start by creating a template description file in XML format. This file contains information about the template, including its name, version, arguments, and associated files.

Here is an example of a template description file:

<?xml version="1.0" encoding="utf-8"?>
<TemplateDescription name="app" isRoot="true" basedOn="template" supportedFirmwareVersions="19.0.0.16199" requiredCliVersion="19.0.0.660" version="1" 
                     xmlns="http://www.phoenixcontact.com/schema/clitemplates" identifier="ProjectSettingsIdentifier"
                     deployEngine="AppDeployEngine" buildEngine="NoBuildEngine" generateEngine="NoGenerateEngine">
  <Arguments>
    <Argument name="name" default="$(output.format.directoryName.format.escapeProjectName)">
      <ValueRestriction min-length="2" max-length="128" description="This must be a valid C++ type name. It must start with an upper case letter.">
        <Pattern>^[a-zA-Z0-9_]*$</Pattern>
      </ValueRestriction>
    </Argument>
    <Argument name="output" default="$([if-specified]name)$(name)$([else])$(active-directory)$([end-if-specified])"/>
  </Arguments>
  <File name="plcnext.proj" template=".proj"/>
  <File name="app_info.json" template="app_info.json" path="content"/>
  <Description>Create an app project. It is intended mainly to pack file into an app format.</Description>
  <Example>
    <Arguments>
      <Argument name="name" value="AppName"/>
    </Arguments>
    <Description>Creates a new app which will generate an "AppName.app" file.</Description>
  </Example>
</TemplateDescription>

The full specification for template description files can be found below.

Additionally, all files associated with the template need to be created using the template syntax described in the previous sections.

Activating a template

To activate a template, add it to a templates-repository file. This file lists all the template descriptions you want to make available for use with the PLCnext CLI. Here is an example of a templates-repository file:

<Templates>
  <Template path="/path/to/your/template/description/file.xml"/>
</Templates>

To include your template in the repository file, add a new <Include> element with the type attribute set to Template and the value set to the path of your template description file.

After updating the templates-repository file, add it to the PLCnext CLI settings using the following command:

PLCnext CLI set setting -a TemplateLocations path/to/Templates.xml

This command adds the path to your templates-repository file to the PLCnext CLI settings, making all the templates defined in the repository available for use.

Special templates

In addition to standard templates defined with the TemplateDescription, the PLCnext CLI Template System also supports three types of special templates: TypeTemplates, FieldTemplates, and FormatTemplates. These templates allow for further customization and functionality when working with C++ types, fields, and string transformations.

Type templates

TypeTemplates are recognized as attributes that can be added to a C++ type. They provide additional metadata or functionality associated with the type. Here is an example of a TypeTemplate:

<?xml version="1.0" encoding="utf-8"?>
<TypeTemplates xmlns="http://www.phoenixcontact.com/schema/clitemplates">
	<TypeTemplate name="attributes" has-value="true" multiplicity="OneOrMore" split="|" default-value="None">
		<ValueRestriction ignore-case="true">
			<Enumeration>Input</Enumeration>
			<Enumeration>Output</Enumeration>
			<Enumeration>ReadOnly</Enumeration>
			<Enumeration>Retain</Enumeration>
			<Enumeration>Opc</Enumeration>
			<Enumeration>Ehmi</Enumeration>
			<Enumeration>ProfiCloud</Enumeration>
			<Enumeration>Archive</Enumeration>
		    <Enumeration>None</Enumeration>
      	    <Enumeration>Hidden</Enumeration>
		</ValueRestriction>
	</TypeTemplate>
</TypeTemplates>

With this TypeTemplate, you can add attributes like //#attributes(Input|Retain) to a type. These attributes can be accessed in the template using placeholders like $(type.attributes).

Field templates

FieldTemplates are recognized as attributes that can be added to a C++ field. Similar to TypeTemplates, they provide additional metadata or functionality associated with the field. Here is an example of a FieldTemplate:

<FieldTemplates xmlns="http://www.phoenixcontact.com/schema/clitemplates">
	<FieldTemplate name="port"/>
	<FieldTemplate name="name" has-value="true" default-value="$(fieldName)">
		<ValueRestriction min-length="2" max-length="128" description="An IEC-conform name that does not match any IEC elementary data type.">
			<Pattern>(?!((?i)^((SAFE)?(BOOL|BYTE|((D|L)?WORD)|(U?(S|D|L)?INT)|(L?(REAL|TIME|T|DATE|D|DATE_AND_TIME|DT|TIME_OF_DAY|TOD))|(W?(STRING|CHAR))|ANALOG))$))(_|==?|-+|\+\+?|##?|:|&amp;|\/|\*)?([0-9a-zA-Z]+(_|&lt;|&gt;|==?|-+|\+\+?|##?|:|&amp;|\/|\*)?)+</Pattern>
		</ValueRestriction>
	</FieldTemplate>
</FieldTemplates>

Format templates

FormatTemplates are used to transform strings into different strings based on defined patterns. For example, consider a file path $(output) with the value path\to\file\directory\file.cpp. By applying a FormatTemplate, $(output.format.directoryName), the result would be directory.

Here is an example of how a FormatTemplate is defined:

<FormatTemplates xmlns="http://www.phoenixcontact.com/schema/clitemplates">
  <FormatTemplate name="directoryName">
	<RegexConversion pattern="^.*?[\/\\](?&lt;directoryName&gt;[^\/\\]*)[\/\\]?\s*$" converted="$(directoryName)"/>
  </FormatTemplate>
</FormatTemplates>

In this example, the FormatTemplate named directoryName uses a regular expression pattern to extract the directory name from the file path. The RegexConversion element specifies the pattern and the converted attribute defines how the matched value should be formatted.

In this case, the directoryName group in the pattern captures the directory name, and the converted attribute uses $(directoryName) to represent the extracted directory name.

Schema Files

Here is a list of the schema files that specify the above documentation:


• Published/reviewed: 2024-10-30   ☀  Revision 074 •