@@ -10,12 +10,18 @@ import (
1010
1111 "github.com/kballard/go-shellquote"
1212 "github.com/spf13/cobra"
13+ "github.com/spf13/pflag"
1314)
1415
1516// Start - Start the console application (readline loop). Blocking.
1617// The error returned will always be an error that the console
1718// application does not understand or cannot handle.
1819func (c * Console ) Start () error {
20+ return c .StartContext (context .Background ())
21+ }
22+
23+ // StartContext is like console.Start(). with a user-provided context.
24+ func (c * Console ) StartContext (ctx context.Context ) error {
1925 c .loadActiveHistories ()
2026
2127 // Print the console logo
@@ -51,7 +57,8 @@ func (c *Console) Start() error {
5157 c .printed = false
5258
5359 if err := c .runAllE (c .PreReadlineHooks ); err != nil {
54- fmt .Printf ("Pre-read error: %s\n " , err .Error ())
60+ menu .ErrorHandler (PreReadError {newError (err , "Pre-read error" )})
61+
5562 continue
5663 }
5764
@@ -81,8 +88,7 @@ func (c *Console) Start() error {
8188 // Parse the line with bash-syntax, removing comments.
8289 args , err := c .parse (line )
8390 if err != nil {
84- fmt .Printf ("Parsing error: %s\n " , err .Error ())
85- lastLine = line
91+ menu .ErrorHandler (ParseError {newError (err , "Parsing error" )})
8692 continue
8793 }
8894
@@ -95,8 +101,7 @@ func (c *Console) Start() error {
95101 // which may modify the input line args.
96102 args , err = c .runLineHooks (args )
97103 if err != nil {
98- fmt .Printf ("Line error: %s\n " , err .Error ())
99- lastLine = line
104+ menu .ErrorHandler (LineHookError {newError (err , "Line error" )})
100105 continue
101106 }
102107
@@ -105,8 +110,8 @@ func (c *Console) Start() error {
105110 // the library user is responsible for setting
106111 // the cobra behavior.
107112 // If it's an interrupt, we take care of it.
108- if err = c .execute (menu , args , false ); err != nil {
109- fmt . Println ( err )
113+ if err : = c .execute (ctx , menu , args , false ); err != nil {
114+ menu . ErrorHandler ( ExecutionError { newError ( err , "" )} )
110115 }
111116 lastLine = line
112117 }
@@ -118,19 +123,19 @@ func (c *Console) Start() error {
118123// workflow.
119124// Although state segregation is a priority for this library to be ensured as much
120125// as possible, you should be cautious when using this function to run commands.
121- func (m * Menu ) RunCommandArgs (args []string ) (err error ) {
126+ func (m * Menu ) RunCommandArgs (ctx context. Context , args []string ) (err error ) {
122127 // The menu used and reset is the active menu.
123128 // Prepare its output buffer for the command.
124129 m .resetPreRun ()
125130
126131 // Run the command and associated helpers.
127- return m .console .execute (m , args , ! m .console .isExecuting )
132+ return m .console .execute (ctx , m , args , ! m .console .isExecuting )
128133}
129134
130135// RunCommandLine is the equivalent of menu.RunCommandArgs(), but accepts
131136// an unsplit command line to execute. This line is split and processed in
132137// *sh-compliant form, identically to how lines are in normal console usage.
133- func (m * Menu ) RunCommandLine (line string ) (err error ) {
138+ func (m * Menu ) RunCommandLine (ctx context. Context , line string ) (err error ) {
134139 if len (line ) == 0 {
135140 return
136141 }
@@ -141,7 +146,7 @@ func (m *Menu) RunCommandLine(line string) (err error) {
141146 return fmt .Errorf ("line error: %w" , err )
142147 }
143148
144- return m .RunCommandArgs (args )
149+ return m .RunCommandArgs (ctx , args )
145150}
146151
147152// execute - The user has entered a command input line, the arguments have been processed:
@@ -150,7 +155,7 @@ func (m *Menu) RunCommandLine(line string) (err error) {
150155// Our main object of interest is the menu's root command, and we explicitly use this reference
151156// instead of the menu itself, because if RunCommand() is asynchronously triggered while another
152157// command is running, the menu's root command will be overwritten.
153- func (c * Console ) execute (menu * Menu , args []string , async bool ) ( err error ) {
158+ func (c * Console ) execute (ctx context. Context , menu * Menu , args []string , async bool ) error {
154159 if ! async {
155160 c .mutex .RLock ()
156161 c .isExecuting = true
@@ -169,12 +174,45 @@ func (c *Console) execute(menu *Menu, args []string, async bool) (err error) {
169174 // Find the target command: if this command is filtered, don't run it.
170175 target , _ , _ := cmd .Find (args )
171176
172- if err = menu .CheckIsAvailable (target ); err != nil {
177+ if err : = menu .CheckIsAvailable (target ); err != nil {
173178 return err
174179 }
175180
181+ // Reset all flags to their default values.
182+ //
183+ // Slice flags accumulate per execution (and do not reset),
184+ // so we must reset them manually.
185+ //
186+ // Example:
187+ //
188+ // Given cmd.Flags().StringSlice("comment", nil, "")
189+ // If you run a command with --comment "a" --comment "b" you will get
190+ // the expected [a, b] slice.
191+ //
192+ // If you run a command again with no --comment flags, you will get
193+ // [a, b] again instead of an empty slice.
194+ //
195+ // If you run the command again with --comment "c" --comment "d" flags,
196+ // you will get [a, b, c, d] instead of just [c, d].
197+ target .Flags ().VisitAll (func (flag * pflag.Flag ) {
198+ flag .Changed = false
199+ switch value := flag .Value .(type ) {
200+ case pflag.SliceValue :
201+ var res []string
202+
203+ if len (flag .DefValue ) > 0 && flag .DefValue != "[]" {
204+ res = append (res , flag .DefValue )
205+ }
206+
207+ value .Replace (res )
208+
209+ default :
210+ flag .Value .Set (flag .DefValue )
211+ }
212+ })
213+
176214 // Console-wide pre-run hooks, cannot.
177- if err = c .runAllE (c .PreCmdRunHooks ); err != nil {
215+ if err : = c .runAllE (c .PreCmdRunHooks ); err != nil {
178216 return fmt .Errorf ("pre-run error: %s" , err .Error ())
179217 }
180218
@@ -183,7 +221,7 @@ func (c *Console) execute(menu *Menu, args []string, async bool) (err error) {
183221
184222 // The command execution should happen in a separate goroutine,
185223 // and should notify the main goroutine when it is done.
186- ctx , cancel := context .WithCancelCause (context . Background () )
224+ ctx , cancel := context .WithCancelCause (ctx )
187225
188226 cmd .SetContext (ctx )
189227
@@ -196,22 +234,26 @@ func (c *Console) execute(menu *Menu, args []string, async bool) (err error) {
196234 // Wait for the command to finish, or for an OS signal to be caught.
197235 select {
198236 case <- ctx .Done ():
199- if ! errors .Is (ctx .Err (), context .Canceled ) {
200- err = ctx .Err ()
237+ cause := context .Cause (ctx )
238+
239+ if ! errors .Is (cause , context .Canceled ) {
240+ return cause
201241 }
202242
203243 case signal := <- sigchan :
204244 cancel (errors .New (signal .String ()))
245+
205246 menu .handleInterrupt (errors .New (signal .String ()))
206247 }
207248
208- return err
249+ return nil
209250}
210251
211252// Run the command in a separate goroutine, and cancel the context when done.
212253func (c * Console ) executeCommand (cmd * cobra.Command , cancel context.CancelCauseFunc ) {
213254 if err := cmd .Execute (); err != nil {
214255 cancel (err )
256+
215257 return
216258 }
217259
0 commit comments