From b47b4477483de2ce1bbd43a83a7b28d1551ee030 Mon Sep 17 00:00:00 2001 From: Jacob Biehler Date: Mon, 10 Aug 2020 16:25:49 -0700 Subject: [PATCH 1/4] Add boolean to UserPrefferedHomeDir to help correctly build configuration file placement and account for users with XDG_CONFIG_HOME defined --- cointop/common/pathutil/pathutil.go | 14 +++++++++++--- cointop/common/pathutil/pathutil_test.go | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 cointop/common/pathutil/pathutil_test.go diff --git a/cointop/common/pathutil/pathutil.go b/cointop/common/pathutil/pathutil.go index def97a9..58bb4e3 100644 --- a/cointop/common/pathutil/pathutil.go +++ b/cointop/common/pathutil/pathutil.go @@ -8,27 +8,35 @@ import ( ) // UserPreferredHomeDir returns the preferred home directory for the user -func UserPreferredHomeDir() string { +func UserPreferredHomeDir() (string, bool) { var home string + var isConfigDir bool if runtime.GOOS == "windows" { home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + isConfigDir = false } else if runtime.GOOS == "linux" { home = os.Getenv("XDG_CONFIG_HOME") + isConfigDir = true } if home == "" { home, _ = os.UserHomeDir() + isConfigDir = false } - return home + return home, isConfigDir } // NormalizePath normalizes and extends the path string func NormalizePath(path string) string { // expand tilde if strings.HasPrefix(path, "~/") { - path = filepath.Join(UserPreferredHomeDir(), path[2:]) + home, isConfigDir := UserPreferredHomeDir() + if !isConfigDir { + path = filepath.Join(home, path[2:]) + } + path = filepath.Join(home, path[10:]) } path = strings.Replace(path, "/", string(filepath.Separator), -1) diff --git a/cointop/common/pathutil/pathutil_test.go b/cointop/common/pathutil/pathutil_test.go new file mode 100644 index 0000000..8f13463 --- /dev/null +++ b/cointop/common/pathutil/pathutil_test.go @@ -0,0 +1,22 @@ +package pathutil + +import ( + "os" + "path/filepath" + "testing" +) + +// TestNormalizePath checks that NormalizePath returns the correct directory +func TestNormalizePath(t *testing.T) { + cases := []struct { + in, want string + }{ + {"~/.config/cointop/config.toml", filepath.Join(os.Getenv("XDG_CONFIG_HOME"), "/cointop/config.toml")}, + } + for _, c := range cases { + got := NormalizePath(c.in) + if got != c.want { + t.Errorf("NormalizePath(%q) == %q, want %q", c.in, got, c.want) + } + } +} From b7ede5a57bae3e832eba9c9c2b7726a5b4ab26b6 Mon Sep 17 00:00:00 2001 From: Jacob Biehler Date: Mon, 10 Aug 2020 16:52:49 -0700 Subject: [PATCH 2/4] Remove runtime package and OS checks in favor UserConfigDir and UserHomeDir which are platform agnostic and add more robust tests --- cointop/common/pathutil/pathutil.go | 11 ++--------- cointop/common/pathutil/pathutil_test.go | 13 +++++++++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cointop/common/pathutil/pathutil.go b/cointop/common/pathutil/pathutil.go index 58bb4e3..6761b28 100644 --- a/cointop/common/pathutil/pathutil.go +++ b/cointop/common/pathutil/pathutil.go @@ -3,22 +3,15 @@ package pathutil import ( "os" "path/filepath" - "runtime" "strings" ) // UserPreferredHomeDir returns the preferred home directory for the user func UserPreferredHomeDir() (string, bool) { - var home string var isConfigDir bool - if runtime.GOOS == "windows" { - home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") - isConfigDir = false - } else if runtime.GOOS == "linux" { - home = os.Getenv("XDG_CONFIG_HOME") - isConfigDir = true - } + home, _ := os.UserConfigDir() + isConfigDir = true if home == "" { home, _ = os.UserHomeDir() diff --git a/cointop/common/pathutil/pathutil_test.go b/cointop/common/pathutil/pathutil_test.go index 8f13463..954ac0b 100644 --- a/cointop/common/pathutil/pathutil_test.go +++ b/cointop/common/pathutil/pathutil_test.go @@ -8,13 +8,22 @@ import ( // TestNormalizePath checks that NormalizePath returns the correct directory func TestNormalizePath(t *testing.T) { + home, _ := os.UserHomeDir() + configDir, _ := os.UserConfigDir() cases := []struct { in, want string }{ - {"~/.config/cointop/config.toml", filepath.Join(os.Getenv("XDG_CONFIG_HOME"), "/cointop/config.toml")}, + {"~/.config/cointop/config.toml", filepath.Join(configDir, "/cointop/config.toml")}, + {"~/.config/cointop/config.toml", filepath.Join(home, ".config/cointop/config.toml")}, + {"~/.config/cointop/config.toml", filepath.Join(configDir, "/cointop/config.toml")}, + {"~/.config/cointop/config.toml", filepath.Join(home, ".config/cointop/config.toml")}, } - for _, c := range cases { + for i, c := range cases { got := NormalizePath(c.in) + if i > 1 { + home = "" + configDir = "" + } if got != c.want { t.Errorf("NormalizePath(%q) == %q, want %q", c.in, got, c.want) } From f56d0a61b1dd395244ef7faacea5234230257579 Mon Sep 17 00:00:00 2001 From: Jacob Biehler Date: Tue, 11 Aug 2020 09:48:49 -0700 Subject: [PATCH 3/4] Removing artifacts from tests to check what would happen if HOME and XDG_CONFIG_HOME are undefined --- cointop/common/pathutil/pathutil_test.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cointop/common/pathutil/pathutil_test.go b/cointop/common/pathutil/pathutil_test.go index 954ac0b..3c5146e 100644 --- a/cointop/common/pathutil/pathutil_test.go +++ b/cointop/common/pathutil/pathutil_test.go @@ -15,15 +15,9 @@ func TestNormalizePath(t *testing.T) { }{ {"~/.config/cointop/config.toml", filepath.Join(configDir, "/cointop/config.toml")}, {"~/.config/cointop/config.toml", filepath.Join(home, ".config/cointop/config.toml")}, - {"~/.config/cointop/config.toml", filepath.Join(configDir, "/cointop/config.toml")}, - {"~/.config/cointop/config.toml", filepath.Join(home, ".config/cointop/config.toml")}, } - for i, c := range cases { + for _, c := range cases { got := NormalizePath(c.in) - if i > 1 { - home = "" - configDir = "" - } if got != c.want { t.Errorf("NormalizePath(%q) == %q, want %q", c.in, got, c.want) } From 97a2f5280d639eb09c1829174bddbd20753ea5d6 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Thu, 13 Aug 2020 14:09:36 -0700 Subject: [PATCH 4/4] Use os.UserConfigDir() for preferred config directory --- cointop/cmd/root.go | 2 +- cointop/cointop.go | 6 +++- cointop/common/pathutil/pathutil.go | 40 +++++++++++++++--------- cointop/common/pathutil/pathutil_test.go | 12 ++++--- cointop/config.go | 13 ++++---- 5 files changed, 47 insertions(+), 26 deletions(-) diff --git a/cointop/cmd/root.go b/cointop/cmd/root.go index e654348..106db28 100644 --- a/cointop/cmd/root.go +++ b/cointop/cmd/root.go @@ -101,7 +101,7 @@ See git.io/cointop for more info.`, rootCmd.Flags().StringVarP(&config, "config", "c", "", fmt.Sprintf("Config filepath. (default %s)", cointop.DefaultConfigFilepath)) rootCmd.Flags().StringVarP(&cmcAPIKey, "coinmarketcap-api-key", "", "", "Set the CoinMarketCap API key") rootCmd.Flags().StringVarP(&apiChoice, "api", "", "", "API choice. Available choices are \"coinmarketcap\" and \"coingecko\"") - rootCmd.Flags().StringVarP(&colorscheme, "colorscheme", "", "", "Colorscheme to use (default \"cointop\"). To install standard themes, do:\n\ngit clone git@github.com:cointop-sh/colors.git ~/.config/cointop/colors\n\nSee git.io/cointop#colorschemes for more info.") + rootCmd.Flags().StringVarP(&colorscheme, "colorscheme", "", "", fmt.Sprintf("Colorscheme to use (default \"cointop\").\n%s", cointop.ColorschemeHelpString)) rootCmd.Flags().StringVarP(&cacheDir, "cache-dir", "", cacheDir, "Cache directory") return rootCmd diff --git a/cointop/cointop.go b/cointop/cointop.go index 8b4c15f..07019ea 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -149,7 +149,7 @@ var DefaultPerPage uint = 100 var DefaultColorscheme = "cointop" // DefaultConfigFilepath ... -var DefaultConfigFilepath = "~/.config/cointop/config.toml" +var DefaultConfigFilepath = pathutil.NormalizePath(":PREFERRED_CONFIG_HOME:/cointop/config.toml") // DefaultCacheDir ... var DefaultCacheDir = filecache.DefaultCacheDir @@ -499,3 +499,7 @@ func Reset(config *ResetConfig) error { return nil } + +func ColorschemeHelpString() string { + return fmt.Sprintf("To install standard themes, do:\n\ngit clone git@github.com:cointop-sh/colors.git %s\n\nSee git.io/cointop#colorschemes for more info.", pathutil.NormalizePath(":PREFERRED_CONFIG_HOME:/cointop/colors")) +} diff --git a/cointop/common/pathutil/pathutil.go b/cointop/common/pathutil/pathutil.go index 6761b28..32e9e50 100644 --- a/cointop/common/pathutil/pathutil.go +++ b/cointop/common/pathutil/pathutil.go @@ -6,32 +6,44 @@ import ( "strings" ) -// UserPreferredHomeDir returns the preferred home directory for the user -func UserPreferredHomeDir() (string, bool) { - var isConfigDir bool +// UserPreferredConfigDir returns the preferred config directory for the user +func UserPreferredConfigDir() string { + defaultConfigDir := "~/.config" + + config, err := os.UserConfigDir() + if err != nil { + return defaultConfigDir + } + + if config == "" { + return defaultConfigDir + } - home, _ := os.UserConfigDir() - isConfigDir = true + return config +} - if home == "" { - home, _ = os.UserHomeDir() - isConfigDir = false +// UserPreferredHomeDir returns the preferred home directory for the user +func UserPreferredHomeDir() string { + home, err := os.UserHomeDir() + if err != nil { + return "" } - return home, isConfigDir + return home } // NormalizePath normalizes and extends the path string func NormalizePath(path string) string { + userHome := UserPreferredHomeDir() + userConfigHome := UserPreferredConfigDir() + // expand tilde if strings.HasPrefix(path, "~/") { - home, isConfigDir := UserPreferredHomeDir() - if !isConfigDir { - path = filepath.Join(home, path[2:]) - } - path = filepath.Join(home, path[10:]) + path = filepath.Join(userHome, path[2:]) } + path = strings.Replace(path, ":HOME:", userHome, -1) + path = strings.Replace(path, ":PREFERRED_CONFIG_HOME:", userConfigHome, -1) path = strings.Replace(path, "/", string(filepath.Separator), -1) return path diff --git a/cointop/common/pathutil/pathutil_test.go b/cointop/common/pathutil/pathutil_test.go index 3c5146e..0a8d070 100644 --- a/cointop/common/pathutil/pathutil_test.go +++ b/cointop/common/pathutil/pathutil_test.go @@ -8,18 +8,22 @@ import ( // TestNormalizePath checks that NormalizePath returns the correct directory func TestNormalizePath(t *testing.T) { + os.Setenv("XDG_CONFIG_HOME", "/tmp/.config") + home, _ := os.UserHomeDir() configDir, _ := os.UserConfigDir() + cases := []struct { - in, want string + in, out string }{ - {"~/.config/cointop/config.toml", filepath.Join(configDir, "/cointop/config.toml")}, {"~/.config/cointop/config.toml", filepath.Join(home, ".config/cointop/config.toml")}, + {":HOME:/.cointop/config.toml", filepath.Join(home, "/.cointop/config.toml")}, + {":PREFERRED_CONFIG_HOME:/cointop/config.toml", filepath.Join(configDir, "/cointop/config.toml")}, } for _, c := range cases { got := NormalizePath(c.in) - if got != c.want { - t.Errorf("NormalizePath(%q) == %q, want %q", c.in, got, c.want) + if got != c.out { + t.Errorf("NormalizePath(%q) == %q, want %q", c.in, got, c.out) } } } diff --git a/cointop/config.go b/cointop/config.go index 5d27b46..f0446e5 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -17,10 +17,11 @@ var fileperm = os.FileMode(0644) // NOTE: this is to support previous default config filepaths var possibleConfigPaths = []string{ - "~/.config/cointop/config.toml", - "~/.config/cointop/config", - "~/.cointop/config", - "~/.cointop/config.toml", + ":PREFERRED_CONFIG_HOME:/cointop/config.toml", + ":HOME:/.config/cointop/config.toml", + ":HOME:/.config/cointop/config", + ":HOME:/.cointop/config", + ":HOME:/.cointop/config.toml", } type config struct { @@ -334,7 +335,7 @@ func (ct *Cointop) getColorschemeColors() (map[string]interface{}, error) { return nil, err } } else { - path := pathutil.NormalizePath(fmt.Sprintf("~/.cointop/colors/%s.toml", ct.colorschemeName)) + path := fmt.Sprintf("%s/colors/%s.toml", ct.ConfigDirPath(), ct.colorschemeName) if _, err := os.Stat(path); os.IsNotExist(err) { // NOTE: case for when cointop is set as the theme but the colorscheme file doesn't exist if ct.colorschemeName == "cointop" { @@ -345,7 +346,7 @@ func (ct *Cointop) getColorschemeColors() (map[string]interface{}, error) { return colors, nil } - return nil, fmt.Errorf("The colorscheme file %q was not found.\n\nTo install standard themes, do:\n\ngit clone git@github.com:cointop-sh/colors.git ~/.config/cointop/colors\n\nFor additional instructions, visit: https://github.com/cointop-sh/colors", path) + return nil, fmt.Errorf("The colorscheme file %q was not found.\n%s", path, ColorschemeHelpString()) } if _, err := toml.DecodeFile(path, &colors); err != nil {