Skip to content

Commit

Permalink
Merge pull request #4 from nodecraft/feat/exact-value
Browse files Browse the repository at this point in the history
  • Loading branch information
Cherry authored Nov 27, 2024
2 parents bf6e203 + c1a1ce2 commit 10d5762
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 3 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ If you want to allow empty sections, you can set this option to `true`.
```
Previously, this would omit the section entirely on encode. Now, it will be included in the output.

## New `exactValue` option
If you want to preserve all characters in a value, you can set this option to `true`.
```ini
key=some string ; comment
```
Previously, this would be parsed as `some string` and not `some string ; comment`.

## Usage

Consider an ini-file `config.ini` that looks like this:
Expand Down
36 changes: 34 additions & 2 deletions ini.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const encode = (obj, options) => {
whitespace: false,
inlineArrays: false,
allowEmptySection: false,
exactValue: false,
};
}else{
options = options || Object.create(null);
Expand Down Expand Up @@ -57,6 +58,7 @@ const encode = (obj, options) => {
inlineArrays: options.inlineArrays,
forceStringifyKeys: options.forceStringifyKeys,
allowEmptySection: options.allowEmptySection,
exactValue: options.exactValue,
});
if(out.length > 0 && child.length > 0){
out += eol;
Expand Down Expand Up @@ -100,7 +102,12 @@ const decode = (str, options = {}) => {
}
let key = unsafe(match[2]);
if(isConstructorOrProto(ref, key)){ continue; }
let value = match[3] ? unsafe(match[3]) : defaultValue;
let value = null;
if(options.exactValue){
value = match[3] ? unsafeExact(match[3]) : defaultValue;
}else{
value = match[3] ? unsafe(match[3]) : defaultValue;
}
switch(value){
case 'true':
case 'True':
Expand Down Expand Up @@ -180,6 +187,10 @@ const safe = (val, key, options = {}) => {
if(key && options.forceStringifyKeys && options.forceStringifyKeys.includes(key)){
return JSON.stringify(val);
}
if(options.exactValue){
// Don't try to escape a comment in a value
return val;
}
// comments
return val.replace(/;/g, '\\;').replace(/#/g, '\\#');
};
Expand Down Expand Up @@ -216,7 +227,11 @@ const unsafe = (val) => {
}
isEscaping = false;
}else if(commentChars.includes(char)){
break;
// Check if there's spaces around this comment character
// If there is, then we're done parsing at the character before this one
if(val.charAt(i - 1) === ' ' && val.charAt(i + 1) === ' '){
break;
}
}else if(char === '\\'){
isEscaping = true;
}else{
Expand All @@ -230,6 +245,23 @@ const unsafe = (val) => {
return escapedVal.trim();
};

const unsafeExact = (val) => {
val = (val || '').trim();
if(isQuoted(val)){
// remove the single quotes before calling JSON.parse
if(val.charAt(0) === "'"){
val = val.substr(1, val.length - 2); // eslint-disable-line unicorn/prefer-string-slice
}
try{
val = JSON.parse(val);
}catch{
// we tried :(
}
return val;
}
return val.trim();
};

module.exports = {
parse: decode,
decode,
Expand Down
68 changes: 68 additions & 0 deletions test/fixtures/fooExactValues.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
o = p

a with spaces = b c

; wrap in quotes to JSON-decode and preserve spaces
" xa n p " = "\"\r\nyoyoyo\r\r\n"
; wrap in quotes to get a key with a bracket, not a section.
"[disturbing]" = hey you never know
; Test single quotes
s = 'something'
; Test mixing quotes
s1 = "something'
; Test double quotes
s2 = "something else"
; Test arrays
zr[] = deedee
ar[] = one
ar[] = three
; This should be included in the array
ar = this is included
; Test resetting of a value (and not turn it into an array)
br = cold
br = warm
eq = "eq=eq"
; Test no value
nv =
; a section
[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?
; Nested array
cr[] = four
cr[] = eight
; nested child without middle parent
; should create otherwise-empty a.b
[a.b.c]
e = 1
j = 2
; dots in the section name should be literally interpreted
[x\.y\.z]
x.y.z = xyz
[x\.y\.z.a\.b\.c]
a.b.c = abc
; this next one is not a comment! it's escaped!
nocomment = this; this is not a comment

# Support the use of the number sign (#) as an alternative to the semicolon for indicating comments.
# http://en.wikipedia.org/wiki/INI_file#Comments

# this next one is not a comment! it's escaped!
noHashComment = this# this is not a comment
93 changes: 92 additions & 1 deletion test/foo.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ const test = tap.test;

const fixture = path.resolve(__dirname, "./fixtures/foo.ini");
const fixtureInlineArrays = path.resolve(__dirname, "./fixtures/fooInlineArrays.ini");
const fixtureExactValues = path.resolve(__dirname, "./fixtures/fooExactValues.ini");
const data = fs.readFileSync(fixture, "utf8");
const dataInlineArrays = fs.readFileSync(fixtureInlineArrays, "utf8");
const dataExactValues = fs.readFileSync(fixtureExactValues, "utf8");

const eol = require('os').EOL;

Expand Down Expand Up @@ -115,6 +117,40 @@ const expectforceStringifyKeys = 'o=p' + eol
+ 'a.b.c=abc' + eol
+ 'nocomment=this\\; this is not a comment' + eol
+ 'noHashComment=this\\# this is not a comment' + eol;
const expectExactValues = 'o=p' + eol
+ 'a with spaces=b c' + eol
+ '" xa n p "="\\"\\r\\nyoyoyo\\r\\r\\n"' + eol
+ '"[disturbing]"=hey you never know' + eol
+ 's=something' + eol
+ 's1="something\'' + eol
+ 's2=something else' + eol
+ 'zr[]=deedee' + eol
+ 'ar[]=one' + eol
+ 'ar[]=three' + eol
+ 'ar[]=this is included' + eol
+ 'br=warm' + eol
+ 'eq="eq=eq"' + eol
+ 'nv=' + eol + eol
+ '[a]' + eol
+ 'av=a val' + eol
+ 'e={ o: p, a: '
+ '{ av: a val, b: { c: { e: "this [value]" '
+ '} } } }' + eol
+ 'j="\\"{ o: \\"p\\", a: { av:'
+ ' \\"a val\\", b: { c: { e: \\"this [value]'
+ '\\" } } } }\\""' + eol
+ '"[]"=a square?' + eol
+ 'cr[]=four' + eol
+ 'cr[]=eight' + eol + eol
+ '[a.b.c]' + eol
+ 'e=1' + eol
+ 'j=2' + eol + eol
+ '[x\\.y\\.z]' + eol
+ 'x.y.z=xyz' + eol + eol
+ '[x\\.y\\.z.a\\.b\\.c]' + eol
+ 'a.b.c=abc' + eol
+ 'nocomment=this; this is not a comment' + eol
+ 'noHashComment=this# this is not a comment' + eol;
const expectD = {
o: 'p',
'a with spaces': 'b c',
Expand Down Expand Up @@ -189,6 +225,43 @@ const expectDInlineArrays = {
},
},
};
const expectDExactValues = {
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',
'zr': ['deedee'],
'ar': ['one', 'three', 'this is included'],
'br': 'warm',
'eq': 'eq=eq',
'nv': '',
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', 'eight',
],
b: {
c: {
e: '1',
j: '2',
},
},
},
'x.y.z': {
'x.y.z': 'xyz',
'a.b.c': {
'a.b.c': 'abc',
'nocomment': 'this; this is not a comment',
noHashComment: 'this# this is not a comment',
},
},
};
const expectF = '[prefix.log]' + eol
+ 'type=file' + eol + eol
+ '[prefix.log.level]' + eol
Expand All @@ -213,6 +286,12 @@ test("decode from file inlineArrays=true", function(t){
t.end();
});

test("decode from file exactValue=true", function(t){
const d = ini.decode(dataExactValues, {exactValue: true});
t.same(d, expectDExactValues);
t.end();
});

test("encode from data, inlineArrays=false", function(t){
let e = ini.encode(expectD, {inlineArrays: false});
t.same(e, expectE);
Expand All @@ -237,6 +316,18 @@ test("encode from data, inlineArrays=true", function(t){
t.end();
});

test("encode from data exactValue=true", function(t){
let e = ini.encode(expectDExactValues, {exactValue: true});
t.same(e, expectExactValues);

const obj = {log: {type: 'file', level: {label: 'debug', value: 10}}};
e = ini.encode(obj);
t.not(e.slice(0, 1), eol, 'Never a blank first line');
t.not(e.slice(-2), eol + eol, 'Never a blank final line');

t.end();
});

test("encode with option", function(t){
const obj = {log: {type: 'file', level: {label: 'debug', value: 10}}};
const e = ini.encode(obj, {section: 'prefix'});
Expand Down Expand Up @@ -309,7 +400,7 @@ test('ignores invalid line (=)', function(t){

test("unsafe escape values", function(t){
t.equal(ini.unsafe(''), '');
t.equal(ini.unsafe('x;y'), 'x');
t.equal(ini.unsafe('x;y'), 'xy');
t.equal(ini.unsafe('x # y'), 'x');
t.equal(ini.unsafe('x "\\'), 'x "\\');
t.end();
Expand Down

0 comments on commit 10d5762

Please sign in to comment.