// Package config provides a configuration system for applications. package config import "fmt" import "math" import "strconv" import "git.tebibyte.media/tomo/tomo/event" type configError string; func (err configError) Error () string { return string(err) } const ( // ErrClosed is returned when Get/Set/Reset is called after the config // has been closed. ErrClosed = configError("attempt to access a closed config") // ErrNonexistentEntry is returned when an entry was not found. ErrNonexistentEntry = configError("nonexistent entry") // ErrMalformedEntry is returned when a config entry could not be // parsed. ErrMalformedEntry = configError("malformed entry") // ErrMalformedKey is returned when a key has invalid runes. ErrMalformedKey = configError("malformed key") // ErrMalformedValue is returned when a value could not be parsed. ErrMalformedValue = configError("malformed value") // ErrMalformedString is returned when a string value could not be // parsed. ErrMalformedStringValue = configError("malformed string value") // ErrMalformedNumber is returned when a number value could not be // parsed. ErrMalformedNumberValue = configError("malformed number value") // ErrMalformedBool is returned when a boolean value could not be // parsed. ErrMalformedBoolValue = configError("malformed bool value") // ErrMalformedEscapeSequence us returned when an escape sequence could // not be parsed. ErrMalformedEscapeSequence = configError("malformed escape sequence") ) // Config provides access to an application's configuration, and can notify an // application of changes to it. type Config interface { // Get gets a value, first considering the user-level config file, and // then falling back to system level config files. If the value could // not be found anywhere, the specified fallback value is returned. If // the key is invalid, it returns nil, ErrMalformedKey. Get (key string, fallback Value) (Value, error) // GetString is like Get, but will only return strings. If the value is // not a string, it will return fallback. GetString (key string, fallback string) (string, error) // GetNumber is like Get, but will only return numbers. If the value is // not a number, it will return fallback. GetNumber (key string, fallback float64) (float64, error) // GetBool is like Get, but will only return booleans. If the value is // not a boolean, it will return fallback. GetBool (key string, fallback bool) (bool, error) // Set sets a value in the user-level config file. If the key is // invalid, it returns ErrMalformedKey. Set (key string, value Value) error // Reset removes the value from the user-level config file, resetting it // to what is described by the system-level config files. If the key is // invalid, it returns ErrMalformedKey. Reset (key string) error // OnChange specifies a function to be called whenever a value is // changed. The callback is always run within the backend's event loop // using tomo.Do. This could have been a channel but I didn't want to do // that to people. OnChange (func (key string)) event.Cookie } // ConfigCloser is a config with a Close behavior, which stops watching the // config file and causes any subsequent sets/gets to return errors. Anything // that receives a ConfigCloser must close it when done. type ConfigCloser interface { Config // Close closes the config, causing it to stop watching for changes. // Reads or writes to the config after this will return an error. Close () error } var negativeZero = math.Copysign(0, -1) // Value is a config value. Its String behavior produces a lossless and // syntactically valid representation of the value. type Value interface { value () fmt.Stringer } // ValueString is a string value. type ValueString string var _ Value = ValueString("") func (ValueString) value () { } func (value ValueString) String () string { return fmt.Sprintf("\"%s\"", escape(string(value))) } // ValueNumber is a number value. type ValueNumber float64 var _ Value = ValueNumber(0) func (ValueNumber) value () { } func (value ValueNumber) String () string { number := float64(value) // the requirements I wrote said lossless in all cases. here's lossless // in all cases! switch { case math.IsInf(number, 0): if math.Signbit(number) { return "-Inf" } else { return "Inf" } case math.IsNaN(number): return "NaN" case number == 0, number == negativeZero: if math.Signbit(number) { return "-0" } else { return "0" } case math.Round(number) == number: return strconv.FormatInt(int64(number), 10) default: return strconv.FormatFloat(number, 'f', -1, 64) } } // ValueBool is a boolean value. var _ Value = ValueBool(false) type ValueBool bool func (ValueBool) value () { } func (value ValueBool) String () string { if value { return "true" } else { return "false" } }