From 91693cd3ebc6f3b4f0ba9cead0fa15bc74b24ec5 Mon Sep 17 00:00:00 2001 From: comp500 Date: Fri, 14 Jun 2019 22:35:34 +0100 Subject: [PATCH] Command to open page in browser; refactoring --- core/mod.go | 6 ++ core/resolve.go | 3 +- curseforge/curseforge.go | 178 +++++---------------------------------- curseforge/install.go | 177 ++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 6 files changed, 208 insertions(+), 159 deletions(-) create mode 100644 curseforge/install.go diff --git a/core/mod.go b/core/mod.go index c9c3d3d..81b561a 100644 --- a/core/mod.go +++ b/core/mod.go @@ -85,3 +85,9 @@ func (m Mod) Write() (string, string, error) { hashString := hex.EncodeToString(h.Sum(nil)) return "sha256", hashString, err } + +// GetParsedUpdater can be used to retrieve updater-specific information after parsing a mod file +func (m Mod) GetParsedUpdater(updaterName string) (Updater, bool) { + upd, ok := m.updaters[updaterName] + return upd, ok +} diff --git a/core/resolve.go b/core/resolve.go index 745edf6..3752cc0 100644 --- a/core/resolve.go +++ b/core/resolve.go @@ -1,4 +1,5 @@ package core + import ( "path/filepath" "strings" @@ -9,7 +10,7 @@ const ModExtension = ".toml" // ResolveMod returns the path to a mod file from it's name func ResolveMod(modName string, flags Flags) string { + // TODO: should this work for any metadata file? fileName := strings.ToLower(strings.TrimSuffix(modName, ModExtension)) + ModExtension return filepath.Join(flags.ModsFolder, fileName) } - diff --git a/curseforge/curseforge.go b/curseforge/curseforge.go index 4c7f836..17abfdb 100644 --- a/curseforge/curseforge.go +++ b/curseforge/curseforge.go @@ -1,18 +1,14 @@ package curseforge import ( - "errors" "fmt" "regexp" - "sort" "strconv" - "strings" - "github.com/agnivade/levenshtein" "github.com/comp500/packwiz/core" "github.com/mitchellh/mapstructure" + "github.com/skratchdot/open-golang/open" "github.com/urfave/cli" - "gopkg.in/dixonwille/wmenu.v4" ) func init() { @@ -32,6 +28,13 @@ func init() { Action: func(c *cli.Context) error { return cmdImport(core.FlagsFromContext(c), c.Args().Get(0)) }, + }, { + Name: "open", + Usage: "Open the project page for a curseforge mod in your browser", + Aliases: []string{"doc"}, + Action: func(c *cli.Context) error { + return cmdDoc(core.FlagsFromContext(c), c.Args().Get(0)) + }, }}, }) core.UpdateParsers["curseforge"] = cfUpdateParser{} @@ -135,167 +138,26 @@ func createModFile(flags core.Flags, modInfo modInfo, fileInfo modFileInfo, inde return index.RefreshFileWithHash(path, format, hash, true) } -func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error { +func cmdDoc(flags core.Flags, mod string) error { if len(mod) == 0 { return cli.NewExitError("You must specify a mod.", 1) } - pack, err := core.LoadPack(flags) + + modData, err := core.LoadMod(core.ResolveMod(mod, flags)) if err != nil { return cli.NewExitError(err, 1) } - index, err := pack.LoadIndex() + updateData, ok := modData.GetParsedUpdater("curseforge") + if !ok { + return cli.NewExitError("This mod doesn't seem to be a curseforge mod!", 1) + } + cfUpdateData := updateData.(cfUpdater) + url := "https://minecraft.curseforge.com/projects/" + strconv.Itoa(cfUpdateData.ProjectID) + err = open.Start(url) if err != nil { - return cli.NewExitError(err, 1) + fmt.Println("Opening page failed, direct link:") + fmt.Println(url) } - mcVersion, err := pack.GetMCVersion() - if err != nil { - return cli.NewExitError(err, 1) - } - - var done bool - var modID, fileID int - // If modArgsTail has anything, go straight to searching - URLs/Slugs should not have spaces! - if len(modArgsTail) == 0 { - done, modID, fileID, err = getFileIDsFromString(mod) - if err != nil { - return cli.NewExitError(err, 1) - } - - if !done { - done, modID, err = getModIDFromString(mod) - // Ignore error, go to search instead (e.g. lowercase to search instead of as a slug) - if err != nil { - done = false - } - } - } - - modInfoObtained := false - var modInfoData modInfo - - if !done { - fmt.Println("Searching CurseForge...") - modArgs := append([]string{mod}, modArgsTail...) - searchTerm := strings.Join(modArgs, " ") - // TODO: Curse search - // TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first? - results, err := getSearch(searchTerm, mcVersion) - if err != nil { - return cli.NewExitError(err, 1) - } - - if len(results) == 0 { - return cli.NewExitError(errors.New("no mods found"), 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 { - return errors.New("Error converting interface from wmenu") - } - modID = modInfoData.ID - modInfoObtained = true - done = true - return nil - }) - err = menu.Run() - if err != nil { - return cli.NewExitError(err, 1) - } - - if !done { - return nil - } - } - } - - if !done { - if err == nil { - err = errors.New("Mod not found") - } - return cli.NewExitError(err, 1) - } - - if !modInfoObtained { - modInfoData, err = getModInfo(modID) - if err != nil { - return cli.NewExitError(err, 1) - } - } - - fileInfoObtained := false - var fileInfoData modFileInfo - if fileID == 0 { - // TODO: how do we decide which version to use? - for _, v := range modInfoData.GameVersionLatestFiles { - // Choose "newest" version by largest ID - if v.GameVersion == mcVersion && v.ID > fileID { - fileID = v.ID - } - } - - if fileID == 0 { - return cli.NewExitError(errors.New("no files available for current Minecraft version"), 1) - } - - // The API also provides some files inline, because that's efficient! - for _, v := range modInfoData.LatestFiles { - if v.ID == fileID { - fileInfoObtained = true - fileInfoData = v - } - } - } - - if !fileInfoObtained { - fileInfoData, err = getFileInfo(modID, fileID) - if err != nil { - return cli.NewExitError(err, 1) - } - } - - err = createModFile(flags, modInfoData, fileInfoData, &index) - if err != nil { - return cli.NewExitError(err, 1) - } - - err = index.Write() - if err != nil { - return cli.NewExitError(err, 1) - } - err = pack.UpdateIndexHash() - if err != nil { - return cli.NewExitError(err, 1) - } - err = pack.Write() - if err != nil { - return cli.NewExitError(err, 1) - } - - fmt.Printf("Mod \"%s\" successfully installed! (%s)\n", modInfoData.Name, fileInfoData.FileName) return nil } diff --git a/curseforge/install.go b/curseforge/install.go new file mode 100644 index 0000000..3291bce --- /dev/null +++ b/curseforge/install.go @@ -0,0 +1,177 @@ +package curseforge + +import ( + "fmt" + "sort" + "strings" + + "github.com/agnivade/levenshtein" + "github.com/comp500/packwiz/core" + "github.com/urfave/cli" + "gopkg.in/dixonwille/wmenu.v4" +) + +func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error { + if len(mod) == 0 { + return cli.NewExitError("You must specify a mod.", 1) + } + pack, err := core.LoadPack(flags) + if err != nil { + return cli.NewExitError(err, 1) + } + index, err := pack.LoadIndex() + if err != nil { + return cli.NewExitError(err, 1) + } + mcVersion, err := pack.GetMCVersion() + if err != nil { + return cli.NewExitError(err, 1) + } + + var done bool + var modID, fileID int + // If modArgsTail has anything, go straight to searching - URLs/Slugs should not have spaces! + if len(modArgsTail) == 0 { + done, modID, fileID, err = getFileIDsFromString(mod) + if err != nil { + return cli.NewExitError(err, 1) + } + + if !done { + done, modID, err = getModIDFromString(mod) + // Ignore error, go to search instead (e.g. lowercase to search instead of as a slug) + if err != nil { + done = false + } + } + } + + modInfoObtained := false + var modInfoData modInfo + + if !done { + fmt.Println("Searching CurseForge...") + modArgs := append([]string{mod}, modArgsTail...) + searchTerm := strings.Join(modArgs, " ") + // TODO: Curse search + // TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first? + results, err := getSearch(searchTerm, mcVersion) + if err != nil { + return cli.NewExitError(err, 1) + } + + if len(results) == 0 { + return cli.NewExitError("No mods found!", 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 { + return cli.NewExitError("Error converting interface from wmenu", 1) + } + modID = modInfoData.ID + modInfoObtained = true + done = true + return nil + }) + err = menu.Run() + if err != nil { + return cli.NewExitError(err, 1) + } + + if !done { + return nil + } + } + } + + if !done { + if err == nil { + return cli.NewExitError("No mods found!", 1) + } + return cli.NewExitError(err, 1) + } + + if !modInfoObtained { + modInfoData, err = getModInfo(modID) + if err != nil { + return cli.NewExitError(err, 1) + } + } + + fileInfoObtained := false + var fileInfoData modFileInfo + if fileID == 0 { + // TODO: how do we decide which version to use? + for _, v := range modInfoData.GameVersionLatestFiles { + // Choose "newest" version by largest ID + if v.GameVersion == mcVersion && v.ID > fileID { + fileID = v.ID + } + } + + if fileID == 0 { + return cli.NewExitError("No files available for current Minecraft version!", 1) + } + + // The API also provides some files inline, because that's efficient! + for _, v := range modInfoData.LatestFiles { + if v.ID == fileID { + fileInfoObtained = true + fileInfoData = v + } + } + } + + if !fileInfoObtained { + fileInfoData, err = getFileInfo(modID, fileID) + if err != nil { + return cli.NewExitError(err, 1) + } + } + + err = createModFile(flags, modInfoData, fileInfoData, &index) + if err != nil { + return cli.NewExitError(err, 1) + } + + err = index.Write() + if err != nil { + return cli.NewExitError(err, 1) + } + err = pack.UpdateIndexHash() + if err != nil { + return cli.NewExitError(err, 1) + } + err = pack.Write() + if err != nil { + return cli.NewExitError(err, 1) + } + + fmt.Printf("Mod \"%s\" successfully installed! (%s)\n", modInfoData.Name, fileInfoData.FileName) + + return nil +} diff --git a/go.mod b/go.mod index a334841..ae0e18d 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/mattn/go-isatty v0.0.4 // indirect github.com/mitchellh/mapstructure v1.1.2 github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e github.com/stretchr/testify v1.2.2 // indirect github.com/urfave/cli v1.20.0 github.com/vbauerster/mpb/v4 v4.7.0 diff --git a/go.sum b/go.sum index cc88358..6225a6d 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=