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 } 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 logger *mlog.Logger 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, logger *mlog.Logger, 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} if !subCmd.noNetwork { fs.FlagSet.StringVar( &fs.network, "network", "", "Which network to perform the command against, if more than one is joined. Can be ID, name, or domain", ) } return subCmdCtx{ Context: ctx, logger: logger, 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) 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 [-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, ctx.logger, subCmd, args, append(ctx.subCmdNames, subCmdName), ) if err := subCmd.do(nextSubCmdCtx); err != nil { return err } return nil }