summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/wiggin77/cfg
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/wiggin77/cfg')
-rw-r--r--vendor/github.com/wiggin77/cfg/.gitignore12
-rw-r--r--vendor/github.com/wiggin77/cfg/.travis.yml5
-rw-r--r--vendor/github.com/wiggin77/cfg/LICENSE21
-rw-r--r--vendor/github.com/wiggin77/cfg/README.md43
-rw-r--r--vendor/github.com/wiggin77/cfg/config.go366
-rw-r--r--vendor/github.com/wiggin77/cfg/go.mod5
-rw-r--r--vendor/github.com/wiggin77/cfg/go.sum2
-rw-r--r--vendor/github.com/wiggin77/cfg/ini/ini.go167
-rw-r--r--vendor/github.com/wiggin77/cfg/ini/parser.go142
-rw-r--r--vendor/github.com/wiggin77/cfg/ini/section.go109
-rw-r--r--vendor/github.com/wiggin77/cfg/listener.go11
-rw-r--r--vendor/github.com/wiggin77/cfg/nocopy.go11
-rw-r--r--vendor/github.com/wiggin77/cfg/source.go58
-rw-r--r--vendor/github.com/wiggin77/cfg/srcfile.go63
-rw-r--r--vendor/github.com/wiggin77/cfg/srcmap.go78
-rw-r--r--vendor/github.com/wiggin77/cfg/timeconv/parse.go108
16 files changed, 1201 insertions, 0 deletions
diff --git a/vendor/github.com/wiggin77/cfg/.gitignore b/vendor/github.com/wiggin77/cfg/.gitignore
new file mode 100644
index 00000000..f1c181ec
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/.gitignore
@@ -0,0 +1,12 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
diff --git a/vendor/github.com/wiggin77/cfg/.travis.yml b/vendor/github.com/wiggin77/cfg/.travis.yml
new file mode 100644
index 00000000..9899b387
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/.travis.yml
@@ -0,0 +1,5 @@
+language: go
+sudo: false
+before_script:
+ - go vet ./...
+ \ No newline at end of file
diff --git a/vendor/github.com/wiggin77/cfg/LICENSE b/vendor/github.com/wiggin77/cfg/LICENSE
new file mode 100644
index 00000000..2b0bf7ef
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 wiggin77
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/wiggin77/cfg/README.md b/vendor/github.com/wiggin77/cfg/README.md
new file mode 100644
index 00000000..583a82cb
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/README.md
@@ -0,0 +1,43 @@
+# cfg
+
+[![GoDoc](https://godoc.org/github.com/wiggin77/cfg?status.svg)](https://godoc.org/github.com/wiggin77/cfg)
+[![Build Status](https://travis-ci.org/wiggin77/cfg.svg?branch=master)](https://travis-ci.org/wiggin77/cfg)
+
+Go package for app configuration. Supports chained configuration sources for multiple levels of defaults.
+Includes APIs for loading Linux style configuration files (name/value pairs) or INI files, map based properties,
+or easily create new configuration sources (e.g. load from database).
+
+Supports monitoring configuration sources for changes, hot loading properties, and notifying listeners of changes.
+
+## Usage
+
+```Go
+config := &cfg.Config{}
+defer config.Shutdown() // stops monitoring
+
+// load file via filespec string, os.File
+src, err := Config.NewSrcFileFromFilespec("./myfile.conf")
+if err != nil {
+ return err
+}
+// add src to top of chain, meaning first searched
+cfg.PrependSource(src)
+
+// fetch prop 'retries', default to 3 if not found
+val := config.Int("retries", 3)
+```
+
+See [example](./example_test.go) for more complete example, including listening for configuration changes.
+
+Config API parses the following data types:
+
+| type | method | example property values |
+| ------- | ------ | -------- |
+| string | Config.String | test, "" |
+| int | Config.Int | -1, 77, 0 |
+| int64 | Config.Int64 | -9223372036854775, 372036854775808 |
+| float64 | Config.Float64 | -77.3456, 95642331.1 |
+| bool | Config.Bool | T,t,true,True,1,0,False,false,f,F |
+| time.Duration | Config.Duration | "10ms", "2 hours", "5 min" * |
+
+\* Units of measure supported: ms, sec, min, hour, day, week, year.
diff --git a/vendor/github.com/wiggin77/cfg/config.go b/vendor/github.com/wiggin77/cfg/config.go
new file mode 100644
index 00000000..0e958102
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/config.go
@@ -0,0 +1,366 @@
+package cfg
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/wiggin77/cfg/timeconv"
+)
+
+// ErrNotFound returned when an operation is attempted on a
+// resource that doesn't exist, such as fetching a non-existing
+// property name.
+var ErrNotFound = errors.New("not found")
+
+type sourceEntry struct {
+ src Source
+ props map[string]string
+}
+
+// Config provides methods for retrieving property values from one or more
+// configuration sources.
+type Config struct {
+ mutexSrc sync.RWMutex
+ mutexListeners sync.RWMutex
+ srcs []*sourceEntry
+ chgListeners []ChangedListener
+ shutdown chan interface{}
+ wantPanicOnError bool
+}
+
+// PrependSource inserts one or more `Sources` at the beginning of
+// the list of sources such that the first source will be the
+// source checked first when resolving a property value.
+func (config *Config) PrependSource(srcs ...Source) {
+ arr := config.wrapSources(srcs...)
+
+ config.mutexSrc.Lock()
+ if config.shutdown == nil {
+ config.shutdown = make(chan interface{})
+ }
+ config.srcs = append(arr, config.srcs...)
+ config.mutexSrc.Unlock()
+
+ for _, se := range arr {
+ if _, ok := se.src.(SourceMonitored); ok {
+ config.monitor(se)
+ }
+ }
+}
+
+// AppendSource appends one or more `Sources` at the end of
+// the list of sources such that the last source will be the
+// source checked last when resolving a property value.
+func (config *Config) AppendSource(srcs ...Source) {
+ arr := config.wrapSources(srcs...)
+
+ config.mutexSrc.Lock()
+ if config.shutdown == nil {
+ config.shutdown = make(chan interface{})
+ }
+ config.srcs = append(config.srcs, arr...)
+ config.mutexSrc.Unlock()
+
+ for _, se := range arr {
+ if _, ok := se.src.(SourceMonitored); ok {
+ config.monitor(se)
+ }
+ }
+}
+
+// wrapSources wraps one or more Source's and returns
+// them as an array of `sourceEntry`.
+func (config *Config) wrapSources(srcs ...Source) []*sourceEntry {
+ arr := make([]*sourceEntry, 0, len(srcs))
+ for _, src := range srcs {
+ se := &sourceEntry{src: src}
+ config.reloadProps(se)
+ arr = append(arr, se)
+ }
+ return arr
+}
+
+// SetWantPanicOnError sets the flag determining if Config
+// should panic when `GetProps` or `GetLastModified` errors
+// for a `Source`.
+func (config *Config) SetWantPanicOnError(b bool) {
+ config.mutexSrc.Lock()
+ config.wantPanicOnError = b
+ config.mutexSrc.Unlock()
+}
+
+// ShouldPanicOnError gets the flag determining if Config
+// should panic when `GetProps` or `GetLastModified` errors
+// for a `Source`.
+func (config *Config) ShouldPanicOnError() (b bool) {
+ config.mutexSrc.RLock()
+ b = config.wantPanicOnError
+ config.mutexSrc.RUnlock()
+ return b
+}
+
+// getProp returns the value of a named property.
+// Each `Source` is checked, in the order created by adding via
+// `AppendSource` and `PrependSource`, until a value for the
+// property is found.
+func (config *Config) getProp(name string) (val string, ok bool) {
+ config.mutexSrc.RLock()
+ defer config.mutexSrc.RUnlock()
+
+ var s string
+ for _, se := range config.srcs {
+ if se.props != nil {
+ if s, ok = se.props[name]; ok {
+ val = strings.TrimSpace(s)
+ return
+ }
+ }
+ }
+ return
+}
+
+// String returns the value of the named prop as a string.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+func (config *Config) String(name string, def string) (val string, err error) {
+ if v, ok := config.getProp(name); ok {
+ val = v
+ err = nil
+ return
+ }
+
+ err = ErrNotFound
+ val = def
+ return
+}
+
+// Int returns the value of the named prop as an `int`.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// See config.String
+func (config *Config) Int(name string, def int) (val int, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ var i int64
+ if i, err = strconv.ParseInt(s, 10, 32); err == nil {
+ val = int(i)
+ }
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// Int64 returns the value of the named prop as an `int64`.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// See config.String
+func (config *Config) Int64(name string, def int64) (val int64, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ val, err = strconv.ParseInt(s, 10, 64)
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// Float64 returns the value of the named prop as a `float64`.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// See config.String
+func (config *Config) Float64(name string, def float64) (val float64, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ val, err = strconv.ParseFloat(s, 64)
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// Bool returns the value of the named prop as a `bool`.
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// Supports (t, true, 1, y, yes) for true, and (f, false, 0, n, no) for false,
+// all case-insensitive.
+//
+// See config.String
+func (config *Config) Bool(name string, def bool) (val bool, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ switch strings.ToLower(s) {
+ case "t", "true", "1", "y", "yes":
+ val = true
+ case "f", "false", "0", "n", "no":
+ val = false
+ default:
+ err = errors.New("invalid syntax")
+ }
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// Duration returns the value of the named prop as a `time.Duration`, representing
+// a span of time.
+//
+// Units of measure are supported: ms, sec, min, hour, day, week, year.
+// See config.UnitsToMillis for a complete list of units supported.
+//
+// If the property is not found then the supplied default `def`
+// and `ErrNotFound` are returned.
+//
+// See config.String
+func (config *Config) Duration(name string, def time.Duration) (val time.Duration, err error) {
+ var s string
+ if s, err = config.String(name, ""); err == nil {
+ var ms int64
+ ms, err = timeconv.ParseMilliseconds(s)
+ val = time.Duration(ms) * time.Millisecond
+ }
+ if err != nil {
+ val = def
+ }
+ return
+}
+
+// AddChangedListener adds a listener that will receive notifications
+// whenever one or more property values change within the config.
+func (config *Config) AddChangedListener(l ChangedListener) {
+ config.mutexListeners.Lock()
+ defer config.mutexListeners.Unlock()
+
+ config.chgListeners = append(config.chgListeners, l)
+}
+
+// RemoveChangedListener removes all instances of a ChangedListener.
+// Returns `ErrNotFound` if the listener was not present.
+func (config *Config) RemoveChangedListener(l ChangedListener) error {
+ config.mutexListeners.Lock()
+ defer config.mutexListeners.Unlock()
+
+ dest := make([]ChangedListener, 0, len(config.chgListeners))
+ err := ErrNotFound
+
+ // Remove all instances of the listener by
+ // copying list while filtering.
+ for _, s := range config.chgListeners {
+ if s != l {
+ dest = append(dest, s)
+ } else {
+ err = nil
+ }
+ }
+ config.chgListeners = dest
+ return err
+}
+
+// Shutdown can be called to stop monitoring of all config sources.
+func (config *Config) Shutdown() {
+ config.mutexSrc.RLock()
+ defer config.mutexSrc.RUnlock()
+ if config.shutdown != nil {
+ close(config.shutdown)
+ }
+}
+
+// onSourceChanged is called whenever one or more properties of a
+// config source has changed.
+func (config *Config) onSourceChanged(src SourceMonitored) {
+ defer func() {
+ if p := recover(); p != nil {
+ fmt.Println(p)
+ }
+ }()
+ config.mutexListeners.RLock()
+ defer config.mutexListeners.RUnlock()
+ for _, l := range config.chgListeners {
+ l.ConfigChanged(config, src)
+ }
+}
+
+// monitor periodically checks a config source for changes.
+func (config *Config) monitor(se *sourceEntry) {
+ go func(se *sourceEntry, shutdown <-chan interface{}) {
+ var src SourceMonitored
+ var ok bool
+ if src, ok = se.src.(SourceMonitored); !ok {
+ return
+ }
+ paused := false
+ last := time.Time{}
+ freq := src.GetMonitorFreq()
+ if freq <= 0 {
+ paused = true
+ freq = 10
+ last, _ = src.GetLastModified()
+ }
+ timer := time.NewTimer(freq)
+ for {
+ select {
+ case <-timer.C:
+ if !paused {
+ if latest, err := src.GetLastModified(); err != nil {
+ if config.ShouldPanicOnError() {
+ panic(fmt.Sprintf("error <%v> getting last modified for %v", err, src))
+ }
+ } else {
+ if last.Before(latest) {
+ last = latest
+ config.reloadProps(se)
+ // TODO: calc diff and provide detailed changes
+ config.onSourceChanged(src)
+ }
+ }
+ }
+ freq = src.GetMonitorFreq()
+ if freq <= 0 {
+ paused = true
+ freq = 10
+ } else {
+ paused = false
+ }
+ timer.Reset(freq)
+ case <-shutdown:
+ // stop the timer and exit
+ if !timer.Stop() {
+ <-timer.C
+ }
+ return
+ }
+ }
+ }(se, config.shutdown)
+}
+
+// reloadProps causes a Source to reload its properties.
+func (config *Config) reloadProps(se *sourceEntry) {
+ config.mutexSrc.Lock()
+ defer config.mutexSrc.Unlock()
+
+ m, err := se.src.GetProps()
+ if err != nil {
+ if config.wantPanicOnError {
+ panic(fmt.Sprintf("GetProps error for %v", se.src))
+ }
+ return
+ }
+
+ se.props = make(map[string]string)
+ for k, v := range m {
+ se.props[k] = v
+ }
+}
diff --git a/vendor/github.com/wiggin77/cfg/go.mod b/vendor/github.com/wiggin77/cfg/go.mod
new file mode 100644
index 00000000..2e5a038e
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/go.mod
@@ -0,0 +1,5 @@
+module github.com/wiggin77/cfg
+
+go 1.12
+
+require github.com/wiggin77/merror v1.0.2
diff --git a/vendor/github.com/wiggin77/cfg/go.sum b/vendor/github.com/wiggin77/cfg/go.sum
new file mode 100644
index 00000000..30fd3b58
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/go.sum
@@ -0,0 +1,2 @@
+github.com/wiggin77/merror v1.0.2 h1:V0nH9eFp64ASyaXC+pB5WpvBoCg7NUwvaCSKdzlcHqw=
+github.com/wiggin77/merror v1.0.2/go.mod h1:uQTcIU0Z6jRK4OwqganPYerzQxSFJ4GSHM3aurxxQpg=
diff --git a/vendor/github.com/wiggin77/cfg/ini/ini.go b/vendor/github.com/wiggin77/cfg/ini/ini.go
new file mode 100644
index 00000000..d28d7444
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/ini/ini.go
@@ -0,0 +1,167 @@
+package ini
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "sync"
+ "time"
+)
+
+// Ini provides parsing and querying of INI format or simple name/value pairs
+// such as a simple config file.
+// A name/value pair format is just an INI with no sections, and properties can
+// be queried using an empty section name.
+type Ini struct {
+ mutex sync.RWMutex
+ m map[string]*Section
+ lm time.Time
+}
+
+// LoadFromFilespec loads an INI file from string containing path and filename.
+func (ini *Ini) LoadFromFilespec(filespec string) error {
+ f, err := os.Open(filespec)
+ if err != nil {
+ return err
+ }
+ return ini.LoadFromFile(f)
+}
+
+// LoadFromFile loads an INI file from `os.File`.
+func (ini *Ini) LoadFromFile(file *os.File) error {
+
+ fi, err := file.Stat()
+ if err != nil {
+ return err
+ }
+ lm := fi.ModTime()
+
+ if err := ini.LoadFromReader(file); err != nil {
+ return err
+ }
+ ini.lm = lm
+ return nil
+}
+
+// LoadFromReader loads an INI file from an `io.Reader`.
+func (ini *Ini) LoadFromReader(reader io.Reader) error {
+ data, err := ioutil.ReadAll(reader)
+ if err != nil {
+ return err
+ }
+ return ini.LoadFromString(string(data))
+}
+
+// LoadFromString parses an INI from a string .
+func (ini *Ini) LoadFromString(s string) error {
+ m, err := getSections(s)
+ if err != nil {
+ return err
+ }
+ ini.mutex.Lock()
+ ini.m = m
+ ini.lm = time.Now()
+ ini.mutex.Unlock()
+ return nil
+}
+
+// GetLastModified returns the last modified timestamp of the
+// INI contents.
+func (ini *Ini) GetLastModified() time.Time {
+ return ini.lm
+}
+
+// GetSectionNames returns the names of all sections in this INI.
+// Note, the returned section names are a snapshot in time, meaning
+// other goroutines may change the contents of this INI as soon as
+// the method returns.
+func (ini *Ini) GetSectionNames() []string {
+ ini.mutex.RLock()
+ defer ini.mutex.RUnlock()
+
+ arr := make([]string, 0, len(ini.m))
+ for key := range ini.m {
+ arr = append(arr, key)
+ }
+ return arr
+}
+
+// GetKeys returns the names of all keys in the specified section.
+// Note, the returned key names are a snapshot in time, meaning other
+// goroutines may change the contents of this INI as soon as the
+// method returns.
+func (ini *Ini) GetKeys(sectionName string) ([]string, error) {
+ sec, err := ini.getSection(sectionName)
+ if err != nil {
+ return nil, err
+ }
+ return sec.getKeys(), nil
+}
+
+// getSection returns the named section.
+func (ini *Ini) getSection(sectionName string) (*Section, error) {
+ ini.mutex.RLock()
+ defer ini.mutex.RUnlock()
+
+ sec, ok := ini.m[sectionName]
+ if !ok {
+ return nil, fmt.Errorf("section '%s' not found", sectionName)
+ }
+ return sec, nil
+}
+
+// GetFlattenedKeys returns all section names plus keys as one
+// flattened array.
+func (ini *Ini) GetFlattenedKeys() []string {
+ ini.mutex.RLock()
+ defer ini.mutex.RUnlock()
+
+ arr := make([]string, 0, len(ini.m)*2)
+ for _, section := range ini.m {
+ keys := section.getKeys()
+ for _, key := range keys {
+ name := section.GetName()
+ if name != "" {
+ key = name + "." + key
+ }
+ arr = append(arr, key)
+ }
+ }
+ return arr
+}
+
+// GetProp returns the value of the specified key in the named section.
+func (ini *Ini) GetProp(section string, key string) (val string, ok bool) {
+ sec, err := ini.getSection(section)
+ if err != nil {
+ return val, false
+ }
+ return sec.GetProp(key)
+}
+
+// ToMap returns a flattened map of the section name plus keys mapped
+// to values.
+func (ini *Ini) ToMap() map[string]string {
+ m := make(map[string]string)
+
+ ini.mutex.RLock()
+ defer ini.mutex.RUnlock()
+
+ for _, section := range ini.m {
+ for _, key := range section.getKeys() {
+ val, ok := section.GetProp(key)
+ if ok {
+ name := section.GetName()
+ var mapkey string
+ if name != "" {
+ mapkey = name + "." + key
+ } else {
+ mapkey = key
+ }
+ m[mapkey] = val
+ }
+ }
+ }
+ return m
+}
diff --git a/vendor/github.com/wiggin77/cfg/ini/parser.go b/vendor/github.com/wiggin77/cfg/ini/parser.go
new file mode 100644
index 00000000..28916409
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/ini/parser.go
@@ -0,0 +1,142 @@
+package ini
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/wiggin77/merror"
+)
+
+// LF is linefeed
+const LF byte = 0x0A
+
+// CR is carriage return
+const CR byte = 0x0D
+
+// getSections parses an INI formatted string, or string containing just name/value pairs,
+// returns map of `Section`'s.
+//
+// Any name/value pairs appearing before a section name are added to the section named
+// with an empty string (""). Also true for Linux-style config files where all props
+// are outside a named section.
+//
+// Any errors encountered are aggregated and returned, along with the partially parsed
+// sections.
+func getSections(str string) (map[string]*Section, error) {
+ merr := merror.New()
+ mapSections := make(map[string]*Section)
+ lines := buildLineArray(str)
+ section := newSection("")
+
+ for _, line := range lines {
+ name, ok := parseSection(line)
+ if ok {
+ // A section name encountered. Stop processing the current one.
+ // Don't add the current section to the map if the section name is blank
+ // and the prop map is empty.
+ nameCurr := section.GetName()
+ if nameCurr != "" || section.hasKeys() {
+ mapSections[nameCurr] = section
+ }
+ // Start processing a new section.
+ section = newSection(name)
+ } else {
+ // Parse the property and add to the current section, or ignore if comment.
+ if k, v, comment, err := parseProp(line); !comment && err == nil {
+ section.setProp(k, v)
+ } else if err != nil {
+ merr.Append(err) // aggregate errors
+ }
+ }
+
+ }
+ // If the current section is not empty, add it.
+ if section.hasKeys() {
+ mapSections[section.GetName()] = section
+ }
+ return mapSections, merr.ErrorOrNil()
+}
+
+// buildLineArray parses the given string buffer and creates a list of strings,
+// one for each line in the string buffer.
+//
+// A line is considered to be terminated by any one of a line feed ('\n'),
+// a carriage return ('\r'), or a carriage return followed immediately by a
+// linefeed.
+//
+// Lines prefixed with ';' or '#' are considered comments and skipped.
+func buildLineArray(str string) []string {
+ arr := make([]string, 0, 10)
+ str = str + "\n"
+
+ iLen := len(str)
+ iPos, iBegin := 0, 0
+ var ch byte
+
+ for iPos < iLen {
+ ch = str[iPos]
+ if ch == LF || ch == CR {
+ sub := str[iBegin:iPos]
+ sub = strings.TrimSpace(sub)
+ if sub != "" && !strings.HasPrefix(sub, ";") && !strings.HasPrefix(sub, "#") {
+ arr = append(arr, sub)
+ }
+ iPos++
+ if ch == CR && iPos < iLen && str[iPos] == LF {
+ iPos++
+ }
+ iBegin = iPos
+ } else {
+ iPos++
+ }
+ }
+ return arr
+}
+
+// parseSection parses the specified string for a section name enclosed in square brackets.
+// Returns the section name found, or `ok=false` if `str` is not a section header.
+func parseSection(str string) (name string, ok bool) {
+ str = strings.TrimSpace(str)
+ if !strings.HasPrefix(str, "[") {
+ return "", false
+ }
+ iCloser := strings.Index(str, "]")
+ if iCloser == -1 {
+ return "", false
+ }
+ return strings.TrimSpace(str[1:iCloser]), true
+}
+
+// parseProp parses the specified string and extracts a key/value pair.
+//
+// If the string is a comment (prefixed with ';' or '#') then `comment=true`
+// and key will be empty.
+func parseProp(str string) (key string, val string, comment bool, err error) {
+ iLen := len(str)
+ iEqPos := strings.Index(str, "=")
+ if iEqPos == -1 {
+ return "", "", false, fmt.Errorf("not a key/value pair:'%s'", str)
+ }
+
+ key = str[0:iEqPos]
+ key = strings.TrimSpace(key)
+ if iEqPos+1 < iLen {
+ val = str[iEqPos+1:]
+ val = strings.TrimSpace(val)
+ }
+
+ // Check that the key has at least 1 char.
+ if key == "" {
+ return "", "", false, fmt.Errorf("key is empty for '%s'", str)
+ }
+
+ // Check if this line is a comment that just happens
+ // to have an equals sign in it. Not an error, but not a
+ // useable line either.
+ if strings.HasPrefix(key, ";") || strings.HasPrefix(key, "#") {
+ key = ""
+ val = ""
+ comment = true
+ }
+ return key, val, comment, err
+}
diff --git a/vendor/github.com/wiggin77/cfg/ini/section.go b/vendor/github.com/wiggin77/cfg/ini/section.go
new file mode 100644
index 00000000..18c4c254
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/ini/section.go
@@ -0,0 +1,109 @@
+package ini
+
+import (
+ "fmt"
+ "strings"
+ "sync"
+)
+
+// Section represents a section in an INI file. The section has a name, which is
+// enclosed in square brackets in the file. The section also has an array of
+// key/value pairs.
+type Section struct {
+ name string
+ props map[string]string
+ mtx sync.RWMutex
+}
+
+func newSection(name string) *Section {
+ sec := &Section{}
+ sec.name = name
+ sec.props = make(map[string]string)
+ return sec
+}
+
+// addLines addes an array of strings containing name/value pairs
+// of the format `key=value`.
+//func addLines(lines []string) {
+// TODO
+//}
+
+// GetName returns the name of the section.
+func (sec *Section) GetName() (name string) {
+ sec.mtx.RLock()
+ name = sec.name
+ sec.mtx.RUnlock()
+ return
+}
+
+// GetProp returns the value associated with the given key, or
+// `ok=false` if key does not exist.
+func (sec *Section) GetProp(key string) (val string, ok bool) {
+ sec.mtx.RLock()
+ val, ok = sec.props[key]
+ sec.mtx.RUnlock()
+ return
+}
+
+// SetProp sets the value associated with the given key.
+func (sec *Section) setProp(key string, val string) {
+ sec.mtx.Lock()
+ sec.props[key] = val
+ sec.mtx.Unlock()
+}
+
+// hasKeys returns true if there are one or more properties in
+// this section.
+func (sec *Section) hasKeys() (b bool) {
+ sec.mtx.RLock()
+ b = len(sec.props) > 0
+ sec.mtx.RUnlock()
+ return
+}
+
+// getKeys returns an array containing all keys in this section.
+func (sec *Section) getKeys() []string {
+ sec.mtx.RLock()
+ defer sec.mtx.RUnlock()
+
+ arr := make([]string, len(sec.props))
+ idx := 0
+ for k := range sec.props {
+ arr[idx] = k
+ idx++
+ }
+ return arr
+}
+
+// combine the given section with this one.
+func (sec *Section) combine(sec2 *Section) {
+ sec.mtx.Lock()
+ sec2.mtx.RLock()
+ defer sec.mtx.Unlock()
+ defer sec2.mtx.RUnlock()
+
+ for k, v := range sec2.props {
+ sec.props[k] = v
+ }
+}
+
+// String returns a string representation of this section.
+func (sec *Section) String() string {
+ return fmt.Sprintf("[%s]\n%s", sec.GetName(), sec.StringPropsOnly())
+}
+
+// StringPropsOnly returns a string representation of this section
+// without the section header.
+func (sec *Section) StringPropsOnly() string {
+ sec.mtx.RLock()
+ defer sec.mtx.RUnlock()
+ sb := &strings.Builder{}
+
+ for k, v := range sec.props {
+ sb.WriteString(k)
+ sb.WriteString("=")
+ sb.WriteString(v)
+ sb.WriteString("\n")
+ }
+ return sb.String()
+}
diff --git a/vendor/github.com/wiggin77/cfg/listener.go b/vendor/github.com/wiggin77/cfg/listener.go
new file mode 100644
index 00000000..12ea4e45
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/listener.go
@@ -0,0 +1,11 @@
+package cfg
+
+// ChangedListener interface is for receiving notifications
+// when one or more properties within monitored config sources
+// (SourceMonitored) have changed values.
+type ChangedListener interface {
+
+ // Changed is called when one or more properties in a `SourceMonitored` has a
+ // changed value.
+ ConfigChanged(cfg *Config, src SourceMonitored)
+}
diff --git a/vendor/github.com/wiggin77/cfg/nocopy.go b/vendor/github.com/wiggin77/cfg/nocopy.go
new file mode 100644
index 00000000..f2450c0b
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/nocopy.go
@@ -0,0 +1,11 @@
+package cfg
+
+// noCopy may be embedded into structs which must not be copied
+// after the first use.
+//
+// See https://golang.org/issues/8005#issuecomment-190753527
+// for details.
+type noCopy struct{}
+
+// Lock is a no-op used by -copylocks checker from `go vet`.
+func (*noCopy) Lock() {}
diff --git a/vendor/github.com/wiggin77/cfg/source.go b/vendor/github.com/wiggin77/cfg/source.go
new file mode 100644
index 00000000..09083e97
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/source.go
@@ -0,0 +1,58 @@
+package cfg
+
+import (
+ "sync"
+ "time"
+)
+
+// Source is the interface required for any source of name/value pairs.
+type Source interface {
+
+ // GetProps fetches all the properties from a source and returns
+ // them as a map.
+ GetProps() (map[string]string, error)
+}
+
+// SourceMonitored is the interface required for any config source that is
+// monitored for changes.
+type SourceMonitored interface {
+ Source
+
+ // GetLastModified returns the time of the latest modification to any
+ // property value within the source. If a source does not support
+ // modifying properties at runtime then the zero value for `Time`
+ // should be returned to ensure reload events are not generated.
+ GetLastModified() (time.Time, error)
+
+ // GetMonitorFreq returns the frequency as a `time.Duration` between
+ // checks for changes to this config source.
+ //
+ // Returning zero (or less) will temporarily suspend calls to `GetLastModified`
+ // and `GetMonitorFreq` will be called every 10 seconds until resumed, after which
+ // `GetMontitorFreq` will be called at a frequency roughly equal to the `time.Duration`
+ // returned.
+ GetMonitorFreq() time.Duration
+}
+
+// AbstractSourceMonitor can be embedded in a custom `Source` to provide the
+// basic plumbing for monitor frequency.
+type AbstractSourceMonitor struct {
+ mutex sync.RWMutex
+ freq time.Duration
+}
+
+// GetMonitorFreq returns the frequency as a `time.Duration` between
+// checks for changes to this config source.
+func (asm *AbstractSourceMonitor) GetMonitorFreq() (freq time.Duration) {
+ asm.mutex.RLock()
+ freq = asm.freq
+ asm.mutex.RUnlock()
+ return
+}
+
+// SetMonitorFreq sets the frequency between checks for changes to this config source.
+func (asm *AbstractSourceMonitor) SetMonitorFreq(freq time.Duration) {
+ asm.mutex.Lock()
+ asm.freq = freq
+ asm.mutex.Unlock()
+}
diff --git a/vendor/github.com/wiggin77/cfg/srcfile.go b/vendor/github.com/wiggin77/cfg/srcfile.go
new file mode 100644
index 00000000..f42c69fa
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/srcfile.go
@@ -0,0 +1,63 @@
+package cfg
+
+import (
+ "os"
+ "time"
+
+ "github.com/wiggin77/cfg/ini"
+)
+
+// SrcFile is a configuration `Source` backed by a file containing
+// name/value pairs or INI format.
+type SrcFile struct {
+ AbstractSourceMonitor
+ ini ini.Ini
+ file *os.File
+}
+
+// NewSrcFileFromFilespec creates a new SrcFile with the specified filespec.
+func NewSrcFileFromFilespec(filespec string) (*SrcFile, error) {
+ file, err := os.Open(filespec)
+ if err != nil {
+ return nil, err
+ }
+ return NewSrcFile(file)
+}
+
+// NewSrcFile creates a new SrcFile with the specified os.File.
+func NewSrcFile(file *os.File) (*SrcFile, error) {
+ sf := &SrcFile{}
+ sf.freq = time.Minute
+ sf.file = file
+ if err := sf.ini.LoadFromFile(file); err != nil {
+ return nil, err
+ }
+ return sf, nil
+}
+
+// GetProps fetches all the properties from a source and returns
+// them as a map.
+func (sf *SrcFile) GetProps() (map[string]string, error) {
+ lm, err := sf.GetLastModified()
+ if err != nil {
+ return nil, err
+ }
+
+ // Check if we need to reload.
+ if sf.ini.GetLastModified() != lm {
+ if err := sf.ini.LoadFromFile(sf.file); err != nil {
+ return nil, err
+ }
+ }
+ return sf.ini.ToMap(), nil
+}
+
+// GetLastModified returns the time of the latest modification to any
+// property value within the source.
+func (sf *SrcFile) GetLastModified() (time.Time, error) {
+ fi, err := sf.file.Stat()
+ if err != nil {
+ return time.Now(), err
+ }
+ return fi.ModTime(), nil
+}
diff --git a/vendor/github.com/wiggin77/cfg/srcmap.go b/vendor/github.com/wiggin77/cfg/srcmap.go
new file mode 100644
index 00000000..321db27a
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/srcmap.go
@@ -0,0 +1,78 @@
+package cfg
+
+import (
+ "time"
+)
+
+// SrcMap is a configuration `Source` backed by a simple map.
+type SrcMap struct {
+ AbstractSourceMonitor
+ m map[string]string
+ lm time.Time
+}
+
+// NewSrcMap creates an empty `SrcMap`.
+func NewSrcMap() *SrcMap {
+ sm := &SrcMap{}
+ sm.m = make(map[string]string)
+ sm.lm = time.Now()
+ sm.freq = time.Minute
+ return sm
+}
+
+// NewSrcMapFromMap creates a `SrcMap` containing a copy of the
+// specified map.
+func NewSrcMapFromMap(mapIn map[string]string) *SrcMap {
+ sm := NewSrcMap()
+ sm.PutAll(mapIn)
+ return sm
+}
+
+// Put inserts or updates a value in the `SrcMap`.
+func (sm *SrcMap) Put(key string, val string) {
+ sm.mutex.Lock()
+ sm.m[key] = val
+ sm.lm = time.Now()
+ sm.mutex.Unlock()
+}
+
+// PutAll inserts a copy of `mapIn` into the `SrcMap`
+func (sm *SrcMap) PutAll(mapIn map[string]string) {
+ sm.mutex.Lock()
+ defer sm.mutex.Unlock()
+
+ for k, v := range mapIn {
+ sm.m[k] = v
+ }
+ sm.lm = time.Now()
+}
+
+// GetProps fetches all the properties from a source and returns
+// them as a map.
+func (sm *SrcMap) GetProps() (m map[string]string, err error) {
+ sm.mutex.RLock()
+ m = sm.m
+ sm.mutex.RUnlock()
+ return
+}
+
+// GetLastModified returns the time of the latest modification to any
+// property value within the source. If a source does not support
+// modifying properties at runtime then the zero value for `Time`
+// should be returned to ensure reload events are not generated.
+func (sm *SrcMap) GetLastModified() (last time.Time, err error) {
+ sm.mutex.RLock()
+ last = sm.lm
+ sm.mutex.RUnlock()
+ return
+}
+
+// GetMonitorFreq returns the frequency as a `time.Duration` between
+// checks for changes to this config source. Defaults to 1 minute
+// unless changed with `SetMonitorFreq`.
+func (sm *SrcMap) GetMonitorFreq() (freq time.Duration) {
+ sm.mutex.RLock()
+ freq = sm.freq
+ sm.mutex.RUnlock()
+ return
+}
diff --git a/vendor/github.com/wiggin77/cfg/timeconv/parse.go b/vendor/github.com/wiggin77/cfg/timeconv/parse.go
new file mode 100644
index 00000000..218ef43a
--- /dev/null
+++ b/vendor/github.com/wiggin77/cfg/timeconv/parse.go
@@ -0,0 +1,108 @@
+package timeconv
+
+import (
+ "fmt"
+ "math"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+// MillisPerSecond is the number of millseconds per second.
+const MillisPerSecond int64 = 1000
+
+// MillisPerMinute is the number of millseconds per minute.
+const MillisPerMinute int64 = MillisPerSecond * 60
+
+// MillisPerHour is the number of millseconds per hour.
+const MillisPerHour int64 = MillisPerMinute * 60
+
+// MillisPerDay is the number of millseconds per day.
+const MillisPerDay int64 = MillisPerHour * 24
+
+// MillisPerWeek is the number of millseconds per week.
+const MillisPerWeek int64 = MillisPerDay * 7
+
+// MillisPerYear is the approximate number of millseconds per year.
+const MillisPerYear int64 = MillisPerDay*365 + int64((float64(MillisPerDay) * 0.25))
+
+// ParseMilliseconds parses a string containing a number plus
+// a unit of measure for time and returns the number of milliseconds
+// it represents.
+//
+// Example:
+// * "1 second" returns 1000
+// * "1 minute" returns 60000
+// * "1 hour" returns 3600000
+//
+// See config.UnitsToMillis for a list of supported units of measure.
+func ParseMilliseconds(str string) (int64, error) {
+ s := strings.TrimSpace(str)
+ reg := regexp.MustCompile("([0-9\\.\\-+]*)(.*)")
+ matches := reg.FindStringSubmatch(s)
+ if matches == nil || len(matches) < 1 || matches[1] == "" {
+ return 0, fmt.Errorf("invalid syntax - '%s'", s)
+ }
+ digits := matches[1]
+ units := "ms"
+ if len(matches) > 1 && matches[2] != "" {
+ units = matches[2]
+ }
+
+ fDigits, err := strconv.ParseFloat(digits, 64)
+ if err != nil {
+ return 0, err
+ }
+
+ msPerUnit, err := UnitsToMillis(units)
+ if err != nil {
+ return 0, err
+ }
+
+ // Check for overflow.
+ fms := float64(msPerUnit) * fDigits
+ if fms > math.MaxInt64 || fms < math.MinInt64 {
+ return 0, fmt.Errorf("out of range - '%s' overflows", s)
+ }
+ ms := int64(fms)
+ return ms, nil
+}
+
+// UnitsToMillis returns the number of milliseconds represented by the specified unit of measure.
+//
+// Example:
+// * "second" returns 1000 <br/>
+// * "minute" returns 60000 <br/>
+// * "hour" returns 3600000 <br/>
+//
+// Supported units of measure:
+// * "milliseconds", "millis", "ms", "millisecond"
+// * "seconds", "sec", "s", "second"
+// * "minutes", "mins", "min", "m", "minute"
+// * "hours", "h", "hour"
+// * "days", "d", "day"
+// * "weeks", "w", "week"
+// * "years", "y", "year"
+func UnitsToMillis(units string) (ms int64, err error) {
+ u := strings.TrimSpace(units)
+ u = strings.ToLower(u)
+ switch u {
+ case "milliseconds", "millisecond", "millis", "ms":
+ ms = 1
+ case "seconds", "second", "sec", "s":
+ ms = MillisPerSecond
+ case "minutes", "minute", "mins", "min", "m":
+ ms = MillisPerMinute
+ case "hours", "hour", "h":
+ ms = MillisPerHour
+ case "days", "day", "d":
+ ms = MillisPerDay
+ case "weeks", "week", "w":
+ ms = MillisPerWeek
+ case "years", "year", "y":
+ ms = MillisPerYear
+ default:
+ err = fmt.Errorf("invalid syntax - '%s' not a supported unit of measure", u)
+ }
+ return
+}