Skip to content

Commit f502754

Browse files
authored
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
1 parent db30342 commit f502754

File tree

4 files changed

+87
-61
lines changed

4 files changed

+87
-61
lines changed

command.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package console
22

33
import (
44
"github.com/spf13/cobra"
5+
"github.com/spf13/pflag"
56
)
67

78
const (
@@ -73,3 +74,39 @@ next:
7374

7475
c.filters = updated
7576
}
77+
78+
// resetFlagsDefaults resets all flags to their default values.
79+
//
80+
// Slice flags accumulate per execution (and do not reset),
81+
//
82+
// so we must reset them manually.
83+
//
84+
// Example:
85+
//
86+
// Given cmd.Flags().StringSlice("comment", nil, "")
87+
// If you run a command with --comment "a" --comment "b" you will get
88+
// the expected [a, b] slice.
89+
//
90+
// If you run a command again with no --comment flags, you will get
91+
// [a, b] again instead of an empty slice.
92+
//
93+
// If you run the command again with --comment "c" --comment "d" flags,
94+
// you will get [a, b, c, d] instead of just [c, d].
95+
func resetFlagsDefaults(target *cobra.Command) {
96+
target.Flags().VisitAll(func(flag *pflag.Flag) {
97+
flag.Changed = false
98+
switch value := flag.Value.(type) {
99+
case pflag.SliceValue:
100+
var res []string
101+
102+
if len(flag.DefValue) > 0 && flag.DefValue != "[]" {
103+
res = append(res, flag.DefValue)
104+
}
105+
106+
value.Replace(res)
107+
108+
default:
109+
flag.Value.Set(flag.DefValue)
110+
}
111+
})
112+
}

example/main-commands.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ func mainMenuCommands(app *console.Console) console.Commands {
3232
// Readline subcommands
3333
rootCmd.AddCommand(readline.Commands(app.Shell()))
3434

35+
exitCmd := &cobra.Command{
36+
Use: "exit",
37+
Short: "Exit the console application",
38+
GroupID: "core",
39+
Run: func(cmd *cobra.Command, args []string) {
40+
exitCtrlD(app)
41+
},
42+
}
43+
rootCmd.AddCommand(exitCmd)
44+
3545
// And let's add a command declared in a traditional "cobra" way.
3646
clientMenuCommand := &cobra.Command{
3747
Use: "client",

interrupt.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package console
22

3-
// AddInterrupt registers a handler to run when the console receives a given
4-
// interrupt error from the underlying readline shell. Mainly two interrupt
5-
// signals are concerned: io.EOF (returned when pressing CtrlD), and console.ErrCtrlC.
3+
// AddInterrupt registers a handler to run when the console receives
4+
// a given interrupt error from the underlying readline shell.
5+
//
6+
// On most systems, the following errors will be returned with keypresses:
7+
// - Linux/MacOS/Windows : Ctrl-C will return os.Interrupt.
8+
//
69
// Many will want to use this to switch menus. Note that these interrupt errors only
710
// work when the console is NOT currently executing a command, only when reading input.
811
func (m *Menu) AddInterrupt(err error, handler func(c *Console)) {

run.go

Lines changed: 34 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,14 @@ func (c *Console) StartContext(ctx context.Context) error {
3131

3232
lastLine := "" // used to check if last read line is empty.
3333

34-
for i := 0; ; i++ {
35-
// Identical to printing it at the end of the loop, and
36-
// leaves some space between the logo and the first prompt.
37-
38-
// If NewlineAfter is set but NewlineWhenEmpty is not set, we do a check to see
39-
// if the last line was empty. If it wasn't, then we can print.
40-
//fmt.Println(lastLine, len(lastLine))
41-
if c.NewlineAfter {
42-
if !c.NewlineWhenEmpty && i != 0 {
43-
// Print on the condition that the last input wasn't empty.
44-
if !c.lineEmpty(lastLine) {
45-
fmt.Println()
46-
}
47-
} else {
48-
fmt.Println()
49-
}
50-
}
34+
for {
35+
c.displayPostRun(lastLine)
5136

5237
// Always ensure we work with the active menu, with freshly
5338
// generated commands, bound prompts and some other things.
5439
menu := c.activeMenu()
5540
menu.resetPreRun()
5641

57-
c.printed = false
58-
5942
if err := c.runAllE(c.PreReadlineHooks); err != nil {
6043
menu.ErrorHandler(PreReadError{newError(err, "Pre-read error")})
6144

@@ -64,19 +47,14 @@ func (c *Console) StartContext(ctx context.Context) error {
6447

6548
// Block and read user input.
6649
line, err := c.shell.Readline()
67-
if c.NewlineBefore {
68-
if !c.NewlineWhenEmpty {
69-
if !c.lineEmpty(line) {
70-
fmt.Println()
71-
}
72-
} else {
73-
fmt.Println()
74-
}
75-
}
50+
51+
c.displayPostRun(line)
7652

7753
if err != nil {
7854
menu.handleInterrupt(err)
55+
7956
lastLine = line
57+
8058
continue
8159
}
8260

@@ -113,6 +91,7 @@ func (c *Console) StartContext(ctx context.Context) error {
11391
if err := c.execute(ctx, menu, args, false); err != nil {
11492
menu.ErrorHandler(ExecutionError{newError(err, "")})
11593
}
94+
11695
lastLine = line
11796
}
11897
}
@@ -179,37 +158,8 @@ func (c *Console) execute(ctx context.Context, menu *Menu, args []string, async
179158
}
180159

181160
// 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)
161+
resetFlagsDefaults(target)
208162

209-
default:
210-
flag.Value.Set(flag.DefValue)
211-
}
212-
})
213163

214164
// Console-wide pre-run hooks, cannot.
215165
if err := c.runAllE(c.PreCmdRunHooks); err != nil {
@@ -302,6 +252,32 @@ func (c *Console) runLineHooks(args []string) ([]string, error) {
302252
return processed, nil
303253
}
304254

255+
func (c *Console) displayPreRun(line string) {
256+
if c.NewlineBefore {
257+
if !c.NewlineWhenEmpty {
258+
if !c.lineEmpty(line) {
259+
fmt.Println()
260+
}
261+
} else {
262+
fmt.Println()
263+
}
264+
}
265+
}
266+
267+
func (c *Console) displayPostRun(lastLine string) {
268+
if c.NewlineAfter {
269+
if !c.NewlineWhenEmpty {
270+
if !c.lineEmpty(lastLine) {
271+
fmt.Println()
272+
}
273+
} else {
274+
fmt.Println()
275+
}
276+
}
277+
278+
c.printed = false
279+
}
280+
305281
// monitorSignals - Monitor the signals that can be sent to the process
306282
// while a command is running. We want to be able to cancel the command.
307283
func (c *Console) monitorSignals() <-chan os.Signal {

0 commit comments

Comments
 (0)