From 3be2affbb93f0ea0f164ddae018033f08970468a Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:05:03 -0400 Subject: [PATCH 1/7] initial implementation of Get-BloggerPost --- README.md | 12 ++ src/public/Get-BloggerPost.ps1 | 97 ++++++++++++++++ src/tests/Get-BloggerPost.Tests.ps1 | 166 ++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 src/public/Get-BloggerPost.ps1 create mode 100644 src/tests/Get-BloggerPost.Tests.ps1 diff --git a/README.md b/README.md index 4504467..b27ee1a 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,18 @@ A PowerShell library for publishing markdown files authored in markdown to Blogg Get-BloggerPosts ``` +1. Download an individual post from your blog + + ``` + Get-BloggerPost -PostId + ``` + + This will download the HTML content of the specified post and save it to a file named `.html` in the current directory. You can also specify a different output directory: + + ``` + Get-BloggerPost -PostId -OutDirectory "C:\MyPosts" + ``` + 1. Publish a markdown file to your blog as draft ``` diff --git a/src/public/Get-BloggerPost.ps1 b/src/public/Get-BloggerPost.ps1 new file mode 100644 index 0000000..6684138 --- /dev/null +++ b/src/public/Get-BloggerPost.ps1 @@ -0,0 +1,97 @@ +<# +.DESCRIPTION + Retrieves an individual post from a specified Blogger blog and saves the HTML content to a file. + +.PARAMETER BlogId + The ID of the blog to retrieve the post from. If not specified, uses the BlogId in the user preferences. + +.PARAMETER PostId + The ID of the post to retrieve. This parameter is required. + +.PARAMETER OutDirectory + The directory where the HTML file will be saved. If not specified, uses the current directory. + +.EXAMPLE + Get-BloggerPost -PostId "1234567890123456789" + +.EXAMPLE + Get-BloggerPost -BlogId "9876543210987654321" -PostId "1234567890123456789" -OutDirectory "C:\temp" +#> +Function Get-BloggerPost { + [CmdletBinding()] + param( + [string]$BlogId, + + [Parameter(Mandatory)] + [string]$PostId, + + [string]$OutDirectory = (Get-Location).Path + ) + + if (!$PSBoundParameters.ContainsKey("BlogId")) { + $BlogId = $BloggerSession.BlogId + if ([string]::IsNullOrEmpty($BlogId) -or $BlogId -eq 0) { + throw "BlogId not specified and no default BlogId found in settings." + } + } + + if ([string]::IsNullOrEmpty($PostId)) { + throw "PostId is required." + } + + # Ensure the output directory exists + if (!(Test-Path -Path $OutDirectory)) { + try { + New-Item -ItemType Directory -Path $OutDirectory -Force | Out-Null + } + catch { + throw "Failed to create output directory '$OutDirectory': $($_.Exception.Message)" + } + } + + try { + $uri = "https://www.googleapis.com/blogger/v3/blogs/$BlogId/posts/$PostId" + + $result = Invoke-GApi -uri $uri + + if ($null -eq $result) { + throw "No post found with PostId '$PostId' in blog '$BlogId'." + } + + # Extract the HTML content + $htmlContent = $result.content + + if ([string]::IsNullOrEmpty($htmlContent)) { + Write-Warning "Post '$PostId' has no content." + $htmlContent = "" + } + + # Create the output file path + $fileName = "$PostId.html" + $filePath = Join-Path -Path $OutDirectory -ChildPath $fileName + + # Save the HTML content to the file + try { + $htmlContent | Out-File -FilePath $filePath -Encoding UTF8 + Write-Verbose "Post content saved to: $filePath" + + # Return the post object for further processing if needed + return $result + } + catch { + throw "Failed to save post content to file '$filePath': $($_.Exception.Message)" + } + } + catch { + # Handle specific HTTP errors + if ($_.Exception -like "*404*" -or $_.Exception -like "*Not Found*") { + throw "Post with PostId '$PostId' not found in blog '$BlogId'. Please verify the PostId and BlogId are correct." + } + elseif ($_.Exception -like "*403*" -or $_.Exception -like "*Forbidden*") { + throw "Access denied to blog '$BlogId' or post '$PostId'. Please verify your permissions." + } + else { + Write-Error $_.ToString() -ErrorAction Stop + } + } +} diff --git a/src/tests/Get-BloggerPost.Tests.ps1 b/src/tests/Get-BloggerPost.Tests.ps1 new file mode 100644 index 0000000..d600c53 --- /dev/null +++ b/src/tests/Get-BloggerPost.Tests.ps1 @@ -0,0 +1,166 @@ +Describe "Get-BloggerPost" { + BeforeEach { + Import-Module $PSScriptRoot\..\PSBlogger.psm1 -Force + Import-Module $PSScriptRoot\_TestHelpers.ps1 -Force + + $OutDirectory = Resolve-Path TestDrive: + } + + AfterEach { + # remove temporarily created files + $ExpectedPath = Join-Path (Get-Location).Path -ChildPath "123.html" + if (Test-Path $ExpectedPath) { + Remove-Item $ExpectedPath -Force + } + } + + Context "Parameter validation" { + It "Should require PostId parameter" { + # act / assert + { Get-BloggerPost } | Should -Throw "*PostId*" + } + + It "Should throw error when BlogId is not provided and not in session" { + # arrange + InModuleScope PSBlogger { + $BloggerSession.BlogId = $null + } + + # act / assert + { + Get-BloggerPost -PostId "123" + } | Should -Throw "*BlogId not specified*" + } + + It "Should use session BlogId when not provided" { + # arrange + InModuleScope PSBlogger { + # setup blog id + $BloggerSession.BlogId = "test-blog-id" + + # setup blog post retrieval + Mock Invoke-GAPi { + return @{ content = "Test content" } + } + } + + # act + $result = Get-BloggerPost -PostId "123" -OutDirectory $OutDirectory + + # assert + $result | Should -Not -BeNullOrEmpty + Test-Path -Path (Join-Path -Path $OutDirectory -ChildPath "123.html") | Should -BeTrue + } + } + + Context "API interaction" { + BeforeEach { + InModuleScope PSBlogger { + # Mock the session to return a test blog ID + $BloggerSession.BlogId = "test-blog-id" + } + } + + It "Should call correct API endpoint" { + InModuleScope PSBlogger { + Mock Invoke-GApi { + return @{ content = "Test content" } + } -ParameterFilter { + $uri -eq "https://www.googleapis.com/blogger/v3/blogs/test-blog-id/posts/123" + } -Verifiable + } + + # act + Get-BloggerPost -PostId "123" + + # assert + Should -InvokeVerifiable + } + + It "Should handle 404 errors with meaningful message" { + # arrange + Mock -ModuleName PSBlogger Invoke-GApi { + throw [System.Exception]::new("404 Not Found") + } + + # act / assert + { + Get-BloggerPost -PostId "nonexistent" + } | Should -Throw "*Post with PostId 'nonexistent' not found*" + } + + It "Should handle 403 errors with meaningful message" { + # arrange + Mock -ModuleName PSBlogger Invoke-GApi { + throw [System.Exception]::new("403 Forbidden") + } + + # act / assert + { + Get-BloggerPost -PostId "123" + } | Should -Throw "*Access denied*" + } + } + + Context "File operations" { + BeforeEach { + InModuleScope PSBlogger { + # Mock the session to return a test blog ID + $BloggerSession.BlogId = "test-blog-id" + + # mock post retrieval + Mock Invoke-GApi { + return @{ content = "Test content" } + } + } + } + + It "Should create output directory if it doesn't exist" { + # arrange + $OutDirectory = "TestDrive:\nonexistent" + + # act + Get-BloggerPost -PostId "123" -OutDirectory $OutDirectory + + # assert + Test-Path -Path $OutDirectory | Should -BeTrue + } + + It "Should save content to correct filename" { + + # arrange + Get-BloggerPost -PostId "123" -OutDirectory (Resolve-Path "TestDrive:") + + # assert + Test-Path -Path "TestDrive:\123.html" | Should -BeTrue + } + + It "Should use current directory when OutDirectory not specified" { + # act + Get-BloggerPost -PostId "123" + + # assert + Test-Path -Path $ExpectedPath | Should -BeTrue + } + + It "Should handle empty content gracefully" { + # arrange + InModuleScope PSBlogger { + Mock Invoke-GApi { + return @{ content = "" } + } + + Mock Write-Warning { } + } + + + # act + Get-BloggerPost -PostId "123" + + # assert: verify warning is issued + Should -Invoke -ModuleName PSBlogger Write-Warning -Exactly 1 -ParameterFilter { + $Message -like "*has no content*" + } + } + } +} From 93ca7fd947a797e0cc753395687bc886af60bfb5 Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:15:16 -0400 Subject: [PATCH 2/7] Add Format parameter for Get-BloggerPost --- README.md | 12 +++++++++--- src/public/Get-BloggerPost.ps1 | 16 ++++++++++++++-- src/tests/Get-BloggerPost.Tests.ps1 | 9 +++++---- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b27ee1a..d9b8d22 100644 --- a/README.md +++ b/README.md @@ -52,16 +52,22 @@ A PowerShell library for publishing markdown files authored in markdown to Blogg Get-BloggerPosts ``` -1. Download an individual post from your blog +1. Fetch an individual post from your blog ``` Get-BloggerPost -PostId ``` - This will download the HTML content of the specified post and save it to a file named `.html` in the current directory. You can also specify a different output directory: + You can also persist the post to disk as HTML in the current directory. + + ``` + Get=BloggerPost -PostId -Format HTML + ``` + You can also specify an output directory. + ``` - Get-BloggerPost -PostId -OutDirectory "C:\MyPosts" + Get-BloggerPost -PostId -OutDirectory "C:\MyPosts" -Format HTML ``` 1. Publish a markdown file to your blog as draft diff --git a/src/public/Get-BloggerPost.ps1 b/src/public/Get-BloggerPost.ps1 index 6684138..5e55a59 100644 --- a/src/public/Get-BloggerPost.ps1 +++ b/src/public/Get-BloggerPost.ps1 @@ -1,6 +1,6 @@ <# .DESCRIPTION - Retrieves an individual post from a specified Blogger blog and saves the HTML content to a file. + Retrieves an individual post from a specified Blogger blog and optionally saves the HTML content to a file. .PARAMETER BlogId The ID of the blog to retrieve the post from. If not specified, uses the BlogId in the user preferences. @@ -8,6 +8,9 @@ .PARAMETER PostId The ID of the post to retrieve. This parameter is required. +.PARAMETER Format + The format of the post content to retrieve. Currently, only "HTML" is supported. + .PARAMETER OutDirectory The directory where the HTML file will be saved. If not specified, uses the current directory. @@ -15,16 +18,25 @@ Get-BloggerPost -PostId "1234567890123456789" .EXAMPLE - Get-BloggerPost -BlogId "9876543210987654321" -PostId "1234567890123456789" -OutDirectory "C:\temp" + Get-BloggerPost -BlogId "9876543210987654321" -PostId "1234567890123456789" -Format HTML -OutDirectory "C:\temp" #> Function Get-BloggerPost { [CmdletBinding()] param( + [Parameter(ParameterSetName = "Default")] + [Parameter(ParameterSetName = "Persist")] [string]$BlogId, + [Parameter(ParameterSetName = "Default")] + [Parameter(ParameterSetName = "Persist")] [Parameter(Mandatory)] [string]$PostId, + [Parameter(Mandatory, ParameterSetName = "Persist")] + [ValidateSet("HTML")] + [string]$Format, + + [Parameter(ParameterSetName = "Persist")] [string]$OutDirectory = (Get-Location).Path ) diff --git a/src/tests/Get-BloggerPost.Tests.ps1 b/src/tests/Get-BloggerPost.Tests.ps1 index d600c53..782853a 100644 --- a/src/tests/Get-BloggerPost.Tests.ps1 +++ b/src/tests/Get-BloggerPost.Tests.ps1 @@ -45,7 +45,7 @@ Describe "Get-BloggerPost" { } # act - $result = Get-BloggerPost -PostId "123" -OutDirectory $OutDirectory + $result = Get-BloggerPost -PostId "123" -Format HTML -OutDirectory $OutDirectory # assert $result | Should -Not -BeNullOrEmpty @@ -71,9 +71,10 @@ Describe "Get-BloggerPost" { } # act - Get-BloggerPost -PostId "123" + $result = Get-BloggerPost -PostId "123" # assert + $result | Should -Not -BeNullOrEmpty Should -InvokeVerifiable } @@ -120,7 +121,7 @@ Describe "Get-BloggerPost" { $OutDirectory = "TestDrive:\nonexistent" # act - Get-BloggerPost -PostId "123" -OutDirectory $OutDirectory + Get-BloggerPost -PostId "123" -OutDirectory $OutDirectory -Format HTML # assert Test-Path -Path $OutDirectory | Should -BeTrue @@ -129,7 +130,7 @@ Describe "Get-BloggerPost" { It "Should save content to correct filename" { # arrange - Get-BloggerPost -PostId "123" -OutDirectory (Resolve-Path "TestDrive:") + Get-BloggerPost -PostId "123" -OutDirectory (Resolve-Path "TestDrive:") -Format HTML # assert Test-Path -Path "TestDrive:\123.html" | Should -BeTrue From e2c876ca6a50e3a8cf52a9382e337d6240810c5e Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:09:20 -0400 Subject: [PATCH 3/7] added ConvertTo-HtmlFromMarkdown --- src/public/ConvertTo-HtmlFromMarkdown.ps1 | 2 +- src/public/ConvertTo-MarkdownFromHtml.ps1 | 71 +++++++++++++++++ .../ConvertTo-HtmlFromMarkdown.Tests.ps1 | 11 --- .../ConvertTo-MarkdownFromHtml.Tests.ps1 | 76 +++++++++++++++++++ 4 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 src/public/ConvertTo-MarkdownFromHtml.ps1 delete mode 100644 src/tests/ConvertTo-HtmlFromMarkdown.Tests.ps1 create mode 100644 src/tests/ConvertTo-MarkdownFromHtml.Tests.ps1 diff --git a/src/public/ConvertTo-HtmlFromMarkdown.ps1 b/src/public/ConvertTo-HtmlFromMarkdown.ps1 index 3ca0081..46da4fe 100644 --- a/src/public/ConvertTo-HtmlFromMarkdown.ps1 +++ b/src/public/ConvertTo-HtmlFromMarkdown.ps1 @@ -1,6 +1,6 @@ <# .SYNOPSIS - Convert a file to Markdown using Pandoc + Convert a Markdown file to HTML using Pandoc .PARAMETER File The file path of the markdown file diff --git a/src/public/ConvertTo-MarkdownFromHtml.ps1 b/src/public/ConvertTo-MarkdownFromHtml.ps1 new file mode 100644 index 0000000..a773c81 --- /dev/null +++ b/src/public/ConvertTo-MarkdownFromHtml.ps1 @@ -0,0 +1,71 @@ +<# +.SYNOPSIS + Convert HTML content or a HTML file to Markdown using Pandoc + +.PARAMETER File + The file path of the html file. Required when Content is not specified. + +.PARAMETER Content + The HTML content to convert to Markdown. Required when File is not specified. + +.PARAMETER OutFile + The resulting markdown file, if specified. + +#> +function ConvertTo-MarkdownFromHtml { + param( + [Parameter(Mandatory, ParameterSetName = "FromFile")] + [ValidateScript({ Test-Path $_ -PathType Leaf })] + [string]$File, + + [Parameter(Mandatory, ParameterSetName = "FromContent")] + [string]$Content, + + [Parameter(ParameterSetName = "FromFile")] + [Parameter(Mandatory=$false, ParameterSetName = "FromContent")] + [string]$OutFile + ) + + # when FromContent is specified, write the content to a temporary file + if ($PSCmdlet.ParameterSetName -eq "FromContent") { + # If content is provided, create a temporary file + $tempFile = [System.IO.Path]::GetTempFileName() + ".html" + Set-Content -Path $tempFile -Value $Content + $File = $tempFile + } + + # ensure that the file is an absolute path because pandoc.exe doesn't like powershell relative paths + $File = (Resolve-Path $File).Path + + # Use pandoc to convert the markdown to Html + $pandocArgs = "`"{0}`" " -f $File + $pandocArgs += "-f {0} " -f $BloggerSession.PandocHtmlFormat + $pandocArgs += "-t {0} " -f $BloggerSession.PandocMarkdownFormat + + # add additional command-line arguments + if ($BloggerSession.PandocAdditionalArgs) { + Write-Verbose "Using additional args" + $pandocArgs += "{0} " -f $BloggerSession.PandocAdditionalArgs + } + + if (!($OutFile)) { + $OutFile = Join-Path (Split-Path $File -Parent) ((Split-Path $File -LeafBase) + ".md") + Write-Verbose "Using OutFile: $OutFile" + } + + $pandocArgs += "-o `"{0}`" " -f $OutFile + + Write-Verbose ">> pandoc $($pandocArgs)" + Start-Process pandoc -ArgumentList $pandocArgs -NoNewWindow -Wait + + $content = Get-Content $OutFile -Raw + + # remove temporary files + if ($PSCmdlet.ParameterSetName -eq "FromContent") { + Remove-Item $File + } elseif (!$PSBoundParameters.ContainsKey("OutFile")) { + Remove-Item $OutFile + } + + return $content +} \ No newline at end of file diff --git a/src/tests/ConvertTo-HtmlFromMarkdown.Tests.ps1 b/src/tests/ConvertTo-HtmlFromMarkdown.Tests.ps1 deleted file mode 100644 index c1fbcc0..0000000 --- a/src/tests/ConvertTo-HtmlFromMarkdown.Tests.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -# Describe "ConvertTo Markdown" { -# BeforeAll { -# Import-Module $PSScriptRoot\_TestHelpers.ps1 -Force -# } - -# BeforeEach { -# Import-Module $PSScriptRoot\..\PSBlogger.psm1 -Force -# } - - -# } \ No newline at end of file diff --git a/src/tests/ConvertTo-MarkdownFromHtml.Tests.ps1 b/src/tests/ConvertTo-MarkdownFromHtml.Tests.ps1 new file mode 100644 index 0000000..df88adb --- /dev/null +++ b/src/tests/ConvertTo-MarkdownFromHtml.Tests.ps1 @@ -0,0 +1,76 @@ +Describe "ConvertTo-MarkdownFromHtml" { + BeforeAll { + Import-Module $PSScriptRoot\_TestHelpers.ps1 -Force + + } + + BeforeEach { + Import-Module $PSScriptRoot\..\PSBlogger.psm1 -Force + } + + Context "Using Content" { + + BeforeEach { + $outFile = "TestDrive:\123.md" + $outFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($outFile) + $htmlContent = "

Hello World

" + } + + It "Should save HTML content to a markdown file" { + # act + ConvertTo-MarkdownFromHtml -Content $htmlContent -OutFile $outFilePath + + # assert + Test-Path $outFile | Should -BeTrue + } + + It "Should convert HTML content to Markdown file" { + # act + $content = ConvertTo-MarkdownFromHtml -Content $htmlContent -OutFile $outFilePath + + # assert + $content = (Get-Content -Path $outFile -Raw).Split("`r") + $content[0] | Should -Be "# Hello World" + } + + It "Should not persist to disk if OutFile is not specified" { + # act + $content = ConvertTo-MarkdownFromHtml -Content $htmlContent + + # assert + $content | Should -Not -BeNullOrEmpty + Test-Path $outFile | Should -BeFalse + } + } + + Context "Using File" { + + BeforeEach { + $htmlContent = "

Hello World

" + $htmlFile = "TestDrive:\123.html" + $htmlFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($htmlFile) + $markdownFile = "TestDrive:\123.md" + $markdownFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($markdownFile) + Set-Content -Path $htmlFile -Value $htmlContent + } + + It "Should convert HTML file to Markdown" { + # act + $content = ConvertTo-MarkdownFromHtml -File $htmlFilePath -OutFile $markdownFilePath + + # assert + Test-Path $markdownFile | Should -BeTrue + $content = (Get-Content -Path $markdownFile -Raw).Split("`r") + $content[0] | Should -Be "# Hello World" + } + + It "Should delete temporary file" { + # act + $content = ConvertTo-MarkdownFromHtml -File $htmlFilePath + + # assert + $content | Should -Not -BeNullOrEmpty + Test-Path $markdownFile | Should -BeFalse + } + } +} \ No newline at end of file From dc170c2abab4ae1f8b602019077e914a67de430d Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:34:55 -0400 Subject: [PATCH 4/7] resolve pspath when using pandoc --- src/public/ConvertTo-MarkdownFromHtml.ps1 | 4 +++- src/tests/ConvertTo-MarkdownFromHtml.Tests.ps1 | 17 ++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/public/ConvertTo-MarkdownFromHtml.ps1 b/src/public/ConvertTo-MarkdownFromHtml.ps1 index a773c81..a350b44 100644 --- a/src/public/ConvertTo-MarkdownFromHtml.ps1 +++ b/src/public/ConvertTo-MarkdownFromHtml.ps1 @@ -35,7 +35,7 @@ function ConvertTo-MarkdownFromHtml { } # ensure that the file is an absolute path because pandoc.exe doesn't like powershell relative paths - $File = (Resolve-Path $File).Path + $File = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($File) # Use pandoc to convert the markdown to Html $pandocArgs = "`"{0}`" " -f $File @@ -52,6 +52,8 @@ function ConvertTo-MarkdownFromHtml { $OutFile = Join-Path (Split-Path $File -Parent) ((Split-Path $File -LeafBase) + ".md") Write-Verbose "Using OutFile: $OutFile" } + # ensure that the file is an absolute path because pandoc.exe doesn't like powershell relative paths + $OutFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutFile) $pandocArgs += "-o `"{0}`" " -f $OutFile diff --git a/src/tests/ConvertTo-MarkdownFromHtml.Tests.ps1 b/src/tests/ConvertTo-MarkdownFromHtml.Tests.ps1 index df88adb..3dcc672 100644 --- a/src/tests/ConvertTo-MarkdownFromHtml.Tests.ps1 +++ b/src/tests/ConvertTo-MarkdownFromHtml.Tests.ps1 @@ -12,13 +12,18 @@ Describe "ConvertTo-MarkdownFromHtml" { BeforeEach { $outFile = "TestDrive:\123.md" - $outFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($outFile) $htmlContent = "

Hello World

" } + AfterEach { + if (Test-Path $outFile) { + Remove-Item $outFile -Force + } + } + It "Should save HTML content to a markdown file" { # act - ConvertTo-MarkdownFromHtml -Content $htmlContent -OutFile $outFilePath + ConvertTo-MarkdownFromHtml -Content $htmlContent -OutFile $outFile # assert Test-Path $outFile | Should -BeTrue @@ -26,7 +31,7 @@ Describe "ConvertTo-MarkdownFromHtml" { It "Should convert HTML content to Markdown file" { # act - $content = ConvertTo-MarkdownFromHtml -Content $htmlContent -OutFile $outFilePath + $content = ConvertTo-MarkdownFromHtml -Content $htmlContent -OutFile $outFile # assert $content = (Get-Content -Path $outFile -Raw).Split("`r") @@ -48,15 +53,13 @@ Describe "ConvertTo-MarkdownFromHtml" { BeforeEach { $htmlContent = "

Hello World

" $htmlFile = "TestDrive:\123.html" - $htmlFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($htmlFile) $markdownFile = "TestDrive:\123.md" - $markdownFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($markdownFile) Set-Content -Path $htmlFile -Value $htmlContent } It "Should convert HTML file to Markdown" { # act - $content = ConvertTo-MarkdownFromHtml -File $htmlFilePath -OutFile $markdownFilePath + $content = ConvertTo-MarkdownFromHtml -File $htmlFile -OutFile $markdownFile # assert Test-Path $markdownFile | Should -BeTrue @@ -66,7 +69,7 @@ Describe "ConvertTo-MarkdownFromHtml" { It "Should delete temporary file" { # act - $content = ConvertTo-MarkdownFromHtml -File $htmlFilePath + $content = ConvertTo-MarkdownFromHtml -File $htmlFile # assert $content | Should -Not -BeNullOrEmpty From c0f7b158ef710f3c04037f64f9c1a2ff196ea213 Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:35:44 -0400 Subject: [PATCH 5/7] Add support for saving as markdown --- README.md | 9 +++- src/public/Get-BloggerPost.ps1 | 40 +++++++++++---- src/tests/Get-BloggerPost.Tests.ps1 | 75 +++++++++++++++++++++++------ 3 files changed, 97 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index d9b8d22..8c2ad2c 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,15 @@ A PowerShell library for publishing markdown files authored in markdown to Blogg Get-BloggerPost -PostId ``` - You can also persist the post to disk as HTML in the current directory. + You can also persist the post to disk as HTML or markdown in the current directory. + + When using `HTML` format, files are saved as `.html` + + When using `Markdown` format, files are saved as `.md` ``` - Get=BloggerPost -PostId <postid> -Format HTML + Get-BloggerPost -PostId <postid> -Format HTML + Get-BloggerPost -PostId <postid> -Format Markdown ``` You can also specify an output directory. diff --git a/src/public/Get-BloggerPost.ps1 b/src/public/Get-BloggerPost.ps1 index 5e55a59..06568d6 100644 --- a/src/public/Get-BloggerPost.ps1 +++ b/src/public/Get-BloggerPost.ps1 @@ -1,6 +1,6 @@ <# .DESCRIPTION - Retrieves an individual post from a specified Blogger blog and optionally saves the HTML content to a file. + Retrieves an individual post from a specified Blogger blog and optionally saves the content to a file as HTML or Markdown. .PARAMETER BlogId The ID of the blog to retrieve the post from. If not specified, uses the BlogId in the user preferences. @@ -9,7 +9,7 @@ The ID of the post to retrieve. This parameter is required. .PARAMETER Format - The format of the post content to retrieve. Currently, only "HTML" is supported. + The format of the post content to retrieve. Use either Markdown or HTML. .PARAMETER OutDirectory The directory where the HTML file will be saved. If not specified, uses the current directory. @@ -33,7 +33,7 @@ Function Get-BloggerPost { [string]$PostId, [Parameter(Mandatory, ParameterSetName = "Persist")] - [ValidateSet("HTML")] + [ValidateSet("HTML", "Markdown")] [string]$Format, [Parameter(ParameterSetName = "Persist")] @@ -79,14 +79,34 @@ Function Get-BloggerPost { } # Create the output file path - $fileName = "$PostId.html" - $filePath = Join-Path -Path $OutDirectory -ChildPath $fileName - - # Save the HTML content to the file try { - $htmlContent | Out-File -FilePath $filePath -Encoding UTF8 - Write-Verbose "Post content saved to: $filePath" - + + switch ($Format) { + + # Save the HTML content to a file + "HTML" { + + $fileName = "$PostId.html" + $filePath = Join-Path -Path $OutDirectory -ChildPath $fileName + $htmlContent | Out-File -FilePath $filePath -Encoding UTF8 + Write-Verbose "Post content saved to: $filePath" + } + + # Save the Post to a Markdown file + "Markdown" { + + $title = $result.title + $frontMatter = [ordered]@{ + postId = $result.id + } + $file = "$title.md" + $filePath = Join-Path -Path $OutDirectory -ChildPath $file + ConvertTo-MarkdownFromHtml -Content $result.content -OutFile $filePath > $null + Set-MarkdownFrontMatter -File $filePath -Replace $frontMatter + Write-Verbose "Post content saved to: $filePath" + } + } + # Return the post object for further processing if needed return $result } diff --git a/src/tests/Get-BloggerPost.Tests.ps1 b/src/tests/Get-BloggerPost.Tests.ps1 index 782853a..6057f93 100644 --- a/src/tests/Get-BloggerPost.Tests.ps1 +++ b/src/tests/Get-BloggerPost.Tests.ps1 @@ -100,10 +100,30 @@ Describe "Get-BloggerPost" { { Get-BloggerPost -PostId "123" } | Should -Throw "*Access denied*" + } + + It "Should handle empty content gracefully" { + # arrange + InModuleScope PSBlogger { + Mock Invoke-GApi { + return @{ content = "" } + } + + Mock Write-Warning { } + } + + + # act + Get-BloggerPost -PostId "123" + + # assert: verify warning is issued + Should -Invoke -ModuleName PSBlogger Write-Warning -Exactly 1 -ParameterFilter { + $Message -like "*has no content*" + } } } - Context "File operations" { + Context "As HTML" { BeforeEach { InModuleScope PSBlogger { # Mock the session to return a test blog ID @@ -137,31 +157,56 @@ Describe "Get-BloggerPost" { } It "Should use current directory when OutDirectory not specified" { + # arrange + $ExpectedPath = Join-Path (Get-Location).Path -ChildPath "123.html" + # act - Get-BloggerPost -PostId "123" + Get-BloggerPost -PostId "123" -Format HTML # assert Test-Path -Path $ExpectedPath | Should -BeTrue } - - It "Should handle empty content gracefully" { - # arrange + } + + Context "As Markdown" { + + BeforeEach { InModuleScope PSBlogger { - Mock Invoke-GApi { - return @{ content = "" } - } + # Mock the session to return a test blog ID + $BloggerSession.BlogId = "test-blog-id" - Mock Write-Warning { } + $postId = "123" + + # mock post retrieval + Mock Invoke-GApi { + return @{ + id = $postId + title = "Test Post" + content = "<h1>Hello World</h1><p>This is a post.</p>" + } + } } + + $postId = "123" + $title = "Test Post" + $outFile = "TestDrive:\$title.md" + } + + AfterEach { + if (Test-Path $outFile) { + Remove-Item $outFile -Force + } + } + + It "Should write post details to frontmatter" { # act - Get-BloggerPost -PostId "123" - - # assert: verify warning is issued - Should -Invoke -ModuleName PSBlogger Write-Warning -Exactly 1 -ParameterFilter { - $Message -like "*has no content*" - } + Get-BloggerPost -PostId $postId -Format Markdown -OutDirectory "TestDrive:\" + + # assert + $frontMatter = Get-MarkdownFrontMatter -File $outFile + $frontMatter.postId | Should -Be "123" } } } From d0b41438029e1717437444bca225f72fa05d5a94 Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Tue, 22 Jul 2025 17:10:17 -0400 Subject: [PATCH 6/7] Added ability to save post in published folder using FolderDateFormat --- README.md | 8 ++- src/public/Get-BloggerPost.ps1 | 42 +++++++++++----- src/tests/Get-BloggerPost.Tests.ps1 | 75 ++++++++++++++++++++++++++++- 3 files changed, 110 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8c2ad2c..a037056 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,18 @@ A PowerShell library for publishing markdown files authored in markdown to Blogg Get-BloggerPost -PostId <postid> -Format Markdown ``` - You can also specify an output directory. + You can specify an output directory where the file will be saved. ``` Get-BloggerPost -PostId <postid> -OutDirectory "C:\MyPosts" -Format HTML ``` + You can also include a `FolderDateFormat` that uses the `published` property of the blog post to construct a subfolder. + + ``` + Get-BloggerPost -PostId <postId> -OutDirectory ".\Blog" -FolderDateFormat "YYYY\\MM" -Format Markdown + ``` + 1. Publish a markdown file to your blog as draft ``` diff --git a/src/public/Get-BloggerPost.ps1 b/src/public/Get-BloggerPost.ps1 index 06568d6..a7758c7 100644 --- a/src/public/Get-BloggerPost.ps1 +++ b/src/public/Get-BloggerPost.ps1 @@ -11,6 +11,10 @@ .PARAMETER Format The format of the post content to retrieve. Use either Markdown or HTML. +.PARAMETER FolderDateFormat + The folder name as expressed in a DateTime format string. For example, "YYYY/MM" which will save files + in a folder structure like "2023/10" based on the date of the post. + .PARAMETER OutDirectory The directory where the HTML file will be saved. If not specified, uses the current directory. @@ -19,6 +23,9 @@ .EXAMPLE Get-BloggerPost -BlogId "9876543210987654321" -PostId "1234567890123456789" -Format HTML -OutDirectory "C:\temp" + +.EXAMPLE + Get-BloggerPost -BlogId "9876543210987654321" -PostId "1234567890123456789" -Format Markdown -DateFormat "YYYY\\MM" -OutDirectory "C:\blogposts" #> Function Get-BloggerPost { [CmdletBinding()] @@ -27,15 +34,17 @@ Function Get-BloggerPost { [Parameter(ParameterSetName = "Persist")] [string]$BlogId, - [Parameter(ParameterSetName = "Default")] - [Parameter(ParameterSetName = "Persist")] - [Parameter(Mandatory)] + [Parameter(Mandatory,ParameterSetName = "Default")] + [Parameter(Mandatory,ParameterSetName = "Persist")] [string]$PostId, [Parameter(Mandatory, ParameterSetName = "Persist")] [ValidateSet("HTML", "Markdown")] [string]$Format, + [Parameter(ParameterSetName ="Persist")] + [string]$FolderDateFormat, + [Parameter(ParameterSetName = "Persist")] [string]$OutDirectory = (Get-Location).Path ) @@ -51,16 +60,6 @@ Function Get-BloggerPost { throw "PostId is required." } - # Ensure the output directory exists - if (!(Test-Path -Path $OutDirectory)) { - try { - New-Item -ItemType Directory -Path $OutDirectory -Force | Out-Null - } - catch { - throw "Failed to create output directory '$OutDirectory': $($_.Exception.Message)" - } - } - try { $uri = "https://www.googleapis.com/blogger/v3/blogs/$BlogId/posts/$PostId" @@ -70,6 +69,23 @@ Function Get-BloggerPost { throw "No post found with PostId '$PostId' in blog '$BlogId'." } + # Construct a subfolder based on the published date + if ($FolderDateFormat -and $result.published) { + $date = [datetime]::Parse($result.published) + $formattedDate = $date.ToString($FolderDateFormat) + $OutDirectory = Join-Path -Path $OutDirectory -ChildPath $formattedDate + } + + # Ensure the output directory exists + if (!(Test-Path -Path $OutDirectory)) { + try { + New-Item -ItemType Directory -Path $OutDirectory -Force | Out-Null + } + catch { + throw "Failed to create output directory '$OutDirectory': $($_.Exception.Message)" + } + } + # Extract the HTML content $htmlContent = $result.content diff --git a/src/tests/Get-BloggerPost.Tests.ps1 b/src/tests/Get-BloggerPost.Tests.ps1 index 6057f93..b1855f3 100644 --- a/src/tests/Get-BloggerPost.Tests.ps1 +++ b/src/tests/Get-BloggerPost.Tests.ps1 @@ -17,7 +17,7 @@ Describe "Get-BloggerPost" { Context "Parameter validation" { It "Should require PostId parameter" { # act / assert - { Get-BloggerPost } | Should -Throw "*PostId*" + { Get-BloggerPost -Format Markdown } | Should -Throw "*PostId*" } It "Should throw error when BlogId is not provided and not in session" { @@ -182,6 +182,7 @@ Describe "Get-BloggerPost" { return @{ id = $postId title = "Test Post" + published = "2023-10-01T12:00:00Z" content = "<h1>Hello World</h1><p>This is a post.</p>" } } @@ -209,4 +210,76 @@ Describe "Get-BloggerPost" { $frontMatter.postId | Should -Be "123" } } + + Context "Using FolderDateFormat" { + + BeforeEach { + InModuleScope PSBlogger { + # Mock the session to return a test blog ID + $BloggerSession.BlogId = "test-blog-id" + + $postId = "123" + + # mock post retrieval + Mock Invoke-GApi { + return @{ + id = $postId + title = "Test Post" + published = "2023-10-01T12:00:00Z" + content = "<h1>Hello World</h1><p>This is a post.</p>" + } + } + } + } + + It "Should write file to specified formatted directory - <dateformat> - <format>" -TestCases @( + @{ DateFormat = "yyyy\\MM"; ExpectedPath = "TestDrive:\2023\10\Test Post.md"; Format = "Markdown" } + @{ DateFormat = "yyyy\\MM\\dd"; ExpectedPath = "TestDrive:\2023\10\01\123.html"; Format = "HTML" } + ) { + # arrange + $invokeArgs = @{ + PostId = 123 + Format = $Format + OutDirectory = "TestDrive:\" + FolderDateFormat = $DateFormat + } + + # act + Get-BloggerPost @invokeArgs + + # assert + Test-Path $ExpectedPath | Should -BeTrue + + } + + It "Should ignore folderdateformat when not specified" { + + # act + Get-BloggerPost -PostId 123 -Format HTML -OutDirectory "TestDrive:\" + + # assert + Test-Path "TestDrive:\123.html" | Should -BeTrue + + } + + It "Should ignore folderdateformat when post has not been published" { + # arrange + InModuleScope PSBlogger { + Mock Invoke-GApi { + return @{ + id = 123 + title = "Test Post" + published = $null + content = "<h1>Hello World</h1><p>This is a post.</p>" + } + } + } + + # act + Get-BloggerPost -PostId 123 -Format HTML -OutDirectory "TestDrive:\" -FolderDateFormat "YYYY\\MM" + + # assert + Test-Path "TestDrive:\123.html" | Should -BeTrue + } + } } From 792f1a757300ea146174e51e995e482fada13628 Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Tue, 22 Jul 2025 17:10:30 -0400 Subject: [PATCH 7/7] install pandoc for executing tests --- .github/workflows/pester-tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/pester-tests.yml b/.github/workflows/pester-tests.yml index dfdc459..e6f3949 100644 --- a/.github/workflows/pester-tests.yml +++ b/.github/workflows/pester-tests.yml @@ -20,6 +20,11 @@ jobs: Set-PSRepository PSGallery -InstallationPolicy Trusted Install-Module -Name Pester -Force -SkipPublisherCheck Install-Module -Name PowerShell-Yaml -Force -SkipPublisherCheck + + - name: Install pandoc + shell: pwsh + run: | + choco install pandoc -y - name: Run Pester Tests shell: pwsh