From db550f81cdacd6448197f264d09a7954aafe9f61 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 16 Dec 2018 18:28:18 +1100 Subject: [PATCH] Initial commit of the Setup command --- cmd/cli.go | 268 +++++++++++++++++++++++++++++++++++++++++++ cmd/fs.go | 130 +++++++++++++++++++++ cmd/log.go | 82 +++++++++++++ cmd/program.go | 43 +++++++ cmd/setup.go | 1 + cmd/system.go | 200 ++++++++++++++++++++++++++++++++ cmd/version.go | 5 + cmd/wails/0_setup.go | 60 ++++++++++ cmd/wails/main.go | 26 +++++ go.mod | 14 +++ go.sum | 21 ++++ 11 files changed, 850 insertions(+) create mode 100644 cmd/cli.go create mode 100644 cmd/fs.go create mode 100644 cmd/log.go create mode 100644 cmd/program.go create mode 100644 cmd/setup.go create mode 100644 cmd/system.go create mode 100644 cmd/version.go create mode 100644 cmd/wails/0_setup.go create mode 100644 cmd/wails/main.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/cmd/cli.go b/cmd/cli.go new file mode 100644 index 000000000..3577d5d8d --- /dev/null +++ b/cmd/cli.go @@ -0,0 +1,268 @@ +package cmd + +import ( + "flag" + "fmt" + "os" + "strings" +) + +// NewCli - Creates a new Cli application object +func NewCli(name, description string) *Cli { + result := &Cli{} + result.rootCommand = NewCommand(name, description, result, "") + result.log = NewLogger() + return result +} + +// Cli - The main application object +type Cli struct { + rootCommand *Command + defaultCommand *Command + preRunCommand func(*Cli) error + log *Logger +} + +// Version - Set the Application version string +func (c *Cli) Version(version string) { + c.rootCommand.AppVersion = version +} + +// PrintHelp - Prints the application's help +func (c *Cli) PrintHelp() { + c.rootCommand.PrintHelp() +} + +// Run - Runs the application with the given arguments +func (c *Cli) Run(args ...string) error { + if c.preRunCommand != nil { + err := c.preRunCommand(c) + if err != nil { + return err + } + } + if len(args) == 0 { + args = os.Args[1:] + } + return c.rootCommand.Run(args) +} + +// DefaultCommand - Sets the given command as the command to run when +// no other commands given +func (c *Cli) DefaultCommand(defaultCommand *Command) *Cli { + c.defaultCommand = defaultCommand + return c +} + +// Command - Adds a command to the application +func (c *Cli) Command(name, description string) *Command { + return c.rootCommand.Command(name, description) +} + +// PreRun - Calls the given function before running the specific command +func (c *Cli) PreRun(callback func(*Cli) error) { + c.preRunCommand = callback +} + +// BoolFlag - Adds a boolean flag to the root command +func (c *Cli) BoolFlag(name, description string, variable *bool) *Command { + c.rootCommand.BoolFlag(name, description, variable) + return c.rootCommand +} + +// StringFlag - Adds a string flag to the root command +func (c *Cli) StringFlag(name, description string, variable *string) *Command { + c.rootCommand.StringFlag(name, description, variable) + return c.rootCommand +} + +// Action represents a function that gets calls when the command is called by +// the user +type Action func() error + +// Command represents a command that may be run by the user +type Command struct { + Name string + CommandPath string + Shortdescription string + Longdescription string + AppVersion string + SubCommands []*Command + SubCommandsMap map[string]*Command + longestSubcommand int + ActionCallback Action + App *Cli + Flags *flag.FlagSet + flagCount int + log *Logger + helpFlag bool +} + +// NewCommand creates a new Command +func NewCommand(name string, description string, app *Cli, parentCommandPath string) *Command { + result := &Command{ + Name: name, + Shortdescription: description, + SubCommandsMap: make(map[string]*Command), + App: app, + } + + // Set up command path + if parentCommandPath != "" { + result.CommandPath += parentCommandPath + " " + } + result.CommandPath += name + + // Set up flag set + result.Flags = flag.NewFlagSet(result.CommandPath, flag.ContinueOnError) + result.BoolFlag("help", "Get help on the '"+result.CommandPath+"' command.", &result.helpFlag) + + // result.Flags.Usage = result.PrintHelp + + return result +} + +// parseFlags parses the given flags +func (c *Command) parseFlags(args []string) error { + // Parse flags + tmp := os.Stderr + os.Stderr = nil + err := c.Flags.Parse(args) + os.Stderr = tmp + if err != nil { + fmt.Printf("Error: %s\n\n", err.Error()) + c.PrintHelp() + } + return err +} + +// Run - Runs the Command with the given arguments +func (c *Command) Run(args []string) error { + + // If we have arguments, process them + if len(args) > 0 { + // Check for subcommand + subcommand := c.SubCommandsMap[args[0]] + if subcommand != nil { + return subcommand.Run(args[1:]) + } + + // Parse flags + err := c.parseFlags(args) + if err != nil { + fmt.Printf("Error: %s\n\n", err.Error()) + c.PrintHelp() + return err + } + + // Help takes precedence + if c.helpFlag { + c.PrintHelp() + return nil + } + } + + // Do we have an action? + if c.ActionCallback != nil { + return c.ActionCallback() + } + + // If we haven't specified a subcommand + // check for an app level default command + if c.App.defaultCommand != nil { + // Prevent recursion! + if c.App.defaultCommand != c { + return c.App.defaultCommand.Run(args) + } + } + + // Nothing left we can do + c.PrintHelp() + return nil +} + +// Action - Define an action from this command +func (c *Command) Action(callback Action) *Command { + c.ActionCallback = callback + return c +} + +// PrintHelp - Output the help text for this command +func (c *Command) PrintHelp() { + versionString := c.AppVersion + if versionString != "" { + versionString = " " + versionString + } + commandTitle := c.CommandPath + if c.Shortdescription != "" { + commandTitle += " - " + c.Shortdescription + } + // Ignore root command + if c.CommandPath != c.Name { + c.log.Yellow(commandTitle) + } + if c.Longdescription != "" { + fmt.Println() + fmt.Println(c.Longdescription + "\n") + } + if len(c.SubCommands) > 0 { + fmt.Println("") + c.log.White("Available commands:") + fmt.Println("") + for _, subcommand := range c.SubCommands { + spacer := strings.Repeat(" ", 3+c.longestSubcommand-len(subcommand.Name)) + isDefault := "" + if subcommand.isDefaultCommand() { + isDefault = "[default]" + } + fmt.Printf(" %s%s%s %s\n", subcommand.Name, spacer, subcommand.Shortdescription, isDefault) + } + } + if c.flagCount > 0 { + fmt.Println("") + c.log.White("Flags:") + fmt.Println() + c.Flags.SetOutput(os.Stdout) + c.Flags.PrintDefaults() + c.Flags.SetOutput(os.Stderr) + + } + fmt.Println() +} + +// isDefaultCommand returns true if called on the default command +func (c *Command) isDefaultCommand() bool { + return c.App.defaultCommand == c +} + +// Command - Defines a subcommand +func (c *Command) Command(name, description string) *Command { + result := NewCommand(name, description, c.App, c.CommandPath) + result.log = c.log + c.SubCommands = append(c.SubCommands, result) + c.SubCommandsMap[name] = result + if len(name) > c.longestSubcommand { + c.longestSubcommand = len(name) + } + return result +} + +// BoolFlag - Adds a boolean flag to the command +func (c *Command) BoolFlag(name, description string, variable *bool) *Command { + c.Flags.BoolVar(variable, name, *variable, description) + c.flagCount++ + return c +} + +// StringFlag - Adds a string flag to the command +func (c *Command) StringFlag(name, description string, variable *string) *Command { + c.Flags.StringVar(variable, name, *variable, description) + c.flagCount++ + return c +} + +// LongDescription - Sets the long description for the command +func (c *Command) LongDescription(Longdescription string) *Command { + c.Longdescription = Longdescription + return c +} diff --git a/cmd/fs.go b/cmd/fs.go new file mode 100644 index 000000000..d517f3284 --- /dev/null +++ b/cmd/fs.go @@ -0,0 +1,130 @@ +package cmd + +import ( + "crypto/md5" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" +) + +type FSHelper struct { +} + +func NewFSHelper() *FSHelper { + result := &FSHelper{} + return result +} + +// Returns true if the given path resolves to a directory on the filesystem +func (fs *FSHelper) DirExists(path string) bool { + fi, err := os.Lstat(path) + if err != nil { + return false + } + + return fi.Mode().IsDir() +} + +// FileExists returns a boolean value indicating whether +// the given file exists +func (fs *FSHelper) FileExists(path string) bool { + fi, err := os.Lstat(path) + if err != nil { + return false + } + + return fi.Mode().IsRegular() +} + +// MkDirs creates the given nested directories. +// Returns error on failure +func (fs *FSHelper) MkDirs(fullPath string, mode ...os.FileMode) error { + var perms os.FileMode + perms = 0700 + if len(mode) == 1 { + perms = mode[0] + } + return os.MkdirAll(fullPath, perms) +} + +// CopyFile from source to target +func (fs *FSHelper) CopyFile(source, target string) error { + s, err := os.Open(source) + if err != nil { + return err + } + defer s.Close() + d, err := os.Create(target) + if err != nil { + return err + } + if _, err := io.Copy(d, s); err != nil { + d.Close() + return err + } + return d.Close() +} + +// Cwd returns the current working directory +// Aborts on Failure +func (fs *FSHelper) Cwd() string { + cwd, err := os.Getwd() + if err != nil { + log.Fatal("Unable to get working directory!") + } + return cwd +} + +// GetSubdirs will return a list of FQPs to subdirectories in the given directory +func (fs *FSHelper) GetSubdirs(dir string) (map[string]string, error) { + + // Read in the directory information + fileInfo, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + // Allocate space for the list + subdirs := make(map[string]string) + + // Pull out the directories and store in the map as + // map["directoryName"] = "path/to/directoryName" + for _, file := range fileInfo { + if file.IsDir() { + subdirs[file.Name()] = filepath.Join(dir, file.Name()) + } + } + return subdirs, nil +} + +// MkDir creates the given directory. +// Returns error on failure +func (fs *FSHelper) MkDir(dir string) error { + return os.Mkdir(dir, 0700) +} + +// LoadAsString will attempt to load the given file and return +// its contents as a string +func (fs *FSHelper) LoadAsString(filename string) (string, error) { + bytes, err := ioutil.ReadFile(filename) + return string(bytes), err +} + +// FileMD5 returns the md5sum of the given file +func (fs *FSHelper) FileMD5(filename string) (string, error) { + f, err := os.Open(filename) + if err != nil { + return "", err + } + defer f.Close() + + h := md5.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + + return fmt.Sprintf("%x", h.Sum(nil)), nil +} diff --git a/cmd/log.go b/cmd/log.go new file mode 100644 index 000000000..3da1f7d2e --- /dev/null +++ b/cmd/log.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/color" +) + +// Logger struct +type Logger struct { +} + +// NewLogger creates a new logger! +func NewLogger() *Logger { + return &Logger{} +} + +// Yellow - Outputs yellow text +func (l *Logger) Yellow(format string, a ...interface{}) { + color.New(color.FgHiYellow).PrintfFunc()(format+"\n", a...) +} + +// Yellowf - Outputs yellow text without the newline +func (l *Logger) Yellowf(format string, a ...interface{}) { + color.New(color.FgHiYellow).PrintfFunc()(format, a...) +} + +// Green - Outputs Green text +func (l *Logger) Green(format string, a ...interface{}) { + color.New(color.FgHiGreen).PrintfFunc()(format+"\n", a...) +} + +// White - Outputs White text +func (l *Logger) White(format string, a ...interface{}) { + color.New(color.FgHiWhite).PrintfFunc()(format+"\n", a...) +} + +// WhiteUnderline - Outputs White text with underline +func (l *Logger) WhiteUnderline(format string, a ...interface{}) { + l.White(format, a...) + l.White(l.underline(format)) +} + +// YellowUnderline - Outputs Yellow text with underline +func (l *Logger) YellowUnderline(format string, a ...interface{}) { + l.Yellow(format, a...) + l.Yellow(l.underline(format)) +} + +// underline returns a string of a line, the length of the message given to it +func (l *Logger) underline(message string) string { + return strings.Repeat("-", len(message)) +} + +// Red - Outputs Red text +func (l *Logger) Red(format string, a ...interface{}) { + color.New(color.FgHiRed).PrintfFunc()(format+"\n", a...) +} + +// Error - Outputs an Error message +func (l *Logger) Error(format string, a ...interface{}) { + color.New(color.FgHiRed).PrintfFunc()("Error: "+format+"\n", a...) +} + +// PrintBanner prints the Wails banner before running commands +func (l *Logger) PrintBanner() error { + banner1 := ` _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ ` + "`" + `/ / / ___/ +| |/ |/ / /_/ / / (__ ) ` + banner2 := `|__/|__/\__,_/_/_/____/ ` + + l.Yellowf(banner1) + l.Red(Version) + l.Yellowf(banner2) + l.Green("https://wails.app") + l.White("The lightweight framework for web-like apps") + fmt.Println() + + return nil +} diff --git a/cmd/program.go b/cmd/program.go new file mode 100644 index 000000000..c19ed989d --- /dev/null +++ b/cmd/program.go @@ -0,0 +1,43 @@ +package cmd + +import ( + "os/exec" + "path/filepath" +) + +// ProgramHelper - Utility functions around installed applications +type ProgramHelper struct{} + +// NewProgramHelper - Creates a new ProgramHelper +func NewProgramHelper() *ProgramHelper { + return &ProgramHelper{} +} + +// IsInstalled tries to determine if the given binary name is installed +func (p *ProgramHelper) IsInstalled(programName string) bool { + _, err := exec.LookPath(programName) + return err == nil +} + +// Program - A struct to define an installed application/binary +type Program struct { + Name string `json:"name"` + Path string `json:"path"` +} + +// FindProgram attempts to find the given program on the system.FindProgram +// Returns a struct with the name and path to the program +func (p *ProgramHelper) FindProgram(programName string) *Program { + path, err := exec.LookPath(programName) + if err != nil { + return nil + } + path, err = filepath.Abs(path) + if err != nil { + return nil + } + return &Program{ + Name: programName, + Path: path, + } +} diff --git a/cmd/setup.go b/cmd/setup.go new file mode 100644 index 000000000..1d619dd05 --- /dev/null +++ b/cmd/setup.go @@ -0,0 +1 @@ +package cmd diff --git a/cmd/system.go b/cmd/system.go new file mode 100644 index 000000000..1e657ab87 --- /dev/null +++ b/cmd/system.go @@ -0,0 +1,200 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "path/filepath" + "strconv" + "time" + + "github.com/AlecAivazis/survey" + homedir "github.com/mitchellh/go-homedir" +) + +type SystemHelper struct { + log *Logger + fs *FSHelper + configFilename string + homeDir string + wailsSystemDir string + wailsSystemConfig string +} + +func NewSystemHelper() *SystemHelper { + result := &SystemHelper{ + fs: NewFSHelper(), + log: NewLogger(), + configFilename: "wails.json", + } + result.setSystemDirs() + return result +} + +// Internal +// setSystemDirs calculates the system directories it is interested in +func (s *SystemHelper) setSystemDirs() { + var err error + s.homeDir, err = homedir.Dir() + if err != nil { + log.Fatal("Cannot find home directory! Please file a bug report!") + } + // TODO: A better config system + s.wailsSystemDir = filepath.Join(s.homeDir, ".wails") + s.wailsSystemConfig = filepath.Join(s.wailsSystemDir, s.configFilename) +} + +// ConfigFileExists - Returns true if it does! +func (s *SystemHelper) ConfigFileExists() bool { + return s.fs.FileExists(s.wailsSystemConfig) +} + +// SystemDirExists - Returns true if it does! +func (s *SystemHelper) systemDirExists() bool { + return s.fs.DirExists(s.wailsSystemDir) +} + +// LoadConfig attempts to load the Wails system config +func (s *SystemHelper) LoadConfig() (*SystemConfig, error) { + return NewSystemConfig(s.wailsSystemConfig) +} + +// ConfigFileIsValid checks if the config file is valid +func (s *SystemHelper) ConfigFileIsValid() bool { + _, err := NewSystemConfig(s.wailsSystemConfig) + return err == nil +} + +// BackupConfig attempts to backup the system config file +func (s *SystemHelper) BackupConfig() (string, error) { + now := strconv.FormatInt(time.Now().UTC().UnixNano(), 10) + backupFilename := s.wailsSystemConfig + "." + now + err := s.fs.CopyFile(s.wailsSystemConfig, backupFilename) + if err != nil { + return "", err + } + return backupFilename, nil +} + +func (s *SystemHelper) setup() error { + + // Answers. We all need them. + answers := &SystemConfig{} + + // Try to load current values - ignore errors + config, err := s.LoadConfig() + defaultName := "" + defaultEmail := "" + if config != nil { + defaultName = config.Name + defaultEmail = config.Email + } + // Questions + var simpleQs = []*survey.Question{ + { + Name: "Name", + Prompt: &survey.Input{ + Message: "What is your name:", + Default: defaultName, + }, + Validate: survey.Required, + }, + { + Name: "Email", + Prompt: &survey.Input{ + Message: "What is your email address:", + Default: defaultEmail, + }, + Validate: survey.Required, + }, + } + + // ask the questions + err = survey.Ask(simpleQs, answers) + if err != nil { + return err + } + + // Create the directory + err = s.fs.MkDirs(s.wailsSystemDir) + if err != nil { + return err + } + + fmt.Println() + s.log.White("Wails config saved to: " + s.wailsSystemConfig) + s.log.White("Feel free to customise these settings.") + fmt.Println() + + return answers.Save(s.wailsSystemConfig) +} + +// Initialise attempts to set up the Wails system. +// An error is returns if there is a problem +func (s *SystemHelper) Initialise() error { + + // System dir doesn't exist + if !s.systemDirExists() { + s.log.Green("Welcome to Wails!") + s.log.Green("To get you set up, I'll need to ask you a few things...") + return s.setup() + } + + // Config doesn't exist + if !s.ConfigFileExists() { + s.log.Green("Looks like the system config is missing.") + s.log.Green("To get you back on track, I'll need to ask you a few things...") + return s.setup() + } + + // Config exists but isn't valid. + if !s.ConfigFileIsValid() { + s.log.Green("Looks like the system config got corrupted.") + backupFile, err := s.BackupConfig() + if err != nil { + s.log.Green("I tried to backup your config file but got this error: %s", err.Error()) + } else { + s.log.Green("Just in case you needed it, I backed up your config file here: %s", backupFile) + } + s.log.Green("To get you back on track, I'll need to ask you a few things...") + return s.setup() + } + + return s.setup() +} + +type SystemConfig struct { + Name string `json:"name"` + Email string `json:"email"` +} + +func NewSystemConfig(filename string) (*SystemConfig, error) { + result := &SystemConfig{} + err := result.load(filename) + return result, err +} + +func (sc *SystemConfig) Save(filename string) error { + // Convert config to JSON string + theJSON, err := json.MarshalIndent(sc, "", " ") + if err != nil { + return err + } + + // Write it out to the config file + return ioutil.WriteFile(filename, theJSON, 0644) +} + +func (sc *SystemConfig) load(filename string) error { + configData, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + // Load and unmarshall! + err = json.Unmarshal(configData, &sc) + if err != nil { + return err + } + return nil +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 000000000..31f1d5364 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,5 @@ +package cmd + +// Version - Wails version +// ...oO(There must be a better way) +const Version = "v0.5 Alpha" diff --git a/cmd/wails/0_setup.go b/cmd/wails/0_setup.go new file mode 100644 index 000000000..141c19506 --- /dev/null +++ b/cmd/wails/0_setup.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "runtime" + + "github.com/wailsapp/wails/cmd" +) + +func init() { + + commandDescription := `Sets up your local environment to develop Wails apps.` + + initCommand := app.Command("setup", "Setup the Wails environment"). + LongDescription(commandDescription) + + initCommand.Action(func() error { + + system := cmd.NewSystemHelper() + err := system.Initialise() + if err != nil { + return err + } + + var successMessage string + + logger.Yellow("Checking for prerequisites...") + // Check we have a cgo capable environment + programHelper := cmd.NewProgramHelper() + prerequisites := make(map[string]map[string]string) + prerequisites["darwin"] = make(map[string]string) + prerequisites["darwin"]["clang"] = "Please install with `xcode-select --install` and try again" + prerequisites["darwin"]["npm"] = "Please download and install npm + node from here: https://nodejs.org/en/" + switch runtime.GOOS { + case "darwin": + successMessage = "🚀 Awesome! We are going to the moon! 🚀" + default: + return fmt.Errorf("platform '%s' is unsupported at this time", runtime.GOOS) + } + + errors := false + for name, help := range prerequisites[runtime.GOOS] { + bin := programHelper.FindProgram(name) + if bin == nil { + errors = true + logger.Red("Unable to find '%s' - %s", name, help) + } else { + logger.Green("Found program '%s' at '%s'", name, bin.Path) + } + } + + if errors { + err = fmt.Errorf("There were missing dependencies") + } else { + logger.Yellow(successMessage) + } + + return err + }) +} diff --git a/cmd/wails/main.go b/cmd/wails/main.go new file mode 100644 index 000000000..62258d618 --- /dev/null +++ b/cmd/wails/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/wailsapp/wails/cmd" +) + +// Create Logger +var logger = cmd.NewLogger() + +// Create main app +var app = cmd.NewCli("wails", "A cli tool for building Wails applications.") + +// Prints the cli banner +func printBanner(app *cmd.Cli) error { + logger.PrintBanner() + return nil +} + +// Main! +func main() { + app.PreRun(printBanner) + err := app.Run() + if err != nil { + logger.Error(err.Error()) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..088821717 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/wailsapp/wails + +require ( + github.com/AlecAivazis/survey v1.7.1 + github.com/fatih/color v1.7.0 + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/leaanthony/spinner v0.5.0 + github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0 // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/mitchellh/go-homedir v1.0.0 + gopkg.in/AlecAivazis/survey.v1 v1.7.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..f2b808ff3 --- /dev/null +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/AlecAivazis/survey v1.7.1 h1:a84v5MG2296rBkTP0e+dd4l7NxFQ69v4jzMpErkjVxc= +github.com/AlecAivazis/survey v1.7.1/go.mod h1:MVECab6WqEH1aXhj8nKIwF7HEAJAj2bhhGiSjNy3wII= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/leaanthony/spinner v0.5.0 h1:OJKn+0KP6ilHxwCEOv5Lo0wPM4PgWZWLJTeUprGJK0g= +github.com/leaanthony/spinner v0.5.0/go.mod h1:2Mmv+8Brcw3NwPT1DdOLmW6+zWpSamDDFFsUvVHo2cc= +github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0 h1:1bGojw4YacLY5bqQalojiQ7mSfQbe4WIWCEgPZagowU= +github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0/go.mod h1:Iz7eybeeG8bdq640iR+CwYb8p+9EOsgMWghkSRyZcqs= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/wailsapp/wails v0.0.0-20181215232634-5de8efff325d h1:lk91T4sKD98eGcaz/xC6ER+3o9Kaun7Mk8e/cNZOPMc= +gopkg.in/AlecAivazis/survey.v1 v1.7.1 h1:mzQIVyOPSXJaQWi1m6AFCjrCEPIwQBSOn48Ri8ZpzAg= +gopkg.in/AlecAivazis/survey.v1 v1.7.1/go.mod h1:2Ehl7OqkBl3Xb8VmC4oFW2bItAhnUfzIjrOzwRxCrOU=