Show suggestions for options based on levenshtein distance

This commit is contained in:
Kovid Goyal 2022-09-30 14:18:21 +05:30
parent 654bd23109
commit 75ead358a2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 72 additions and 0 deletions

View File

@ -301,6 +301,28 @@ func (self *Command) GetVisibleOptions() ([]string, map[string][]*Option) {
return group_titles, gmap
}
func (self *Command) SuggestionsForOption(name_with_hyphens string, max_distance int /* good default is 2 */) []string {
ans := make([]string, 0, 8)
q := strings.ToLower(name_with_hyphens)
self.VisitAllOptions(func(opt *Option) error {
for _, a := range opt.Aliases {
as := a.String()
if utils.LevenshteinDistance(as, q, true) <= max_distance {
ans = append(ans, as)
}
}
return nil
})
utils.StableSort(ans, func(a, b string) bool {
la, lb := utils.LevenshteinDistance(a, q, true), utils.LevenshteinDistance(b, q, true)
if la != lb {
return la < lb
}
return a < b
})
return ans
}
func (self *Command) FindSubCommand(name string) *Command {
for _, g := range self.SubCommandGroups {
c := g.FindSubCommand(name)

View File

@ -26,6 +26,10 @@ func (self *Command) parse_args(ctx *Context, args []string) error {
opt = possible_options[0]
opt_str = opt.MatchingAlias(NormalizeOptionName(opt_str), !strings.HasPrefix(opt_str, "--"))
} else if len(possible_options) == 0 {
possibles := self.SuggestionsForOption(opt_str, 2)
if len(possibles) > 0 {
return &ParseError{Message: fmt.Sprintf("Unknown option: :yellow:`%s`. Did you mean:\n\t%s", opt_str, strings.Join(possibles, "\n\t"))}
}
return &ParseError{Message: fmt.Sprintf("Unknown option: :yellow:`%s`", opt_str)}
} else {
ambi := make([]string, len(possible_options))

View File

@ -0,0 +1,46 @@
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
package utils
import (
"fmt"
"strings"
)
var _ = fmt.Print
// compares two strings and returns the Levenshtein distance between them.
func LevenshteinDistance(s, t string, ignore_case bool) int {
if ignore_case {
s = strings.ToLower(s)
t = strings.ToLower(t)
}
d := make([][]int, len(s)+1)
for i := range d {
d[i] = make([]int, len(t)+1)
}
for i := range d {
d[i][0] = i
}
for j := range d[0] {
d[0][j] = j
}
for j := 1; j <= len(t); j++ {
for i := 1; i <= len(s); i++ {
if s[i-1] == t[j-1] {
d[i][j] = d[i-1][j-1]
} else {
min := d[i-1][j]
if d[i][j-1] < min {
min = d[i][j-1]
}
if d[i-1][j-1] < min {
min = d[i-1][j-1]
}
d[i][j] = min + 1
}
}
}
return d[len(s)][len(t)]
}