From 2b90c21ef551bf42c151fd574c0bd88ad1ee673b Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Fri, 26 Jul 2024 12:44:19 -0600 Subject: [PATCH 1/3] fix: ignore escaped delimiters --- src/parser/parse.ts | 12 +++++++++++- test/parser/parse.test.ts | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/parser/parse.ts b/src/parser/parse.ts index ff8489e1..adb3d950 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -412,17 +412,27 @@ export class Parser< // multiple with custom delimiter if (fws.inputFlag.flag.type === 'option' && fws.inputFlag.flag.delimiter && fws.inputFlag.flag.multiple) { + // regex that will identify unescaped delimiters + const makeDelimiter = (delimiter: string) => new RegExp(`(? ( await Promise.all( (i.tokens ?? []) - .flatMap((token) => token.input.split((i.inputFlag.flag as OptionFlag).delimiter ?? ',')) + .flatMap((token) => + token.input.split(makeDelimiter((i.inputFlag.flag as OptionFlag).delimiter ?? ',')), + ) // trim, and remove surrounding doubleQuotes (which would hav been needed if the elements contain spaces) .map((v) => v .trim() + // remove escaped characters from delimiter + // example: --opt="a\,b,c" -> ["a,b", "c"] + .replaceAll( + new RegExp(`\\\\${(i.inputFlag.flag as OptionFlag).delimiter}`, 'g'), + (i.inputFlag.flag as OptionFlag).delimiter ?? ',', + ) .replace(/^"(.*)"$/, '$1') .replace(/^'(.*)'$/, '$1'), ) diff --git a/test/parser/parse.test.ts b/test/parser/parse.test.ts index bb1c4cbf..1616fa78 100644 --- a/test/parser/parse.test.ts +++ b/test/parser/parse.test.ts @@ -646,6 +646,14 @@ See more help with --help`) expect(error.message).to.include('Expected --foo=b c to be one of: a a, b b') } }) + it('does not split on escaped delimiter', async () => { + const out = await parse(['--foo', 'a\\,b,c'], { + flags: { + foo: Flags.string({multiple: true, delimiter: ','}), + }, + }) + expect(out.flags).to.deep.include({foo: ['a,b', 'c']}) + }) }) }) From afbaf98c21f4ae5a91d2b1f6ef458e9b5199b241 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 26 Jul 2024 16:02:27 -0500 Subject: [PATCH 2/3] test: qa for delimiter escaping --- test/parser/parse.test.ts | 49 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/test/parser/parse.test.ts b/test/parser/parse.test.ts index 1616fa78..b7296002 100644 --- a/test/parser/parse.test.ts +++ b/test/parser/parse.test.ts @@ -519,6 +519,27 @@ See more help with --help`) }) expect(out.flags).to.deep.include({foo: ['a', 'b']}) }) + it('parses single flag starting with with \\ escape char', async () => { + const out = await parse(['--foo', '\\file:foo'], { + flags: { + foo: Flags.custom({multiple: true})(), + }, + }) + expect(out.flags).to.deep.include({foo: ['\\file:foo']}) + }) + it('parses multiple space-delimited flags ending with with \\ escape char', async () => { + const out = await parse(['--foo', 'c:\\', 'd:\\'], { + flags: {foo: Flags.string({multiple: true})}, + }) + expect(out.flags).to.deep.include({foo: ['c:\\', 'd:\\']}) + }) + it('parses multiple space-delimited flags ending with with \\ escape char', async () => { + const out = await parse(['--foo', 'c:\\', 'd:\\'], { + flags: {foo: Flags.string({multiple: true})}, + }) + expect(out.flags).to.deep.include({foo: ['c:\\', 'd:\\']}) + }) + it('allowed options on multiple', async () => { const out = await parse(['--foo', 'a', '--foo=b'], { flags: { @@ -646,6 +667,14 @@ See more help with --help`) expect(error.message).to.include('Expected --foo=b c to be one of: a a, b b') } }) + it('retains escape char without delimiter', async () => { + const out = await parse(['--foo', 'a\\'], { + flags: { + foo: Flags.string({multiple: true, delimiter: ','}), + }, + }) + expect(out.flags).to.deep.include({foo: ['a\\']}) + }) it('does not split on escaped delimiter', async () => { const out = await parse(['--foo', 'a\\,b,c'], { flags: { @@ -654,6 +683,25 @@ See more help with --help`) }) expect(out.flags).to.deep.include({foo: ['a,b', 'c']}) }) + it('escapes with multiple invocation', async () => { + const out = await parse(['--foo', 'a\\,b', '--foo', 'b'], { + flags: { + foo: Flags.string({multiple: true, delimiter: ','}), + }, + }) + expect(out.flags).to.deep.include({foo: ['a,b', 'b']}) + }) + + it('comma-escaped stringified json', async () => { + const val = '{"a":"b"\\,"c":"d"}' + const expected = '{"a":"b","c":"d"}' + const out = await parse(['--foo', val], { + flags: { + foo: Flags.string({multiple: true, delimiter: ','}), + }, + }) + expect(out.flags).to.deep.include({foo: [expected]}) + }) }) }) @@ -717,7 +765,6 @@ See more help with --help`) strict: false, '--': false, }) - console.log(out) expect(out.argv).to.deep.equal(['foo', 'bar', '--', '--myflag']) expect(out.args).to.deep.equal({argOne: 'foo'}) }) From 276039a712d05ab4c4545fef9d9404542644a8d0 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 26 Jul 2024 16:07:14 -0500 Subject: [PATCH 3/3] test: basic space delimiter --- test/parser/parse.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/parser/parse.test.ts b/test/parser/parse.test.ts index b7296002..1a5cbd48 100644 --- a/test/parser/parse.test.ts +++ b/test/parser/parse.test.ts @@ -527,6 +527,12 @@ See more help with --help`) }) expect(out.flags).to.deep.include({foo: ['\\file:foo']}) }) + it('parses multiple space-delimited flags', async () => { + const out = await parse(['--foo', 'a', 'b', 'c'], { + flags: {foo: Flags.string({multiple: true})}, + }) + expect(out.flags).to.deep.include({foo: ['a', 'b', 'c']}) + }) it('parses multiple space-delimited flags ending with with \\ escape char', async () => { const out = await parse(['--foo', 'c:\\', 'd:\\'], { flags: {foo: Flags.string({multiple: true})},