From 5fcb2b52aba10d59c91c3ebd173f93ac6e1f26f4 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Sat, 26 Feb 2022 18:38:37 +0000 Subject: [PATCH] refactor: extract core logic to a plain Go package --- command_config.go | 35 ---- command_exec.go | 135 --------------- command_list.go | 81 --------- command_root.go | 80 --------- command_use.go | 118 ------------- commands/commands.go | 39 +++++ commands/config.go | 28 ++++ commands/evm.go | 48 ++++++ commands/exec.go | 131 +++++++++++++++ commands/list.go | 72 ++++++++ commands/rehash.go | 58 +++++++ render.go => commands/render.go | 4 +- commands/use.go | 53 ++++++ config.go | 152 ----------------- go.mod | 15 +- go.sum | 45 +---- main.go | 18 +- manager/config.go | 276 ++++++++++++++++++++++++++++++ manager/manager.go | 286 ++++++++++++++++++++++++++++++++ manager/version.go | 114 +++++++++++++ versions.go | 101 ----------- 21 files changed, 1126 insertions(+), 763 deletions(-) delete mode 100644 command_config.go delete mode 100644 command_exec.go delete mode 100644 command_list.go delete mode 100644 command_root.go delete mode 100644 command_use.go create mode 100644 commands/commands.go create mode 100644 commands/config.go create mode 100644 commands/evm.go create mode 100644 commands/exec.go create mode 100644 commands/list.go create mode 100644 commands/rehash.go rename render.go => commands/render.go (92%) create mode 100644 commands/use.go delete mode 100644 config.go create mode 100644 manager/config.go create mode 100644 manager/manager.go create mode 100644 manager/version.go delete mode 100644 versions.go diff --git a/command_config.go b/command_config.go deleted file mode 100644 index 24c90b6..0000000 --- a/command_config.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func configCommand() (*cobra.Command, error) { - cmd := &cobra.Command{ - Use: "config", - Short: "Show evm environment/setup details.", - Aliases: []string{"env", "info"}, - ValidArgs: []string{}, - RunE: configRunE, - } - - cmd.Flags().StringP("format", "f", "", "output format (yaml or json)") - err := viper.BindPFlag("info.format", cmd.Flags().Lookup("format")) - if err != nil { - return nil, err - } - - return cmd, nil -} - -func configRunE(cmd *cobra.Command, _ []string) error { - conf, err := getConfig() - if err != nil { - return err - } - - format := viper.GetString("info.format") - - return render(cmd.OutOrStdout(), format, conf) -} diff --git a/command_exec.go b/command_exec.go deleted file mode 100644 index 44b4ea1..0000000 --- a/command_exec.go +++ /dev/null @@ -1,135 +0,0 @@ -package main - -import ( - "bytes" - "errors" - "os" - "strings" - "syscall" - "text/template" - - "github.com/spf13/cobra" -) - -func execCommand() (*cobra.Command, error) { - cmd := &cobra.Command{ - Use: "exec [args]...", - Short: "Execute named binary from current " + - "Emacs version", - Args: cobra.MinimumNArgs(1), - SilenceUsage: true, - DisableFlagParsing: true, - DisableFlagsInUseLine: true, - Hidden: true, - ValidArgsFunction: execValidArgs, - RunE: execRunE, - } - - return cmd, nil -} - -func execRunE(cmd *cobra.Command, args []string) error { - conf, err := getConfig() - if err != nil { - return err - } - - version, err := newEmacsVersion(conf, conf.Current.Version) - if err != nil { - return err - } - - if bin, ok := version.BinPath(args[0]); ok { - select { - case <-cmd.Context().Done(): - return cmd.Context().Err() - default: - } - - execArgs := append([]string{bin}, args[1:]...) - execEnv := os.Environ() - for i := 0; i < len(execEnv); i++ { - if strings.HasPrefix(execEnv[i], "PATH=") { - execEnv[i] = "PATH=" + version.Bin + ":" + execEnv[i][5:] - } - } - - return syscall.Exec(bin, execArgs, execEnv) - } - - versions, err := newEmacsVersions(conf) - if err != nil { - return err - } - - var availableIn []string - for _, ev := range versions { - if _, ok := ev.BinPath(args[0]); ok { - availableIn = append(availableIn, ev.Version) - } - } - - var buf bytes.Buffer - err = execOtherVersionsTemplate.Execute(&buf, &execOtherVersionsData{ - Name: args[0], - Current: conf.Current.Version, - AvailableIn: availableIn, - }) - if err != nil { - return err - } - - return errors.New(buf.String()) -} - -func execValidArgs( - _ *cobra.Command, - args []string, - toComplete string, -) ([]string, cobra.ShellCompDirective) { - var r []string - - if len(args) > 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - conf, err := getConfig() - if err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - version, err := newEmacsVersion(conf, conf.Current.Version) - if err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - for _, bin := range version.Binaries { - if toComplete == "" || strings.HasPrefix(bin, toComplete) { - r = append(r, bin) - } - } - - return r, cobra.ShellCompDirectiveNoFileComp -} - -type execOtherVersionsData struct { - Name string - Current string - AvailableIn []string -} - -var execOtherVersionsTemplate = template.Must(template.New("other").Parse( - `{{ if gt (len .AvailableIn) 0 -}} -Current Emacs version ({{.Current}}) does not have a "{{.Name}}" executable. - -"{{.Name}}" is available in the following Emacs versions: -{{- range .AvailableIn }} - - {{ . }} -{{- end -}} -{{ else -}} -"{{.Name}}" executable is not available in any installed Emacs version. -{{- end }} - -Change version with: evm use -List all installed versions with: evm list`, -)) diff --git a/command_list.go b/command_list.go deleted file mode 100644 index a0cecc8..0000000 --- a/command_list.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "io" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func listCommand() (*cobra.Command, error) { - cmd := &cobra.Command{ - Use: "list", - Short: "List all Emacs versions found in " + - "`$EVM_ROOT/versions/*'", - Aliases: []string{"ls", "versions"}, - Args: cobra.ExactArgs(0), - ValidArgsFunction: listValidArgs, - RunE: listRunE, - } - - cmd.Flags().StringP("format", "f", "", "output format (yaml or json)") - err := viper.BindPFlag("list.format", cmd.Flags().Lookup("format")) - if err != nil { - return nil, err - } - - return cmd, nil -} - -func listRunE(cmd *cobra.Command, _ []string) error { - conf, err := getConfig() - if err != nil { - return err - } - - format := viper.GetString("list.format") - - versions, err := newEmacsVersions(conf) - if err != nil { - return err - } - - results := &listResults{ - Current: conf.Current.Version, - Versions: versions, - } - - return render(cmd.OutOrStdout(), format, results) -} - -func listValidArgs( - _ *cobra.Command, - args []string, - toComplete string, -) ([]string, cobra.ShellCompDirective) { - return nil, cobra.ShellCompDirectiveNoFileComp -} - -type listResults struct { - Current string `yaml:"current" json:"current"` - Versions emacsVersions `yaml:"versions" json:"versions"` -} - -func (lr *listResults) WriteTo(w io.Writer) (int64, error) { - var b []byte - - for _, ver := range lr.Versions { - if lr.Current == ver.Version { - b = append(b, []byte("* ")...) - } else { - b = append(b, []byte(" ")...) - } - - b = append(b, []byte(ver.Version)...) - b = append(b, byte('\n')) - } - - n, err := w.Write(b) - - return int64(n), err -} diff --git a/command_root.go b/command_root.go deleted file mode 100644 index 8b1f9d5..0000000 --- a/command_root.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func rootCommand() (*cobra.Command, error) { - cmd := &cobra.Command{ - Use: "evm", - Short: "A simple and opinionated Emacs Version Manager and build tool.", - } - - mode := os.Getenv("EVM_MODE") - if mode != "system" { - mode = "user" - } - - viper.SetDefault("mode", mode) - - rootDir := filepath.Join(string(os.PathSeparator), "opt", "evm") - if mode == "user" { - rootDir = filepath.Join("$HOME", ".evm") - } - - cmd.PersistentFlags().String("root", rootDir, "Root directory") - viper.SetDefault("path.root", rootDir) - err := viper.BindPFlag( - "path.root", cmd.PersistentFlags().Lookup("root"), - ) - if err != nil { - return nil, err - } - - err = viper.BindEnv("path.root", "EVM_ROOT") - if err != nil { - return nil, err - } - - viper.SetDefault("path.shims", filepath.Join("$EVM_ROOT", "shims")) - viper.SetDefault("path.sources", filepath.Join("$EVM_ROOT", "sources")) - viper.SetDefault("path.versions", filepath.Join("$EVM_ROOT", "versions")) - - infoCmd, err := configCommand() - if err != nil { - return nil, err - } - - listCmd, err := listCommand() - if err != nil { - return nil, err - } - - useCmd, err := useCommand() - if err != nil { - return nil, err - } - - execCmd, err := execCommand() - if err != nil { - return nil, err - } - - cmd.AddCommand( - infoCmd, - listCmd, - useCmd, - execCmd, - ) - - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.SetEnvPrefix("evm") - viper.AutomaticEnv() - - return cmd, nil -} diff --git a/command_use.go b/command_use.go deleted file mode 100644 index ebabbac..0000000 --- a/command_use.go +++ /dev/null @@ -1,118 +0,0 @@ -package main - -import ( - "bytes" - "io/fs" - "os" - "path/filepath" - "strings" - "text/template" - - "github.com/spf13/cobra" -) - -func useCommand() (*cobra.Command, error) { - cmd := &cobra.Command{ - Use: "use ", - Short: "Activate specified version.", - Aliases: []string{"activate", "switch"}, - Args: cobra.ExactArgs(1), - ValidArgsFunction: useValidArgs, - RunE: useRunE, - } - - return cmd, nil -} - -func useRunE(_ *cobra.Command, args []string) error { - conf, err := getConfig() - if err != nil { - return err - } - - version, err := newEmacsVersion(conf, args[0]) - if err != nil { - return err - } - - var shim bytes.Buffer - err = useShimTemplate.Execute(&shim, conf) - if err != nil { - return err - } - - err = os.MkdirAll(conf.Path.Shims, 0o755) - if err != nil { - return err - } - - for _, bin := range version.Binaries { - shimFile := filepath.Join(conf.Path.Shims, bin) - err = os.WriteFile(shimFile, shim.Bytes(), 0o755) - if err != nil { - return err - } - - var f fs.FileInfo - f, err = os.Stat(shimFile) - if err != nil { - return err - } - - if f.Mode().Perm() != 0o755 { - err = os.Chmod(shimFile, 0o755) - if err != nil { - return err - } - } - } - - currentFile := filepath.Join(conf.Path.Root, currentFileName) - err = os.WriteFile(currentFile, []byte(version.Version), 0o644) - if err != nil { - return err - } - - return nil -} - -func useValidArgs( - _ *cobra.Command, - args []string, - toComplete string, -) ([]string, cobra.ShellCompDirective) { - var r []string - - if len(args) > 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - conf, err := getConfig() - if err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - versions, err := newEmacsVersions(conf) - if err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - for _, ver := range versions { - if toComplete == "" || - strings.HasPrefix(ver.Version, toComplete) { - r = append(r, ver.Version) - } - } - - return r, cobra.ShellCompDirectiveNoFileComp -} - -var useShimTemplate = template.Must(template.New("other").Parse( - `#!/usr/bin/env bash -set -e -[ -n "$EVM_DEBUG" ] && set -x - -program="${0##*/}" -export EVM_ROOT="{{.Path.Root}}" -exec "{{.Path.Binary}}" exec "$program" "$@" -`)) diff --git a/commands/commands.go b/commands/commands.go new file mode 100644 index 0000000..abca8d0 --- /dev/null +++ b/commands/commands.go @@ -0,0 +1,39 @@ +package commands + +import "github.com/spf13/cobra" + +type runEFunc func(cmd *cobra.Command, _ []string) error + +type validArgsFunc func( + cmd *cobra.Command, + args []string, + toComplete string, +) ([]string, cobra.ShellCompDirective) + +func noValidArgs( + _ *cobra.Command, + _ []string, + _ string, +) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveNoFileComp +} + +func flagString(cmd *cobra.Command, name string) string { + var r string + + if f := cmd.Flag(name); f != nil { + r = f.Value.String() + } + + return r +} + +func stringsContains(haystack []string, needle string) bool { + for _, s := range haystack { + if s == needle { + return true + } + } + + return false +} diff --git a/commands/config.go b/commands/config.go new file mode 100644 index 0000000..28ce64d --- /dev/null +++ b/commands/config.go @@ -0,0 +1,28 @@ +package commands + +import ( + "github.com/jimeh/evm/manager" + "github.com/spf13/cobra" +) + +func NewConfig(mgr *manager.Manager) (*cobra.Command, error) { + cmd := &cobra.Command{ + Use: "config", + Short: "Show evm environment/setup details", + Aliases: []string{"env", "info"}, + ValidArgs: []string{}, + RunE: configRunE(mgr), + } + + cmd.Flags().StringP("format", "f", "", "output format (yaml or json)") + + return cmd, nil +} + +func configRunE(mgr *manager.Manager) runEFunc { + return func(cmd *cobra.Command, _ []string) error { + format := flagString(cmd, "format") + + return render(cmd.OutOrStdout(), format, mgr.Config) + } +} diff --git a/commands/evm.go b/commands/evm.go new file mode 100644 index 0000000..035c1b4 --- /dev/null +++ b/commands/evm.go @@ -0,0 +1,48 @@ +package commands + +import ( + "github.com/jimeh/evm/manager" + "github.com/spf13/cobra" +) + +func NewEvm(mgr *manager.Manager) (*cobra.Command, error) { + cmd := &cobra.Command{ + Use: "evm", + Short: "A simple and opinionated Emacs Version Manager and build tool", + } + + configCmd, err := NewConfig(mgr) + if err != nil { + return nil, err + } + + listCmd, err := NewList(mgr) + if err != nil { + return nil, err + } + + useCmd, err := NewUse(mgr) + if err != nil { + return nil, err + } + + rehashCmd, err := NewRehash(mgr) + if err != nil { + return nil, err + } + + execCmd, err := NewExec(mgr) + if err != nil { + return nil, err + } + + cmd.AddCommand( + configCmd, + listCmd, + useCmd, + rehashCmd, + execCmd, + ) + + return cmd, nil +} diff --git a/commands/exec.go b/commands/exec.go new file mode 100644 index 0000000..1c26dc6 --- /dev/null +++ b/commands/exec.go @@ -0,0 +1,131 @@ +package commands + +import ( + "bytes" + "errors" + "html/template" + "path/filepath" + "strings" + + "github.com/jimeh/evm/manager" + "github.com/spf13/cobra" +) + +func NewExec(mgr *manager.Manager) (*cobra.Command, error) { + cmd := &cobra.Command{ + Use: "exec [args]...", + Short: "Execute named binary from current " + + "Emacs version", + Args: cobra.MinimumNArgs(1), + SilenceUsage: true, + DisableFlagParsing: true, + DisableFlagsInUseLine: true, + Hidden: true, + ValidArgsFunction: execValidArgs(mgr), + RunE: execRunE(mgr), + } + + return cmd, nil +} + +func execRunE(mgr *manager.Manager) runEFunc { + return func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + program := args[0] + args = args[1:] + + err := mgr.Exec(ctx, program, args) + if err != nil { + if errors.Is(err, manager.ErrBinNotFound) { + var versions []*manager.Version + versions, err = mgr.FindBin(ctx, program) + if err != nil { + return err + } + + return newExecOtherVersionsError(&execOtherVersionsData{ + Name: program, + Current: mgr.CurrentVersion(), + AvailableIn: versions, + }) + } + + if errors.Is(err, manager.ErrNoCurrentVersion) { + return newExecNoCurrentVersionError() + } + + return err + } + + return nil + } +} + +func execValidArgs(mgr *manager.Manager) validArgsFunc { + return func( + cmd *cobra.Command, + args []string, + toComplete string, + ) ([]string, cobra.ShellCompDirective) { + var r []string + + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + version, err := mgr.Get(cmd.Context(), mgr.CurrentVersion()) + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + for _, bin := range version.Binaries { + base := filepath.Base(bin) + if toComplete == "" || strings.HasPrefix(base, toComplete) { + r = append(r, base) + } + } + + return r, cobra.ShellCompDirectiveNoFileComp + } +} + +func newExecNoCurrentVersionError() error { + return errors.New(`No current Emacs version is set. + +List all installed versions with: evm list +Change version with: evm use `, + ) +} + +type execOtherVersionsData struct { + Name string + Current string + AvailableIn []*manager.Version +} + +func newExecOtherVersionsError(data *execOtherVersionsData) error { + var buf bytes.Buffer + err := execOtherVersionsTemplate.Execute(&buf, data) + if err != nil { + return err + } + + return errors.New(buf.String()) +} + +var execOtherVersionsTemplate = template.Must(template.New("other").Parse( + `{{ if gt (len .AvailableIn) 0 -}} +Executable "{{.Name}}" is not available in the current Emacs version ({{.Current}}). + +"{{.Name}}" is available in the following Emacs versions: +{{- range .AvailableIn }} + - {{ .Version }} +{{- end -}} +{{ else -}} +Executable "{{.Name}}" is not available in any installed Emacs version. +{{- end }} + +Change version with: evm use +List all installed versions with: evm list`, +)) diff --git a/commands/list.go b/commands/list.go new file mode 100644 index 0000000..36f5aab --- /dev/null +++ b/commands/list.go @@ -0,0 +1,72 @@ +package commands + +import ( + "io" + + "github.com/jimeh/evm/manager" + "github.com/spf13/cobra" +) + +func NewList(mgr *manager.Manager) (*cobra.Command, error) { + cmd := &cobra.Command{ + Use: "list", + Short: "List all Emacs versions found in " + + "`$EVM_ROOT/versions/*'", + Aliases: []string{"ls", "versions"}, + Args: cobra.ExactArgs(0), + ValidArgsFunction: noValidArgs, + RunE: listRunE(mgr), + } + + cmd.Flags().StringP("format", "f", "", "output format (yaml or json)") + + return cmd, nil +} + +func listRunE(mgr *manager.Manager) runEFunc { + return func(cmd *cobra.Command, _ []string) error { + format := flagString(cmd, "format") + + versions, err := mgr.List(cmd.Context()) + if err != nil { + return err + } + + output := &listOutput{ + Current: mgr.CurrentVersion(), + SetBy: mgr.CurrentSetBy(), + Versions: versions, + } + + return render(cmd.OutOrStdout(), format, output) + } +} + +type listOutput struct { + Current string `yaml:"current" json:"current"` + SetBy string `yaml:"current_set_by,omitempty" json:"current_set_by,omitempty"` + Versions []*manager.Version `yaml:"versions" json:"versions"` +} + +func (lr *listOutput) WriteTo(w io.Writer) (int64, error) { + var b []byte + + for _, ver := range lr.Versions { + if lr.Current == ver.Version { + b = append(b, []byte("* ")...) + } else { + b = append(b, []byte(" ")...) + } + + b = append(b, []byte(ver.Version)...) + if lr.Current == ver.Version && lr.SetBy != "" { + b = append(b, []byte(" (set by "+lr.SetBy+")")...) + } + + b = append(b, byte('\n')) + } + + n, err := w.Write(b) + + return int64(n), err +} diff --git a/commands/rehash.go b/commands/rehash.go new file mode 100644 index 0000000..3312022 --- /dev/null +++ b/commands/rehash.go @@ -0,0 +1,58 @@ +package commands + +import ( + "strings" + + "github.com/jimeh/evm/manager" + "github.com/spf13/cobra" +) + +func NewRehash(mgr *manager.Manager) (*cobra.Command, error) { + cmd := &cobra.Command{ + Use: "rehash [...]", + Short: "Update shims for all or specific versions", + Aliases: []string{"reshim"}, + ValidArgsFunction: rehashValidArgs(mgr), + RunE: rehashRunE(mgr), + } + + return cmd, nil +} + +func rehashRunE(mgr *manager.Manager) runEFunc { + return func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + if len(args) > 0 { + return mgr.RehashVersions(ctx, args) + } + + return mgr.RehashAll(ctx) + } +} + +func rehashValidArgs(mgr *manager.Manager) validArgsFunc { + return func( + cmd *cobra.Command, + args []string, + toComplete string, + ) ([]string, cobra.ShellCompDirective) { + versions, err := mgr.List(cmd.Context()) + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + var r []string + for _, ver := range versions { + if stringsContains(args, ver.Version) { + continue + } + + if toComplete == "" || strings.HasPrefix(ver.Version, toComplete) { + r = append(r, ver.Version) + } + } + + return r, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/render.go b/commands/render.go similarity index 92% rename from render.go rename to commands/render.go index 13b657b..896a5be 100644 --- a/render.go +++ b/commands/render.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding" @@ -9,7 +9,7 @@ import ( ) func render(w io.Writer, format string, v interface{}) error { - if format == "yaml" { + if format == "yaml" || format == "yml" { return renderYAML(w, v) } diff --git a/commands/use.go b/commands/use.go new file mode 100644 index 0000000..defd96b --- /dev/null +++ b/commands/use.go @@ -0,0 +1,53 @@ +package commands + +import ( + "strings" + + "github.com/jimeh/evm/manager" + "github.com/spf13/cobra" +) + +func NewUse(mgr *manager.Manager) (*cobra.Command, error) { + cmd := &cobra.Command{ + Use: "use ", + Short: "Switch to a specific version", + Aliases: []string{"activate", "switch"}, + Args: cobra.ExactArgs(1), + ValidArgsFunction: useValidArgs(mgr), + RunE: useRunE(mgr), + } + + return cmd, nil +} + +func useRunE(mgr *manager.Manager) runEFunc { + return func(cmd *cobra.Command, args []string) error { + return mgr.Use(cmd.Context(), args[0]) + } +} + +func useValidArgs(mgr *manager.Manager) validArgsFunc { + return func( + cmd *cobra.Command, + args []string, + toComplete string, + ) ([]string, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + versions, err := mgr.List(cmd.Context()) + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + var r []string + for _, ver := range versions { + if toComplete == "" || strings.HasPrefix(ver.Version, toComplete) { + r = append(r, ver.Version) + } + } + + return r, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/config.go b/config.go deleted file mode 100644 index 82c5719..0000000 --- a/config.go +++ /dev/null @@ -1,152 +0,0 @@ -package main - -import ( - "errors" - "io/fs" - "os" - "path" - "path/filepath" - "strings" - - "github.com/spf13/viper" -) - -const currentFileName = "current" - -var ( - configUnmashaled bool - cachedConfig = &config{} -) - -type config struct { - Mode string `yaml:"mode" json:"mode"` - Current currentConfig `yaml:"current" json:"current"` - Path pathsConfig `yaml:"path" json:"path"` -} - -type currentConfig struct { - Version string `yaml:"version" json:"version"` - SetBy string `yaml:"set_by" json:"set_by"` -} - -type pathsConfig struct { - Binary string `yaml:"binary" json:"binary"` - Root string `yaml:"root" json:"root"` - Shims string `yaml:"shims" json:"shims"` - Sources string `yaml:"sources" json:"sources"` - Versions string `yaml:"versions" json:"versions"` -} - -func getConfig() (*config, error) { - if configUnmashaled { - cc, err := getCurrentVersion(cachedConfig) - if err != nil { - return nil, err - } - - cachedConfig.Current = *cc - - return cachedConfig, nil - } - - conf := &config{} - err := viper.Unmarshal(conf) - if err != nil { - return nil, err - } - - if conf.Mode != "user" && conf.Mode != "system" { - return nil, errors.New(`When set EVM_MODE must be "user" or "system"`) - } - - conf.Path.Binary, err = os.Executable() - if err != nil { - return nil, err - } - - var homePrefix string - switch { - case strings.HasPrefix(conf.Path.Root, "$HOME") || - strings.HasPrefix(conf.Path.Root, "$home"): - homePrefix = conf.Path.Root[0:5] - case strings.HasPrefix(conf.Path.Root, "~"): - homePrefix = conf.Path.Root[0:1] - } - - if homePrefix != "" { - var home string - home, err = os.UserHomeDir() - if err != nil { - return nil, err - } - conf.Path.Root = filepath.Join( - home, strings.TrimPrefix(conf.Path.Root, homePrefix)) - } - - if strings.HasPrefix(conf.Path.Shims, "$EVM_ROOT") { - conf.Path.Shims = filepath.Join( - conf.Path.Root, conf.Path.Shims[9:], - ) - } else if !path.IsAbs(conf.Path.Shims) { - conf.Path.Shims = filepath.Join( - conf.Path.Root, conf.Path.Shims, - ) - } - - if strings.HasPrefix(conf.Path.Sources, "$EVM_ROOT") { - conf.Path.Sources = filepath.Join( - conf.Path.Root, conf.Path.Sources[9:], - ) - } else if !path.IsAbs(conf.Path.Sources) { - conf.Path.Sources = filepath.Join( - conf.Path.Root, conf.Path.Sources, - ) - } - - if strings.HasPrefix(conf.Path.Versions, "$EVM_ROOT") { - conf.Path.Versions = filepath.Join( - conf.Path.Root, conf.Path.Versions[9:], - ) - } else if !path.IsAbs(conf.Path.Versions) { - conf.Path.Versions = filepath.Join( - conf.Path.Root, conf.Path.Versions, - ) - } - - cc, err := getCurrentVersion(conf) - if err != nil { - return nil, err - } - - conf.Current = *cc - cachedConfig = conf - - return cachedConfig, nil -} - -func getCurrentVersion(conf *config) (*currentConfig, error) { - cc := ¤tConfig{} - - if v := os.Getenv("EVM_VERSION"); v != "" { - cc.Version = strings.TrimSpace(v) - cc.SetBy = "EVM_VERSION environment variable" - - return cc, nil - } - - currentFile := filepath.Join(conf.Path.Root, currentFileName) - b, err := os.ReadFile(currentFile) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return cc, nil - } - return nil, err - } - - if len(b) > 0 { - cc.Version = strings.TrimSpace(string(b)) - cc.SetBy = currentFile - } - - return cc, nil -} diff --git a/go.mod b/go.mod index 98370c8..dae1091 100644 --- a/go.mod +++ b/go.mod @@ -3,25 +3,12 @@ module github.com/jimeh/evm go 1.17 require ( + github.com/sethvargo/go-envconfig v0.5.0 github.com/spf13/cobra v1.3.0 - github.com/spf13/viper v1.10.1 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) require ( - github.com/fsnotify/fsnotify v1.5.1 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/magiconair/properties v1.8.6 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect - github.com/pelletier/go-toml v1.9.4 // indirect - github.com/spf13/afero v1.8.1 // indirect - github.com/spf13/cast v1.4.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.2.0 // indirect - golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 // indirect - golang.org/x/text v0.3.7 // indirect - gopkg.in/ini.v1 v1.66.4 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index a5d2576..d093fbc 100644 --- a/go.sum +++ b/go.sum @@ -3,7 +3,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -16,7 +15,6 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -47,7 +45,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -92,7 +89,6 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -108,7 +104,6 @@ github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -167,6 +162,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -182,7 +178,6 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -194,10 +189,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= -github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -219,7 +212,6 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -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/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= @@ -253,8 +245,6 @@ 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/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -275,7 +265,6 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -285,14 +274,10 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= @@ -313,26 +298,21 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= -github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sethvargo/go-envconfig v0.5.0 h1:puRfYHj1HUe03MSPVBW8zc2BtRyn/KySgZcICXOzpVg= +github.com/sethvargo/go-envconfig v0.5.0/go.mod h1:00S1FAhRUuTNJazWBWcJGvEHOM+NO6DhoRMAOX7FY5o= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.8.1 h1:izYHOT71f9iZ7iq37Uqjael60/vYC6vMtzedudZ0zEk= -github.com/spf13/afero v1.8.1/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= -github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= -github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -340,9 +320,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -373,9 +351,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -446,7 +422,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= @@ -526,7 +501,6 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -534,7 +508,6 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -549,9 +522,6 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 h1:BXxu8t6QN0G1uff4bzZzSkpsax8+ALqTGUtz08QrV00= -golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -561,7 +531,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -614,7 +583,6 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -657,7 +625,6 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -700,9 +667,7 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -757,7 +722,6 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -779,15 +743,12 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= -gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= diff --git a/main.go b/main.go index 81a99d0..2746a59 100644 --- a/main.go +++ b/main.go @@ -6,13 +6,20 @@ import ( "os" "os/signal" "syscall" + + "github.com/jimeh/evm/commands" + "github.com/jimeh/evm/manager" ) func main() { - cmd, err := rootCommand() + mgr, err := manager.New(nil) if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) - os.Exit(1) + fatal(err) + } + + cmd, err := commands.NewEvm(mgr) + if err != nil { + fatal(err) } ctx, cancel := signal.NotifyContext( @@ -26,3 +33,8 @@ func main() { os.Exit(1) } } + +func fatal(err error) { + fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) + os.Exit(1) +} diff --git a/manager/config.go b/manager/config.go new file mode 100644 index 0000000..21d78d7 --- /dev/null +++ b/manager/config.go @@ -0,0 +1,276 @@ +package manager + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/sethvargo/go-envconfig" + "gopkg.in/yaml.v3" +) + +var ErrConfig = fmt.Errorf("%w", Err) + +type Mode string + +const ( + User Mode = "user" + System Mode = "system" +) + +type ConfigFile struct { + Paths ConfigFilePaths `yaml:"paths" json:"paths"` +} + +type ConfigFilePaths struct { + Shims string `yaml:"shims" json:"shims" env:"EVM_SHIMS,overwrite"` + Sources string `yaml:"sources" json:"sources" env:"EVM_SOURCES,overwrite"` + Versions string `yaml:"versions" json:"versions" env:"EVM_VERSIONS,overwrite"` +} + +type Config struct { + Mode Mode `yaml:"mode" json:"mode"` + Current CurrentConfig `yaml:"current" json:"current"` + Paths PathsConfig `yaml:"paths" json:"paths"` +} + +type CurrentConfig struct { + Version string `yaml:"version" json:"version"` + SetBy string `yaml:"set_by,omitempty" json:"set_by,omitempty"` +} + +type PathsConfig struct { + Binary string `yaml:"binary" json:"binary"` + Root string `yaml:"root" json:"root"` + Shims string `yaml:"shims" json:"shims"` + Sources string `yaml:"sources" json:"sources"` + Versions string `yaml:"versions" json:"versions"` +} + +func NewConfig() (*Config, error) { + mode := Mode(os.Getenv("EVM_MODE")) + if mode != System { + mode = User + } + + defaultRoot := filepath.Join(string(os.PathSeparator), "opt", "evm") + if mode == User { + defaultRoot = filepath.Join("$HOME", ".evm") + } + + if v := os.Getenv("EVM_ROOT"); v != "" { + defaultRoot = v + } + + conf := &Config{ + Mode: mode, + Paths: PathsConfig{ + Root: defaultRoot, + Shims: "$EVM_ROOT/shims", + Sources: "$EVM_ROOT/sources", + Versions: "$EVM_ROOT/versions", + }, + } + + var err error + conf.Paths.Root, err = conf.normalizePath(conf.Paths.Root) + if err != nil { + return nil, err + } + + err = conf.load() + if err != nil { + return nil, err + } + + conf.Paths.Shims, err = conf.normalizePath(conf.Paths.Shims) + if err != nil { + return nil, err + } + conf.Paths.Sources, err = conf.normalizePath(conf.Paths.Sources) + if err != nil { + return nil, err + } + conf.Paths.Versions, err = conf.normalizePath(conf.Paths.Versions) + if err != nil { + return nil, err + } + + conf.Paths.Binary, err = os.Executable() + if err != nil { + return nil, err + } + + err = conf.PopulateCurrent() + if err != nil { + return nil, err + } + + return conf, nil +} + +const currentFileName = "current" + +func (conf *Config) PopulateCurrent() error { + if v := os.Getenv("EVM_VERSION"); v != "" { + conf.Current.Version = strings.TrimSpace(v) + conf.Current.SetBy = "EVM_VERSION environment variable" + + return nil + } + + currentFile := filepath.Join(conf.Paths.Root, currentFileName) + b, err := os.ReadFile(currentFile) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil + } + return err + } + + if len(b) > 0 { + conf.Current.Version = strings.TrimSpace(string(b)) + conf.Current.SetBy = currentFile + } + + return nil +} + +var configFileNames = []string{ + "config.yaml", + "config.yml", + "config.json", + "evm.yaml", + "evm.yml", + "evm.json", +} + +func (c *Config) load() error { + var path string + for _, name := range configFileNames { + f := filepath.Join(c.Paths.Root, name) + + _, err := os.Stat(f) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + continue + } + return err + } + + path = f + break + } + + cf := &ConfigFile{} + if path != "" { + var err error + cf, err = c.loadConfigFile(path) + if err != nil { + return err + } + } + + err := envconfig.Process(context.Background(), cf) + if err != nil { + return err + } + + if cf.Paths.Shims != "" { + c.Paths.Shims = cf.Paths.Shims + } + if cf.Paths.Sources != "" { + c.Paths.Sources = cf.Paths.Sources + } + if cf.Paths.Versions != "" { + c.Paths.Versions = cf.Paths.Versions + } + + return nil +} + +func (c *Config) loadConfigFile(path string) (*ConfigFile, error) { + if path == "" { + return nil, nil + } + + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + cf := &ConfigFile{} + + buf := bytes.NewBuffer(content) + switch filepath.Ext(path) { + case ".yaml", ".yml": + dec := yaml.NewDecoder(buf) + dec.KnownFields(true) + err = dec.Decode(cf) + case ".json": + dec := json.NewDecoder(buf) + dec.DisallowUnknownFields() + err = dec.Decode(cf) + default: + return nil, fmt.Errorf( + `%w"%s" does not have a ".yaml", ".yml", `+ + `or ".json" file extension`, + ErrConfig, path, + ) + } + if err != nil && !errors.Is(err, io.EOF) { + return nil, err + } + + return cf, nil +} + +func (c *Config) normalizePath(path string) (string, error) { + path = strings.TrimSpace(path) + + var homePrefix string + switch { + case strings.HasPrefix(path, "$HOME") || + strings.HasPrefix(path, "$home"): + homePrefix = path[0:5] + case strings.HasPrefix(path, "~"): + homePrefix = path[0:1] + } + + if homePrefix != "" { + if c.Mode == System { + return "", fmt.Errorf( + `%wEVM_MODE is set to "%s" which prohibits `+ + `using "$HOME" or "~" in EVM_ROOT`, + ErrConfig, string(System), + ) + } + + var home string + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + path = filepath.Join( + home, strings.TrimPrefix(path, homePrefix)) + } + + if c.Paths.Root == "" { + return path, nil + } + + if strings.HasPrefix(path, "$EVM_ROOT") { + path = filepath.Join(c.Paths.Root, path[9:]) + } else if !filepath.IsAbs(path) { + path = filepath.Join(c.Paths.Root, path) + } + + return path, nil +} diff --git a/manager/manager.go b/manager/manager.go new file mode 100644 index 0000000..41c9ce4 --- /dev/null +++ b/manager/manager.go @@ -0,0 +1,286 @@ +package manager + +import ( + "bytes" + "context" + "errors" + "fmt" + "html/template" + "io/fs" + "os" + "path/filepath" + "strings" + "syscall" +) + +var ( + Err = errors.New("") + ErrVersion = fmt.Errorf("%w", Err) + ErrNoCurrentVersion = fmt.Errorf("%w", ErrVersion) + ErrVersionNotFound = fmt.Errorf("%w", ErrVersion) + ErrBinNotFound = fmt.Errorf("%w", ErrVersion) +) + +type Manager struct { + Config *Config +} + +func New(config *Config) (*Manager, error) { + if config == nil { + var err error + config, err = NewConfig() + if err != nil { + return nil, err + } + } + + return &Manager{Config: config}, nil +} + +func (m *Manager) CurrentVersion() string { + return m.Config.Current.Version +} + +func (m *Manager) CurrentSetBy() string { + return m.Config.Current.SetBy +} + +func (m *Manager) List(ctx context.Context) ([]*Version, error) { + return newVersions(ctx, m.Config) +} + +func (m *Manager) Get(ctx context.Context, version string) (*Version, error) { + return newVersion(ctx, m.Config, version) +} + +func (m *Manager) Use(ctx context.Context, version string) error { + ver, err := m.Get(ctx, version) + if err != nil { + return err + } + + currentFile := filepath.Join(m.Config.Paths.Root, currentFileName) + err = os.WriteFile(currentFile, []byte(ver.Version), 0o644) + if err != nil { + return err + } + + err = m.rehashVersions(ctx, false, []*Version{ver}) + if err != nil { + return err + } + + return nil +} + +func (m *Manager) RehashAll(ctx context.Context) error { + versions, err := m.List(ctx) + if err != nil { + return err + } + + return m.rehashVersions(ctx, true, versions) +} + +func (m *Manager) RehashVersions( + ctx context.Context, + versions []string, +) error { + var vers []*Version + for _, s := range versions { + v, err := m.Get(ctx, s) + if err != nil { + return err + } + + vers = append(vers, v) + } + + return m.rehashVersions(ctx, false, vers) +} + +func (m *Manager) rehashVersions( + ctx context.Context, + tidy bool, + versions []*Version, +) error { + + programs := map[string]bool{} + for _, ver := range versions { + for _, bin := range ver.Binaries { + base := filepath.Base(bin) + programs[base] = true + } + } + + shims, err := m.ListShims(ctx) + if err != nil { + return err + } + + shimMap := map[string]bool{} + for _, s := range shims { + base := filepath.Base(s) + shimMap[base] = true + } + + shim, err := m.shim() + if err != nil { + return err + } + + err = os.MkdirAll(m.Config.Paths.Shims, 0o755) + if err != nil { + return err + } + + for name := range programs { + shimFile := filepath.Join(m.Config.Paths.Shims, name) + err = os.WriteFile(shimFile, shim, 0o755) + if err != nil { + return err + } + + var f fs.FileInfo + f, err = os.Stat(shimFile) + if err != nil { + return err + } + + if f.Mode().Perm() != 0o755 { + err = os.Chmod(shimFile, 0o755) + if err != nil { + return err + } + } + + delete(shimMap, name) + } + + if tidy { + for name := range shimMap { + shimFile := filepath.Join(m.Config.Paths.Shims, name) + err := os.Remove(shimFile) + if err != nil { + return err + } + } + } + + return nil +} + +var shimTemplate = template.Must(template.New("other").Parse( + `#!/usr/bin/env bash +set -e +[ -n "$EVM_DEBUG" ] && set -x + +program="${0##*/}" +export EVM_ROOT="{{.Paths.Root}}" +exec "{{.Paths.Binary}}" exec "$program" "$@" +`)) + +func (m *Manager) shim() ([]byte, error) { + var buf bytes.Buffer + err := shimTemplate.Execute(&buf, m.Config) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (m *Manager) ListShims(ctx context.Context) ([]string, error) { + entries, err := os.ReadDir(m.Config.Paths.Shims) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return []string{}, nil + } + return nil, err + } + + r := []string{} + + for _, entry := range entries { + if ctx.Err() != nil { + return nil, ctx.Err() + } + + shimPath := filepath.Join(m.Config.Paths.Shims, entry.Name()) + f, err := os.Stat(shimPath) + if err != nil { + return nil, err + } + + if f.Mode().IsRegular() { + r = append(r, shimPath) + } + } + + return r, nil +} + +func (m *Manager) Exec( + ctx context.Context, + program string, + args []string, +) error { + current := m.CurrentVersion() + if current == "" { + return ErrNoCurrentVersion + } + + return m.ExecVersion(ctx, current, program, args) +} + +func (m *Manager) ExecVersion( + ctx context.Context, + version string, + program string, + args []string, +) error { + ver, err := m.Get(ctx, version) + if err != nil { + return err + } + + bin, err := ver.FindBin(program) + if err != nil { + return err + } + + if ctx.Err() != nil { + return ctx.Err() + } + + execArgs := append([]string{bin}, args...) + execEnv := os.Environ() + + // Prepend selected version's bin directory to PATH. + for i := 0; i < len(execEnv); i++ { + if strings.HasPrefix(execEnv[i], "PATH=") { + execEnv[i] = "PATH=" + ver.BinDir + ":" + execEnv[i][5:] + } + } + + return syscall.Exec(bin, execArgs, execEnv) +} + +func (m *Manager) FindBin( + ctx context.Context, + name string, +) ([]*Version, error) { + versions, err := m.List(ctx) + if err != nil { + return nil, err + } + + var availableIn []*Version + for _, ver := range versions { + if _, err := ver.FindBin(name); err == nil { + availableIn = append(availableIn, ver) + } + } + + return availableIn, nil +} diff --git a/manager/version.go b/manager/version.go new file mode 100644 index 0000000..6414f32 --- /dev/null +++ b/manager/version.go @@ -0,0 +1,114 @@ +package manager + +import ( + "context" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" +) + +type Version struct { + Version string `yaml:"version" json:"version"` + Current bool `yaml:"current" json:"current"` + Path string `yaml:"path" json:"path"` + BinDir string `yaml:"bin_dir" json:"bin_dir"` + Binaries []string `yaml:"binaries" json:"binaries"` +} + +func (ver *Version) FindBin(name string) (string, error) { + for _, b := range ver.Binaries { + if filepath.Base(b) == name { + return b, nil + } + } + + return "", fmt.Errorf( + `%wExecutable "%s" not found in Emacs version %s`, + ErrBinNotFound, name, ver.Version, + ) +} + +func newVersion( + ctx context.Context, + conf *Config, + version string, +) (*Version, error) { + if version == "" { + return nil, fmt.Errorf("%wversion cannot be empty", ErrVersion) + } + + path := filepath.Join(conf.Paths.Versions, version) + + _, err := os.Stat(path) + if err != nil { + return nil, fmt.Errorf( + "%wVersion %s is not available in %s", + ErrVersionNotFound, version, conf.Paths.Versions, + ) + } + + ver := &Version{ + Version: version, + Path: path, + BinDir: filepath.Join(path, "bin"), + Current: version == conf.Current.Version, + } + + entries, err := os.ReadDir(ver.BinDir) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return nil, err + } + + for _, entry := range entries { + if ctx.Err() != nil { + return nil, ctx.Err() + } + + binPath := filepath.Join(ver.BinDir, entry.Name()) + + f, err := os.Stat(binPath) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + continue + } + return nil, err + } + + // Ensure f is Regular file and executable. + if f.Mode().IsRegular() && f.Mode().Perm()&0111 == 0111 { + ver.Binaries = append(ver.Binaries, binPath) + } + } + + return ver, nil +} + +func newVersions(ctx context.Context, conf *Config) ([]*Version, error) { + results := []*Version{} + + entries, err := os.ReadDir(conf.Paths.Versions) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return nil, err + } + + for _, entry := range entries { + if ctx.Err() != nil { + return nil, ctx.Err() + } + + if !entry.IsDir() { + continue + } + + ver, err := newVersion(ctx, conf, entry.Name()) + if err != nil { + return nil, err + } + + results = append(results, ver) + } + + return results, nil +} diff --git a/versions.go b/versions.go deleted file mode 100644 index 8322f1c..0000000 --- a/versions.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "io/fs" - "os" - "path/filepath" -) - -type emacsVersions []*emacsVersion - -func newEmacsVersions(conf *config) (emacsVersions, error) { - results := emacsVersions{} - - entries, err := os.ReadDir(conf.Path.Versions) - if err != nil && !errors.Is(err, fs.ErrNotExist) { - return nil, err - } - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - vi, err := newEmacsVersion(conf, entry.Name()) - if err != nil { - return nil, err - } - - results = append(results, vi) - } - - return results, nil -} - -type emacsVersion struct { - Version string `yaml:"version" json:"version"` - Current bool `yaml:"current" json:"current"` - Path string `yaml:"path" json:"path"` - Bin string `yaml:"bin" json:"bin"` - Binaries []string `yaml:"binaries" json:"binaries"` -} - -func (ev *emacsVersion) BinPath(name string) (string, bool) { - for _, b := range ev.Binaries { - if filepath.Base(b) == name { - return filepath.Join(ev.Bin, b), true - } - } - - return "", false -} - -func newEmacsVersion(conf *config, version string) (*emacsVersion, error) { - path := filepath.Join(conf.Path.Versions, version) - - _, err := os.Stat(path) - if err != nil { - return nil, fmt.Errorf( - "Version %s is not available in %s", - version, conf.Path.Versions, - ) - } - - ev := &emacsVersion{ - Version: version, - Path: path, - Bin: filepath.Join(path, "bin"), - Current: version == conf.Current.Version, - } - - entries, err := os.ReadDir(ev.Bin) - if err != nil && !errors.Is(err, fs.ErrNotExist) { - return nil, err - } - - for _, entry := range entries { - binPath := filepath.Join(ev.Bin, entry.Name()) - - f, err := os.Stat(binPath) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - continue - } - return nil, err - } - - relPath, err := filepath.Rel(ev.Bin, binPath) - if err != nil { - return nil, err - } - - // Regular and executable file. - if f.Mode().IsRegular() && f.Mode().Perm()&0111 == 0111 { - ev.Binaries = append(ev.Binaries, relPath) - } - } - - return ev, nil -}