1
0
mirror of https://github.com/packwiz/packwiz.git synced 2025-04-27 16:46:30 +02:00

Switch to cobra/viper

This commit is contained in:
comp500 2019-09-16 21:44:40 +01:00
parent 8915e16614
commit 4fea7ceebf
17 changed files with 985 additions and 719 deletions

54
cmd/refresh.go Normal file

@ -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)
}

71
cmd/remove.go Normal file

@ -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)
}

72
cmd/root.go Normal file

@ -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())
}
}

228
cmd/update.go Normal file

@ -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)
}

@ -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

@ -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",
},
}

@ -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"))

@ -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
}

@ -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
}

@ -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)
}

@ -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"`

@ -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)
}

@ -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)
}

66
curseforge/open.go Normal file

@ -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)
}

7
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

139
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=

309
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()
}