2024-08-07 17:55:23 +02:00
<#
. DESCRIPTION
2026-02-12 20:33:11 +00:00
Generates Hugo-compatible markdown files for the development documentation
based on config/tweaks.json and config/feature.json.
Each JSON entry gets its own .md file with the raw JSON snippet or PowerShell function embedded.
Called 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 {
<#
. SYNOPSIS
Extracts the raw JSON text for a specific item from a JSON file's lines.
Returns the line number and raw text, excluding the "link" property and closing brace.
#>
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 )
$startIndex = -1
$startIndent = ""
# Find the line containing "ItemName": {
for ( $i = 0 ; $i -lt $JsonLines . Count ; $i ++) {
if ( $JsonLines [ $i ] -match "^(\s*) `" $escapedName `" \s*:\s*\{" ) {
$startIndex = $i
$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
# Find the closing } at the same indentation level
$escapedIndent = [ regex ]:: Escape ( $startIndent )
$endIndex = -1
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-12 20:33:11 +00:00
# Walk backwards from closing brace to exclude "link" property and empty lines
$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
$rawLines = $JsonLines [ $startIndex .. $lastContentIndex ]
$rawText = $rawLines -join " `r`n "
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
return @ {
LineNumber = $startIndex + 1 # 1-based
RawText = $rawText
2024-08-07 17:55:23 +02:00
}
}
2026-02-12 20:33:11 +00:00
function Get-ButtonFunctionMapping {
<#
. SYNOPSIS
Parses Invoke-WPFButton.ps1 to build a hashtable mapping button names to function names.
#>
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 = @ {}
$lines = Get-Content -Path $ButtonFilePath
2024-08-07 17:55:23 +02:00
foreach ( $line in $lines ) {
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 {
<#
. SYNOPSIS
Updates the "link" property on each top-level entry in a JSON config file
to point to the corresponding documentation page URL.
#>
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-12 20:33:11 +00:00
$jsonText = Get-Content -Path $JsonFilePath -Raw
$jsonData = $jsonText | ConvertFrom-Json
foreach ( $item in $jsonData . PSObject . Properties ) {
$itemName = $item . Name
$itemDetails = $item . Value
$category = $itemDetails . category -replace '[^a-zA-Z0-9]' , '-'
$displayName = $itemName -replace $ItemNameToCut , ''
$docLink = " $UrlPrefix / $( $category . ToLower ()) / $( $displayName . ToLower ()) "
$itemDetails | Add-Member -NotePropertyName "link" -NotePropertyValue $docLink -Force
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
$jsonText = ( $jsonData | ConvertTo-Json -Depth 100 ). replace ( '\n' , " `n " ). replace ( '\r' , " `r " )
Set-Content -Path $JsonFilePath -Value $jsonText -Encoding utf8
2024-08-07 17:55:23 +02:00
}
2026-02-12 20:33:11 +00:00
# ==============================================================================
# Main Script
# ==============================================================================
# Use PSScriptRoot if available (running as a script file), otherwise assume CWD is tools/
$scriptDir = if ( $PSScriptRoot ) { $PSScriptRoot } else { ( Get-Location ). Path }
$repoRoot = Resolve-Path " $scriptDir /.."
# Paths
$tweaksJsonPath = " $repoRoot /config/tweaks.json"
$featuresJsonPath = " $repoRoot /config/feature.json"
$tweaksOutputDir = " $repoRoot /docs/content/dev/tweaks"
$featuresOutputDir = " $repoRoot /docs/content/dev/features"
$publicFunctionsDir = " $repoRoot /functions/public"
$privateFunctionsDir = " $repoRoot /functions/private"
$itemnametocut = 'WPF(WinUtil|Toggle|Features?|Tweaks?|Panel|Fix(es)?)?'
$baseUrl = "https://winutil.christitus.com"
# Categories that should have generated documentation
$documentedCategories = @ (
"Essential Tweaks" ,
"z__Advanced Tweaks - CAUTION" ,
"Customize Preferences" ,
"Performance Plans" ,
"Features" ,
"Fixes" ,
"Legacy Windows Panels"
)
# --- Load data ---
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Update-Progress "Loading JSON files" 10
$tweaks = Get-Content -Path $tweaksJsonPath -Raw | ConvertFrom-Json
$features = Get-Content -Path $featuresJsonPath -Raw | ConvertFrom-Json
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
# --- Load function files (content + relative path) ---
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
Update-Progress "Loading function files" 20
$functionFiles = @ {}
Get-ChildItem -Path $publicFunctionsDir -Filter *. ps1 | ForEach-Object {
$functionFiles [ $_ . BaseName ] = @ {
Content = ( Get-Content -Path $_ . FullName -Raw ). TrimEnd ()
RelativePath = "functions/public/ $( $_ . Name ) "
}
}
Get-ChildItem -Path $privateFunctionsDir -Filter *. ps1 | ForEach-Object {
$functionFiles [ $_ . BaseName ] = @ {
Content = ( Get-Content -Path $_ . FullName -Raw ). TrimEnd ()
RelativePath = "functions/private/ $( $_ . Name ) "
}
}
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
# --- Build button-to-function mapping ---
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 link attributes in JSON files ---
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
Add-LinkAttributeToJson -JsonFilePath $featuresJsonPath -UrlPrefix " $baseUrl /dev/features" -ItemNameToCut $itemnametocut
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
# Reload JSON lines after link update (so line numbers are accurate)
$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
# ==============================================================================
# Clean up old generated .md files (keep _index.md)
# ==============================================================================
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-12 20:33:11 +00:00
$tweakNames = $tweaks . PSObject . Properties . Name
$totalTweaks = $tweakNames . Count
$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-12 20:33:11 +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-12 20:33:11 +00:00
# Hugo frontmatter
$title = $item . Content -replace '"' , '\"'
$content = "--- `r`n title: `" $title `"`r`n description: `"`"`r`n --- `r`n "
if ( $item . Type -eq "Button" ) {
# Button-type tweak: embed the mapped PowerShell function
$funcName = $buttonFunctionMap [ $itemName ]
if ( $funcName -and $functionFiles . ContainsKey ( $funcName )) {
$func = $functionFiles [ $funcName ]
$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 {
# Standard tweak: embed raw JSON block
$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
# Registry Changes section
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
# Service function reference
if ( $item . service -and $functionFiles . ContainsKey ( "Set-WinUtilService" )) {
$svcFunc = $functionFiles [ "Set-WinUtilService" ]
$content += "#Function `r`n "
$content += " `````` powershell {filename= `" $( $svcFunc . RelativePath ) `" ,linenos=inline,linenostart=1} `r`n "
$content += $svcFunc . Content + " `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-12 20:33:11 +00:00
$percent = 50 + [ int ](( $tweakCount / $totalTweaks ) * 20 )
if ( $percent -gt 70 ) { $percent = 70 }
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-12 20:33:11 +00:00
$featureNames = $features . PSObject . Properties . Name
$totalFeatures = $featureNames . Count
$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 }
2024-08-07 17:55:23 +02:00
2026-02-12 20:33:11 +00:00
# Skip pure UI buttons that don't need docs
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-12 20:33:11 +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-12 20:33:11 +00:00
$title = $item . Content -replace '"' , '\"'
$content = "--- `r`n title: `" $title `"`r`n description: `"`"`r`n --- `r`n "
if ( $item . category -eq "Fixes" -or $item . category -eq "Legacy Windows Panels" ) {
# Embed the PowerShell function file
$funcName = $buttonFunctionMap [ $itemName ]
if ( $funcName -and $functionFiles . ContainsKey ( $funcName )) {
$func = $functionFiles [ $funcName ]
$content += " `````` powershell {filename= `" $( $func . RelativePath ) `" ,linenos=inline,linenostart=1} `r`n "
$content += $func . Content + " `r`n "
$content += " ```````r`n "
}
} else {
# Features category: embed raw JSON block
$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-12 20:33:11 +00:00
$percent = 70 + [ int ](( $featureCount / $totalFeatures ) * 20 )
if ( $percent -gt 90 ) { $percent = 90 }
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."