gorepost/config/config.go

193 lines
4.7 KiB
Go

// Copyright 2015 Robert S. Gerus. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package config
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"sort"
"sync"
"time"
)
type cacheEntry struct {
modTime time.Time
contents interface{}
}
// A Config is a dynamic configuration key lookup mechanism.
type Config struct {
cache map[string]cacheEntry
buildFileList func(map[string]string) []string
l sync.Mutex
}
// New takes a file list builder and returns a new instance of Config.
//
// The instance is ready to use.
func New(f func(map[string]string) []string) *Config {
var c Config
c.cache = make(map[string]cacheEntry)
c.buildFileList = f
return &c
}
func (c *Config) lookupvar(key, path string) interface{} {
var f interface{}
i, err := os.Stat(path)
_, ok := c.cache[path]
if os.IsNotExist(err) {
log.Println("Config does not exist", path)
if ok {
log.Println("Purging", path, "from cache")
delete(c.cache, path)
}
}
if err != nil {
return nil
}
if c.cache[path].modTime.Before(i.ModTime()) || !ok {
log.Println("Stale cache for", path)
data, err := ioutil.ReadFile(path)
if err != nil {
log.Println("Purging", path, "from cache:", err)
delete(c.cache, path)
return nil
}
err = json.Unmarshal(data, &f)
if err != nil {
log.Println("Cannot parse", path)
log.Println("Purging", path, "from cache:", err)
delete(c.cache, path)
return nil
}
log.Println("Updating cache for", path)
c.cache[path] = cacheEntry{
modTime: i.ModTime(),
contents: f,
}
return f.(map[string]interface{})[key]
}
log.Println("Found cache for", path)
return c.cache[path].contents.(map[string]interface{})[key]
}
// Lookup searches for requested configuration key in file list built using
// context.
func (c *Config) Lookup(context map[string]string, key string) interface{} {
var value interface{}
c.l.Lock()
defer c.l.Unlock()
for _, fpath := range c.buildFileList(context) {
log.Println("Context:", context, "Looking up", key, "in", fpath)
value = c.lookupvar(key, fpath)
if value != nil {
log.Println("Context:", context, "Found", key, value)
return value
}
}
log.Println("Context:", context, "Key", key, "not found")
return nil
}
// LookupString is analogous to Lookup(), but does the cast to string.
func (c *Config) LookupString(context map[string]string, key string) string {
var value interface{}
c.l.Lock()
defer c.l.Unlock()
for _, fpath := range c.buildFileList(context) {
log.Println("Context:", context, "Looking up", key, "in", fpath)
value = c.lookupvar(key, fpath)
if value != nil {
log.Println("Context:", context, "Found", key, value)
return value.(string)
}
}
log.Println("Context:", context, "Key", key, "not found")
return ""
}
// LookupInt is analogous to Lookup(), but does the cast to int.
func (c *Config) LookupInt(context map[string]string, key string) int {
var value interface{}
c.l.Lock()
defer c.l.Unlock()
for _, fpath := range c.buildFileList(context) {
log.Println("Context:", context, "Looking up", key, "in", fpath)
value = c.lookupvar(key, fpath)
if value != nil {
log.Println("Context:", context, "Found", key, value)
return int(value.(float64))
}
}
log.Println("Context:", context, "Key", key, "not found")
return -1
}
// LookupStringSlice is analogous to Lookup(), but does the cast to []string
func (c *Config) LookupStringSlice(context map[string]string, key string) (retval []string) {
var value interface{}
c.l.Lock()
defer c.l.Unlock()
for _, fpath := range c.buildFileList(context) {
log.Println("Context:", context, "Looking up", key, "in", fpath)
value = c.lookupvar(key, fpath)
if value != nil {
log.Println("Context:", context, "Found", key, value)
for _, s := range value.([]interface{}) {
retval = append(retval, s.(string))
}
sort.Strings(retval)
return retval
}
}
log.Println("Context:", context, "Key", key, "not found")
return []string{""}
}
// LookupStringMap is analogous to Lookup(), but does the cast to
// map[string]bool for optimised lookups.
func (c *Config) LookupStringMap(context map[string]string, key string) (retval map[string]bool) {
var value interface{}
retval = make(map[string]bool)
c.l.Lock()
defer c.l.Unlock()
for _, fpath := range c.buildFileList(context) {
log.Println("Context:", context, "Looking up", key, "in", fpath)
value = c.lookupvar(key, fpath)
if value != nil {
log.Println("Context:", context, "Found", key, value)
for _, s := range value.([]interface{}) {
retval[s.(string)] = true
}
return retval
}
}
log.Println("Context:", context, "Key", key, "not found")
return retval
}