From 0d7c1a7931f62ab89ed144f09f3bb0bd077e6187 Mon Sep 17 00:00:00 2001 From: Cory Bennett Date: Tue, 6 Mar 2018 22:34:49 -0800 Subject: [PATCH] refactor to simplify main --- cmd/jira/main.go | 280 +---------------------------------------------- jiracli/cli.go | 13 ++- jiracli/log.go | 43 ++++++++ jiracli/usage.go | 198 +++++++++++++++++++++++++++++++++ 4 files changed, 254 insertions(+), 280 deletions(-) create mode 100644 jiracli/log.go create mode 100644 jiracli/usage.go diff --git a/cmd/jira/main.go b/cmd/jira/main.go index 41df8115..329617c2 100644 --- a/cmd/jira/main.go +++ b/cmd/jira/main.go @@ -3,34 +3,19 @@ package main import ( "fmt" "os" - "os/exec" "path/filepath" - "regexp" - "runtime" "runtime/debug" - "strconv" - "syscall" "github.com/coryb/figtree" - "github.com/coryb/kingpeon" "github.com/coryb/oreo" - jira "gopkg.in/Netflix-Skunkworks/go-jira.v1" "gopkg.in/Netflix-Skunkworks/go-jira.v1/jiracli" "gopkg.in/Netflix-Skunkworks/go-jira.v1/jiracmd" - kingpin "gopkg.in/alecthomas/kingpin.v2" "gopkg.in/op/go-logging.v1" ) var ( - log = logging.MustGetLogger("jira") - defaultFormat = func() string { - format := os.Getenv("JIRA_LOG_FORMAT") - if format != "" { - return format - } - return "%{color}%{level:-5s}%{color:reset} %{message}" - }() + log = logging.MustGetLogger("jira") ) func handleExit() { @@ -44,138 +29,10 @@ func handleExit() { } } -func increaseLogLevel(verbosity int) { - logging.SetLevel(logging.GetLevel("")+logging.Level(verbosity), "") - if logging.GetLevel("") > logging.DEBUG { - oreo.TraceRequestBody = true - oreo.TraceResponseBody = true - } -} - -var usage = `{{define "FormatCommand"}}\ -{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ -{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ -{{end}}\ - -{{define "FormatBriefCommands"}}\ -{{range .FlattenedCommands}}\ -{{if not .Hidden}}\ - {{ print .FullCommand ":" | printf "%-20s"}} {{.Help}} -{{end}}\ -{{end}}\ -{{end}}\ - -{{define "FormatCommands"}}\ -{{range .FlattenedCommands}}\ -{{if not .Hidden}}\ - {{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} -{{.Help|Wrap 4}} -{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}} -{{end}}\ -{{end}}\ -{{end}}\ - -{{define "FormatUsage"}}\ -{{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} -{{if .Help}} -{{.Help|Wrap 0}}\ -{{end}}\ - -{{end}}\ - -{{if .Context.SelectedCommand}}\ -usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatCommand" .Context.SelectedCommand}} -{{if .Context.SelectedCommand.Aliases }}\ -{{range $top := .App.Commands}}\ -{{if eq $top.FullCommand $.Context.SelectedCommand.FullCommand}}\ -{{range $alias := $.Context.SelectedCommand.Aliases}}\ -alias: {{$.App.Name}} {{$alias}}{{template "FormatCommand" $.Context.SelectedCommand}} -{{end}}\ -{{else}}\ -{{range $sub := $top.Commands}}\ -{{if eq $sub.FullCommand $.Context.SelectedCommand.FullCommand}}\ -{{range $alias := $.Context.SelectedCommand.Aliases}}\ -alias: {{$.App.Name}} {{$top.Name}} {{$alias}}{{template "FormatCommand" $.Context.SelectedCommand}} -{{end}}\ -{{end}}\ -{{end}}\ -{{end}}\ -{{end}}\ -{{end}} -{{if .Context.SelectedCommand.Help}}\ -{{.Context.SelectedCommand.Help|Wrap 0}} -{{end}}\ -{{else}}\ -usage: {{.App.Name}}{{template "FormatUsage" .App}} -{{end}}\ - -{{if .App.Flags}}\ -Global flags: -{{.App.Flags|FlagsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.SelectedCommand}}\ -{{if and .Context.SelectedCommand.Flags|RequiredFlags}}\ -Required flags: -{{.Context.SelectedCommand.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.SelectedCommand.Flags|OptionalFlags}}\ -Optional flags: -{{.Context.SelectedCommand.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{end}}\ -{{if .Context.Args}}\ -Args: -{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.SelectedCommand}}\ -{{if .Context.SelectedCommand.Commands}}\ -Subcommands: -{{template "FormatCommands" .Context.SelectedCommand}} -{{end}}\ -{{else if .App.Commands}}\ -Commands: -{{template "FormatBriefCommands" .App}} -{{end}}\ -` - func main() { defer handleExit() - logBackend := logging.NewLogBackend(os.Stderr, "", 0) - format := os.Getenv("JIRA_LOG_FORMAT") - if format == "" { - format = defaultFormat - } - logging.SetBackend( - logging.NewBackendFormatter( - logBackend, - logging.MustStringFormatter(format), - ), - ) - if os.Getenv("JIRA_DEBUG") == "" { - logging.SetLevel(logging.NOTICE, "") - } else { - logging.SetLevel(logging.DEBUG, "") - } - - app := kingpin.New("jira", "Jira Command Line Interface") - app.Command("version", "Prints version").PreAction(func(*kingpin.ParseContext) error { - fmt.Println(jira.VERSION) - panic(jiracli.Exit{Code: 0}) - }) - app.UsageTemplate(usage) - - var verbosity int - app.Flag("verbose", "Increase verbosity for debugging").Short('v').PreAction(func(_ *kingpin.ParseContext) error { - os.Setenv("JIRA_DEBUG", fmt.Sprintf("%d", verbosity)) - increaseLogLevel(1) - return nil - }).CounterVar(&verbosity) - if os.Getenv("JIRA_DEBUG") != "" { - if verbosity, err := strconv.Atoi(os.Getenv("JIRA_DEBUG")); err == nil { - increaseLogLevel(verbosity) - } - } + jiracli.InitLogging() fig := figtree.NewFigTree() fig.EnvPrefix = "JIRA" @@ -188,135 +45,8 @@ func main() { o := oreo.New().WithCookieFile(filepath.Join(jiracli.Homedir(), fig.ConfigDir, "cookies.js")) - registry := []jiracli.CommandRegistry{ - {Command: "acknowledge", Entry: jiracmd.CmdTransitionRegistry("acknowledge"), Aliases: []string{"ack"}}, - {Command: "assign", Entry: jiracmd.CmdAssignRegistry(), Aliases: []string{"give"}}, - {Command: "attach create", Entry: jiracmd.CmdAttachCreateRegistry()}, - {Command: "attach get", Entry: jiracmd.CmdAttachGetRegistry()}, - {Command: "attach list", Entry: jiracmd.CmdAttachListRegistry(), Aliases: []string{"ls"}}, - {Command: "attach remove", Entry: jiracmd.CmdAttachRemoveRegistry(), Aliases: []string{"rm"}}, - {Command: "backlog", Entry: jiracmd.CmdTransitionRegistry("Backlog")}, - {Command: "block", Entry: jiracmd.CmdBlockRegistry()}, - {Command: "browse", Entry: jiracmd.CmdBrowseRegistry(), Aliases: []string{"b"}}, - {Command: "close", Entry: jiracmd.CmdTransitionRegistry("close")}, - {Command: "comment", Entry: jiracmd.CmdCommentRegistry()}, - {Command: "component add", Entry: jiracmd.CmdComponentAddRegistry()}, - {Command: "components", Entry: jiracmd.CmdComponentsRegistry()}, - {Command: "create", Entry: jiracmd.CmdCreateRegistry()}, - {Command: "createmeta", Entry: jiracmd.CmdCreateMetaRegistry()}, - {Command: "done", Entry: jiracmd.CmdTransitionRegistry("Done")}, - {Command: "dup", Entry: jiracmd.CmdDupRegistry()}, - {Command: "edit", Entry: jiracmd.CmdEditRegistry()}, - {Command: "editmeta", Entry: jiracmd.CmdEditMetaRegistry()}, - {Command: "epic add", Entry: jiracmd.CmdEpicAddRegistry()}, - {Command: "epic create", Entry: jiracmd.CmdEpicCreateRegistry()}, - {Command: "epic list", Entry: jiracmd.CmdEpicListRegistry(), Aliases: []string{"ls"}}, - {Command: "epic remove", Entry: jiracmd.CmdEpicRemoveRegistry(), Aliases: []string{"rm"}}, - {Command: "export-templates", Entry: jiracmd.CmdExportTemplatesRegistry()}, - {Command: "fields", Entry: jiracmd.CmdFieldsRegistry()}, - {Command: "in-progress", Entry: jiracmd.CmdTransitionRegistry("Progress"), Aliases: []string{"prog", "progress"}}, - {Command: "issuelink", Entry: jiracmd.CmdIssueLinkRegistry()}, - {Command: "issuelinktypes", Entry: jiracmd.CmdIssueLinkTypesRegistry()}, - {Command: "issuetypes", Entry: jiracmd.CmdIssueTypesRegistry()}, - {Command: "labels add", Entry: jiracmd.CmdLabelsAddRegistry()}, - {Command: "labels remove", Entry: jiracmd.CmdLabelsRemoveRegistry(), Aliases: []string{"rm"}}, - {Command: "labels set", Entry: jiracmd.CmdLabelsSetRegistry()}, - {Command: "list", Entry: jiracmd.CmdListRegistry(), Aliases: []string{"ls"}}, - {Command: "login", Entry: jiracmd.CmdLoginRegistry()}, - {Command: "logout", Entry: jiracmd.CmdLogoutRegistry()}, - {Command: "rank", Entry: jiracmd.CmdRankRegistry()}, - {Command: "reopen", Entry: jiracmd.CmdTransitionRegistry("reopen")}, - {Command: "request", Entry: jiracmd.CmdRequestRegistry(), Aliases: []string{"req"}}, - {Command: "resolve", Entry: jiracmd.CmdTransitionRegistry("resolve")}, - {Command: "start", Entry: jiracmd.CmdTransitionRegistry("start")}, - {Command: "stop", Entry: jiracmd.CmdTransitionRegistry("stop")}, - {Command: "subtask", Entry: jiracmd.CmdSubtaskRegistry()}, - {Command: "take", Entry: jiracmd.CmdTakeRegistry()}, - {Command: "todo", Entry: jiracmd.CmdTransitionRegistry("To Do")}, - {Command: "transition", Entry: jiracmd.CmdTransitionRegistry(""), Aliases: []string{"trans"}}, - {Command: "transitions", Entry: jiracmd.CmdTransitionsRegistry("transitions")}, - {Command: "transmeta", Entry: jiracmd.CmdTransitionsRegistry("debug")}, - {Command: "unassign", Entry: jiracmd.CmdUnassignRegistry()}, - {Command: "unexport-templates", Entry: jiracmd.CmdUnexportTemplatesRegistry()}, - {Command: "view", Entry: jiracmd.CmdViewRegistry()}, - {Command: "vote", Entry: jiracmd.CmdVoteRegistry()}, - {Command: "watch", Entry: jiracmd.CmdWatchRegistry()}, - {Command: "worklog add", Entry: jiracmd.CmdWorklogAddRegistry()}, - {Command: "worklog list", Entry: jiracmd.CmdWorklogListRegistry(), Default: true}, - } - - jiracli.Register(app, o, fig, registry) - - // register custom commands - data := struct { - CustomCommands kingpeon.DynamicCommands `yaml:"custom-commands" json:"custom-commands"` - }{} + jiracmd.RegisterAllCommands() - if err := fig.LoadAllConfigs("config.yml", &data); err != nil { - log.Errorf("%s", err) - panic(jiracli.Exit{Code: 1}) - } - - if len(data.CustomCommands) > 0 { - runner := syscall.Exec - if runtime.GOOS == "windows" { - runner = func(binary string, cmd []string, env []string) error { - command := exec.Command(binary, cmd[1:]...) - command.Stdin = os.Stdin - command.Stdout = os.Stdout - command.Stderr = os.Stderr - command.Env = env - return command.Run() - } - } - - tmp := map[string]interface{}{} - fig.LoadAllConfigs("config.yml", &tmp) - kingpeon.RegisterDynamicCommandsWithRunner(runner, app, data.CustomCommands, jiracli.TemplateProcessor()) - } - - app.Terminate(func(status int) { - for _, arg := range os.Args { - if arg == "-h" || arg == "--help" || len(os.Args) == 1 { - panic(jiracli.Exit{Code: 0}) - } - } - panic(jiracli.Exit{Code: 1}) - }) - - // checking for default usage of `jira ISSUE-123` but need to allow - // for global options first like: `jira --user mothra ISSUE-123` - ctx, err := app.ParseContext(os.Args[1:]) - if err != nil && ctx == nil { - // This is an internal kingpin usage error, duplicate options/commands - log.Fatalf("error: %s, ctx: %v", err, ctx) - } - - if ctx != nil { - if ctx.SelectedCommand == nil { - next := ctx.Next() - if next != nil { - if ok, err := regexp.MatchString("^[A-Z]+-[0-9]+$", next.Value); err != nil { - log.Errorf("Invalid Regex: %s", err) - } else if ok { - // insert "view" at i=1 (2nd position) - os.Args = append(os.Args[:1], append([]string{"view"}, os.Args[1:]...)...) - } - } - } - } - - if _, err := app.Parse(os.Args[1:]); err != nil { - if _, ok := err.(*jiracli.Error); ok { - log.Errorf("%s", err) - panic(jiracli.Exit{Code: 1}) - } else { - ctx, _ := app.ParseContext(os.Args[1:]) - if ctx != nil { - app.UsageForContext(ctx) - } - log.Errorf("Invalid Usage: %s", err) - panic(jiracli.Exit{Code: 1}) - } - } + app := jiracli.CommandLine(fig, o) + jiracli.ParseCommandLine(app, os.Args[1:]) } diff --git a/jiracli/cli.go b/jiracli/cli.go index 8d1639d8..184c32bf 100644 --- a/jiracli/cli.go +++ b/jiracli/cli.go @@ -20,11 +20,8 @@ import ( "gopkg.in/AlecAivazis/survey.v1" kingpin "gopkg.in/alecthomas/kingpin.v2" yaml "gopkg.in/coryb/yaml.v2" - logging "gopkg.in/op/go-logging.v1" ) -var log = logging.MustGetLogger("jira") - type Exit struct { Code int } @@ -66,7 +63,13 @@ type kingpinAppOrCommand interface { GetCommand(string) *kingpin.CmdClause } -func Register(app *kingpin.Application, o *oreo.Client, fig *figtree.FigTree, reg []CommandRegistry) { +var globalCommandRegistry = []CommandRegistry{} + +func RegisterCommand(regEntry CommandRegistry) { + globalCommandRegistry = append(globalCommandRegistry, regEntry) +} + +func register(app *kingpin.Application, o *oreo.Client, fig *figtree.FigTree) { globals := GlobalOptions{ User: figtree.NewStringOption(os.Getenv("USER")), } @@ -98,7 +101,7 @@ func Register(app *kingpin.Application, o *oreo.Client, fig *figtree.FigTree, re }, ) - for _, command := range reg { + for _, command := range globalCommandRegistry { copy := command commandFields := strings.Fields(copy.Command) var appOrCmd kingpinAppOrCommand = app diff --git a/jiracli/log.go b/jiracli/log.go new file mode 100644 index 00000000..fa6f04d3 --- /dev/null +++ b/jiracli/log.go @@ -0,0 +1,43 @@ +package jiracli + +import ( + "os" + "strconv" + + "github.com/coryb/oreo" + logging "gopkg.in/op/go-logging.v1" +) + +var ( + log = logging.MustGetLogger("jira") +) + +func IncreaseLogLevel(verbosity int) { + logging.SetLevel(logging.GetLevel("")+logging.Level(verbosity), "") + if logging.GetLevel("") > logging.DEBUG { + oreo.TraceRequestBody = true + oreo.TraceResponseBody = true + } +} + +func InitLogging() { + logBackend := logging.NewLogBackend(os.Stderr, "", 0) + format := os.Getenv("JIRA_LOG_FORMAT") + if format == "" { + format = "%{color}%{level:-5s}%{color:reset} %{message}" + } + logging.SetBackend( + logging.NewBackendFormatter( + logBackend, + logging.MustStringFormatter(format), + ), + ) + if os.Getenv("JIRA_DEBUG") == "" { + logging.SetLevel(logging.NOTICE, "") + } else { + logging.SetLevel(logging.DEBUG, "") + if verbosity, err := strconv.Atoi(os.Getenv("JIRA_DEBUG")); err == nil { + IncreaseLogLevel(verbosity) + } + } +} diff --git a/jiracli/usage.go b/jiracli/usage.go new file mode 100644 index 00000000..bb452453 --- /dev/null +++ b/jiracli/usage.go @@ -0,0 +1,198 @@ +package jiracli + +import ( + "fmt" + "os" + "os/exec" + "regexp" + "runtime" + "syscall" + + "github.com/coryb/figtree" + "github.com/coryb/kingpeon" + "github.com/coryb/oreo" + jira "gopkg.in/Netflix-Skunkworks/go-jira.v1" + kingpin "gopkg.in/alecthomas/kingpin.v2" +) + +var usage = `{{define "FormatCommand"}}\ +{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ +{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ +{{end}}\ + +{{define "FormatBriefCommands"}}\ +{{range .FlattenedCommands}}\ +{{if not .Hidden}}\ + {{ print .FullCommand ":" | printf "%-20s"}} {{.Help}} +{{end}}\ +{{end}}\ +{{end}}\ + +{{define "FormatCommands"}}\ +{{range .FlattenedCommands}}\ +{{if not .Hidden}}\ + {{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} +{{.Help|Wrap 4}} +{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}} +{{end}}\ +{{end}}\ +{{end}}\ + +{{define "FormatUsage"}}\ +{{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} +{{if .Help}} +{{.Help|Wrap 0}}\ +{{end}}\ + +{{end}}\ + +{{if .Context.SelectedCommand}}\ +usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatCommand" .Context.SelectedCommand}} +{{if .Context.SelectedCommand.Aliases }}\ +{{range $top := .App.Commands}}\ +{{if eq $top.FullCommand $.Context.SelectedCommand.FullCommand}}\ +{{range $alias := $.Context.SelectedCommand.Aliases}}\ +alias: {{$.App.Name}} {{$alias}}{{template "FormatCommand" $.Context.SelectedCommand}} +{{end}}\ +{{else}}\ +{{range $sub := $top.Commands}}\ +{{if eq $sub.FullCommand $.Context.SelectedCommand.FullCommand}}\ +{{range $alias := $.Context.SelectedCommand.Aliases}}\ +alias: {{$.App.Name}} {{$top.Name}} {{$alias}}{{template "FormatCommand" $.Context.SelectedCommand}} +{{end}}\ +{{end}}\ +{{end}}\ +{{end}}\ +{{end}}\ +{{end}} +{{if .Context.SelectedCommand.Help}}\ +{{.Context.SelectedCommand.Help|Wrap 0}} +{{end}}\ +{{else}}\ +usage: {{.App.Name}}{{template "FormatUsage" .App}} +{{end}}\ + +{{if .App.Flags}}\ +Global flags: +{{.App.Flags|FlagsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.SelectedCommand}}\ +{{if and .Context.SelectedCommand.Flags|RequiredFlags}}\ +Required flags: +{{.Context.SelectedCommand.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.SelectedCommand.Flags|OptionalFlags}}\ +Optional flags: +{{.Context.SelectedCommand.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{end}}\ +{{if .Context.Args}}\ +Args: +{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.SelectedCommand}}\ +{{if .Context.SelectedCommand.Commands}}\ +Subcommands: +{{template "FormatCommands" .Context.SelectedCommand}} +{{end}}\ +{{else if .App.Commands}}\ +Commands: +{{template "FormatBriefCommands" .App}} +{{end}}\ +` + +func CommandLine(fig *figtree.FigTree, o *oreo.Client) *kingpin.Application { + app := kingpin.New("jira", "Jira Command Line Interface") + + app.Command("version", "Prints version").PreAction(func(*kingpin.ParseContext) error { + fmt.Println(jira.VERSION) + panic(Exit{Code: 0}) + }) + app.UsageTemplate(usage) + + var verbosity int + app.Flag("verbose", "Increase verbosity for debugging").Short('v').PreAction(func(_ *kingpin.ParseContext) error { + os.Setenv("JIRA_DEBUG", fmt.Sprintf("%d", verbosity)) + IncreaseLogLevel(1) + return nil + }).CounterVar(&verbosity) + + app.Terminate(func(status int) { + for _, arg := range os.Args { + if arg == "-h" || arg == "--help" || len(os.Args) == 1 { + panic(Exit{Code: 0}) + } + } + panic(Exit{Code: 1}) + }) + + register(app, o, fig) + + // register custom commands + data := struct { + CustomCommands kingpeon.DynamicCommands `yaml:"custom-commands" json:"custom-commands"` + }{} + + if err := fig.LoadAllConfigs("config.yml", &data); err != nil { + log.Errorf("%s", err) + panic(Exit{Code: 1}) + } + + if len(data.CustomCommands) > 0 { + runner := syscall.Exec + if runtime.GOOS == "windows" { + runner = func(binary string, cmd []string, env []string) error { + command := exec.Command(binary, cmd[1:]...) + command.Stdin = os.Stdin + command.Stdout = os.Stdout + command.Stderr = os.Stderr + command.Env = env + return command.Run() + } + } + + tmp := map[string]interface{}{} + fig.LoadAllConfigs("config.yml", &tmp) + kingpeon.RegisterDynamicCommandsWithRunner(runner, app, data.CustomCommands, TemplateProcessor()) + } + + return app +} + +func ParseCommandLine(app *kingpin.Application, args []string) { + // checking for default usage of `jira ISSUE-123` but need to allow + // for global options first like: `jira --user mothra ISSUE-123` + ctx, err := app.ParseContext(args) + if err != nil && ctx == nil { + // This is an internal kingpin usage error, duplicate options/commands + log.Fatalf("error: %s, ctx: %v", err, ctx) + } + + if ctx != nil { + if ctx.SelectedCommand == nil { + next := ctx.Next() + if next != nil { + if ok, err := regexp.MatchString("^[A-Z]+-[0-9]+$", next.Value); err != nil { + log.Errorf("Invalid Regex: %s", err) + } else if ok { + // insert "view" at i=1 (2nd position) + os.Args = append(os.Args[:1], append([]string{"view"}, os.Args[1:]...)...) + } + } + } + } + + if _, err := app.Parse(os.Args[1:]); err != nil { + if _, ok := err.(*Error); ok { + log.Errorf("%s", err) + panic(Exit{Code: 1}) + } else { + ctx, _ := app.ParseContext(os.Args[1:]) + if ctx != nil { + app.UsageForContext(ctx) + } + log.Errorf("Invalid Usage: %s", err) + panic(Exit{Code: 1}) + } + } +}