From a41962af85fb9d84040614c4a1aab7a8e4c7d4af Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Fri, 26 Jul 2024 15:45:54 -0600 Subject: [PATCH] fix: ignore escaped delimiters (#1148) * fix: ignore escaped delimiters * test: qa for delimiter escaping * test: basic space delimiter --------- Co-authored-by: mshanemc --- src/parser/parse.ts | 12 +++++++- test/parser/parse.test.ts | 63 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) 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..1a5cbd48 100644 --- a/test/parser/parse.test.ts +++ b/test/parser/parse.test.ts @@ -519,6 +519,33 @@ 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', 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})}, + }) + 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 +673,41 @@ 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: { + foo: Flags.string({multiple: true, delimiter: ','}), + }, + }) + 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]}) + }) }) }) @@ -709,7 +771,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'}) })