Public/Templates.ps1
|
# PSSnips — Snippet template management (New-SnipFromTemplate, Get-SnipTemplate). function New-SnipFromTemplate { <# .SYNOPSIS Creates a new snippet from a named template with variable substitution. .DESCRIPTION Resolves a template by name, first checking ~/.pssnips/templates/ for custom templates, then falling back to built-in templates. Fills {{VARIABLE}} placeholders from the -Variables hashtable; any remaining placeholders are prompted interactively. Saves the result as a new snippet via New-Snip. Built-in templates: azure-function PowerShell Azure Function boilerplate Variables: FUNCTION_NAME, HTTP_METHOD rest-call Invoke-RestMethod template Variables: URL, METHOD, BODY k8s-job Kubernetes Job manifest (text/YAML) Variables: JOB_NAME, IMAGE Custom templates are stored in ~/.pssnips/templates/ as plain-text files with {{VARIABLE}} placeholders. The file base name is the template name. .PARAMETER Template Mandatory. The name of the template to use (e.g., 'azure-function'). .PARAMETER Name Mandatory. The name for the new snippet to create. .PARAMETER Variables Optional. A hashtable of placeholder values (keys are case-insensitive). Any placeholder not found here is prompted interactively. .PARAMETER Force Optional switch. Overwrites an existing snippet with the same name. .EXAMPLE New-SnipFromTemplate -Template azure-function -Name my-func ` -Variables @{ FUNCTION_NAME='MyFunc'; HTTP_METHOD='GET' } Creates 'my-func' from the azure-function template. .EXAMPLE New-SnipFromTemplate rest-call my-api Creates a snippet from the rest-call template, prompting for URL, METHOD, BODY. .INPUTS None. This function does not accept pipeline input. .OUTPUTS None. Delegates to New-Snip which writes a confirmation message. .NOTES Placeholder syntax: {{VARIABLE_NAME}} — uppercase letters, digits, underscores. Custom templates override built-in templates of the same name. Use Get-SnipTemplate to list all available templates. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, Position=0, HelpMessage='Template name')] [ValidateNotNullOrEmpty()] [string]$Template, [Parameter(Mandatory, Position=1, HelpMessage='New snippet name')] [ValidateNotNullOrEmpty()] [string]$Name, [hashtable]$Variables = @{}, [switch]$Force ) script:InitEnv # ── Built-in template content (single-quoted here-strings, no variable expansion) ── $azFuncContent = @' using namespace System.Net param($Request, $TriggerMetadata) # Azure Function: {{FUNCTION_NAME}} # HTTP Method: {{HTTP_METHOD}} $name = $Request.Query.Name if (-not $name) { $name = $Request.Body.Name } if ($name) { $status = [HttpStatusCode]::OK $body = "Hello, $name. Function {{FUNCTION_NAME}} executed successfully." } else { $status = [HttpStatusCode]::BadRequest $body = 'Pass a name in the query string or request body.' } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $status Body = $body }) '@ $restContent = @' # REST Call to {{URL}} # Method: {{METHOD}} $params = @{ Uri = '{{URL}}' Method = '{{METHOD}}' Headers = @{ 'Content-Type' = 'application/json' } } $body = '{{BODY}}' if ($body -and $body -ne '{{BODY}}') { $params['Body'] = $body } try { $response = Invoke-RestMethod @params -ErrorAction Stop $response | ConvertTo-Json -Depth 10 } catch { Write-Error "Request failed: $_" } '@ $k8sContent = @' apiVersion: batch/v1 kind: Job metadata: name: {{JOB_NAME}} spec: template: spec: containers: - name: {{JOB_NAME}} image: {{IMAGE}} imagePullPolicy: IfNotPresent restartPolicy: Never backoffLimit: 4 '@ $builtinTemplates = @{ 'azure-function' = @{ extension = 'ps1'; content = $azFuncContent } 'rest-call' = @{ extension = 'ps1'; content = $restContent } 'k8s-job' = @{ extension = 'txt'; content = $k8sContent } } # ── Resolve template ────────────────────────────────────────────────────── $templateContent = $null $templateExt = 'ps1' $customDir = Join-Path $script:Home 'templates' if (Test-Path $customDir) { $customFile = @(Get-ChildItem $customDir -Filter "$Template.*" -File -ErrorAction SilentlyContinue) | Select-Object -First 1 if ($customFile) { $templateContent = Get-Content $customFile.FullName -Raw -Encoding UTF8 $templateExt = $customFile.Extension.TrimStart('.') } } if (-not $templateContent) { if (-not $builtinTemplates.ContainsKey($Template)) { Write-Error "Template '$Template' not found. Use Get-SnipTemplate to list available templates." -ErrorAction Continue return } $tpl = $builtinTemplates[$Template] $templateContent = $tpl.content $templateExt = $tpl.extension } # ── Fill placeholders ───────────────────────────────────────────────────── $phMatches = [regex]::Matches($templateContent, '\{\{([A-Z0-9_]+)(?::([^}]*))?\}\}', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) $placeholders = @($phMatches | ForEach-Object { $_.Groups[1].Value.ToUpper() } | Select-Object -Unique) $phDefaults = @{} foreach ($m in $phMatches) { $phName = $m.Groups[1].Value.ToUpper() if ($m.Groups[2].Success -and -not $phDefaults.ContainsKey($phName)) { $phDefaults[$phName] = $m.Groups[2].Value } } $resolved = @{} foreach ($k in $Variables.Keys) { $resolved[$k.ToUpper()] = $Variables[$k] } foreach ($ph in $placeholders) { if (-not $resolved.ContainsKey($ph)) { $envVal = (Get-Item "env:$ph" -ErrorAction SilentlyContinue).Value if ($envVal) { $resolved[$ph] = $envVal } elseif ($phDefaults.ContainsKey($ph) -and $phDefaults[$ph] -ne '') { $default = $phDefaults[$ph] $userInput = Read-Host " Value for {{$ph}} [$default]" $resolved[$ph] = if ($userInput -ne '') { $userInput } else { $default } } else { $resolved[$ph] = Read-Host " Value for {{$ph}}" } } } $filled = $templateContent foreach ($ph in $placeholders) { $filled = $filled -replace "\{\{$ph(?::[^}]*)?\}\}", $resolved[$ph] } New-Snip -Name $Name -Language $templateExt -Content $filled -Force:$Force } function Get-SnipTemplate { <# .SYNOPSIS Lists all available snippet templates (built-in and custom). .DESCRIPTION Displays templates from two sources: Built-in — three templates embedded in the module: azure-function, rest-call, and k8s-job. Custom — any files stored in ~/.pssnips/templates/. For each template, shows its name, source (builtin/custom), extension, and a comma-separated list of {{VARIABLE}} placeholders it contains. .EXAMPLE Get-SnipTemplate Lists all available templates with their placeholder variables. .EXAMPLE $templates = Get-SnipTemplate $templates | Where-Object Source -eq 'custom' Returns only custom templates for further processing. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject[] Each object has: Name, Source, Extension, Variables. .NOTES Custom templates are stored in ~/.pssnips/templates/. Custom templates override built-in templates of the same name. Built-in templates: azure-function, rest-call, k8s-job. #> [CmdletBinding()] [OutputType([PSCustomObject[]])] param() script:InitEnv $builtinDefs = @{ 'azure-function' = @{ ext = 'ps1'; vars = @('FUNCTION_NAME', 'HTTP_METHOD') } 'rest-call' = @{ ext = 'ps1'; vars = @('URL', 'METHOD', 'BODY') } 'k8s-job' = @{ ext = 'txt'; vars = @('JOB_NAME', 'IMAGE') } } $rows = [System.Collections.Generic.List[PSCustomObject]]::new() $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $customDir = Join-Path $script:Home 'templates' if (Test-Path $customDir) { foreach ($f in @(Get-ChildItem $customDir -File -ErrorAction SilentlyContinue)) { $tName = $f.BaseName $seen.Add($tName) | Out-Null $content = try { Get-Content $f.FullName -Raw -Encoding UTF8 -ErrorAction Stop } catch { '' } $vars = @([regex]::Matches($content, '\{\{([A-Z0-9_]+)\}\}', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) | ForEach-Object { $_.Groups[1].Value.ToUpper() } | Select-Object -Unique) $rows.Add([pscustomobject]@{ Name = $tName Source = 'custom' Extension = $f.Extension.TrimStart('.') Variables = $vars -join ', ' }) } } foreach ($tName in ($builtinDefs.Keys | Sort-Object)) { if (-not $seen.Contains($tName)) { $def = $builtinDefs[$tName] $rows.Add([pscustomobject]@{ Name = $tName Source = 'builtin' Extension = $def.ext Variables = $def.vars -join ', ' }) } } $result = @($rows | Sort-Object Source, Name) if ($result.Count -eq 0) { script:Out-Info 'No templates found.'; return @() } Write-Host '' Write-Host ' Available Templates' -ForegroundColor Cyan Write-Host " $('─' * 72)" -ForegroundColor DarkGray Write-Host (" {0,-22} {1,-8} {2,-5} {3}" -f 'NAME', 'SOURCE', 'EXT', 'VARIABLES') -ForegroundColor DarkCyan Write-Host " $('─' * 72)" -ForegroundColor DarkGray foreach ($r in $result) { $c = if ($r.Source -eq 'custom') { 'Yellow' } else { 'Cyan' } Write-Host (" {0,-22} " -f $r.Name) -ForegroundColor $c -NoNewline Write-Host ("{0,-8} {1,-5} {2}" -f $r.Source, $r.Extension, $r.Variables) -ForegroundColor Gray } Write-Host '' return $result } |