Alt3.Docusaurus.Powershell.psm1

#Region 'PREFIX' 0
Set-StrictMode -Version Latest
$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' # full stop on first error
#EndRegion 'PREFIX'
#Region '.\Private\GetCustomEditUrl.ps1' 0
function GetCustomEditUrl() {
    <#
        .SYNOPSIS
            Returns the `custom_edit_url` for the given .md file.
 
        .DESCRIPTION
            Generates a URL pointing to the Powershell source file that was used to generate the markdown file.
 
        .NOTES
            - URLs for non-monolithic modules point to a .ps1 file with same name as the markdown file
            - URLs for monolithic modules will always point to a .psm1 with same name as passed module
    #>

    param(
        [Parameter(Mandatory = $True)][string]$Module,
        [Parameter(Mandatory = $True)][System.IO.FileSystemInfo]$MarkdownFile,
        [Parameter(Mandatory = $False)][string]$EditUrl,
        [switch]$Monolithic
    )

    # return "false" so Docusaurus will not render the `Edit this page` button
    if (-not($EditUrl)) {
        return
    }

    # point to the function source file for non-monlithic modules
    if (-not($Monolithic)) {
        $command = [System.IO.Path]::GetFileNameWithoutExtension($MarkdownFile)

        return $EditUrl + '/' + $command + ".ps1"
    }

    # point to the module source file for monolithic modules
    if (Test-Path $Module) {
        $Module = [System.IO.Path]::GetFileNameWithoutExtension($Module)
    }

    return $EditUrl + '/' + $Module + ".psm1"
}
#EndRegion '.\Private\GetCustomEditUrl.ps1' 38
#Region '.\Private\GetMdxFilePath.ps1' 0
function GetMdxFilePath() {
    <#
        .SYNOPSIS
            Returns the .mdx file path for a given .md file.
    #>

    param(
        [Parameter(Mandatory = $True)][System.IO.FileSystemInfo]$MarkdownFile
    )

    Join-Path $MarkdownFile.DirectoryName -ChildPath "$([System.IO.Path]::GetFileNameWithoutExtension($MarkdownFile.Name)).mdx"
}
#EndRegion '.\Private\GetMdxFilePath.ps1' 11
#Region '.\Private\NewSidebarIncludeFile.ps1' 0
function NewSidebarIncludeFile() {
    <#
        .SYNOPSIS
            Generates a `.js` file holding an array with all .mdx 'ids` to be imported in Docusaurus `sidebar.js`.
 
        .LINK
            https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-powershell-1.0/ff730948(v=technet.10)
    #>

    param(
        [Parameter(Mandatory = $True)][string]$OutputFolder,
        [Parameter(Mandatory = $True)][string]$Sidebar,
        [Parameter(Mandatory = $True)][Object]$MarkdownFiles
    )

    # generate a list of Powershell commands by stripping .md from the generated PlatyPs files
    [array]$commands = $MarkdownFiles | Select-Object @{ Name = "PowershellCommand"; Expression={ "'$Sidebar/" + [System.IO.Path]::GetFileNameWithoutExtension($_) + "'" } } | Select-Object  -Expand PowershellCommand

    # generate content using Here-String block
$content = @"
/**
 * Import this file in your Docusaurus ``sidebars.js`` file.
 *
 * Auto-generated by Alt3.Powershell.Docusaurus.
 *
 * Copyright (c) 2019-present, ALT3 B.V.
 *
 * Licensed under the MIT license.
 */
 
module.exports = [
    $($commands -Join ",`n ")
];
"@


    # generate file path, convert relative outputfolder to absolute if needed
    if (-Not([System.IO.Path]::IsPathRooted($OutputFolder))) {
        $outputFolder = Join-Path "$(Get-Location)" -ChildPath $OutputFolder
    }

    $filePath = Join-Path -Path $OutputFolder -ChildPath "docusaurus.sidebar.js"

    # create the file
    $fileEncoding = New-Object System.Text.UTF8Encoding $False
    [System.IO.File]::WriteAllLines($filePath, $content, $fileEncoding)

    # add created file to output
    Get-Item $filePath
}
#EndRegion '.\Private\NewSidebarIncludeFile.ps1' 48
#Region '.\Private\RemoveMarkdownHeaderOne.ps1' 0
function RemoveMarkdownHeaderOne() {
    <#
        .SYNOPSIS
        Removes the H1 element from the markdown content because Docusaurus already generates H1 using the `title` front matter.
    #>

    param(
        [Parameter(Mandatory = $True)][System.IO.FileSystemInfo]$MarkdownFile
    )

    $content = (Get-Content -Path $MarkdownFile.FullName -Raw).TrimEnd()

    $regex = '\n#{1}\s.+\n\r'

    $newContent = [regex]::replace($content, $regex, '')

    # replace file (UTF-8 without BOM)
    $fileEncoding = New-Object System.Text.UTF8Encoding $False
    [System.IO.File]::WriteAllLines($markdownFile.FullName, $newContent, $fileEncoding)
}
#EndRegion '.\Private\RemoveMarkdownHeaderOne.ps1' 19
#Region '.\Private\SetMarkdownFrontMatter.ps1' 0
function SetMarkdownFrontMatter() {
    <#
        .SYNOPSIS
            Replaces PlatyPS generated front matter with Docusaurus compatible front matter.
 
        .LINK
            https://www.apharmony.com/software-sagacity/2014/08/multi-line-regular-expression-replace-in-powershell/
    #>

    param(
        [Parameter(Mandatory = $True)][System.IO.FileSystemInfo]$MarkdownFile,
        [Parameter(Mandatory = $False)][string]$CustomEditUrl,
        [Parameter(Mandatory = $False)][string]$MetaDescription,
        [Parameter(Mandatory = $False)][array]$MetaKeywords
    )

    $powershellCommandName = [System.IO.Path]::GetFileNameWithoutExtension($markdownFile.Name)

    # prepare front matter
    $newFrontMatter = [System.Collections.ArrayList]::new()
    $newFrontMatter.Add("---") | Out-Null
    $newFrontMatter.Add("id: $($powershellCommandName)") | Out-Null
    $newFrontMatter.Add("title: $($powershellCommandName)") | Out-Null

    if ($MetaDescription) {
        $description = [regex]::replace($MetaDescription, '%1', $powershellCommandName)
        $newFrontMatter.Add("description: $($description)") | Out-Null
    }

    if ($MetaKeywords) {
        $newFrontMatter.Add("keywords:") | Out-Null
        $MetaKeywords | ForEach-Object {
            $newFrontMatter.Add(" - $($_)") | Out-Null
        }
    }

    if ($EditUrl) {
        $newFrontMatter.Add("custom_edit_url: $($EditUrl)") | Out-Null
    }

    $newFrontMatter.Add("---") | Out-Null

    # replace front matter
    $content = (Get-Content -Path $MarkdownFile.FullName -Raw).TrimEnd()

    $regex = "(?sm)^(---)(.+)^(---).$\n"

    $newContent = $content -replace $regex, ($newFrontMatter | Out-String)

    # replace file (UTF-8 without BOM)
    $fileEncoding = New-Object System.Text.UTF8Encoding $False
    [System.IO.File]::WriteAllLines($markdownFile.FullName, $newContent, $fileEncoding)
}
#EndRegion '.\Private\SetMarkdownFrontMatter.ps1' 52
#Region '.\Private\UpdateMarkdownBackticks.ps1' 0
function UpdateMarkdownBackticks() {
    <#
        .SYNOPSIS
        Replaces platyPS-produced "escaped backticks" with normal backticks so markdown gets rendered as expected.
    #>

    param(
        [Parameter(Mandatory = $True)][System.IO.FileSystemInfo]$MarkdownFile
    )

    $content = (Get-Content -Path $MarkdownFile.FullName -Raw).TrimEnd()

    $regex = '\\`'

    $newContent = [regex]::replace($content, $regex, '`')

    # replace file (UTF-8 without BOM)
    $fileEncoding = New-Object System.Text.UTF8Encoding $False
    [System.IO.File]::WriteAllLines($markdownFile.FullName, $newContent, $fileEncoding)
}
#EndRegion '.\Private\UpdateMarkdownBackticks.ps1' 19
#Region '.\Private\UpdateMarkdownCodeBlocks.ps1' 0
function UpdateMarkdownCodeBlocks() {
    <#
        .SYNOPSIS
        Add `powershell` syntax highlighting to generated code blocks.
 
        .NOTES
        1. unfortunately we need to do this because PlatyPS does not add the language (design choice)
        2. @todo change regex so it will match on \n as well (now only works on CRLF files)
    #>

    param(
        [Parameter(Mandatory = $True)][System.IO.FileSystemInfo]$MarkdownFile
    )

    $content = (Get-Content -Path $MarkdownFile.FullName -Raw).TrimEnd()

    # this regex replaces all code blocks without a language (test on https://regex101.com using /$regex/g)
    $regex = '(```)\r((?:(?!```)[\s\S])+)(```)\r'

    $newContent = [regex]::replace($content, $regex, '```powershell$2```')

    # replace file (UTF-8 without BOM)
    $fileEncoding = New-Object System.Text.UTF8Encoding $False
    [System.IO.File]::WriteAllLines($markdownFile.FullName, $newContent, $fileEncoding)
}
#EndRegion '.\Private\UpdateMarkdownCodeBlocks.ps1' 24
#Region '.\Public\New-DocusaurusHelp.ps1' 0
function New-DocusaurusHelp() {
    <#
        .SYNOPSIS
            Generates Get-Help documentation in Docusaurus compatible `.mdx` format.
 
        .DESCRIPTION
            The `New-DocusaurusHelp` cmdlet generates Get-Help documentation in Docusaurus
            compatible format by creating an `.mdx` file for each command exported by
            the module, enriched with command-specific front matter variables.
 
            Also creates a `sidebar.js` file for simplified integration into the Docusaurus sidebar menu.
 
        .OUTPUTS
            System.Object
 
        .EXAMPLE
            New-DocusaurusHelp -Module Alt3.Docusaurus.Powershell -EditUrl "https://github.com/alt3/Docusaurus.Powershell/edit/master/source/Public"
 
        .PARAMETER Module
            Specifies the module this cmdlet will generate Docusaurus documentation for.
 
            You may specify a module name, a `.psd1` file or a `.psm1` file.
 
        .PARAMETER OutputFolder
            Specifies the folder where the Docusaurus sidebar subfolder will be created.
 
            Optional, defaults to `docusaurus/docs`, case sensitive.
 
        .PARAMETER Sidebar
            Specifies the subfolder where the Get-Help `.mdx` files for the module will be created.
 
            Optional, defaults to `CmdLets`, case sensitive.
 
        .PARAMETER EditUrl
            Specifies the URL prefixed to all Docusaurus `custom_edit_url` front matter variables.
 
            Optional, defaults to `null`.
 
        .PARAMETER Exclude
            Optional array with command names to exclude.
 
        .PARAMETER MetaDescription
            Optional string that will be inserted into Docusaurus front matter to be used as html meta tag 'description'.
 
            If placeholder `%1` is detected in the string, it will be replaced by the command name.
 
        .PARAMETER MetaKeywords
            Optional array of keywords inserted into Docusaurus front matter to be used as html meta tag `keywords`.
 
        .PARAMETER Monolithic
            Use this optional argument if the Powershell module source is monolithic.
 
            Will point all `custom_edit_url` front matter variables to the `.psm1` file.
 
        .NOTES
            Please note that Docusaurus v2 is an early and alpha version, just like this module.
 
        .LINK
            https://v2.docusaurus.io/
 
        .LINK
            https://github.com/PowerShell/platyPS
    #>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $True)][string]$Module,
        [Parameter(Mandatory = $False)][string]$OutputFolder = "docusaurus/docs",
        [Parameter(Mandatory = $False)][string]$Sidebar = "CmdLets",
        [Parameter(Mandatory = $False)][string]$EditUrl,
        [Parameter(Mandatory = $False)][array]$Exclude = @(),
        [Parameter(Mandatory = $False)][string]$MetaDescription,
        [Parameter(Mandatory = $False)][array]$MetaKeywords = @(),
        [switch]$Monolithic
    )

    # make sure the passed module is valid
    if (Test-Path($Module)) {
        Import-Module $Module -Force -Global
        $Module = [System.IO.Path]::GetFileNameWithoutExtension($Module)
    }

    if (-Not(Get-Module -Name $Module)) {
        $Module = $Module
        throw "New-DocusaurusHelp: Specified module '$Module' is not loaded"
    }

    # markdown for the module will be isolated in a subfolder
    $markdownFolder = Join-Path -Path $OutputFolder -ChildPath $Sidebar

    # generate PlatyPs markdown files
    New-MarkdownHelp -Module $Module -OutputFolder $markdownFolder -Force | Out-Null

    # remove excluded files
    $Exclude | ForEach-Object {
        Remove-Item -Path (Join-Path -Path $markdownFolder -ChildPath "$($_).md")
    }

    # process remaining files
    $markdownFiles = Get-ChildItem -Path $markdownFolder -Filter *.md

    # update generated markdown file(s) to make them Docusaurus compatible
    ForEach ($markdownFile in $markdownFiles) {
        $customEditUrl = GetCustomEditUrl -Module $Module -MarkdownFile $markdownFile -EditUrl $EditUrl -Monolithic:$Monolithic

        SetMarkdownFrontMatter -MarkdownFile $markdownFile -CustomEditUrl $customEditUrl -MetaDescription $MetaDescription -MetaKeywords $MetaKeywords
        RemoveMarkdownHeaderOne -MarkdownFile $markdownFile
        UpdateMarkdownCodeBlocks -MarkdownFile $markdownFile
        UpdateMarkdownBackticks -MarkdownFile $markdownFile

        # rename to .mdx
        $mdxFilePath = GetMdxFilePath -MarkdownFile $markdownFile
        Move-Item -Path $markdownFile.FullName -Destination $mdxFilePath -Force | Out-Null

        # output .mdx item so end-user can post-process files as they see fit
        Get-Item $mdxFilePath
    }

    # generate the `.js` file used for the docusaurus sidebar
    NewSidebarIncludeFile -MarkdownFiles $markdownFiles -OutputFolder $markdownFolder -Sidebar $Sidebar
}
#EndRegion '.\Public\New-DocusaurusHelp.ps1' 120