⇚ Back to Home Page
templatefx.png

TemplateFx | Dynamic Templating Tool

by Chris Mason <templatefx@netnix.org>

1. Introduction

TemplateFx is a Dynamic Templating Tool which allows you to generate outputs based on a template and some source data. Its main use is in generating configurations for CLI based devices like Cisco routers and switches. It aims to be portable and platform independent through the use of Java and as such requires a Java Runtime Environment (JRE) of at least version 1.6 to be installed.

This document aims to be short as there isn't really a lot to talk about, due to the simplicity of TemplateFx and its hopeful ease of use. However, if you feel something could be made easier or simpler then feedback is welcomed using the email address above.

Download links:

All the changes in this release and previous releases can be found within the CHANGELOG.

2. User Interface

The TemplateFx user interface consists of the "DataTemplate" tab and multiple "Output" tabs, the "DataTemplate" tab is the main tab where you compose your template and the "Output" tabs are where you see your merged data after clicking on the "Generate Output" button on the toolbar.

templatefx-screenshot.png    templatefx-screenshot2.png

You have the ability to right click on the various panes to allow you to copy and paste or perform other normal operations. As well as the "File" menu to load and save content, you also have the ability to drag and drop data files and template files into the respective panes.

The "Group By" drop down allows you to select one of your data fields to allow grouping of your output data. As an example, if you have configuration that needs to be applied to different interfaces on a series of routers, then you could use this to group by your router identifier, to allow you to group interfaces for the same router in the same output. The "Merge Rows" checkbox is slightly different and is used in conjunction with the "Group By" drop down and it allows you to merge rows together which are grouped together. Using the same example, you would only have a single row per router with all the interfaces merged together using a delimiter.

2.1. Data Pane

The data pane is where you enter your raw data that TemplateFx will use within your template. Text that is inserted into the data pane has a couple of restrictions that must be followed:

  1. Data is entered in rows and columns, with the columns separated by tabs or commas - you can't mix and match. In the same sense if you click on "Import Data" from the "File" menu then it expects tab or comma separated data.

  2. You must define at least two rows within your data. The first row is considered the header row which allows TemplateFx to map the substitution fields in your template to the data. The only exception, is if you haven't used any template fields within the template plane - this is to support situations where you are just using the template pane and in this unusual situation you may leave the data pane blank.

A nice feature of TemplateFx is its ability to accept data from Microsoft Excel. If you construct your data within Excel then you are able to copy and paste it into the data pane. This is the preferred method of constructing source data as you are able to manipulate and sort the data before pasting it into TemplateFx - however be aware of Excel dropping leading zeroes and converting "x/x/x" syntax into dates by default.

2.2. Template Pane

The template pane is where you enter the text of your template that will be used as the basis for your output. TemplateFx fields are defined through the use of double chevrons (e.g. "<<FIELD>>") and once you define them within the template pane, they are added to the "Template Fields" list (fields are highlighted in red if they don't exist in the data pane).

When you click on the "Generate Output" button, the fields will be substituted for the values in the source data. In the "Generate Output" dialog you have the option of specifying a label for the output tab as well as some optional criteria which allows you to limit which entries within the data pane are processed. When limiting which entries are processed you can select rows where a field "contains (=~)", "does not contain (!~)", "equals (==)" or "does not equal (!=)" a certain string, with the "contains" and "does not contain" operands supporting a regular expression.

In the event that a template field hasn't been defined within the source data then you will get a warning message as well as it being highlighted in the output to allow you to easily identify it.

2.3. Output Pane

Every time you click on the "Generate Output" button a new output pane is added as a new tab. This pane allows you to see the merged output with the ability to save it or to zip multiple outputs together into a single archive. If you have selected a field in the "Group By" drop down, then you have the ability to cycle through the different output groups. In the event that TemplateFx detects you are creating a HTML based template then it will give you the option of showing the output in your default browser using the "In Browser" button.

2.4. Shortcut Keys

TemplateFx defines the following shortcut/accelerator keys that can be used to perform tasks using the keyboard:

Action Windows Mac OS
New ^N Option-N
Load DataTemplate ^L Option-L
Save DataTemplate ^S Option-S
Bookmark DataTemplate ^D Option-D
Generate Output ^G Option-G
Close Tab ^W Option-W
Help F1 F1

3. DataTemplates

A DataTemplate is a proprietary file format which uses the file extension ".dt" and is used to store a template along with the source data. Instead of having to distribute the template and source data in separate files, you can save it as a DataTemplate which combines them both together. Warning: The file format isn't text based and if you attempt to edit it outside of TemplateFx then you will screw up the checksum and it won't load.

4. Templates

To help demonstrate how a template is constructed let us assume we have the following source data defined within our data pane:

ROUTER, INTERFACE, IP, DEST
PE-1A, Gi0/0/0, 192.0.2.1, R1 (Gi0/0)
PE-1A, Gi0/0/1, 192.0.2.5, R2 (Gi0/1)
PE-1A, Gi0/0/2, 192.0.2.9, R3 (Gi0/2)

We are going to create a template which configures a series of interfaces based on the data provided within the data pane. You can see in the example below that we have used chevrons to mark where we want the substituted data to be placed:

interface <<INTERFACE>>
 description ## Connection from <<ROUTER>> (<<INTERFACE>>) to <<DEST>> ##
 ip address <<IP>> 255.255.255.252
 no shutdown
!

Once you click on "Generate Output", the data within the template pane is effectively duplicated for each line within your source data with the necessary substitutions taking place - it will produce the following output:

interface Gi0/0/0
 description ## Connection from PE-1A (Gi0/0/0) to R1 (Gi0/0) ##
 ip address 192.0.2.1 255.255.255.252
 no shutdown
!

interface Gi0/0/1
 description ## Connection from PE-1A (Gi0/0/1) to R2 (Gi0/1) ##
 ip address 192.0.2.5 255.255.255.252
 no shutdown
!

interface Gi0/0/2
 description ## Connection from PE-1A (Gi0/0/2) to R3 (Gi0/2) ##
 ip address 192.0.2.9 255.255.255.252
 no shutdown
!

4.1. Branching and Looping Constructs

4.1.1. IF

The IF construct is one of the most important features of many languages, templates included. It allows for the conditional use of template text based on a true or false statement. If the statement evaluates to true, the text will be displayed, and if it evaluates to false, it won't.

<IF ("expression")>
  ...
<ELSE IF ("expression")>
  ...
<ELSE IF ("expression")>
  ...
<ELSE>
  ...
</IF>

The "expression" will be evaluated by the JavaScript parser, so it needs to be valid JavaScript, however the syntax is relatively simple as demonstrated in the example below (white space is maintained at the beginning and end of IF blocks, but not empty lines).

interface <<INTERFACE>>
<IF ("<<INTERFACE>>" == "Gi0/0/0")>
 no shutdown
</IF>
!

In the above example, the block will only be displayed if the "INTERFACE" field equals "Gi0/0/0".

4.1.2. FOR

The FOR construct is also an important feature of many languages, templates included - it is used to support intra-row looping. There are two different syntaxes that are supported for this construct; the first allows you to specify a variable "VAR" and a comma separated list of data values, e.g. "VAL1", "VAL2", ..., "VALn", the second allows you to specify a variable "VAR" and then use the "X TO Y [STEP N]" syntax for the values - if "STEP" is omitted then a default value of 1 or -1 will be used depending on the context. It will then duplicate the contents of the FOR block for every entry by substituting "{{VAR}}" for the value. If there are no entries within the list of data values then the block isn't displayed (white space is maintained at the beginning and end of FOR blocks, but not empty lines). To maintain performance and preserve memory, a maximum iteration limit of 1024 has been imposed.

<FOR VAR = ["VAL1", "VAL2", ..., "VALn"]>
{{VAR}}
</FOR>

The following example demonstrates how this could be used to include a series of "ip helper-address" statements on an interface:

interface <<INTERFACE>>
 ip address <<IP>> 255.255.255.252
<FOR H = ["192.0.2.129", "192.0.2.130"]>
 ip helper-address {{H}}
</FOR>
 no shutdown
!

Once you click on "Generate Output", the following output will be generated where you can see the "ip helper-address" has been duplicated for each entry within the list of data values:

interface Gi0/0/0
 ip address 192.0.2.1 255.255.255.252
 ip helper-address 192.0.2.129
 ip helper-address 192.0.2.130
 no shutdown
!

interface Gi0/0/1
 ip address 192.0.2.5 255.255.255.252
 ip helper-address 192.0.2.129
 ip helper-address 192.0.2.130
 no shutdown
!

interface Gi0/0/2
 ip address 192.0.2.9 255.255.255.252
 ip helper-address 192.0.2.129
 ip helper-address 192.0.2.130
 no shutdown
!

It should be noted that the above could have been easily done without the use of a FOR block as the values were already known. However, the real power comes when you provide the list from the data pane, where it could be different on a per line basis - this concept will be further explained within the "Scripting" section.

The second type of syntax allows you to do more conventional loops that go from a start value to an end value, with an optional step (where "X" and "Y" could be a number or a character with the smaller value on the left or the right):

<FOR VAR = ["X" TO "Y" STEP N]>
{{VAR}}
</FOR>

You are permitted to nest FOR blocks as many times as you want while mixing and matching the syntax - the following primitive example demonstrates an example of this:

<FOR I = [1 TO 5 STEP 2]>
  <FOR J = [B TO A]>
R = {{I}}:{{J}}
  </FOR>
</FOR>

Once you click on "Generate Output", the following output will be generated:

R = 1:B
R = 1:A
R = 3:B
R = 3:A
R = 5:B
R = 5:A

4.2. Snippets

To allow the re-using of templating components, TemplateFx also supports the notion of including the contents of snippets into a template. This allows a common piece of text to be written once but used multiple times in the same template - any modifications to the common snippet is seen in all occurrences within the template. Snippets are defined in the snippet panel and can then be referenced in the main template using the following syntax:

ipv4 access-list ACL-IN
 permit tcp host 192.0.2.1 gt 1023 host 192.0.2.2 eq 179
 permit tcp host 192.0.2.2 eq 179 host 192.0.2.1 gt 1023
 %{ACL-STANDARD}%
 deny ip any any
!

The above "%{NAME}%" syntax tells TemplateFx to replace it with the contents of the snippet named "ACL-STANDARD". This directive is processed first before any substitutions have taken place which means any valid template syntax can be used within a snippet. The scope of snippets is within the DataTemplate and when the DataTemplate is saved or loaded then the snippets are included with it.

4.3. Including External Files

As well as snippets, TemplateFx also supports including content from external text files that are located on a locally accessible file system. This approach might be used over snippets if you have content which is shared amongst multiple templates. External files are included using the following syntax:

%INCLUDE "\\uk\ukroot\templates\site-build-template.txt"

The above syntax instructs TemplateFx to include the contents of "\\uk\ukroot\templates\site-build-template.txt" into the template at this point. The current order of operation means that snippets are imported into the template before includes, which means snippets can include external files. A maximum filesize of 262,144 bytes is currently enforced per external file.

4.4. Comments

There are two types of comments which can be used within a template (and snippets), both of which must be used at the beginning of a line as TemplateFx doesn't presently support end of line comments. The first is a template comment and uses the "#" syntax to comment out a line in a template - this results in the line being completely ignored and not appearing in your output. The second type of comment, which uses the "!" syntax is used to supplement your output with comments:

# This comment won't appear in your output and ignores <<FIELDS>>, etc.
! This comment will appear in your output and doesn't ignore <<FIELDS>>, etc..

5. Grouping and Merging

If you don't use the "Group By" or "Merge Rows" features then TemplateFx will generate a separate output per row within your data source. To demonstrate the difference between these two features, we are going to use the following data source as an example:

ROUTER, INTERFACE
UKPE-ABC-01, Gi0/1
UKPE-ABC-01, Gi0/2
UKPE-ABC-01, Gi0/3
UKPE-ABC-02, Gi0/1
UKPE-ABC-02, Gi0/2
UKPE-ABC-03, Gi0/1

Without either option selected, we will end up with 6 separate outputs (one per row). If we select "ROUTER" as our "Group By" field then TemplateFx will generate outputs in the same way as before, but it will combine outputs with the same value for "ROUTER" into a single output - this will result in 3 separate outputs (one per unique "ROUTER" value). As it is processing each row, it basically checks to see if we have generated an output with the same "ROUTER" value before, if we have then it appends it to the same output and if not then it starts a new output.

The "Merge Rows" feature goes a lot further and actually combines rows together in your data source where the "Group By" field has the same value. Using the example above, TemplateFx would merge rows where the "ROUTER" field was the same, which would result in 3 rows within our data source (any optional criteria specified in the "Generate Output" dialogue is processed before merging). When the rows are merged, TemplateFx will pick a delimiter (from a pre-defined list, which includes "|;:%$?^+-") to separate the different rows - the first delimiter which it doesn't find in your data source is used (the actual value is accessible using the "templatefx.delimiter" internal variable). Based on the above example, the merged data source would look like this:

ROUTER, INTERFACE
UKPE-ABC-01|UKPE-ABC-01|UKPE-ABC-01, Gi0/1|Gi0/2|Gi0/3
UKPE-ABC-02|UKPE-ABC-02, Gi0/1|Gi0/2
UKPE-ABC-03, Gi0/1

If you were to use the "<<ROUTER>>" field in your template then you would end up with the value "UKPE-ABC-01|UKPE-ABC-01|UKPE-ABC-01" being substituted in your output. You have two options, you can either use JavaScript to split out the different values manually using "templatefx.delimiter", or you can use a TemplateFx function to assist you. To support the "Merge Rows" feature, TemplateFx now supports the "templatefx.mrows" internal variable which tells you how many rows have been merged into the current row and the "<<ROUTER>>[n]" syntax, where "n" is the index to the merged row. This syntax is actually a shortcut function and will get substitued for JavaScript during pre-processing, which splits the data on the delimiter, places it into a temporary array and then returns the array element at position "n".

However, there is a big difference between "<<ROUTER>>" and "<<ROUTER>>[n]", with the first being substituted for a string literal and the second being substituted for a JavaScript function - this is an important difference when they are being used in the context of a script block:

# In this context, <<FIELD>> must be contained within quotes as it will be substituted for a string literal.
<?= ip("<<FIELD>>", +1) ?>

# In this context, <<FIELD>>[n] must not be contained within quotes as it will be substituted for a function.
<?= ip(<<FIELD>>[n], +1) ?>

With the confusing bit out of the way, the following hopefully demonstrates how you can elevate your templates by using "Merge Rows" to provide looping within a single output. The following example generates an output per router, but it uses the different interfaces contained on different rows within the configuration of a single router (we are using "templatefx.mrows" to loop through the different merged rows):

! @ <<ROUTER>>[0]

router isis
<FOR I = [0 TO <?= templatefx.mrows - 1 ?>]>
 interface <<INTERFACE>>[{{I}}]
  bfd fast-detect ipv4
  address-family ipv4 unicast
   mpls ldp sync
  !
 !
</FOR>
!

It should be noted that even without "Merge Rows" enabled, the syntax "<<FIELD>>[0]" is still valid and would return the same value as "<<FIELD>>". This is useful as it allows you to turn off the merging without changing your template.

It should also be noted that neither of these features modify the order of the data source, the data is processed in the order provided - any sorting should be done before the data is entered into TemplateFx.

6. Scripting

For the more advanced users who want to do more than just simple substitution, TemplateFx has the ability to interpret dynamic templates which are written using JavaScript. This User Guide assumes you have a basic understanding of JavaScript, but if this isn't the case then the web (http://lmgtfy.com/?q=JavaScript) has a large amount of information on it.

TemplateFx supports the following different syntax within a template to support dynamic content:

<? ... ?>

This is used to define a dynamic content block where the contents of the block between the "<? ... ?>" will be evaluated through the JavaScript parser and any output will be substituted. For the purpose of this example we are going to modify our source data to demonstrate how we can use dynamic content to generate BGP configurations:

ROUTER, LOCATION, INTERFACE, PEER_IP
PEXCD1A, DEV, Gi0/0/0/1, 192.0.2.2
PEXCD1B, DEV, Gi0/0/0/1, 192.0.2.6
PEXAY1A, PROD, Te0/7/0/1, 192.0.2.66
PEXAY1B, PROD, Te0/7/0/1, 192.0.2.70

The below example will demonstrate how to generate some BGP configuration from the source data above. It will determine the BGP AS (Autonomous System) number based on the "LOCATION" field as well as working out the suffix for the RD (Route Distinguisher) based on the "ROUTER" field (it will look to see if the router name ends in "A" or not to determine the correct suffix):

! <<ROUTER>>
<?
 var BGP_AS = ("<<LOCATION>>" == "DEV") ? "64512" : "37119";
 var RD_SUFFIX = ("<<ROUTER>>".match(/A$/)) ? "01" : "02";
?>

router bgp <? print(BGP_AS) ?>
 vrf TEST
  rd <? print(BGP_AS) ?>:100<? print(RD_SUFFIX) ?>
  address-family ipv4 unicast
  !
  neighbor <<PEER_IP>>
   remote-as 65000
   address-family ipv4 unicast
    maximum-prefix 100 80 warning-only
    soft-reconfiguration inbound always
   !
  !
 !
!

Once you submit the data, it will produce the following output:

! PEXCD1A

router bgp 64512
 vrf TEST
  rd 64512:10001
  address-family ipv4 unicast
  !
  neighbor 192.0.2.2
   remote-as 65000
   address-family ipv4 unicast
    maximum-prefix 100 80 warning-only
    soft-reconfiguration inbound always
   !
  !
 !
!


! PEXCD1B

router bgp 64512
 vrf TEST
  rd 64512:10002
  address-family ipv4 unicast
  !
  neighbor 192.0.2.6
   remote-as 65000
   address-family ipv4 unicast
    maximum-prefix 100 80 warning-only
    soft-reconfiguration inbound always
   !
  !
 !
!


! PEXAY1A

router bgp 37119
 vrf TEST
  rd 37119:10001
  address-family ipv4 unicast
  !
  neighbor 192.0.2.66
   remote-as 65000
   address-family ipv4 unicast
    maximum-prefix 100 80 warning-only
    soft-reconfiguration inbound always
   !
  !
 !
!


! PEXAY1B

router bgp 37119
 vrf TEST
  rd 37119:10002
  address-family ipv4 unicast
  !
  neighbor 192.0.2.70
   remote-as 65000
   address-family ipv4 unicast
    maximum-prefix 100 80 warning-only
    soft-reconfiguration inbound always
   !
  !
 !
!
<?= ... ?>

This is used as shorthand to "<? print(...) ?>", where the "<?=" is used to tell TemplateFx that we want to use the value of the variable. We could write some of the example above using the following syntax instead of the print statements:

router bgp <?= BGP_AS ?>
 vrf TEST
  rd <?= BGP_AS ?>:100<?= RD_SUFFIX ?>
 !
!

To demonstrate some of the more powerful uses of this construct, if we revisit our FOR example above and see how we can update it to allow the user to provide the list of values within the source data. For the purpose of this example we have updated our source data to include an additional column called "HELPERS" as well as changing our "IP" field to the base "IP_SUBNET":

ROUTER, INTERFACE, IP_SUBNET, DEST, HELPERS
PE-1A, Gi0/0/0, 192.0.2.0, R1 (Gi0/0), 192.0.2.129; 192.0.2.130; 192.0.2.131
PE-1A, Gi0/0/1, 192.0.2.4, R2 (Gi0/1)
PE-1A, Gi0/0/2, 192.0.2.8, R3 (Gi0/2), 10.254.0.1

If we now update our original template to use the "<?= ... ?>" syntax we can see how this becomes a much more powerful construct:

interface <<INTERFACE>>
 ip address <?= ip("<<IP_SUBNET>>", +1) ?> 255.255.255.252
<FOR H = [<?= "<<HELPERS>>".split("; ") ?>]>
 ip helper-address {{H}}
</FOR>
 no shutdown
!

First off we are now using the built-in "ip()" function to derive the IP address of the interface - the "ip()" function is being used to add a single IP address to the base value. The JavaScript "split()" function is being used to derive the list of data from a variable provided within the data set. If we click on "Generate Output" we will get the following output:

interface Gi0/0/0
 ip address 192.0.2.1 255.255.255.252
 ip helper-address 192.0.2.129
 ip helper-address 192.0.2.130
 ip helper-address 192.0.2.131
 no shutdown
!

interface Gi0/0/1
 ip address 192.0.2.5 255.255.255.252
 no shutdown
!

interface Gi0/0/2
 ip address 192.0.2.9 255.255.255.252
 ip helper-address 10.254.0.1
 no shutdown
!

6.1. Built-In JavaScript Variables

As well as the standard JavaScript syntax there is also a series of built-in variables that are included with TemplateFx. These will be expanded with future versions of TemplateFx, but for the time being here is the current list:

templatefx.version

This variable returns the current TemplateFx version as a string (e.g. "2.38").

templatefx.build

This variable returns the current TemplateFx build number as a string (e.g. "14222").

templatefx.group_start

This variable will be set to true if the row is the first in the group. This allows you to add text to your template that would be included at the beginning of every output. An example is included below:

<IF (templatefx.group_start)>
!-----
! @ <<ROUTER>>
!-----
</IF>
templatefx.group_end

This variable is the opposite of the "templatefx.group_start" where it will be set to true if the row is the last in the group. This allows you to add text to your template that would be included at the end of every output.

templatefx.row

This variable returns the current row within the data set.

templatefx.rows

This variable returns the total amount of rows within the data set.

templatefx.mrows

When using "Merge Rows", this variable returns the number of rows merged into the current row of the data set.

templatefx.delimiter

When using "Merge Rows", this variable returns the character which is used as the delimiter between merged rows.

6.2. Built-In JavaScript Functions

In addition to the built-in variables, there is also a series of built-in functions that are included with TemplateFx. These will be expanded with future versions of TemplateFx, but for the time being here is the current list:

counter ("key", [increment], [start])

This function provides a persistent counter across rows. Every time you call the function with the same "key" then it will increment the counter and return the number. Different persistent counters can be created using different "key" values. There is an optional "increment" parameter which allows you to specify a different increment as opposed to the default of 1. There is also an optional "start" parameter which allows you to specify a different start number as opposed to the default of 1. The following example demonstrates both of these parameters to produce a positive and negative counter:

# This will produce a positive sequence of 0, 5, 10, 15, 20, etc
<?= counter("", 5, 0) ?>

# This will produce a negative sequence of 0, -5, -10, -15, -20, etc
<?= counter("", -5, 0) ?>
cancel ([message])

This function provides a way to cancel processing a template if something isn't right. This could be if an invalid value has been specified in the data source or if a key field hasn't been filled in correctly. An optional "message" can be provided to give the user more information about why processing was cancelled. The first time the function is executed, the whole processing is stopped.

ip ("ip", number, [ipv6format]) IPv4 + IPv6

This function takes an IPv4 or IPv6 address (doesn't support IPv4-mapped IPv6 addresses) and adds or subtracts a number of IP addresses from the value. If it is an IPv6 address then you can specify an optional third parameter which allows you to specify the presentation format of the returned IPv6 address, using one of the following values:

ipv6format = 0 || null
This, or the default if omitted, will output the address using the RFC4291 preferred format of "x:x:x:x:x:x:x:x" (i.e. 2001:db8:0:0:8:800:200c:417a), where the 'x's are one to four hexadecimal digits of the eight 16-bit pieces of the address with leading zeroes omitted.

ipv6format = 1
This will output the address using a special compressed format (i.e. 2001:db8::8:800:200c:417a), which allows you to replace long strings of zero bits with "::". The use of "::" indicates one or more groups of 16 bits of zeroes. The "::" can only appear once in an address and should be the longest run of groups of zeroes. The "::" can also be used to compress leading or trailing zeroes in an address.

ipv6format = 2
This will output the address using the full expanded format (i.e. 2001:0db8:0000:0000:0008:0800:200c:417a).

To demonstrate this function, some examples are provided below:

# Obtain the IP address before "192.0.2.10" (i.e. 192.0.2.9)
<?= ip("192.0.2.10", -1) ?>

# Convert the IPv6 address "2001:0db8:0000:0000:0008:0800:200c:417a" into compressed form (i.e. 2001:db8::8:800:200c:417a)
<?= ip("2001:0db8:0000:0000:0008:0800:200c:417a", 0, 1) ?>

# Add 32 to the IPv6 address "2001:db8::" and output in preferred form (i.e. 2001:db8:0:0:0:0:0:20)
<?= ip("2001:db8::", 32) ?>
insubnet ("subnet", "ip") IPv4 Only

This function takes a subnet in CIDR format (i.e. "192.0.2.0/24") and returns true if the passed IPv4 address is in the subnet or false if it isn't. The following example verifies if "192.0.2.1" is contained within "192.0.2.0/24":

<IF (insubnet("192.0.2.0/24", "192.0.2.1"))>
  ...
</IF>
network ("cidr") IPv4 Only

This function takes an IPv4 prefix in CIDR format (i.e. "192.0.2.13/24") and returns the network address. The function will return "undefined" if an error occurs, an invalid parameter is detected or if the mask length doesn't support a network address (i.e. /32). In the above example it will return the string "192.0.2.0".

ipfirst ("cidr") IPv4 Only

This function takes an IPv4 prefix in CIDR format (i.e. "192.0.2.13/24") and returns the first usable address of the subnet. The function will return "undefined" if an error occurs or an invalid parameter is detected. In the above example it will return the string "192.0.2.1".

iplast ("cidr") IPv4 Only

This function takes an IPv4 prefix in CIDR format (i.e. "192.0.2.13/24") and returns the last usable address of the subnet. The function will return "undefined" if an error occurs or an invalid parameter is detected. In the above example it will return the string "192.0.2.254".

broadcast ("cidr") IPv4 Only

This function takes an IPv4 prefix in CIDR format (i.e. "192.0.2.13/24") and returns the broadcast address. The function will return "undefined" if an error occurs, an invalid parameter is detected or if the mask length doesn't support a broadcast address (i.e. /31 or /32). In the above example it will return the string "192.0.2.255".

ip2long ("ip") IPv4 Only

This function takes an IPv4 address and converts it into a long.

long2ip (long) IPv4 Only

This function takes a long and converts it into an IPv4 address.

passwd ([length], [chars])

This function generates a random password using the following characters: a-z, A-Z, 0-9, unless a custom character set using the "chars" parameter has been provided. The length is determined by the passed "length" parameter or a length of 16 is used if the parameter is omitted.

# Generate a random 12 character password using the provided characters
<?= passwd(12, "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZZ") ?>
timestamp (["format"])

This function generates a timestamp based on the current date and time, either based on a provided format (using the same format as Java's SimpleDateFormat class) or using a default pattern if omitted.

clog ("message")

This function is used for console logging to aid troubleshooting. It will write the passed message to the console (if the console has been enabled). This allows you to debug your templates to output the current value of a variable, etc in different places.