eps
eps copied to clipboard
A templating engine for PowerShell
EPS
EPS ( Embedded PowerShell ), inspired by ERB, is a templating tool that embeds PowerShell code into a text document. It is conceptually and syntactically similar to ERB for Ruby or Twig for PHP.
EPS can be used to generate any kind of text. The example below illustrates generating plain text, but it could be used to generate HTML as in DS or PowerShell code as in the Forge Module generator.
EPS is available in the PowerShell Gallary. You can install the module with the following command:
Install-Module -Name EPS
Syntax
EPS allows PowerShell code to be embedded within a pair of <% ... %>,
<%= ... %>, or <%# ... %> as well:
- Code in
<% CODE %>blocks are executed but no value is inserted.- If started with
<%-: the preceding indentation is trimmed. - If terminated with
-%>: the following line break is trimmed.
- If started with
- Code in
<%= EXPRESSION %>blocks insert the value ofEXPRESSION.- If terminated with
-%>: the following line break is trimmed.
- If terminated with
- Text in
<%# ... %>blocks are treated as comments and are removed from the output.- If terminated with
-%>: the following line break is trimmed.
- If terminated with
<%%and%%>: are replaced respectively by<%and%>in the output.
All blocks accept multi-line content as long as it is valid PowerShell.
Command Line Usage
Invoke-EpsTemplate [-Template <string>] [-Binding <hashtable>] [-Safe] [<CommonParameters>]
Invoke-EpsTemplate [-Path <string>] [-Binding <hashtable>] [-Safe] [<CommonParameters>]
- use
-Templateto render the template in the corresponding string. than a file - use
-Pathto render the template in the corresponding file. -Saferenders the template in isolated mode (in another thread/powershell instance) to avoid variable pollution (variable that are already in the current scope).- if
-Safeis provided, you must bind your values using-Bindingoption with aHashtablecontaining key/value pairs.
Example
A very simple example of EPS would be :
$name = "Dave"
Invoke-EpsTemplate -Template 'Hello <%= $name %>!'
This script produces the following result:
Hello Dave!
In a template file Test.eps:
Hi <%= $name %>
<%# this is a comment -%>
Please buy me the following items:
<% 1..5 | %{ -%>
- <%= $_ %> pigs ...
<% } -%>
Dave is a <% if($True) { %>boy<% } else { %>girl<% } %>.
Thanks,
Dave
<%= (Get-Date -f yyyy-MM-dd) %>
Then render it in on the command line:
Import-Module EPS
$name = "ABC"
Invoke-EpsTemplate -Path Test.eps
Here it is in non-safe mode (render template with values in current run space)
To use safe mode (render the template in an isolated scope) execute: Invoke-EpsTemplate -Path Test.eps -Safe with binding values
It will produce:
Hi dave
Please buy me the following items:
- 1 pigs ...
- 2 pigs ...
- 3 pigs ...
- 4 pigs ...
- 5 pigs ...
Dave is a boy.
Thanks,
Dave
2016-12-07
Or you can use safe mode with data bindings:
Invoke-EpsTemplate -Path Test.eps -Safe -binding @{ name = "dave" }
which will generate the same output.
More examples
Multi-line code or expression blocks
You can use multi-line statements in blocks:
$name = "Dave"
Invoke-EpsTemplate -Template @'
Hello <%= $name %>!
Today is <%= Get-Date -UFormat %x %>.
'@
will produce:
Hello Dave!
Today is 11/12/17.
Iterating and joining the results
Sometimes we would like to iterate over a collection, generate some text for each element and finally join the generated blocks together with a separator.
Inside expression blocks
In an expression block we can use the following idiomatic PowerShell snippet:
Invoke-EpsTemplate -Template @'
<%= ("Id", "Name", "Description" | ForEach-Object { "[String]`$$_" }) -Join ",`n" -%>
'@
which would generate the following result:
[String]$Id,
[String]$Name,
[String]$Description
With EPS templating elements
However, due to EPS internal workings, the following code would not work:
Invoke-EpsTemplate -Template @'
<% ("Id", "Name", "Description" | ForEach-Object { -%>
[String]$<%= $_ -%>
<% }) -join ",`n" -%>
'@
The -join operator is ignored by EPS:
[String]$Id[String]$Name[String]$Description
This is expected behavior because -join is applied to the result of
ForEach-Object which is defined inside a CODE block and should not produce
any output.
EPS provides an internal Each function whose behavior is similar to
ForEach-Object but achieves the desired result inside a template :
Each [-Process] <scriptblock> [-InputObject <Object[]>] [-Begin <scriptblock>] [-End <scriptblock>] [-Join <string>]
Each can only be used in PS v3 or above.
This snippet of EPS would generate the desired result (notice that -Join is a parameter of Each
and is not applied to its result value as would be the case with the -join operator):
Invoke-EpsTemplate -Template @'
<% "Id", "Name", "Description" | Each { -%>
[String]$<%= $_ -%>
<% } -Join ",`n" -%>
'@
and generate:
[String]$Id,
[String]$Name,
[String]$Description
In some cases it can be useful to also generate a prefix and suffix to the iterated part:
Invoke-EpsTemplate -Template @'
<% "Id", "Name", "Description" | Each { -%>
[String]$<%= $_ -%>
<% } -Begin { %>[NSSession]$Session<% } -End { %>[String]$LogLevel<% } -Join ",`n" -%>
'@
will generate:
[NSSession]$Session,
[String]$Id,
[String]$Name,
[String]$Description,
[String]$LogLevel
Notice that when using -Begin and/or -End with -Join all blocks are joined together.
If you want to prefix and suffix, without joining the prefix and suffix, use the following pattern:
Invoke-EpsTemplate -Template @'
Param(
<% "Id", "Name", "Description" | Each { -%>
[String]$<%= $_ -%>
<% } -Join ",`n" %>
)
'@
Which will generate:
Param(
[String]$Id,
[String]$Name,
[String]$Description
)
Iterating with an index
In some cases it is useful to iterate over a collection while maintaining an index of the
current item. The Each function exposes a $index variable to the script blocks it executes.
The $index variable starts at 0 for the first element.
Invoke-EpsTemplate -Template @'
<% "Dave", "Bob", "Alice" | Each { -%>
<%= $Index + 1 %>. <%= $_ %>
<% } -%>
'@
would generate the following listing:
1. Dave
2. Bob
3. Alice
Handling default values
Using default values if the provided variable is $Null or empty (as returned by the [string]::IsNullOrEmpty function) is a very common pattern which can be done easily with a template like:
$config = [PSCustomObject]@{
Host = "localhost"
#Port = "8080" # this would be an optional configuration value
}
Invoke-EpsTemplate -Template @'
<%= $config.Host
%>:<%=
if ([string]::IsNullOrEmpty($config.Port)) { "80" } else { $config.Port }
%>
'@
EPS provides a Get-OrElse function that allows for a shorter version:
$config = [PSCustomObject]@{
Host = "localhost"
#Port = "8080" # this would be an optional configuration value
}
Invoke-EpsTemplate -Template @'
<%= $config.Host %>:<%= Get-OrElse $config.Port "80" %>
<%= $config.Host %>:<%= $config.Port | Get-OrElse -Default "80" %>
'@
Helper functions
If there's a more complicated piece of powershell code that should be reused across multiple files, it can be encapsulated in a helper function, i.e.:
$helpers = @{
NumberedList = {
param($arr)
$i = 1
$arr | foreach-object { "$i. $_"; $i++ } | out-string
}
}
$helpers should be passed to Invoke-Eps:
Invoke-EpsTemplate -Path Test.eps -helpers $helpers
Now, NumberedList can be used in the template.
<%= NumberedList "Dave", "Bob", "Alice" %>
would generate the following listing:
1. Dave
2. Bob
3. Alice
Contribution
- Original version was written by Dave Wu.
- Maintained now and extended by Dominique Broeglin (@dbroeglin), thank you pal 谢谢!
Help find more bugs! Or find more usage of this tool... Author's email: [email protected]