fix: command parsing quotes
This commit is contained in:
parent
8d778f9867
commit
284251ddf2
|
|
@ -8,13 +8,17 @@
|
|||
/**
|
||||
* 解析命令行输入字符串为 Command 对象
|
||||
* 支持格式:commandName [params...] [--flags...] [-o value...]
|
||||
* 支持引号:单引号 (') 和双引号 (") 可以包裹包含空格的参数
|
||||
* 支持转义:使用反斜杠 (\) 转义引号或反斜杠本身
|
||||
*
|
||||
* @example
|
||||
* parseCommand("move meeple1 region1 --force -x 10")
|
||||
* // returns { name: "move", params: ["meeple1", "region1"], flags: { force: true }, options: { x: "10" } }
|
||||
* parseCommand('place tile "large castle" --x 5')
|
||||
* // returns { name: "place", params: ["tile", "large castle"], flags: {}, options: { x: "5" } }
|
||||
*/
|
||||
export function parseCommand(input: string): Command {
|
||||
const tokens = input.trim().split(/\s+/).filter(Boolean);
|
||||
const tokens = tokenize(input);
|
||||
|
||||
if (tokens.length === 0) {
|
||||
return { name: '', flags: {}, options: {}, params: [] };
|
||||
|
|
@ -66,3 +70,56 @@ export function parseCommand(input: string): Command {
|
|||
|
||||
return { name, flags, options, params };
|
||||
}
|
||||
|
||||
/**
|
||||
* 将输入字符串分解为 tokens,支持引号和转义
|
||||
*/
|
||||
function tokenize(input: string): string[] {
|
||||
const tokens: string[] = [];
|
||||
let current = '';
|
||||
let inQuote: string | null = null; // ' 或 " 或 null
|
||||
let escaped = false;
|
||||
let i = 0;
|
||||
|
||||
while (i < input.length) {
|
||||
const char = input[i];
|
||||
|
||||
if (escaped) {
|
||||
// 转义字符:直接添加到当前 token
|
||||
current += char;
|
||||
escaped = false;
|
||||
} else if (char === '\\') {
|
||||
// 开始转义
|
||||
escaped = true;
|
||||
} else if (inQuote) {
|
||||
// 在引号内
|
||||
if (char === inQuote) {
|
||||
// 结束引号
|
||||
inQuote = null;
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
} else if (char === '"' || char === "'") {
|
||||
// 开始引号
|
||||
inQuote = char;
|
||||
} else if (/\s/.test(char)) {
|
||||
// 空白字符
|
||||
if (current.length > 0) {
|
||||
tokens.push(current);
|
||||
current = '';
|
||||
}
|
||||
} else {
|
||||
// 普通字符
|
||||
current += char;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// 处理未闭合的引号
|
||||
if (current.length > 0) {
|
||||
tokens.push(current);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,4 +111,64 @@ describe('parseCommand', () => {
|
|||
params: []
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse quoted string with double quotes', () => {
|
||||
const result = parseCommand('place tile "large castle" --x 5');
|
||||
expect(result).toEqual({
|
||||
name: 'place',
|
||||
flags: {},
|
||||
options: { x: '5' },
|
||||
params: ['tile', 'large castle']
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse quoted string with single quotes', () => {
|
||||
const result = parseCommand("place tile 'large castle' --x 5");
|
||||
expect(result).toEqual({
|
||||
name: 'place',
|
||||
flags: {},
|
||||
options: { x: '5' },
|
||||
params: ['tile', 'large castle']
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle escaped quotes', () => {
|
||||
const result = parseCommand('say "hello \\"world\\""');
|
||||
expect(result).toEqual({
|
||||
name: 'say',
|
||||
flags: {},
|
||||
options: {},
|
||||
params: ['hello "world"']
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle escaped backslash', () => {
|
||||
const result = parseCommand('set path "C:\\\\Users"');
|
||||
expect(result).toEqual({
|
||||
name: 'set',
|
||||
flags: {},
|
||||
options: {},
|
||||
params: ['path', 'C:\\Users']
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle mixed quotes', () => {
|
||||
const result = parseCommand('cmd "hello world" \'foo bar\' --flag');
|
||||
expect(result).toEqual({
|
||||
name: 'cmd',
|
||||
flags: { flag: true },
|
||||
options: {},
|
||||
params: ['hello world', 'foo bar']
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle quote in middle of argument', () => {
|
||||
const result = parseCommand('cmd "hello\'s world"');
|
||||
expect(result).toEqual({
|
||||
name: 'cmd',
|
||||
flags: {},
|
||||
options: {},
|
||||
params: ["hello's world"]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue