@@ -11,13 +11,18 @@ import (
1111 "github.com/spf13/pflag"
1212)
1313
14- // wraps the `Execute()` call of the command to inject boilerplate details like `Use`, `Version` and
15- // handling of error to `Command.Execute()` (such as flag validation, missing command etc.)
14+ // wraps the `Execute()` call of the command to inject boilerplate details for root command:
15+ // - `Use` (for root cmd should be `os.Args[0]`)
16+ // - `Version` (should be program's built-in version info)
17+ // - UX improvements (some Cobra defaults are in poor taste)
18+ // - configure logging
19+ // - deliver context & cancel it on signals
1620func Execute (app * cobra.Command ) {
1721 // dirty to mutate after-the-fact
1822
1923 app .Use = os .Args [0 ]
2024 app .Version = dynversion .Version
25+
2126 // hide the default "completion" subcommand from polluting UX (it can still be used). https://github.com/spf13/cobra/issues/1507
2227 app .CompletionOptions = cobra.CompletionOptions {HiddenDefaultCmd : true }
2328
@@ -26,32 +31,44 @@ func Execute(app *cobra.Command) {
2631 // https://github.com/spf13/cobra/issues/587#issuecomment-843747825
2732 app .SetHelpCommand (& cobra.Command {Hidden : true })
2833
34+ // don't display error internally by Cobra. we do better job of displaying it here (and most importantly, setting exit code)
35+ app .SilenceErrors = true
36+
2937 // cannot `AddLogLevelControls(app.Flags())` here because it gets confusing if:
3038 // a) the root command is not runnable
3139 // b) the root command is runnable BUT it doesn't do logging (or there is no debug-level logs to suppress)
3240
33- osutil .ExitIfError (app .Execute ())
41+ // handle logging
42+ configureLogging ()
43+
44+ // handle signals
45+ ctx := notifyContextInterruptOrTerminate ()
46+
47+ // this is where the magic happens
48+ _ , err := app .ExecuteContextC (ctx )
49+
50+ osutil .ExitIfError (err )
3451}
3552
3653// fixes problems of cobra commands' bare run callbacks with regards to application quality:
37- // 1. logging not configured
38- // 2. no interrupt handling
39- // 3. no error handling
40- func WrapRun ( run func ( ctx context. Context , args [] string ) error ) func ( * cobra.Command , [] string ) {
41- return func (_ * cobra.Command , args []string ) {
42- // handle logging
43- configureLogging ()
44-
45- // handle interrupts
46- ctx := notifyContextInterruptOrTerminate ()
47-
48- // run the actual code (jump from CLI context to higher-level application context)
49- // this can be kinda read as:
50- // output = logic(input)
51- err := run ( ctx , args )
52-
53- // handle errors
54- osutil . ExitIfError ( err )
54+ // 1. `context.Context` (interrupt handling) has to be fetched from a middle man ( not idiomatic Go)
55+ // 2. CLI usage help would be shown for all (even higher app-level) errors
56+ //
57+ // this is intended to be given to `RunE` field in ` *cobra.Command`
58+ func WrapRun ( run func (ctx context. Context , args [] string ) error ) func ( * cobra.Command , []string ) error {
59+ return func ( cmd * cobra. Command , args [] string ) error {
60+ // Cobra would displays CLI usage help for any error happened inside `Run` func.
61+ //
62+ // in reality we want the usage to only appear on failed input validation, because it would be confusing to
63+ // show CLI usage help for app-level errors happening above the CLI layer.
64+ //
65+ // since we arrived at the `Run` func we know the error won't be due to input validation anymore.
66+ cmd . SilenceUsage = true
67+
68+ // retrieve back the context that was injected by our `Execute()`
69+ ctx := cmd . Context ()
70+
71+ return run ( ctx , args )
5572 }
5673}
5774
0 commit comments