mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-31 20:47:00 +00:00
* refactor: share runtime parsing and routing owners * chore: shrink assertion allowances after reader consolidation
191 lines
5.9 KiB
JavaScript
191 lines
5.9 KiB
JavaScript
/** CLI token that stops root option scanning and leaves following args positional. */
|
|
export const FLAG_TERMINATOR = "--";
|
|
|
|
const ROOT_BOOLEAN_FLAGS = new Set(["--dev", "--no-color"]);
|
|
const ROOT_VALUE_FLAGS = new Set(["--profile", "--log-level", "--container"]);
|
|
|
|
/** Returns whether a token can be consumed as a root option value. */
|
|
export function isValueToken(arg) {
|
|
if (!arg || arg === FLAG_TERMINATOR) {
|
|
return false;
|
|
}
|
|
if (!arg.startsWith("-")) {
|
|
return true;
|
|
}
|
|
return /^-\d+(?:\.\d+)?$/.test(arg);
|
|
}
|
|
|
|
/** Count root-option tokens conservatively for route matching. */
|
|
export function consumeRootOptionToken(args, index) {
|
|
const arg = args[index];
|
|
if (!arg) {
|
|
return 0;
|
|
}
|
|
if (ROOT_BOOLEAN_FLAGS.has(arg)) {
|
|
return 1;
|
|
}
|
|
if (
|
|
arg.startsWith("--profile=") ||
|
|
arg.startsWith("--log-level=") ||
|
|
arg.startsWith("--container=")
|
|
) {
|
|
return 1;
|
|
}
|
|
if (ROOT_VALUE_FLAGS.has(arg)) {
|
|
return isValueToken(args[index + 1]) ? 2 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Consume required root values by their Commander role before startup policy is selected. */
|
|
export function consumeRootCommandOptionToken(args, index) {
|
|
return consumeKnownOptionToken(args, index, ROOT_BOOLEAN_FLAGS, ROOT_VALUE_FLAGS, "command-path");
|
|
}
|
|
|
|
/** Read positional command tokens while accepting root options at any pre-terminator position. */
|
|
export function getRootOptionAwareCommandPath(argv, depth) {
|
|
const args = argv.slice(2);
|
|
const path = [];
|
|
let literal = false;
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const arg = args[index];
|
|
if (!arg) {
|
|
break;
|
|
}
|
|
if (!literal && arg === FLAG_TERMINATOR) {
|
|
// A leading terminator still leaves a command to discover; later operands belong to callers.
|
|
if (path.length > 0) {
|
|
break;
|
|
}
|
|
literal = true;
|
|
continue;
|
|
}
|
|
const consumed = literal ? 0 : consumeRootCommandOptionToken(args, index);
|
|
if (consumed > 0) {
|
|
index += consumed - 1;
|
|
continue;
|
|
}
|
|
if (!literal && arg.startsWith("-")) {
|
|
continue;
|
|
}
|
|
path.push(arg);
|
|
if (path.length >= depth) {
|
|
break;
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
export function consumeKnownOptionToken(args, index, booleanFlags, valueFlags, mode) {
|
|
const arg = args[index];
|
|
if (!arg || arg === FLAG_TERMINATOR || !arg.startsWith("-")) {
|
|
return 0;
|
|
}
|
|
|
|
const equalsIndex = arg.indexOf("=");
|
|
const flag = equalsIndex === -1 ? arg : arg.slice(0, equalsIndex);
|
|
if (booleanFlags.has(flag)) {
|
|
return equalsIndex === -1 ? 1 : 0;
|
|
}
|
|
if (!valueFlags.has(flag)) {
|
|
return 0;
|
|
}
|
|
if (equalsIndex !== -1) {
|
|
return mode === "command-path" || arg.slice(equalsIndex + 1).trim() ? 1 : 0;
|
|
}
|
|
// Required Commander values include empty strings, flag-looking tokens, and `--`.
|
|
// Discovery must consume them before choosing startup policy; routes still validate values.
|
|
if (mode === "command-path") {
|
|
return args[index + 1] !== undefined ? 2 : 0;
|
|
}
|
|
return isValueToken(args[index + 1]) ? 2 : 0;
|
|
}
|
|
|
|
/** Parse command positionals while consuming known root and command options. */
|
|
export function getCommandPositionalsWithRootOptions(argv, options) {
|
|
return parseCommandArgsWithRootOptions(argv, options, false);
|
|
}
|
|
|
|
/** Preserve the leaf's raw arguments after consuming its root and parent options. */
|
|
export function getCommandArgsWithRootOptions(argv, options) {
|
|
return parseCommandArgsWithRootOptions(argv, options, true);
|
|
}
|
|
|
|
/** Keep option roles intact when another command must use the same root selectors. */
|
|
export function getCommandOptionsWithRootOptions(argv, options) {
|
|
return parseCommandArgsWithRootOptions(argv, options, false, true);
|
|
}
|
|
|
|
function parseCommandArgsWithRootOptions(argv, options, returnTail, returnOptions = false) {
|
|
const args = argv.slice(2);
|
|
const booleanFlags = new Set(options.booleanFlags ?? []);
|
|
const valueFlags = new Set(options.valueFlags ?? []);
|
|
const positionals = [];
|
|
const rootOptions = [];
|
|
const commandOptions = [];
|
|
let commandIndex = 0;
|
|
let literal = false;
|
|
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const arg = args[index];
|
|
if (arg === undefined) {
|
|
break;
|
|
}
|
|
if (!literal && arg === FLAG_TERMINATOR) {
|
|
if (options.mode !== "command-path") {
|
|
break;
|
|
}
|
|
literal = true;
|
|
continue;
|
|
}
|
|
const rootConsumed = literal
|
|
? 0
|
|
: options.mode === "command-path"
|
|
? consumeRootCommandOptionToken(args, index)
|
|
: consumeRootOptionToken(args, index);
|
|
if (rootConsumed > 0) {
|
|
// Gateway's post-command --dev bootstraps its workspace, not a root profile.
|
|
const destination = commandIndex > 0 && booleanFlags.has(arg) ? commandOptions : rootOptions;
|
|
destination.push(...args.slice(index, index + rootConsumed));
|
|
index += rootConsumed - 1;
|
|
continue;
|
|
}
|
|
if (!literal && arg.startsWith("-")) {
|
|
const optionConsumed = consumeKnownOptionToken(
|
|
args,
|
|
index,
|
|
booleanFlags,
|
|
valueFlags,
|
|
options.mode,
|
|
);
|
|
if (optionConsumed === 0 || commandIndex === 0) {
|
|
return null;
|
|
}
|
|
commandOptions.push(arg);
|
|
index += optionConsumed - 1;
|
|
continue;
|
|
}
|
|
if (commandIndex < options.commandPath.length) {
|
|
if (arg !== options.commandPath[commandIndex]) {
|
|
return null;
|
|
}
|
|
commandIndex += 1;
|
|
if (returnTail && commandIndex === options.commandPath.length) {
|
|
const tail = args.slice(index + 1);
|
|
// A downstream parser must not reactivate flags after an earlier literal boundary.
|
|
return literal ? [FLAG_TERMINATOR, ...tail] : tail;
|
|
}
|
|
continue;
|
|
}
|
|
positionals.push(arg);
|
|
if (!returnOptions && positionals.length === options.maxPositionals) {
|
|
return positionals;
|
|
}
|
|
}
|
|
|
|
return commandIndex < options.commandPath.length
|
|
? null
|
|
: returnOptions
|
|
? { rootOptions, commandOptions }
|
|
: positionals;
|
|
}
|