From 4337bfd3b013fb60f8171b53b49f0f4b8c4bebc0 Mon Sep 17 00:00:00 2001 From: Aaron Bennett <10927621+abennett@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:30:49 -0500 Subject: [PATCH 1/6] include a newline between different categories --- template.go | 1 + 1 file changed, 1 insertion(+) diff --git a/template.go b/template.go index 8981ab447d..c150403a12 100644 --- a/template.go +++ b/template.go @@ -10,6 +10,7 @@ var authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end var visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}` var visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}` var visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}} From 117c4901623fdb1660f9816665cafa76c3c3ff5c Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Thu, 4 Apr 2024 00:40:19 +0200 Subject: [PATCH 2/6] shell completion: do not suggest aliases --- help.go | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/help.go b/help.go index d4a056adc6..8f9cb5bd98 100644 --- a/help.go +++ b/help.go @@ -160,13 +160,9 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) { continue } if strings.HasSuffix(os.Getenv("SHELL"), "zsh") { - for _, name := range command.Names() { - _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) - } + _, _ = fmt.Fprintf(writer, "%s:%s\n", command.Name, command.Usage) } else { - for _, name := range command.Names() { - _, _ = fmt.Fprintf(writer, "%s\n", name) - } + _, _ = fmt.Fprintf(writer, "%s\n", command.Name) } } } @@ -195,23 +191,23 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden { continue } - for _, name := range flag.Names() { - name = strings.TrimSpace(name) - // this will get total count utf8 letters in flag name - count := utf8.RuneCountInString(name) - if count > 2 { - count = 2 // reuse this count to generate single - or -- in flag completion - } - // if flag name has more than one utf8 letter and last argument in cli has -- prefix then - // skip flag completion for short flags example -v or -x - if strings.HasPrefix(lastArg, "--") && count == 1 { - continue - } - // match if last argument matches this flag and it is not repeated - if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name) { - flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) - fmt.Fprintln(writer, flagCompletion) - } + + name := strings.TrimSpace(flag.Names()[0]) + // this will get total count utf8 letters in flag name + count := utf8.RuneCountInString(name) + if count > 2 { + count = 2 // reuse this count to generate single - or -- in flag completion + } + // if flag name has more than one utf8 letter and last argument in cli has -- prefix then + // skip flag completion for short flags example -v or -x + if strings.HasPrefix(lastArg, "--") && count == 1 { + continue + } + // match if last argument matches this flag and it is not repeated + if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name) { + flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) + fmt.Fprintln(writer, flagCompletion) + } } } From 6e38501c9872cd13967341aa67e7eb42c0b64c0e Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Thu, 4 Apr 2024 00:43:10 +0200 Subject: [PATCH 3/6] fix tests --- docs/v3/examples/bash-completions.md | 2 +- examples_test.go | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/v3/examples/bash-completions.md b/docs/v3/examples/bash-completions.md index 73c7fde707..737401ca63 100644 --- a/docs/v3/examples/bash-completions.md +++ b/docs/v3/examples/bash-completions.md @@ -188,7 +188,7 @@ The default shell completion flag (`--generate-bash-completion`) is defined as ```go package main diff --git a/examples_test.go b/examples_test.go index 1d0ee0d25f..b7050b801d 100644 --- a/examples_test.go +++ b/examples_test.go @@ -269,11 +269,8 @@ func ExampleCommand_Run_shellComplete_bash_withShortFlag() { _ = cmd.Run(context.Background(), os.Args) // Output: // --other - // -o // --xyz - // -x // --help - // -h } func ExampleCommand_Run_shellComplete_bash_withLongFlag() { @@ -376,10 +373,8 @@ func ExampleCommand_Run_shellComplete_bash() { _ = cmd.Run(context.Background(), os.Args) // Output: // describeit - // d // next // help - // h } func ExampleCommand_Run_shellComplete_zsh() { @@ -415,10 +410,8 @@ func ExampleCommand_Run_shellComplete_zsh() { _ = cmd.Run(context.Background(), os.Args) // Output: // describeit:use it to see a description - // d:use it to see a description // next:next example // help:Shows a list of commands or help for one command - // h:Shows a list of commands or help for one command } func ExampleCommand_Run_sliceValues() { From 754679552005a97dbee3a2b67ec6c97b7eaa9bfd Mon Sep 17 00:00:00 2001 From: yigithankarabulut Date: Tue, 30 Apr 2024 02:24:48 +0300 Subject: [PATCH 4/6] replacing the jaro-winkler algorithm usage with an internal function for suggestion. --- suggestions.go | 77 +++++++++++++++++++++++++++++++++++++++++++-- suggestions_test.go | 27 ++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/suggestions.go b/suggestions.go index 607de09deb..19683a4114 100644 --- a/suggestions.go +++ b/suggestions.go @@ -1,7 +1,7 @@ package cli import ( - "github.com/xrash/smetrics" + "math" ) const suggestDidYouMeanTemplate = "Did you mean %q?" @@ -16,13 +16,84 @@ type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string type SuggestCommandFunc func(commands []*Command, provided string) string +// Jaro is the measure of similarity between two strings. +// The result is 1 for equal strings, and 0 for completely different strings. +func jaroDistance(a, b string) float64 { + if len(a) == 0 && len(b) == 0 { + return 1 + } + if len(a) == 0 || len(b) == 0 { + return 0 + } + + lenA := float64(len(a)) + lenB := float64(len(b)) + hashA := make([]bool, len(a)) + hashB := make([]bool, len(b)) + maxDistance := int(math.Max(0, math.Floor(math.Max(lenA, lenB)/2.0)-1)) + + var matches float64 + for i := 0; i < len(a); i++ { + start := int(math.Max(0, float64(i-maxDistance))) + end := int(math.Min(lenB-1, float64(i+maxDistance))) + + for j := start; j <= end; j++ { + if hashB[j] { + continue + } + if a[i] == b[j] { + hashA[i] = true + hashB[j] = true + matches++ + break + } + } + } + if matches == 0 { + return 0 + } + + var transpositions float64 + var j int + for i := 0; i < len(a); i++ { + if !hashA[i] { + continue + } + for !hashB[j] { + j++ + } + if a[i] != b[j] { + transpositions++ + } + j++ + } + + transpositions /= 2 + return ((matches / lenA) + (matches / lenB) + ((matches - transpositions) / matches)) / 3.0 +} + +// jaroWinkler is more accurate when strings have a common prefix up to a defined maximum length. func jaroWinkler(a, b string) float64 { - // magic values are from https://github.com/xrash/smetrics/blob/039620a656736e6ad994090895784a7af15e0b80/jaro-winkler.go#L8 const ( boostThreshold = 0.7 prefixSize = 4 ) - return smetrics.JaroWinkler(a, b, boostThreshold, prefixSize) + jaroDist := jaroDistance(a, b) + if jaroDist <= boostThreshold { + return jaroDist + } + + prefix := int(math.Min(float64(len(a)), math.Min(float64(prefixSize), float64(len(b))))) + + var prefixMatch float64 + for i := 0; i < prefix; i++ { + if a[i] == b[i] { + prefixMatch++ + } else { + break + } + } + return jaroDist + 0.1*prefixMatch*(1.0-jaroDist) } func suggestFlag(flags []Flag, provided string, hideHelp bool) string { diff --git a/suggestions_test.go b/suggestions_test.go index 979fbe0cb4..b1e962104e 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -8,6 +8,33 @@ import ( "github.com/stretchr/testify/assert" ) +func TestJaroWinkler(t *testing.T) { + // Given + for _, testCase := range []struct { + a, b string + expected float64 + }{ + {"", "", 1}, + {"a", "", 0}, + {"", "a", 0}, + {"a", "a", 1}, + {"a", "b", 0}, + {"aa", "aa", 1}, + {"aa", "bb", 0}, + {"aaa", "aaa", 1}, + {"aa", "ab", 0.6666666666666666}, + {"aa", "ba", 0.6666666666666666}, + {"ba", "aa", 0.6666666666666666}, + {"ab", "aa", 0.6666666666666666}, + } { + // When + res := jaroWinkler(testCase.a, testCase.b) + + // Then + assert.Equal(t, testCase.expected, res) + } +} + func TestSuggestFlag(t *testing.T) { // Given app := buildExtendedTestCommand() From efa78e79a864419d4e1787eecf42a78e07fb9c30 Mon Sep 17 00:00:00 2001 From: Eng Zer Jun Date: Tue, 30 Apr 2024 22:02:20 +0800 Subject: [PATCH 5/6] Fix go.mod and add comments Signed-off-by: Eng Zer Jun --- go.mod | 5 +---- go.sum | 2 -- suggestions.go | 12 +++++++++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index ccc913baf8..2425a9b7fc 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,7 @@ module github.com/urfave/cli/v3 go 1.18 -require ( - github.com/stretchr/testify v1.8.4 - github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 -) +require github.com/stretchr/testify v1.8.4 require ( github.com/BurntSushi/toml v1.3.2 // indirect diff --git a/go.sum b/go.sum index 39b6c7abd3..1ce67a49f3 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/urfave/cli-altsrc/v3 v3.0.0-alpha2 h1:j4SaBpPB8++L0c0KuTnz/Yus3UQoWJ54hQjhIMW8rCM= github.com/urfave/cli-altsrc/v3 v3.0.0-alpha2/go.mod h1:Q79oyIY/z4jtzIrKEK6MUeWC7/szGr46x4QdOaOAIWc= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/suggestions.go b/suggestions.go index 19683a4114..6f29f12213 100644 --- a/suggestions.go +++ b/suggestions.go @@ -16,8 +16,11 @@ type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string type SuggestCommandFunc func(commands []*Command, provided string) string -// Jaro is the measure of similarity between two strings. -// The result is 1 for equal strings, and 0 for completely different strings. +// jaroDistance is the measure of similarity between two strings. It returns a +// value between 0 and 1, where 1 indicates identical strings and 0 indicates +// completely different strings. +// +// Adapted from https://github.com/xrash/smetrics/blob/5f08fbb34913bc8ab95bb4f2a89a0637ca922666/jaro.go. func jaroDistance(a, b string) float64 { if len(a) == 0 && len(b) == 0 { return 1 @@ -72,7 +75,10 @@ func jaroDistance(a, b string) float64 { return ((matches / lenA) + (matches / lenB) + ((matches - transpositions) / matches)) / 3.0 } -// jaroWinkler is more accurate when strings have a common prefix up to a defined maximum length. +// jaroWinkler is more accurate when strings have a common prefix up to a +// defined maximum length. +// +// Adapted from https://github.com/xrash/smetrics/blob/5f08fbb34913bc8ab95bb4f2a89a0637ca922666/jaro-winkler.go. func jaroWinkler(a, b string) float64 { const ( boostThreshold = 0.7 From 93c4b3414741768cad3bda4f5348d469b338efb2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 30 Apr 2024 19:13:29 -0400 Subject: [PATCH 6/6] Update to latest codecov action and turn up the verbosity to see if it helps reveal why we are getting rate limited. --- .github/workflows/cli.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index a2a7df6a5d..4a4c556f24 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -38,10 +38,11 @@ jobs: - if: matrix.go == '1.20.x' && matrix.os == 'ubuntu-latest' run: make v3diff - if: success() && matrix.go == '1.20.x' && matrix.os == 'ubuntu-latest' - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true + verbose: true test-docs: name: test-docs