From f0bbe8f9b648950b1fd009e764d5511691524707 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Mon, 19 Feb 2024 15:10:53 -0500 Subject: [PATCH] feat(golang-rewrite): add config methods * Create PluginRepoCheckDuration struct to represent config value * Make some functions private * write basic tests for Config methods * Add Loaded field to Settings struct * Define constants for config default values --- config.go | 153 +++++++++++++++++++++++++++++++++++++++---------- config_test.go | 52 +++++++++++++++-- 2 files changed, 170 insertions(+), 35 deletions(-) diff --git a/config.go b/config.go index be46c7f5..633edd10 100644 --- a/config.go +++ b/config.go @@ -2,6 +2,7 @@ package main import ( "context" + "strconv" "strings" "github.com/mitchellh/go-homedir" @@ -9,29 +10,57 @@ import ( "gopkg.in/ini.v1" ) -// Not sure how best to represent this enum type -//type PluginRepositoryLastCheckDuration struct { -// Never bool -// Every int -//} +const LegacyVersionFileDefault = false +const AlwaysKeepDownloadDefault = false +const DisablePluginShortNameRepositoryDefault = false +const ForcePrependDefault = false +const DataDirDefault = "~/.asdf" +const ConfigFileDefault = "~/.asdfrc" +const DefaultToolVersionsFilenameDefault = ".tool-versions" + +/* Struct to represent the remote plugin repo check duration (never or every N +* seconds). It's not clear to me how this should be represented in Golang so +* using a struct for maximum flexibility. */ +type PluginRepoCheckDuration struct { + Never bool + Every int +} + +var PluginRepoCheckDurationDefault = PluginRepoCheckDuration{Every: 60} type Settings struct { + Loaded bool LegacyVersionFile bool // I don't think this setting should be supported in the Golang implementation //UseReleaseCandidates bool AlwaysKeepDownload bool - PluginRepositoryLastCheckDuration string + PluginRepositoryLastCheckDuration PluginRepoCheckDuration DisablePluginShortNameRepository bool } type Config struct { Home string - ConfigFile string `env:"ASDF_CONFIG_FILE"` - DefaultToolVersionsFilename string `env:"ASDF_DEFAULT_TOOL_VERSIONS_FILENAME"` + ConfigFile string `env:"ASDF_CONFIG_FILE, overwrite"` + DefaultToolVersionsFilename string `env:"ASDF_DEFAULT_TOOL_VERSIONS_FILENAME, overwrite"` // Unclear if this value will be needed with the golang implementation. //AsdfDir string - DataDir string `env:"ASDF_DATA_DIR"` - ForcePrepend bool `env:"ASDF_FORCE_PREPEND"` + DataDir string `env:"ASDF_DATA_DIR, overwrite"` + ForcePrepend bool `env:"ASDF_FORCE_PREPEND, overwrite"` + // Field that stores the settings struct if it is loaded + Settings Settings +} + +func NewPluginRepoCheckDuration(checkDuration string) PluginRepoCheckDuration { + if strings.ToLower(checkDuration) == "never" { + return PluginRepoCheckDuration{Never: true} + } else { + every, err := strconv.Atoi(checkDuration) + if err != nil { + // if error parsing config use default value + return PluginRepoCheckDurationDefault + } + return PluginRepoCheckDuration{Every: every} + } } func LoadConfig() (Config, error) { @@ -52,16 +81,90 @@ func LoadConfig() (Config, error) { return config, nil } +// Methods on the Config struct that allow it to load and cache values from the +// Settings struct, which is loaded from file on disk and therefor somewhat +// "expensive". +func (c *Config) LegacyVersionFile() (bool, error) { + err := c.loadSettings() + + if err != nil { + return false, err + } + + return c.Settings.LegacyVersionFile, nil +} + +func (c *Config) AlwaysKeepDownload() (bool, error) { + err := c.loadSettings() + + if err != nil { + return false, err + } + + return c.Settings.AlwaysKeepDownload, nil +} + +func (c *Config) PluginRepositoryLastCheckDuration() (PluginRepoCheckDuration, error) { + err := c.loadSettings() + + if err != nil { + return NewPluginRepoCheckDuration(""), err + } + + return c.Settings.PluginRepositoryLastCheckDuration, nil +} + +func (c *Config) DisablePluginShortNameRepository() (bool, error) { + err := c.loadSettings() + + if err != nil { + return false, err + } + + return c.Settings.DisablePluginShortNameRepository, nil +} + +func (c *Config) loadSettings() error { + if c.Settings.Loaded { + return nil + } + + settings, err := loadSettings(c.ConfigFile) + + if err != nil { + return err + } + + c.Settings = settings + + return nil +} + func loadConfigEnv() (Config, error) { - context := context.Background() - config := Config{} + dataDir, err := homedir.Expand(DataDirDefault) + if err != nil { + return Config{}, err + } - err := envconfig.Process(context, &config) + configFile, err := homedir.Expand(ConfigFileDefault) + if err != nil { + return Config{}, err + } + + config := Config{ + ForcePrepend: ForcePrependDefault, + DataDir: dataDir, + ConfigFile: configFile, + DefaultToolVersionsFilename: DefaultToolVersionsFilenameDefault, + } + + context := context.Background() + err = envconfig.Process(context, &config) return config, err } -func LoadSettings(asdfrcPath string) (Settings, error) { +func loadSettings(asdfrcPath string) (Settings, error) { // asdfrc is effectively formatted as ini config, err := ini.Load(asdfrcPath) @@ -70,17 +173,18 @@ func LoadSettings(asdfrcPath string) (Settings, error) { } mainConf := config.Section("") - checkDuration := stringToCheckDuration(mainConf.Key("plugin_repository_last_check_duration").String(), "60") + checkDuration := NewPluginRepoCheckDuration(mainConf.Key("plugin_repository_last_check_duration").String()) return Settings{ - LegacyVersionFile: YesNoToBool(mainConf, "legacy_version_file", false), - AlwaysKeepDownload: YesNoToBool(mainConf, "use_release_candidates", false), + Loaded: true, + LegacyVersionFile: yesNoToBool(mainConf, "legacy_version_file", LegacyVersionFileDefault), + AlwaysKeepDownload: yesNoToBool(mainConf, "use_release_candidates", AlwaysKeepDownloadDefault), PluginRepositoryLastCheckDuration: checkDuration, - DisablePluginShortNameRepository: YesNoToBool(mainConf, "disable_plugin_short_name_repository", false), + DisablePluginShortNameRepository: yesNoToBool(mainConf, "disable_plugin_short_name_repository", DisablePluginShortNameRepositoryDefault), }, nil } -func YesNoToBool(section *ini.Section, key string, defaultValue bool) bool { +func yesNoToBool(section *ini.Section, key string, defaultValue bool) bool { yesOrNo := section.Key(key).String() lcYesOrNo := strings.ToLower(yesOrNo) if lcYesOrNo == "yes" { @@ -92,14 +196,3 @@ func YesNoToBool(section *ini.Section, key string, defaultValue bool) bool { return defaultValue } - -// Eventually this should return a custom type but I need to figure out how to -// represent the (never string, duration int) type. For now it just returns a -// string. -func stringToCheckDuration(checkDuration string, defaultValue string) string { - if checkDuration != "" { - return checkDuration - } - - return defaultValue -} diff --git a/config_test.go b/config_test.go index 34d66f7c..595d27bb 100644 --- a/config_test.go +++ b/config_test.go @@ -22,37 +22,79 @@ func TestLoadConfigEnv(t *testing.T) { func TestLoadSettings(t *testing.T) { t.Run("When given invalid path returns error", func(t *testing.T) { - _, err := LoadSettings("./foobar") + settings, err := loadSettings("./foobar") if err == nil { t.Fatal("Didn't get an error") } + + if settings.Loaded { + t.Fatal("Didn't expect settings to be loaded") + } }) t.Run("When given path to populated asdfrc returns populated settings struct", func(t *testing.T) { - settings, err := LoadSettings("testdata/asdfrc") + settings, err := loadSettings("testdata/asdfrc") refuteError(t, err) + assert(t, settings.Loaded, "Expected Loaded field to be set to true") assert(t, settings.LegacyVersionFile == true, "LegacyVersionFile field has wrong value") assert(t, settings.AlwaysKeepDownload == true, "AlwaysKeepDownload field has wrong value") - assert(t, settings.PluginRepositoryLastCheckDuration == "never", "PluginRepositoryLastCheckDuration field has wrong value") + assert(t, settings.PluginRepositoryLastCheckDuration.Never, "PluginRepositoryLastCheckDuration field has wrong value") + assert(t, settings.PluginRepositoryLastCheckDuration.Every == 0, "PluginRepositoryLastCheckDuration field has wrong value") assert(t, settings.DisablePluginShortNameRepository == true, "DisablePluginShortNameRepository field has wrong value") }) t.Run("When given path to empty file returns settings struct with defaults", func(t *testing.T) { - settings, err := LoadSettings("testdata/empty-asdfrc") + settings, err := loadSettings("testdata/empty-asdfrc") refuteError(t, err) assert(t, settings.LegacyVersionFile == false, "LegacyVersionFile field has wrong value") assert(t, settings.AlwaysKeepDownload == false, "AlwaysKeepDownload field has wrong value") - assert(t, settings.PluginRepositoryLastCheckDuration == "60", "PluginRepositoryLastCheckDuration field has wrong value") + assert(t, settings.PluginRepositoryLastCheckDuration.Never == false, "PluginRepositoryLastCheckDuration field has wrong value") + assert(t, settings.PluginRepositoryLastCheckDuration.Every == 60, "PluginRepositoryLastCheckDuration field has wrong value") assert(t, settings.DisablePluginShortNameRepository == false, "DisablePluginShortNameRepository field has wrong value") }) } +func TestConfigMethods(t *testing.T) { + // Set the asdf config file location to the test file + t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc") + + config, err := LoadConfig() + assert(t, err == nil, "Returned error when building config") + + t.Run("Returns LegacyVersionFile from asdfrc file", func(t *testing.T) { + legacyFile, err := config.LegacyVersionFile() + assert(t, err == nil, "Returned error when loading settings") + assert(t, legacyFile == true, "Expected LegacyVersionFile to be set") + }) + + t.Run("Returns AlwaysKeepDownload from asdfrc file", func(t *testing.T) { + alwaysKeepDownload, err := config.AlwaysKeepDownload() + assert(t, err == nil, "Returned error when loading settings") + assert(t, alwaysKeepDownload == true, "Expected AlwaysKeepDownload to be set") + }) + + t.Run("Returns PluginRepositoryLastCheckDuration from asdfrc file", func(t *testing.T) { + checkDuration, err := config.PluginRepositoryLastCheckDuration() + assert(t, err == nil, "Returned error when loading settings") + assert(t, checkDuration.Never == true, "Expected PluginRepositoryLastCheckDuration to be set") + assert(t, checkDuration.Every == 0, "Expected PluginRepositoryLastCheckDuration to be set") + }) + + t.Run("Returns DisablePluginShortNameRepository from asdfrc file", func(t *testing.T) { + DisablePluginShortNameRepository, err := config.DisablePluginShortNameRepository() + assert(t, err == nil, "Returned error when loading settings") + assert(t, DisablePluginShortNameRepository == true, "Expected DisablePluginShortNameRepository to be set") + }) +} + func assert(t *testing.T, expr bool, message string) { + t.Helper() + if !expr { t.Error(message) }