From b16caaa6862c834ab6c5df2ad7ad7cf294a2ed8f Mon Sep 17 00:00:00 2001 From: maxlandon Date: Tue, 28 Nov 2023 19:41:43 +0100 Subject: [PATCH 01/11] Use mvdan/sh for parsing and removing comments --- commands/readline/set.go | 10 ++++------ go.mod | 1 + go.sum | 3 +++ line.go | 26 ++++++++++++++++++++++++++ menu.go | 1 - run.go | 6 +++--- 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/commands/readline/set.go b/commands/readline/set.go index 7f8f49b..53c66ad 100644 --- a/commands/readline/set.go +++ b/commands/readline/set.go @@ -15,12 +15,10 @@ import ( "github.com/reeflective/readline/inputrc" ) -var ( - // We here must assume that all bind changes during the lifetime - // of the binary are all made by a single readline application. - // This config only stores the vars/binds that have been changed. - cfgChanged = inputrc.NewConfig() -) +// We here must assume that all bind changes during the lifetime +// of the binary are all made by a single readline application. +// This config only stores the vars/binds that have been changed. +var cfgChanged = inputrc.NewConfig() // Set returns a command named `set`, for manipulating readline global options. func Set(shell *readline.Shell) *cobra.Command { diff --git a/go.mod b/go.mod index d9de271..0c5adb4 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( golang.org/x/term v0.8.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + mvdan.cc/sh/v3 v3.7.0 // indirect ) replace github.com/rsteube/carapace v0.43.0 => github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d diff --git a/go.sum b/go.sum index adab06f..be64a89 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,7 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= @@ -38,3 +39,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= +mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= diff --git a/line.go b/line.go index 06408df..d33ce06 100644 --- a/line.go +++ b/line.go @@ -5,6 +5,9 @@ import ( "errors" "strings" "unicode/utf8" + + "github.com/kballard/go-shellquote" + "mvdan.cc/sh/v3/syntax" ) var ( @@ -21,6 +24,29 @@ var ( errUnterminatedEscape = errors.New("unterminated backslash-escape") ) +// parse is in charge of removing all comments from the input line +// before execution, and if successfully parsed, split into words. +func (c *Console) parse(line string) (args []string, err error) { + lineReader := strings.NewReader(line) + parser := syntax.NewParser(syntax.KeepComments(false)) + + // Parse the shell string a syntax, removing all comments. + stmts, err := parser.Parse(lineReader, "") + if err != nil { + return nil, err + } + + var parsedLine bytes.Buffer + + err = syntax.NewPrinter().Print(&parsedLine, stmts) + if err != nil { + return nil, err + } + + // Split the line into shell words. + return shellquote.Split(parsedLine.String()) +} + // acceptMultiline determines if the line just accepted is complete (in which case // we should execute it), or incomplete (in which case we must read in multiline). func (c *Console) acceptMultiline(line []rune) (accept bool) { diff --git a/menu.go b/menu.go index 14ed949..488b63e 100644 --- a/menu.go +++ b/menu.go @@ -215,7 +215,6 @@ func (m *Menu) CheckIsAvailable(cmd *cobra.Command) error { "cmd": cmd, "filters": filters, }) - if err != nil { return err } diff --git a/run.go b/run.go index b14a7df..5de9878 100644 --- a/run.go +++ b/run.go @@ -59,10 +59,10 @@ func (c *Console) Start() error { // so we must be sure we use the good one. menu = c.activeMenu() - // Split the line into shell words. - args, err := shellquote.Split(line) + // Parse the line with bash-syntax, removing comments. + args, err := c.parse(line) if err != nil { - fmt.Printf("Line error: %s\n", err.Error()) + fmt.Printf("Parsing error: %s\n", err.Error()) continue } From ff2c313b8316246edcc619298f48111af59800a1 Mon Sep 17 00:00:00 2001 From: maxlandon Date: Tue, 28 Nov 2023 20:10:08 +0100 Subject: [PATCH 02/11] Update readline dep --- go.mod | 8 ++++---- go.sum | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 54b9364..68fb096 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.21 require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/reeflective/readline v1.0.9 + github.com/reeflective/readline v1.0.11 github.com/rsteube/carapace v0.43.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 + golang.org/x/exp v0.0.0-20231127185646-65229373498e ) require ( @@ -16,8 +16,8 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mvdan.cc/sh/v3 v3.7.0 // indirect diff --git a/go.sum b/go.sum index be64a89..8951a4e 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d h1:RK0OaQs github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d/go.mod h1:jkLt41Ne2TD2xPuMdX/2O05Smhy8vMgG7O2TYvC0yOc= github.com/reeflective/readline v1.0.9 h1:ZA+V4HIWonwn8B4gUaaKwPtBogch19qgdk1I+hqULdk= github.com/reeflective/readline v1.0.9/go.mod h1:mcD0HxNVJVteVwDm9caXKg52nQACVyfh8EyuBmgVlzY= +github.com/reeflective/readline v1.0.11 h1:4+aiebj7a89hTRJOMM98H+md1Kxu+v1XkfdCs0n6odQ= +github.com/reeflective/readline v1.0.11/go.mod h1:mcD0HxNVJVteVwDm9caXKg52nQACVyfh8EyuBmgVlzY= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -30,10 +32,16 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= +golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From c86516e77b3c9b8912edc907ae50cae9683273cd Mon Sep 17 00:00:00 2001 From: maxlandon Date: Wed, 29 Nov 2023 10:22:56 +0100 Subject: [PATCH 03/11] Update carapace dependency library --- completer.go | 5 +++-- go.mod | 10 ++++------ go.sum | 19 ++++++------------- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/completer.go b/completer.go index ad65fad..b0554e9 100644 --- a/completer.go +++ b/completer.go @@ -50,8 +50,9 @@ func (c *Console) complete(line []rune, pos int) readline.Completions { comps = c.justifyCommandComps(comps) // Suffix matchers for the completions if any. - if meta.Nospace.String() != "" { - comps = comps.NoSpace([]rune(meta.Nospace.String())...) + suffixes, err := meta.Nospace.MarshalJSON() + if len(suffixes) > 0 && err == nil { + comps = comps.NoSpace([]rune(string(suffixes))...) } // Other status/error messages diff --git a/go.mod b/go.mod index 68fb096..9cfab5a 100644 --- a/go.mod +++ b/go.mod @@ -5,22 +5,20 @@ go 1.21 require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/reeflective/readline v1.0.11 - github.com/rsteube/carapace v0.43.3 - github.com/spf13/cobra v1.7.0 + github.com/rsteube/carapace v0.45.0 + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 golang.org/x/exp v0.0.0-20231127185646-65229373498e + mvdan.cc/sh/v3 v3.7.0 ) require ( - github.com/google/go-cmp v0.5.9 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - mvdan.cc/sh/v3 v3.7.0 // indirect ) -replace github.com/rsteube/carapace v0.43.3 => github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d +replace github.com/rsteube/carapace v0.45.0 => github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d diff --git a/go.sum b/go.sum index 8951a4e..9415e78 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -13,33 +15,24 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d h1:RK0OaQs+3CMJnfXc5SNEg+Kbu4A2AVljPuG5/HcaUdM= github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d/go.mod h1:jkLt41Ne2TD2xPuMdX/2O05Smhy8vMgG7O2TYvC0yOc= -github.com/reeflective/readline v1.0.9 h1:ZA+V4HIWonwn8B4gUaaKwPtBogch19qgdk1I+hqULdk= -github.com/reeflective/readline v1.0.9/go.mod h1:mcD0HxNVJVteVwDm9caXKg52nQACVyfh8EyuBmgVlzY= github.com/reeflective/readline v1.0.11 h1:4+aiebj7a89hTRJOMM98H+md1Kxu+v1XkfdCs0n6odQ= github.com/reeflective/readline v1.0.11/go.mod h1:mcD0HxNVJVteVwDm9caXKg52nQACVyfh8EyuBmgVlzY= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= +github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 6ea0cd6cf287b27e92d4310cb0242473e17b3ea7 Mon Sep 17 00:00:00 2001 From: maxlandon Date: Tue, 5 Dec 2023 09:18:07 +0100 Subject: [PATCH 04/11] Update readline dependency --- go.mod | 3 +-- go.sum | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a52ff0e..cc9d752 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/reeflective/readline v1.0.11 + github.com/reeflective/readline v1.0.12 github.com/rsteube/carapace v0.45.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 @@ -19,7 +19,6 @@ require ( golang.org/x/term v0.15.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - mvdan.cc/sh/v3 v3.7.0 // indirect ) replace github.com/rsteube/carapace v0.45.0 => github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d diff --git a/go.sum b/go.sum index 9415e78..b87aa95 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d h1:RK0OaQs github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d/go.mod h1:jkLt41Ne2TD2xPuMdX/2O05Smhy8vMgG7O2TYvC0yOc= github.com/reeflective/readline v1.0.11 h1:4+aiebj7a89hTRJOMM98H+md1Kxu+v1XkfdCs0n6odQ= github.com/reeflective/readline v1.0.11/go.mod h1:mcD0HxNVJVteVwDm9caXKg52nQACVyfh8EyuBmgVlzY= +github.com/reeflective/readline v1.0.12 h1:QPhnlGCqWXR4iZvApU5RJ5Bo3vIaVAW6ICBJ8F8QZII= +github.com/reeflective/readline v1.0.12/go.mod h1:3iOe/qyb2jEy0KqLrNlb/CojBVqxga9ACqz/VU22H6A= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= From 80d3598e7be1e36cbd6921c9dd9d144aa035a67a Mon Sep 17 00:00:00 2001 From: maxlandon Date: Mon, 18 Dec 2023 12:08:51 +0100 Subject: [PATCH 05/11] Ensure the completion function is initialized --- completer.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/completer.go b/completer.go index b0554e9..3a093ac 100644 --- a/completer.go +++ b/completer.go @@ -20,6 +20,10 @@ import ( func (c *Console) complete(line []rune, pos int) readline.Completions { menu := c.activeMenu() + // Ensure the carapace library is called so that the function + // completer.Complete() variable is correctly initialized before use. + carapace.Gen(menu.Command) + // Split the line as shell words, only using // what the right buffer (up to the cursor) args, prefixComp, prefixLine := splitArgs(line, pos) From 58bc0b64bb63db7bda2e0561dc47ca73355ce16e Mon Sep 17 00:00:00 2001 From: maxlandon Date: Mon, 18 Dec 2023 11:13:22 +0000 Subject: [PATCH 06/11] Update completer.go --- completer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/completer.go b/completer.go index 6f23a0f..d5ca4cc 100644 --- a/completer.go +++ b/completer.go @@ -10,6 +10,7 @@ import ( "unicode" "unicode/utf8" + "github.com/rsteube/carapace" "github.com/rsteube/carapace/pkg/style" completer "github.com/rsteube/carapace/pkg/x" "github.com/rsteube/carapace/pkg/xdg" From bf36af4e90a3bfc37d0f423b566d3de1e9a7be63 Mon Sep 17 00:00:00 2001 From: maxlandon Date: Mon, 18 Dec 2023 13:04:45 +0100 Subject: [PATCH 07/11] Update carapace to latest --- go.mod | 5 ++--- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cc9d752..eb52e06 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/reeflective/readline v1.0.12 - github.com/rsteube/carapace v0.45.0 + github.com/rsteube/carapace v0.47.3-0.20231218105343-1ac50779a098 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 golang.org/x/exp v0.0.0-20231127185646-65229373498e @@ -15,10 +15,9 @@ require ( require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect + github.com/rsteube/carapace-shlex v0.1.1 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/rsteube/carapace v0.45.0 => github.com/reeflective/carapace v0.25.2-0.20230816093630-a30f5184fa0d diff --git a/go.sum b/go.sum index b87aa95..dcae362 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,10 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rsteube/carapace v0.47.3-0.20231218105343-1ac50779a098 h1:E6OJi7GVK/DnRGRaheuB+UPcgm16M7erWk3gpeaoECM= +github.com/rsteube/carapace v0.47.3-0.20231218105343-1ac50779a098/go.mod h1:4ZC5bulItu9t9sZ5yPcHgPREd8rPf274Q732n+wfl/o= +github.com/rsteube/carapace-shlex v0.1.1 h1:fRQEBBKyYKm4TXUabm4tzH904iFWSmXJl3UZhMfQNYU= +github.com/rsteube/carapace-shlex v0.1.1/go.mod h1:zPw1dOFwvLPKStUy9g2BYKanI6bsQMATzDMYQQybo3o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= From c81f697f6effc80e98a95d676013bede5ca923eb Mon Sep 17 00:00:00 2001 From: maxlandon Date: Fri, 9 Aug 2024 06:49:36 +0200 Subject: [PATCH 08/11] Update deps, usability tweaks --- completer.go | 1 - errors.go | 65 ++++++++++++++++++++++++++++++++++++++++++ example/main.go | 6 ++-- go.mod | 10 +++---- go.sum | 10 +++++++ menu.go | 6 ++++ run.go | 76 ++++++++++++++++++++++++++++++++++++++----------- 7 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 errors.go diff --git a/completer.go b/completer.go index 5d1df57..f5f4584 100644 --- a/completer.go +++ b/completer.go @@ -262,7 +262,6 @@ func splitCompWords(input string) (words []string, remainder string, err error) var word string word, input, err = splitCompWord(input, &buf) - if err != nil { return words, word + input, err } diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..7d98733 --- /dev/null +++ b/errors.go @@ -0,0 +1,65 @@ +package console + +import ( + "fmt" + "os" +) + +type ( + // ErrorHandler is a function that handles errors. + // + // The handler can choose not to bubble up the error by returning nil. + ErrorHandler func(err error) error + + // Err is the Console base error type. + // + // All errors that bubble up to the error handler should be + // wrapped in this error type. + // + // There are more concrete error types that wrap this one defined below + // this allow for easy use of errors.As. + Err struct { + err error + message string + } + + // PreReadError is an error that occurs during the pre-read phase. + PreReadError struct{ Err } + + // ParseError is an error that occurs during the parsing phase. + ParseError struct{ Err } + + // LineHookError is an error that occurs during the line hook phase. + LineHookError struct{ Err } + + // ExecutionError is an error that occurs during the execution phase. + ExecutionError struct{ Err } +) + +func defaultErrorHandler(err error) error { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + + return nil +} + +// newError creates a new Err. +func newError(err error, message string) Err { + return Err{ + err: err, + message: message, + } +} + +// Error returns the error message with an optional +// message prefix. +func (e Err) Error() string { + if len(e.message) > 0 { + return fmt.Sprintf("%s: %s", e.message, e.err.Error()) + } + return e.err.Error() +} + +// Unwrap implements the errors Unwrap interface. +func (e Err) Unwrap() error { + return e.err +} diff --git a/example/main.go b/example/main.go index 8e4a374..12f6061 100644 --- a/example/main.go +++ b/example/main.go @@ -22,9 +22,9 @@ func main() { app.SetPrintLogo(func(_ *console.Console) { fmt.Print(` - _____ __ _ _ _ _____ _ - | __ \ / _| | | | (_) / ____| | | - | |__) |___ ___| |_| | ___ ___| |_ ___ _____ | | ___ _ __ ___ ___ | | ___ + _____ __ _ _ _ _____ _ + | __ \ / _| | | | (_) / ____| | | + | |__) |___ ___| |_| | ___ ___| |_ ___ _____ | | ___ _ __ ___ ___ | | ___ | _ // _ \/ _ \ _| |/ _ \/ __| __| \ \ / / _ \ | | / _ \| '_ \/ __|/ _ \| |/ _ \ | | \ \ __/ __/ | | | __/ (__| |_| |\ V / __/ | |___| (_) | | | \__ \ (_) | | __/ |_| \_\___|\___|_| |_|\___|\___|\__|_| \_/ \___| \_____\___/|_| |_|___/\___/|_|\___| diff --git a/go.mod b/go.mod index 2460ed4..4a213fe 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,20 @@ go 1.21 require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/reeflective/readline v1.0.13 + github.com/reeflective/readline v1.0.15 github.com/rsteube/carapace v0.46.3-0.20231214181515-27e49f3c3b69 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa mvdan.cc/sh/v3 v3.7.0 ) require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rsteube/carapace-shlex v0.1.1 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2add551..f89b822 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/reeflective/readline v1.0.13 h1:TeJmYw9B7VRPZWfNExr9QHxL1m0iSicyqBSQIRn39Ss= github.com/reeflective/readline v1.0.13/go.mod h1:3iOe/qyb2jEy0KqLrNlb/CojBVqxga9ACqz/VU22H6A= +github.com/reeflective/readline v1.0.15 h1:uB/M1sAc2yZGO14Ujgr/imLwQXqGdOhDDWAEHF+MBaE= +github.com/reeflective/readline v1.0.15/go.mod h1:3iOe/qyb2jEy0KqLrNlb/CojBVqxga9ACqz/VU22H6A= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rsteube/carapace v0.46.3-0.20231214181515-27e49f3c3b69 h1:ctOUuKn5PO6VtwtaS7unNrm6u20YXESPtnKEie/u304= @@ -31,10 +35,16 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/menu.go b/menu.go index 68e9be5..f1d466b 100644 --- a/menu.go +++ b/menu.go @@ -26,6 +26,11 @@ type Menu struct { // Maps interrupt signals (CtrlC/IOF, etc) to specific error handlers. interruptHandlers map[error]func(c *Console) + // ErrorHandler is called when an error is encountered. + // + // If not set, the error is printed to the console on os.Stderr. + ErrorHandler ErrorHandler + // Input/output channels out *bytes.Buffer @@ -60,6 +65,7 @@ func newMenu(name string, console *Console) *Menu { interruptHandlers: make(map[error]func(c *Console)), histories: make(map[string]readline.History), mutex: &sync.RWMutex{}, + ErrorHandler: defaultErrorHandler, } // Add a default in memory history to each menu diff --git a/run.go b/run.go index 29c6821..f255aea 100644 --- a/run.go +++ b/run.go @@ -10,12 +10,18 @@ import ( "github.com/kballard/go-shellquote" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) // Start - Start the console application (readline loop). Blocking. // The error returned will always be an error that the console // application does not understand or cannot handle. func (c *Console) Start() error { + return c.StartContext(context.Background()) +} + +// StartContext is like console.Start(). with a user-provided context. +func (c *Console) StartContext(ctx context.Context) error { c.loadActiveHistories() // Print the console logo @@ -38,7 +44,8 @@ func (c *Console) Start() error { c.printed = false if err := c.runAllE(c.PreReadlineHooks); err != nil { - fmt.Printf("Pre-read error: %s\n", err.Error()) + menu.ErrorHandler(PreReadError{newError(err, "Pre-read error")}) + continue } @@ -62,7 +69,7 @@ func (c *Console) Start() error { // Parse the line with bash-syntax, removing comments. args, err := c.parse(line) if err != nil { - fmt.Printf("Parsing error: %s\n", err.Error()) + menu.ErrorHandler(ParseError{newError(err, "Parsing error")}) continue } @@ -74,7 +81,7 @@ func (c *Console) Start() error { // which may modify the input line args. args, err = c.runLineHooks(args) if err != nil { - fmt.Printf("Line error: %s\n", err.Error()) + menu.ErrorHandler(LineHookError{newError(err, "Line error")}) continue } @@ -83,8 +90,8 @@ func (c *Console) Start() error { // the library user is responsible for setting // the cobra behavior. // If it's an interrupt, we take care of it. - if err := c.execute(menu, args, false); err != nil { - fmt.Println(err) + if err := c.execute(ctx, menu, args, false); err != nil { + menu.ErrorHandler(ExecutionError{newError(err, "")}) } } } @@ -95,19 +102,19 @@ func (c *Console) Start() error { // workflow. // Although state segregation is a priority for this library to be ensured as much // as possible, you should be cautious when using this function to run commands. -func (m *Menu) RunCommandArgs(args []string) (err error) { +func (m *Menu) RunCommandArgs(ctx context.Context, args []string) (err error) { // The menu used and reset is the active menu. // Prepare its output buffer for the command. m.resetPreRun() // Run the command and associated helpers. - return m.console.execute(m, args, !m.console.isExecuting) + return m.console.execute(ctx, m, args, !m.console.isExecuting) } // RunCommandLine is the equivalent of menu.RunCommandArgs(), but accepts // an unsplit command line to execute. This line is split and processed in // *sh-compliant form, identically to how lines are in normal console usage. -func (m *Menu) RunCommandLine(line string) (err error) { +func (m *Menu) RunCommandLine(ctx context.Context, line string) (err error) { if len(line) == 0 { return } @@ -118,7 +125,7 @@ func (m *Menu) RunCommandLine(line string) (err error) { return fmt.Errorf("line error: %w", err) } - return m.RunCommandArgs(args) + return m.RunCommandArgs(ctx, args) } // execute - The user has entered a command input line, the arguments have been processed: @@ -127,7 +134,7 @@ func (m *Menu) RunCommandLine(line string) (err error) { // Our main object of interest is the menu's root command, and we explicitly use this reference // instead of the menu itself, because if RunCommand() is asynchronously triggered while another // command is running, the menu's root command will be overwritten. -func (c *Console) execute(menu *Menu, args []string, async bool) (err error) { +func (c *Console) execute(ctx context.Context, menu *Menu, args []string, async bool) error { if !async { c.mutex.RLock() c.isExecuting = true @@ -146,12 +153,45 @@ func (c *Console) execute(menu *Menu, args []string, async bool) (err error) { // Find the target command: if this command is filtered, don't run it. target, _, _ := cmd.Find(args) - if err = menu.CheckIsAvailable(target); err != nil { + if err := menu.CheckIsAvailable(target); err != nil { return err } + // 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) + + default: + flag.Value.Set(flag.DefValue) + } + }) + // Console-wide pre-run hooks, cannot. - if err = c.runAllE(c.PreCmdRunHooks); err != nil { + if err := c.runAllE(c.PreCmdRunHooks); err != nil { return fmt.Errorf("pre-run error: %s", err.Error()) } @@ -160,7 +200,7 @@ func (c *Console) execute(menu *Menu, args []string, async bool) (err error) { // The command execution should happen in a separate goroutine, // and should notify the main goroutine when it is done. - ctx, cancel := context.WithCancelCause(context.Background()) + ctx, cancel := context.WithCancelCause(ctx) cmd.SetContext(ctx) @@ -173,22 +213,26 @@ func (c *Console) execute(menu *Menu, args []string, async bool) (err error) { // Wait for the command to finish, or for an OS signal to be caught. select { case <-ctx.Done(): - if !errors.Is(ctx.Err(), context.Canceled) { - err = ctx.Err() + cause := context.Cause(ctx) + + if !errors.Is(cause, context.Canceled) { + return cause } case signal := <-sigchan: cancel(errors.New(signal.String())) + menu.handleInterrupt(errors.New(signal.String())) } - return err + return nil } // Run the command in a separate goroutine, and cancel the context when done. func (c *Console) executeCommand(cmd *cobra.Command, cancel context.CancelCauseFunc) { if err := cmd.Execute(); err != nil { cancel(err) + return } From 1e002433780dc7703f0aac099ace67b335cb67b3 Mon Sep 17 00:00:00 2001 From: maxlandon Date: Fri, 9 Aug 2024 08:05:19 +0200 Subject: [PATCH 09/11] Add exit command to example and document interrupts --- example/main-commands.go | 10 ++++++++++ interrupt.go | 9 ++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) 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)) { From 68bee6fdacccb71b8a266d8c0c9d5f2808427b1e Mon Sep 17 00:00:00 2001 From: maxlandon Date: Fri, 9 Aug 2024 08:15:09 +0200 Subject: [PATCH 10/11] Small refactor for flag reset --- command.go | 37 +++++++++++++++++++++++++++++++++++++ run.go | 38 +++++--------------------------------- 2 files changed, 42 insertions(+), 33 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/run.go b/run.go index d2fc0a5..db9c782 100644 --- a/run.go +++ b/run.go @@ -10,7 +10,6 @@ import ( "github.com/kballard/go-shellquote" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) // Start - Start the console application (readline loop). Blocking. @@ -37,7 +36,6 @@ func (c *Console) StartContext(ctx context.Context) error { // 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. @@ -64,6 +62,7 @@ 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) { @@ -76,7 +75,9 @@ func (c *Console) StartContext(ctx context.Context) error { if err != nil { menu.handleInterrupt(err) + lastLine = line + continue } @@ -113,6 +114,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 +181,7 @@ 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) - - default: - flag.Value.Set(flag.DefValue) - } - }) + resetFlagsDefaults(target) // Console-wide pre-run hooks, cannot. if err := c.runAllE(c.PreCmdRunHooks); err != nil { From 144f3b05f76b23f3b5d0707e105f4f6e5ba02f04 Mon Sep 17 00:00:00 2001 From: maxlandon Date: Fri, 9 Aug 2024 08:22:45 +0200 Subject: [PATCH 11/11] Refactors in run --- run.go | 56 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/run.go b/run.go index db9c782..9d8eb95 100644 --- a/run.go +++ b/run.go @@ -30,30 +30,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. - 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")}) @@ -63,15 +47,7 @@ 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) @@ -274,6 +250,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 {