package envconf import ( "errors" "fmt" "os" "strings" "time" "unicode" ) type cEntry struct { value string parsed cValue dtype DataType unset bool empty bool defval string // The default value hasdef bool // Default value is defined } type Config struct { parsed bool env map[string]cEntry } // NewConfig returns an envconf.Config that is used to read configuration from environment variables. // The environment variables are stored in envconf.Config, so changes to the environment after NewConfig has been called // will not be taken into account. func NewConfig() *Config { config := new(Config) config.parsed = false config.env = make(map[string]cEntry) for _, v := range os.Environ() { splitted := strings.SplitN(v, "=", 2) if len(splitted) == 2 { key := strings.TrimSpace(strings.ToUpper(splitted[0])) if unicode.IsLetter(getFirstRune(key)) { var entry cEntry entry.value = splitted[1] entry.dtype = TypeNone entry.unset = false entry.empty = false config.env[key] = entry } } } return config } // Define defines the type of an environment variable. // Variables without a defined type will be ignored by Parse. func (c *Config) Define(key string, dtype DataType) { upper := strings.ToUpper(key) entry, ok := c.env[upper] if ok { entry.dtype = dtype c.env[upper] = entry } else { var entry cEntry entry.dtype = dtype entry.unset = true entry.empty = true c.env[upper] = entry } } // DefineDefault defines the type and default value of an environment variable. // Variables without a defined type will be ignored by Parse. func (c *Config) DefineDefault(key string, val string, dtype DataType) { upper := strings.ToUpper(key) entry, ok := c.env[upper] if ok { if entry.unset { entry.value = val } entry.dtype = dtype entry.empty = false entry.defval = val entry.hasdef = true c.env[upper] = entry } else { var entry cEntry entry.dtype = dtype entry.unset = true entry.empty = false entry.value = val entry.defval = val entry.hasdef = true c.env[upper] = entry } } // Parse parses the environment variables previously defined by Define and DefineDefault. // Parse should only be called once for a given envconf.Config. func (c *Config) Parse() { if c.parsed { return } c.parsed = true failed := false for k, v := range c.env { if v.empty { if v.unset { failed = true } } else { v.parsed = v.dtype.parse(k, v.value) c.env[k] = v } } if failed { for k, v := range c.env { if (v.parsed.err == nil) && v.unset { if v.empty { v.parsed.err = errors.New(fmt.Sprintf(`Environment variable "%s" not found. It should have been of type %s.`, k, v.dtype)) c.env[k] = v } else { v.parsed.err = errors.New(fmt.Sprintf(`Environment variable "%s" not found. Default value "%s" of type %s used.`, k, v.value, v.dtype)) c.env[k] = v } } } } } func (c *Config) help() { max := make([]int, 2, 2) preq := false pdef := false for k, v := range c.env { if v.dtype != TypeNone { if len(k) > max[0] { max[0] = len(k) } if len(v.dtype.String()) > max[1] { max[1] = len(v.dtype.String()) } if v.hasdef { pdef = true } else { preq = true } } } if pdef { fmt.Println() for k, v := range c.env { if v.dtype != TypeNone { if v.hasdef { format := fmt.Sprintf("Variable %%-%ds| Type %%-%ds| Default %%s\n", max[0]+5, max[1]+3) fmt.Printf(format, strings.Sprintf(`"%s"`, k), v.dtype, strings.Sprintf(`"%s"`, v.defval)) } } } } if preq { fmt.Println() for k, v := range c.env { if v.dtype != TypeNone { if !v.hasdef { format := fmt.Sprintf("Variable %%-%ds| Type %%-%ds| Required\n", max[0]+5, max[1]+3) fmt.Printf(format, strings.Sprintf(`"%s"`, k), v.dtype) } } } } fmt.Println() } func (c *Config) StatusHelp() bool { flags := make(map[string]bool) flags["--help"] = true flags["-help"] = true flags["-h"] = true if len(os.Args) > 1 { key := strings.ToLower(strings.TrimSpace(os.Args[1])) _, ok := flags[key] if ok { c.help() return false } } return c.Status() } // Status prints out failures that occured while parsing the environment to os.Stderr. // Variables that have been defined without a default value and are // missing from the environment will be considered a failure. // If parsing of any of the variables has failed Status will return false. func (c *Config) Status() (ok bool) { ok = c.parsed if ok { for _, v := range c.env { err := v.parsed.err if err != nil { ok = false if !v.empty { fmt.Fprintln(os.Stderr, err) } } } if !ok { fmt.Fprintln(os.Stderr, "") for _, v := range c.env { err := v.parsed.err if (err != nil) && v.empty { fmt.Fprintln(os.Stderr, err) } } } } return } func (c *Config) getRaw(key string, dtype DataType) (val cValue) { val.dtype = TypeNone if c.parsed { upper := strings.ToUpper(key) entry, ok := c.env[upper] if ok && (entry.dtype == dtype) { return entry.parsed } } return } // GetInt returns the value of an environment variable. // If the variable is not defined as envconf.TypeInt the function will return 0. func (c *Config) GetInt(key string) int64 { val := c.getRaw(key, TypeInt) return val.intval } // GetMetric returns the value of an environment variable. // If the variable is not defined as envconf.TypeMetric the function will return 0. func (c *Config) GetMetric(key string) int64 { val := c.getRaw(key, TypeMetric) return val.intval } // GetDirectory returns the value of an environment variable. // If the variable is not defined as envconf.TypeDirectory the // function will return the empty string. func (c *Config) GetDirectory(key string) string { val := c.getRaw(key, TypeDirectory) return val.strval } // GetString returns the value of an environment variable. // If the variable is not defined as envconf.TypeString the // function will return the empty string. func (c *Config) GetString(key string) string { val := c.getRaw(key, TypeString) return val.strval } // GetDuration returns the value of an environment variable. // If the variable is not defined as envconf.TypeDuration the // function will return time.Duration(0). func (c *Config) GetDuration(key string) time.Duration { val := c.getRaw(key, TypeDuration) return val.durval } // GetBool returns the value of an environment variable. // If the variable is not defined as envconf.TypeBool the // function will return false. func (c *Config) GetBool(key string) bool { val := c.getRaw(key, TypeBool) return val.boolval } func getFirstRune(str string) rune { for _, v := range str { return v } return rune(0) }