Skip to content

Commit bef6476

Browse files
committed
format-oneline
1 parent 9646aa7 commit bef6476

File tree

3 files changed

+243
-0
lines changed

3 files changed

+243
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@{
2+
RootModule = '.\Format-OneLine.psm1'
3+
ModuleVersion = '0.1.0'
4+
GUID = 'd46a00ff-0741-4278-85ae-6a71e9aecf14'
5+
Author = 'Martin C Zarate (AKA Pxtl)'
6+
Copyright = '2025, Martin C Zarate'
7+
Description = 'Simple module providing a function Format-OneLine that converts
8+
objects into semicolon-delimited lists of key=value pairs on a single line.'
9+
10+
# Minimum version of the Windows PowerShell engine required by this module
11+
PowerShellVersion = '5.1'
12+
PrivateData = @{
13+
PSData = @{
14+
LicenseUri = 'https://github.com/Pxtl/powershell-modules?tab=MIT-1-ov-file'
15+
ProjectUri = 'https://github.com/Pxtl/powershell-modules'
16+
# IconUri = ''
17+
ReleaseNotes = @'
18+
0.1.0
19+
- Initial Version
20+
'@
21+
} # End of PSData hashtable
22+
} # End of PrivateData hashtable
23+
HelpInfoURI = 'https://github.com/Pxtl/powershell-modules'
24+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
function Format-OneLine {
2+
<#
3+
.SYNOPSIS
4+
Format the given object as a single-line string.
5+
6+
.DESCRIPTION
7+
Format the given object as a single-line string. Will recurse into
8+
objects up to the given depth. Special exception for PSVariables, they
9+
will render even at depth 0, allowing. Only outputs on pipeline-end
10+
because it uses a pipeline-accumulator hack. This allows you to pipe
11+
multiple objects into Format-OneLine and have them all output as a
12+
single shared line. See example for the usefulness of these foibles WRT
13+
Get-Variable.
14+
15+
.EXAMPLE
16+
```powershell
17+
$foo = 'bar'; $baz = 'quux'
18+
Get-Variable foo, baz | Format-OneLine
19+
# returns "foo: bar; baz: quux"
20+
```
21+
#>
22+
[OutputType([string])]
23+
[CmdletBinding()]
24+
param (
25+
[Parameter(ValueFromPipeline, Position = 0)]
26+
[object] $InputObject,
27+
28+
# Depth to search to. Note that PSVariables are still iterated at depth 0, so that a pipeline of GetVariables
29+
[int] $Depth = 1,
30+
31+
# Include the key/value pairs for null or otherwise empty objects.
32+
[switch] $ShowEmpty,
33+
34+
# Wrap nested HashTables/PSObjects in `{}`
35+
[switch] $DoBraceNestedTables
36+
)
37+
begin {
38+
$accumulator = [Collections.ArrayList]::new()
39+
}
40+
process {
41+
$accumulator.Add($InputObject) | Out-Null
42+
}
43+
end {
44+
$obj = if ($accumulator.Count -gt 1) {
45+
$accumulator
46+
} else {
47+
# unwrap single-element ArrayList.
48+
$accumulator | Select-Object -First 1
49+
}
50+
Format-OneLineRecursive $obj $Depth $Depth -ShowEmpty:$ShowEmpty -DoBraceNestedTables:$DoBraceNestedTables
51+
}
52+
}
53+
54+
55+
Export-ModuleMember -Function *
56+
57+
58+
# private function below
59+
function Format-OneLineRecursive {
60+
<#
61+
.SYNOPSIS
62+
Recursive internal implementation of Format-OneLine with extra needed parameter(s).
63+
#>
64+
[OutputType([string])]
65+
[CmdletBinding()]
66+
param (
67+
[Parameter(ValueFromPipeline, Position = 0)]
68+
[object] $InputObject,
69+
70+
# Initial search depth. Used to compare against current depth for DoBraceNestedTables.
71+
[Parameter(Position = 1)]
72+
[int] $StartingDepth = 1,
73+
74+
# Current depth. Note that PSVariables are still iterated at depth 0, so that a pipeline of GetVariables
75+
[Parameter(Position = 2)]
76+
[int] $CurrentDepth = $StartingDepth,
77+
78+
# Include the key/value pairs for null or otherwise empty objects.
79+
[switch] $ShowEmpty,
80+
81+
# Wrap nested HashTables/PSObjects in `{}`
82+
[switch] $DoBraceNestedTables
83+
)
84+
begin {
85+
$paramTable = @{StartingDepth=$StartingDepth; ShowEmpty=$ShowEmpty; DoBraceNestedTables=$DoBraceNestedTables}
86+
}
87+
process {
88+
$resultList = [Collections.ArrayList]::new()
89+
foreach ($item in $InputObject) {
90+
Write-Verbose "Object '$item', CurrentDepth '$CurrentDepth',"
91+
# if the input object is a dictionary, convert it to a PSCustomObject so we can use its properties.
92+
# this is useful for converting HashTables to objects.
93+
if ($item -is [System.Collections.IDictionary]) {
94+
$item = [PSCustomObject]$item
95+
}
96+
97+
$result = if ($null -eq $item) {
98+
$null
99+
} elseif ($item -is [Management.Automation.PSVariable] -and $CurrentDepth -ge 0) {
100+
"$($item.Name): $(Format-OneLineRecursive $item.Value -CurrentDepth($CurrentDepth - 1) @paramTable)"
101+
} elseif ($CurrentDepth -le 0) {
102+
# anything recursive above this line must have a depth-check to prevent infinite recursion.
103+
$item.ToString()
104+
} elseif ($item -is [Collections.IEnumerable]) {
105+
# if it's an IEnumerable, use its items
106+
($item |
107+
ForEach-Object {
108+
if ($ShowEmpty -or $_) {
109+
Format-OneLineRecursive $_ -CurrentDepth ($CurrentDepth - 1) @paramTable
110+
}
111+
}
112+
) -join '; '
113+
} elseif ($item -is [Management.Automation.PSObject]) {
114+
# if it's a PSObject, use its properties
115+
$psObjectRendered = ($item.PSObject.Properties |
116+
ForEach-Object {
117+
if ($ShowEmpty -or $_.Value) {
118+
"$($_.Name): $(Format-OneLineRecursive $_.Value -CurrentDepth ($CurrentDepth - 1) @paramTable)"
119+
}
120+
}
121+
) -join '; '
122+
if ($DoBraceNestedTables -and $CurrentDepth -ne $StartingDepth) {
123+
$psObjectRendered = "{$psObjectRendered}"
124+
}
125+
# output
126+
$psObjectRendered
127+
} else {
128+
# if it's not a PSObject or IDictionary, just use ToString()
129+
$item.ToString()
130+
}
131+
# replace excess whitespace/newlines with a single space.
132+
$resultList.Add(($result -replace '\s+', ' ')) | Out-Null
133+
}
134+
# Then output.
135+
$resultList -join '; '
136+
}
137+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
Import-Module $PSScriptRoot\.. -Force
2+
# Full disclosure: This test-suite was produced with the assistance of ChatGPT4
3+
# on 2025-06-22, using the prompt "I have a powershell function I want pester
4+
# tests for. Can you help me with those? Here's the headers of the function."
5+
# and the text of the help-comments and param block of Format-OneLine.
6+
7+
8+
Describe 'Format-OneLine' {
9+
10+
Context 'Basic variable formatting' {
11+
It 'Formats single variable as "name: value"' {
12+
$var = 'value'
13+
$result = Get-Variable var | Format-OneLine
14+
$result | Should -Be 'var: value'
15+
}
16+
17+
It 'Formats multiple variables as "name: value; name2: value2"' {
18+
$foo = 'bar'; $baz = 'quux'
19+
$result = Get-Variable foo, baz | Format-OneLine
20+
$result | Should -Be 'foo: bar; baz: quux'
21+
}
22+
}
23+
24+
Context 'Depth parameter behavior' {
25+
It 'Shows nested objects up to the specified depth' {
26+
$obj = [PSCustomObject]@{
27+
Key = [PSCustomObject]@{ SubKey = 'value' }
28+
}
29+
$result = $obj | Format-OneLine -Depth 2
30+
$result | Should -Match 'SubKey: value'
31+
}
32+
33+
It 'Does not show nested keys if depth is too shallow' {
34+
$obj = [PSCustomObject]@{
35+
Key = [PSCustomObject]@{ SubKey = 'value' }
36+
}
37+
$result = $obj | Format-OneLine -Depth 1
38+
$result | Should -Not -Match 'SubKey'
39+
}
40+
}
41+
42+
Context 'ShowEmpty switch' {
43+
It 'Includes empty objects if ShowEmpty is set' {
44+
$obj = [PSCustomObject]@{ Empty = $null }
45+
$result = $obj | Format-OneLine -ShowEmpty
46+
$result | Should -Match 'Empty'
47+
}
48+
49+
It 'Omits empty objects if ShowEmpty is not set' {
50+
$obj = [PSCustomObject]@{ Empty = $null }
51+
$result = $obj | Format-OneLine
52+
$result | Should -Not -Match 'Empty'
53+
}
54+
}
55+
56+
Context 'DoBraceNestedTables switch' {
57+
It 'Wraps nested objects in braces when enabled' {
58+
$obj = [PSCustomObject]@{
59+
Info = @{ Nested = 'val' }
60+
}
61+
$result = $obj | Format-OneLine -DoBraceNestedTables -Depth 2
62+
$result | Should -Match 'Info: {Nested: val}'
63+
}
64+
65+
It 'Does not wrap nested objects in braces when disabled' {
66+
$obj = [PSCustomObject]@{
67+
Info = @{ Nested = 'val' }
68+
}
69+
$result = $obj | Format-OneLine -Depth 2
70+
$result | Should -Not -Match '{Nested: val}'
71+
}
72+
}
73+
74+
Context 'Pipeline behavior' {
75+
It 'Combines multiple inputs into one line' {
76+
$a = [PSCustomObject]@{ A = 1 }
77+
$b = [PSCustomObject]@{ B = 2 }
78+
$result = @($a, $b) | Format-OneLine
79+
$result | Should -Be 'A: 1; B: 2'
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)