From 4fea7ceebf6440e23c4d45c6abd19f4bdd579acd Mon Sep 17 00:00:00 2001 From: comp500 Date: Mon, 16 Sep 2019 21:44:40 +0100 Subject: [PATCH] Switch to cobra/viper --- cmd/refresh.go | 54 +++++ cmd/remove.go | 71 +++++++ cmd/root.go | 72 +++++++ cmd/update.go | 228 ++++++++++++++++++++ core/commands.go | 6 - core/flags.go | 33 --- core/index.go | 11 +- core/mod.go | 4 +- core/pack.go | 17 +- core/resolve.go | 6 +- curseforge/curseforge.go | 82 +------- curseforge/import.go | 157 +++++++------- curseforge/install.go | 442 +++++++++++++++++++++------------------ curseforge/open.go | 66 ++++++ go.mod | 7 +- go.sum | 139 +++++++++++- main.go | 309 +-------------------------- 17 files changed, 985 insertions(+), 719 deletions(-) create mode 100644 cmd/refresh.go create mode 100644 cmd/remove.go create mode 100644 cmd/root.go create mode 100644 cmd/update.go delete mode 100644 core/commands.go delete mode 100644 core/flags.go create mode 100644 curseforge/open.go diff --git a/cmd/refresh.go b/cmd/refresh.go new file mode 100644 index 0000000..8a56557 --- /dev/null +++ b/cmd/refresh.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/comp500/packwiz/core" + "github.com/spf13/cobra" +) + +// refreshCmd represents the refresh command +var refreshCmd = &cobra.Command{ + Use: "refresh", + Short: "Refresh the index file", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Loading modpack...") + pack, err := core.LoadPack() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + index, err := pack.LoadIndex() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = index.Refresh() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = index.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.UpdateIndexHash() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Index refreshed!") + }, +} + +func init() { + rootCmd.AddCommand(refreshCmd) +} diff --git a/cmd/remove.go b/cmd/remove.go new file mode 100644 index 0000000..73ab46a --- /dev/null +++ b/cmd/remove.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/comp500/packwiz/core" + "github.com/spf13/cobra" +) + +// removeCmd represents the remove command +var removeCmd = &cobra.Command{ + Use: "remove", + Short: "Remove a mod from the modpack", + Aliases: []string{"delete", "uninstall"}, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if len(args[0]) == 0 { + fmt.Println("You must specify a mod.") + os.Exit(1) + } + fmt.Println("Loading modpack...") + pack, err := core.LoadPack() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + index, err := pack.LoadIndex() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + resolvedMod, ok := index.FindMod(args[0]) + if !ok { + fmt.Println("You don't have this mod installed.") + os.Exit(1) + } + err = os.Remove(resolvedMod) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Removing mod from index...") + err = index.RemoveFile(resolvedMod) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = index.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.UpdateIndexHash() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + fmt.Printf("Mod %s removed successfully!", args[0]) + }, +} + +func init() { + rootCmd.AddCommand(removeCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..3d66333 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,72 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" +) + +var packFile string +var modsFolder string +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "packwiz", + Short: "A command line tool for creating Minecraft modpacks", +} + +// Execute starts the root command for packwiz +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +// Add adds a new command as a subcommand to packwiz +func Add(newCommand *cobra.Command) { + rootCmd.AddCommand(newCommand) +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&packFile, "pack-file", "pack.toml", "The modpack metadata file to use") + viper.BindPFlag("pack-file", rootCmd.PersistentFlags().Lookup("pack-file")) + + rootCmd.PersistentFlags().StringVar(&modsFolder, "mods-folder", "mods", "The default folder to store mod metadata files in") + viper.BindPFlag("mods-folder", rootCmd.PersistentFlags().Lookup("mods-folder")) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.packwiz.toml)") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Search config in home directory with name ".packwiz" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".packwiz") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 0000000..cc3e789 --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,228 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/comp500/packwiz/core" + "github.com/spf13/cobra" +) + +// updateCmd represents the update command +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update a mod (or all mods) in the modpack", + Aliases: []string{"upgrade"}, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // TODO: --check flag? + // TODO: specify multiple mods to update at once? + + fmt.Println("Loading modpack...") + pack, err := core.LoadPack() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + index, err := pack.LoadIndex() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + mcVersion, err := pack.GetMCVersion() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + multiple := false + var singleUpdatedName string + if len(args[0]) == 0 || args[0] == "*" { + multiple = true + + updaterMap := make(map[string][]core.Mod) + fmt.Println("Reading mod files...") + for _, v := range index.GetAllMods() { + modData, err := core.LoadMod(v) + if err != nil { + fmt.Printf("Error reading mod file: %s", err.Error()) + continue + } + + updaterFound := false + for k := range modData.Update { + slice, ok := updaterMap[k] + if !ok { + _, ok = core.Updaters[k] + if !ok { + continue + } + slice = []core.Mod{} + } + updaterFound = true + updaterMap[k] = append(slice, modData) + } + if !updaterFound { + fmt.Printf("A supported update system for \"%s\" cannot be found.", modData.Name) + } + } + + fmt.Println("Checking for updates...") + updatesFound := false + updaterPointerMap := make(map[string][]*core.Mod) + updaterCachedStateMap := make(map[string][]interface{}) + for k, v := range updaterMap { + checks, err := core.Updaters[k].CheckUpdate(v, mcVersion) + if err != nil { + // TODO: do we return err code 1? + fmt.Println(err.Error()) + continue + } + for i, check := range checks { + if check.Error != nil { + // TODO: do we return err code 1? + // TODO: better error message? + fmt.Println(check.Error.Error()) + continue + } + if check.UpdateAvailable { + if !updatesFound { + fmt.Println("Updates found:") + updatesFound = true + } + fmt.Printf("%s: %s\n", v[i].Name, check.UpdateString) + updaterPointerMap[k] = append(updaterPointerMap[k], &v[i]) + updaterCachedStateMap[k] = append(updaterCachedStateMap[k], check.CachedState) + } + } + } + + if !updatesFound { + fmt.Println("All mods are up to date!") + return + } + + fmt.Print("Do you want to update? [Y/n]: ") + answer, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + ansNormal := strings.ToLower(strings.TrimSpace(answer)) + if len(ansNormal) > 0 && ansNormal[0] == 'n' { + fmt.Println("Cancelled!") + return + } + + for k, v := range updaterPointerMap { + err := core.Updaters[k].DoUpdate(v, updaterCachedStateMap[k]) + if err != nil { + // TODO: do we return err code 1? + fmt.Println(err.Error()) + continue + } + for _, modData := range v { + format, hash, err := modData.Write() + if err != nil { + fmt.Println(err.Error()) + continue + } + err = index.RefreshFileWithHash(modData.GetFilePath(), format, hash, true) + if err != nil { + fmt.Println(err.Error()) + continue + } + } + } + } else { + modPath, ok := index.FindMod(args[0]) + if !ok { + fmt.Println("You don't have this mod installed.") + os.Exit(1) + } + modData, err := core.LoadMod(modPath) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + singleUpdatedName = modData.Name + updaterFound := false + for k := range modData.Update { + updater, ok := core.Updaters[k] + if !ok { + continue + } + updaterFound = true + + check, err := updater.CheckUpdate([]core.Mod{modData}, mcVersion) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + if len(check) != 1 { + fmt.Println("Invalid update check response") + os.Exit(1) + } + + if check[0].UpdateAvailable { + fmt.Printf("Update available: %s\n", check[0].UpdateString) + + err = updater.DoUpdate([]*core.Mod{&modData}, []interface{}{check[0].CachedState}) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + format, hash, err := modData.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = index.RefreshFileWithHash(modPath, format, hash, true) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } else { + fmt.Printf("\"%s\" is already up to date!\n", modData.Name) + return + } + + break + } + if !updaterFound { + // TODO: use file name instead of Name when len(Name) == 0 in all places? + fmt.Println("A supported update system for \"" + modData.Name + "\" cannot be found.") + os.Exit(1) + } + } + + err = index.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.UpdateIndexHash() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + if multiple { + fmt.Println("Mods updated!") + } else { + fmt.Printf("\"%s\" updated!\n", singleUpdatedName) + } + }, +} + +func init() { + rootCmd.AddCommand(updateCmd) +} diff --git a/core/commands.go b/core/commands.go deleted file mode 100644 index b00fb8b..0000000 --- a/core/commands.go +++ /dev/null @@ -1,6 +0,0 @@ -package core -import "github.com/urfave/cli" - -// Commands stores all the commands that packwiz can run. Append to this slice to add your own commands. -var Commands []cli.Command - diff --git a/core/flags.go b/core/flags.go deleted file mode 100644 index 2d00953..0000000 --- a/core/flags.go +++ /dev/null @@ -1,33 +0,0 @@ -package core -import ( - "github.com/urfave/cli" -) - -// Flags stores common information passed as flags to the program. -type Flags struct { - PackFile string - ModsFolder string -} - -// FlagsFromContext converts a CLI context (from commands) into a Flags struct, for use in helper functions. -func FlagsFromContext(c *cli.Context) Flags { - return Flags{ - c.GlobalString("pack-file"), - c.GlobalString("mods-folder"), - } -} - -// CLIFlags is used internally to initialise the internal flags (easier to keep in one place) -var CLIFlags = [...]cli.Flag{ - cli.StringFlag{ - Name: "pack-file", - Value: "pack.toml", - Usage: "The modpack metadata file to use", - }, - cli.StringFlag{ - Name: "mods-folder", - Value: "mods", - Usage: "The mods folder to use", - }, -} - diff --git a/core/index.go b/core/index.go index 05b11eb..1f65672 100644 --- a/core/index.go +++ b/core/index.go @@ -12,6 +12,7 @@ import ( "github.com/BurntSushi/toml" "github.com/denormal/go-gitignore" + "github.com/spf13/viper" "github.com/vbauerster/mpb/v4" "github.com/vbauerster/mpb/v4/decor" ) @@ -20,7 +21,6 @@ import ( type Index struct { HashFormat string `toml:"hash-format"` Files []IndexFile `toml:"files"` - flags Flags indexFile string } @@ -37,12 +37,11 @@ type IndexFile struct { } // LoadIndex attempts to load the index file from a path -func LoadIndex(indexFile string, flags Flags) (Index, error) { +func LoadIndex(indexFile string) (Index, error) { var index Index if _, err := toml.DecodeFile(indexFile, &index); err != nil { return Index{}, err } - index.flags = flags index.indexFile = indexFile if len(index.HashFormat) == 0 { index.HashFormat = "sha256" @@ -144,7 +143,7 @@ func (in *Index) updateFile(path string) error { // of files, like CraftTweaker resources. absFileDir, err := filepath.Abs(filepath.Dir(path)) if err == nil { - absModsDir, err := filepath.Abs(in.flags.ModsFolder) + absModsDir, err := filepath.Abs(viper.GetString("mods-folder")) if err == nil { if absFileDir == absModsDir { mod = true @@ -161,11 +160,11 @@ func (in *Index) Refresh() error { // for i := 0; i < runtime.NumCPU(); i++ {} // Is case-sensitivity a problem? - pathPF, _ := filepath.Abs(in.flags.PackFile) + pathPF, _ := filepath.Abs(viper.GetString("pack-file")) pathIndex, _ := filepath.Abs(in.indexFile) // TODO: A method of specifying pack root directory? - packRoot := filepath.Dir(in.flags.PackFile) + packRoot := filepath.Dir(viper.GetString("pack-file")) ignoreExists := true pathIgnore, _ := filepath.Abs(filepath.Join(packRoot, ".packwizignore")) ignore, err := gitignore.NewFromFile(filepath.Join(packRoot, ".packwizignore")) diff --git a/core/mod.go b/core/mod.go index 6fdd801..9b4e7c9 100644 --- a/core/mod.go +++ b/core/mod.go @@ -67,8 +67,8 @@ func LoadMod(modFile string) (Mod, error) { } // SetMetaName sets the mod metadata file from a given file name (to be put in the mods folder) -func (m *Mod) SetMetaName(metaName string, flags Flags) string { - m.metaFile = ResolveMod(metaName, flags) +func (m *Mod) SetMetaName(metaName string) string { + m.metaFile = ResolveMod(metaName) return m.metaFile } diff --git a/core/pack.go b/core/pack.go index 857e934..ec8dbd0 100644 --- a/core/pack.go +++ b/core/pack.go @@ -1,4 +1,5 @@ package core + import ( "crypto/sha256" "encoding/hex" @@ -8,6 +9,7 @@ import ( "path/filepath" "github.com/BurntSushi/toml" + "github.com/spf13/viper" ) // Pack stores the modpack metadata, usually in pack.toml @@ -22,36 +24,34 @@ type Pack struct { Versions map[string]string `toml:"versions"` Client map[string]toml.Primitive `toml:"client"` Server map[string]toml.Primitive `toml:"server"` - flags Flags } // LoadPack loads the modpack metadata to a Pack struct -func LoadPack(flags Flags) (Pack, error) { +func LoadPack() (Pack, error) { var modpack Pack - if _, err := toml.DecodeFile(flags.PackFile, &modpack); err != nil { + if _, err := toml.DecodeFile(viper.GetString("pack-file"), &modpack); err != nil { return Pack{}, err } if len(modpack.Index.File) == 0 { modpack.Index.File = "index.toml" } - modpack.flags = flags return modpack, nil } // LoadIndex attempts to load the index file of this modpack func (pack Pack) LoadIndex() (Index, error) { if filepath.IsAbs(pack.Index.File) { - return LoadIndex(pack.Index.File, pack.flags) + return LoadIndex(pack.Index.File) } fileNative := filepath.FromSlash(pack.Index.File) - return LoadIndex(filepath.Join(filepath.Dir(pack.flags.PackFile), fileNative), pack.flags) + return LoadIndex(filepath.Join(filepath.Dir(viper.GetString("pack-file")), fileNative)) } // UpdateIndexHash recalculates the hash of the index file of this modpack func (pack *Pack) UpdateIndexHash() error { fileNative := filepath.FromSlash(pack.Index.File) - indexFile := filepath.Join(filepath.Dir(pack.flags.PackFile), fileNative) + indexFile := filepath.Join(filepath.Dir(viper.GetString("pack-file")), fileNative) if filepath.IsAbs(pack.Index.File) { indexFile = pack.Index.File } @@ -78,7 +78,7 @@ func (pack *Pack) UpdateIndexHash() error { // Write saves the pack file func (pack Pack) Write() error { - f, err := os.Create(pack.flags.PackFile) + f, err := os.Create(viper.GetString("pack-file")) if err != nil { return err } @@ -98,4 +98,3 @@ func (pack Pack) GetMCVersion() (string, error) { } return mcVersion, nil } - diff --git a/core/resolve.go b/core/resolve.go index 3752cc0..093bd37 100644 --- a/core/resolve.go +++ b/core/resolve.go @@ -3,14 +3,16 @@ package core import ( "path/filepath" "strings" + + "github.com/spf13/viper" ) // ModExtension is the file extension of the mod metadata files const ModExtension = ".toml" // ResolveMod returns the path to a mod file from it's name -func ResolveMod(modName string, flags Flags) string { +func ResolveMod(modName string) string { // TODO: should this work for any metadata file? fileName := strings.ToLower(strings.TrimSuffix(modName, ModExtension)) + ModExtension - return filepath.Join(flags.ModsFolder, fileName) + return filepath.Join(viper.GetString("mods-folder"), fileName) } diff --git a/curseforge/curseforge.go b/curseforge/curseforge.go index f15e64b..cfdaacb 100644 --- a/curseforge/curseforge.go +++ b/curseforge/curseforge.go @@ -2,44 +2,23 @@ package curseforge import ( "errors" - "fmt" "regexp" "strconv" + "github.com/comp500/packwiz/cmd" "github.com/comp500/packwiz/core" "github.com/mitchellh/mapstructure" - "github.com/skratchdot/open-golang/open" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) +var curseforgeCmd = &cobra.Command{ + Use: "curseforge", + Aliases: []string{"cf", "curse"}, + Short: "Manage curseforge-based mods", +} + func init() { - core.Commands = append(core.Commands, cli.Command{ - Name: "curseforge", - Aliases: []string{"cf", "curse"}, - Usage: "Manage curseforge-based mods", - Subcommands: []cli.Command{{ - Name: "install", - Usage: "Install a mod from a curseforge URL, slug or ID", - Aliases: []string{"add", "get"}, - Action: func(c *cli.Context) error { - return cmdInstall(core.FlagsFromContext(c), c.Args().Get(0), c.Args().Tail()) - }, - }, { - Name: "import", - Usage: "Import an installed curseforge modpack", - Action: func(c *cli.Context) error { - return cmdImport(core.FlagsFromContext(c), c.Args().Get(0)) - }, - }, { - Name: "open", - // TODO: change semantics to "project" rather than "mod", as this supports texture packs and misc content as well? - 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)) - }, - }}, - }) + cmd.Add(curseforgeCmd) core.Updaters["curseforge"] = cfUpdater{} } @@ -99,7 +78,7 @@ func getModIDFromString(mod string) (bool, int, error) { return false, 0, nil } -func createModFile(flags core.Flags, modInfo modInfo, fileInfo modFileInfo, index *core.Index) error { +func createModFile(modInfo modInfo, fileInfo modFileInfo, index *core.Index) error { updateMap := make(map[string]map[string]interface{}) var err error @@ -126,7 +105,7 @@ func createModFile(flags core.Flags, modInfo modInfo, fileInfo modFileInfo, inde }, Update: updateMap, } - path := modMeta.SetMetaName(modInfo.Slug, flags) + path := modMeta.SetMetaName(modInfo.Slug) // If the file already exists, this will overwrite it!!! // TODO: Should this be improved? @@ -141,45 +120,6 @@ func createModFile(flags core.Flags, modInfo modInfo, fileInfo modFileInfo, inde return index.RefreshFileWithHash(path, format, hash, true) } -func cmdDoc(flags core.Flags, mod string) error { - if len(mod) == 0 { - return cli.NewExitError("You must specify a mod.", 1) - } - - fmt.Println("Loading modpack...") - 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) - } - resolvedMod, ok := index.FindMod(mod) - if !ok { - // TODO: should this auto-refresh??????? - return cli.NewExitError("You don't have this mod installed.", 1) - } - modData, err := core.LoadMod(resolvedMod) - if err != nil { - return cli.NewExitError(err, 1) - } - updateData, ok := modData.GetParsedUpdateData("curseforge") - if !ok { - return cli.NewExitError("This mod doesn't seem to be a curseforge mod!", 1) - } - cfUpdateData := updateData.(cfUpdateData) - fmt.Println("Opening browser...") - url := "https://minecraft.curseforge.com/projects/" + strconv.Itoa(cfUpdateData.ProjectID) - err = open.Start(url) - if err != nil { - fmt.Println("Opening page failed, direct link:") - fmt.Println(url) - } - - return nil -} - type cfUpdateData struct { ProjectID int `mapstructure:"project-id"` FileID int `mapstructure:"file-id"` diff --git a/curseforge/import.go b/curseforge/import.go index cfce9ad..2efb28e 100644 --- a/curseforge/import.go +++ b/curseforge/import.go @@ -6,7 +6,7 @@ import ( "os" "github.com/comp500/packwiz/core" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) type twitchPackMeta struct { @@ -42,78 +42,95 @@ type twitchPackMeta struct { } `json:"installedAddons"` } -func cmdImport(flags core.Flags, file string) error { - 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) - } - - var packMeta twitchPackMeta - // TODO: is this relative to something? - f, err := os.Open(file) - if err != nil { - return cli.NewExitError(err, 1) - } - err = json.NewDecoder(f).Decode(&packMeta) - if err != nil { - return cli.NewExitError(err, 1) - } - - modIDs := make([]int, len(packMeta.Mods)) - for i, v := range packMeta.Mods { - modIDs[i] = v.ID - } - - fmt.Println("Querying Curse API...") - - modInfos, err := getModInfoMultiple(modIDs) - if err != nil { - return cli.NewExitError(err, 1) - } - - modInfosMap := make(map[int]modInfo) - for _, v := range modInfos { - modInfosMap[v.ID] = v - } - - // TODO: multithreading???? - for _, v := range packMeta.Mods { - modInfoValue, ok := modInfosMap[v.ID] - if !ok { - if len(v.File.FriendlyName) > 0 { - fmt.Printf("Failed to obtain mod information for \"%s\"\n", v.File.FriendlyName) - } else { - fmt.Printf("Failed to obtain mod information for \"%s\"\n", v.File.FileName) - } - continue - } - - fmt.Printf("Imported mod \"%s\" successfully!\n", modInfoValue.Name) - - err = createModFile(flags, modInfoValue, modFileInfo(v.File), &index) +// importCmd represents the import command +var importCmd = &cobra.Command{ + Use: "import", + Short: "Import an installed curseforge modpack", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + pack, err := core.LoadPack() if err != nil { - return cli.NewExitError(err, 1) + fmt.Println(err) + os.Exit(1) + } + index, err := pack.LoadIndex() + if err != nil { + fmt.Println(err) + os.Exit(1) } - } - // TODO: import existing files (config etc.) + var packMeta twitchPackMeta + // TODO: is this relative to something? + f, err := os.Open(args[0]) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = json.NewDecoder(f).Decode(&packMeta) + if err != nil { + fmt.Println(err) + os.Exit(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) - } + modIDs := make([]int, len(packMeta.Mods)) + for i, v := range packMeta.Mods { + modIDs[i] = v.ID + } - return nil + fmt.Println("Querying Curse API...") + + modInfos, err := getModInfoMultiple(modIDs) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + modInfosMap := make(map[int]modInfo) + for _, v := range modInfos { + modInfosMap[v.ID] = v + } + + // TODO: multithreading???? + for _, v := range packMeta.Mods { + modInfoValue, ok := modInfosMap[v.ID] + if !ok { + if len(v.File.FriendlyName) > 0 { + fmt.Printf("Failed to obtain mod information for \"%s\"\n", v.File.FriendlyName) + } else { + fmt.Printf("Failed to obtain mod information for \"%s\"\n", v.File.FileName) + } + continue + } + + fmt.Printf("Imported mod \"%s\" successfully!\n", modInfoValue.Name) + + err = createModFile(modInfoValue, modFileInfo(v.File), &index) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + // TODO: import existing files (config etc.) + + err = index.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.UpdateIndexHash() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + }, +} + +func init() { + curseforgeCmd.AddCommand(importCmd) } diff --git a/curseforge/install.go b/curseforge/install.go index c3519ea..eca3c5d 100644 --- a/curseforge/install.go +++ b/curseforge/install.go @@ -10,7 +10,7 @@ import ( "github.com/agnivade/levenshtein" "github.com/comp500/packwiz/core" - "github.com/urfave/cli" + "github.com/spf13/cobra" "gopkg.in/dixonwille/wmenu.v4" ) @@ -21,255 +21,279 @@ type installableDep struct { fileInfo modFileInfo } -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) +// installCmd represents the install command +var installCmd = &cobra.Command{ + Use: "install", + Short: "Install a mod from a curseforge URL, slug or ID", + Aliases: []string{"add", "get"}, + Args: cobra.ArbitraryArgs, + Run: func(cmd *cobra.Command, args []string) { + if len(args[0]) == 0 { + fmt.Println("You must specify a mod.") + os.Exit(1) + } + pack, err := core.LoadPack() if err != nil { - return cli.NewExitError(err, 1) + fmt.Println(err) + os.Exit(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, " ") - results, err := getSearch(searchTerm, mcVersion) + index, err := pack.LoadIndex() if err != nil { - return cli.NewExitError(err, 1) + fmt.Println(err) + os.Exit(1) + } + mcVersion, err := pack.GetMCVersion() + if err != nil { + fmt.Println(err) + os.Exit(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() + var done bool + var modID, fileID int + // If there are more than 1 argument, go straight to searching - URLs/Slugs should not have spaces! + if len(args) == 1 { + done, modID, fileID, err = getFileIDsFromString(args[0]) if err != nil { - return cli.NewExitError(err, 1) + fmt.Println(err) + os.Exit(1) } if !done { - return nil + done, modID, err = getModIDFromString(args[0]) + // Ignore error, go to search instead (e.g. lowercase to search instead of as a slug) + if err != nil { + done = false + } } } - } - if !done { - if err == nil { - return cli.NewExitError("No mods found!", 1) + modInfoObtained := false + 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 + } + } } - return cli.NewExitError(err, 1) - } - if !modInfoObtained { - modInfoData, err = getModInfo(modID) + if !done { + if err == nil { + fmt.Println("No mods found!") + os.Exit(1) + } + fmt.Println(err) + os.Exit(1) + } + + if !modInfoObtained { + modInfoData, err = getModInfo(modID) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + var fileInfoData modFileInfo + fileInfoData, err = getLatestFile(modInfoData, mcVersion, fileID) if err != nil { - return cli.NewExitError(err, 1) + fmt.Println(err) + os.Exit(1) } - } - var fileInfoData modFileInfo - fileInfoData, err = getLatestFile(modInfoData, mcVersion, fileID) - if err != nil { - return cli.NewExitError(err, 1) - } - - if len(fileInfoData.Dependencies) > 0 { - var depsInstallable []installableDep - var depIDPendingQueue []int - for _, dep := range fileInfoData.Dependencies { - if dep.Type == dependencyTypeRequired { - depIDPendingQueue = append(depIDPendingQueue, dep.ModID) + if len(fileInfoData.Dependencies) > 0 { + var depsInstallable []installableDep + var depIDPendingQueue []int + for _, dep := range fileInfoData.Dependencies { + if dep.Type == dependencyTypeRequired { + depIDPendingQueue = append(depIDPendingQueue, dep.ModID) + } } - } - if len(depIDPendingQueue) > 0 { - fmt.Println("Finding dependencies...") + if len(depIDPendingQueue) > 0 { + fmt.Println("Finding dependencies...") - cycles := 0 - var installedIDList []int - for len(depIDPendingQueue) > 0 && cycles < maxCycles { - if installedIDList == nil { - // Get modids of all mods - for _, modPath := range index.GetAllMods() { - mod, err := core.LoadMod(modPath) - if err == nil { - data, ok := mod.GetParsedUpdateData("curseforge") - if ok { - updateData, ok := data.(cfUpdateData) + cycles := 0 + var installedIDList []int + for len(depIDPendingQueue) > 0 && cycles < maxCycles { + if installedIDList == nil { + // Get modids of all mods + for _, modPath := range index.GetAllMods() { + mod, err := core.LoadMod(modPath) + if err == nil { + data, ok := mod.GetParsedUpdateData("curseforge") if ok { - if updateData.ProjectID > 0 { - installedIDList = append(installedIDList, updateData.ProjectID) + updateData, ok := data.(cfUpdateData) + if ok { + if updateData.ProjectID > 0 { + installedIDList = append(installedIDList, updateData.ProjectID) + } } } } } } - } - // Remove installed IDs from dep queue - i := 0 - for _, id := range depIDPendingQueue { - contains := false - for _, id2 := range installedIDList { - if id == id2 { - contains = true - break + // Remove installed IDs from dep queue + i := 0 + for _, id := range depIDPendingQueue { + contains := false + for _, id2 := range installedIDList { + if id == id2 { + contains = true + break + } + } + for _, data := range depsInstallable { + if id == data.ID { + contains = true + break + } + } + if !contains { + depIDPendingQueue[i] = id + i++ } } - for _, data := range depsInstallable { - if id == data.ID { - contains = true - break - } - } - if !contains { - depIDPendingQueue[i] = id - i++ - } - } - depIDPendingQueue = depIDPendingQueue[:i] + depIDPendingQueue = depIDPendingQueue[:i] - depInfoData, err := getModInfoMultiple(depIDPendingQueue) - if err != nil { - fmt.Printf("Error retrieving dependency data: %s\n", err.Error()) - } - depIDPendingQueue = depIDPendingQueue[:0] - - for _, currData := range depInfoData { - depFileInfo, err := getLatestFile(currData, mcVersion, 0) + depInfoData, err := getModInfoMultiple(depIDPendingQueue) if err != nil { fmt.Printf("Error retrieving dependency data: %s\n", err.Error()) } + depIDPendingQueue = depIDPendingQueue[:0] - for _, dep := range depFileInfo.Dependencies { - if dep.Type == dependencyTypeRequired { - depIDPendingQueue = append(depIDPendingQueue, dep.ModID) - } - } - - depsInstallable = append(depsInstallable, installableDep{ - currData, depFileInfo, - }) - } - - cycles++ - } - if cycles >= maxCycles { - return cli.NewExitError("Dependencies recurse too deeply! Try increasing maxCycles.", 1) - } - - if len(depsInstallable) > 0 { - fmt.Println("Dependencies found:") - for _, v := range depsInstallable { - fmt.Println(v.Name) - } - - fmt.Print("Would you like to install them? [Y/n]: ") - answer, err := bufio.NewReader(os.Stdin).ReadString('\n') - if err != nil { - return cli.NewExitError(err, 1) - } - - ansNormal := strings.ToLower(strings.TrimSpace(answer)) - if !(len(ansNormal) > 0 && ansNormal[0] == 'n') { - for _, v := range depsInstallable { - err = createModFile(flags, v.modInfo, v.fileInfo, &index) + for _, currData := range depInfoData { + depFileInfo, err := getLatestFile(currData, mcVersion, 0) if err != nil { - return cli.NewExitError(err, 1) + fmt.Printf("Error retrieving dependency data: %s\n", err.Error()) } - fmt.Printf("Dependency \"%s\" successfully installed! (%s)\n", v.modInfo.Name, v.fileInfo.FileName) + + for _, dep := range depFileInfo.Dependencies { + if dep.Type == dependencyTypeRequired { + depIDPendingQueue = append(depIDPendingQueue, dep.ModID) + } + } + + depsInstallable = append(depsInstallable, installableDep{ + currData, depFileInfo, + }) } + + cycles++ + } + if cycles >= maxCycles { + fmt.Println("Dependencies recurse too deeply! Try increasing maxCycles.") + os.Exit(1) + } + + if len(depsInstallable) > 0 { + fmt.Println("Dependencies found:") + for _, v := range depsInstallable { + fmt.Println(v.Name) + } + + fmt.Print("Would you like to install them? [Y/n]: ") + answer, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + ansNormal := strings.ToLower(strings.TrimSpace(answer)) + if !(len(ansNormal) > 0 && ansNormal[0] == 'n') { + for _, v := range depsInstallable { + err = createModFile(v.modInfo, v.fileInfo, &index) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Printf("Dependency \"%s\" successfully installed! (%s)\n", v.modInfo.Name, v.fileInfo.FileName) + } + } + } else { + fmt.Println("All dependencies are already installed!") } - } else { - fmt.Println("All dependencies are already installed!") } } - } - err = createModFile(flags, modInfoData, fileInfoData, &index) - if err != nil { - return cli.NewExitError(err, 1) - } + err = createModFile(modInfoData, fileInfoData, &index) + if err != nil { + fmt.Println(err) + os.Exit(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) - } + err = index.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.UpdateIndexHash() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = pack.Write() + if err != nil { + fmt.Println(err) + os.Exit(1) + } - fmt.Printf("Mod \"%s\" successfully installed! (%s)\n", modInfoData.Name, fileInfoData.FileName) - - return nil + fmt.Printf("Mod \"%s\" successfully installed! (%s)\n", modInfoData.Name, fileInfoData.FileName) + }, } func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileInfo, error) { @@ -300,3 +324,7 @@ func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileIn } return fileInfoData, nil } + +func init() { + curseforgeCmd.AddCommand(installCmd) +} diff --git a/curseforge/open.go b/curseforge/open.go new file mode 100644 index 0000000..13a2cab --- /dev/null +++ b/curseforge/open.go @@ -0,0 +1,66 @@ +package curseforge + +import ( + "fmt" + "os" + "strconv" + + "github.com/comp500/packwiz/core" + "github.com/skratchdot/open-golang/open" + "github.com/spf13/cobra" +) + +// openCmd represents the open command +var openCmd = &cobra.Command{ + Use: "open", + // TODO: change semantics to "project" rather than "mod", as this supports texture packs and misc content as well? + Short: "Open the project page for a curseforge mod in your browser", + Aliases: []string{"doc"}, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if len(args[0]) == 0 { + fmt.Println("You must specify a mod.") + os.Exit(1) + } + + fmt.Println("Loading modpack...") + pack, err := core.LoadPack() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + index, err := pack.LoadIndex() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + resolvedMod, ok := index.FindMod(args[0]) + if !ok { + // TODO: should this auto-refresh??????? + fmt.Println("You don't have this mod installed.") + os.Exit(1) + } + modData, err := core.LoadMod(resolvedMod) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + updateData, ok := modData.GetParsedUpdateData("curseforge") + if !ok { + fmt.Println("This mod doesn't seem to be a curseforge mod!") + os.Exit(1) + } + cfUpdateData := updateData.(cfUpdateData) + fmt.Println("Opening browser...") + url := "https://minecraft.curseforge.com/projects/" + strconv.Itoa(cfUpdateData.ProjectID) + err = open.Start(url) + if err != nil { + fmt.Println("Opening page failed, direct link:") + fmt.Println(url) + } + }, +} + +func init() { + curseforgeCmd.AddCommand(openCmd) +} diff --git a/go.mod b/go.mod index 4744c83..6dd0e5c 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,17 @@ 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/davecgh/go-spew v1.1.1 // indirect github.com/daviddengcn/go-colortext v0.0.0-20180409174941-186a3d44e920 // indirect github.com/denormal/go-gitignore v0.0.0-20180930084346-ae8ad1d07817 github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450 // indirect github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995 // indirect github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e // indirect github.com/mattn/go-isatty v0.0.4 // indirect + github.com/mitchellh/go-homedir v1.1.0 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/spf13/cobra v0.0.5 + github.com/spf13/viper v1.4.0 github.com/vbauerster/mpb/v4 v4.7.0 gopkg.in/dixonwille/wlog.v2 v2.0.0 // indirect gopkg.in/dixonwille/wmenu.v4 v4.0.2 diff --git a/go.sum b/go.sum index 974d69a..5ab7355 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,25 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +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= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -12,36 +28,155 @@ github.com/daviddengcn/go-colortext v0.0.0-20180409174941-186a3d44e920 h1:d/cVoZ github.com/daviddengcn/go-colortext v0.0.0-20180409174941-186a3d44e920/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/denormal/go-gitignore v0.0.0-20180930084346-ae8ad1d07817 h1:0nsrg//Dc7xC74H/TZ5sYR8uk4UQRNjsw8zejqH5a4Q= github.com/denormal/go-gitignore v0.0.0-20180930084346-ae8ad1d07817/go.mod h1:C/+sI4IFnEpCn6VQ3GIPEp+FrQnQw+YQP3+n+GdGq7o= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450 h1:7xqw01UYS+KCI25bMrPxwNYkSns2Db1ziQPpVq99FpE= github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995 h1:f5gsjBiF9tRRVomCvrkGMMWI8W1f2OBFar2c5oakAP0= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e h1:KhcknUwkWHKZPbFy2P7jH5LKJ3La+0ZeknkkmrSgqb0= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/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= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +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= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/vbauerster/mpb/v4 v4.7.0 h1:Et+zVewxG6qmfBf4Ez+nDhLbCSh6WhZrUPHg9a6e+hw= github.com/vbauerster/mpb/v4 v4.7.0/go.mod h1:ugxYn2kSUrY10WK5CWDUZvQxjdwKFN9K3Ja3/z6p4X0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/dixonwille/wlog.v2 v2.0.0 h1:TbGWtD8ahWVSihKKr+z2Dw7Cv/7IrfN6dwrcrre17pU= gopkg.in/dixonwille/wlog.v2 v2.0.0/go.mod h1:JYQHRnhGPLno/iATOiGkEXoRanJXqdz9Qo6/QwfARUc= gopkg.in/dixonwille/wmenu.v4 v4.0.2 h1:QZVHQatLr41TOvVWiNGdwGk2DYtGEljtuxyrEdR0JIQ= gopkg.in/dixonwille/wmenu.v4 v4.0.2/go.mod h1:qgH60HxGljYu/uXZW8ctlQ8nIf5mhmraJ3Vsksexnqk= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 3ec02ab..a06120d 100644 --- a/main.go +++ b/main.go @@ -1,316 +1,11 @@ package main import ( - "fmt" - "log" - "os" - "strings" - "bufio" - - "github.com/comp500/packwiz/core" - "github.com/urfave/cli" - // Modules of packwiz + "github.com/comp500/packwiz/cmd" _ "github.com/comp500/packwiz/curseforge" ) -func init() { - core.Commands = append(core.Commands, cli.Command{ - Name: "remove", - Aliases: []string{"delete", "uninstall"}, - Usage: "Remove a mod from the modpack", - Action: func(c *cli.Context) error { - return cmdDelete(core.FlagsFromContext(c), c.Args().Get(0)) - }, - }, cli.Command{ - Name: "update", - Aliases: []string{"upgrade"}, - Usage: "Update a mod (or all mods) in the modpack", - Action: func(c *cli.Context) error { - return cmdUpdate(core.FlagsFromContext(c), c.Args().Get(0)) - }, - }, cli.Command{ - Name: "refresh", - Usage: "Refresh the index file", - Action: func(c *cli.Context) error { - return cmdRefresh(core.FlagsFromContext(c)) - }, - }) -} func main() { - app := cli.NewApp() - app.Commands = core.Commands - app.Flags = core.CLIFlags[:] - app.HideVersion = true - app.Usage = "A command line tool for creating Minecraft modpacks." - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} - -func cmdDelete(flags core.Flags, mod string) error { - if len(mod) == 0 { - return cli.NewExitError("You must specify a mod.", 1) - } - fmt.Println("Loading modpack...") - 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) - } - resolvedMod, ok := index.FindMod(mod) - if !ok { - return cli.NewExitError("You don't have this mod installed.", 1) - } - err = os.Remove(resolvedMod) - if err != nil { - return cli.NewExitError(err, 1) - } - fmt.Println("Removing mod from index...") - err = index.RemoveFile(resolvedMod) - 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 removed successfully!", mod) - return nil -} - -func cmdRefresh(flags core.Flags) error { - fmt.Println("Loading modpack...") - 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) - } - err = index.Refresh() - 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.Println("Index refreshed!") - return nil -} - -func cmdUpdate(flags core.Flags, mod string) error { - // TODO: --check flag? - // TODO: specify multiple mods to update at once? - - fmt.Println("Loading modpack...") - 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) - } - - multiple := false - var singleUpdatedName string - if len(mod) == 0 || mod == "*" { - multiple = true - - updaterMap := make(map[string][]core.Mod) - fmt.Println("Reading mod files...") - for _, v := range index.GetAllMods() { - modData, err := core.LoadMod(v) - if err != nil { - fmt.Printf("Error reading mod file: %s", err.Error()) - continue - } - - updaterFound := false - for k := range modData.Update { - slice, ok := updaterMap[k] - if !ok { - _, ok = core.Updaters[k] - if !ok { - continue - } - slice = []core.Mod{} - } - updaterFound = true - updaterMap[k] = append(slice, modData) - } - if !updaterFound { - fmt.Printf("A supported update system for \"%s\" cannot be found.", modData.Name) - } - } - - fmt.Println("Checking for updates...") - updatesFound := false - updaterPointerMap := make(map[string][]*core.Mod) - updaterCachedStateMap := make(map[string][]interface{}) - for k, v := range updaterMap { - checks, err := core.Updaters[k].CheckUpdate(v, mcVersion) - if err != nil { - // TODO: do we return err code 1? - fmt.Println(err.Error()) - continue - } - for i, check := range checks { - if check.Error != nil { - // TODO: do we return err code 1? - // TODO: better error message? - fmt.Println(check.Error.Error()) - continue - } - if check.UpdateAvailable { - if !updatesFound { - fmt.Println("Updates found:") - updatesFound = true - } - fmt.Printf("%s: %s\n", v[i].Name, check.UpdateString) - updaterPointerMap[k] = append(updaterPointerMap[k], &v[i]) - updaterCachedStateMap[k] = append(updaterCachedStateMap[k], check.CachedState) - } - } - } - - if !updatesFound { - fmt.Println("All mods are up to date!") - return nil - } - - fmt.Print("Do you want to update? [Y/n]: ") - answer, err := bufio.NewReader(os.Stdin).ReadString('\n') - if err != nil { - return cli.NewExitError(err, 1) - } - - ansNormal := strings.ToLower(strings.TrimSpace(answer)) - if len(ansNormal) > 0 && ansNormal[0] == 'n' { - fmt.Println("Cancelled!") - return nil - } - - for k, v := range updaterPointerMap { - err := core.Updaters[k].DoUpdate(v, updaterCachedStateMap[k]) - if err != nil { - // TODO: do we return err code 1? - fmt.Println(err.Error()) - continue - } - for _, modData := range v { - format, hash, err := modData.Write() - if err != nil { - fmt.Println(err.Error()) - continue - } - err = index.RefreshFileWithHash(modData.GetFilePath(), format, hash, true) - if err != nil { - fmt.Println(err.Error()) - continue - } - } - } - } else { - modPath, ok := index.FindMod(mod) - if !ok { - return cli.NewExitError("You don't have this mod installed.", 1) - } - modData, err := core.LoadMod(modPath) - if err != nil { - return cli.NewExitError(err, 1) - } - singleUpdatedName = modData.Name - updaterFound := false - for k := range modData.Update { - updater, ok := core.Updaters[k] - if !ok { - continue - } - updaterFound = true - - check, err := updater.CheckUpdate([]core.Mod{modData}, mcVersion) - if err != nil { - return cli.NewExitError(err, 1) - } - if len(check) != 1 { - return cli.NewExitError("Invalid update check response", 1) - } - - if check[0].UpdateAvailable { - fmt.Printf("Update available: %s\n", check[0].UpdateString) - - err = updater.DoUpdate([]*core.Mod{&modData}, []interface{}{check[0].CachedState}) - if err != nil { - return cli.NewExitError(err, 1) - } - - format, hash, err := modData.Write() - if err != nil { - return cli.NewExitError(err, 1) - } - err = index.RefreshFileWithHash(modPath, format, hash, true) - if err != nil { - return cli.NewExitError(err, 1) - } - } else { - fmt.Printf("\"%s\" is already up to date!\n", modData.Name) - return nil - } - - break - } - if !updaterFound { - // TODO: use file name instead of Name when len(Name) == 0 in all places? - return cli.NewExitError("A supported update system for \""+modData.Name+"\" cannot be found.", 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) - } - if multiple { - fmt.Println("Mods updated!") - } else { - fmt.Printf("\"%s\" updated!\n", singleUpdatedName) - } - return nil + cmd.Execute() }