Skip to content

Commit 9a0d92f

Browse files
psjamespHeyItsGilbert
authored andcommitted
More updates
1 parent a05818a commit 9a0d92f

7 files changed

Lines changed: 269 additions & 51 deletions

Plaster/JsonManifestHandler.ps1

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#Requires -Version 5.1
1+
# Requires -Version 5.1
22

33
using namespace System.Management.Automation
44

@@ -11,6 +11,7 @@ using namespace System.Management.Automation
1111
including validation, conversion, and processing capabilities.
1212
#>
1313

14+
1415
# JSON Schema validation function
1516
function Test-JsonManifest {
1617
[CmdletBinding()]
@@ -1000,13 +1001,4 @@ function Get-PlasterManifestType {
10001001
} catch {
10011002
throw "Error determining manifest type for '$ManifestPath': $($_.Exception.Message)"
10021003
}
1003-
}
1004-
1005-
# Export functions
1006-
Export-ModuleMember -Function @(
1007-
'Test-JsonManifest'
1008-
'ConvertFrom-JsonManifest'
1009-
'ConvertTo-JsonManifest'
1010-
'Get-PlasterManifestType'
1011-
'New-JsonManifestStructure'
1012-
)
1004+
}

Plaster/Plaster.psm1

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,42 @@ $ParameterDefaultValueStoreRootPath = switch ($true) {
115115
"$Home/.plaster"
116116
}
117117
}
118+
# Dot source the individual module command scripts with error handling
119+
$commandFiles = @(
120+
'writePlasterLog.ps1'
121+
'PlasterVariables.ps1'
122+
'JsonManifestHandler.ps1'
123+
'NewPlasterManifest.ps1'
124+
'TestPlasterManifest.ps1'
125+
'GetPlasterTemplate.ps1'
126+
'InvokePlaster.ps1'
127+
128+
)
129+
foreach ($file in $commandFiles) {
130+
$filePath = Join-Path $PSScriptRoot $file
131+
if (Test-Path $filePath) {
132+
try {
133+
Write-Verbose "Loading command file: $file"
134+
. $filePath
135+
Write-Verbose "Successfully loaded: $file"
136+
} catch {
137+
$errorMessage = "Failed to load command file '$file': $($_.Exception.Message)"
138+
if (Get-Command Write-PlasterLog -ErrorAction SilentlyContinue) {
139+
Write-PlasterLog -Level Error -Message $errorMessage
140+
} else {
141+
Write-Error $errorMessage
142+
}
143+
throw $_
144+
}
145+
} else {
146+
$warningMessage = "Command file not found: $filePath"
147+
if (Get-Command Write-PlasterLog -ErrorAction SilentlyContinue) {
148+
Write-PlasterLog -Level Warning -Message $warningMessage
149+
} else {
150+
Write-Warning $warningMessage
151+
}
152+
}
153+
}
118154

119155
# Enhanced platform detection with fallback
120156
if (-not (Get-Variable -Name 'IsWindows' -ErrorAction SilentlyContinue)) {

Plaster/PlasterVariables.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ if (-not (Get-Variable -Name 'JsonSchemaVersion' -Scope Script -ErrorAction Sile
1818
}
1919

2020
# Export the variables that need to be available globally
21-
Export-ModuleMember -Variable @('TargetNamespace', 'DefaultEncoding', 'JsonSchemaVersion')
21+
Export-ModuleMember -Variable @('TargetNamespace', 'DefaultEncoding', 'JsonSchemaVersion')
22+

Plaster/Private/Get-ManifestsUnderPath.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function Get-ManifestsUnderPath {
4545
)
4646
$getChildItemSplat = @{
4747
Path = $RootPath
48-
Include = "plasterManifest.xml"
48+
Include = "plasterManifest.xml", "plasterManifest.json"
4949
Recurse = $Recurse
5050
}
5151
$manifestPaths = Get-ChildItem @getChildItemSplat

Plaster/Private/New-TemplateObjectFromManifest.ps1

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,47 @@ function New-TemplateObjectFromManifest {
3434
[string]$Tag
3535
)
3636

37-
$manifestXml = Test-PlasterManifest -Path $ManifestPath
38-
$metadata = $manifestXml["plasterManifest"]["metadata"]
39-
40-
$manifestObj = [PSCustomObject]@{
41-
Name = $metadata["name"].InnerText
42-
Title = $metadata["title"].InnerText
43-
Author = $metadata["author"].InnerText
44-
Version = [System.Version]::Parse($metadata["version"].InnerText)
45-
Description = $metadata["description"].InnerText
46-
Tags = $metadata["tags"].InnerText.split(",") | ForEach-Object { $_.Trim() }
47-
TemplatePath = $manifestPath.Directory.FullName
48-
}
37+
try{
38+
$manifestXml = Test-PlasterManifest -Path $ManifestPath
39+
$metadata = $manifestXml["plasterManifest"]["metadata"]
40+
41+
$manifestObj = [PSCustomObject]@{
42+
Name = [string]$metadata.name
43+
Title = [string]$metadata.title
44+
Author = [string]$metadata.author
45+
Version = [System.Version]::Parse([string]$metadata.version)
46+
Description = if ($metadata.description) { [string]$metadata.description } else { "" }
47+
Tags = if ($metadata.tags) { ([string]$metadata.tags).split(",") | ForEach-Object { $_.Trim() } } else { @() }
48+
TemplatePath = $manifestPath.Directory.FullName
49+
Format = if ($manifestPath.Extension -eq '.json') { 'JSON' } else { 'XML' }
50+
}
51+
52+
$manifestObj.PSTypeNames.Insert(0, "Microsoft.PowerShell.Plaster.PlasterTemplate")
53+
$addMemberSplat = @{
54+
MemberType = 'ScriptMethod'
55+
InputObject = $manifestObj
56+
Name = "InvokePlaster"
57+
Value = { Invoke-Plaster -TemplatePath $this.TemplatePath }
58+
}
59+
Add-Member @addMemberSplat
4960

50-
$manifestObj.PSTypeNames.Insert(0, "Microsoft.PowerShell.Plaster.PlasterTemplate")
51-
$addMemberSplat = @{
52-
MemberType = 'ScriptMethod'
53-
InputObject = $manifestObj
54-
Name = "InvokePlaster"
55-
Value = { Invoke-Plaster -TemplatePath $this.TemplatePath }
61+
# Fix the filtering logic
62+
$result = $manifestObj
63+
if ($name -and $name -ne "*") {
64+
$result = $result | Where-Object Name -like $name
65+
}
66+
if ($tag -and $tag -ne "*") {
67+
# Only filter by tags if the template actually has tags
68+
if ($result.Tags -and $result.Tags.Count -gt 0) {
69+
$result = $result | Where-Object { $_.Tags -contains $tag -or ($_.Tags | Where-Object { $_ -like $tag }) }
70+
} elseif ($tag -ne "*") {
71+
# If template has no tags but we're filtering for a specific tag, exclude it
72+
$result = $null
73+
}
74+
}
75+
return $result
76+
} catch {
77+
Write-Debug "Failed to process manifest at $($manifestPath.FullName): $($_.Exception.Message)"
78+
return $null
5679
}
57-
Add-Member @addMemberSplat
58-
return $manifestObj | Where-Object Name -Like $Name | Where-Object Tags -Like $Tag
5980
}

Plaster/Private/Resolve-ProcessParameter.ps1

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,147 @@ function Resolve-ProcessParameter {
77
$name = $Node.name
88
$type = $Node.type
99
$store = $Node.store
10-
$pattern = $Node.pattern # New for JSON format
10+
11+
$pattern = $Node.pattern
12+
1113
$condition = $Node.condition
1214

13-
$default = Resolve-AttributeValue $Node.default (GetErrorLocationParameterAttrVal $name default)
15+
$default = Resolve-AttributeValue $Node.default (Get-ErrorLocationParameterAttrVal $name default)
1416

15-
# Enhanced condition evaluation with JSON support
16-
if ($condition -and !(EvaluateConditionAttribute $condition "'<$($Node.LocalName)>'")) {
17+
if ($condition -and !(Test-ConditionAttribute $condition "'<$($Node.LocalName)>'")) {
1718
if (-not [string]::IsNullOrEmpty($default) -and $type -eq 'text') {
1819
Set-PlasterVariable -Name $name -Value $default -IsParam $true
19-
$PSCmdlet.WriteDebug("Parameter $($name) condition false, using default: $default")
20+
$PSCmdlet.WriteDebug("The condition of the parameter $($name) with the type 'text' evaluated to false. The parameter has a default value which will be used.")
2021
} else {
22+
# Define the parameter so later conditions can use it but its value will be $null
2123
Set-PlasterVariable -Name $name -Value $null -IsParam $true
2224
$PSCmdlet.WriteDebug("Skipping parameter $($name), condition evaluated to false.")
2325
}
26+
2427
return
2528
}
2629

27-
$prompt = Resolve-AttributeValue $Node.prompt (GetErrorLocationParameterAttrVal $name prompt)
30+
$prompt = Resolve-AttributeValue $Node.prompt (Get-ErrorLocationParameterAttrVal $name prompt)
2831

29-
# Check for dynamic parameter value
30-
if ($boundParameters.ContainsKey($name)) {
31-
$value = $boundParameters[$name]
32+
# Check if parameter was provided via a dynamic parameter.
33+
if ($script:boundParameters.ContainsKey($name)) {
34+
$value = $script:boundParameters[$name]
35+
} else {
36+
# Not a dynamic parameter so prompt user for the value but first check for a stored default value.
37+
if ($store -and ($null -ne $script:defaultValueStore[$name])) {
38+
$default = $script:defaultValueStore[$name]
39+
$PSCmdlet.WriteDebug("Read default value '$default' for parameter '$name' from default value store.")
3240

33-
# Enhanced validation for JSON parameters
34-
if ($pattern -and $type -eq 'text' -and $value -notmatch $pattern) {
35-
$validationMessage = if ($Node.validationMessage) { $Node.validationMessage } else { "Value does not match required pattern: $pattern" }
36-
throw "Parameter '$name' validation failed: $validationMessage"
41+
if (($store -eq 'encrypted') -and ($default -is [System.Security.SecureString])) {
42+
try {
43+
$cred = New-Object -TypeName PSCredential -ArgumentList 'jsbplh', $default
44+
$default = $cred.GetNetworkCredential().Password
45+
$PSCmdlet.WriteDebug("Unencrypted default value for parameter '$name'.")
46+
} catch [System.Exception] {
47+
Write-Warning ($LocalizedData.ErrorUnencryptingSecureString_F1 -f $name)
48+
}
49+
}
3750
}
3851

39-
Write-PlasterLog -Level Debug -Message "Using dynamic parameter value for '$name': $value"
40-
} else {
41-
# Interactive parameter collection with enhanced validation
42-
# ... (rest of parameter processing logic)
52+
# If the prompt message failed to evaluate or was empty, supply a diagnostic prompt message
53+
if (!$prompt) {
54+
$prompt = $LocalizedData.MissingParameterPrompt_F1 -f $name
55+
}
56+
57+
# Some default values might not come from the template e.g. some are harvested from .gitconfig if it exists.
58+
$defaultNotFromTemplate = $false
59+
60+
$splat = @{}
61+
62+
if ($null -ne $pattern) {
63+
$splat.Add('pattern', $pattern)
64+
}
65+
66+
# Now prompt user for parameter value based on the parameter type.
67+
switch -regex ($type) {
68+
'text' {
69+
# Display an appropriate "default" value in the prompt string.
70+
if ($default) {
71+
if ($store -eq 'encrypted') {
72+
$obscuredDefault = $default -replace '(....).*', '$1****'
73+
$prompt += " ($obscuredDefault)"
74+
} else {
75+
$prompt += " ($default)"
76+
}
77+
}
78+
# Prompt the user for text input.
79+
$value = Read-PromptForInput $prompt $default @splat
80+
$valueToStore = $value
81+
}
82+
'user-fullname' {
83+
# If no default, try to get a name from git config.
84+
if (!$default) {
85+
$default = Get-GitConfigValue('name')
86+
$defaultNotFromTemplate = $true
87+
}
88+
89+
if ($default) {
90+
if ($store -eq 'encrypted') {
91+
$obscuredDefault = $default -replace '(....).*', '$1****'
92+
$prompt += " ($obscuredDefault)"
93+
} else {
94+
$prompt += " ($default)"
95+
}
96+
}
97+
98+
# Prompt the user for text input.
99+
$value = Read-PromptForInput $prompt $default @splat
100+
$valueToStore = $value
101+
}
102+
'user-email' {
103+
# If no default, try to get an email from git config
104+
if (-not $default) {
105+
$default = Get-GitConfigValue('email')
106+
$defaultNotFromTemplate = $true
107+
}
108+
109+
if ($default) {
110+
if ($store -eq 'encrypted') {
111+
$obscuredDefault = $default -replace '(....).*', '$1****'
112+
$prompt += " ($obscuredDefault)"
113+
} else {
114+
$prompt += " ($default)"
115+
}
116+
}
117+
118+
# Prompt the user for text input.
119+
$value = Read-PromptForInput $prompt $default @splat
120+
$valueToStore = $value
121+
}
122+
'choice|multichoice' {
123+
$choices = $Node.ChildNodes
124+
$defaults = [int[]]($default -split ',')
125+
126+
# Prompt the user for choice or multichoice selection input.
127+
$selections = Read-PromptForChoice $name $choices $prompt $defaults -IsMultiChoice:($type -eq 'multichoice')
128+
$value = $selections.Values
129+
$OFS = ","
130+
$valueToStore = "$($selections.Indices)"
131+
}
132+
default { throw ($LocalizedData.UnrecognizedParameterType_F2 -f $type, $Node.LocalName) }
133+
}
134+
135+
# If parameter specifies that user's input be stored as the default value,
136+
# store it to file if the value has changed.
137+
if ($store -and (($default -ne $valueToStore) -or $defaultNotFromTemplate)) {
138+
if ($store -eq 'encrypted') {
139+
$PSCmdlet.WriteDebug("Storing new, encrypted default value for parameter '$name' to default value store.")
140+
$script:defaultValueStore[$name] = ConvertTo-SecureString -String $valueToStore -AsPlainText -Force
141+
} else {
142+
$PSCmdlet.WriteDebug("Storing new default value '$valueToStore' for parameter '$name' to default value store.")
143+
$script:defaultValueStore[$name] = $valueToStore
144+
}
145+
146+
$script:flags.DefaultValueStoreDirty = $true
147+
}
43148
}
44149

45-
# Make template defined parameters available as PowerShell variables
150+
# Make template defined parameters available as a PowerShell variable PLASTER_PARAM_<parameterName>.
46151
Set-PlasterVariable -Name $name -Value $value -IsParam $true
47152
Write-PlasterLog -Level Debug -Message "Set parameter variable: PLASTER_PARAM_$name = $value"
48153
}

Plaster/WritePlasterLog.ps1

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
function global:Write-PlasterLog {
2+
[CmdletBinding()]
3+
param(
4+
[Parameter(Mandatory)]
5+
[ValidateSet('Error', 'Warning', 'Information', 'Verbose', 'Debug')]
6+
[string]$Level,
7+
8+
[Parameter(Mandatory)]
9+
[string]$Message,
10+
11+
[Parameter()]
12+
[string]$Source = 'Plaster'
13+
)
14+
15+
# Check if we should log at this level
16+
$logLevels = @{
17+
'Error' = 0
18+
'Warning' = 1
19+
'Information' = 2
20+
'Verbose' = 3
21+
'Debug' = 4
22+
}
23+
24+
$currentLogLevel = $script:LogLevel ?? 'Information'
25+
$currentLevelValue = $logLevels[$currentLogLevel] ?? 2
26+
$messageLevelValue = $logLevels[$Level] ?? 2
27+
28+
if ($messageLevelValue -gt $currentLevelValue) {
29+
return
30+
}
31+
32+
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
33+
$logMessage = "[$timestamp] [$Level] [$Source] $Message"
34+
35+
# Handle different log levels appropriately
36+
switch ($Level) {
37+
'Error' {
38+
Write-Error $logMessage -ErrorAction Continue
39+
}
40+
'Warning' {
41+
Write-Warning $logMessage
42+
}
43+
'Information' {
44+
Write-Information $logMessage -InformationAction Continue
45+
}
46+
'Verbose' {
47+
Write-Verbose $logMessage
48+
}
49+
'Debug' {
50+
Write-Debug $logMessage
51+
}
52+
}
53+
54+
# Also write to host for immediate feedback during interactive sessions
55+
if ($Level -in @('Error', 'Warning') -and $Host.Name -ne 'ServerRemoteHost') {
56+
$color = switch ($Level) {
57+
'Error' { 'Red' }
58+
'Warning' { 'Yellow' }
59+
default { 'White' }
60+
}
61+
Write-Host $logMessage -ForegroundColor $color
62+
}
63+
}

0 commit comments

Comments
 (0)