Auto-generate documentation for Wiki (#292)
This adds a new script utility, `Build-Wiki.ps1`, which can be used to auto-generate the documentation for the Wiki. This uses the [PlatyPS](https://github.com/PowerShell/PlatyPS) module to generate the actual help documentation. Relevant notes: * This will generate the `Home.md`, `_sidebar.md`, and `_footer.md` files for the Wiki if they don't already exist in the destination directory. * If they do already exist, it intelligently updates the content, only replacing the parts that it generated itself (denoted with comments in the markdown content itself). * At present, this is not being hooked-up to CI because I don't want to deal with storing credentials that have admin access to the actual repo. Tip of the hat to @X-Guardian for the suggestion and the initial help with writing this out. Resolves #208
This commit is contained in:
Родитель
35be16a9c4
Коммит
4379a46572
|
@ -0,0 +1,514 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Builds the markdown documentation for the module.
|
||||
|
||||
.DESCRIPTION
|
||||
Builds the markdown documentation for the module using the PlatyPS PowerShell module.
|
||||
|
||||
.PARAMETER Path
|
||||
Specifies the output path for the function markdown files.
|
||||
|
||||
.PARAMETER ModulePath
|
||||
Specifies the path of the module to generate the help for.
|
||||
|
||||
.PARAMETER ModuleName
|
||||
Specifies the name of the already loaded module to generate the help for.
|
||||
|
||||
.PARAMETER Description
|
||||
Specifies the description for the module.
|
||||
|
||||
.PARAMETER RemoveDeprecated
|
||||
Removes any files that were previously generated but were not generated during this update.
|
||||
Those files likely represent functions that were either renamed, removed or that stopped
|
||||
being exported.
|
||||
|
||||
.PARAMETER Force
|
||||
Indicates that this should overwrite existing files that have the same names.
|
||||
|
||||
.INPUTS
|
||||
None
|
||||
|
||||
.OUTPUTS
|
||||
None
|
||||
|
||||
.EXAMPLE
|
||||
Build-Wiki -Path './' -ModuleName 'PowerShellForGitHub' -RemoveDeprecated
|
||||
#>
|
||||
[CmdletBinding(DefaultParameterSetName='ModuleName')]
|
||||
param
|
||||
(
|
||||
[string] $Path = 'docs',
|
||||
|
||||
[Parameter(
|
||||
Mandatory,
|
||||
ParameterSetName='ModulePath')]
|
||||
[string] $ModulePath,
|
||||
|
||||
[Parameter(
|
||||
ParameterSetName='ModuleName')]
|
||||
[string] $ModuleName = 'PowerShellForGitHub',
|
||||
|
||||
[string] $Description = 'PowerShellForGitHub is a PowerShell module that provides command-line interaction and automation for the [GitHub v3 API](https://developer.github.com/v3/).',
|
||||
|
||||
[switch] $RemoveDeprecated,
|
||||
|
||||
[switch] $Force
|
||||
)
|
||||
|
||||
function Out-Utf8File
|
||||
{
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Writes a file using UTF8 (no BOM) encoding.
|
||||
|
||||
.PARAMETER Path
|
||||
The path to the file to write to.
|
||||
|
||||
.PARAMETER Content
|
||||
The string content for the file.
|
||||
|
||||
.PARAMETER Force
|
||||
Indicates that this should overwrite an existing file that has the same name.
|
||||
|
||||
.INPUTS
|
||||
String
|
||||
|
||||
.EXAMPLE
|
||||
Out-Utf8File -Path ./foo.txt -Content 'bar'
|
||||
Creates a 'foo.txt' in the current working directory with the content 'bar' as a UTF-8 file
|
||||
without BOM.
|
||||
|
||||
.NOTES
|
||||
This is being used because the PS5 Encoding options only include utf8 with BOM, and we
|
||||
want to write without BOM. This is fixed in PS6+, but we need to support PS4+.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Path,
|
||||
|
||||
[Parameter(ValueFromPipeline)]
|
||||
[string] $Content,
|
||||
|
||||
[switch] $Force
|
||||
)
|
||||
|
||||
begin
|
||||
{
|
||||
if (Test-Path -Path $Path -PathType Leaf)
|
||||
{
|
||||
if ($Force.IsPresent)
|
||||
{
|
||||
Remove-Item -Path $Path -Force | Out-Null
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "[$Path] already exists and -Force was not specified."
|
||||
}
|
||||
}
|
||||
|
||||
$stream = New-Object -TypeName System.IO.StreamWriter -ArgumentList ($Path, [System.Text.Encoding]::UTF8)
|
||||
}
|
||||
|
||||
process
|
||||
{
|
||||
|
||||
$stream.WriteLine($Content)
|
||||
}
|
||||
|
||||
end
|
||||
{
|
||||
$stream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
function Build-SideBar
|
||||
{
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Generate the sidebar content file.
|
||||
|
||||
.PARAMETER Path
|
||||
The path where the file should be written to.
|
||||
|
||||
.PARAMETER ModuleRootPageFileName
|
||||
The filename for the root of the module documentation.
|
||||
|
||||
.PARAMETER ModuleName
|
||||
The name of the module the documentation is for.
|
||||
|
||||
.PARAMETER ModulePages
|
||||
The names of the module pages that have been generated.
|
||||
|
||||
.EXAMPLE
|
||||
Build-SideBar -Path ./docs -ModuleRootPageFileName 'root.md' -ModuleName 'PowerShellForGitHub' -ModulePages @('Foo', 'Bar')
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Path,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $ModuleRootPageFileName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $ModuleName,
|
||||
|
||||
[string[]] $ModulePages
|
||||
)
|
||||
|
||||
$sideBarFilePath = Join-Path -Path $Path -ChildPath '_sidebar.md'
|
||||
|
||||
$moduleRootPageBaseName = $ModuleRootPageFileName.Substring(0, $ModuleRootPageFileName.lastIndexOf('.'))
|
||||
|
||||
$moduleContentStartMarker = '<!-- startDocs -->'
|
||||
$moduleContentEndMarker = '<!-- endDocs -->'
|
||||
$moduleContent = @()
|
||||
$moduleContent += $moduleContentStartMarker
|
||||
$moduleContent += '### Docs'
|
||||
$moduleContent += ''
|
||||
$moduleContent += "[$ModuleName]($moduleRootPageBaseName)"
|
||||
$moduleContent += ''
|
||||
$moduleContent += '#### Functions'
|
||||
$moduleContent += ''
|
||||
foreach ($modulePage in $modulePages)
|
||||
{
|
||||
$moduleContent += "- [$modulePage]($modulePage)"
|
||||
}
|
||||
$moduleContent += $moduleContentEndMarker
|
||||
$moduleContent += ''
|
||||
|
||||
$content = ''
|
||||
$docsSideBarRegEx = "$moduleContentStartMarker[\r\n]+(?:[^<]+[\r\n]+)*$moduleContentEndMarker[\r\n]+"
|
||||
if (Test-Path -Path $sideBarFilePath -PathType Leaf)
|
||||
{
|
||||
$content = Get-Content -Path $sideBarFilePath -Raw -Encoding utf8
|
||||
if ($content -match $docsSideBarRegEx)
|
||||
{
|
||||
$content = $content -replace $docsSideBarRegEx,($moduleContent -join [Environment]::NewLine)
|
||||
}
|
||||
else
|
||||
{
|
||||
$content += [Environment]::NewLine
|
||||
$content += ($moduleContent -join [Environment]::NewLine)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$newContent = @()
|
||||
$newContent += "## $ModuleName"
|
||||
$newContent += ''
|
||||
$newContent += $moduleContent
|
||||
$content = $newContent -join [Environment]::NewLine
|
||||
}
|
||||
|
||||
$content | Out-Utf8File -Path $sideBarFilePath -Force
|
||||
}
|
||||
|
||||
function Build-Footer
|
||||
{
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Generate the footer content file.
|
||||
|
||||
.PARAMETER Path
|
||||
The path where the file should be written to.
|
||||
|
||||
.PARAMETER ModuleRootPageFileName
|
||||
The filename for the root of the module documentation.
|
||||
|
||||
.PARAMETER ModuleName
|
||||
The name of the module the documentation is for.
|
||||
|
||||
.EXAMPLE
|
||||
Build-Footer -Path ./docs -ModuleRootPageFileName 'root.md' -ModuleName 'PowerShellForGitHub'
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Path,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $ModuleRootPageFileName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $ModuleName
|
||||
)
|
||||
|
||||
$footerFilePath = Join-Path -Path $Path -ChildPath '_footer.md'
|
||||
|
||||
$moduleRootPageBaseName = $ModuleRootPageFileName.Substring(0, $ModuleRootPageFileName.lastIndexOf('.'))
|
||||
|
||||
$moduleContentStartMarker = '<!-- startDocs -->'
|
||||
$moduleContentEndMarker = '<!-- endDocs -->'
|
||||
$moduleContent = @()
|
||||
$moduleContent += $moduleContentStartMarker
|
||||
$moduleContent += ''
|
||||
$moduleContent += "[Back to [$ModuleName]($moduleRootPageBaseName)]"
|
||||
$moduleContent += ''
|
||||
$moduleContent += $moduleContentEndMarker
|
||||
$moduleContent += ''
|
||||
|
||||
$content = ''
|
||||
$docsFooterRegEx = "$moduleContentStartMarker[\r\n]+(?:[^<]+[\r\n]+)*$moduleContentEndMarker[\r\n]+"
|
||||
if (Test-Path -Path $footerFilePath -PathType Leaf)
|
||||
{
|
||||
$content = Get-Content -Path $footerFilePath -Raw -Encoding utf8
|
||||
if ($content -match $docsFooterRegEx)
|
||||
{
|
||||
$content = $content -replace $docsFooterRegEx,($moduleContent -join [Environment]::NewLine)
|
||||
}
|
||||
else
|
||||
{
|
||||
$content += [Environment]::NewLine
|
||||
$content += ($moduleContent -join [Environment]::NewLine)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$content = ($moduleContent -join [Environment]::NewLine)
|
||||
}
|
||||
|
||||
$content | Out-Utf8File -Path $footerFilePath -Force
|
||||
}
|
||||
|
||||
function Build-HomePage
|
||||
{
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Generate the home page file for the Wiki.
|
||||
|
||||
.PARAMETER Path
|
||||
The path where the file should be written to.
|
||||
|
||||
.PARAMETER ModuleRootPageFileName
|
||||
The filename for the root of the module documentation.
|
||||
|
||||
.EXAMPLE
|
||||
Build-HomePage -Path ./docs -ModuleRootPageFileName 'root.md'
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Path,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $ModuleRootPageFileName
|
||||
)
|
||||
|
||||
$homePageFilePath = Join-Path -Path $Path -ChildPath 'Home.md'
|
||||
|
||||
$moduleRootPageBaseName = $ModuleRootPageFileName.Substring(0, $ModuleRootPageFileName.lastIndexOf('.'))
|
||||
|
||||
$moduleContentStartMarker = '<!-- startDocs -->'
|
||||
$moduleContentEndMarker = '<!-- endDocs -->'
|
||||
$moduleContent = @()
|
||||
$moduleContent += $moduleContentStartMarker
|
||||
$moduleContent += ''
|
||||
$moduleContent += "[Full Module Documentation]($moduleRootPageBaseName)"
|
||||
$moduleContent += ''
|
||||
$moduleContent += $moduleContentEndMarker
|
||||
$moduleContent += ''
|
||||
|
||||
$content = ''
|
||||
$docsFooterRegEx = "$moduleContentStartMarker[\r\n]+(?:[^<]+[\r\n]+)*$moduleContentEndMarker[\r\n]+"
|
||||
if (Test-Path -Path $homePageFilePath -PathType Leaf)
|
||||
{
|
||||
$content = Get-Content -Path $homePageFilePath -Raw -Encoding utf8
|
||||
if ($content -match $docsFooterRegEx)
|
||||
{
|
||||
$content = $content -replace $docsFooterRegEx,($moduleContent -join [Environment]::NewLine)
|
||||
}
|
||||
else
|
||||
{
|
||||
$content += [Environment]::NewLine
|
||||
$content += ($moduleContent -join [Environment]::NewLine)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$content = ($moduleContent -join [Environment]::NewLine)
|
||||
}
|
||||
|
||||
$content | Out-Utf8File -Path $homePageFilePath -Force
|
||||
}
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version 1.0
|
||||
|
||||
$Path = Resolve-Path -Path $Path
|
||||
|
||||
$numSteps = 11
|
||||
$currentStep = 0
|
||||
$progressParams = @{
|
||||
'Activity' = 'Generating documentation for wiki'
|
||||
'Id' = 1
|
||||
}
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status 'Ensuring PlatyPS installed' -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
if ($null -eq (Get-Module -Name 'PlatyPS'))
|
||||
{
|
||||
Write-Verbose -Message 'Installing PlatyPS Module'
|
||||
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force -Verbose:$false | Out-Null
|
||||
Install-Module PlatyPS -Scope CurrentUser -Force
|
||||
}
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status 'Ensuring source module is loaded' -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
if (-not [String]::IsNullOrEmpty($ModulePath))
|
||||
{
|
||||
Write-Verbose -Message "Importing [$ModulePath]"
|
||||
$module = Import-Module -Name $ModulePath -PassThru -Force -Verbose:$false
|
||||
$ModuleName = $module.Name
|
||||
}
|
||||
|
||||
$moduleRootPageFileName = "$ModuleName.md"
|
||||
|
||||
# We generate the files to a _temp_ directory so that we can determine if there have been any
|
||||
# files that should be _removed_ from the Wiki due to rename/removal of exports.
|
||||
$tempFolder = Join-Path -Path $env:TEMP -ChildPath ([Guid]::NewGuid().Guid)
|
||||
New-Item -Path $tempFolder -ItemType Directory | Out-Null
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status 'Creating the new module markdown help files' -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
|
||||
# The ModulePage is generated to the current working directory, so we need to be temporarily located
|
||||
# at the temp folder.
|
||||
Push-Location -Path $tempFolder
|
||||
|
||||
$params = @{
|
||||
Module = $ModuleName
|
||||
OutputFolder = $tempFolder
|
||||
UseFullTypeName = $true
|
||||
AlphabeticParamsOrder = $true
|
||||
WithModulePage = $true
|
||||
ModulePagePath = $moduleRootPageFileName
|
||||
NoMetadata = $false # Otherwise was having issues with Update-MarkdownHelpModule
|
||||
FwLink = 'N/A'
|
||||
Encoding = ([System.Text.Encoding]::UTF8)
|
||||
Force = $true
|
||||
}
|
||||
New-MarkdownHelp @params | Out-Null
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status 'Updating the generated documentation' -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
$params = @{
|
||||
Path = $tempFolder
|
||||
RefreshModulePage = $true
|
||||
ModulePagePath = $moduleRootPageFileName
|
||||
UseFullTypeName = $true
|
||||
AlphabeticParamsOrder = $true
|
||||
Encoding = ([System.Text.Encoding]::UTF8)
|
||||
}
|
||||
Update-MarkdownHelpModule @params | Out-Null
|
||||
|
||||
# The ModulePage is generated to the current working directory. Now that we're done generating,
|
||||
# let's go back to our original location
|
||||
Pop-Location
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status "Cleaning up content in $moduleRootPageFileName" -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
$moduleRootPageFilePath = Join-Path -Path $tempFolder -ChildPath $moduleRootPageFileName
|
||||
$moduleRootPageContent = Get-Content -Path $moduleRootPageFilePath -Raw -Encoding utf8
|
||||
$moduleRootPageContent = $moduleRootPageContent.Replace('.md)', ')')
|
||||
|
||||
$descriptionMarker = '{{ Fill in the Description }}'
|
||||
$moduleRootPageContent = $moduleRootPageContent.Replace($descriptionMarker, $Description)
|
||||
$moduleRootPageContent | Out-Utf8File -Path $moduleRootPageFilePath -Force | Out-Null
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status "Removing metadata from generated files" -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
$modulePages = @()
|
||||
$generatedFiles = Get-ChildItem -Path $tempFolder -Filter '*.md'
|
||||
$metadataRegEx = '^---[\r\n]+(?:[^-].+[\r\n]+){1,10}---[\r\n]{1,4}'
|
||||
$generatedMarker = '<!-- Generated -->' + [Environment]::NewLine
|
||||
foreach ($file in $generatedFiles)
|
||||
{
|
||||
$fileContent = Get-Content -Path $file.FullName -Raw -Encoding utf8
|
||||
if ($fileContent -match $metadataRegEx)
|
||||
{
|
||||
$fileContent = $fileContent -replace $metadataRegEx,$generatedMarker
|
||||
$fileContent | Out-Utf8File -Path $file.FullName -Force
|
||||
|
||||
if ($file.Name -ne $moduleRootPageFileName)
|
||||
{
|
||||
$modulePages += $file.BaseName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status "Updating sidebar" -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
Build-SideBar -Path $Path -ModuleRootPageFileName $moduleRootPageFileName -ModuleName $ModuleName -ModulePages $modulePages
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status "Updating footer" -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
Build-Footer -Path $Path -ModuleRootPageFileName $moduleRootPageFileName -ModuleName $ModuleName
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status "Updating home page" -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
Build-HomePage -Path $Path -ModuleRootPageFileName $moduleRootPageFileName
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status "Detecting deprecated pages" -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
$deprecatedFiles = @()
|
||||
$currentFiles = Get-ChildItem -Path $Path -Filter '*.md'
|
||||
foreach ($file in $currentFiles)
|
||||
{
|
||||
$content = Get-Content -Path $file -Raw -Encoding utf8
|
||||
if (($content -match $generatedMarker) -and
|
||||
($file.BaseName -notin $modulePages) -and
|
||||
($file.Name -ne $moduleRootPageFileName))
|
||||
{
|
||||
$deprecatedFiles += $file
|
||||
}
|
||||
}
|
||||
|
||||
if ($deprecatedFiles.Length -gt 0)
|
||||
{
|
||||
if ($RemoveDeprecated.IsPresent)
|
||||
{
|
||||
Write-Verbose "The following files have been deprecated and will be removed:"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Verbose "The following files have been deprecated. They can be removed automatically by specifying the -RemoveDeprecated switch."
|
||||
}
|
||||
|
||||
foreach ($file in $deprecatedFiles)
|
||||
{
|
||||
Write-Verbose "* $($file.Name)"
|
||||
if ($RemoveDeprecated.IsPresent)
|
||||
{
|
||||
Remove-Item -Path $file.FullName -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#######
|
||||
$currentStep++
|
||||
Write-Progress @progressParams -Status "Moving generated content to final destination" -PercentComplete (($currentStep / $numSteps) * 100)
|
||||
$files = Get-ChildItem -Path $tempFolder
|
||||
foreach ($file in $files)
|
||||
{
|
||||
Move-Item -Path $file -Destination $Path -Force:$Force.IsPresent
|
||||
}
|
||||
|
||||
Remove-Item -Path $tempFolder -Recurse -Force
|
||||
|
||||
#######
|
||||
Write-Progress @progressParams -Completed
|
Загрузка…
Ссылка в новой задаче