blob: ee1f31b9a1f5b0c7ae5cd46141b131ecedd40f20 [file] [log] [blame]
/* eslint-disable @typescript-eslint/quotes */
/*---------------------------------------------------------
* Copyright 2023 The Go Authors. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
type ParseError = string;
/**
* 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 {
const result: string[] = [];
let word = '';
let bufferedWord = false;
for (let i = 0; i < args.length; ) {
if (args[i] === "'" || args[i] === '"') {
const quoteBegin = args[i];
let j = i + 1;
let k = i + 1;
for (; k < args.length && args[k] !== quoteBegin; ) {
// escaped quotes
if (args[k] === '\\' && k + 1 < args.length && (args[k + 1] === "'" || args[k + 1] === '"')) {
bufferedWord = true;
// buffer everything up to this point, skipping backslash
word += args.slice(j, k);
word += args.charAt(k + 1);
j = k + 2;
k = k + 2;
} else {
k++;
}
}
if (k >= args.length) {
if (quoteBegin === "'") {
return "args has unmatched single quotes ('). starting index: " + i;
} else {
return 'args has unmatched double quotes ("). starting index: ' + i;
}
}
bufferedWord = true;
word += args.slice(j, k);
i = k + 1;
} else if (args[i] === '\\' && i + 1 < args.length && (args[i + 1] === "'" || args[i + 1] === '"')) {
// escaped quotes
bufferedWord = true;
word += args.charAt(i + 1);
i = i + 2;
} else if (args[i] !== ' ') {
// a word
let j = i + 1;
// advance until a whitespace, or special char is encountered
for (; j < args.length && args[j] !== ' ' && args[j] !== "'" && args[j] !== '"' && args[j] !== '\\'; j++) {
// a word
}
bufferedWord = true;
word += args.slice(i, j);
i = j;
} else if (bufferedWord) {
// also true that args[i] === ' '
result.push(word);
word = '';
bufferedWord = false;
i++;
} else {
// args[i] === ' '
i++;
}
}
if (bufferedWord) {
result.push(word);
}
return result;
}