From b736fb6a32d90c5d3e25f648e0bed0eddb0027a3 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 11:34:19 +0300 Subject: [PATCH 1/4] [options] Improved 'Errors.String' output formatting --- CHANGELOG.md | 4 ++++ ek.go | 2 +- options/options.go | 8 ++++++-- options/options_test.go | 13 +++++++++++-- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b167c68..0864b769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Changelog +### 12.117.1 + +- `[options]` Improved `Errors.String` output formatting + ### 12.117.0 - `[terminal/input]` Added new package for working with user input diff --git a/ek.go b/ek.go index 69df8e19..da223c17 100644 --- a/ek.go +++ b/ek.go @@ -21,7 +21,7 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // // VERSION is current ek package version -const VERSION = "12.117.0" +const VERSION = "12.117.1" // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/options/options.go b/options/options.go index b8d1ac19..83bf873b 100644 --- a/options/options.go +++ b/options/options.go @@ -403,8 +403,12 @@ func (e Errors) String() string { b := &strings.Builder{} - for _, err := range e { - fmt.Fprintf(b, " - %s\n", err.Error()) + for i, err := range e { + fmt.Fprintf(b, " - %s", err.Error()) + + if i != len(e)-1 { + b.WriteRune('\n') + } } return b.String() diff --git a/options/options_test.go b/options/options_test.go index 7f1ef75e..c7abe5d4 100644 --- a/options/options_test.go +++ b/options/options_test.go @@ -8,6 +8,7 @@ package options // ////////////////////////////////////////////////////////////////////////////////// // import ( + "fmt" "strings" "testing" @@ -396,9 +397,7 @@ func (s *OptUtilSuite) TestParsing(c *C) { _, errs = NewOptions().Parse([]string{"--test", "100"}, Map{"t:test": {Type: 10}}) c.Assert(errs, Not(HasLen), 0) - c.Assert(errs.IsEmpty(), Equals, false) c.Assert(errs[0], ErrorMatches, `Option "--test" has unsupported type`) - c.Assert(errs.String(), Equals, " - Option \"--test\" has unsupported type\n") // //////////////////////////////////////////////////////////////////////////////// // @@ -527,6 +526,16 @@ func (s *OptUtilSuite) TestParsing(c *C) { c.Assert(errs[0], Equals, ErrEmptyName) } +func (s *OptUtilSuite) TestErrors(c *C) { + errs := Errors{ + fmt.Errorf(`Option "--test" has unsupported type`), + fmt.Errorf(`Option "--dump" has unsupported type`), + } + + c.Assert(errs.IsEmpty(), Equals, false) + c.Assert(errs.String(), Equals, " - Option \"--test\" has unsupported type\n - Option \"--dump\" has unsupported type") +} + func (s *OptUtilSuite) TestFormat(c *C) { c.Assert(Format(""), Equals, "") c.Assert(Format("test"), Equals, "--test") From 0f822c63699bf80f7041ee3338c6e385e8c3faf1 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 11:39:43 +0300 Subject: [PATCH 2/4] [terminal/input] Actualize windows stubs --- terminal/input/input_windows.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terminal/input/input_windows.go b/terminal/input/input_windows.go index 81204141..900a0760 100644 --- a/terminal/input/input_windows.go +++ b/terminal/input/input_windows.go @@ -53,7 +53,7 @@ func Read(title string, nonEmpty bool) (string, error) { } // ❗ ReadAnswer reads user's answer for yes/no question -func ReadAnswer(title, defaultAnswer string) (bool, error) { +func ReadAnswer(title string, defaultAnswers ...string) (bool, error) { panic("UNSUPPORTED") } @@ -70,12 +70,12 @@ func ReadPasswordSecure(title string, nonEmpty bool) (*secstr.String, error) { } // ❗ AddHistory adds line to input history -func AddHistory(ui string) { +func AddHistory(data string) { panic("UNSUPPORTED") } // ❗ SetCompletionHandler adds function for autocompletion -func SetCompletionHandler(h func(in string) []string) { +func SetCompletionHandler(h func(input string) []string) { panic("UNSUPPORTED") } From 60cb2d9d5c45cf801097ffa40f8ab8bbf25b9670 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 15:14:42 +0300 Subject: [PATCH 3/4] [terminal/input] Improvements --- CHANGELOG.md | 5 +- ek.go | 2 +- go.mod | 2 +- go.sum | 4 +- terminal/input/example_test.go | 126 +++++++++++++++++++++++++++++--- terminal/input/input.go | 39 ++++++---- terminal/input/input_windows.go | 20 +++-- 7 files changed, 160 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0864b769..6e04c34d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ ## Changelog -### 12.117.1 +### 12.118.0 +- `[terminal/input]` Added method `SetHistoryCapacity` - `[options]` Improved `Errors.String` output formatting +- `[terminal/input]` Added more usage examples +- `[terminal/input]` Code refactoring ### 12.117.0 diff --git a/ek.go b/ek.go index da223c17..c89b60ec 100644 --- a/ek.go +++ b/ek.go @@ -21,7 +21,7 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // // VERSION is current ek package version -const VERSION = "12.117.1" +const VERSION = "12.118.0" // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/go.mod b/go.mod index 8d61667c..a06b6bd7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/essentialkaos/check v1.4.0 github.com/essentialkaos/depsy v1.1.0 - github.com/essentialkaos/go-linenoise/v3 v3.4.0 + github.com/essentialkaos/go-linenoise/v3 v3.6.0 golang.org/x/crypto v0.22.0 golang.org/x/sys v0.19.0 ) diff --git a/go.sum b/go.sum index a453a4ed..05d7b082 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/essentialkaos/check v1.4.0 h1:kWdFxu9odCxUqo1NNFNJmguGrDHgwi3A8daXX1n github.com/essentialkaos/check v1.4.0/go.mod h1:LMKPZ2H+9PXe7Y2gEoKyVAwUqXVgx7KtgibfsHJPus0= github.com/essentialkaos/depsy v1.1.0 h1:U6dp687UkQwXlZU17Hg2KMxbp3nfZAoZ8duaeUFYvJI= github.com/essentialkaos/depsy v1.1.0/go.mod h1:kpiTAV17dyByVnrbNaMcZt2jRwvuXClUYOzpyJQwtG8= -github.com/essentialkaos/go-linenoise/v3 v3.4.0 h1:g72w8x+/HIwOMBVvNaPYp+wMWVHrYZwzFAF7OfZR5Ts= -github.com/essentialkaos/go-linenoise/v3 v3.4.0/go.mod h1:t1kNLY2bSMQCy1JXOefD2BDLs/TTPMtTv3DFNV5uDSI= +github.com/essentialkaos/go-linenoise/v3 v3.6.0 h1:deLcrodtLIkcHjNyW/MoQpjznXPVqvwlspxk7s/5YeY= +github.com/essentialkaos/go-linenoise/v3 v3.6.0/go.mod h1:Fi6kLdZdURkXHpRkIiX2nFGORNv81CXTZ2Mn72i/cn0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/terminal/input/example_test.go b/terminal/input/example_test.go index 7e289769..df665fe0 100644 --- a/terminal/input/example_test.go +++ b/terminal/input/example_test.go @@ -7,7 +7,10 @@ package input // // // ////////////////////////////////////////////////////////////////////////////////// // -import "fmt" +import ( + "fmt" + "strings" +) // ////////////////////////////////////////////////////////////////////////////////// // @@ -16,20 +19,22 @@ func ExampleRead() { input, err := Read("Please enter user name", true) if err != nil { - fmt.Printf("Error: %v", err) + fmt.Printf("Error: %v\n", err) + return } - fmt.Printf("User name: %s\v", input) + fmt.Printf("User name: %s\n", input) // You can read user input without providing any title fmt.Println("Please enter user name") input, err = Read("", true) if err != nil { - fmt.Printf("Error: %v", err) + fmt.Printf("Error: %v\n", err) + return } - fmt.Printf("User name: %s\v", input) + fmt.Printf("User name: %s\n", input) } func ExampleReadPassword() { @@ -41,10 +46,11 @@ func ExampleReadPassword() { password, err := ReadPassword("Please enter password", true) if err != nil { - fmt.Printf("Error: %v", err) + fmt.Printf("Error: %v\n", err) + return } - fmt.Printf("User password: %s\v", password) + fmt.Printf("User password: %s\n", password) } func ExampleReadPasswordSecure() { @@ -56,10 +62,11 @@ func ExampleReadPasswordSecure() { password, err := ReadPasswordSecure("Please enter password", true) if err != nil { - fmt.Printf("Error: %v", err) + fmt.Printf("Error: %v\n", err) + return } - fmt.Printf("User password: %s\v", string(password.Data)) + fmt.Printf("User password: %s\n", string(password.Data)) password.Destroy() } @@ -77,3 +84,104 @@ func ExampleReadAnswer() { fmt.Println("File removed") } } + +func ExampleAddHistory() { + input, err := Read("Please enter user name", true) + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + // Save entered value to the input history + AddHistory(input) + + fmt.Printf("User name: %s\n", input) +} + +func ExampleSetHistoryCapacity() { + input, err := Read("Please enter user name", true) + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + // Limit history size to last 3 entries + SetHistoryCapacity(3) + + // Save entered value to the input history + AddHistory(input) + + fmt.Printf("User name: %s\n", input) +} + +func ExampleSetCompletionHandler() { + commands := []string{"add", "delete", "search", "help", "quit"} + + input.SetCompletionHandler(func(input string) []string { + var result []string + + for _, c := range commands { + if strings.HasPrefix(c, input) { + result = append(result, c) + } + } + + return result + }) + + input.SetHintHandler(func(input string) string { + for _, c := range commands { + if strings.HasPrefix(c, input) { + return c[len(input):] + } + } + + return "" + }) + + input, err := input.Read("Please enter command", true) + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Command: %s\n", input) +} + +func ExampleSetHintHandler() { + commands := []string{"add", "delete", "search", "help", "quit"} + + input.SetCompletionHandler(func(input string) []string { + var result []string + + for _, c := range commands { + if strings.HasPrefix(c, input) { + result = append(result, c) + } + } + + return result + }) + + input.SetHintHandler(func(input string) string { + for _, c := range commands { + if strings.HasPrefix(c, input) { + return c[len(input):] + } + } + + return "" + }) + + input, err := input.Read("Please enter command", true) + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Command: %s\n", input) +} diff --git a/terminal/input/input.go b/terminal/input/input.go index 2ba2c2b8..0aa078a7 100644 --- a/terminal/input/input.go +++ b/terminal/input/input.go @@ -29,9 +29,19 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // +// CompletionHandler is completion handler +type CompletionHandler = func(input string) []string + +// HintHandler is hint handler +type HintHandler = func(input string) string + +// ////////////////////////////////////////////////////////////////////////////////// // + // ErrKillSignal is error type when user cancel input var ErrKillSignal = linenoise.ErrKillSignal +// ////////////////////////////////////////////////////////////////////////////////// // + // Prompt is prompt string var Prompt = "> " @@ -46,13 +56,11 @@ var HideLength = false // custom masking symbol, so it always will be an asterisk (*). var HidePassword = false -var ( - // MaskSymbolColorTag is fmtc color tag used for MaskSymbol output - MaskSymbolColorTag = "" +// MaskSymbolColorTag is fmtc color tag used for MaskSymbol output +var MaskSymbolColorTag = "" - // TitleColorTag is fmtc color tag used for input titles - TitleColorTag = "{s}" -) +// TitleColorTag is fmtc color tag used for input titles +var TitleColorTag = "{s}" // AlwaysYes is a flag, if set ReadAnswer will always return true (useful for working // with option for forced actions) @@ -64,12 +72,12 @@ var tmux int8 // ////////////////////////////////////////////////////////////////////////////////// // -// Read reads user's input +// Read reads user input func Read(title string, nonEmpty bool) (string, error) { return readUserInput(title, nonEmpty, false) } -// ReadAnswer reads user's answer for yes/no question +// ReadAnswer reads user's answer to yes/no question func ReadAnswer(title string, defaultAnswers ...string) (bool, error) { var defaultAnswer string @@ -109,13 +117,13 @@ func ReadAnswer(title string, defaultAnswers ...string) (bool, error) { } } -// ReadPassword reads password or some private input which will be hidden +// ReadPassword reads password or some private input that will be hidden // after pressing Enter func ReadPassword(title string, nonEmpty bool) (string, error) { return readUserInput(title, nonEmpty, true) } -// ReadPasswordSecure reads password or some private input which will be hidden +// ReadPasswordSecure reads password or some private input that will be hidden // after pressing Enter func ReadPasswordSecure(title string, nonEmpty bool) (*secstr.String, error) { password, err := readUserInput(title, nonEmpty, true) @@ -132,13 +140,18 @@ func AddHistory(data string) { linenoise.AddHistory(data) } -// SetCompletionHandler adds function for autocompletion -func SetCompletionHandler(h func(input string) []string) { +// SetHistoryCapacity sets maximum capacity of history +func SetHistoryCapacity(capacity int) error { + return linenoise.SetHistoryCapacity(capacity) +} + +// SetCompletionHandler adds autocompletion function (using Tab key) +func SetCompletionHandler(h CompletionHandler) { linenoise.SetCompletionHandler(h) } // SetHintHandler adds function for input hints -func SetHintHandler(h func(input string) string) { +func SetHintHandler(h HintHandler) { linenoise.SetHintHandler(h) } diff --git a/terminal/input/input_windows.go b/terminal/input/input_windows.go index 900a0760..fe96e660 100644 --- a/terminal/input/input_windows.go +++ b/terminal/input/input_windows.go @@ -28,13 +28,11 @@ var MaskSymbol = "*" // ❗ HideLength is flag for hiding password length var HideLength = false -var ( - // ❗ MaskSymbolColorTag is fmtc color tag used for MaskSymbol output - MaskSymbolColorTag = "" +// ❗ MaskSymbolColorTag is fmtc color tag used for MaskSymbol output +var MaskSymbolColorTag = "" - // ❗ TitleColorTag is fmtc color tag used for input titles - TitleColorTag = "{s}" -) +// ❗ TitleColorTag is fmtc color tag used for input titles +var TitleColorTag = "{s}" // ❗ HidePassword is flag for hiding password while typing // Because of using the low-level linenoise method for this feature, we can not use a @@ -47,23 +45,23 @@ var AlwaysYes = false // ////////////////////////////////////////////////////////////////////////////////// // -// ❗ Read reads user's input +// ❗ Read reads user input func Read(title string, nonEmpty bool) (string, error) { panic("UNSUPPORTED") } -// ❗ ReadAnswer reads user's answer for yes/no question +// ❗ ReadAnswer reads user's answer to yes/no question func ReadAnswer(title string, defaultAnswers ...string) (bool, error) { panic("UNSUPPORTED") } -// ❗ ReadPassword reads password or some private input which will be hidden +// ❗ ReadPassword reads password or some private input that will be hidden // after pressing Enter func ReadPassword(title string, nonEmpty bool) (string, error) { panic("UNSUPPORTED") } -// ❗ ReadPasswordSecure reads password or some private input which will be hidden +// ❗ ReadPasswordSecure reads password or some private input that will be hidden // after pressing Enter func ReadPasswordSecure(title string, nonEmpty bool) (*secstr.String, error) { panic("UNSUPPORTED") @@ -74,7 +72,7 @@ func AddHistory(data string) { panic("UNSUPPORTED") } -// ❗ SetCompletionHandler adds function for autocompletion +// ❗ SetCompletionHandler adds autocompletion function (using Tab key) func SetCompletionHandler(h func(input string) []string) { panic("UNSUPPORTED") } From a6468b964c4798e8943ae5cfe44fb7397c8124da Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 15:48:39 +0300 Subject: [PATCH 4/4] [terminal/input] Fix usage examples --- .github/workflows/ci.yml | 2 -- terminal/input/example_test.go | 12 ++++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c5afd19..b9138e0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -168,8 +168,6 @@ jobs: name: Typos runs-on: ubuntu-latest - needs: SendCoverage - steps: - name: Checkout uses: actions/checkout@v4 diff --git a/terminal/input/example_test.go b/terminal/input/example_test.go index df665fe0..51caf1ea 100644 --- a/terminal/input/example_test.go +++ b/terminal/input/example_test.go @@ -119,7 +119,7 @@ func ExampleSetHistoryCapacity() { func ExampleSetCompletionHandler() { commands := []string{"add", "delete", "search", "help", "quit"} - input.SetCompletionHandler(func(input string) []string { + SetCompletionHandler(func(input string) []string { var result []string for _, c := range commands { @@ -131,7 +131,7 @@ func ExampleSetCompletionHandler() { return result }) - input.SetHintHandler(func(input string) string { + SetHintHandler(func(input string) string { for _, c := range commands { if strings.HasPrefix(c, input) { return c[len(input):] @@ -141,7 +141,7 @@ func ExampleSetCompletionHandler() { return "" }) - input, err := input.Read("Please enter command", true) + input, err := Read("Please enter command", true) if err != nil { fmt.Printf("Error: %v\n", err) @@ -154,7 +154,7 @@ func ExampleSetCompletionHandler() { func ExampleSetHintHandler() { commands := []string{"add", "delete", "search", "help", "quit"} - input.SetCompletionHandler(func(input string) []string { + SetCompletionHandler(func(input string) []string { var result []string for _, c := range commands { @@ -166,7 +166,7 @@ func ExampleSetHintHandler() { return result }) - input.SetHintHandler(func(input string) string { + SetHintHandler(func(input string) string { for _, c := range commands { if strings.HasPrefix(c, input) { return c[len(input):] @@ -176,7 +176,7 @@ func ExampleSetHintHandler() { return "" }) - input, err := input.Read("Please enter command", true) + input, err := Read("Please enter command", true) if err != nil { fmt.Printf("Error: %v\n", err)