Skip to content

Commit dc64a1a

Browse files
authored
feat: add bracketedArray option (#204)
1 parent ad4b5d8 commit dc64a1a

File tree

5 files changed

+122
-3
lines changed

5 files changed

+122
-3
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ The `options` object may contain the following:
9090
to be used with: when `platform` is `win32`, line terminations are
9191
CR+LF, for other platforms line termination is LF. By default the
9292
current platform name is used.
93+
* `bracketedArrays` Boolean to specify whether array values are appended
94+
with `[]`. By default this is true but there are some ini parsers
95+
that instead treat duplicate names as arrays.
9396

9497
For backwards compatibility reasons, if a `string` options is passed
9598
in, then it is assumed to be the `section` value.

lib/ini.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@ const encode = (obj, opt = {}) => {
88
opt.newline = opt.newline === true
99
/* istanbul ignore next */
1010
opt.platform = opt.platform || process?.platform
11+
opt.bracketedArray = opt.bracketedArray !== false
1112

1213
/* istanbul ignore next */
1314
const eol = opt.platform === 'win32' ? '\r\n' : '\n'
1415
const separator = opt.whitespace ? ' = ' : '='
1516
const children = []
1617
let out = ''
18+
const arraySuffix = opt.bracketedArray ? '[]' : ''
1719

1820
for (const k of Object.keys(obj)) {
1921
const val = obj[k]
2022
if (val && Array.isArray(val)) {
2123
for (const item of val) {
22-
out += safe(k + '[]') + separator + safe(item) + eol
24+
out += safe(`${k}${arraySuffix}`) + separator + safe(item) + eol
2325
}
2426
} else if (val && typeof val === 'object') {
2527
children.push(k)
@@ -75,13 +77,15 @@ function splitSections (str, separator) {
7577
return sections
7678
}
7779

78-
const decode = str => {
80+
const decode = (str, opt = {}) => {
81+
opt.bracketedArray = opt.bracketedArray !== false
7982
const out = Object.create(null)
8083
let p = out
8184
let section = null
8285
// section |key = value
8386
const re = /^\[([^\]]*)\]\s*$|^([^=]+)(=(.*))?$/i
8487
const lines = str.split(/[\r\n]+/g)
88+
const duplicates = {}
8589

8690
for (const line of lines) {
8791
if (!line || line.match(/^\s*[;#]/) || line.match(/^\s*$/)) {
@@ -103,7 +107,13 @@ const decode = str => {
103107
continue
104108
}
105109
const keyRaw = unsafe(match[2])
106-
const isArray = keyRaw.length > 2 && keyRaw.slice(-2) === '[]'
110+
let isArray
111+
if (opt.bracketedArray) {
112+
isArray = keyRaw.length > 2 && keyRaw.slice(-2) === '[]'
113+
} else {
114+
duplicates[keyRaw] = (duplicates?.[keyRaw] || 0) + 1
115+
isArray = duplicates[keyRaw] > 1
116+
}
107117
const key = isArray ? keyRaw.slice(0, -2) : keyRaw
108118
if (key === '__proto__') {
109119
continue
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* IMPORTANT
2+
* This snapshot file is auto-generated, but designed for humans.
3+
* It should be checked into source control and tracked carefully.
4+
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
5+
* Make sure to inspect the output below. Do not ignore changes!
6+
*/
7+
'use strict'
8+
exports[`test/duplicate-properties.js TAP decode duplicate properties with bracketedArray=false > must match snapshot 1`] = `
9+
Null Object {
10+
"ar": Array [
11+
"three",
12+
],
13+
"ar[]": "one",
14+
"b": Array [
15+
"2",
16+
"3",
17+
"3",
18+
],
19+
"brr": "1",
20+
"str": "3",
21+
"zr": "123",
22+
"zr[]": "deedee",
23+
}
24+
`
25+
26+
exports[`test/duplicate-properties.js TAP decode with duplicate properties > must match snapshot 1`] = `
27+
Null Object {
28+
"ar": Array [
29+
"one",
30+
"three",
31+
],
32+
"brr": "3",
33+
"str": "3",
34+
"zr": Array [
35+
"deedee",
36+
"123",
37+
],
38+
}
39+
`
40+
41+
exports[`test/duplicate-properties.js TAP encode duplicate properties with bracketedArray=false > must match snapshot 1`] = `
42+
ar=1
43+
ar=2
44+
ar=3
45+
br=1
46+
br=2
47+
48+
`
49+
50+
exports[`test/duplicate-properties.js TAP encode with duplicate properties > must match snapshot 1`] = `
51+
ar[]=1
52+
ar[]=2
53+
ar[]=3
54+
br[]=1
55+
br[]=2
56+
57+
`

test/duplicate-properties.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const i = require('../')
2+
const tap = require('tap')
3+
const test = tap.test
4+
const fs = require('fs')
5+
const path = require('path')
6+
7+
const fixture = path.resolve(__dirname, './fixtures/duplicate.ini')
8+
const data = fs.readFileSync(fixture, 'utf8')
9+
10+
tap.cleanSnapshot = s => s.replace(/\r\n/g, '\n')
11+
12+
test('decode with duplicate properties', function (t) {
13+
const d = i.decode(data)
14+
t.matchSnapshot(d)
15+
t.end()
16+
})
17+
18+
test('encode with duplicate properties', function (t) {
19+
const e = i.encode({
20+
ar: ['1', '2', '3'],
21+
br: ['1', '2'],
22+
})
23+
t.matchSnapshot(e)
24+
t.end()
25+
})
26+
27+
test('decode duplicate properties with bracketedArray=false', function (t) {
28+
const d = i.decode(data, { bracketedArray: false })
29+
t.matchSnapshot(d)
30+
t.end()
31+
})
32+
33+
test('encode duplicate properties with bracketedArray=false', function (t) {
34+
const e = i.encode({
35+
ar: ['1', '2', '3'],
36+
br: ['1', '2'],
37+
}, { bracketedArray: false })
38+
t.matchSnapshot(e)
39+
t.end()
40+
})

test/fixtures/duplicate.ini

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
zr[] = deedee
2+
zr=123
3+
ar[] = one
4+
ar[] = three
5+
str = 3
6+
brr = 1
7+
brr = 2
8+
brr = 3
9+
brr = 3

0 commit comments

Comments
 (0)