Private/ConvertFrom-CommentHelp.ps1
|
#!/usr/bin/env pwsh Set-StrictMode -Version Latest function ConvertFrom-CommentHelp { <# .SYNOPSIS Parses the comment-based help of a script into data. .DESCRIPTION Finds the first comment block containing a help keyword and parses it into an ordered structure. Found is true only when such a block exists; every other field is an empty string or an empty array when absent, so the result never contains null. Recognized keywords are SYNOPSIS, DESCRIPTION, PARAMETER, EXAMPLE, INPUTS, OUTPUTS, NOTES, and LINK. Within an example, the first blank line ends the code and starts the explanation, and the code is dedented by the keyword indent plus four spaces. .PARAMETER Content The raw text of the script to parse. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. One object with Found, Synopsis, Description, Inputs, Outputs, Notes, Links, Parameters, and Examples. Parameters contains Name and Text; Examples contains Code and Text. .EXAMPLE $data = ConvertFrom-CommentHelp -Content (Get-Content -LiteralPath ./Public/Get-AclItem.ps1 -Raw) $data.Parameters | Format-Table Name, Text Lists the documented parameters of Get-AclItem. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory, HelpMessage = 'Enter the raw script text to parse.')] [ValidateNotNullOrEmpty()] [string]$Content ) $keywords = 'SYNOPSIS|DESCRIPTION|PARAMETER|EXAMPLE|INPUTS|OUTPUTS|NOTES|LINK' $keywordPattern = [regex]"^\s*\.(?<keyword>$keywords)(?:\s+(?<argument>.*))?$" # The first comment block containing a help keyword is the help block. $block = [regex]::Matches($Content, '(?s)<#(?<body>.*?)#>') | Where-Object { $_.Groups['body'].Value -match "(?m)^\s*\.($keywords)\b" } | Select-Object -First 1 $lines = @(if ($block) { $block.Groups['body'].Value -split "`r?`n" }) $synopsis = [System.Collections.Generic.List[string]]::new() $description = [System.Collections.Generic.List[string]]::new() $inputs = [System.Collections.Generic.List[string]]::new() $outputs = [System.Collections.Generic.List[string]]::new() $notes = [System.Collections.Generic.List[string]]::new() $links = [System.Collections.Generic.List[string]]::new() $parameters = [System.Collections.Generic.List[object]]::new() $examples = [System.Collections.Generic.List[object]]::new() # Lines outside a recognized section, such as after .LINK, collect here unused. $ignored = [System.Collections.Generic.List[string]]::new() $current = $ignored $inExampleCode = $false $exampleIndent = 0 foreach ($line in $lines) { $keywordMatch = $keywordPattern.Match($line) if ($keywordMatch.Success) { $keyword = $keywordMatch.Groups['keyword'].Value.ToUpperInvariant() $argument = $keywordMatch.Groups['argument'].Value $inExampleCode = $false switch ($keyword) { 'SYNOPSIS' { $current = $synopsis } 'DESCRIPTION' { $current = $description } 'INPUTS' { $current = $inputs } 'OUTPUTS' { $current = $outputs } 'NOTES' { $current = $notes } 'PARAMETER' { $parameter = [ordered]@{ Name = $argument.Trim() Text = [System.Collections.Generic.List[string]]::new() } $parameters.Add($parameter) $current = $parameter.Text } 'EXAMPLE' { $example = [ordered]@{ Code = [System.Collections.Generic.List[string]]::new() Text = [System.Collections.Generic.List[string]]::new() } $examples.Add($example) $current = $example.Code $inExampleCode = $true $exampleIndent = ([regex]::Match($line, '^\s*')).Value.Length } 'LINK' { if ($argument) { $links.Add($argument.Trim()) } $current = $ignored } } continue } # The first blank line inside an example ends the code and starts the text. if ($inExampleCode -and $line.Trim().Length -eq 0) { $current = $example.Text $inExampleCode = $false continue } if ($inExampleCode) { $strip = $exampleIndent + 4 $codeLine = if ($line.Length -ge $strip) { $line.Substring($strip) } else { $line.TrimStart() } $current.Add($codeLine) } else { $current.Add($line.Trim()) } } $parameterRows = @($parameters | ForEach-Object { [pscustomobject]@{ Name = $_.Name Text = ConvertTo-CleanText -Text $_.Text } }) $exampleRows = @($examples | ForEach-Object { [pscustomobject]@{ Code = ConvertTo-CleanText -Text $_.Code Text = ConvertTo-CleanText -Text $_.Text } }) [PSCustomObject]@{ Found = [bool]$block Synopsis = ConvertTo-CleanText -Text $synopsis Description = ConvertTo-CleanText -Text $description Inputs = ConvertTo-CleanText -Text $inputs Outputs = ConvertTo-CleanText -Text $outputs Notes = ConvertTo-CleanText -Text $notes Links = $links.ToArray() Parameters = $parameterRows Examples = $exampleRows } } |