dehub/cmd/dehub/dcmd/dcmd.go
mediocregopher 326de2afc6 Fully implement credential commits
---
type: change
message: |-
  Fully implement credential commits

  The actual commit objects and related refactoring had already been done, this
  commit takes the next step of implementing the access control changes, tests for
  verification, and refactoring of the dehub command to support multiple commit
  message types (as well as a small fix to dcmd).
change_hash: AJyuAR0koVoe+uPBisa5qXsbW8YhlgOKNhnvy9uv7hQ8
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5tVzoACgkQlcRvpqQRSKyznw//b9lWd4V4G81cFwGAxZtJ3JiFpspYdtTAUUcLi9nogGsDmqkkSQxLdmBCT99QtaenKsxpad+9sXhkZpgWF/AyCX9pN6TTlMKuRcDXeoMUjeKjRpRhCHN0Lt8Sz80NDPYIa81r9cH0o1987GirgGmDEkYNDAFPDdGNDcCad/LLnG+ONwOl9WEM1q5O4etUPurTywlBiELDjHxeLzqgxCo8fMaMejW6mxnMDV6DIHiX6INWZAAG66HPVetmq6EVl9bnFgZmgKNzqKzFVZJRdGQNbhR/WzlOh0HPyJGwCveZPM5Zjd/dpfQUYEGGprVKc0G0YVNU2Hcz6O7hqafGGxWpCFW6zKrNmBRaW2u2zjVJD4ukmWn9gFuKJKhs0kyawRTbHNIX+gonYv9lDFO3cZ5qcsJbSAYSHrCav121z0GsQDoFJMJDQnP0syEEbAaxdQe7Bd7bmOM3SpCOLJLF1+X7Srrq5//u6fiFDxQ82Ylo3hG/r7/QT/vSipUCglx4POq33+z8VEHGhVfl4dgSU6OgIV/S7evKC7EiS/jh/xywU44RHpxFhwS3hthHxZqgRIHTm65DqGYWWZds2Hkr29TTRajuf0t4MxqY2MrLAhNJUc6OmrVN+lWMmm/z1FEhfrOvZ8v7mOSqTKwkvbsZzk5mpeo2RrLdNnnWvTCy87FpA48=
  account: mediocregopher
2020-03-14 16:14:18 -06:00

171 lines
4.2 KiB
Go

// Package dcmd implements command and sub-command parsing and runtime
// management. It wraps the stdlib flag package as well, to incorporate
// configuration into the mix.
package dcmd
import (
"context"
"errors"
"flag"
"fmt"
"os"
"sort"
"strings"
)
func exitErr(err error) {
fmt.Fprintf(os.Stderr, "exiting: %v\n", err)
os.Stderr.Sync()
os.Stdout.Sync()
os.Exit(1)
}
type subCmd struct {
name, descr string
run func(context.Context, *Cmd)
}
// Cmd wraps a flag.FlagSet instance to provide extra functionality that dehub
// wants, specifically around sub-command support.
type Cmd struct {
flagSet *flag.FlagSet
binary string // only gets set on root Cmd, during Run
subCmds []subCmd
// these fields get set by the parent Cmd, if this is a sub-command.
name string
args []string
parent *Cmd
}
// New initializes and returns an empty Cmd instance.
func New() *Cmd {
return &Cmd{}
}
func (cmd *Cmd) getFlagSet() *flag.FlagSet {
if cmd.flagSet == nil {
cmd.flagSet = flag.NewFlagSet(cmd.name, flag.ContinueOnError)
}
return cmd.flagSet
}
// FlagSet returns a flag.Cmd instance on which parameter creation methods can
// be called, e.g. String(...) or Int(...).
func (cmd *Cmd) FlagSet() *flag.FlagSet {
return cmd.getFlagSet()
}
// SubCmd registers a sub-command of this Cmd.
//
// A new Cmd will be instantiated when this sub-command is picked on the
// command-line during this Cmd's Run method. The Context returned from that Run
// and the new Cmd will be passed into the callback given here. The sub-command
// should then be performed in the same manner as this Cmd is performed
// (including setting flags, adding sub-sub-commands, etc...)
func (cmd *Cmd) SubCmd(name, descr string, run func(context.Context, *Cmd)) {
cmd.subCmds = append(cmd.subCmds, subCmd{
name: name,
descr: descr,
run: run,
})
// it's not the most efficient to do this here, but it is the easiest
sort.Slice(cmd.subCmds, func(i, j int) bool {
return cmd.subCmds[i].name < cmd.subCmds[j].name
})
}
func (cmd *Cmd) printUsageHead(subCmdTitle string) {
var title string
if cmd.parent == nil {
title = fmt.Sprintf("USAGE: %s [flags]", cmd.binary)
} else {
title = fmt.Sprintf("%s [%s flags]", cmd.name, cmd.name)
}
if subCmdTitle != "" {
title += " " + subCmdTitle
} else if len(cmd.subCmds) > 0 {
title += fmt.Sprint(" <sub-command> [sub-command flags]")
}
if cmd.parent == nil {
fmt.Printf("\n%s\n\n", title)
fmt.Print("### FLAGS ###\n\n")
} else {
cmd.parent.printUsageHead(title)
fmt.Printf("### %s FLAGS ###\n\n", strings.ToUpper(cmd.name))
}
cmd.getFlagSet().PrintDefaults()
fmt.Print("\n")
}
// Run performs the comand. It starts by parsing all flags in the Cmd's FlagSet,
// and possibly exiting with a usage message if appropriate. It will then
// perform the given body callback, and then perform any sub-commands (if
// selected).
//
// The context returned from the callback will be passed into the callback
// (given to SubCmd) of any sub-commands which are run, and so on.
func (cmd *Cmd) Run(body func() (context.Context, error)) {
args := cmd.args
if cmd.parent == nil {
cmd.binary, args = os.Args[0], os.Args[1:]
}
fs := cmd.getFlagSet()
fs.Usage = func() {
cmd.printUsageHead("")
if len(cmd.subCmds) == 0 {
return
}
fmt.Printf("### SUB-COMMANDS ###\n\n")
for _, subCmd := range cmd.subCmds {
fmt.Printf("\t%s : %s\n", subCmd.name, subCmd.descr)
}
fmt.Println("")
}
if err := fs.Parse(args); err != nil {
exitErr(err)
return
}
ctx, err := body()
if err != nil {
exitErr(err)
}
// body has run, now do sub-command (if there is one)
subArgs := fs.Args()
if len(cmd.subCmds) == 0 {
return
} else if len(subArgs) == 0 && len(cmd.subCmds) > 0 {
fs.Usage()
exitErr(errors.New("no sub-command selected"))
}
// now find that sub-command
subCmdName := strings.ToLower(subArgs[0])
var subCmd subCmd
var subCmdOk bool
for _, subCmd = range cmd.subCmds {
if subCmdOk = subCmd.name == subCmdName; subCmdOk {
break
}
}
if !subCmdOk {
fs.Usage()
exitErr(fmt.Errorf("unknown command %q", subCmdName))
}
subCmdCmd := New()
subCmdCmd.name = subCmd.name
subCmdCmd.args = subArgs[1:]
subCmdCmd.parent = cmd
subCmd.run(ctx, subCmdCmd)
}