Skip to content

Commit 622106a

Browse files
authored
feat: add "align" and "sort" options (#205)
1 parent dc64a1a commit 622106a

File tree

4 files changed

+199
-6
lines changed

4 files changed

+199
-6
lines changed

README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,25 @@ prepended to all sub-sections, see the usage example above.
7676

7777
The `options` object may contain the following:
7878

79-
* `section` A string which will be the first `section` in the encoded
79+
* `align` Boolean to specify whether to align the `=` characters for
80+
each section. This option will automatically enable `whitespace`.
81+
Defaults to `false`.
82+
* `section` String which will be the first `section` in the encoded
8083
ini data. Defaults to none.
84+
* `sort` Boolean to specify if all keys in each section, as well as
85+
all sections, will be alphabetically sorted. Defaults to `false`.
8186
* `whitespace` Boolean to specify whether to put whitespace around the
8287
`=` character. By default, whitespace is omitted, to be friendly to
8388
some persnickety old parsers that don't tolerate it well. But some
8489
find that it's more human-readable and pretty with the whitespace.
90+
Defaults to `false`.
8591
* `newline` Boolean to specify whether to put an additional newline
8692
after a section header. Some INI file parsers (for example the TOSHIBA
8793
FlashAir one) need this to parse the file successfully. By default,
8894
the additional newline is omitted.
8995
* `platform` String to define which platform this INI file is expected
9096
to be used with: when `platform` is `win32`, line terminations are
91-
CR+LF, for other platforms line termination is LF. By default the
97+
CR+LF, for other platforms line termination is LF. By default, the
9298
current platform name is used.
9399
* `bracketedArrays` Boolean to specify whether array values are appended
94100
with `[]`. By default this is true but there are some ini parsers

lib/ini.js

+29-4
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ const encode = (obj, opt = {}) => {
44
if (typeof opt === 'string') {
55
opt = { section: opt }
66
}
7-
opt.whitespace = opt.whitespace === true
7+
opt.align = opt.align === true
88
opt.newline = opt.newline === true
9+
opt.sort = opt.sort === true
10+
opt.whitespace = opt.whitespace === true || opt.align === true
911
/* istanbul ignore next */
1012
opt.platform = opt.platform || process?.platform
1113
opt.bracketedArray = opt.bracketedArray !== false
@@ -14,19 +16,42 @@ const encode = (obj, opt = {}) => {
1416
const eol = opt.platform === 'win32' ? '\r\n' : '\n'
1517
const separator = opt.whitespace ? ' = ' : '='
1618
const children = []
19+
20+
const keys = opt.sort ? Object.keys(obj).sort() : Object.keys(obj)
21+
22+
let padToChars = 0
23+
// If aligning on the separator, then padToChars is determined as follows:
24+
// 1. Get the keys
25+
// 2. Exclude keys pointing to objects unless the value is null or an array
26+
// 3. Add `[]` to array keys
27+
// 4. Ensure non empty set of keys
28+
// 5. Reduce the set to the longest `safe` key
29+
// 6. Get the `safe` length
30+
if (opt.align) {
31+
padToChars = safe(
32+
(
33+
keys
34+
.filter(k => obj[k] === null || Array.isArray(obj[k]) || typeof obj[k] !== 'object')
35+
.map(k => Array.isArray(obj[k]) ? `${k}[]` : k)
36+
)
37+
.concat([''])
38+
.reduce((a, b) => safe(a).length >= safe(b).length ? a : b)
39+
).length
40+
}
41+
1742
let out = ''
1843
const arraySuffix = opt.bracketedArray ? '[]' : ''
1944

20-
for (const k of Object.keys(obj)) {
45+
for (const k of keys) {
2146
const val = obj[k]
2247
if (val && Array.isArray(val)) {
2348
for (const item of val) {
24-
out += safe(`${k}${arraySuffix}`) + separator + safe(item) + eol
49+
out += safe(`${k}${arraySuffix}`).padEnd(padToChars, ' ') + separator + safe(item) + eol
2550
}
2651
} else if (val && typeof val === 'object') {
2752
children.push(k)
2853
} else {
29-
out += safe(k) + separator + safe(val) + eol
54+
out += safe(k).padEnd(padToChars, ' ') + separator + safe(val) + eol
3055
}
3156
}
3257

tap-snapshots/test/foo.js.test.cjs

+138
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,98 @@ noHashComment=this\\# this is not a comment
111111
112112
`
113113

114+
exports[`test/foo.js TAP encode with align > must match snapshot 1`] = `
115+
o = p
116+
a with spaces = b c
117+
" xa n p " = "\\"\\r\\nyoyoyo\\r\\r\\n"
118+
"[disturbing]" = hey you never know
119+
s = something
120+
s1 = "something'
121+
s2 = something else
122+
s3 =
123+
s4 =
124+
s5 = " "
125+
s6 = " a "
126+
s7 = true
127+
true = true
128+
false = false
129+
null = null
130+
undefined = undefined
131+
zr[] = deedee
132+
ar[] = one
133+
ar[] = three
134+
ar[] = this is included
135+
br = warm
136+
eq = "eq=eq"
137+
138+
[a]
139+
av = a val
140+
e = { o: p, a: { av: a val, b: { c: { e: "this [value]" } } } }
141+
j = "\\"{ o: \\"p\\", a: { av: \\"a val\\", b: { c: { e: \\"this [value]\\" } } } }\\""
142+
"[]" = a square?
143+
cr[] = four
144+
cr[] = eight
145+
146+
[a.b.c]
147+
e = 1
148+
j = 2
149+
150+
[x\\.y\\.z]
151+
x.y.z = xyz
152+
153+
[x\\.y\\.z.a\\.b\\.c]
154+
a.b.c = abc
155+
nocomment = this\\; this is not a comment
156+
noHashComment = this\\# this is not a comment
157+
158+
`
159+
160+
exports[`test/foo.js TAP encode with align and sort > must match snapshot 1`] = `
161+
" xa n p " = "\\"\\r\\nyoyoyo\\r\\r\\n"
162+
"[disturbing]" = hey you never know
163+
a with spaces = b c
164+
ar[] = one
165+
ar[] = three
166+
ar[] = this is included
167+
br = warm
168+
eq = "eq=eq"
169+
false = false
170+
null = null
171+
o = p
172+
s = something
173+
s1 = "something'
174+
s2 = something else
175+
s3 =
176+
s4 =
177+
s5 = " "
178+
s6 = " a "
179+
s7 = true
180+
true = true
181+
undefined = undefined
182+
zr[] = deedee
183+
184+
[a]
185+
"[]" = a square?
186+
av = a val
187+
cr[] = four
188+
cr[] = eight
189+
e = { o: p, a: { av: a val, b: { c: { e: "this [value]" } } } }
190+
j = "\\"{ o: \\"p\\", a: { av: \\"a val\\", b: { c: { e: \\"this [value]\\" } } } }\\""
191+
192+
[a.b.c]
193+
e = 1
194+
j = 2
195+
196+
[x\\.y\\.z]
197+
x.y.z = xyz
198+
199+
[x\\.y\\.z.a\\.b\\.c]
200+
a.b.c = abc
201+
noHashComment = this\\# this is not a comment
202+
nocomment = this\\; this is not a comment
203+
204+
`
205+
114206
exports[`test/foo.js TAP encode with newline > must match snapshot 1`] = `
115207
[log]
116208
@@ -145,6 +237,52 @@ Array [
145237
]
146238
`
147239

240+
exports[`test/foo.js TAP encode with sort > must match snapshot 1`] = `
241+
" xa n p "="\\"\\r\\nyoyoyo\\r\\r\\n"
242+
"[disturbing]"=hey you never know
243+
a with spaces=b c
244+
ar[]=one
245+
ar[]=three
246+
ar[]=this is included
247+
br=warm
248+
eq="eq=eq"
249+
false=false
250+
null=null
251+
o=p
252+
s=something
253+
s1="something'
254+
s2=something else
255+
s3=
256+
s4=
257+
s5=" "
258+
s6=" a "
259+
s7=true
260+
true=true
261+
undefined=undefined
262+
zr[]=deedee
263+
264+
[a]
265+
"[]"=a square?
266+
av=a val
267+
cr[]=four
268+
cr[]=eight
269+
e={ o: p, a: { av: a val, b: { c: { e: "this [value]" } } } }
270+
j="\\"{ o: \\"p\\", a: { av: \\"a val\\", b: { c: { e: \\"this [value]\\" } } } }\\""
271+
272+
[a.b.c]
273+
e=1
274+
j=2
275+
276+
[x\\.y\\.z]
277+
x.y.z=xyz
278+
279+
[x\\.y\\.z.a\\.b\\.c]
280+
a.b.c=abc
281+
noHashComment=this\\# this is not a comment
282+
nocomment=this\\; this is not a comment
283+
284+
`
285+
148286
exports[`test/foo.js TAP encode with whitespace > must match snapshot 1`] = `
149287
[log]
150288
type = file

test/foo.js

+24
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,27 @@ test('encode with platform=win32', function (t) {
6060
t.matchSnapshot(e.split('\r\n'))
6161
t.end()
6262
})
63+
64+
test('encode with align', function (t) {
65+
const d = i.decode(data)
66+
const e = i.encode(d, { align: true })
67+
68+
t.matchSnapshot(e)
69+
t.end()
70+
})
71+
72+
test('encode with sort', function (t) {
73+
const d = i.decode(data)
74+
const e = i.encode(d, { sort: true })
75+
76+
t.matchSnapshot(e)
77+
t.end()
78+
})
79+
80+
test('encode with align and sort', function (t) {
81+
const d = i.decode(data)
82+
const e = i.encode(d, { align: true, sort: true })
83+
84+
t.matchSnapshot(e)
85+
t.end()
86+
})

0 commit comments

Comments
 (0)