From 7a033ad8aaa1a3f353b0fa26ec83314f9927c077 Mon Sep 17 00:00:00 2001 From: David Sveningsson Date: Sat, 18 Apr 2020 13:49:33 +0200 Subject: [PATCH] add option to set custom section delimiter fixes #60 --- README.md | 11 +++- ini.js | 31 +++++++---- test/delimiter.js | 134 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 test/delimiter.js diff --git a/README.md b/README.md index 33df258..78cd080 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,16 @@ to the filesystem with the following content: ## API -### decode(inistring) +### decode(inistring, [options]) Decode the ini-style formatted `inistring` into a nested object. -### parse(inistring) +The `options` object may contain the following: + +* `delimiter` Character used when splitting sections into nested objects. + Can be set to `false` to disable splitting. Defaults to `"."`. + +### parse(inistring, [options]) Alias for `decode(inistring)` @@ -78,6 +83,8 @@ The `options` object may contain the following: `=` 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. +* `delimiter` Character used when joining nested objects into sections. + Defaults to `"."`. For backwards compatibility reasons, if a `string` options is passed in, then it is assumed to be the `section` value. diff --git a/ini.js b/ini.js index 590195d..d9ae9be 100644 --- a/ini.js +++ b/ini.js @@ -5,6 +5,8 @@ exports.stringify = exports.encode = encode exports.safe = safe exports.unsafe = unsafe +var DEFAULT_DELIMITER = '.' + var eol = typeof process !== 'undefined' && process.platform === 'win32' ? '\r\n' : '\n' @@ -15,11 +17,13 @@ function encode (obj, opt) { if (typeof opt === 'string') { opt = { section: opt, - whitespace: false + whitespace: false, + delimiter: DEFAULT_DELIMITER } } else { opt = opt || {} opt.whitespace = opt.whitespace === true + opt.delimiter = opt.delimiter || DEFAULT_DELIMITER } var separator = opt.whitespace ? ' = ' : '=' @@ -42,11 +46,12 @@ function encode (obj, opt) { } children.forEach(function (k, _, __) { - var nk = dotSplit(k).join('\\.') - var section = (opt.section ? opt.section + '.' : '') + nk + var nk = dotSplit(k, opt.delimiter).join('\\' + opt.delimiter) + var section = (opt.section ? opt.section + opt.delimiter : '') + nk var child = encode(obj[k], { section: section, - whitespace: opt.whitespace + whitespace: opt.whitespace, + delimiter: opt.delimiter }) if (out.length && child.length) { out += eol @@ -57,16 +62,21 @@ function encode (obj, opt) { return out } -function dotSplit (str) { +function dotSplit (str, delimiter) { + if (delimiter === false) { + return [str] + } + var escapeRegex = new RegExp('\\\\[' + delimiter[0] + ']', 'g') + var splitRegex = new RegExp('[' + delimiter[0] + ']') return str.replace(/\1/g, '\u0002LITERAL\\1LITERAL\u0002') - .replace(/\\\./g, '\u0001') - .split(/\./).map(function (part) { + .replace(escapeRegex, '\u0001') + .split(splitRegex).map(function (part) { return part.replace(/\1/g, '\\.') .replace(/\2LITERAL\\1LITERAL\2/g, '\u0001') }) } -function decode (str) { +function decode (str, opt) { var out = {} var p = out var section = null @@ -74,6 +84,9 @@ function decode (str) { var re = /^\[([^\]]*)\]$|^([^=]+)(=(.*))?$/i var lines = str.split(/[\r\n]+/g) + opt = opt || {} + opt.delimiter = typeof opt.delimiter === 'undefined' ? DEFAULT_DELIMITER : opt.delimiter + lines.forEach(function (line, _, __) { if (!line || line.match(/^\s*[;#]/)) return var match = line.match(re) @@ -120,7 +133,7 @@ function decode (str) { } // see if the parent section is also an object. // if so, add it to that, and mark this one for deletion - var parts = dotSplit(k) + var parts = dotSplit(k, opt.delimiter) var p = out var l = parts.pop() var nl = l.replace(/\\\./g, '.') diff --git a/test/delimiter.js b/test/delimiter.js new file mode 100644 index 0000000..4d5f42d --- /dev/null +++ b/test/delimiter.js @@ -0,0 +1,134 @@ +var i = require('../') +var tap = require('tap') +var test = tap.test + +test('decode with default delimiter', function (t) { + var decoded = i.decode([ + '[a.b]', + 'foo = 1', + '[c\\.d]', + 'foo = 2', + '[a_b]', + 'foo = 3' + ].join('\n')) + t.deepEqual(decoded, { + a: { + b: { + foo: '1' + } + }, + 'c.d': { + foo: '2' + }, + 'a_b': { + foo: '3' + } + }) + t.end() +}) + +test('decode with custom delimiter', function (t) { + var decoded = i.decode([ + '[a_b]', + 'foo = 1', + '[c\\_d]', + 'foo = 2', + '[a.b]', + 'foo = 3' + ].join('\n'), { delimiter: '_' }) + t.deepEqual(decoded, { + a: { + b: { + foo: '1' + } + }, + 'c.d': { + foo: '2' + }, + 'a.b': { + foo: '3' + } + }) + t.end() +}) + +test('decode with no delimiter', function (t) { + var decoded = i.decode([ + '[a.b]', + 'foo = 1', + '[c\\.d]', + 'foo = 2' + ].join('\n'), { delimiter: false }) + t.deepEqual(decoded, { + 'a.b': { + foo: '1' + }, + 'c.d': { + foo: '2' + } + }) + t.end() +}) + +test('encode with default delimiter', function (t) { + var obj = { + a: { + b: { + foo: 'bar' + } + }, + 'a.b': { + foo: 'bar' + } + } + var encoded = i.encode(obj) + t.equal(encoded, [ + '[a.b]', + 'foo=bar', + '', + '[a\\.b]', + 'foo=bar', + '' + ].join('\n')) + t.end() +}) + +test('encode with custom delimiter', function (t) { + var obj = { + a: { + b: { + foo: 'bar' + } + }, + 'a_b': { + foo: 'bar' + } + } + var encoded = i.encode(obj, { delimiter: '_' }) + t.equal(encoded, [ + '[a_b]', + 'foo=bar', + '', + '[a\\_b]', + 'foo=bar', + '' + ].join('\n')) + t.end() +}) + +test('encode with no delimiter set should default to dot', function (t) { + var obj = { + a: { + b: { + foo: 'bar' + } + } + } + var encoded = i.encode(obj, { delimiter: false }) + t.equal(encoded, [ + '[a.b]', + 'foo=bar', + '' + ].join('\n')) + t.end() +})