isle/go/cmd/entrypoint/sub_cmd.go

194 lines
4.0 KiB
Go

package main
import (
"context"
"fmt"
"isle/daemon"
"os"
"strings"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"github.com/spf13/pflag"
)
type flagSet struct {
*pflag.FlagSet
network string
logLevel logLevelFlag
}
type subCmd struct {
name string
descr string
do func(subCmdCtx) error
// If set then the name will be allowed to be suffixed with this string.
plural string
// noNetwork, if true, means the call doesn't require a network to be
// specified on the command-line if there are more than one networks
// configured.
noNetwork bool
// Extra arguments on the command-line will be passed through to some
// underlying command.
passthroughArgs bool
}
// subCmdCtx contains all information available to a subCmd's do method.
type subCmdCtx struct {
context.Context
subCmd subCmd // the subCmd itself
args []string // command-line arguments, excluding the subCmd itself.
subCmdNames []string // names of subCmds so far, including this one
flags *flagSet
}
func newSubCmdCtx(
ctx context.Context,
subCmd subCmd,
args []string,
subCmdNames []string,
) subCmdCtx {
flags := pflag.NewFlagSet(subCmd.name, pflag.ExitOnError)
flags.Usage = func() {
var passthroughStr string
if subCmd.passthroughArgs {
passthroughStr = " [--] [args...]"
}
fmt.Fprintf(
os.Stderr, "%s[-h|--help] [%s flags...]%s\n\n",
usagePrefix(subCmdNames), subCmd.name, passthroughStr,
)
fmt.Fprintf(os.Stderr, "%s FLAGS:\n\n", strings.ToUpper(subCmd.name))
fmt.Fprintln(os.Stderr, flags.FlagUsages())
os.Stderr.Sync()
os.Exit(2)
}
fs := &flagSet{
FlagSet: flags,
logLevel: logLevelFlag{mlog.LevelInfo},
}
if !subCmd.noNetwork {
fs.FlagSet.StringVar(
&fs.network, "network", "", "Which network to perform the command against, if more than one is joined. Can be an ID, name, or domain.",
)
}
fs.FlagSet.VarP(
&fs.logLevel,
"log-level", "l",
"Maximum log level to output. Can be DEBUG, CHILD, INFO, WARN, ERROR, or FATAL.",
)
return subCmdCtx{
Context: ctx,
subCmd: subCmd,
args: args,
subCmdNames: subCmdNames,
flags: fs,
}
}
func usagePrefix(subCmdNames []string) string {
subCmdNamesStr := strings.Join(subCmdNames, " ")
if subCmdNamesStr != "" {
subCmdNamesStr += " "
}
return fmt.Sprintf("\nUSAGE: %s %s", os.Args[0], subCmdNamesStr)
}
func (ctx subCmdCtx) logger() *mlog.Logger {
return mlog.NewLogger(&mlog.LoggerOpts{
MaxLevel: ctx.flags.logLevel.Int(),
})
}
func (ctx subCmdCtx) withParsedFlags() (subCmdCtx, error) {
ctx.flags.VisitAll(func(f *pflag.Flag) {
if f.Shorthand == "h" {
panic(fmt.Sprintf("flag %+v has reserved shorthand `-h`", f))
}
if f.Name == "help" {
panic(fmt.Sprintf("flag %+v has reserved name `--help`", f))
}
})
if err := ctx.flags.Parse(ctx.args); err != nil {
return ctx, err
}
ctx.Context = daemon.WithNetwork(ctx.Context, ctx.flags.network)
return ctx, nil
}
func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
printUsageExit := func(subCmdName string) {
fmt.Fprintf(os.Stderr, "unknown sub-command %q\n", subCmdName)
fmt.Fprintf(
os.Stderr,
"%s<subCmd> [-h|--help] [sub-command flags...]\n",
usagePrefix(ctx.subCmdNames),
)
fmt.Fprintf(os.Stderr, "\nSUB-COMMANDS:\n\n")
for _, subCmd := range subCmds {
name := subCmd.name
if subCmd.plural != "" {
name += "(" + subCmd.plural + ")"
}
fmt.Fprintf(os.Stderr, " %s\t%s\n", name, subCmd.descr)
}
fmt.Fprintf(os.Stderr, "\n")
os.Stderr.Sync()
os.Exit(2)
}
args := ctx.args
if len(args) == 0 {
printUsageExit("")
}
subCmdsMap := map[string]subCmd{}
for _, subCmd := range subCmds {
subCmdsMap[subCmd.name] = subCmd
if subCmd.plural != "" {
subCmdsMap[subCmd.name+subCmd.plural] = subCmd
}
}
subCmdName, args := args[0], args[1:]
subCmd, ok := subCmdsMap[subCmdName]
if !ok {
printUsageExit(subCmdName)
}
nextSubCmdCtx := newSubCmdCtx(
ctx.Context,
subCmd,
args,
append(ctx.subCmdNames, subCmdName),
)
if err := subCmd.do(nextSubCmdCtx); err != nil {
return err
}
return nil
}