Last active
September 13, 2022 11:03
-
-
Save Arlodotexe/a35f87380205ebfd79cfe969c0226eeb to your computer and use it in GitHub Desktop.
PowerShell script to unflatten and nest the namespaces in a standard DocFx toc.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
##################################### | |
# About | |
##################################### | |
# This is a PowerShell port of Aaron Bernstein's NodeJS code by Arlo Godfrey. | |
# Unflattens and nests the namespaces in a standard DocFx toc.yaml | |
# Source: https://github.com/dotnet/docfx/issues/274#issuecomment-456168196 | |
# Last updated 5/14/2022. | |
# Licensed under MIT. | |
##################################### | |
##################################### | |
# Usage | |
##################################### | |
# docfx metadata ./path/to/docfx.json | |
# ./unflatten-namespaces.ps1 ./path/to/toc.yaml | |
# docfx build ./path/to/docfx.json | |
##################################### | |
##################################### | |
# Parameters | |
##################################### | |
param ($tocLocation) | |
$yamlModuleName = "powershell-yaml"; | |
##################################### | |
# Functions | |
##################################### | |
# LoadYml function that will read YML file and deserialize it | |
function LoadYml { | |
param ($FileName) | |
# Load file content to a string array containing all YML file lines | |
[string[]]$fileContent = Get-Content $FileName | |
$content = '' | |
# Convert a string array to a string | |
foreach ($line in $fileContent) { $content = $content + "`n" + $line } | |
# Deserialize a string to the PowerShell object | |
$toc = ConvertFrom-YAML $content -Ordered | |
# return the object | |
return $toc | |
} | |
# WriteYml function that writes the YML content to a file | |
function WriteYml { | |
param ($FileName, $Content) | |
#Serialize a PowerShell object to string | |
Write-Verbose "Serializing content to YAML." | |
$result = ConvertTo-YAML $Content | |
# Some YAML libraries improperly produce a MappingStart | |
# instead of a SequenceStart by excluding the leading "-" | |
# This fixes it with regex. | |
Write-Verbose "Fixing missing SequenceStart identifiers." | |
$result = $result -replace '(items:\s+) ([^-]+?:)', '$1- $2'; | |
#write to a file | |
Write-Verbose "Saving content to $FileName."; | |
Set-Content -Path $FileName -Value $result | |
} | |
##################################### | |
# YAML/Module Loading | |
##################################### | |
$isModuleInstalled = Get-InstalledModule $yamlModuleName -ErrorAction silentlycontinue; | |
if (-not $isModuleInstalled) { | |
Write-Verbose "Module $yamlModuleName is not installed, installing." | |
# Install and import the yaml reading module | |
# Install module has a -Force -Verbose -Scope CurrentUser arguments | |
# which might be necessary in your CI/CD environment to install the module | |
Install-PackageProvider -Name NuGet -Force -Scope CurrentUser | |
Install-Module -Name $yamlModuleName -Confirm:$False -Force -Verbose -Scope CurrentUser | |
Import-Module $yamlModuleName -Force -Scope Local | |
} | |
else { | |
Write-Verbose "Module $yamlModuleName is installed." | |
} | |
$tocExists = Test-Path -Path $tocLocation -PathType Leaf | |
if (-not $tocExists) { | |
Write-Output "toc.yml not found at $tocLocation." | |
Exit; | |
} | |
else { | |
Write-Verbose "Found toc.yml." | |
} | |
$toc = LoadYml $tocLocation; | |
##################################### | |
# Main code | |
##################################### | |
$namespaces = @{}; | |
for ($i = 0; $i -lt $toc.Length; $i++) { | |
$fullnamespace = $toc[$i].uid; | |
$splitnamespace = $fullnamespace.split('.'); | |
$parent = $namespaces; | |
for ($j = 0; $j -lt $splitnamespace.Length; $j++) { | |
$partialNamespace = $splitnamespace[$j]; | |
if ($null -eq $parent[$partialnamespace]) { | |
$parent[$partialnamespace] = @{}; | |
} | |
$parent = $parent[$partialnamespace]; | |
} | |
if ($null -eq $parent.items) { | |
$parent.items = $toc[$i].items; | |
} | |
else { | |
$parent.items.push($toc[$i]) | |
} | |
} | |
function recurse { | |
param ($obj, $path) | |
$items = @(); # Empty array | |
foreach ($key in $obj.Keys) { | |
$value = $obj[$key]; | |
if (!($key -eq "items")) { | |
$newPath = ""; | |
if ($null -eq $path) { | |
$newPath = $key; | |
} | |
else { | |
$newPath = $path + '.' + $key; | |
} | |
# null coalescing only available in powershell 7+ | |
if ($null -eq $value.items) { | |
$value.items = @(); | |
} | |
Write-Verbose "Processing $newPath"; | |
$newObj = [PSCustomObject]@{ | |
name = $newPath | |
uid = $newPath | |
items = $value.items | |
} | |
foreach ($recursedItem in recurse $value $newPath) { | |
$newObj.items = $newObj.items + $recursedItem; | |
} | |
$items = $items + $newObj; | |
} | |
} | |
return $items; | |
} | |
$result = recurse $namespaces; | |
Write-Verbose "Finished building nested namespace data." | |
WriteYml $tocLocation $result; | |
Write-Output "Done." |
Good catch @wcoder, I've updated it with a slightly cleaner fix. I wanted to use null coalescing but that's not available until PowerShell 7.
Thanks @wcoder, hadn't heard about Codefactor till now. I've fixed this issue + a few more 🙂
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In line 171:
items
in$newObj.items
can be nullhot fix:
Environment: