From 5ba9ff5c731b5a6cb9fc50e88712e121beedb19d Mon Sep 17 00:00:00 2001 From: comp500 Date: Fri, 1 Nov 2019 15:46:50 +0000 Subject: [PATCH] Fix some printfs, clean regexes, switch to fuzzy matching for search --- cmd/init.go | 2 +- cmd/remove.go | 2 +- cmd/update.go | 4 +- curseforge/curseforge.go | 10 +-- curseforge/install.go | 140 +++++++++++++++++++++++---------------- curseforge/request.go | 1 - go.mod | 3 +- go.sum | 6 +- 8 files changed, 97 insertions(+), 71 deletions(-) diff --git a/cmd/init.go b/cmd/init.go index ed7bc4a..58600b8 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -65,7 +65,7 @@ var initCmd = &cobra.Command{ mcVersions, err := getValidMCVersions() if err != nil { - fmt.Printf("Failed to get latest minecraft versions: %s", err) + fmt.Printf("Failed to get latest minecraft versions: %s\n", err) os.Exit(1) } diff --git a/cmd/remove.go b/cmd/remove.go index 73ab46a..3f2b9f9 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -62,7 +62,7 @@ var removeCmd = &cobra.Command{ os.Exit(1) } - fmt.Printf("Mod %s removed successfully!", args[0]) + fmt.Printf("Mod %s removed successfully!\n", args[0]) }, } diff --git a/cmd/update.go b/cmd/update.go index cc3e789..059bd90 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -47,7 +47,7 @@ var updateCmd = &cobra.Command{ for _, v := range index.GetAllMods() { modData, err := core.LoadMod(v) if err != nil { - fmt.Printf("Error reading mod file: %s", err.Error()) + fmt.Printf("Error reading mod file: %s\n", err.Error()) continue } @@ -65,7 +65,7 @@ var updateCmd = &cobra.Command{ updaterMap[k] = append(slice, modData) } if !updaterFound { - fmt.Printf("A supported update system for \"%s\" cannot be found.", modData.Name) + fmt.Printf("A supported update system for \"%s\" cannot be found.\n", modData.Name) } } diff --git a/curseforge/curseforge.go b/curseforge/curseforge.go index cfdaacb..63eec4f 100644 --- a/curseforge/curseforge.go +++ b/curseforge/curseforge.go @@ -23,9 +23,9 @@ func init() { } var fileIDRegexes = [...]*regexp.Regexp{ - regexp.MustCompile("^https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/(.+)\\/files\\/(\\d+)"), - regexp.MustCompile("^https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/(.+)\\/files\\/(\\d+)"), - regexp.MustCompile("^https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/(.+)\\/download\\/(\\d+)"), + regexp.MustCompile("^https?://minecraft\\.curseforge\\.com/projects/(.+)/files/(\\d+)"), + regexp.MustCompile("^https?://(?:www\\.)?curseforge\\.com/minecraft/mc-mods/(.+)/files/(\\d+)"), + regexp.MustCompile("^https?://(?:www\\.)?curseforge\\.com/minecraft/mc-mods/(.+)/download/(\\d+)"), } func getFileIDsFromString(mod string) (bool, int, int, error) { @@ -44,8 +44,8 @@ func getFileIDsFromString(mod string) (bool, int, int, error) { } var modSlugRegexes = [...]*regexp.Regexp{ - regexp.MustCompile("^https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)"), - regexp.MustCompile("^https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)"), + regexp.MustCompile("^https?://minecraft\\.curseforge\\.com/projects/([^/]+)"), + regexp.MustCompile("^https?://(?:www\\.)?curseforge\\.com/minecraft/mc-mods/([^/]+)"), // Exact slug matcher regexp.MustCompile("^[a-z][\\da-z\\-_]{0,127}$"), } diff --git a/curseforge/install.go b/curseforge/install.go index c2ad528..c8a821f 100644 --- a/curseforge/install.go +++ b/curseforge/install.go @@ -4,11 +4,10 @@ import ( "bufio" "errors" "fmt" + "github.com/sahilm/fuzzy" "os" - "sort" "strings" - "github.com/agnivade/levenshtein" "github.com/comp500/packwiz/core" "github.com/spf13/cobra" "gopkg.in/dixonwille/wmenu.v4" @@ -71,63 +70,14 @@ var installCmd = &cobra.Command{ var modInfoData modInfo if !done { - fmt.Println("Searching CurseForge...") - searchTerm := strings.Join(args, " ") - results, err := getSearch(searchTerm, mcVersion) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - if len(results) == 0 { - fmt.Println("No mods found!") - os.Exit(1) - } else if len(results) == 1 { - modInfoData = results[0] - modID = modInfoData.ID - modInfoObtained = true - done = true - } else { - // Find the closest value to the search term - sort.Slice(results, func(i, j int) bool { - return levenshtein.ComputeDistance(searchTerm, results[i].Name) < levenshtein.ComputeDistance(searchTerm, results[j].Name) - }) - - menu := wmenu.NewMenu("Choose a number:") - - for i, v := range results { - menu.Option(v.Name, v, i == 0, nil) - } - menu.Option("Cancel", nil, false, nil) - - menu.Action(func(menuRes []wmenu.Opt) error { - if len(menuRes) != 1 || menuRes[0].Value == nil { - fmt.Println("Cancelled!") - return nil - } - - // Why is variable shadowing a thing!!!! - var ok bool - modInfoData, ok = menuRes[0].Value.(modInfo) - if !ok { - fmt.Println("Error converting interface from wmenu") - os.Exit(1) - } - modID = modInfoData.ID - modInfoObtained = true - done = true - return nil - }) - err = menu.Run() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - if !done { - return - } + var cancelled bool + cancelled, modInfoData = searchCurseforgeInternal(args, mcVersion) + if cancelled { + return } + done = true + modID = modInfoData.ID + modInfoObtained = true } if !done { @@ -220,6 +170,7 @@ var installCmd = &cobra.Command{ depFileInfo, err := getLatestFile(currData, mcVersion, 0) if err != nil { fmt.Printf("Error retrieving dependency data: %s\n", err.Error()) + continue } for _, dep := range depFileInfo.Dependencies { @@ -296,6 +247,79 @@ var installCmd = &cobra.Command{ }, } +// Used to implement interface for fuzzy matching +type modResultsList []modInfo + +func (r modResultsList) String(i int) string { + return r[i].Name +} + +func (r modResultsList) Len() int { + return len(r) +} + +func searchCurseforgeInternal(args []string, mcVersion string) (bool, modInfo) { + fmt.Println("Searching CurseForge...") + searchTerm := strings.Join(args, " ") + results, err := getSearch(searchTerm, mcVersion) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + if len(results) == 0 { + fmt.Println("No mods found!") + os.Exit(1) + } else if len(results) == 1 { + return false, results[0] + } else { + // Fuzzy search on results list + fuzzySearchResults := fuzzy.FindFrom(searchTerm, modResultsList(results)) + + menu := wmenu.NewMenu("Choose a number:") + + if len(fuzzySearchResults) == 0 { + for i, v := range results { + menu.Option(v.Name, v, i == 0, nil) + } + } else { + for i, v := range fuzzySearchResults { + menu.Option(results[v.Index].Name, results[v.Index], i == 0, nil) + } + } + menu.Option("Cancel", nil, false, nil) + + var modInfoData modInfo + var cancelled bool + menu.Action(func(menuRes []wmenu.Opt) error { + if len(menuRes) != 1 || menuRes[0].Value == nil { + fmt.Println("Cancelled!") + cancelled = true + return nil + } + + // Why is variable shadowing a thing!!!! + var ok bool + modInfoData, ok = menuRes[0].Value.(modInfo) + if !ok { + return errors.New("error converting interface from wmenu") + } + return nil + }) + err = menu.Run() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if cancelled { + return true, modInfo{} + } + return false, modInfoData + } + // This should never be executed, but Go requires it! + return false, modInfo{} +} + func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileInfo, error) { if fileID == 0 { // TODO: change to timestamp-based comparison?? diff --git a/curseforge/request.go b/curseforge/request.go index c5f91ad..0d3fc46 100644 --- a/curseforge/request.go +++ b/curseforge/request.go @@ -256,7 +256,6 @@ func getFileInfo(modID int, fileID int) (modFileInfo, error) { return infoRes, nil } -// TODO: pass gameVersion? func getSearch(searchText string, gameVersion string) ([]modInfo, error) { var infoRes []modInfo client := &http.Client{} diff --git a/go.mod b/go.mod index ae0564d..3071e0d 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,6 @@ module github.com/comp500/packwiz require ( github.com/BurntSushi/toml v0.3.1 - github.com/agnivade/levenshtein v1.0.2 github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/daviddengcn/go-colortext v0.0.0-20180409174941-186a3d44e920 // indirect github.com/denormal/go-gitignore v0.0.0-20180930084346-ae8ad1d07817 @@ -11,8 +10,10 @@ require ( github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995 // indirect github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e // indirect github.com/igorsobreira/titlecase v0.0.0-20140109233139-4156b5b858ac + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-isatty v0.0.4 // indirect github.com/mitchellh/mapstructure v1.1.2 + github.com/sahilm/fuzzy v0.1.0 github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e github.com/spf13/cobra v0.0.5 github.com/spf13/viper v1.4.0 diff --git a/go.sum b/go.sum index cda6c12..43779f9 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= -github.com/agnivade/levenshtein v1.0.2 h1:xKF7WlEzoa+ZVkzBxy0ukdzI2etYiWGlTPMNTBGncKI= -github.com/agnivade/levenshtein v1.0.2/go.mod h1:JLvzGblJATanj48SD0YhHTEFGkWvw3ASLFWSiMIFXsE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -76,6 +74,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= @@ -104,6 +104,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e h1:VAzdS5Nw68fbf5RZ8RDVlUvPXNU6Z3jtPCK/qvm4FoQ= github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=