mirror of
https://github.com/chenasraf/massarg.git
synced 2026-05-18 01:39:05 +00:00
feat: built-in help command + flag
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { z } from "zod"
|
||||
import { isZodError, ValidationError } from "./error"
|
||||
import { isZodError, ParseError, ValidationError } from "./error"
|
||||
import { HelpGenerator } from "./help"
|
||||
import MassargOption, { MassargFlag, OptionConfig, TypedOptionConfig } from "./option"
|
||||
import MassargOption, {
|
||||
MassargFlag,
|
||||
OptionConfig,
|
||||
TypedOptionConfig,
|
||||
MassargHelpFlag,
|
||||
} from "./option"
|
||||
import { setOrPush } from "./utils"
|
||||
|
||||
export const CommandConfig = <RunArgs extends z.ZodType>(args: RunArgs) =>
|
||||
@@ -13,6 +18,9 @@ export const CommandConfig = <RunArgs extends z.ZodType>(args: RunArgs) =>
|
||||
.function()
|
||||
.args(args, z.any())
|
||||
.returns(z.union([z.promise(z.void()), z.void()])) as z.ZodType<Runner<z.infer<RunArgs>>>,
|
||||
bindHelpCommand: z.boolean().optional(),
|
||||
bindHelpOption: z.boolean().optional(),
|
||||
// argsHint: z.string().optional(),
|
||||
})
|
||||
|
||||
export type CommandConfig<T = unknown> = z.infer<ReturnType<typeof CommandConfig<z.ZodType<T>>>>
|
||||
@@ -39,6 +47,12 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
this.description = options.description
|
||||
this.aliases = options.aliases ?? []
|
||||
this._run = options.run
|
||||
if (options.bindHelpCommand) {
|
||||
this.command(new MassargHelpCommand())
|
||||
}
|
||||
if (options.bindHelpOption) {
|
||||
this.option(new MassargHelpFlag())
|
||||
}
|
||||
}
|
||||
|
||||
command<A extends ArgsObject = Args>(config: CommandConfig<A>): MassargCommand<Args>
|
||||
@@ -122,7 +136,12 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
if (command) {
|
||||
return command.parse(_argv, this.args, parent ?? this)
|
||||
}
|
||||
// TODO pass all un-handled args to an "args" option
|
||||
const defaultOption = this.options.find((o) => o.isDefault)
|
||||
if (defaultOption) {
|
||||
console.log("Parsing default option")
|
||||
_argv = this.parseOption(`--${defaultOption.name}`, [arg, ..._argv])
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (this._run) {
|
||||
this._run({ ...args, ...this.args } as Args, parent ?? this)
|
||||
@@ -138,6 +157,7 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
message: "Unknown option",
|
||||
})
|
||||
}
|
||||
console.log("parseOption", [arg, ...argv])
|
||||
const res = option._parseDetails([arg, ...argv])
|
||||
this.args[res.key as keyof Args] = setOrPush<Args[keyof Args]>(
|
||||
res.value,
|
||||
@@ -189,11 +209,12 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
||||
}
|
||||
|
||||
export class MassargHelpCommand<T extends ArgsObject = ArgsObject> extends MassargCommand<T> {
|
||||
constructor(config: Partial<Omit<CommandConfig<T>, "run">>) {
|
||||
constructor(config: Partial<Omit<CommandConfig<T>, "run">> = {}) {
|
||||
super({
|
||||
name: "help",
|
||||
aliases: ["h"],
|
||||
description: "Print help",
|
||||
description: "Print help for this command, or a subcommand if specified",
|
||||
// argsHint: "[command]",
|
||||
run: (args, parent) => {
|
||||
if (args.command) {
|
||||
const command = parent.commands.find((c) => c.name === args.command)
|
||||
@@ -201,10 +222,11 @@ export class MassargHelpCommand<T extends ArgsObject = ArgsObject> extends Massa
|
||||
command.printHelp()
|
||||
return
|
||||
} else {
|
||||
throw new ValidationError({
|
||||
throw new ParseError({
|
||||
path: ["command"],
|
||||
code: "unknown_command",
|
||||
message: "Unknown command",
|
||||
received: args.command,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ const removeCmd = new MassargCommand<{ component: string }>({
|
||||
const args = massarg<A>({
|
||||
name: "my-cli",
|
||||
description: "This is an example CLI",
|
||||
bindHelpOption: true,
|
||||
bindHelpCommand: true,
|
||||
})
|
||||
.main((opts, parser) => {
|
||||
console.log("Main command - printing all opts")
|
||||
|
||||
@@ -49,6 +49,7 @@ export default class MassargOption<T = unknown> {
|
||||
aliases: string[]
|
||||
parse: (value: string) => T
|
||||
isArray: boolean
|
||||
isDefault: boolean
|
||||
|
||||
constructor(options: OptionConfig<T>) {
|
||||
OptionConfig(z.any()).parse(options)
|
||||
@@ -58,6 +59,7 @@ export default class MassargOption<T = unknown> {
|
||||
this.aliases = options.aliases
|
||||
this.parse = options.parse ?? ((x) => x as unknown as T)
|
||||
this.isArray = options.array ?? false
|
||||
this.isDefault = options.isDefault ?? false
|
||||
}
|
||||
|
||||
static fromTypedConfig<T = unknown>(config: TypedOptionConfig<T>): MassargOption<T> {
|
||||
@@ -70,10 +72,8 @@ export default class MassargOption<T = unknown> {
|
||||
|
||||
_parseDetails(argv: string[]): ArgvValue<T> {
|
||||
// TODO: support --option=value
|
||||
argv.shift()
|
||||
let input = ""
|
||||
try {
|
||||
input = argv.shift()!
|
||||
if (!this._match(argv[0])) {
|
||||
throw new ParseError({
|
||||
path: [this.name],
|
||||
@@ -82,6 +82,8 @@ export default class MassargOption<T = unknown> {
|
||||
received: JSON.stringify(argv[0]),
|
||||
})
|
||||
}
|
||||
argv.shift()
|
||||
input = argv.shift()!
|
||||
const value = this.parse(input)
|
||||
return { key: this.name, value, argv }
|
||||
} catch (e) {
|
||||
@@ -103,6 +105,7 @@ export default class MassargOption<T = unknown> {
|
||||
}
|
||||
|
||||
_match(arg: string): boolean {
|
||||
if (!arg) return false
|
||||
// full prefix
|
||||
if (arg.startsWith(OPT_FULL_PREFIX)) {
|
||||
// negate full prefix
|
||||
@@ -225,7 +228,7 @@ export class MassargFlag extends MassargOption<boolean> {
|
||||
}
|
||||
|
||||
export class MassargHelpFlag extends MassargFlag {
|
||||
constructor(config: Partial<Omit<OptionConfig<boolean>, "parse">>) {
|
||||
constructor(config: Partial<Omit<OptionConfig<boolean>, "parse">> = {}) {
|
||||
super({
|
||||
name: "help",
|
||||
description: "Show this help message",
|
||||
|
||||
Reference in New Issue
Block a user