Skip to content

Commit

Permalink
feat: add "align" and "sort" options (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
rquadling committed Apr 13, 2023
1 parent dc64a1a commit 622106a
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 6 deletions.
10 changes: 8 additions & 2 deletions README.md
Expand Up @@ -76,19 +76,25 @@ prepended to all sub-sections, see the usage example above.

The `options` object may contain the following:

* `section` A string which will be the first `section` in the encoded
* `align` Boolean to specify whether to align the `=` characters for
each section. This option will automatically enable `whitespace`.
Defaults to `false`.
* `section` String which will be the first `section` in the encoded
ini data. Defaults to none.
* `sort` Boolean to specify if all keys in each section, as well as
all sections, will be alphabetically sorted. Defaults to `false`.
* `whitespace` Boolean to specify whether to put whitespace around the
`=` character. By default, whitespace is omitted, to be friendly to
some persnickety old parsers that don't tolerate it well. But some
find that it's more human-readable and pretty with the whitespace.
Defaults to `false`.
* `newline` Boolean to specify whether to put an additional newline
after a section header. Some INI file parsers (for example the TOSHIBA
FlashAir one) need this to parse the file successfully. By default,
the additional newline is omitted.
* `platform` String to define which platform this INI file is expected
to be used with: when `platform` is `win32`, line terminations are
CR+LF, for other platforms line termination is LF. By default the
CR+LF, for other platforms line termination is LF. By default, the
current platform name is used.
* `bracketedArrays` Boolean to specify whether array values are appended
with `[]`. By default this is true but there are some ini parsers
Expand Down
33 changes: 29 additions & 4 deletions lib/ini.js
Expand Up @@ -4,8 +4,10 @@ const encode = (obj, opt = {}) => {
if (typeof opt === 'string') {
opt = { section: opt }
}
opt.whitespace = opt.whitespace === true
opt.align = opt.align === true
opt.newline = opt.newline === true
opt.sort = opt.sort === true
opt.whitespace = opt.whitespace === true || opt.align === true
/* istanbul ignore next */
opt.platform = opt.platform || process?.platform
opt.bracketedArray = opt.bracketedArray !== false
Expand All @@ -14,19 +16,42 @@ const encode = (obj, opt = {}) => {
const eol = opt.platform === 'win32' ? '\r\n' : '\n'
const separator = opt.whitespace ? ' = ' : '='
const children = []

const keys = opt.sort ? Object.keys(obj).sort() : Object.keys(obj)

let padToChars = 0
// If aligning on the separator, then padToChars is determined as follows:
// 1. Get the keys
// 2. Exclude keys pointing to objects unless the value is null or an array
// 3. Add `[]` to array keys
// 4. Ensure non empty set of keys
// 5. Reduce the set to the longest `safe` key
// 6. Get the `safe` length
if (opt.align) {
padToChars = safe(
(
keys
.filter(k => obj[k] === null || Array.isArray(obj[k]) || typeof obj[k] !== 'object')
.map(k => Array.isArray(obj[k]) ? `${k}[]` : k)
)
.concat([''])
.reduce((a, b) => safe(a).length >= safe(b).length ? a : b)
).length
}

let out = ''
const arraySuffix = opt.bracketedArray ? '[]' : ''

for (const k of Object.keys(obj)) {
for (const k of keys) {
const val = obj[k]
if (val && Array.isArray(val)) {
for (const item of val) {
out += safe(`${k}${arraySuffix}`) + separator + safe(item) + eol
out += safe(`${k}${arraySuffix}`).padEnd(padToChars, ' ') + separator + safe(item) + eol
}
} else if (val && typeof val === 'object') {
children.push(k)
} else {
out += safe(k) + separator + safe(val) + eol
out += safe(k).padEnd(padToChars, ' ') + separator + safe(val) + eol
}
}

Expand Down
138 changes: 138 additions & 0 deletions tap-snapshots/test/foo.js.test.cjs
Expand Up @@ -111,6 +111,98 @@ noHashComment=this\\# this is not a comment
`

exports[`test/foo.js TAP encode with align > must match snapshot 1`] = `
o = p
a with spaces = b c
" xa n p " = "\\"\\r\\nyoyoyo\\r\\r\\n"
"[disturbing]" = hey you never know
s = something
s1 = "something'
s2 = something else
s3 =
s4 =
s5 = " "
s6 = " a "
s7 = true
true = true
false = false
null = null
undefined = undefined
zr[] = deedee
ar[] = one
ar[] = three
ar[] = this is included
br = warm
eq = "eq=eq"
[a]
av = a val
e = { o: p, a: { av: a val, b: { c: { e: "this [value]" } } } }
j = "\\"{ o: \\"p\\", a: { av: \\"a val\\", b: { c: { e: \\"this [value]\\" } } } }\\""
"[]" = a square?
cr[] = four
cr[] = eight
[a.b.c]
e = 1
j = 2
[x\\.y\\.z]
x.y.z = xyz
[x\\.y\\.z.a\\.b\\.c]
a.b.c = abc
nocomment = this\\; this is not a comment
noHashComment = this\\# this is not a comment
`

exports[`test/foo.js TAP encode with align and sort > must match snapshot 1`] = `
" xa n p " = "\\"\\r\\nyoyoyo\\r\\r\\n"
"[disturbing]" = hey you never know
a with spaces = b c
ar[] = one
ar[] = three
ar[] = this is included
br = warm
eq = "eq=eq"
false = false
null = null
o = p
s = something
s1 = "something'
s2 = something else
s3 =
s4 =
s5 = " "
s6 = " a "
s7 = true
true = true
undefined = undefined
zr[] = deedee
[a]
"[]" = a square?
av = a val
cr[] = four
cr[] = eight
e = { o: p, a: { av: a val, b: { c: { e: "this [value]" } } } }
j = "\\"{ o: \\"p\\", a: { av: \\"a val\\", b: { c: { e: \\"this [value]\\" } } } }\\""
[a.b.c]
e = 1
j = 2
[x\\.y\\.z]
x.y.z = xyz
[x\\.y\\.z.a\\.b\\.c]
a.b.c = abc
noHashComment = this\\# this is not a comment
nocomment = this\\; this is not a comment
`

exports[`test/foo.js TAP encode with newline > must match snapshot 1`] = `
[log]
Expand Down Expand Up @@ -145,6 +237,52 @@ Array [
]
`

exports[`test/foo.js TAP encode with sort > must match snapshot 1`] = `
" xa n p "="\\"\\r\\nyoyoyo\\r\\r\\n"
"[disturbing]"=hey you never know
a with spaces=b c
ar[]=one
ar[]=three
ar[]=this is included
br=warm
eq="eq=eq"
false=false
null=null
o=p
s=something
s1="something'
s2=something else
s3=
s4=
s5=" "
s6=" a "
s7=true
true=true
undefined=undefined
zr[]=deedee
[a]
"[]"=a square?
av=a val
cr[]=four
cr[]=eight
e={ o: p, a: { av: a val, b: { c: { e: "this [value]" } } } }
j="\\"{ o: \\"p\\", a: { av: \\"a val\\", b: { c: { e: \\"this [value]\\" } } } }\\""
[a.b.c]
e=1
j=2
[x\\.y\\.z]
x.y.z=xyz
[x\\.y\\.z.a\\.b\\.c]
a.b.c=abc
noHashComment=this\\# this is not a comment
nocomment=this\\; this is not a comment
`

exports[`test/foo.js TAP encode with whitespace > must match snapshot 1`] = `
[log]
type = file
Expand Down
24 changes: 24 additions & 0 deletions test/foo.js
Expand Up @@ -60,3 +60,27 @@ test('encode with platform=win32', function (t) {
t.matchSnapshot(e.split('\r\n'))
t.end()
})

test('encode with align', function (t) {
const d = i.decode(data)
const e = i.encode(d, { align: true })

t.matchSnapshot(e)
t.end()
})

test('encode with sort', function (t) {
const d = i.decode(data)
const e = i.encode(d, { sort: true })

t.matchSnapshot(e)
t.end()
})

test('encode with align and sort', function (t) {
const d = i.decode(data)
const e = i.encode(d, { align: true, sort: true })

t.matchSnapshot(e)
t.end()
})

0 comments on commit 622106a

Please sign in to comment.