Skip to content

Commit

Permalink
feat: add bracketedArray option (#204)
Browse files Browse the repository at this point in the history
  • Loading branch information
wraithgar authored Apr 13, 2023
1 parent ad4b5d8 commit dc64a1a
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 3 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ The `options` object may contain the following:
to be used with: when `platform` is `win32`, line terminations are
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
that instead treat duplicate names as arrays.

For backwards compatibility reasons, if a `string` options is passed
in, then it is assumed to be the `section` value.
Expand Down
16 changes: 13 additions & 3 deletions lib/ini.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@ const encode = (obj, opt = {}) => {
opt.newline = opt.newline === true
/* istanbul ignore next */
opt.platform = opt.platform || process?.platform
opt.bracketedArray = opt.bracketedArray !== false

/* istanbul ignore next */
const eol = opt.platform === 'win32' ? '\r\n' : '\n'
const separator = opt.whitespace ? ' = ' : '='
const children = []
let out = ''
const arraySuffix = opt.bracketedArray ? '[]' : ''

for (const k of Object.keys(obj)) {
const val = obj[k]
if (val && Array.isArray(val)) {
for (const item of val) {
out += safe(k + '[]') + separator + safe(item) + eol
out += safe(`${k}${arraySuffix}`) + separator + safe(item) + eol
}
} else if (val && typeof val === 'object') {
children.push(k)
Expand Down Expand Up @@ -75,13 +77,15 @@ function splitSections (str, separator) {
return sections
}

const decode = str => {
const decode = (str, opt = {}) => {
opt.bracketedArray = opt.bracketedArray !== false
const out = Object.create(null)
let p = out
let section = null
// section |key = value
const re = /^\[([^\]]*)\]\s*$|^([^=]+)(=(.*))?$/i
const lines = str.split(/[\r\n]+/g)
const duplicates = {}

for (const line of lines) {
if (!line || line.match(/^\s*[;#]/) || line.match(/^\s*$/)) {
Expand All @@ -103,7 +107,13 @@ const decode = str => {
continue
}
const keyRaw = unsafe(match[2])
const isArray = keyRaw.length > 2 && keyRaw.slice(-2) === '[]'
let isArray
if (opt.bracketedArray) {
isArray = keyRaw.length > 2 && keyRaw.slice(-2) === '[]'
} else {
duplicates[keyRaw] = (duplicates?.[keyRaw] || 0) + 1
isArray = duplicates[keyRaw] > 1
}
const key = isArray ? keyRaw.slice(0, -2) : keyRaw
if (key === '__proto__') {
continue
Expand Down
57 changes: 57 additions & 0 deletions tap-snapshots/test/duplicate-properties.js.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* IMPORTANT
* This snapshot file is auto-generated, but designed for humans.
* It should be checked into source control and tracked carefully.
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
exports[`test/duplicate-properties.js TAP decode duplicate properties with bracketedArray=false > must match snapshot 1`] = `
Null Object {
"ar": Array [
"three",
],
"ar[]": "one",
"b": Array [
"2",
"3",
"3",
],
"brr": "1",
"str": "3",
"zr": "123",
"zr[]": "deedee",
}
`

exports[`test/duplicate-properties.js TAP decode with duplicate properties > must match snapshot 1`] = `
Null Object {
"ar": Array [
"one",
"three",
],
"brr": "3",
"str": "3",
"zr": Array [
"deedee",
"123",
],
}
`

exports[`test/duplicate-properties.js TAP encode duplicate properties with bracketedArray=false > must match snapshot 1`] = `
ar=1
ar=2
ar=3
br=1
br=2
`

exports[`test/duplicate-properties.js TAP encode with duplicate properties > must match snapshot 1`] = `
ar[]=1
ar[]=2
ar[]=3
br[]=1
br[]=2
`
40 changes: 40 additions & 0 deletions test/duplicate-properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const i = require('../')
const tap = require('tap')
const test = tap.test
const fs = require('fs')
const path = require('path')

const fixture = path.resolve(__dirname, './fixtures/duplicate.ini')
const data = fs.readFileSync(fixture, 'utf8')

tap.cleanSnapshot = s => s.replace(/\r\n/g, '\n')

test('decode with duplicate properties', function (t) {
const d = i.decode(data)
t.matchSnapshot(d)
t.end()
})

test('encode with duplicate properties', function (t) {
const e = i.encode({
ar: ['1', '2', '3'],
br: ['1', '2'],
})
t.matchSnapshot(e)
t.end()
})

test('decode duplicate properties with bracketedArray=false', function (t) {
const d = i.decode(data, { bracketedArray: false })
t.matchSnapshot(d)
t.end()
})

test('encode duplicate properties with bracketedArray=false', function (t) {
const e = i.encode({
ar: ['1', '2', '3'],
br: ['1', '2'],
}, { bracketedArray: false })
t.matchSnapshot(e)
t.end()
})
9 changes: 9 additions & 0 deletions test/fixtures/duplicate.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
zr[] = deedee
zr=123
ar[] = one
ar[] = three
str = 3
brr = 1
brr = 2
brr = 3
brr = 3

0 comments on commit dc64a1a

Please sign in to comment.