From b8561fe4ea46ce5f789cd0141934041a400288f1 Mon Sep 17 00:00:00 2001 From: Nam PHAM Date: Wed, 19 Apr 2023 21:12:19 +0200 Subject: [PATCH] feat: handle comment line for parse and stringify --- source/index.ts | 50 +++++++++++++++++++++++++++++++++++++++++-------- source/test.ts | 26 ++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/source/index.ts b/source/index.ts index 6be3928..d7f4827 100644 --- a/source/index.ts +++ b/source/index.ts @@ -4,31 +4,65 @@ export type Data = Record /** We typecast the value as a string so that it is compatible with envfiles. */ export type Input = Record +export type ParseOptions = { + // This only work when use direct with api, not work via bin + keepComments: boolean +} + +export type StringifyOptions = { + keepComments: boolean +} + // perhaps in the future we can use @bevry/json's toJSON and parseJSON and JSON.stringify to support more advanced types /** Parse an envfile string. */ -export function parse(src: string): Data { +export function parse( + src: string, + options: ParseOptions = { keepComments: false } +): Data { const result: Data = {} const lines = src.toString().split('\n') - for (const line of lines) { + let notHandleCount = 0 + for (const [lineIndex, line] of lines.entries()) { const match = line.match(/^([^=:#]+?)[=:](.*)/) if (match) { const key = match[1].trim() const value = match[2].trim().replace(/['"]+/g, '') result[key] = value + } else if (options.keepComments && line.trim().startsWith('#')) { + const sym = Symbol.for(`comment#${lineIndex - notHandleCount}`) + result[sym as any] = line + } else { + notHandleCount++ } } return result } /** Turn an object into an envfile string. */ -export function stringify(obj: Input): string { - let result = '' - for (const [key, value] of Object.entries(obj)) { +export function stringify( + obj: Input, + options: StringifyOptions = { keepComments: false } +): string { + const result = [] + for (const key of Reflect.ownKeys(obj)) { + const value = obj[key as string] if (key) { - const line = `${key}=${String(value)}` - result += line + '\n' + if ( + typeof key === 'symbol' && + (key as Symbol).toString().startsWith('Symbol(comment') + ) { + if (options.keepComments) { + const [_, lineIndex] = ( + (key as Symbol).description ?? 'comment#0' + ).split('#') + result.splice(parseInt(lineIndex, 10), 0, value) + } + } else { + const line = `${key as string}=${String(value)}` + result.push(line) + } } } - return result + return result.join('\n') } diff --git a/source/test.ts b/source/test.ts index 72418c5..b142d2b 100644 --- a/source/test.ts +++ b/source/test.ts @@ -4,7 +4,7 @@ import kava from 'kava' import safeps from 'safeps' import { resolve } from 'path' import { readJSON } from '@bevry/json' -import { parse } from './index.js' +import { parse, stringify } from './index.js' import filedirname from 'filedirname' const [file, dir] = filedirname() @@ -23,7 +23,7 @@ kava.test('envfile test prep', function (done) { // Test kava.suite('envfile', function (suite, test) { test('should work without comments', function (done) { - const command = `echo "a=1\\nb:2\\nc = 3\\nd : 4" | node ${binPath} env2json | node ${binPath} json2env` + const command = `echo "a=1\nb:2\nc = 3\nd : 4" | node ${binPath} env2json | node ${binPath} json2env` // @ts-ignore safeps.exec(command, { cwd: root }, function (err, stdout) { errorEqual(err, null, 'no error to exist') @@ -33,7 +33,7 @@ kava.suite('envfile', function (suite, test) { }) test('comments should be ignored', function (done) { - const command = `echo "#comments with = are ignored\\na=1\\n" | node ${binPath} env2json | node ${binPath} json2env` + const command = `echo "#comments with = are ignored\na=1\n" | node ${binPath} env2json | node ${binPath} json2env` // @ts-ignore safeps.exec(command, { cwd: root }, function (err, stdout) { errorEqual(err, null, 'no error to exist') @@ -54,4 +54,24 @@ kava.suite('envfile', function (suite, test) { deepEqual(result, expected) done() }) + + test('comment should be maintain', function (done) { + const str = ` #hello\nname="bob"\n#world \nplanet="earth"\nrace='human'` + const expected = ` #hello\nname=bob\n#world \nplanet=earth\nrace=human` + const options = { keepComments: true } + const result = stringify(parse(str, options), options) + + equal(result, expected) + done() + }) + + test('comment should be maintain correct with blank line', function (done) { + const str = ` #hello\n\nboo=foo\n\n#world\nhi=he` + const expected = ` #hello\nboo=foo\n#world\nhi=he` + const options = { keepComments: true } + const result = stringify(parse(str, options), options) + + equal(result, expected) + done() + }) })