Manifest.psm1
#!/usr/bin/env pwsh $ErrorActionPreference = "Stop" $script:schema = Get-Content ` "$PSScriptRoot/schemas/manifest.schema.json" | Out-String function Get-Manifest { <# .SYNOPSIS Load the archive manifest .EXAMPLE Get-Manifest 'manifest.json' #> Param( # filesystem location of manifest [Parameter(Mandatory)] [string] $File ) Begin { $basePath = Split-Path $File } Process { try { $raw = Get-Content $File | Out-String } catch { Write-Debug $_ $raw = '{"Pages":[], "attachments": []}' } If ($PSVersionTable.PSEdition -eq 'Core') { $raw | Test-JSON -Schema $script:schema | Out-Null } $manifest = $raw | ConvertFrom-JSON ForEach($pageMeta in $manifest.Pages) { # patching to be an absolute path, the inverse function # (Set-Manifest) must check, whether _Ref is set and substitute for # Ref before writing to filesystem If ($pageMeta.Ref) { $pageMeta | Add-Member ` -NotePropertyName '_Ref' ` -NotePropertyValue $pageMeta.Ref ` -Force $pageMeta | Add-Member ` -NotePropertyName 'Ref' ` -NotePropertyValue ( Join-Path $basePath $pageMeta.Ref | Resolve-Path ) ` -Force } } ForEach($attachmentMeta in $manifest.Attachments) { # patching to be an absolute path, the inverse function # (Set-Manifest) must check, whether _Ref is set and substitute for # Ref before writing to filesystem If ($attachmentMeta.Ref) { $attachmentMeta | Add-Member ` -NotePropertyName '_Ref' ` -NotePropertyValue $attachmentMeta.Ref ` -Force $attachmentMeta | Add-Member ` -NotePropertyName 'Ref' ` -NotePropertyValue ( Join-Path $basePath $attachmentMeta.Ref | Resolve-Path ) ` -Force } } } End { $manifest } } function Set-Manifest { <# .SYNOPSIS Dump the archive manifest .EXAMPLE Set-Manifest 'manifest.json' #> Param( # manifest object [Parameter(Mandatory)] [PSObject] $Manifest, # filesystem location of manifest [Parameter(Mandatory)] [string] $File, # create a backup first [Parameter()] [bool] $Backup = $false ) Process { ForEach($pageMeta in $Manifest.Pages) { # patching to be an absolute path, the inverse function # (Set-Manifest) must check, whether _Ref is set and substitute for # Ref before writing to filesystem If ($pageMeta._Ref) { $pageMeta.Ref = $pageMeta._Ref $Manifest.Pages.PSObject.Properties.Remove('_Ref') } } ForEach($attachmentMeta in $Manifest.Attachments) { # patching to be an absolute path, the inverse function # (Set-Manifest) must check, whether _Ref is set and substitute for # Ref before writing to filesystem If ($attachmentMeta._Ref) { $attachmentMeta.Ref = $attachmentMeta._Ref $Manifest.Attachments.PSObject.Properties.Remove('_Ref') } } $raw = $Manifest | ConvertTo-JSON If ($PSVersionTable.PSEdition -eq 'Core') { $raw | Test-JSON -Schema $script:schema } if ($Backup) { $baseDir = Split-Path $File $baseName = "$(Split-Path -Leaf $File).bck" #FIXME: this should be handled without an explicit condition if ($baseDir) { $path = Join-Path $baseDir $baseName } else { $path = $baseName } Copy-Item -Path $File -Destination $path } Set-Content -Path $File -Value $raw } } function New-AncestralPageGenerationCache { <# .SYNOPSIS Calculate the numeric ancestral generation of a page .DESCRIPTION The Get-AncestralPageGeneration calculates a numeric ancestral generation of a page, which is used for sorting. The index required as input can be retrieved through the New-PagesManifestIndex function. .EXAMPLES $generation = Get-AncestralPageGeneration ` -Title 'foobar4' ` -Manifest @() ` -Index @{} #> Param( # Pages manifest [Parameter(Mandatory)] [Array] $Manifest, # Title of page to calculate generation of [Parameter()] [String] $Title, # Index for lookup of page metadata manifest item position [Parameter()] [Collections.Hashtable] $Index ) Begin { $cache = @{} If (-Not $Index) { Write-Debug "rebuilding index" $Index = ,$Manifest | New-PagesManifestIndex } } Process { ForEach ($pageMeta in $Manifest) { $generation = 0 $pageMeta = If ($Title) {$Manifest[$Index.$Title]} Else {$pageMeta} $ancestor = $pageMeta.AncestorTitle $pageMeta_ = $pageMeta While ($ancestor) { $generation += 1 $pageMeta_ = $Manifest[$Index."$($pageMeta_.AncestorTitle)"] $ancestor = $pageMeta_.AncestorTitle } $cache[$pageMeta.Title] = $generation if ($Title) {Break} } } End {$cache} } function Optimize-PagesManifest { <# .SYNOPSIS Sort Pages Manifest in accordance with the pages ancestry .DESCRIPTION The Optimize-PagesManifest function sorts a Pages manifest in accordance with the pages ancestry. This makes sure that an ancestor is already published, before its descendant is published. The sorting is done with a quick-sort algorithm, using the Hoare partitioning scheme. Older/lower ancestral generations take precedence over higher/younger ones. Syblings within a generation are treated as LIFO, where the youngest (last) has precedence over the oldest (fist). .EXAMPLE $manifest Optimize-PagesManifest -Manifest ,@() or $manifest = ,@() | Optimize-PagesManifest .NOTES whichever system generates the manifest should already output the array in the correct order, however it is not wise to depend upon this. #> Param( # Manifest to sort [Parameter(Mandatory)] [Array] $Manifest, # Left partition border [Parameter(Mandatory)] [Int] $Lo, # Right partition border [Parameter(Mandatory)] [Int] $Hi, # Cache for storing the numeric ancestral generation of pages [Parameter(Mandatory)] [Collections.Hashtable] $GenerationCache ) Process { $pivotPageMeta = $Manifest[($Lo + $Hi) / 2] $pivot = $generationCache[$pivotPageMeta.Title] # left index $i = $Lo # right index $j = $Hi While($i -le $j) { # Move the left index to the right at least once and while # the element at the left index is less than the pivot While ( $generationCache."$($Manifest[$i].Title)" -lt $pivot ` -And ` $i -lt $Hi ) { $i += 1 } # Move the right index to the left at least once and while # element at the right index is greater than the pivot While ( $generationCache."$($Manifest[$j].Title)" -gt $pivot ` -And ` $j -gt $Lo ) { $j -= 1 } If ($i -le $j) { $tmp = $Manifest[$i] $Manifest[$i] = $Manifest[$j] $Manifest[$j] = $tmp $i += 1 $j -= 1 } If ($Lo -lt $j) { Optimize-PagesManifest ` -Manifest $Manifest ` -Lo $Lo` -Hi $j ` -GenerationCache $GenerationCache } If ($i -lt $Hi) { Optimize-PagesManifest ` -Manifest $Manifest ` -Lo $i ` -Hi $Hi ` -GenerationCache $GenerationCache } } } } function New-PagesManifestIndex { <# .SYNOPSIS Create an index of pages from a manifest .DESCRIPTION The New-PageIndex function builds a page index from a manifest for faster lookup of page metadata. The title of a page is used for indexing, since a page title is unique within a Confluence space. .INPUTS Manifest .OUTPUTS Returns a Hashtable, where the key of each key-value pair is the page title and attachment name, and the value is the index of the array item within the Attachments porition of the manifest. .EXAMPLE New-PageIndex -Manifest @{} #> Param( [Parameter(Mandatory, ValueFromPipeline)] [Array] $Manifest ) Process { $index = @{} For($i = 0; $i -lt $Manifest.Count; $i += 1) { $index[$Manifest[$i].Title] = $i } $index } } function New-AttachmentsManifestIndex { <# .SYNOPSIS Create an index of page container attachments from a manifest .DESCRIPTION The New-AttachmentIndex function builds an attachment index from a manifest for faster lookup of attachment metadata. The title of the container page, including the attachment name a is used for indexing, since attachment names are unique within a container page. .INPUTS Manifest .OUTPUTS Returns a Hashtable, where the key of each key-value pair is the interpolation of the container page title and attachment name, and the value is the index of the array item within the Attachments porition of the manifest. .EXAMPLE New-AttachmentIndex -Manifest @{} #> Param( #manifest [Parameter(Mandatory, ValueFromPipeline)] [Array] $Manifest ) Process { $index = @{} For($i = 0; $i -lt $Manifest.Count; $i += 1) { $key = "$($Manifest[$i].ContainerPageTitle):" + ` "$($Manifest[$i].Name)" $index[$key] = $i } $index } } |