more winutil website update and new features (#4030)

* updated theme to the latest version

* Update en.yaml

* removed all the order from the docs

* add the store link

* update homepage

* add filename and line number to all the code blocks and also removed pages

* auto pull the code from the json files

* Merge branch 'main' into winutil-new-features

* the script updated linenostart=

* Delete LaptopHibernation.md
This commit is contained in:
Sean (ANGRYxScotsman)
2026-02-12 20:33:11 +00:00
committed by GitHub
parent 9d841687e3
commit 9b03b94435
94 changed files with 1046 additions and 963 deletions

View File

@@ -1,644 +1,347 @@
<#
.DESCRIPTION
This script generates markdown files for the development documentation based on the existing JSON files.
Create table of content and archive any files in the dev folder not modified by this script.
This script is not meant to be used manually, it is called by the github action workflow.
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.
#>
function Process-MultilineStrings {
param (
[Parameter(Mandatory, position=0)]
[string]$str
)
$lines = $str.Split("`r`n")
$count = $lines.Count
# Loop through every line, expect last line in the string
# We'll add it after the for loop
for ($i = 0; $i -lt ($count - 1); $i++) {
$line = $lines[$i]
$processedStr += $line -replace ('^\s*\\\\', '')
# Add the previously removed NewLine character by 'Split' Method
$processedStr += "`r`n"
}
# Add last line *without* a NewLine character.
$processedStr += $lines[$($count - 1)] -replace ('^\s*\\\\', '')
return $processedStr
}
function Update-Progress {
param (
[Parameter(Mandatory, position=0)]
[string]$StatusMessage,
[Parameter(Mandatory, position=1)]
[ValidateRange(0,100)]
[int]$Percent,
[Parameter(position=2)]
[string]$Activity = "Compiling"
[int]$Percent
)
Write-Progress -Activity $Activity -Status $StatusMessage -PercentComplete $Percent
Write-Progress -Activity "Generating Dev Docs" -Status $StatusMessage -PercentComplete $Percent
}
function Load-Functions {
param (
[Parameter(Mandatory, position=0)]
[string]$dir
)
Get-ChildItem -Path $dir -Filter *.ps1 | ForEach-Object {
$functionName = $_.BaseName
$functionContent = Get-Content -Path $_.FullName -Raw
$functions[$functionName] = $functionContent
}
}
function Get-CalledFunctions {
param (
[Parameter(Mandatory, position=0)]
$scriptContent,
[Parameter(Mandatory, position=1)]
[hashtable]$functionList,
[Parameter(Mandatory, position=2)]
[ref]$processedFunctions
)
$calledFunctions = @()
foreach ($functionName in $functionList.Keys) {
if ($scriptContent -match "\b$functionName\b" -and -not $processedFunctions.Value.Contains($functionName)) {
$calledFunctions += $functionName
$processedFunctions.Value.Add($functionName)
if ($functionList[$functionName]) {
$nestedFunctions = Get-CalledFunctions -scriptContent $functionList[$functionName] -functionList $functionList -processedFunctions $processedFunctions
$calledFunctions += $nestedFunctions
}
}
}
return $calledFunctions
}
function Get-AdditionalFunctionsFromToggle {
param (
[Parameter(Mandatory, position=0)]
[string]$buttonName
)
$invokeWpfToggleContent = Get-Content -Path "$publicFunctionsDir/Invoke-WPFToggle.ps1" -Raw
$lines = $invokeWpfToggleContent -split "`r`n"
foreach ($line in $lines) {
if ($line -match "`"$buttonName`" \{Invoke-(WinUtil[a-zA-Z]+)") {
return $matches[1]
}
}
}
function Get-AdditionalFunctionsFromButton {
param (
[Parameter(Mandatory, position=0)]
[string]$buttonName
)
$invokeWpfButtonContent = Get-Content -Path "$publicFunctionsDir/Invoke-WPFButton.ps1" -Raw
$lines = $invokeWpfButtonContent -split "`r`n"
foreach ($line in $lines) {
if ($line -match "`"$buttonName`" \{Invoke-(WPF[a-zA-Z]+)") {
return $matches[1]
}
}
}
function Add-LinkAttribute {
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.
#>
param (
[Parameter(Mandatory)]
[PSCustomObject]$jsonObject
[string]$ItemName,
[Parameter(Mandatory)]
[AllowEmptyString()]
[string[]]$JsonLines
)
$totalProperties = ($jsonObject.PSObject.Properties | Measure-Object).Count
$progressIncrement = 50 / $totalProperties
$currentProgress = 50
$escapedName = [regex]::Escape($ItemName)
$startIndex = -1
$startIndent = ""
foreach ($property in $jsonObject.PSObject.Properties) {
if ($property.Value -is [PSCustomObject]) {
Add-LinkAttribute -jsonObject $property.Value
} elseif ($property.Value -is [System.Collections.ArrayList]) {
foreach ($item in $property.Value) {
if ($item -is [PSCustomObject]) {
Add-LinkAttribute -jsonObject $item
}
}
# 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
}
$currentProgress += $progressIncrement
$roundedProgress = [math]::Round($currentProgress)
Update-Progress -StatusMessage "Adding documentation links" -Percent $roundedProgress
}
if ($jsonObject -ne $global:rootObject) {
$jsonObject | Add-Member -NotePropertyName "link" -NotePropertyValue "" -Force
if ($startIndex -eq -1) {
Write-Warning "Could not find '$ItemName' in JSON"
return $null
}
# 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
}
}
if ($endIndex -eq -1) {
Write-Warning "Could not find closing brace for '$ItemName'"
return $null
}
# 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
}
}
$rawLines = $JsonLines[$startIndex..$lastContentIndex]
$rawText = $rawLines -join "`r`n"
return @{
LineNumber = $startIndex + 1 # 1-based
RawText = $rawText
}
}
function Generate-MarkdownFiles {
function Get-ButtonFunctionMapping {
<#
.SYNOPSIS
Parses Invoke-WPFButton.ps1 to build a hashtable mapping button names to function names.
#>
param (
[Parameter(Mandatory, position=0)]
[PSCustomObject]$data,
[Parameter(Mandatory, position=1)]
[string]$outputDir,
[Parameter(Mandatory, position=2)]
[string]$jsonFilePath,
[Parameter(Mandatory, position=3)]
[string]$lastModified,
[Parameter(Mandatory, position=4)]
[string]$type,
[Parameter(position=5)]
[int]$initialProgress
[Parameter(Mandatory)]
[string]$ButtonFilePath
)
# TODO: Make the function reference generation better by making a Graph, so it highlights
# Which function "depends" on which, and makes it clearer on a high-level for the reader
# to understand the general structure.
$totalItems = ($data.PSObject.Properties | Measure-Object).Count
$progressIncrement = 10 / $totalItems
$currentProgress = [int]$initialProgress
$tocEntries = @()
$processedFiles = @()
foreach ($itemName in $data.PSObject.Properties.Name) {
# Create Category Directory if needed.
$itemDetails = $data.$itemName
$category = $itemDetails.category -replace '[^a-zA-Z0-9]', '-'
$categoryDir = "$outputDir/$category"
if (-Not (Test-Path -Path $categoryDir)) {
New-Item -ItemType Directory -Path $categoryDir | Out-Null
}
# Create empty files with correct path
$fullItemName = $itemName
$displayName = $itemName -replace $itemnametocut, ''
$filename = "$categoryDir/$displayName.md"
$relativePath = "$outputDir/$category/$displayName.md" -replace '^docs/', ''
if (-Not (Test-Path -Path $filename)) {
Set-Content -Path $filename -Value "" -Encoding utf8
}
# Add the entry to 'tocEntries' so we can generate Table Of Content easily
# And add the Full FileName of entry
$tocEntries += @{
Category = $category
Path = $relativePath
Name = $itemDetails.Content
Type = $type
}
$processedFiles += (Get-Item $filename).FullName
$header = "# $([string]$itemDetails.Content)" + "`r`n"
$lastUpdatedNotice = "Last Updated: $lastModified" + "`r`n"
$autoupdatenotice = Process-MultilineStrings @"
\\!!! info
\\ The Development Documentation is auto generated for every compilation of WinUtil, meaning a part of it will always stay up-to-date. **Developers do have the ability to add custom content, which won't be updated automatically.**
"@
$description = Process-MultilineStrings @"
\\## Description
\\
\\$([string]$itemDetails.Description)
"@
$jsonContent = ($itemDetails | ConvertTo-Json -Depth 10).replace('\n',"`n").replace('\r', "`r")
$codeBlock = Process-MultilineStrings @"
\\<details>
\\<summary>Preview Code</summary>
\\
\\``````json
\\$jsonContent
\\``````
\\
\\</details>
"@
# Clear the variable before continuing, will cause problems otherwise
$FeaturesDocs = ""
if ($itemDetails.feature) {
$FeaturesDocs += Process-MultilineStrings @"
\\## Features
\\
\\
\\Optional Windows Features are additional functionalities or components in the Windows operating system that users can choose to enable or disable based on their specific needs and preferences.
\\
\\
\\You can find information about Optional Windows Features on [Microsoft's Website for Optional Features](https://learn.microsoft.com/en-us/windows/client-management/client-tools/add-remove-hide-features?pivots=windows-11).
\\
\\
"@
if (($itemDetails.feature).Count -gt 1) {
$FeaturesDocs += "### Features to install" + "`r`n"
} else {
$FeaturesDocs += "### Feature to install" + "`r`n"
}
foreach ($feature in $itemDetails.feature) {
$FeaturesDocs += "- $($feature)" + "`r`n"
}
}
# Clear the variable before continuing, will cause problems otherwise
$InvokeScript = ""
if ($itemDetails.InvokeScript) {
$InvokeScriptContent = $itemDetails.InvokeScript | Out-String
$InvokeScript = Process-MultilineStrings @"
\\## Invoke Script
\\
\\``````powershell
\\$InvokeScriptContent
\\``````
"@
}
# Clear the variable before continuing, will cause problems otherwise
$UndoScript = ""
if ($itemDetails.UndoScript) {
$UndoScriptContent = $itemDetails.UndoScript | Out-String
$UndoScript = Process-MultilineStrings @"
\\## Undo Script
\\
\\``````powershell
\\$UndoScriptContent
\\``````
"@
}
# Clear the variable before continuing, will cause problems otherwise
$ToggleScript = ""
if ($itemDetails.ToggleScript) {
$ToggleScriptContent = $itemDetails.ToggleScript | Out-String
$ToggleScript = Process-MultilineStrings @"
\\## Toggle Script
\\
\\``````powershell
\\$ToggleScriptContent
\\``````
"@
}
# Clear the variable before continuing, will cause problems otherwise
$ButtonScript = ""
if ($itemDetails.ButtonScript) {
$ButtonScriptContent = $itemDetails.ButtonScript | Out-String
$ButtonScript = Process-MultilineStrings @"
\\## Button Script
\\
\\``````powershell
\\$ButtonScriptContent
\\``````
"@
}
# Clear the variable before continuing, will cause problems otherwise
$FunctionDetails = ""
$processedFunctions = New-Object 'System.Collections.Generic.HashSet[System.String]'
$allScripts = @($itemDetails.InvokeScript, $itemDetails.UndoScript, $itemDetails.ToggleScript, $itemDetails.ButtonScript)
foreach ($script in $allScripts) {
if ($script) {
$calledFunctions = Get-CalledFunctions -scriptContent $script -functionList $functions -processedFunctions ([ref]$processedFunctions)
foreach ($functionName in $calledFunctions) {
if ($functions.ContainsKey($functionName)) {
$FunctionDetails += Process-MultilineStrings @"
\\## Function: $functionName
\\
\\``````powershell
\\$($functions[$functionName])
\\``````
\\
"@
}
}
}
}
$additionalFunctionToggle = Get-AdditionalFunctionsFromToggle -buttonName $fullItemName
if ($additionalFunctionToggle) {
$additionalFunctionNameToggle = "Invoke-$additionalFunctionToggle"
if ($functions.ContainsKey($additionalFunctionNameToggle) -and -not $processedFunctions.Contains($additionalFunctionNameToggle)) {
$FunctionDetails += Process-MultilineStrings @"
\\## Function: $additionalFunctionNameToggle
\\
\\``````powershell
\\$($functions[$additionalFunctionNameToggle])
\\``````
\\
"@
$processedFunctions.Add($additionalFunctionNameToggle)
}
}
$additionalFunctionButton = Get-AdditionalFunctionsFromButton -buttonName $fullItemName
if ($additionalFunctionButton) {
$additionalFunctionNameButton = "Invoke-$additionalFunctionButton"
if ($functions.ContainsKey($additionalFunctionNameButton) -and -not $processedFunctions.Contains($additionalFunctionNameButton)) {
$FunctionDetails += Process-MultilineStrings @"
\\## Function: $additionalFunctionNameButton
\\
\\``````powershell
\\$($functions[$additionalFunctionNameButton])
\\``````
\\
"@
$processedFunctions.Add($additionalFunctionNameButton)
}
}
# Clear the variable before continuing, will cause problems otherwise
$registryDocs = ""
if ($itemDetails.registry) {
$registryDocs += Process-MultilineStrings @"
\\## Registry Changes
\\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.
\\
\\
\\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).
\\
\\
"@
foreach ($regEntry in $itemDetails.registry) {
$registryDocs += Process-MultilineStrings @"
\\### Registry Key: $($regEntry.Name)
\\
\\**Type:** $($regEntry.Type)
\\
\\**Original Value:** $($regEntry.OriginalValue)
\\
\\**New Value:** $($regEntry.Value)
\\
\\
"@
}
}
# Clear the variable before continuing, will cause problems otherwise
$serviceDocs = ""
if ($itemDetails.service) {
$serviceDocs = Process-MultilineStrings @"
\\## Service Changes
\\
\\Windows services are background processes for system functions or applications. Setting some to manual optimizes performance by starting them only when needed.
\\
\\You can find information about services on [Wikipedia](https://www.wikiwand.com/en/Windows_service) and [Microsoft's Website](https://learn.microsoft.com/en-us/dotnet/framework/windows-services/introduction-to-windows-service-applications).
\\
\\
"@
foreach ($service in $itemDetails.service) {
$serviceDocs += Process-MultilineStrings @"
\\### Service Name: $($service.Name)
\\
\\**Startup Type:** $($service.StartupType)
\\
\\**Original Type:** $($service.OriginalType)
\\
\\
"@
}
}
# Clear the variable before continuing, will cause problems otherwise
$scheduledTaskDocs = ""
if ($itemDetails.ScheduledTask) {
$scheduledTaskDocs = Process-MultilineStrings @"
\\## Scheduled Task Changes
\\
\\Windows scheduled tasks are used to run scripts or programs at specific times or events. Disabling unnecessary tasks can improve system performance and reduce unwanted background activity.
\\
\\
\\You can find information about scheduled tasks on [Wikipedia](https://www.wikiwand.com/en/Windows_Task_Scheduler) and [Microsoft's Website](https://learn.microsoft.com/en-us/windows/desktop/taskschd/about-the-task-scheduler).
\\
\\
"@
foreach ($task in $itemDetails.ScheduledTask) {
$scheduledTaskDocs += Process-MultilineStrings @"
\\### Task Name: $($task.Name)
\\
\\**State:** $($task.State)
\\
\\**Original State:** $($task.OriginalState)
\\
\\
"@
}
}
$jsonLink = "[View the JSON file](https://github.com/ChrisTitusTech/winutil/tree/main/$jsonFilePath)"
$customContentStartTag = "<!-- BEGIN CUSTOM CONTENT -->"
$customContentEndTag = "<!-- END CUSTOM CONTENT -->"
$secondCustomContentStartTag = "<!-- BEGIN SECOND CUSTOM CONTENT -->"
$secondCustomContentEndTag = "<!-- END SECOND CUSTOM CONTENT -->"
if (Test-Path -Path "$filename") {
$existingContent = Get-Content -Path "$filename" -Raw
$customContentPattern = "(?s)$customContentStartTag(.*?)$customContentEndTag"
$secondCustomContentPattern = "(?s)$secondCustomContentStartTag(.*?)$secondCustomContentEndTag"
if ($existingContent -match $customContentPattern) {
$customContent = $matches[1].Trim()
}
if ($existingContent -match $secondCustomContentPattern) {
$secondCustomContent = $matches[1].Trim()
}
}
$fileContent = Process-MultilineStrings @"
\\$header
\\$lastUpdatedNotice
\\
\\$autoupdatenotice
\\$( if ($itemDetails.Description) { $description } )
\\
\\$customContentStartTag
\\$customContent
\\$customContentEndTag
\\
\\$codeBlock
\\
\\$(
if ($FeaturesDocs) { $FeaturesDocs + "`r`n" }
if ($itemDetails.InvokeScript) { $InvokeScript + "`r`n" }
if ($itemDetails.UndoScript) { $UndoScript + "`r`n" }
if ($itemDetails.ToggleScript) { $ToggleScript + "`r`n" }
if ($itemDetails.ButtonScript) { $ButtonScript + "`r`n" }
if ($FunctionDetails) { $FunctionDetails + "`r`n" }
if ($itemDetails.registry) { $registryDocs + "`r`n" }
if ($itemDetails.service) { $serviceDocs + "`r`n" }
if ($itemDetails.ScheduledTask) { $scheduledTaskDocs + "`r`n" }
)
\\$secondCustomContentStartTag
\\$secondCustomContent
\\$secondCustomContentEndTag
\\
\\
\\$jsonLink
"@
Set-Content -Path "$filename" -Value "$fileContent" -Encoding utf8
# TODO: For whatever reason, some headers have a space before them,
# so as a temporary fix.. we'll remove these it so mkdocs can render properly
(Get-Content -Raw -Path "$filename").Replace(' ##', '##') | Set-Content "$filename"
$currentProgress += $progressIncrement
$roundedProgress = [math]::Round($currentProgress)
Update-Progress -StatusMessage "Generating content for documentation" -Percent $roundedProgress
}
return [PSCustomObject]@{
TocEntries = $tocEntries
ProcessedFiles = $processedFiles
}
}
function Generate-TypeSectionContent {
param (
[array]$entries
)
$totalEntries = $entries.Count
$progressIncrement = 10 / $totalEntries
$currentProgress = 90
$sectionContent = ""
$categories = @{}
foreach ($entry in $entries) {
if (-Not $categories.ContainsKey($entry.Category)) {
$categories[$entry.Category] = @()
}
$categories[$entry.Category] += $entry
$currentProgress += $progressIncrement
$roundedProgress = [math]::Round($currentProgress)
Update-Progress -StatusMessage "Generating table of contents" -Percent $roundedProgress
}
foreach ($category in $categories.Keys) {
$sectionContent += "### $category`r`n`r`n"
foreach ($entry in $categories[$category]) {
$sectionContent += "- [$($entry.Name)]($($entry.Path))`r`n"
$mapping = @{}
$lines = Get-Content -Path $ButtonFilePath
foreach ($line in $lines) {
if ($line -match '^\s*"(\w+)"\s*\{(Invoke-\w+)') {
$mapping[$matches[1]] = $matches[2]
}
}
return $sectionContent
return $mapping
}
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.
#>
param (
[string]$jsonFilePath,
[string]$outputDir
[Parameter(Mandatory)]
[string]$JsonFilePath,
[Parameter(Mandatory)]
[string]$UrlPrefix,
[Parameter(Mandatory)]
[string]$ItemNameToCut
)
$jsonText = Get-Content -Path $jsonFilePath -Raw
$jsonText = Get-Content -Path $JsonFilePath -Raw
$jsonData = $jsonText | ConvertFrom-Json
$totalItems = ($jsonData.PSObject.Properties | Measure-Object).Count
$progressIncrement = 20 / $totalItems
$currentProgress = 70
foreach ($item in $jsonData.PSObject.Properties) {
$itemName = $item.Name
$itemDetails = $item.Value
$category = $itemDetails.category -replace '[^a-zA-Z0-9]', '-'
$displayName = $itemName -replace "$itemnametocut", ''
$relativePath = "$outputDir/$category/$displayName" -replace '^docs/', ''
$docLink = "https://christitustech.github.io/winutil/$relativePath"
$jsonData.$itemName.link = $docLink
$displayName = $itemName -replace $ItemNameToCut, ''
$docLink = "$UrlPrefix/$($category.ToLower())/$($displayName.ToLower())"
$currentProgress += $progressIncrement
$roundedProgress = [math]::Round($currentProgress)
Update-Progress -StatusMessage "Adding documentation links to JSON" -Percent $roundedProgress
$itemDetails | Add-Member -NotePropertyName "link" -NotePropertyValue $docLink -Force
}
$jsonText = ($jsonData | ConvertTo-Json -Depth 10).replace('\n',"`n").replace('\r', "`r")
Set-Content -Path $jsonFilePath -Value ($jsonText) -Encoding utf8
$jsonText = ($jsonData | ConvertTo-Json -Depth 100).replace('\n', "`n").replace('\r', "`r")
Set-Content -Path $JsonFilePath -Value $jsonText -Encoding utf8
}
# ==============================================================================
# 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 ---
Update-Progress "Loading JSON files" 10
$tweaks = Get-Content -Path "../config/tweaks.json" | ConvertFrom-Json
$features = Get-Content -Path "../config/feature.json" | ConvertFrom-Json
$tweaks = Get-Content -Path $tweaksJsonPath -Raw | ConvertFrom-Json
$features = Get-Content -Path $featuresJsonPath -Raw | ConvertFrom-Json
Update-Progress "Getting last modified dates of the JSON files" 20
$tweaksLastModified = (Get-Item "../config/tweaks.json").LastWriteTime.ToString("yyyy-MM-dd")
$featuresLastModified = (Get-Item "../config/feature.json").LastWriteTime.ToString("yyyy-MM-dd")
# --- Load function files (content + relative path) ---
$tweaksOutputDir = "../docs/dev/tweaks"
$featuresOutputDir = "../docs/dev/features"
$privateFunctionsDir = "../functions/private"
$publicFunctionsDir = "../functions/public"
$functions = @{}
$itemnametocut = "WPF(WinUtil|Toggle|Features?|Tweaks?|Panel|Fix(es)?)?"
Update-Progress "Creating Directories" 30
if (-Not (Test-Path -Path $tweaksOutputDir)) {
New-Item -ItemType Directory -Path $tweaksOutputDir | Out-Null
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)"
}
}
if (-Not (Test-Path -Path $featuresOutputDir)) {
New-Item -ItemType Directory -Path $featuresOutputDir | Out-Null
Get-ChildItem -Path $privateFunctionsDir -Filter *.ps1 | ForEach-Object {
$functionFiles[$_.BaseName] = @{
Content = (Get-Content -Path $_.FullName -Raw).TrimEnd()
RelativePath = "functions/private/$($_.Name)"
}
}
Update-Progress "Loading existing Functions" 40
Load-Functions -dir $privateFunctionsDir
Load-Functions -dir $publicFunctionsDir
# --- Build button-to-function mapping ---
Update-Progress "Adding documentation links to JSON files" 50
Update-Progress "Building button-to-function mapping" 30
$buttonFunctionMap = Get-ButtonFunctionMapping -ButtonFilePath "$publicFunctionsDir/Invoke-WPFButton.ps1"
# Define the JSON file paths
$jsonPaths = @("../config/feature.json", "../config/tweaks.json")
# --- Update link attributes in JSON files ---
# Loop through each JSON file path
foreach ($jsonPath in $jsonPaths) {
# Load the JSON content
$json = Get-Content -Raw -Path $jsonPath | ConvertFrom-Json
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
# Set the global root object to the current json object
$global:rootObject = $json
# Reload JSON lines after link update (so line numbers are accurate)
$tweaksLines = Get-Content -Path $tweaksJsonPath
$featuresLines = Get-Content -Path $featuresJsonPath
# Add the "link" attribute to the JSON
Add-LinkAttribute -jsonObject $json
# ==============================================================================
# Clean up old generated .md files (keep _index.md)
# ==============================================================================
# Convert back to JSON with the original formatting
$jsonString = ($json | ConvertTo-Json -Depth 100).replace('\n',"`n").replace('\r', "`r")
# Save the JSON back to the file
Set-Content -Path $jsonPath -Value $jsonString
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
}
Add-LinkAttributeToJson -jsonFilePath "../config/tweaks.json" -outputDir "dev/tweaks"
Add-LinkAttributeToJson -jsonFilePath "../config/feature.json" -outputDir "dev/features"
# ==============================================================================
# Generate Tweak Documentation
# ==============================================================================
Update-Progress "Generating content for documentation" 60
$tweakResult = Generate-MarkdownFiles -data $tweaks -outputDir $tweaksOutputDir -jsonFilePath "../config/tweaks.json" -lastModified $tweaksLastModified -type "tweak" -initialProgress 60
$featureResult = Generate-MarkdownFiles -data $features -outputDir $featuresOutputDir -jsonFilePath "../config/feature.json" -lastModified $featuresLastModified -type "feature" -initialProgress 70
Update-Progress "Generating tweak documentation" 50
Update-Progress "Generating table of contents" 80
$allTocEntries = $tweakResult.TocEntries + $featureResult.TocEntries
$tweakEntries = ($allTocEntries).where{ $_.Type -eq 'tweak' } | Sort-Object Category, Name
$featureEntries = ($allTocEntries).where{ $_.Type -eq 'feature' } | Sort-Object Category, Name
$tweakNames = $tweaks.PSObject.Properties.Name
$totalTweaks = $tweakNames.Count
$tweakCount = 0
$indexContent += Process-MultilineStrings @"
\\# Table of Contents
\\
\\
\\## Tweaks
\\
\\
"@
$indexContent += $(Generate-TypeSectionContent $tweakEntries) + "`r`n"
$indexContent += Process-MultilineStrings @"
\\## Features
\\
\\
"@
$indexContent += $(Generate-TypeSectionContent $featureEntries) + "`r`n"
Set-Content -Path "../docs/devdocs.md" -Value $indexContent -Encoding utf8
foreach ($itemName in $tweakNames) {
$item = $tweaks.$itemName
$tweakCount++
if ($item.category -notin $documentedCategories) { continue }
$category = $item.category -replace '[^a-zA-Z0-9]', '-'
$displayName = $itemName -replace $itemnametocut, ''
$categoryDir = "$tweaksOutputDir/$category"
$filename = "$categoryDir/$displayName.md"
if (-Not (Test-Path -Path $categoryDir)) {
New-Item -ItemType Directory -Path $categoryDir | Out-Null
}
# Hugo frontmatter
$title = $item.Content -replace '"', '\"'
$content = "---`r`ntitle: `"$title`"`r`ndescription: `"`"`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"
}
} 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"
}
# 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"
}
# 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"
}
}
Set-Content -Path $filename -Value $content -Encoding utf8 -NoNewline
$percent = 50 + [int](($tweakCount / $totalTweaks) * 20)
if ($percent -gt 70) { $percent = 70 }
Update-Progress "Generating tweak documentation ($tweakCount/$totalTweaks)" $percent
}
# ==============================================================================
# Generate Feature Documentation
# ==============================================================================
Update-Progress "Generating feature documentation" 70
$featureNames = $features.PSObject.Properties.Name
$totalFeatures = $featureNames.Count
$featureCount = 0
foreach ($itemName in $featureNames) {
$item = $features.$itemName
$featureCount++
if ($item.category -notin $documentedCategories) { continue }
# Skip pure UI buttons that don't need docs
if ($itemName -eq "WPFFeatureInstall") { continue }
$category = $item.category -replace '[^a-zA-Z0-9]', '-'
$displayName = $itemName -replace $itemnametocut, ''
$categoryDir = "$featuresOutputDir/$category"
$filename = "$categoryDir/$displayName.md"
if (-Not (Test-Path -Path $categoryDir)) {
New-Item -ItemType Directory -Path $categoryDir | Out-Null
}
$title = $item.Content -replace '"', '\"'
$content = "---`r`ntitle: `"$title`"`r`ndescription: `"`"`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"
}
}
Set-Content -Path $filename -Value $content -Encoding utf8 -NoNewline
$percent = 70 + [int](($featureCount / $totalFeatures) * 20)
if ($percent -gt 90) { $percent = 90 }
Update-Progress "Generating feature documentation ($featureCount/$totalFeatures)" $percent
}
Update-Progress "Process Completed" 100
Write-Host "Documentation generation complete."