Public/Convert-CommentHelpToMarkdown.ps1
|
#!/usr/bin/env pwsh Set-StrictMode -Version Latest function Convert-CommentHelpToMarkdown { <# .SYNOPSIS Converts PowerShell comment-based help into Markdown documents. .DESCRIPTION Reads script files, extracts each comment-based help block, and writes one Markdown document per script. Sections are emitted in a stable order: - FUNCTION - .SYNOPSIS - .DESCRIPTION - .PARAMETERS - .EXAMPLES - .INPUTS - .OUTPUTS - .NOTES - .RELATED LINKS Empty sections are omitted. Each example renders as a fenced powershell code block followed by its explanation, and the code is dedented by the keyword indent plus four spaces so the block reads as runnable PowerShell. Only .ps1 files are processed. A directory under Path is searched recursively, so -Path .\ collects every script in the tree. Wildcards are also supported, for example .\Public\*.ps1. The parser recognizes the common help keywords: SYNOPSIS, DESCRIPTION, PARAMETER, EXAMPLE, INPUTS, OUTPUTS, NOTES, and LINK. A script without a recognizable help block is reported in Errors instead of being skipped silently, so a documentation run over a whole tree shows exactly which files still need help written. Each generated document overwrites an existing .md file with the same name. Returns one summary object with counts, per-file Results, and Errors, matching the module's Compress-FileTree report shape. A request that matches no scripts terminates with an error. .PARAMETER Path Script files or directories to convert. Directories are searched recursively and wildcards are supported, for example .\Public\*.ps1. Only .ps1 files are processed. .PARAMETER OutPath Directory for the generated .md files, created when missing. When omitted, each file is written beside its source. Existing documents with the same name are overwritten. .INPUTS None. You can't pipe objects to this function. .OUTPUTS System.Management.Automation.PSCustomObject. One object with TotalFiles, ConvertedFiles, FailedFiles, Results, and Errors. Results contains Path and OutputPath for every document that was written. Errors contains Path and Message for files with no comment-based help and for conversion or write failures. ConvertedFiles plus FailedFiles equals TotalFiles. .EXAMPLE Convert-CommentHelpToMarkdown -Path .\Public\Get-AclItem.ps1 Writes Get-AclItem.md next to the source file. .EXAMPLE $report = Convert-CommentHelpToMarkdown -Path .\Public\*.ps1 -OutPath .\Doc\Public $report.Results | Format-Table Path, OutputPath Generates one Markdown file per public function under .\Doc\Public and inspects the per-file records. .EXAMPLE $report = Convert-CommentHelpToMarkdown -Path .\ $report.Errors | Format-Table Path, Message Recurses the current tree for .ps1 files and lists the scripts that still need help written. .NOTES Output names come from the function declaration inside the script when one is present, and from the file name otherwise, so a help block that documents a single function is headed under that function's name. Files are read with -Raw and written as UTF-8. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory, HelpMessage = 'Enter the script files or directories to convert.')] [ValidateNotNullOrEmpty()] [string[]]$Path, [Parameter(HelpMessage = 'Enter the output directory. Defaults to beside each source file.')] [ValidateNotNullOrEmpty()] [string]$OutPath ) $resolvedFiles = @( foreach ($entry in $Path) { Get-ChildItem -Path $entry -Recurse -File -Filter '*.ps1' -ErrorAction SilentlyContinue } ) if ($resolvedFiles.Count -eq 0) { throw "No files matched: $($Path -join ', ')" } if ($PSBoundParameters.ContainsKey('OutPath') -and -not (Test-Path -LiteralPath $OutPath -PathType Container)) { [void](New-Item -Path $OutPath -ItemType Directory -Force) } $results = [System.Collections.Generic.List[PSCustomObject]]::new() $errors = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($file in $resolvedFiles) { try { $content = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction Stop $data = ConvertFrom-CommentHelp -Content $content if (-not $data.Found) { $errors.Add([PSCustomObject]@{ Path = $file.FullName Message = 'No comment-based help block was found in this file.' }) continue } # The function declaration wins over the file name so a help block that # documents one function is headed under that function's name. $name = $file.BaseName $functionMatch = [regex]::Match($content, '(?m)^\s*function\s+([\w-]+)') if ($functionMatch.Success) { $name = $functionMatch.Groups[1].Value } $markdown = Format-HelpMarkdown -Name $name -Data $data $outDirectory = if ($PSBoundParameters.ContainsKey('OutPath')) { $OutPath } else { $file.DirectoryName } $outFile = Join-Path -Path $outDirectory -ChildPath "$($file.BaseName).md" Set-Content -LiteralPath $outFile -Value $markdown -Encoding utf8 Write-Verbose -Message "Converted $($file.FullName) to $outFile" $results.Add([PSCustomObject]@{ Path = $file.FullName OutputPath = $outFile }) } catch { $errors.Add([PSCustomObject]@{ Path = $file.FullName Message = $_.Exception.Message }) } } [PSCustomObject]@{ TotalFiles = $resolvedFiles.Count ConvertedFiles = $results.Count FailedFiles = $errors.Count Results = $results.ToArray() Errors = $errors.ToArray() } } |