Skip to content

Commit

Permalink
Exit command, interrupt doc fix (#52)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
maxlandon authored Aug 9, 2024
1 parent db30342 commit f502754
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 61 deletions.
37 changes: 37 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package console

import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

const (
Expand Down Expand Up @@ -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)
}
})
}
10 changes: 10 additions & 0 deletions example/main-commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 6 additions & 3 deletions interrupt.go
Original file line number Diff line number Diff line change
@@ -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)) {
Expand Down
92 changes: 34 additions & 58 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")})

Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit f502754

Please sign in to comment.