diff --git a/README.md b/README.md index c246505..beb1613 100755 --- a/README.md +++ b/README.md @@ -92,6 +92,22 @@ to exist. Publish-Page ``` +## Compatibility + +This program is compatible with the following Microsoft PowerShell runtimes: + +- Microsoft PowerShell Desktop >=5 +- Microsoft PowerShell Core >=7 + +## Runtime Dependencies + +This program has no runtime dependencies. + +On PowerShell Desktop, however, it is necessary to obtain the +`Microsoft.PowerShell.Utility` module for JSON schema verification of the +manifest. Whether that's possible for PowerShell Desktop; We do not know. +Should the aforementioned module not be present, JSON validation is skipped. + ## Debugging To display debug messages, set @@ -108,7 +124,13 @@ Execute `pwsh scripts/analyze.ps1` to do a static code analysis. ## Testing -This program requires [Pester v5](https://pester.dev/) to execute it's test suite. +This program requires [Pester](https://pester.dev/) to execute it's test suite. + +The test suite aims to be executable under most circumstances. We've been +dropping usage of Pester v5 functionalities so that it works with Pester down +to version 3, since Pester v3 is available in PowerShell (5) Desktop by default. +Due to the security mechanisms implemented in PowerShell Desktop, installing the +Pester v5 module may not be feasible for some. Execute `pwsh scripts/test.ps1` to run the entire test suite. @@ -116,9 +138,13 @@ Execute `pwsh scripts/test.ps1` to run the entire test suite. This program does not adhere to Microsoft's Best-Practices of publishing PowerShell modules, in the sense of that it does not use the *PowerShellGet* -module to do so and uses plain `nuget` CLI instead. +module to do so and uses the plain `nuget` CLI instead. -Execute `pwsh scripts/pack.ps1` to create a nuget package. +This program requires [nuget +CLI](https://learn.microsoft.com/en-us/nuget/install-nuget-client-tools). Be +aware that the `dotnet nuget` CLI may not be sufficient on some platforms. -Execute `pwsh scripts/publish` to publish the nuget package to +Execute `pwsh scripts/pack.ps1` to create the nuget package. + +Execute `pwsh scripts/publish.ps1` to publish the nuget package to [PowerShellGallery](https://www.powershellgallery.com). diff --git a/src/Manifest.psm1 b/src/Manifest.psm1 index d5627ea..2458ca5 100755 --- a/src/Manifest.psm1 +++ b/src/Manifest.psm1 @@ -2,9 +2,8 @@ $ErrorActionPreference = "Stop" -$script:schema = Get-Content ( - Join-Path -Path $PSScriptRoot 'schemas' 'manifest.schema.json' -) | Out-String +$script:schema = Get-Content ` + "$PSScriptRoot/schemas/manifest.schema.json" | Out-String function Get-Manifest @@ -21,6 +20,11 @@ function Get-Manifest [Parameter(Mandatory)] [string] $File ) + Begin + { + $basePath = Split-Path $File + } + Process { try @@ -35,9 +39,59 @@ function Get-Manifest $raw = '{"Pages":[], "attachments": []}' } - $raw | Test-JSON -Schema $script:schema | Out-Null + If ($PSVersionTable.PSEdition -eq 'Core') + { + $raw | Test-JSON -Schema $script:schema | Out-Null + } - $raw | ConvertFrom-JSON + $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 } } @@ -62,9 +116,38 @@ function Set-Manifest 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 - $raw | Test-JSON -Schema $script:schema + If ($PSVersionTable.PSEdition -eq 'Core') + { + $raw | Test-JSON -Schema $script:schema + } if ($Backup) { @@ -136,7 +219,7 @@ function New-AncestralPageGenerationCache { { $generation = 0 - $pageMeta = $Title ? $Manifest[$Index.$Title] : $pageMeta + $pageMeta = If ($Title) {$Manifest[$Index.$Title]} Else {$pageMeta} $ancestor = $pageMeta.AncestorTitle diff --git a/src/PSConfluencePublisher.psm1 b/src/PSConfluencePublisher.psm1 index 1136573..9d36105 100755 --- a/src/PSConfluencePublisher.psm1 +++ b/src/PSConfluencePublisher.psm1 @@ -33,3 +33,112 @@ $ErrorActionPreference = "Stop" +function Initialize-Manifest +{ + <# + + #> + Param( + # path of manifest to load + [Parameter(Mandatory)] [String] $Path + ) + + Begin + { + $literalPath = Resolve-Path -Path $Path + } + + Process + { + Write-Debug 'loading manifest...' + + $manifest = Get-Manifest $literalPath + + Write-Debug 'creating pages manifest index...' + + $pagesManifestIndex = New-PagesManifestIndex -Manifest $manifest.Pages + + Write-Debug 'creating ancestral page generation cache...' + + $ancestralGenerationCache = New-AncestralPageGenerationCache ` + -Manifest $manifest.Pages ` + -Index $pagesManifestIndex + + Write-Debug 'sorting pages manifest...' + + Optimize-PagesManifest ` + -Manifest $manifest.Pages ` + -Lo 0 ` + -Hi ($manifest.Pages.Count - 1) ` + -GenerationCache $ancestralGenerationCache | Out-Null + } + + End + { + @{ + 'Path' = $literalPath + 'Manifest' = $manifest + 'Index' = @{ + 'Pages' = New-PagesManifestIndex ` + -Manifest $manifest.Pages + 'Attachments' = New-AttachmentsManifestIndex ` + -Manifest $manifest.Attachments + } + } + } +} + + +function Initialize-Connection +{ + Param( + [Parameter(Mandatory)] [String]$Host, + [Parameter(Mandatory)] [String]$Space, + [Parameter(Mandatory)] [String]$PersonalAccessToken + ) + + Process + { + Register-PersonalAccessToken ` + -Host $Host ` + -Token $PersonalAccessToken | Out-Null + + Test-Connection -Host $Host | Out-Null + } + + End + { + @{ + 'Host' = $Host + 'Space' = $Space + } + } +} + + +function Publish-Pages +{ + Param( + # connection object created through Initialize-Connection + [Parameter(Mandatory)] [Collections.Hashtable]$Connection, + # manifest object created through Initialize-Manifest + [Parameter(Mandatory)] [PSCustomObject]$Manifest, + # + [Parameter()] [Switch]$Strict, + # + [Parameter()] [Switch]$Force, + # title of page to be published + [Parameter()] [String]$Title + ) + + Process + { + $Manifest.Manifest.Pages | Publish-Page ` + -Host $Connection.Host ` + -Space $Connection.Space ` + -Title $Title ` + -Index $Manifest.Index.Pages ` + -Strict:$Strict ` + -Force:$Force | Out-Null + } +} diff --git a/src/Page.psm1 b/src/Page.psm1 index 2a2f71c..e430ebf 100755 --- a/src/Page.psm1 +++ b/src/Page.psm1 @@ -38,8 +38,8 @@ function New-Page # title of page to be published [Parameter()] [string]$Title, # pages manifest - [Parameter(Mandatory, ValueFromPipeline)] - [Collections.Hashtable[]]$Manifest, + [Parameter(Mandatory)] + [PSCustomObject[]]$Manifest, # pages manifest index, mandatory for ancestor lookup [Parameter(Mandatory)] [Collections.Hashtable]$Index, # flag on whether to fail hard, or just continue @@ -66,7 +66,8 @@ function New-Page ElseIf (-Not $pageMeta.Ref) { - $errMsg = "no reference to local content for page ``$Title``." + $errMsg = ("``$($pageMeta.Title)``: no reference to local " + + 'content for page .') If ($Strict) {throw $errMsg} @@ -78,7 +79,10 @@ function New-Page ElseIf ($pageMeta.Id) { - Write-Debug "skipping, page already published: $($pageMeta.Id)" + Write-Debug ( + "New-Page: ``$($pageMeta.Title)``: skipping, already " + + "published ($($pageMeta.Id))" + ) $pageMeta @@ -87,13 +91,29 @@ function New-Page Else { - $content = Get-Content -Path $pageMeta.Ref + Write-Host "New-Page: ``$($pageMeta.Title)``: creating" + + Try + { + $content = Get-Content -Path $pageMeta.Ref | Out-String + } + + Catch + { + $errMsg = "``New-Page: $($PageMeta.Title)``: $_" + + If ($Strict) {throw $errMsg} + + Write-Host $errMsg + + continue + } $contentHash = (Get-StringHash $content).Hash $transportBody = @{ 'type' = 'page' - 'title' = $Title + 'title' = $pageMeta.Title 'space' = @{ 'key' = $Space } @@ -103,7 +123,7 @@ function New-Page 'representation' = 'storage' } } - } | ConvertTo-JSON + } | ConvertTo-JSON -Depth 5 Try { @@ -120,10 +140,12 @@ function New-Page Catch { - $errMsg = "skipping ``$pageMeta.Title``: $_" + $errMsg = "skipping ``$($pageMeta.Title)``: $($_)" If ($Strict) { + $_ + throw $errMsg } @@ -141,7 +163,7 @@ function New-Page $pageMeta | Add-Member ` -NotePropertyName 'Version' ` - -NotePropertyValue $response.version.number ` + -NotePropertyValue "1" ` -Force $pageMeta | Add-Member ` @@ -176,107 +198,224 @@ function Update-Page { <# .SYNOPSIS - Add a confluence page + Update an existing Confluence page .DESCRIPTION + This function is unaware of the publishing status of ancestors and + assumes that ancestral hierarchy is maintained through the + manifest's item order, therefore an index must be supplied. + + If a page's metadata does not include a reference, it will be + treated as a publishing failure and therefore not output the + original metadata. + + .OUTPUTS + + When no $Title is provided and the $Manifest array only contains 1 + page metadata, the ``Count`` attribute is faulty. Why? Don't know. + .EXAMPLE - Update-ConfluencePage + Add-ConfluencePage ` -Host 'confluence.contoso.com' ` -Space 'TIARA' ` -Title 'Testitest' ` - -Manifest @{} + -Content @{} #> Param( - [Parameter(Mandatory)] [string] $Host, - # The name of the Confluence space to publish to - [Parameter(Mandatory)] [string] $Space, + # confluence instance hostname + [Parameter(Mandatory)] [string]$Host, + # name of the Confluence space to publish to + [Parameter(Mandatory)] [string]$Space, # title of page to be published - [Parameter(Mandatory)] [string] $Title, + [Parameter()] [string]$Title, # pages manifest - [Parameter(Mandatory)] [Array] $Manifest, - # pages manifest index - [Parameter()] [Collections.Hashtable] $Index + [Parameter(Mandatory, ValueFromPipeline)] + [PSCustomObject[]]$Manifest, + # pages manifest index, mandatory for ancestor lookup + [Parameter(Mandatory)] [Collections.Hashtable]$Index, + # flag on whether to fail hard, or just continue + [Parameter()] [Switch]$Strict, + # flag on whether to force update of page, regardless of content + [Parameter()] [Switch]$Force ) + Begin + { + $pat = Get-PersonalAccessToken $Host + } + Process { - $pageMeta = Get-PageMeta ` - -Host $Host ` - -Space $Space ` - -Title $Title ` - -Manifest $Manifest ` - -Index $Index - - if (-Not $pageMeta.Ref) + If ($Title -And $Manifest[$Index.$Title]) { - throw "no reference to local content for page '$Title'." + $Manifest = @( + $Manifest[$Index.$Title] + ) } - if (-Not $pageMeta.Id) + ForEach($pageMeta in $Manifest) { - throw "no id for page '$Title'." - } + If ($Title -And $pageMeta.Title -ne $Title) {continue} - $content = Get-Content -Path $pageMeta.Ref + ElseIf (-Not $pageMeta.Ref) + { + $errMsg = "no reference to local content for page ``$Title``." - $hash = (Get-StringHash $content).Hash + If ($Strict) {throw $errMsg} - if ($hash -eq $pageMeta.Hash) - { - Write-Host "content unchanged, skipping: '$Title'" + Write-Host $errMsg - # yep, this is funny... This behaves like a return statement, because - # a cmdlet, treats the input as an array of inputs. We keep it that - # way so that all functions can properly act upon pipes. See - # additional information on 'Process' blocks. - continue - } - - # we're not updating this in place, so that we don't have to reset the - # value opon failure - $version = $pageMeta.Version + 1 - - $transportBody = @{ - 'id' = $PageMeta.Id - 'type' = 'page' - 'title' = $Title - 'space' = @{ - 'key' = $Space + continue } - 'body' = @{ - 'storage' = @{ - 'value' = $content - 'representation' = 'storage' + + ElseIf (-Not $pageMeta.Id) + { + $errMsg = "``$($pageMeta.Title)``: unknown page id" + + If ($Strict) {throw $errMsg} + + Write-Host $errMsg + + continue + } + + ElseIf (-Not $pageMeta.Version) + { + Write-Host "``$($pageMeta.Title)``: unknown (current) version" + + $pageMeta + + continue + } + + Else + { + Try + { + $content = Get-Content -Path $pageMeta.Ref | Out-String + } + + Catch + { + $errMsg = "``$Title``: $_" + + If ($Strict) {throw $errMsg} + + Write-Host $errMsg + + continue + } + + $version = [Int]($pageMeta.Version) + 1 + + $contentHash = (Get-StringHash $content).Hash + + If ( + $pageMeta.Hash -And + $pageMeta.Hash -eq $contentHash -And + -Not $Force + ) + { + Write-Debug ( + "Update-Page: ``$($pageMeta.Title)``: skipping, no " + + "content changes" + ) + + $pageMeta + + continue + } + + Else + { + Write-Host "``$($pageMeta.Title)``: updating" + } + + # status needs to be set as to restore the page, if it is + # trashed + $transportBody = @{ + 'id' = $PageMeta.Id + 'type' = 'page' + 'title' = $pageMeta.Title + 'space' = @{ + 'key' = $Space + } + 'body' = @{ + 'storage' = @{ + 'value' = $content + 'representation' = 'storage' + } + } + 'status' = 'current' + 'version' = @{ + 'number' = $version + } + } | ConvertTo-JSON -WarningAction 'SilentlyContinue' + + Try + { + Invoke-WebRequest ` + -Uri ("https://${Host}/rest/api/content/" + + $PageMeta.Id) ` + -Method 'Put' ` + -Headers @{ + 'Authorization' = "Bearer $pat" + } ` + -ContentType "application/json" ` + -Body $transportBody ` + -OutVariable rawResponse | Out-Null + } + + Catch + { + $errMsg = "skipping ``$($pageMeta.Title)``: $_" + + If ($Strict) + { + $_ + + throw $errMsg + } + + Write-Host $errMsg + + continue + } + + # response isn't needed since no field will be updated by the + # Confluence instance itself + #$response = ($rawResponse.Content | ConvertFrom-JSON) + + $pageMeta | Add-Member ` + -NotePropertyName 'Version' ` + -NotePropertyValue $version ` + -Force + + $pageMeta | Add-Member ` + -NotePropertyName 'Hash' ` + -NotePropertyValue $contentHash ` + -Force + + If ( + ($Title -And $pageMeta.Title -eq $Title) -Or + $Manifest.Count -eq 1 + ) + { + # TODO: further research mechanism of expanding single item + # array pipelines. For now we have to apply the unary + # operator, otherwise we get a wrong count on the output + ,@($pageMeta) + + break + } + + Else + { + $pageMeta } } - 'version' = @{ - 'number' = $version - } - } | ConvertTo-JSON - - Invoke-WebRequest ` - -Uri "https://${Host}/rest/api/content/$($PageMeta.Id)" ` - -Method 'Put' ` - -Headers @{ - 'Authorization' = "Bearer $(Get-PersonalAccessToken $Host)" - } ` - -ContentType "application/json" ` - -Body $transportBody ` - -OutVariable rawResponse | Out-Null - } - - End - { - $response = ($rawResponse.Content | ConvertFrom-JSON) - - Update-PageMeta ` - -Title $Title ` - -Id $pageMeta.Id ` - -Version $response.version.number ` - -Hash $hash ` - -Manifest $Manifest ` - -Index $Index + } } } @@ -284,82 +423,43 @@ function Update-Page function Publish-Page { Param( - # title of the page (used for manifest lookup) - [Parameter(Mandatory)] [string] $Title, - # hostname of Confluence instance - [Parameter(Mandatory)] [string] $Host, - # name of Confluence space - [Parameter(Mandatory)] [string] $Space, - # manifest object - [Parameter(Mandatory, ValueFromPipeline)] [PSObject] $Meta + # confluence instance hostname + [Parameter(Mandatory)] [string]$Host, + # name of the Confluence space to publish to + [Parameter(Mandatory)] [string]$Space, + # title of page to be published + [Parameter()] [string]$Title, + # pages manifest + [Parameter(Mandatory, ValueFromPipeline)] + [PSCustomObject[]]$Manifest, + # pages manifest index, mandatory for ancestor lookup + [Parameter(Mandatory)] [Collections.Hashtable]$Index, + # flag on whether to fail hard, or just continue + [Parameter()] [Switch]$Strict, + # flag on whether to force update of page, regardless of content + [Parameter()] [Switch]$Force ) Process { - ForEach($meta in $Meta) - { - $meta = Get-PageMeta ` - -Host $hostname ` - -Space $spaceName ` - -Title $Title ` - -Manifest $Manifest + $result = Update-Page ` + -Host $Host ` + -Space $Space ` + -Manifest $Manifest ` + -Index $Index ` + -Strict:$Strict ` + -Force:$Force - if ($meta.AncestorTitle) - { - $ancestorPageMeta = Get-PageMeta ` - -Host $hostname ` - -Space $spaceName ` - -Title $pageMeta.AncestorTitle ` - -Manifest $Manifest + $result = New-Page ` + -Host $Host ` + -Space $Space ` + -Manifest $result ` + -Index $Index ` + -Strict:$Strict + } - if (-Not ($ancestorPageMeta -Or $ancestorPageMeta.PageId)) - { - Write-Host "ancestor, not published, skipping: $Title" - - continue - } - } - - if (-Not $pageId) - { - Write-Host ("create ${_}: $prettyName") - - try { - New-Page ` - -Host $hostname ` - -Space $spaceName ` - -Title $Title ` - -Manifest $Manifest - } - - catch - { - Write-Host "error for '$Title', skipping: $_" - - continue - } - } - - else - { - Write-Host ("update ${_} (${pageId}): $prettyName") - - try - { - Update-Page ` - -Host $hostname ` - -Space $Space ` - -Title $Title ` - -Manifest $Manifest - } - - catch - { - Write-Host "error for '$Title', skipping: $_" - - continue - } - } - } + End + { + $result } } diff --git a/src/PageMeta.psm1 b/src/PageMeta.psm1 index cb998ba..9927c68 100755 --- a/src/PageMeta.psm1 +++ b/src/PageMeta.psm1 @@ -1,60 +1,7 @@ -#!/usr/bin/env pwsh + $ErrorActionPreference = "Stop" -function Get-PageMetaCache -{ - <# - .SYNOPSIS - Get a locally indexed/cached Confluence page id - - .EXAMPLE - Get-PageMetaCache ` - -Title 'Page Title' ` - -Manifest @() ` - -Index @{} - - .NOTES - To test or not to test, that is the question... Since the - `Test-JSON` cmdlet requires serialized JSON, but we are working with - the deserialized Hashtable, it's too computationally intense to - always test the input upon every call. We therefore only make sure, - that correct data is written to the filesystem. For the rest, each - function is responsible for themself (learned that that's a valid - reflexive pronoun today 🤓). - - This function is lucky to get this note, because it's at the top 💯. - Of course this applies to every function. - #> - Param( - [Parameter(Mandatory)] [string] $Title, - [Parameter(Mandatory)] [Array] $Manifest, - [Parameter()] [Collections.Hashtable] $Index - ) - - Process - { - If ($Index -And $Manifest.Count -gt 0 -And $Manifest[$Index.$Title]) - { - $Manifest[$Index.$Title] - } - - Else - { - For ($i = 0; $i -lt $Manifest.Count; $i += 1) - { - If ($Manifest[$i].Title -eq $Title) - { - $Manifest[$i] - - break - } - } - } - } -} - - function Get-PageMeta { <# @@ -74,127 +21,112 @@ function Get-PageMeta -CacheIndexFile 'confluence-page-cache.json' #> Param( + # Confluence instance hostname [Parameter(Mandatory)] [string] $Host, - [Parameter(Mandatory)] [string] $Title, + # Page title + [Parameter()] [string] $Title, + # Confluence space id [Parameter(Mandatory)] [string] $Space, - [Parameter(Mandatory)] [Array] $Manifest, - [Parameter()] [Collections.Hashtable] $Index + # pages manifest + [Parameter(Mandatory, ValueFromPipeline)] [Array] $Manifest, + # page metadata index for faster lookup of single page + [Parameter()] [Collections.Hashtable] $Index, + # force to get metadata from remote + [Parameter()] [Switch] $Force = $false, + # throw an exception on error + [Parameter()] [Switch] $Strict = $true ) - Begin - { - $pageMeta = Get-PageMetaCache ` - -Title $Title ` - -Manifest $Manifest ` - -Index $Index - } - Process { - If ($pageMeta -And $pageMeta.Id) + If ($Title -And $Index -And $Manifest[$Index.$Title].Id) { - $pageMeta + $Manifest[$Index.$Title] return } - $escapedTitle = [Uri]::EscapeDataString($Title) - - #TODO: move this to a separate function - $query = "title=${escapedTitle}&spaceKey=${Space}&expand=history" - - Invoke-WebRequest ` - -Uri "https://${Host}/rest/api/content?$query" ` - -Method 'Get' ` - -Headers @{ - 'Authorization' = "Bearer $(Get-PersonalAccessToken $Host)" - } ` - -OutVariable response | Out-Null - - $results = ($response.Content | ConvertFrom-JSON).results - - if ($results.Count -gt 1) + ForEach ($pageMeta in $Manifest) { - throw "more than one result for query: $query" - } + If ($Title -And $pageMeta.Title -ne $Title) {continue} - elseif ($results.Count -eq 1) - { - Update-PageMeta ` - -Id $results[0].id ` - -Version $results[0]._expandable.version ` - -Title $Title ` - -Manifest $Manifest + If ($pageMeta.Id -And -Not $Force) + { + Write-Debug "local (cache): $($pageMeta.Title) ($($pageMeta.Id))" + + $pageMeta + } + + Else + { + $escapedTitle = [Uri]::EscapeDataString($pageMeta.Title) + + $query = ( + "title=${escapedTitle}&spaceKey=${Space}&expand=version" + ) + + Invoke-WebRequest ` + -Uri "https://${Host}/rest/api/content?$query" ` + -Method 'Get' ` + -Headers @{ + 'Authorization' = 'Bearer ' + + $(Get-PersonalAccessToken $Host) + } ` + -OutVariable response | Out-Null + + $results = ($response.Content | ConvertFrom-JSON).results + + If ($results.Count -gt 1) + { + $errMsg = "error: more than one result for query: $query" + + If ($Strict) {throw $errMsg} + + Write-Host $errMsg + + $pageMeta + + continue + } + + ElseIf ($results.Count -eq 1) + { + Write-Debug ( + "Get-PageMetadata: ``$($pageMeta.Title)``: " + + "updating metadata through remote ($($results[0].id))" + ) + + $pageMeta | Add-Member ` + -NotePropertyName Id ` + -NotePropertyValue $results[0].id ` + -Force + + $pageMeta | Add-Member ` + -NotePropertyName 'Version' ` + -NotePropertyValue ` + $results[0].version.number ` + -Force + } + + Else + { + Write-Debug "local: $($pageMeta.Title) (no remote)" + } + + If (-Not $pageMeta.Hash) + { + $content = Get-Content $pageMeta.Ref | Out-String + + $hash = (Get-StringHash $content).Hash + + $pageMeta | Add-Member ` + -NotePropertyName 'Hash' ` + -NotePropertyValue $hash ` + -Force + } + + $pageMeta + } } } } - - -function Update-PageMeta -{ - <# - .SYNOPSIS - Register a Confluence page's metadata in the local cache - - .DESCRIPTION - Synchronizes the locally cached page metadata (in manifest) with the - data stored by the Confluence instance. Therefore it is required to - supply a page id, since this is the reference linking the locally - cached page to a published instance of a page. - - .EXAMPLE - Update-PageMeta ` - -Title 'foobar' ` - -PageId 'pageId' ` - -Version 9001 ` - -AncestorTitle 'ancestorTitle' ` - -Hash 'hash' ` - -Manifest $mockManifest - #> - Param( - [Parameter(Mandatory)] [String] $Title, - # remote Confluence page instance id - [Parameter(Mandatory)] [String] $Id, - [Parameter()] [Int] $Version, - [Parameter()] [String] $AncestorTitle, - [Parameter()] [String] $Hash, - [Parameter(Mandatory)] [Array] $Manifest, - [Parameter()] [Collections.Hashtable] $Index - ) - - Process - { - $pageMeta = Get-PageMetaCache ` - -Title $Title ` - -Manifest $Manifest ` - -Index $Index - - If (-Not $pageMeta) - { - throw "page titled `$Title` not indexed in Manifest." - } - - $pageMeta.Id = $Id - - If ($Version) - { - $pageMeta.Version = $Version - } - - If ($AncestorTitle) - { - $pageMeta.AncestorTitle = $AncestorTitle - } - - # if content didn't update, hash stays the same - If ($Hash) - { - $pageMeta.Hash = $Hash - } - - Write-Debug "register: $Title -> $PageId" - - $pageMeta - } -} -