Skip to content

Commit

Permalink
Add backslash for escaping
Browse files Browse the repository at this point in the history
  • Loading branch information
weikanglim committed Feb 19, 2023
1 parent e6ae23b commit 48639fa
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 16 deletions.
70 changes: 56 additions & 14 deletions src/utils/argsUtil.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,108 @@
type ParseError = string

const escapeAtEndError = "args string has escape character (\\) at the end of the string, with nothing to escape.";

/**
* Parses an argument string with shell-like semantics into a list of arguments.
* Returns an error only if the argument string is malformed.
*
* - Whitespace is treated as the word separator.
* - Each word is treated as an argument to be passed to the invocation process.
* - Single-quotes and double-quotes can be used to escape whitespace characters as literals.
* - A backslash followed by a quote can be used (\' or \") to escape quotes as literals.
* - Null arguments ("" or '') are retained and passed as empty strings.
* When a null argument appears as part of a non-null argument, the null argument is removed.
* That is, the word -d'' or ''-d becomes -d after word splitting and null argument removal.
* @param args The string containing arguments to be parsed.
*/
export function parseArgsString(args: string): string[] | ParseError {
let result: string[] = [];
let resOrErr: string[] = [];
let word: string = "";
let bufferedWord: boolean = false;

for (let i = 0; i < args.length;) {
if (args[i] == "'") {
let j = i + 1;
for (; j < args.length && args[j] != "'"; j++) {
let k = i + 1;
for (; k < args.length && args[k] != "'";) {
if (args[k] == '\\'
&& k + 1 < args.length
&& (args[k + 1] == "'" || args[k + 1] == '"')) {
bufferedWord = true;
// buffer everything up to this point
word += args.slice(j, k)
word += args.charAt(k + 1)

j = k + 2
k = k + 2
} else {
k++
}
}
if (j >= args.length) {
if (k >= args.length) {
return "args string has unmatched single quotes ('). starting index: " + i
}

bufferedWord = true;
word += args.slice(i + 1, j);
i = j + 1;
word += args.slice(j, k);
i = k + 1;
} else if (args[i] == '"') {
let j = i + 1;
for (; j < args.length && args[j] != '"'; j++) {
let k = i + 1;
for (; k < args.length && args[k] != '"';) {
if (args[k] == '\\'
&& k + 1 < args.length
&& (args[k + 1] == "'" || args[k + 1] == '"')) {
bufferedWord = true;
// buffer everything up to this point
word += args.slice(j, k)
word += args.charAt(k + 1)

j = k + 2
k = k + 2
} else {
k++;
}
}
if (j >= args.length) {
return "args string has unmatched double quotes (\"). starting index: " + i
if (k >= args.length) {
return 'args string has unmatched double quotes ("). starting index: ' + i
}

bufferedWord = true;
word += args.slice(i + 1, j);
i = j + 1;
word += args.slice(j, k);
i = k + 1;
} else if (args[i] == '\\'
&& i + 1 < args.length
&& (args[i + 1] == "'" || args[i + 1] == '"')) {
bufferedWord = true;
word += args.charAt(i + 1);
i = i + 2;
} else if (args[i] != ' ') {
let j = i + 1;
for (; j < args.length
&& args[j] != ' ' && args[j] != "'" && args[j] != '"'; j++) {
&& args[j] != ' '
&& args[j] != "'"
&& args[j] != '"'
&& args[j] != '\\';
j++) {
}

bufferedWord = true;
word += args.slice(i, j);
i = j;
} else if (bufferedWord) { // also true that args[i] == ' '
result.push(word);
resOrErr.push(word);
word = "";
bufferedWord = false;
i++;
} else { // args[i] == ' '
i++
}
}

if (bufferedWord) {
result.push(word);
resOrErr.push(word);
}

return result;
return resOrErr;
}
14 changes: 12 additions & 2 deletions test/unit/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ suite('util tests', () => {
want: ['value 1', 'value 2', 'value3', 'value4']
},
{
test: 'InnerQuotes',
test: 'SingleSurroundingDoubleQuotes',
args: [`' "text" '`],
want: [' "text" ']
},
Expand All @@ -30,14 +30,24 @@ suite('util tests', () => {
args: [`" 'text' "`],
want: [` 'text' `]
},
{
test: 'EscapedQuotes',
args: [`\\'a \\'b \\"c \\"d`],
want: [`'a`, `'b`, '"c', '"d']
},
{
test: 'EscapedQuotesInsideQuotes',
args: [`'a\\' b\\"' "\\'c \\"d"`],
want: [`a' b"`, `'c "d`]
},
{
test: 'Null',
args: [`''`, '""'],
want: ['']
},
{
test: 'Empty',
args: [' '],
args: ['', ' ', ' '],
want: [] as string[]
},
{
Expand Down

0 comments on commit 48639fa

Please sign in to comment.