Skip to content

Commit 3e16495

Browse files
Ash258r15ch13
authored andcommitted
formatjson.ps1: Format single value Arrays into String (#2642)
- Automatically convert single value arrays and recursively normalize PSObjects values - Add tests - Add .yml .editorconfig settings
1 parent 067f5f7 commit 3e16495

14 files changed

+370
-51
lines changed

.editorconfig

+3
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ trim_trailing_whitespace = true
1515
[*.{bat,cmd,[Bb][Aa][Tt],[Cc][Mm][Dd]]
1616
# DOS/Win *requires* BAT/CMD files to have CRLF newlines
1717
end_of_line = crlf
18+
19+
[*.{yml, yaml}]
20+
indent_size = 2

.vscode/settings.json

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
// Configure PSScriptAnalyzer settings
22
{
3+
"[powershell]": {
4+
// Disable formating until: https://github.com/PowerShell/vscode-powershell/issues/1019 is fixed
5+
"editor.formatOnSave": false
6+
},
37
"powershell.scriptAnalysis.settingsPath": "PSScriptAnalyzerSettings.psd1",
48
"powershell.codeFormatting.preset": "OTBS",
59
"powershell.codeFormatting.alignPropertyValuePairs": true,

bin/formatjson.ps1

+36-23
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,44 @@
1-
# reformat manifest json
2-
param($path)
1+
<#
2+
.SYNOPSIS
3+
Format manifest.
4+
.PARAMETER App
5+
Manifest to format.
36
4-
. "$psscriptroot\..\lib\core.ps1"
5-
. "$psscriptroot\..\lib\manifest.ps1"
6-
. "$psscriptroot\..\lib\json.ps1"
7+
Wildcards are supported.
8+
.PARAMETER Dir
9+
Where to search for manifest(s).
10+
.EXAMPLE
11+
PS BUCKETROOT> .\bin\formatjson.ps1
12+
Format all manifests inside bucket directory.
13+
.EXAMPLE
14+
PS BUCKETROOT> .\bin\formatjson.ps1 7zip
15+
Format manifest '7zip' inside bucket directory.
16+
#>
17+
param(
18+
[String] $App = '*',
19+
[ValidateScript( {
20+
if (!(Test-Path $_ -Type Container)) {
21+
throw "$_ is not a directory!"
22+
}
23+
$true
24+
})]
25+
[Alias('Path')]
26+
[String] $Dir = "$PSScriptRoot\..\bucket"
27+
)
728

8-
if(!$path) { $path = "$psscriptroot\..\bucket" }
9-
$path = resolve-path $path
29+
. "$PSScriptRoot\..\lib\core.ps1"
30+
. "$PSScriptRoot\..\lib\manifest.ps1"
31+
. "$PSScriptRoot\..\lib\json.ps1"
1032

11-
$dir = ""
12-
$type = Get-Item $path
13-
if ($type -is [System.IO.DirectoryInfo]) {
14-
$dir = "$path\"
15-
$files = Get-ChildItem $path "*.json"
16-
} elseif ($type -is [System.IO.FileInfo]) {
17-
$files = @($path)
18-
} else {
19-
Write-Error "unknown item"
20-
exit
21-
}
33+
$Dir = Resolve-Path $Dir
34+
35+
Get-ChildItem $Dir "$App.json" | ForEach-Object {
36+
if ($PSVersionTable.PSVersion.Major -gt 5) { $_ = $_.Name } # Fix for pwsh
2237

23-
$files | ForEach-Object {
2438
# beautify
25-
$json = parse_json "$dir$($_.Name)" | ConvertToPrettyJson
39+
$json = parse_json "$Dir\$_" | ConvertToPrettyJson
2640

2741
# convert to 4 spaces
28-
$json = $json -replace "`t",' '
29-
30-
[System.IO.File]::WriteAllLines("$dir$($_.Name)", $json)
42+
$json = $json -replace "`t", ' '
43+
[System.IO.File]::WriteAllLines("$Dir\$_", $json)
3144
}

lib/json.ps1

+68-27
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,43 @@
11
# Convert objects to pretty json
22
# Only needed until PowerShell ConvertTo-Json will be improved https://github.com/PowerShell/PowerShell/issues/2736
3-
Function ConvertToPrettyJson {
4-
[cmdletbinding()]
3+
# https://github.com/PowerShell/PowerShell/issues/2736 was fixed in pwsh
4+
# Still needed in normal powershell
5+
6+
function ConvertToPrettyJson {
7+
[CmdletBinding()]
58

69
Param (
7-
[parameter(Mandatory, ValueFromPipeline)]
10+
[Parameter(Mandatory, ValueFromPipeline)]
811
$data
912
)
1013

11-
Process {
14+
Process {
1215
$data = normalize_values $data
1316

1417
# convert to string
1518
[String]$json = $data | ConvertTo-Json -Depth 8 -Compress
16-
[String]$output = ""
19+
[String]$output = ''
1720

1821
# state
19-
[String]$buffer = ""
22+
[String]$buffer = ''
2023
[Int]$depth = 0
2124
[Bool]$inString = $false
2225

2326
# configuration
24-
[String]$indent = " " * 4
27+
[String]$indent = ' ' * 4
2528
[Bool]$unescapeString = $true
2629
[String]$eol = "`r`n"
2730

2831
for ($i = 0; $i -lt $json.Length; $i++) {
2932
# read current char
3033
$buffer = $json.Substring($i, 1)
3134

32-
#
33-
$objectStart = !$inString -and $buffer.Equals("{")
34-
$objectEnd = !$inString -and $buffer.Equals("}")
35-
$arrayStart = !$inString -and $buffer.Equals("[")
36-
$arrayEnd = !$inString -and $buffer.Equals("]")
37-
$colon = !$inString -and $buffer.Equals(":")
38-
$comma = !$inString -and $buffer.Equals(",")
35+
$objectStart = !$inString -and $buffer.Equals('{')
36+
$objectEnd = !$inString -and $buffer.Equals('}')
37+
$arrayStart = !$inString -and $buffer.Equals('[')
38+
$arrayEnd = !$inString -and $buffer.Equals(']')
39+
$colon = !$inString -and $buffer.Equals(':')
40+
$comma = !$inString -and $buffer.Equals(',')
3941
$quote = $buffer.Equals('"')
4042
$escape = $buffer.Equals('\')
4143

@@ -79,20 +81,20 @@ Function ConvertToPrettyJson {
7981

8082
# add whitespace and newlines after the content
8183
if ($colon) {
82-
$output += " "
84+
$output += ' '
8385
} elseif ($comma -or $arrayStart -or $objectStart) {
8486
$output += $eol
8587
$output += $indent * $depth
8688
}
8789
}
8890

89-
$output
91+
return $output
9092
}
9193
}
9294

9395
function json_path([String] $json, [String] $jsonpath, [String] $basename) {
9496
Add-Type -Path "$psscriptroot\..\supporting\validator\bin\Newtonsoft.Json.dll"
95-
$jsonpath = $jsonpath.Replace("`$basename", $basename)
97+
$jsonpath = $jsonpath.Replace('$basename', $basename)
9698
try {
9799
$obj = [Newtonsoft.Json.Linq.JObject]::Parse($json)
98100
} catch [Newtonsoft.Json.JsonReaderException] {
@@ -112,24 +114,24 @@ function json_path([String] $json, [String] $jsonpath, [String] $basename) {
112114

113115
function json_path_legacy([String] $json, [String] $jsonpath, [String] $basename) {
114116
$result = $json | ConvertFrom-Json -ea stop
115-
$isJsonPath = $jsonpath.StartsWith("`$")
116-
$jsonpath.split(".") | ForEach-Object {
117+
$isJsonPath = $jsonpath.StartsWith('$')
118+
$jsonpath.split('.') | ForEach-Object {
117119
$el = $_
118120

119121
# substitute the base filename into the jsonpath
120-
if($el.Contains("`$basename")) {
121-
$el = $el.Replace("`$basename", $basename)
122+
if ($el.Contains('$basename')) {
123+
$el = $el.Replace('$basename', $basename)
122124
}
123125

124126
# skip $ if it's jsonpath format
125-
if($el -eq "`$" -and $isJsonPath) {
127+
if ($el -eq '$' -and $isJsonPath) {
126128
return
127129
}
128130

129131
# array detection
130-
if($el -match "^(?<property>\w+)?\[(?<index>\d+)\]$") {
132+
if ($el -match '^(?<property>\w+)?\[(?<index>\d+)\]$') {
131133
$property = $matches['property']
132-
if($property) {
134+
if ($property) {
133135
$result = $result.$property[$matches['index']]
134136
} else {
135137
$result = $result[$matches['index']]
@@ -146,19 +148,58 @@ function normalize_values([psobject] $json) {
146148
# Iterate Through Manifest Properties
147149
$json.PSObject.Properties | ForEach-Object {
148150

151+
$value = $_.Value
152+
153+
# Recursively edit psobjects
154+
# If the values is psobjects, its not normalized
155+
# For example if manifest have architecture and it's architecture have array with single value it's not formatted.
156+
# @see https://github.com/lukesampson/scoop/pull/2642#issue-220506263
157+
if ($value -is [System.Management.Automation.PSCustomObject]) {
158+
$value = normalize_values $value
159+
}
160+
149161
# Process String Values
150-
if ($_.Value -is [string]) {
162+
if ($value -is [String]) {
151163

152164
# Split on new lines
153-
[array] $parts = ($_.Value -split '\r?\n').Trim()
165+
[Array] $parts = ($value -split '\r?\n').Trim()
154166

155167
# Replace with string array if result is multiple lines
156168
if ($parts.Count -gt 1) {
157-
$_.Value = $parts
169+
$value = $parts
170+
}
171+
}
172+
173+
# Convert single value array into string
174+
if ($value -is [Array]) {
175+
# Array contains only 1 element String or Array
176+
if ($value.Count -eq 1) {
177+
# Array
178+
if ($value[0] -is [Array]) {
179+
$value = $value
180+
} else {
181+
# String
182+
$value = $value[0]
183+
}
184+
} else {
185+
# Array of Arrays
186+
$resulted_arrs = @()
187+
foreach ($element in $value) {
188+
if ($element.Count -eq 1) {
189+
$resulted_arrs += $element
190+
} else {
191+
$resulted_arrs += , $element
192+
}
193+
}
194+
195+
$value = $resulted_arrs
158196
}
159197
}
160198

161199
# Process other values as needed...
200+
201+
# Bind edited values into original
202+
$_.Value = $value
162203
}
163204

164205
return $json

test/00-Project.Tests.ps1

+2-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ describe 'Style constraints for non-binary project files' {
8181
# gather all files except '*.exe', '*.zip', or any .git repository files
8282
$repo_files |
8383
where-object { $_.fullname -inotmatch $($project_file_exclusions -join '|') } |
84-
where-object { $_.fullname -inotmatch '(.exe|.zip|.dll)$' }
84+
where-object { $_.fullname -inotmatch '(.exe|.zip|.dll)$' } |
85+
where-object { $_.fullname -inotmatch '(unformated)' }
8586
)
8687

8788
$files_exist = ($files.Count -gt 0)

test/Scoop-Format-Manifest.Tests.ps1

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
. "$psscriptroot\Scoop-TestLib.ps1"
2+
. "$psscriptroot\..\lib\json.ps1"
3+
. "$psscriptroot\..\lib\manifest.ps1"
4+
5+
Describe 'Pretty json formating' -Tag 'Scoop' {
6+
BeforeAll {
7+
$format = "$PSScriptRoot\fixtures\format"
8+
$manifests = Get-ChildItem "$format\formated" -File -Filter '*.json'
9+
}
10+
11+
Context 'Beautify manifest' {
12+
$manifests | ForEach-Object {
13+
if ($PSVersionTable.PSVersion.Major -gt 5) { $_ = $_.Name } # Fix for pwsh
14+
15+
It "$_" {
16+
$pretty_json = (parse_json "$format\unformated\$_") | ConvertToPrettyJson
17+
$correct = (Get-Content "$format\formated\$_") -join "`r`n"
18+
$correct.CompareTo($pretty_json) | Should Be 0
19+
}
20+
}
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"bin": "single"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"version": "0.5.18",
3+
"url": "https://whatever",
4+
"hash": "whatever",
5+
"architecture": {
6+
"64bit": {
7+
"installer": {
8+
"script": [
9+
"Do something",
10+
"cosi"
11+
]
12+
}
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"homepage": "http://www.7-zip.org/",
3+
"description": "A multi-format file archiver with high compression ratios",
4+
"license": {
5+
"identifier": "LGPL-2.0-only,BSD-3-Clause",
6+
"url": "https://www.7-zip.org/license.txt"
7+
},
8+
"version": "18.05",
9+
"architecture": {
10+
"64bit": {
11+
"url": "https://7-zip.org/a/7z1805-x64.msi",
12+
"hash": "898c1ca0015183fe2ba7d55cacf0a1dea35e873bf3f8090f362a6288c6ef08d7"
13+
},
14+
"32bit": {
15+
"url": "https://7-zip.org/a/7z1805.msi",
16+
"hash": "c554238bee18a03d736525e06d9258c9ecf7f64ead7c6b0d1eb04db2c0de30d0"
17+
}
18+
},
19+
"extract_dir": "Files/7-Zip",
20+
"bin": [
21+
"single",
22+
[
23+
"7z.exe",
24+
"cosi"
25+
],
26+
[
27+
"7z.exe",
28+
"cosi",
29+
"param",
30+
"icon"
31+
],
32+
[
33+
"7z.exe",
34+
"empty",
35+
"",
36+
""
37+
],
38+
"singtwo"
39+
],
40+
"checkver": "Download 7-Zip ([\\d.]+)",
41+
"autoupdate": {
42+
"architecture": {
43+
"64bit": {
44+
"url": "https://7-zip.org/a/7z$cleanVersion-x64.msi"
45+
},
46+
"32bit": {
47+
"url": "https://7-zip.org/a/7z$cleanVersion.msi"
48+
}
49+
}
50+
},
51+
"shortcuts": [
52+
[
53+
"7zFM.exe",
54+
"7-Zip"
55+
],
56+
[
57+
"name with spaces.exe",
58+
"Shortcut with spaces in name"
59+
]
60+
]
61+
}

0 commit comments

Comments
 (0)