From b6e9a38342ada1f24533f7260a4466dd0335c399 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 1 Jul 2025 14:34:29 -0700 Subject: [PATCH 1/4] Parse the multiline metadata correctly --- .../CommandHelpMarkdownReader.cs | 52 ++++++++++--------- src/MarkdownReader/MarkdownProbeInfo.cs | 2 +- .../ModuleFileMarkdownReader.cs | 6 +-- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/MarkdownReader/CommandHelpMarkdownReader.cs b/src/MarkdownReader/CommandHelpMarkdownReader.cs index bc8a7daa..9adef1cb 100644 --- a/src/MarkdownReader/CommandHelpMarkdownReader.cs +++ b/src/MarkdownReader/CommandHelpMarkdownReader.cs @@ -12,7 +12,7 @@ using Markdig.Syntax.Inlines; using Microsoft.PowerShell.PlatyPS.MarkdownWriter; using Microsoft.PowerShell.PlatyPS.Model; -using Markdig.Renderers; +using System.Collections; namespace Microsoft.PowerShell.PlatyPS { @@ -198,7 +198,7 @@ internal static CommandHelp GetCommandHelpFromMarkdown(ParsedMarkdownContent mar CommandHelp commandHelp; - OrderedDictionary? metadata = GetMetadata(markdownContent.Ast); + OrderedDictionary? metadata = GetMetadata(markdownContent); if (metadata is null) { throw new InvalidDataException($"No metadata found in {markdownContent.FilePath}"); @@ -327,8 +327,9 @@ internal static CommandHelp GetCommandHelpFromMarkdown(ParsedMarkdownContent mar } - internal static OrderedDictionary? GetMetadata(MarkdownDocument ast) + internal static OrderedDictionary? GetMetadata(ParsedMarkdownContent md) { + var ast = md.Ast; // The metadata must be the first block in the markdown if (ast.Count < 2) { @@ -353,33 +354,36 @@ internal static CommandHelp GetCommandHelpFromMarkdown(ParsedMarkdownContent mar } else if (ast[2] is Markdig.Syntax.ListBlock && ast[1] is ParagraphBlock paragraphWithList) { - List listItems = new(); - - if (ast[2] is Markdig.Syntax.ListBlock listBlock) + if (paragraphWithList.Inline?.FirstChild is LiteralInline metadataAsParagraph) { - foreach (var listItem in listBlock) + var metadataDictionary = ConvertTextToOrderedDictionary(metadataAsParagraph.Content.Text); + + StringBuilder? sb = null; + + try { - if (listItem is ListItemBlock listItemBlock) + sb = Constants.StringBuilderPool.Get(); + int startIndex = ast[2].Line - 1; // -1 because we are 0 based + int endIndex = ast[3].Line - 1; // -1 because we are 0 based + for (int i = startIndex; i < endIndex; i++) + { + sb.AppendLine(md.MarkdownLines[i].TrimEnd()); + } + string blockContent = sb.ToString().Replace("\r", "").Trim(); + + foreach(DictionaryEntry kv in ConvertTextToOrderedDictionary(blockContent)) { - if (listItemBlock is ContainerBlock containerBlock) - { - if (containerBlock.LastChild is ParagraphBlock paragraphListItem) - { - if (paragraphListItem.Inline?.FirstChild is LiteralInline listItemText) - { - listItems.Add(listItemText.Content.ToString().Trim()); - } - } - } + metadataDictionary[kv.Key] = kv.Value; + } + } + finally + { + if (sb is not null) + { + Constants.StringBuilderPool.Return(sb); } } - } - if (paragraphWithList.Inline?.FirstChild is LiteralInline metadataAsParagraph) - { - var metadataDictionary = ConvertTextToOrderedDictionary(metadataAsParagraph.Content.Text); - // update the metadata dictionary with the list items of aliases - metadataDictionary["aliases"] = listItems; return metadataDictionary; } } diff --git a/src/MarkdownReader/MarkdownProbeInfo.cs b/src/MarkdownReader/MarkdownProbeInfo.cs index bb7f6f95..7569be72 100644 --- a/src/MarkdownReader/MarkdownProbeInfo.cs +++ b/src/MarkdownReader/MarkdownProbeInfo.cs @@ -68,7 +68,7 @@ public MarkdownProbeInfo(string filePath) FilePath = filePath; DiagnosticMessages = new(); MarkdownContent = ParsedMarkdownContent.ParseFile(filePath); - MetaData = MarkdownConverter.GetMetadata(MarkdownContent.Ast); + MetaData = MarkdownConverter.GetMetadata(MarkdownContent); DetermineContentType(); } diff --git a/src/MarkdownReader/ModuleFileMarkdownReader.cs b/src/MarkdownReader/ModuleFileMarkdownReader.cs index 954fe5a1..ca5e5f33 100644 --- a/src/MarkdownReader/ModuleFileMarkdownReader.cs +++ b/src/MarkdownReader/ModuleFileMarkdownReader.cs @@ -33,7 +33,7 @@ internal static ModuleFileInfo GetModuleFileInfoFromMarkdown(ParsedMarkdownConte */ ModuleFileInfo moduleFileInfo = new(); - OrderedDictionary? metadata = GetMetadata(markdownContent.Ast); + OrderedDictionary? metadata = GetMetadata(markdownContent); if (metadata is null) { throw new InvalidDataException("null metadata"); @@ -151,7 +151,7 @@ internal static string GetModuleFileTitleFromMarkdown(ParsedMarkdownContent md, return string.Empty; } - var titleString = titleBlock.Inline?.FirstChild?.ToString().Trim(); + var titleString = titleBlock.Inline?.FirstChild?.ToString().Trim(); if (titleString is null) { diagnostics.Add(new DiagnosticMessage(DiagnosticMessageSource.ModuleFileTitle, "Null title", DiagnosticSeverity.Error, "GetModuleFileTitle", -1)); @@ -190,7 +190,7 @@ internal static string GetModuleFileDescriptionFromMarkdown(ParsedMarkdownConten } /// - /// Read the module file looking for + /// Read the module file looking for /// "### [name](markdownfile.md) /// Description /// From e47b76a5fed5b85c6b1ea7af231f614940d71fa4 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 1 Jul 2025 14:54:59 -0700 Subject: [PATCH 2/4] fixes and test --- .../CommandHelpMarkdownReader.cs | 2 +- .../ImportMarkdownCommandHelp.Tests.ps1 | 6 ++ test/Pester/MeasurePlatyPSMarkdown.Tests.ps1 | 4 +- test/Pester/assets/CimCmdlets2.md | 80 +++++++++++++++++++ 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 test/Pester/assets/CimCmdlets2.md diff --git a/src/MarkdownReader/CommandHelpMarkdownReader.cs b/src/MarkdownReader/CommandHelpMarkdownReader.cs index 9adef1cb..fecd88b1 100644 --- a/src/MarkdownReader/CommandHelpMarkdownReader.cs +++ b/src/MarkdownReader/CommandHelpMarkdownReader.cs @@ -364,7 +364,7 @@ internal static CommandHelp GetCommandHelpFromMarkdown(ParsedMarkdownContent mar { sb = Constants.StringBuilderPool.Get(); int startIndex = ast[2].Line - 1; // -1 because we are 0 based - int endIndex = ast[3].Line - 1; // -1 because we are 0 based + int endIndex = ast[3].Line; for (int i = startIndex; i < endIndex; i++) { sb.AppendLine(md.MarkdownLines[i].TrimEnd()); diff --git a/test/Pester/ImportMarkdownCommandHelp.Tests.ps1 b/test/Pester/ImportMarkdownCommandHelp.Tests.ps1 index c1028836..288828af 100644 --- a/test/Pester/ImportMarkdownCommandHelp.Tests.ps1 +++ b/test/Pester/ImportMarkdownCommandHelp.Tests.ps1 @@ -24,6 +24,8 @@ Describe 'Import-MarkdownCommandHelp Tests' { BeforeAll { $result = Import-MarkdownCommandHelp -Path $goodMarkdownPath $metadata = $result.GetType().GetProperty('Metadata', [System.Reflection.BindingFlags]'Public,NonPublic,Instance').GetValue($result, $null) + + $md = Import-MarkdownModuleFile -Path "$assetdir/CimCmdlets2.md" } It 'Should be a valid CommandHelp object' { @@ -59,6 +61,10 @@ Describe 'Import-MarkdownCommandHelp Tests' { $result.Metadata.aliases[1] | Should -Be "ls" $result.Metadata.aliases[2] | Should -Be "gci" } + + It 'Should be able to read multiline metadata' { + $md.Metadata['foo'] | Should -Be "bar" + } } Context "Validate Aliases" { diff --git a/test/Pester/MeasurePlatyPSMarkdown.Tests.ps1 b/test/Pester/MeasurePlatyPSMarkdown.Tests.ps1 index 8f86e589..256a8df8 100644 --- a/test/Pester/MeasurePlatyPSMarkdown.Tests.ps1 +++ b/test/Pester/MeasurePlatyPSMarkdown.Tests.ps1 @@ -14,8 +14,8 @@ Describe "Export-MarkdownModuleFile" { It "Should identify all the '' assets" -TestCases @( @{ fileType = "unknown"; expectedCount = 2 } @{ fileType = "CommandHelp"; expectedCount = 40 } - @{ fileType = "ModuleFile"; expectedCount = 14 } - @{ fileType = "V1Schema"; expectedCount = 49 } + @{ fileType = "ModuleFile"; expectedCount = 15 } + @{ fileType = "V1Schema"; expectedCount = 50 } @{ fileType = "V2Schema"; expectedCount = 5 } ) { param ($fileType, $expectedCount) diff --git a/test/Pester/assets/CimCmdlets2.md b/test/Pester/assets/CimCmdlets2.md new file mode 100644 index 00000000..a7144b6b --- /dev/null +++ b/test/Pester/assets/CimCmdlets2.md @@ -0,0 +1,80 @@ +--- +Download Help Link: https://aka.ms/powershell74-help +Help Version: 7.4.0.0 +Locale: en-US +Module Guid: fb6cc51d-c096-4b38-b78d-0fed6277096a +Module Name: CimCmdlets +ms.date: 02/20/2019 +schema: 2.0.0 +title: CimCmdlets Module +isPreview: true +foo: + - bar +test: true +--- +# CimCmdlets Module + +## Description + +Contains cmdlets that interact with Common Information Model (CIM) Servers like the Windows +Management Instrumentation (WMI) service. + +This module is only available on the Windows platform. + +## CimCmdlets Cmdlets + +### [Export-BinaryMiLog](Export-BinaryMiLog.md) + +Creates a binary encoded representation of an object or objects and stores it in a file. + +### [Get-CimAssociatedInstance](Get-CimAssociatedInstance.md) + +Retrieves the CIM instances that are connected to a specific CIM instance by an association. + +### [Get-CimClass](Get-CimClass.md) + +Gets a list of CIM classes in a specific namespace. + +### [Get-CimInstance](Get-CimInstance.md) + +Gets the CIM instances of a class from a CIM server. + +### [Get-CimSession](Get-CimSession.md) + +Gets the CIM session objects from the current session. + +### [Import-BinaryMiLog](Import-BinaryMiLog.md) + +Used to re-create the saved objects based on the contents of an export file. + +### [Invoke-CimMethod](Invoke-CimMethod.md) + +Invokes a method of a CIM class. + +### [New-CimInstance](New-CimInstance.md) + +Creates a CIM instance. + +### [New-CimSession](New-CimSession.md) + +Creates a CIM session. + +### [New-CimSessionOption](New-CimSessionOption.md) + +Specifies advanced options for the `New-CimSession` cmdlet. + +### [Register-CimIndicationEvent](Register-CimIndicationEvent.md) + +Subscribes to indications using a filter expression or a query expression. + +### [Remove-CimInstance](Remove-CimInstance.md) + +Removes a CIM instance from a computer. + +### [Remove-CimSession](Remove-CimSession.md) + +Removes one or more CIM sessions. + +### [Set-CimInstance](Set-CimInstance.md) + +Modifies a CIM instance on a CIM server by calling the **ModifyInstance** method of the CIM class. From 4d86c60c1978a31ebe87053dd4ad4d97c92a9148 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 1 Jul 2025 15:25:14 -0700 Subject: [PATCH 3/4] Fix CI --- test/Pester/assets/CimCmdlets2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Pester/assets/CimCmdlets2.md b/test/Pester/assets/CimCmdlets2.md index a7144b6b..7e0e4bdf 100644 --- a/test/Pester/assets/CimCmdlets2.md +++ b/test/Pester/assets/CimCmdlets2.md @@ -3,7 +3,7 @@ Download Help Link: https://aka.ms/powershell74-help Help Version: 7.4.0.0 Locale: en-US Module Guid: fb6cc51d-c096-4b38-b78d-0fed6277096a -Module Name: CimCmdlets +Module Name: CimCmdlets2 ms.date: 02/20/2019 schema: 2.0.0 title: CimCmdlets Module @@ -12,7 +12,7 @@ foo: - bar test: true --- -# CimCmdlets Module +# CimCmdlets2 Module ## Description From e5aa828461787e78a2254710e429b777041f8335 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 1 Jul 2025 15:31:39 -0700 Subject: [PATCH 4/4] update test --- test/Pester/ImportMarkdownModuleFile.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Pester/ImportMarkdownModuleFile.Tests.ps1 b/test/Pester/ImportMarkdownModuleFile.Tests.ps1 index 71c1a630..1c52f660 100644 --- a/test/Pester/ImportMarkdownModuleFile.Tests.ps1 +++ b/test/Pester/ImportMarkdownModuleFile.Tests.ps1 @@ -9,7 +9,7 @@ Describe "Import-ModuleFile tests" { Context "File creation" { It "Should be able to read module files" { $results = $modFiles.FullName | Import-MarkdownModuleFile - $results.Count | Should -Be 13 + $results.Count | Should -Be 14 } It "Should produce the correct type of object" {