2024-08-07 17:55:23 +02:00
<#
. DESCRIPTION
2026-02-22 23:19:43 +00:00
Generates Hugo markdown docs from config/tweaks.json and config/feature.json.
Run by the GitHub Actions docs workflow before Hugo build.
2024-08-07 17:55:23 +02:00
#>
function Update-Progress {
param (
[ Parameter ( Mandatory , position = 0 )]
[ string ] $StatusMessage ,
[ Parameter ( Mandatory , position = 1 )]
[ ValidateRange ( 0 , 100 )]
2026-02-12 20:33:11 +00:00
[ int ] $Percent
2024-08-07 17:55:23 +02:00
)
2026-02-12 20:33:11 +00:00
Write-Progress -Activity "Generating Dev Docs" -Status $StatusMessage -PercentComplete $Percent
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
function Get-RawJsonBlock {
2026-02-22 23:19:43 +00:00
# Returns the raw JSON text and 1-based start line for an item, excluding the "link" property.
2024-08-07 17:55:23 +02:00
param (
2026-02-12 20:33:11 +00:00
[ Parameter ( Mandatory )]
[ string ] $ItemName ,
[ Parameter ( Mandatory )]
[ AllowEmptyString ()]
[ string[] ] $JsonLines
2024-08-07 17:55:23 +02:00
)
2026-02-12 20:33:11 +00:00
$escapedName = [ regex ]:: Escape ( $ItemName )
2026-02-22 23:19:43 +00:00
$startIndex = -1
2026-02-12 20:33:11 +00:00
$startIndent = ""
for ( $i = 0 ; $i -lt $JsonLines . Count ; $i ++) {
if ( $JsonLines [ $i ] -match "^(\s*) `" $escapedName `" \s*:\s*\{" ) {
2026-02-22 23:19:43 +00:00
$startIndex = $i
2026-02-12 20:33:11 +00:00
$startIndent = $matches [ 1 ]
break
}
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
if ( $startIndex -eq -1 ) {
Write-Warning "Could not find ' $ItemName ' in JSON"
return $null
}
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
$escapedIndent = [ regex ]:: Escape ( $startIndent )
2026-02-22 23:19:43 +00:00
$endIndex = -1
2026-02-12 20:33:11 +00:00
for ( $i = ( $startIndex + 1 ); $i -lt $JsonLines . Count ; $i ++) {
if ( $JsonLines [ $i ] -match "^ $escapedIndent \}" ) {
$endIndex = $i
break
}
}
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
if ( $endIndex -eq -1 ) {
Write-Warning "Could not find closing brace for ' $ItemName '"
return $null
}
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
# Strip trailing "link" property and blank lines before returning
2026-02-12 20:33:11 +00:00
$lastContentIndex = $endIndex - 1
while ( $lastContentIndex -gt $startIndex ) {
$trimmed = $JsonLines [ $lastContentIndex ]. Trim ()
if ( $trimmed -eq "" -or $trimmed -match '^"link"' ) {
$lastContentIndex --
} else {
break
2024-08-07 17:55:23 +02:00
}
}
2026-02-12 20:33:11 +00:00
return @ {
2026-02-22 23:19:43 +00:00
LineNumber = $startIndex + 1
RawText = ( $JsonLines [ $startIndex .. $lastContentIndex ] -join " `r`n " )
2024-08-07 17:55:23 +02:00
}
}
2026-02-12 20:33:11 +00:00
function Get-ButtonFunctionMapping {
2026-02-22 23:19:43 +00:00
# Parses Invoke-WPFButton.ps1 and returns a hashtable of button name -> function name.
2024-08-07 17:55:23 +02:00
param (
2026-02-12 20:33:11 +00:00
[ Parameter ( Mandatory )]
[ string ] $ButtonFilePath
2024-08-07 17:55:23 +02:00
)
2026-02-12 20:33:11 +00:00
$mapping = @ {}
2026-02-22 23:19:43 +00:00
foreach ( $line in ( Get-Content -Path $ButtonFilePath )) {
2026-02-12 20:33:11 +00:00
if ( $line -match '^\s*"(\w+)"\s*\{(Invoke-\w+)' ) {
$mapping [ $matches [ 1 ]] = $matches [ 2 ]
2024-08-07 17:55:23 +02:00
}
}
2026-02-12 20:33:11 +00:00
return $mapping
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
function Add-LinkAttributeToJson {
2026-02-22 23:19:43 +00:00
# Updates only the "link" property for each entry in a JSON config file.
# Reads via ConvertFrom-Json for metadata, then edits lines directly to avoid reformatting.
2024-08-07 17:55:23 +02:00
param (
[ Parameter ( Mandatory )]
2026-02-12 20:33:11 +00:00
[ string ] $JsonFilePath ,
[ Parameter ( Mandatory )]
[ string ] $UrlPrefix ,
[ Parameter ( Mandatory )]
[ string ] $ItemNameToCut
2024-08-07 17:55:23 +02:00
)
2026-02-22 23:19:43 +00:00
$jsonData = Get-Content -Path $JsonFilePath -Raw | ConvertFrom-Json
$lines = [ System.Collections.Generic.List[string] ]( Get-Content -Path $JsonFilePath )
2026-02-12 20:33:11 +00:00
foreach ( $item in $jsonData . PSObject . Properties ) {
2026-02-22 23:19:43 +00:00
$itemName = $item . Name
$category = $item . Value . category -replace '[^a-zA-Z0-9]' , '-'
2026-02-12 20:33:11 +00:00
$displayName = $itemName -replace $ItemNameToCut , ''
2026-02-22 23:19:43 +00:00
$newLink = " $UrlPrefix / $( $category . ToLower ()) / $( $displayName . ToLower ()) "
$escapedName = [ regex ]:: Escape ( $itemName )
# Find item start line
$startIdx = -1
for ( $i = 0 ; $i -lt $lines . Count ; $i ++) {
if ( $lines [ $i ] -match "^\s* `" $escapedName `" \s*:\s*\{" ) {
$startIdx = $i
break
}
}
if ( $startIdx -eq -1 ) { continue }
# Derive indentation: propIndent is one level deeper than the item start.
# Used to target only top-level properties and skip nested object braces.
$null = $lines [ $startIdx ] -match '^(\s*)'
$propIndent = $matches [ 1 ] + ' '
$propIndentLen = $propIndent . Length
$escapedPropIndent = [ regex ]:: Escape ( $propIndent )
# Scan forward: update existing "link" or find the closing brace to insert one.
# Closing brace is matched by indent <= propIndentLen to handle inconsistent formatting.
$linkUpdated = $false
$closeBraceIdx = -1
for ( $j = $startIdx + 1 ; $j -lt $lines . Count ; $j ++) {
if ( $lines [ $j ] -match "^ $escapedPropIndent `" link `" \s*:" ) {
$lines [ $j ] = $lines [ $j ] -replace '"link"\s*:\s*"[^"]*"' , " `" link `" : `" $newLink `" "
$linkUpdated = $true
break
}
if ( $lines [ $j ] -match '^\s*\}' ) {
$null = $lines [ $j ] -match '^(\s*)'
if ( $matches [ 1 ]. Length -le $propIndentLen ) {
$closeBraceIdx = $j
break
}
}
}
2026-02-12 20:33:11 +00:00
2026-02-22 23:19:43 +00:00
if ( -not $linkUpdated -and $closeBraceIdx -ne -1 ) {
# Insert "link" before the closing brace
$prevPropIdx = $closeBraceIdx - 1
while ( $prevPropIdx -gt $startIdx -and $lines [ $prevPropIdx ]. Trim () -eq '' ) { $prevPropIdx -- }
if ( $lines [ $prevPropIdx ] -notmatch ',\s*$' ) {
$lines [ $prevPropIdx ] = $lines [ $prevPropIdx ]. TrimEnd () + ','
}
$lines . Insert ( $closeBraceIdx , " $propIndent `" link `" : `" $newLink `" " )
}
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
2026-02-22 23:19:43 +00:00
Set-Content -Path $JsonFilePath -Value $lines -Encoding utf8
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
# ==============================================================================
2026-02-22 23:19:43 +00:00
# Main
2026-02-12 20:33:11 +00:00
# ==============================================================================
$scriptDir = if ( $PSScriptRoot ) { $PSScriptRoot } else { ( Get-Location ). Path }
2026-02-22 23:19:43 +00:00
$repoRoot = Resolve-Path " $scriptDir /.."
2026-02-12 20:33:11 +00:00
2026-02-22 23:19:43 +00:00
$tweaksJsonPath = " $repoRoot /config/tweaks.json"
$featuresJsonPath = " $repoRoot /config/feature.json"
$tweaksOutputDir = " $repoRoot /docs/content/dev/tweaks"
$featuresOutputDir = " $repoRoot /docs/content/dev/features"
2026-02-12 20:33:11 +00:00
$publicFunctionsDir = " $repoRoot /functions/public"
$privateFunctionsDir = " $repoRoot /functions/private"
$itemnametocut = 'WPF(WinUtil|Toggle|Features?|Tweaks?|Panel|Fix(es)?)?'
2026-02-22 23:19:43 +00:00
$baseUrl = "https://winutil.christitus.com"
2026-02-12 20:33:11 +00:00
2026-02-22 23:19:43 +00:00
# Categories with generated docs
2026-02-12 20:33:11 +00:00
$documentedCategories = @ (
"Essential Tweaks" ,
"z__Advanced Tweaks - CAUTION" ,
"Customize Preferences" ,
"Performance Plans" ,
"Features" ,
"Fixes" ,
2026-02-22 23:19:43 +00:00
"Legacy Windows Panels" ,
"Powershell Profile Powershell 7+ Only" ,
"Remote Access"
2026-02-12 20:33:11 +00:00
)
2026-02-22 23:19:43 +00:00
# Categories where Button entries embed a PS function instead of raw JSON
$functionEmbedCategories = @ (
"Fixes" ,
"Powershell Profile Powershell 7+ Only" ,
"Remote Access"
)
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Update-Progress "Loading JSON files" 10
2026-02-22 23:19:43 +00:00
$tweaks = Get-Content -Path $tweaksJsonPath -Raw | ConvertFrom-Json
2026-02-12 20:33:11 +00:00
$features = Get-Content -Path $featuresJsonPath -Raw | ConvertFrom-Json
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Update-Progress "Loading function files" 20
$functionFiles = @ {}
2026-02-22 23:19:43 +00:00
Get-ChildItem -Path $publicFunctionsDir -Filter *. ps1 | ForEach-Object {
$functionFiles [ $_ . BaseName ] = @ { Content = ( Get-Content -Path $_ . FullName -Raw ). TrimEnd (); RelativePath = "functions/public/ $( $_ . Name ) " }
2026-02-12 20:33:11 +00:00
}
Get-ChildItem -Path $privateFunctionsDir -Filter *. ps1 | ForEach-Object {
2026-02-22 23:19:43 +00:00
$functionFiles [ $_ . BaseName ] = @ { Content = ( Get-Content -Path $_ . FullName -Raw ). TrimEnd (); RelativePath = "functions/private/ $( $_ . Name ) " }
2026-02-12 20:33:11 +00:00
}
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Update-Progress "Building button-to-function mapping" 30
$buttonFunctionMap = Get-ButtonFunctionMapping -ButtonFilePath " $publicFunctionsDir /Invoke-WPFButton.ps1"
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Update-Progress "Updating documentation links in JSON" 40
Add-LinkAttributeToJson -JsonFilePath $tweaksJsonPath -UrlPrefix " $baseUrl /dev/tweaks" -ItemNameToCut $itemnametocut
2026-02-22 23:19:43 +00:00
Add-LinkAttributeToJson -JsonFilePath $featuresJsonPath -UrlPrefix " $baseUrl /dev/features" -ItemNameToCut $itemnametocut
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
# Reload lines after link update so line numbers in docs are accurate
2026-02-12 20:33:11 +00:00
$tweaksLines = Get-Content -Path $tweaksJsonPath
$featuresLines = Get-Content -Path $featuresJsonPath
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
# ==============================================================================
2026-02-22 23:19:43 +00:00
# Clean up old generated .md files (preserve _index.md)
2026-02-12 20:33:11 +00:00
# ==============================================================================
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Update-Progress "Cleaning up old generated docs" 45
foreach ( $dir in @ ( $tweaksOutputDir , $featuresOutputDir )) {
Get-ChildItem -Path $dir -Recurse -Filter *. md | Where-Object {
$_ . Name -ne "_index.md"
} | Remove-Item -Force
}
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
# ==============================================================================
# Generate Tweak Documentation
# ==============================================================================
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Update-Progress "Generating tweak documentation" 50
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
$tweakNames = $tweaks . PSObject . Properties . Name
2026-02-12 20:33:11 +00:00
$totalTweaks = $tweakNames . Count
2026-02-22 23:19:43 +00:00
$tweakCount = 0
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
foreach ( $itemName in $tweakNames ) {
$item = $tweaks . $itemName
$tweakCount ++
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
if ( $item . category -notin $documentedCategories ) { continue }
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
$category = $item . category -replace '[^a-zA-Z0-9]' , '-'
$displayName = $itemName -replace $itemnametocut , ''
$categoryDir = " $tweaksOutputDir / $category "
$filename = " $categoryDir / $displayName .md"
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
if ( -Not ( Test-Path -Path $categoryDir )) { New-Item -ItemType Directory -Path $categoryDir | Out-Null }
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
$title = $item . Content -replace '"' , '\"'
2026-02-17 18:28:36 +00:00
$content = "--- `r`n title: `" $title `"`r`n description: `"`"`r`n --- `r`n`r`n "
2026-02-12 20:33:11 +00:00
if ( $item . Type -eq "Button" ) {
$funcName = $buttonFunctionMap [ $itemName ]
if ( $funcName -and $functionFiles . ContainsKey ( $funcName )) {
2026-02-22 23:19:43 +00:00
$func = $functionFiles [ $funcName ]
2026-02-12 20:33:11 +00:00
$content += " `````` powershell {filename= `" $( $func . RelativePath ) `" ,linenos=inline,linenostart=1} `r`n "
$content += $func . Content + " `r`n "
$content += " ```````r`n "
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
} else {
$jsonBlock = Get-RawJsonBlock -ItemName $itemName -JsonLines $tweaksLines
if ( $jsonBlock ) {
$content += " `````` json {filename= `" config/tweaks.json `" ,linenos=inline,linenostart= $( $jsonBlock . LineNumber ) } `r`n "
$content += $jsonBlock . RawText + " `r`n "
$content += " ```````r`n "
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
if ( $item . registry ) {
$content += " `r`n ## Registry Changes `r`n`r`n "
$content += "Applications and System Components store and retrieve configuration data to modify windows settings, so we can use the registry to change many settings in one place. `r`n`r`n "
$content += "You can find information about the registry on [Wikipedia](https://www.wikiwand.com/en/Windows_Registry) and [Microsoft's Website](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry). `r`n "
2024-08-07 17:55:23 +02:00
}
}
2026-02-12 20:33:11 +00:00
Set-Content -Path $filename -Value $content -Encoding utf8 -NoNewline
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
$percent = [ Math ]:: Min ( 70 , 50 + [ int ](( $tweakCount / $totalTweaks ) * 20 ))
2026-02-12 20:33:11 +00:00
Update-Progress "Generating tweak documentation ( $tweakCount / $totalTweaks )" $percent
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
# ==============================================================================
# Generate Feature Documentation
# ==============================================================================
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Update-Progress "Generating feature documentation" 70
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
$featureNames = $features . PSObject . Properties . Name
2026-02-12 20:33:11 +00:00
$totalFeatures = $featureNames . Count
2026-02-22 23:19:43 +00:00
$featureCount = 0
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
foreach ( $itemName in $featureNames ) {
$item = $features . $itemName
$featureCount ++
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
if ( $item . category -notin $documentedCategories ) { continue }
if ( $itemName -eq "WPFFeatureInstall" ) { continue }
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
$category = $item . category -replace '[^a-zA-Z0-9]' , '-'
$displayName = $itemName -replace $itemnametocut , ''
$categoryDir = " $featuresOutputDir / $category "
$filename = " $categoryDir / $displayName .md"
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
if ( -Not ( Test-Path -Path $categoryDir )) { New-Item -ItemType Directory -Path $categoryDir | Out-Null }
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
$title = $item . Content -replace '"' , '\"'
2026-02-17 18:28:36 +00:00
$content = "--- `r`n title: `" $title `"`r`n description: `"`"`r`n --- `r`n`r`n "
2026-02-12 20:33:11 +00:00
2026-02-22 23:19:43 +00:00
if ( $item . category -in $functionEmbedCategories ) {
$funcName = if ( $item . function ) { $item . function } else { $buttonFunctionMap [ $itemName ] }
2026-02-12 20:33:11 +00:00
if ( $funcName -and $functionFiles . ContainsKey ( $funcName )) {
2026-02-22 23:19:43 +00:00
$func = $functionFiles [ $funcName ]
2026-02-12 20:33:11 +00:00
$content += " `````` powershell {filename= `" $( $func . RelativePath ) `" ,linenos=inline,linenostart=1} `r`n "
$content += $func . Content + " `r`n "
$content += " ```````r`n "
}
} else {
$jsonBlock = Get-RawJsonBlock -ItemName $itemName -JsonLines $featuresLines
if ( $jsonBlock ) {
$content += " `````` json {filename= `" config/feature.json `" ,linenos=inline,linenostart= $( $jsonBlock . LineNumber ) } `r`n "
$content += $jsonBlock . RawText + " `r`n "
$content += " ```````r`n "
}
}
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Set-Content -Path $filename -Value $content -Encoding utf8 -NoNewline
2024-08-07 17:55:23 +02:00
2026-02-22 23:19:43 +00:00
$percent = [ Math ]:: Min ( 90 , 70 + [ int ](( $featureCount / $totalFeatures ) * 20 ))
2026-02-12 20:33:11 +00:00
Update-Progress "Generating feature documentation ( $featureCount / $totalFeatures )" $percent
2024-08-07 17:55:23 +02:00
}
Update-Progress "Process Completed" 100
2026-02-12 20:33:11 +00:00
Write-Host "Documentation generation complete."