Skip to content

Instantly share code, notes, and snippets.

@Arlodotexe
Last active September 13, 2022 11:03
Show Gist options
  • Save Arlodotexe/a35f87380205ebfd79cfe969c0226eeb to your computer and use it in GitHub Desktop.
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
#####################################
# 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."
@Arlodotexe
Copy link
Author

Arlodotexe commented Jan 19, 2022

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.

@wcoder
Copy link

wcoder commented Jan 19, 2022

Codefactor found an issue:
Screen Shot 2022-01-20 at 12 09 24 AM

@Arlodotexe
Copy link
Author

Arlodotexe commented Jan 19, 2022

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