mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-11 07:56:50 +02:00
143 lines
3.7 KiB
TypeScript
143 lines
3.7 KiB
TypeScript
import { parse as parseShellTokens } from "shell-quote";
|
|
import { manPages } from "@/types/man";
|
|
import { extractGlobalFlagsFromTokens, type GlobalFlags } from "./global-flags";
|
|
|
|
export interface ParsedCommand {
|
|
commandName: string;
|
|
args: string[];
|
|
fullInput: string;
|
|
command?: (typeof manPages)[string];
|
|
props?: any;
|
|
error?: string;
|
|
globalFlags?: GlobalFlags;
|
|
}
|
|
|
|
/**
|
|
* Parses a command string into its components.
|
|
* Returns basic parsing info without executing argParser.
|
|
*
|
|
* Now supports:
|
|
* - Proper quote handling via shell-quote
|
|
* - Global flag extraction (--title, etc.)
|
|
*/
|
|
export function parseCommandInput(input: string): ParsedCommand {
|
|
const fullInput = input.trim();
|
|
|
|
// Pre-process: Escape $ to prevent shell-quote from expanding variables
|
|
// We use $me and $contacts as literal syntax, not shell variables
|
|
const DOLLAR_PLACEHOLDER = "___DOLLAR___";
|
|
const escapedInput = fullInput.replace(/\$/g, DOLLAR_PLACEHOLDER);
|
|
|
|
// Tokenize with quote support (on escaped input)
|
|
const rawTokens = parseShellTokens(escapedInput);
|
|
|
|
// Convert tokens to strings and restore $ characters
|
|
const tokens = rawTokens.map((token) => {
|
|
const str = typeof token === "string" ? token : String(token);
|
|
return str.replace(new RegExp(DOLLAR_PLACEHOLDER, "g"), "$");
|
|
});
|
|
|
|
// Extract global flags before command parsing
|
|
let globalFlags: GlobalFlags = {};
|
|
let remainingTokens = tokens;
|
|
|
|
try {
|
|
const extracted = extractGlobalFlagsFromTokens(tokens);
|
|
globalFlags = extracted.globalFlags;
|
|
remainingTokens = extracted.remainingTokens;
|
|
} catch (error) {
|
|
// Global flag parsing error
|
|
return {
|
|
commandName: "",
|
|
args: [],
|
|
fullInput,
|
|
error:
|
|
error instanceof Error ? error.message : "Failed to parse global flags",
|
|
};
|
|
}
|
|
|
|
// Parse command from remaining tokens
|
|
const commandName = remainingTokens[0]?.toLowerCase() || "";
|
|
const args = remainingTokens.slice(1);
|
|
|
|
const command = commandName && manPages[commandName];
|
|
|
|
if (!commandName) {
|
|
return {
|
|
commandName: "",
|
|
args: [],
|
|
fullInput: "",
|
|
globalFlags,
|
|
error: "No command provided",
|
|
};
|
|
}
|
|
|
|
if (!command) {
|
|
return {
|
|
commandName,
|
|
args,
|
|
fullInput,
|
|
globalFlags,
|
|
error: `Unknown command: ${commandName}`,
|
|
};
|
|
}
|
|
|
|
return {
|
|
commandName,
|
|
args,
|
|
fullInput,
|
|
command,
|
|
globalFlags,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Executes the argParser for a command and returns complete parsed command data.
|
|
* This is async to support commands like profile that use NIP-05 resolution.
|
|
*/
|
|
export async function executeCommandParser(
|
|
parsed: ParsedCommand,
|
|
activeAccountPubkey?: string,
|
|
): Promise<ParsedCommand> {
|
|
if (!parsed.command) {
|
|
return parsed; // Already has error, return as-is
|
|
}
|
|
|
|
try {
|
|
// Use argParser if available, otherwise use defaultProps
|
|
const props = parsed.command.argParser
|
|
? await Promise.resolve(
|
|
parsed.command.argParser(parsed.args, activeAccountPubkey),
|
|
)
|
|
: parsed.command.defaultProps || {};
|
|
|
|
return {
|
|
...parsed,
|
|
props,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
...parsed,
|
|
error:
|
|
error instanceof Error
|
|
? error.message
|
|
: "Failed to parse command arguments",
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Complete command parsing pipeline: parse input → execute argParser.
|
|
* Returns fully parsed command ready for window creation.
|
|
*/
|
|
export async function parseAndExecuteCommand(
|
|
input: string,
|
|
activeAccountPubkey?: string,
|
|
): Promise<ParsedCommand> {
|
|
const parsed = parseCommandInput(input);
|
|
if (parsed.error || !parsed.command) {
|
|
return parsed;
|
|
}
|
|
return executeCommandParser(parsed, activeAccountPubkey);
|
|
}
|