import { JUNCTION_FIELDS, PIPE_FIELDS, type ElementType } from "./constants.js"; import { CliError } from "./errors.js"; import type { OptionSchema, ParsedOptionValue, ParsedOptions } from "./types.js"; export function requireValue(argv: string[], index: number, option: string): string { const value = argv[index]; if (!value || value.startsWith("--")) throw new CliError("CLI 参数错误", "MISSING_OPTION_VALUE", `${option} requires a value`, 2); return value; } export function parseIntStrict(value: string, option: string): number { const parsed = Number.parseInt(value, 10); if (!Number.isFinite(parsed)) throw new CliError("CLI 参数错误", "INVALID_INTEGER", `${option} must be an integer`, 2); return parsed; } export function parseFloatStrict(value: string, option: string): number { const parsed = Number.parseFloat(value); if (!Number.isFinite(parsed)) throw new CliError("CLI 参数错误", "INVALID_NUMBER", `${option} must be a number`, 2); return parsed; } export function parseOptions(argv: string[], schema: OptionSchema = {}): ParsedOptions { const values: Record = {}; const positionals: string[] = []; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]!; if (!arg.startsWith("--")) { positionals.push(arg); continue; } const name = arg.slice(2); const kind = schema[name] ?? "string"; if (kind === "boolean") { values[name] = true; } else { const raw = requireValue(argv, ++i, arg); if (kind === "repeat") values[name] = [...asStringArray(values[name]), raw]; else if (kind === "number") values[name] = parseFloatStrict(raw, arg); else if (kind === "integer") values[name] = parseIntStrict(raw, arg); else values[name] = raw; } } return { values, positionals }; } export function required(values: Record, name: string): string | number | boolean | string[] { const value = values[name]; if (value === undefined || value === null || value === "") { throw new CliError("CLI 参数错误", "MISSING_PARAMETER", `Missing option '--${name}'`, 2); } return value; } export function requiredString(values: Record, name: string): string { const value = required(values, name); if (typeof value !== "string") throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be a string`, 2); return value; } export function requiredNumber(values: Record, name: string): number { const value = required(values, name); if (typeof value !== "number") throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be a number`, 2); return value; } export function requiredStringArray(values: Record, name: string): string[] { const value = required(values, name); if (!Array.isArray(value)) throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be repeatable`, 2); return value; } export function optionalString(values: Record, name: string): string | undefined { const value = values[name]; if (value === undefined) return undefined; if (typeof value !== "string") throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be a string`, 2); return value; } export function optionalNumber(values: Record, name: string): number | undefined { const value = values[name]; if (value === undefined) return undefined; if (typeof value !== "number") throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be a number`, 2); return value; } export function optionalStringArray(values: Record, name: string): string[] | undefined { const value = values[name]; if (value === undefined) return undefined; if (!Array.isArray(value)) throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be repeatable`, 2); return value; } export function asStringArray(value: ParsedOptionValue | undefined): string[] { if (value === undefined) return []; if (Array.isArray(value)) return value; throw new CliError("CLI 参数错误", "INVALID_PARAMETER", "repeat option received a non-array value", 2); } export function validateChoice(value: string, valid: T, option: string): T[number] { if (!valid.includes(value)) { throw new CliError("CLI 参数错误", `INVALID_${option.replace(/^--/, "").replaceAll("-", "_").toUpperCase()}`, `${option} must be one of: ${valid.join(", ")}`, 2); } return value as T[number]; } export function fieldsFor(type: ElementType): typeof PIPE_FIELDS | typeof JUNCTION_FIELDS { if (type === "pipe") return PIPE_FIELDS; if (type === "junction") return JUNCTION_FIELDS; throw new CliError("CLI 参数错误", "INVALID_TYPE", "--type must be one of: pipe, junction", 2); }