Private/SwaggerOutput.ps1
|
# Output helpers for New-FunctionsFromSwagger: writing generated files (honouring -PreviewOnly and ShouldProcess), # removing files from earlier versions and applying FunctionCustomizations.ps1. They read $script:SwaggerGeneratorState. # Reads a code template from Templates/Swagger (<Name>.ps1.template) with LF line endings function Get-SwaggerTemplate { [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory = $true)][string] $Name) $templateFolder = Join-Path -Path (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Templates') -ChildPath 'Swagger' $templatePath = Join-Path -Path $templateFolder -ChildPath ($Name + '.ps1.template') # The file ends with one line break that is not part of the template $text = (Get-Content -LiteralPath $templatePath -Raw -Encoding UTF8 -ErrorAction Stop) -replace "`r`n", "`n" if ($text.EndsWith("`n")) { $text = $text.Substring(0, $text.Length - 1) } return $text } # Writes a generated file. -PreviewOnly: nothing is written. Otherwise ShouldProcess decides (-WhatIf, -Confirm). # Content is written as UTF-8 (with a BOM for script files so Windows PowerShell 5.1 reads non-ASCII text) through a # temporary file, so a failure never leaves a partial file behind. Every file is recorded in the run's FileResults. function Write-AtomicFile { param( [Parameter(Mandatory = $true)][string] $Path, [Parameter(Mandatory = $true)][AllowEmptyString()][object] $Content, [Parameter()][string] $Kind = 'Function', [Parameter()][switch] $NoBom ) $record = [pscustomobject]@{ Kind = $Kind; Name = (Split-Path -Path $Path -Leaf); Path = $Path; Action = 'Preview' } $script:SwaggerGeneratorState.FileResults.Add($record) if ($script:SwaggerGeneratorState.PreviewOnly) { return } if (-not $script:SwaggerGeneratorState.Cmdlet.ShouldProcess($Path, 'Write generated file')) { $record.Action = 'Skipped' return } if ($Content -is [array]) { $Content = ($Content -join "`n") + "`n" } $dir = Split-Path -Path $Path if (-not (Test-Path -LiteralPath $dir)) { $null = New-Item -ItemType Directory -Path $dir -Force -WhatIf:$false -Confirm:$false } $tmpName = Join-Path -Path $dir -ChildPath ([System.Guid]::NewGuid().ToString() + '.tmp') try { Write-Utf8TextFile -Path $tmpName -Content ([string]$Content) -Utf8Bom:(-not $NoBom) Move-Item -Force -LiteralPath $tmpName -Destination $Path -WhatIf:$false -Confirm:$false } catch { if (Test-Path -LiteralPath $tmpName) { Remove-Item -LiteralPath $tmpName -Force -ErrorAction SilentlyContinue -WhatIf:$false -Confirm:$false } throw } $record.Action = 'Written' } # Removes a file written by an earlier version of the generator (honours -PreviewOnly and ShouldProcess) function Invoke-LegacyGeneratedFileRemoval { param([Parameter(Mandatory = $true)][string] $Path) if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return } # Never remove a file this run writes (for example when the noun prefix makes the same file name) foreach ($recorded in $script:SwaggerGeneratorState.FileResults) { if ([string]::Equals($recorded.Path, $Path, [System.StringComparison]::OrdinalIgnoreCase)) { return } } $record = [pscustomobject]@{ Kind = 'Removed'; Name = (Split-Path -Path $Path -Leaf); Path = $Path; Action = 'Preview' } $script:SwaggerGeneratorState.FileResults.Add($record) if ($script:SwaggerGeneratorState.PreviewOnly) { return } if ($script:SwaggerGeneratorState.Cmdlet.ShouldProcess($Path, 'Remove file written by an earlier version of New-FunctionsFromSwagger')) { Remove-Item -LiteralPath $Path -Force -WhatIf:$false -Confirm:$false $record.Action = 'Removed' } else { $record.Action = 'Skipped' } } # Loads Public/Generated/FunctionCustomizations.ps1 from the target module (user-authored settings, not generated code) function Get-GeneratedFunctionCustomizationMap { if ($null -ne $script:SwaggerGeneratorState.CustomizationCache) { return $script:SwaggerGeneratorState.CustomizationCache } $script:SwaggerGeneratorState.CustomizationCache = @{} $customDir = Join-Path -Path (Join-Path -Path $script:SwaggerGeneratorState.ModuleRoot -ChildPath 'Public') -ChildPath 'Generated' $customFile = Join-Path $customDir 'FunctionCustomizations.ps1' if (Test-Path -LiteralPath $customFile) { try { $result = . $customFile if ($result -is [hashtable]) { $script:SwaggerGeneratorState.CustomizationCache = $result } elseif (Get-Variable -Name 'FunctionCustomizations' -Scope 0 -ErrorAction SilentlyContinue) { $candidate = (Get-Variable -Name 'FunctionCustomizations' -Scope 0 -ErrorAction SilentlyContinue).Value if ($candidate -is [hashtable]) { $script:SwaggerGeneratorState.CustomizationCache = $candidate } Remove-Variable -Name 'FunctionCustomizations' -Scope 0 -ErrorAction SilentlyContinue } elseif ($null -ne $result -and $result.PSObject.Properties.Name -contains 'FunctionCustomizations' -and $result.FunctionCustomizations -is [hashtable]) { $script:SwaggerGeneratorState.CustomizationCache = $result.FunctionCustomizations } } catch { Write-Warning "Failed to load function customization script at ${customFile}: $($_.Exception.Message)" } } if (-not $script:SwaggerGeneratorState.CustomizationCache) { $script:SwaggerGeneratorState.CustomizationCache = @{} } return $script:SwaggerGeneratorState.CustomizationCache } function Invoke-GeneratedFunctionCustomization { param( [Parameter(Mandatory)][string] $FunctionName, [Parameter()][AllowNull()][string] $Content, [Parameter()][hashtable] $Metadata ) $map = Get-GeneratedFunctionCustomizationMap if (-not $map -or $map.Count -eq 0) { return $Content } $current = $Content if ($map.ContainsKey('__Pipeline')) { foreach ($transform in @($map['__Pipeline'])) { if ($transform -isnot [scriptblock]) { continue } $ctx = [pscustomobject]@{ FunctionName = $FunctionName; Content = $current; Metadata = $Metadata } try { $result = & $transform $ctx if ($null -ne $result) { if ($result -is [string]) { $current = $result } elseif ($result -is [hashtable] -and $result.ContainsKey('Content')) { $current = [string]$result['Content'] } elseif ($result -is [psobject] -and $result.PSObject.Properties.Name -contains 'Content') { $current = [string]$result.Content } } } catch { Write-Warning "Function customization pipeline for $FunctionName failed: $($_.Exception.Message)" } } } if (-not $map.ContainsKey($FunctionName)) { return $current } $spec = $map[$FunctionName] if ($null -eq $spec) { return $current } if ($spec -is [string]) { return [string]$spec } if ($spec -is [scriptblock]) { $ctx = [pscustomobject]@{ FunctionName = $FunctionName; Content = $current; Metadata = $Metadata } try { $res = & $spec $ctx if ($null -eq $res) { return $current } if ($res -is [string]) { return $res } if ($res -is [hashtable] -and $res.ContainsKey('Content')) { return [string]$res['Content'] } if ($res -is [psobject] -and $res.PSObject.Properties.Name -contains 'Content') { return [string]$res.Content } return $current } catch { Write-Warning "Function customization for $FunctionName failed: $($_.Exception.Message)" return $current } } if ($spec -is [hashtable]) { $mode = 'Transform' if ($spec.ContainsKey('Mode') -and $spec['Mode']) { $mode = [string]$spec['Mode'] } switch ($mode.ToLowerInvariant()) { 'replace' { if ($spec.ContainsKey('Content')) { return [string]$spec['Content'] } elseif ($spec.ContainsKey('Script') -and $spec['Script'] -is [scriptblock]) { $ctx = [pscustomobject]@{ FunctionName = $FunctionName; Content = $current; Metadata = $Metadata; Spec = $spec } try { $res = & $spec['Script'] $ctx if ($null -eq $res) { return $current } if ($res -is [string]) { return $res } if ($res -is [hashtable] -and $res.ContainsKey('Content')) { return [string]$res['Content'] } if ($res -is [psobject] -and $res.PSObject.Properties.Name -contains 'Content') { return [string]$res.Content } return $current } catch { Write-Warning "Replacement customization for $FunctionName failed: $($_.Exception.Message)" return $current } } return $current } 'skip' { return $null } default { $handler = $null if ($spec.ContainsKey('Script') -and $spec['Script'] -is [scriptblock]) { $handler = $spec['Script'] } if ($null -eq $handler -and $spec.ContainsKey('Content')) { return [string]$spec['Content'] } if ($null -eq $handler) { return $current } $ctx = [pscustomobject]@{ FunctionName = $FunctionName; Content = $current; Metadata = $Metadata; Spec = $spec } try { $res = & $handler $ctx if ($null -eq $res) { return $current } if ($res -is [string]) { return $res } if ($res -is [hashtable] -and $res.ContainsKey('Content')) { return [string]$res['Content'] } if ($res -is [psobject] -and $res.PSObject.Properties.Name -contains 'Content') { return [string]$res.Content } return $current } catch { Write-Warning "Transform customization for $FunctionName failed: $($_.Exception.Message)" return $current } } } } return $current } |