From f502754e73b23a89f555c23932b3243c074c0abe Mon Sep 17 00:00:00 2001 From: maxlandon Date: Fri, 9 Aug 2024 06:24:01 +0000 Subject: [PATCH] Exit command, interrupt doc fix (#52) * Use mvdan/sh for parsing and removing comments * Update readline dep * Update carapace dependency library * Update readline dependency * Ensure the completion function is initialized * Update completer.go * Update carapace to latest * Update deps, usability tweaks * Add exit command to example and document interrupts * Small refactor for flag reset * Refactors in run --- command.go | 37 ++++++++++++++++ example/main-commands.go | 10 +++++ interrupt.go | 9 ++-- run.go | 92 +++++++++++++++------------------------- 4 files changed, 87 insertions(+), 61 deletions(-) diff --git a/command.go b/command.go index d3858cd..a33a7c5 100644 --- a/command.go +++ b/command.go @@ -2,6 +2,7 @@ package console import ( "github.com/spf13/cobra" + "github.com/spf13/pflag" ) const ( @@ -73,3 +74,39 @@ next: c.filters = updated } + +// resetFlagsDefaults resets all flags to their default values. +// +// Slice flags accumulate per execution (and do not reset), +// +// so we must reset them manually. +// +// Example: +// +// Given cmd.Flags().StringSlice("comment", nil, "") +// If you run a command with --comment "a" --comment "b" you will get +// the expected [a, b] slice. +// +// If you run a command again with no --comment flags, you will get +// [a, b] again instead of an empty slice. +// +// If you run the command again with --comment "c" --comment "d" flags, +// you will get [a, b, c, d] instead of just [c, d]. +func resetFlagsDefaults(target *cobra.Command) { + target.Flags().VisitAll(func(flag *pflag.Flag) { + flag.Changed = false + switch value := flag.Value.(type) { + case pflag.SliceValue: + var res []string + + if len(flag.DefValue) > 0 && flag.DefValue != "[]" { + res = append(res, flag.DefValue) + } + + value.Replace(res) + + default: + flag.Value.Set(flag.DefValue) + } + }) +} diff --git a/example/main-commands.go b/example/main-commands.go index 5b5fde5..2248639 100644 --- a/example/main-commands.go +++ b/example/main-commands.go @@ -32,6 +32,16 @@ func mainMenuCommands(app *console.Console) console.Commands { // Readline subcommands rootCmd.AddCommand(readline.Commands(app.Shell())) + exitCmd := &cobra.Command{ + Use: "exit", + Short: "Exit the console application", + GroupID: "core", + Run: func(cmd *cobra.Command, args []string) { + exitCtrlD(app) + }, + } + rootCmd.AddCommand(exitCmd) + // And let's add a command declared in a traditional "cobra" way. clientMenuCommand := &cobra.Command{ Use: "client", diff --git a/interrupt.go b/interrupt.go index b37105c..a70ba33 100644 --- a/interrupt.go +++ b/interrupt.go @@ -1,8 +1,11 @@ package console -// AddInterrupt registers a handler to run when the console receives a given -// interrupt error from the underlying readline shell. Mainly two interrupt -// signals are concerned: io.EOF (returned when pressing CtrlD), and console.ErrCtrlC. +// AddInterrupt registers a handler to run when the console receives +// a given interrupt error from the underlying readline shell. +// +// On most systems, the following errors will be returned with keypresses: +// - Linux/MacOS/Windows : Ctrl-C will return os.Interrupt. +// // Many will want to use this to switch menus. Note that these interrupt errors only // work when the console is NOT currently executing a command, only when reading input. func (m *Menu) AddInterrupt(err error, handler func(c *Console)) { diff --git a/run.go b/run.go index d2fc0a5..0aaacbb 100644 --- a/run.go +++ b/run.go @@ -31,31 +31,14 @@ func (c *Console) StartContext(ctx context.Context) error { lastLine := "" // used to check if last read line is empty. - for i := 0; ; i++ { - // Identical to printing it at the end of the loop, and - // leaves some space between the logo and the first prompt. - - // If NewlineAfter is set but NewlineWhenEmpty is not set, we do a check to see - // if the last line was empty. If it wasn't, then we can print. - //fmt.Println(lastLine, len(lastLine)) - if c.NewlineAfter { - if !c.NewlineWhenEmpty && i != 0 { - // Print on the condition that the last input wasn't empty. - if !c.lineEmpty(lastLine) { - fmt.Println() - } - } else { - fmt.Println() - } - } + for { + c.displayPostRun(lastLine) // Always ensure we work with the active menu, with freshly // generated commands, bound prompts and some other things. menu := c.activeMenu() menu.resetPreRun() - c.printed = false - if err := c.runAllE(c.PreReadlineHooks); err != nil { menu.ErrorHandler(PreReadError{newError(err, "Pre-read error")}) @@ -64,19 +47,14 @@ func (c *Console) StartContext(ctx context.Context) error { // Block and read user input. line, err := c.shell.Readline() - if c.NewlineBefore { - if !c.NewlineWhenEmpty { - if !c.lineEmpty(line) { - fmt.Println() - } - } else { - fmt.Println() - } - } + + c.displayPostRun(line) if err != nil { menu.handleInterrupt(err) + lastLine = line + continue } @@ -113,6 +91,7 @@ func (c *Console) StartContext(ctx context.Context) error { if err := c.execute(ctx, menu, args, false); err != nil { menu.ErrorHandler(ExecutionError{newError(err, "")}) } + lastLine = line } } @@ -179,37 +158,8 @@ func (c *Console) execute(ctx context.Context, menu *Menu, args []string, async } // Reset all flags to their default values. - // - // Slice flags accumulate per execution (and do not reset), - // so we must reset them manually. - // - // Example: - // - // Given cmd.Flags().StringSlice("comment", nil, "") - // If you run a command with --comment "a" --comment "b" you will get - // the expected [a, b] slice. - // - // If you run a command again with no --comment flags, you will get - // [a, b] again instead of an empty slice. - // - // If you run the command again with --comment "c" --comment "d" flags, - // you will get [a, b, c, d] instead of just [c, d]. - target.Flags().VisitAll(func(flag *pflag.Flag) { - flag.Changed = false - switch value := flag.Value.(type) { - case pflag.SliceValue: - var res []string - - if len(flag.DefValue) > 0 && flag.DefValue != "[]" { - res = append(res, flag.DefValue) - } - - value.Replace(res) + resetFlagsDefaults(target) - default: - flag.Value.Set(flag.DefValue) - } - }) // Console-wide pre-run hooks, cannot. if err := c.runAllE(c.PreCmdRunHooks); err != nil { @@ -302,6 +252,32 @@ func (c *Console) runLineHooks(args []string) ([]string, error) { return processed, nil } +func (c *Console) displayPreRun(line string) { + if c.NewlineBefore { + if !c.NewlineWhenEmpty { + if !c.lineEmpty(line) { + fmt.Println() + } + } else { + fmt.Println() + } + } +} + +func (c *Console) displayPostRun(lastLine string) { + if c.NewlineAfter { + if !c.NewlineWhenEmpty { + if !c.lineEmpty(lastLine) { + fmt.Println() + } + } else { + fmt.Println() + } + } + + c.printed = false +} + // monitorSignals - Monitor the signals that can be sent to the process // while a command is running. We want to be able to cancel the command. func (c *Console) monitorSignals() <-chan os.Signal {