boardgame-core/src/commands/CommandParser.ts

164 lines
3.6 KiB
TypeScript

import type { ParsedCliCommand, CliCommandArgs } from './CliCommand';
/**
* 命令解析错误
*/
export class CommandParseError extends Error {
constructor(message: string) {
super(message);
this.name = 'CommandParseError';
}
}
/**
* CLI 命令解析器
* 解析 CLI 风格的命令字符串
*/
export class CommandParser {
/**
* 解析命令字符串
* @param input 命令字符串,如 "move card-1 discard --faceup=true"
*/
parse(input: string): ParsedCliCommand {
const trimmed = input.trim();
if (!trimmed) {
throw new CommandParseError('Empty command');
}
const tokens = this.tokenize(trimmed);
if (tokens.length === 0) {
throw new CommandParseError('No command found');
}
const commandName = tokens[0];
const args = this.parseArgs(tokens.slice(1));
return {
commandName,
args,
raw: input,
};
}
/**
* 将命令字符串分词
*/
private tokenize(input: string): string[] {
const tokens: string[] = [];
let current = '';
let inQuotes = false;
let quoteChar = '';
for (let i = 0; i < input.length; i++) {
const char = input[i];
if (inQuotes) {
if (char === quoteChar) {
tokens.push(current);
current = '';
inQuotes = false;
} else {
current += char;
}
} else if (char === '"' || char === "'") {
inQuotes = true;
quoteChar = char;
} else if (char === ' ' || char === '\t') {
if (current) {
tokens.push(current);
current = '';
}
} else {
current += char;
}
}
if (current) {
tokens.push(current);
}
if (inQuotes) {
throw new CommandParseError('Unclosed quote in command');
}
return tokens;
}
/**
* 解析参数
*/
private parseArgs(tokens: string[]): CliCommandArgs {
const positional: string[] = [];
const flags: Record<string, string | boolean> = {};
for (const token of tokens) {
if (token.startsWith('--')) {
// 长标志 --key=value 或 --flag
const flagMatch = token.match(/^--([^=]+)(?:=(.+))?$/);
if (flagMatch) {
const [, key, value] = flagMatch;
flags[key] = value !== undefined ? this.parseFlagValue(value) : true;
}
} else if (token.startsWith('-') && token.length === 2) {
// 短标志 -f 或 -k=v
const flagMatch = token.match(/^-([^=]+)(?:=(.+))?$/);
if (flagMatch) {
const [, key, value] = flagMatch;
flags[key] = value !== undefined ? this.parseFlagValue(value) : true;
}
} else {
// 位置参数
positional.push(token);
}
}
return { positional, flags };
}
/**
* 解析标志值
*/
private parseFlagValue(value: string): string | boolean {
// 布尔值
if (value.toLowerCase() === 'true') return true;
if (value.toLowerCase() === 'false') return false;
// 数字转换为字符串
return value;
}
/**
* 格式化命令用于显示
*/
static formatCommand(name: string, args?: CliCommandArgs): string {
if (!args) {
return name;
}
const parts = [name];
// 添加位置参数
parts.push(...args.positional);
// 添加标志参数
for (const [key, value] of Object.entries(args.flags)) {
if (value === true) {
parts.push(`--${key}`);
} else {
parts.push(`--${key}=${value}`);
}
}
return parts.join(' ');
}
}
/**
* 创建命令解析器
*/
export function createCommandParser(): CommandParser {
return new CommandParser();
}