UNPKG

commandstruct

Version:
443 lines (434 loc) 13 kB
import sade from "sade"; import { Box } from "getbox"; //#region src/arg.ts var Arg = class { constructor(_type) { this._type = _type; } optional() { this._type = "optional"; return this; } static toObject(arg$1) { return { type: arg$1._type }; } static toString(arg$1, name) { if (arg$1._type === "required") return `<${name}>`; else return `[${name}]`; } }; function arg() { return new Arg("required"); } //#endregion //#region src/errors.ts var CommandError = class extends Error { constructor(reason, message) { super(message); this.reason = reason; this.message = message; } }; //#endregion //#region src/flag.ts var Flag = class Flag { constructor(_desc, _char, _param, _preserveCase, _negate) { this._desc = _desc; this._char = _char; this._param = _param; this._preserveCase = _preserveCase; this._negate = _negate; } char(char) { this._char = char; return this; } requiredParam(type) { this._param = { _type: "required", type }; return this; } optionalParam(type, defaultValue) { this._param = { _type: "optional", type, defaultValue }; return this; } withNegated(description) { this._negate = description; return this; } preserveCase() { this._preserveCase = true; return this; } static toObject(flag$1) { return { desc: flag$1._desc, char: flag$1._char, param: flag$1._param, negate: flag$1._negate, preserveCase: flag$1._preserveCase }; } static toString(flag$1, name) { let str = `--${flag$1._preserveCase ? name : Flag.toKebabCase(name)}`; if (flag$1._char) str = `-${flag$1._char}, ${str}`; return str; } static toNegatedString(flag$1, name) { return `--no-${flag$1._preserveCase ? name : Flag.toKebabCase(name)}`; } static isNegated(str) { return str.startsWith("no-"); } static toKebabCase(str) { return str.split("").map((char, index) => { if (index === 0) return char.toLowerCase(); return char.toLowerCase() !== char ? `-${char.toLowerCase()}` : char; }).join(""); } }; function flag(description) { return new Flag(description, void 0, void 0, false, void 0); } //#endregion //#region src/utils.ts function registerFlags(program$1, flags) { for (const [name, flag$1] of Object.entries(flags)) { const flagObj = Flag.toObject(flag$1); if (flagObj.char !== void 0 && flagObj.char.length !== 1) throw new CommandError("invalid_flag", `option ${"`" + Flag.toString(flag$1, name) + "`"} char must be 1 character`); if (flagObj.param && flagObj.negate !== void 0) throw new CommandError("invalid_flag", `negated option ${name} ${"`" + Flag.toString(flag$1, name) + "`"} cannot have param`); const key = flagObj.preserveCase ? name : Flag.toKebabCase(name); if (Flag.isNegated(key)) { if (flagObj.param) throw new CommandError("invalid_flag", `negated option ${name} ${"`" + Flag.toString(flag$1, name) + "`"} cannot have param`); if (flagObj.char !== void 0) throw new CommandError("invalid_flag", `negated option ${name} ${"`" + Flag.toString(flag$1, name) + "`"} cannot have char`); if (flagObj.negate !== void 0) throw new CommandError("invalid_flag", `negated option ${name} ${"`" + Flag.toString(flag$1, name) + "`"} is already negated`); const existingKey = flags[key.slice(3)]; if (existingKey && Flag.toObject(existingKey).param) throw new CommandError("invalid_flag", `negated option ${name} ${"`" + Flag.toString(flag$1, name) + "`"} can only be used with boolean flags`); } program$1.option(Flag.toString(flag$1, name), flagObj.desc, flagObj.negate && true); if (flagObj.negate !== void 0) program$1.option(Flag.toNegatedString(flag$1, name), flagObj.negate); } } function commandUsage(baseCmd, cmd, args) { let usage = cmd; if (baseCmd) usage = baseCmd + " " + cmd; let hasOptional = false; for (const [name, arg$1] of Object.entries(args)) { const argObj = Arg.toObject(arg$1); if (argObj.type === "required" && hasOptional) throw new CommandError("invalid_arg", `required positional argument ${"`" + Arg.toString(arg$1, name) + "`"} cannot appear after an optional argument`); hasOptional ||= argObj.type === "optional"; usage += " " + Arg.toString(arg$1, name); } return usage; } function commandContext(args, flags, programFlags, fnArgs) { const mergedFlags = { ...programFlags, ...flags }; const _args = Object.keys(args).reduce((acc, name, index) => { acc[name] = fnArgs[index]; return acc; }, {}); const { _, ...opts } = fnArgs[fnArgs.length - 1]; for (const [name, flag$1] of Object.entries(mergedFlags)) { const flagObj = Flag.toObject(flag$1); const key = flagObj.preserveCase ? name : Flag.toKebabCase(name); const value = opts[key]; if (!flagObj.param) { if (flagObj.negate || Flag.isNegated(key)) { const negatedKey = flagObj.negate ? `no-${key}` : key; delete opts[negatedKey]; } if (value === void 0) { if (Flag.isNegated(key)) { const negatedKey = key.slice(3); if (opts[negatedKey] === void 0) opts[negatedKey] = true; continue; } opts[key] = false; if (flagObj.char) opts[flagObj.char] = false; } else if (typeof value !== "boolean") { opts[key] = true; if (flagObj.char) opts[flagObj.char] = true; } continue; } if (value === void 0) { if (flagObj.param._type === "optional") { if (flagObj.param.defaultValue !== void 0) { opts[key] = flagObj.param.defaultValue; if (flagObj.char) opts[flagObj.char] = flagObj.param.defaultValue; } continue; } throw new CommandError("invalid_flag", `option ${"`" + Flag.toString(flag$1, name) + "`"} value is missing`); } if (flagObj.param.type === "string" && typeof value !== "string") if (typeof value !== "object") { if (value === true || value === void 0) throw new CommandError("invalid_flag", `option ${"`" + Flag.toString(flag$1, name) + "`"} value is missing`); const parsedValue = String(value); opts[key] = parsedValue; if (flagObj.char) opts[flagObj.char] = parsedValue; } else if (Array.isArray(value)) { const val = value[value.length - 1]; const parsedValue = val === true ? "" : String(val); opts[key] = parsedValue; if (flagObj.char) opts[flagObj.char] = parsedValue; } else throw new CommandError("invalid_flag", `option ${"`" + Flag.toString(flag$1, name) + "`"} value is not a string`); if (flagObj.param.type === "number" && typeof value !== "number") { const parsedValue = Number(value); if (typeof value !== "string" || Number.isNaN(parsedValue)) throw new CommandError("invalid_flag", `option ${"`" + Flag.toString(flag$1, name) + "`"} value is not a number`); opts[key] = parsedValue; if (flagObj.char) opts[flagObj.char] = parsedValue; } if (flagObj.param.type === "array") if (Array.isArray(value)) { const parsedValue = value.map((val) => val === true ? "" : String(val)); opts[key] = parsedValue; if (flagObj.char) opts[flagObj.char] = parsedValue; } else { if (typeof value === "object" || value === true) throw new CommandError("invalid_flag", `option ${"`" + Flag.toString(flag$1, name) + "`"} value is not an array`); const parsedValue = [String(value)]; opts[key] = parsedValue; if (flagObj.char) opts[flagObj.char] = parsedValue; } } return { args: _args, flags: opts, restArgs: _ }; } //#endregion //#region src/command.ts var Command = class { constructor(options) { this.options = options; } command(box, options) { const { program: program$1, programFlags, baseCmd, defaultCmd } = options; const usage = commandUsage(baseCmd, this.options.name, this.options.args); const command$1 = program$1.command(usage, this.options.description, { default: defaultCmd === this }); if (this.options.aliases.length) command$1.alias(...this.options.aliases); for (const example of this.options.examples) command$1.example(example); registerFlags(command$1, this.options.flags); command$1.action((...fnArgs) => { const ctx = commandContext(this.options.args, this.options.flags, programFlags, fnArgs); return this.options.action(ctx, box); }); for (const subcommand of this.options.subcommands) subcommand.command(box, { program: program$1, programFlags, baseCmd: baseCmd ? baseCmd + " " + this.options.name : this.options.name, defaultCmd: defaultCmd === this ? void 0 : defaultCmd }); } }; var CommandBuilder = class { options; constructor(name) { this.options = { name, description: void 0, aliases: [], examples: [], args: {}, flags: {}, subcommands: [] }; } describe(description) { this.options.description = description; return this; } alias(...aliases) { this.options.aliases.push(...aliases); return this; } example(example) { this.options.examples.push(example); return this; } args(args) { this.options.args = args; return this; } flags(flags) { this.options.flags = flags; return this; } programFlags() { return this; } subcommands(...commands) { this.options.subcommands.push(...commands); return this; } action(fn) { return new Command({ ...this.options, action: fn }); } }; function command(name) { return new CommandBuilder(name); } //#endregion //#region src/run.ts async function run(cli, options, argv = process.argv) { try { const output = cli.parse(argv, { lazy: true, unknown: options?.errorOnUnknown === true ? () => {} : options?.errorOnUnknown === false ? void 0 : options?.errorOnUnknown }); if (!output) return; return await output.handler.apply(null, output.args); } catch (err) { if (process.exitCode === void 0) process.exitCode = 1; if (options?.onError) options.onError(err); else if (err instanceof CommandError) console.error("error:", err.message); else throw err; } } //#endregion //#region src/program.ts var Program = class { constructor(box, options) { this.box = box; this.options = options; } program() { const program$1 = sade(this.options.name); if (this.options.version) program$1.version(this.options.version); if (this.options.description) program$1.describe(this.options.description); for (const example of this.options.examples) program$1.example(example); registerFlags(program$1, this.options.flags); for (const command$1 of this.options.commands) command$1.command(this.box, { program: program$1, programFlags: this.options.flags, defaultCmd: this.options.default }); return program$1; } run(options, argv) { return run(this.program(), options, argv); } }; var ProgramBuilder = class { options; constructor(box, name) { this.box = box; this.options = { name, version: void 0, description: void 0, examples: [], flags: {}, commands: [], default: void 0 }; } version(version) { this.options.version = version; return this; } describe(description) { this.options.description = description; return this; } example(example) { this.options.examples.push(example); return this; } flags(flags) { this.options.flags = flags; return this; } commands(...commands) { this.options.commands.push(...commands); return this; } default(command$1) { this.options.default = command$1; return this; } build() { return new Program(this.box, this.options); } }; function program(name, box = new Box()) { return new ProgramBuilder(box, name); } //#endregion //#region src/single.ts var SingleProgram = class { constructor(box, options) { this.box = box; this.options = options; } program() { const program$1 = sade(commandUsage(void 0, this.options.name, this.options.args), true); if (this.options.version) program$1.version(this.options.version); if (this.options.description) program$1.describe(this.options.description); for (const example of this.options.examples) program$1.example(example); registerFlags(program$1, this.options.flags); return program$1.action((...fnArgs) => { const ctx = commandContext(this.options.args, this.options.flags, {}, fnArgs); return this.options.action(ctx, this.box); }); } run(options, argv) { return run(this.program(), options, argv); } }; var SingleProgramBuilder = class { options; constructor(box, name) { this.box = box; this.options = { name, version: void 0, description: void 0, examples: [], args: {}, flags: {} }; } version(version) { this.options.version = version; return this; } describe(description) { this.options.description = description; return this; } example(example) { this.options.examples.push(example); return this; } args(args) { this.options.args = args; return this; } flags(flags) { this.options.flags = flags; return this; } action(fn) { return new SingleProgram(this.box, { ...this.options, action: fn }); } }; function singleProgram(name, box = new Box()) { return new SingleProgramBuilder(box, name); } //#endregion export { Arg, Command, CommandError, Flag, Program, SingleProgram, arg, command, flag, program, run, singleProgram };