I recently started working with Plaster and I really like this module. I covered my first template in my Adventures in Plaster blog post last week. I have been pulling together ideas for more Plaster templates and I thought up a fun one to work on.
I am going to build a Plaster template that builds a Plaster template. I am calling this new template GetPlastered
.
This will be a good example demonstrating the TemplateFile features of Plaster.
Index
- Index
- Project plan
- Starting a new template
- Planning the questions
- TemplateFile
- GetPlastered in action
- Wrapping it up
Project plan
My primary goal is to have a Plaster template that will turn an existing folder/project into a Plaster template. Our Plaster template will generate a PlasterTemplate.xml
for that folder.
This can be confusing because we are also creating a PlasterTemplate.xml
to make this template that generates a PlasterTemplate.xml
for a new template. It is like we are writing code that writes the same code that we are writing.
Starting a new template
I already have a repository for my Plaster templates, so all I need to do is create the initial template manifest.
$templateName = 'GetPlastered'
$manifestProperties = @{
Path = ".\$templateName\PlasterManifest.xml"
Title = "Generate Plaster Maifest"
TemplateName = $templateName
TemplateVersion = '0.0.1'
Author = 'Kevin Marquette'
}
New-Item -Path $templateName -ItemType Directory
New-PlasterManifest @manifestProperties
Planning the questions
Because my intent is that this template will be used instead of the New-PlasterManifest
Cmdlet, we need to capture that functionality.
- Template Name
- Template Title
- Template Author
That should sum up the information we need to collect.
Creating parameters
Now we can turn those planned questions into parameters. These questions are straightforward parameters to create.
<parameter name="TemplateName"
type="text"
prompt="Template Name"
default="${PLASTER_DestinationName}" />
<parameter name="TemplateTitle"
type="text"
prompt="Template Title"
default="${PLASTER_PARAM_TemplateName" />
<parameter name="TemplateAuthor"
type="user-fullname"
prompt="Author" />
I added these parameters to the parameters section of the PlasterManifest.xml
file.
For the TemplateName
default value, I use the name of the destination folder that is specified when Invoke-Plaster
is invoked.
For the TemplateAuthor
, I used user-fullname
for the type
. That is a special type that pulls the value from the user’s .gitconfig
as a default.
TemplateFile
Now we need to create a TemplateFile to generate the PlasterTemplate.xml
file. The first half of the TemplateFile will be basic value substitution.
<?xml version="1.0" encoding="utf-8"?>
<plasterManifest schemaVersion="1.0"
xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
<metadata>
<name><%= $PLASTER_PARAM_TemplateName %></name>
<id><%= $PLASTER_GUID1 %></id>
<version>0.0.1</version>
<title><%= $PLASTER_PARAM_TemplateTitle %></title>
<description></description>
<author><%= $PLASTER_PARAM_TemplateAuthor %></author>
<tags></tags>
</metadata>
<parameters>
</parameters>
<content>
...
All the magic happens in the second half of this TemplateFile. We walk the destination folder for both folders and files to create the content section.
...
<content>
<%
$path = $PLASTER_DestinationPath
$folders = Get-ChildItem -Path $path -Directory -Recurse
$files = Get-ChildItem -Path $path -File -Recurse
$path += '\'
foreach($node in $folders.fullname)
{
$destination = $node.replace($path,'')
" <file source='' destination='$destination'/>"
}
foreach($node in $files.fullname)
{
$source = $node.replace($path,'')
" <file source='$source' destination=''/>"
}
%>
</content>
</plasterManifest>
Then we save this into a template file called PlasterTemplate.aps1
and add a templateFile
entry to our original PlasterTemplate.xml
content section.
<content>
<templateFile source="PlasterTemplate.aps1"
destination="PlasterManifest.xml" />
</content>
I can call that template file anything I want and could easily have left the file extension as xml. I am currently using .aps1
as that designation.
GetPlastered in action
Now we can take any folder and turn that into a Plaster template. The idea is that I would build out the folder first with all the files that should be included. Then instead of running New-PlasterManifest
, I would use Invoke-Plaster
with this template.
Example
Here is a quick example to see this working. Lets say I have two folders of common tests that I drop into modules. I first move all of these to a new folder called MyTests.
MyTests
├───spec
│ module.feature
│ module.Steps.ps1
│
└───tests
Export-PSGraph.Tests.ps1
Feature.Tests.ps1
Help.Tests.ps1
Project.Tests.ps1
Regression.Tests.ps1
Unit.Tests.ps1
Now we run the GetPlastered
template.
PS:> Invoke-Plaster -DestinationPath .\MyTests -TemplatePath .\GetPlastered
____ _ _
| _ \| | __ _ ___| |_ ___ _ __
| |_) | |/ _` / __| __/ _ \ '__|
| __/| | (_| \__ \ || __/ |
|_| |_|\__,_|___/\__\___|_|
v1.0.1
==================================================
Template Name (MyTests):
Template Title (MyTests):
Author (KevinMarquette):
Destination path: .\MyTests
Create PlasterManifest.xml
I just accepted all the defaults and my .\MyTests\PlasterManifest.xml
was created. Here is the contents of that file showing every file in the content
section.
<?xml version="1.0" encoding="utf-8"?>
<plasterManifest schemaVersion="1.0"
xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
<metadata>
<name>MyTests</name>
<id>3c3480d8-c2fa-4777-bb0c-a2453c8147c3</id>
<version>0.0.1</version>
<title></title>
<description></description>
<author>KevinMarquette</author>
<tags>GetPlastered</tags>
</metadata>
<parameters>
</parameters>
<content>
<file source='' destination='spec'/>
<file source='' destination='tests'/>
<file source='spec\module.feature' destination=''/>
<file source='spec\module.Steps.ps1' destination=''/>
<file source='tests\Feature.Tests.ps1' destination=''/>
<file source='tests\Help.Tests.ps1' destination=''/>
<file source='tests\Project.Tests.ps1' destination=''/>
<file source='tests\Regression.Tests.ps1' destination=''/>
<file source='tests\Unit.Tests.ps1' destination=''/>
</content>
</plasterManifest>
We can now use this new template to deploy our tests.
PS:> Invoke-Plaster -DestinationPath .\TestFolder -TemplatePath .\MyTests
____ _ _
| _ \| | __ _ ___| |_ ___ _ __
| |_) | |/ _` / __| __/ _ \ '__|
| __/| | (_| \__ \ || __/ |
|_| |_|\__,_|___/\__\___|_|
v1.0.1
==================================================
Destination path: .\TestFolder
Create spec\
Create tests\
Create spec\module.feature
Create spec\module.Steps.ps1
Create tests\Feature.Tests.ps1
Create tests\Help.Tests.ps1
Create tests\Project.Tests.ps1
Create tests\Regression.Tests.ps1
Create tests\Unit.Tests.ps1
PS:> cd .\TestFolder
PS:> tree /f
TestFolder
├───spec
│ module.feature
│ module.Steps.ps1
│
└───tests
Feature.Tests.ps1
Help.Tests.ps1
Project.Tests.ps1
Regression.Tests.ps1
Unit.Tests.ps1
Wrapping it up
Other than the layers of inception going on, this really was an easy template to create. This was a fun project and I have it published with my other Plaster templates.
I am working on setting this up to be published to the PSGallery. I’ll update this post when that happens. I hope you enjoy it.