HandlebarsPS.psm1
|
# HandlebarsPS.psm1 # PowerShell wrapper around Handlebars.Net with automatic NuGet download/restore (no nuget.exe required) #region: internal state $script:HandlebarsAssemblyLoaded = $false $script:HandlebarsAssembly = $null $script:HandlebarsType = $null $script:Handlebars = $null #endregion function Get-ModuleRoot { <# .SYNOPSIS Returns the root folder of this module. #> return (Split-Path -Parent $PSCommandPath) } function Restore-HandlebarsNuGetPackage { <# .SYNOPSIS Downloads and extracts the Handlebars.Net NuGet package (no nuget.exe required). .PARAMETER PackageId NuGet package id (default: Handlebars.Net). .PARAMETER Version Optional specific version, e.g. '3.0.0'. If omitted, latest is used. #> [CmdletBinding()] param( [string]$PackageId = 'Handlebars.Net', [string]$Version = '' ) $moduleRoot = Get-ModuleRoot $packagesRoot = Join-Path $moduleRoot 'packages' $packageFolder = Join-Path $packagesRoot $PackageId if (Test-Path $packageFolder) { Write-Verbose "Package '$PackageId' already restored at '$packageFolder'." return } if (-not (Test-Path $packagesRoot)) { New-Item -ItemType Directory -Path $packagesRoot | Out-Null } $nupkgPath = Join-Path $moduleRoot "$PackageId.nupkg" # Build NuGet download URL $url = if ($Version) { "https://www.nuget.org/api/v2/package/$PackageId/$Version" } else { "https://www.nuget.org/api/v2/package/$PackageId" } Write-Verbose "Downloading NuGet package '$PackageId' from '$url'..." try { Invoke-WebRequest -Uri $url -OutFile $nupkgPath -UseBasicParsing } catch { throw "Failed to download NuGet package '$PackageId' from '$url': $($_.Exception.Message)" } Write-Verbose "Expanding NuGet package '$nupkgPath' to '$packageFolder'..." try { Expand-Archive -Path $nupkgPath -DestinationPath $packageFolder -Force } catch { throw "Failed to extract NuGet package '$nupkgPath': $($_.Exception.Message)" } finally { # Best effort cleanup Remove-Item $nupkgPath -Force -ErrorAction SilentlyContinue } } function Get-HandlebarsDllPath { <# .SYNOPSIS Ensures Handlebars.Net is restored and returns the DLL path. #> $moduleRoot = Get-ModuleRoot $dllPath = Join-Path $moduleRoot 'packages/Handlebars.Net/lib/netstandard2.0/Handlebars.dll' if (-not (Test-Path $dllPath)) { Write-Verbose "Handlebars.Net.dll not found at '$dllPath'. Restoring package..." Restore-HandlebarsNuGetPackage } if (-not (Test-Path $dllPath)) { throw "Handlebars.Net.dll not found after restore. Expected at '$dllPath'." } return $dllPath } function Import-HandlebarsLibrary { <# .SYNOPSIS Loads Handlebars.Net into the current session, restoring from NuGet if needed. #> [CmdletBinding()] param() if ($script:HandlebarsAssemblyLoaded) { return } $dllPath = Get-HandlebarsDllPath try { $script:HandlebarsAssembly = [System.Reflection.Assembly]::LoadFrom((Resolve-Path $dllPath)) } catch { throw "Failed to load Handlebars.Net assembly from '$dllPath': $($_.Exception.Message)" } $script:HandlebarsType = $script:HandlebarsAssembly.GetType('HandlebarsDotNet.Handlebars') if (-not $script:HandlebarsType) { throw "Could not find type 'HandlebarsDotNet.Handlebars' in the loaded assembly." } $script:Handlebars = $script:HandlebarsType $script:HandlebarsAssemblyLoaded = $true } function New-HandlebarsTemplate { <# .SYNOPSIS Compiles a Handlebars template and returns a callable delegate. .PARAMETER Template Handlebars template string. .EXAMPLE $tmpl = New-HandlebarsTemplate -Template 'Hello {{name}}' Invoke-HandlebarsTemplate -TemplateDelegate $tmpl -Data @{ name = 'World' } #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Template ) Import-HandlebarsLibrary try { $delegate = $script:Handlebars::Compile($Template) } catch { throw "Failed to compile Handlebars template: $($_.Exception.Message)" } return $delegate } function Invoke-HandlebarsTemplate { <# .SYNOPSIS Renders a Handlebars template with the given data. .DESCRIPTION You can: - Use -Template to compile from text on each call, or - Use -TemplateDelegate for a precompiled template. Data can be a hashtable or any object. .PARAMETER Template Handlebars template string. .PARAMETER TemplateDelegate Compiled template returned by New-HandlebarsTemplate. .PARAMETER Data Data context (hashtable or object). .EXAMPLE Invoke-HandlebarsTemplate -Template 'Hello {{name}}' -Data @{ name = 'World' } .EXAMPLE $tmpl = New-HandlebarsTemplate -Template 'Hello {{name}}' Invoke-HandlebarsTemplate -TemplateDelegate $tmpl -Data @{ name = 'World' } #> [CmdletBinding(DefaultParameterSetName = 'ByTemplateText')] param( [Parameter(Mandatory = $true, ParameterSetName = 'ByTemplateText')] [string]$Template, [Parameter(Mandatory = $true, ParameterSetName = 'ByDelegate')] [object]$TemplateDelegate, [Parameter(Mandatory = $true)] [object]$Data ) Import-HandlebarsLibrary if ($Data -is [System.Collections.IDictionary]) { $Data = [PSCustomObject]$Data } $delegateToUse = $TemplateDelegate if ($PSCmdlet.ParameterSetName -eq 'ByTemplateText') { $delegateToUse = New-HandlebarsTemplate -Template $Template } if (-not $delegateToUse) { throw "No template delegate available to invoke." } try { return $delegateToUse.Invoke($Data) } catch { throw "Failed to render Handlebars template: $($_.Exception.Message)" } } function Register-HandlebarsHelper { <# .SYNOPSIS Registers a custom Handlebars helper. .DESCRIPTION ScriptBlock signature: param($context, $args) - $context: current Handlebars context - $args : array of arguments passed from the template Return a string or any value; it will be written into the template output. .PARAMETER Name Helper name as used in the template. .PARAMETER ScriptBlock ScriptBlock implementing the helper logic. .EXAMPLE Register-HandlebarsHelper -Name 'upper' -ScriptBlock { param($context, $args) $args[0].ToString().ToUpper() } Invoke-HandlebarsTemplate -Template 'Hi {{upper name}}' -Data @{ name = 'john' } #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Name, [Parameter(Mandatory = $true)] [ScriptBlock]$ScriptBlock ) Import-HandlebarsLibrary $callback = [HandlebarsDotNet.HelperFunction]{ param($output, $context, $arguments) $result = & $ScriptBlock $context $arguments if ($null -ne $result) { $output.WriteSafeString($result.ToString()) } } try { [HandlebarsDotNet.Handlebars]::RegisterHelper($Name, $callback) } catch { throw "Failed to register Handlebars helper '$Name': $($_.Exception.Message)" } } Export-ModuleMember -Function ` Import-HandlebarsLibrary, ` New-HandlebarsTemplate, ` Invoke-HandlebarsTemplate, ` Register-HandlebarsHelper |