Use external dyncfg.
parent
5823922316
commit
acad1ec1cb
|
@ -10,7 +10,6 @@ notifications:
|
|||
before_install:
|
||||
- go get github.com/arachnist/gorepost/bot
|
||||
- go get github.com/arachnist/gorepost/irc
|
||||
- go get github.com/arachnist/gorepost/config
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get github.com/go-playground/overalls
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
package bot
|
||||
|
||||
import (
|
||||
"github.com/arachnist/gorepost/config"
|
||||
"github.com/arachnist/dyncfg"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var cfg *config.Config
|
||||
var cfg *dyncfg.Dyncfg
|
||||
var cfgLock sync.Mutex
|
||||
|
||||
func Initialize(config *config.Config) {
|
||||
func Initialize(config *dyncfg.Dyncfg) {
|
||||
cfg = config
|
||||
cfgLock.Unlock()
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"TestLookup":"this can be anything you can imagine, but now it's just a string",
|
||||
"TestLookupString":"this should be a string",
|
||||
"TestLookupStringSlice":["i", "am", "going", "to", "be", "a", "slice"],
|
||||
"TestLookupStringMap":["I", "want", "to", "be", "a", "map[string]bool"],
|
||||
"ThisWillBeAnInt":42
|
||||
}
|
192
config/config.go
192
config/config.go
|
@ -1,192 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
// +build go1.4
|
||||
|
||||
// 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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var emptyContext map[string]string
|
||||
|
||||
// func Lookup(context map[string]string, key string) interface{}
|
||||
var testsLookup = []struct {
|
||||
key string
|
||||
expectedValue string
|
||||
}{
|
||||
{
|
||||
key: "TestLookup",
|
||||
expectedValue: "this can be anything you can imagine, but now it's just a string",
|
||||
},
|
||||
{
|
||||
key: "NotExisting",
|
||||
expectedValue: "<nil>",
|
||||
},
|
||||
}
|
||||
|
||||
func TestLookup(t *testing.T) {
|
||||
for i, e := range testsLookup {
|
||||
v := c.Lookup(emptyContext, e.key)
|
||||
if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
|
||||
t.Log("test#", i, "Lookup key", e.key)
|
||||
t.Logf("expected: %+v\n", e.expectedValue)
|
||||
t.Logf("result : %+v\n", v)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func LookupString(context map[string]string, key string) string
|
||||
var testsLookupString = []struct {
|
||||
key string
|
||||
expectedValue string
|
||||
}{
|
||||
{
|
||||
key: "TestLookupString",
|
||||
expectedValue: "this should be a string",
|
||||
},
|
||||
{
|
||||
key: "NotExisting",
|
||||
expectedValue: "",
|
||||
},
|
||||
}
|
||||
|
||||
func TestLookupString(t *testing.T) {
|
||||
for i, e := range testsLookupString {
|
||||
v := c.LookupString(emptyContext, e.key)
|
||||
if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
|
||||
t.Log("test#", i+1, "Lookup key", e.key)
|
||||
t.Logf("expected: %+v\n", e.expectedValue)
|
||||
t.Logf("result : %+v\n", v)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func LookupInt(context map[string]string, key string) int
|
||||
var testsLookupInt = []struct {
|
||||
key string
|
||||
expectedValue int
|
||||
}{
|
||||
{
|
||||
key: "ThisWillBeAnInt",
|
||||
expectedValue: 42,
|
||||
},
|
||||
{
|
||||
key: "NotExisting",
|
||||
expectedValue: -1,
|
||||
},
|
||||
}
|
||||
|
||||
func TestLookupInt(t *testing.T) {
|
||||
for i, e := range testsLookupInt {
|
||||
v := c.LookupInt(emptyContext, e.key)
|
||||
if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
|
||||
t.Log("test#", i+1, "Lookup key", e.key)
|
||||
t.Logf("expected: %+v\n", e.expectedValue)
|
||||
t.Logf("result : %+v\n", v)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func LookupStringSlice(context map[string]string, key string) []string
|
||||
var testsLookupStringSlice = []struct {
|
||||
key string
|
||||
expectedValue []string
|
||||
}{
|
||||
{
|
||||
// TestLookupStringSlice return slice is ordered
|
||||
key: "TestLookupStringSlice",
|
||||
expectedValue: []string{"a", "am", "be", "going", "i", "slice", "to"},
|
||||
},
|
||||
{
|
||||
key: "NotExisting",
|
||||
expectedValue: []string{""},
|
||||
},
|
||||
}
|
||||
|
||||
func TestLookupStringSlice(t *testing.T) {
|
||||
for i, e := range testsLookupStringSlice {
|
||||
v := c.LookupStringSlice(emptyContext, e.key)
|
||||
if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
|
||||
t.Log("test#", i+1, "Lookup key", e.key)
|
||||
t.Logf("expected: %+v\n", e.expectedValue)
|
||||
t.Logf("result : %+v\n", v)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func LookupStringMap(context map[string]string, key string) map[string]bool
|
||||
var testsLookupStringMap = []struct {
|
||||
key string
|
||||
expectedValue map[string]bool
|
||||
}{
|
||||
{
|
||||
key: "TestLookupStringMap",
|
||||
expectedValue: map[string]bool{
|
||||
"to": true,
|
||||
"be": true,
|
||||
"a": true,
|
||||
"map[string]bool": true,
|
||||
"I": true,
|
||||
"want": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "NotExisting",
|
||||
expectedValue: map[string]bool{},
|
||||
},
|
||||
}
|
||||
|
||||
func TestLookupStringMap(t *testing.T) {
|
||||
for i, e := range testsLookupStringMap {
|
||||
v := c.LookupStringMap(emptyContext, e.key)
|
||||
if !reflect.DeepEqual(v, e.expectedValue) {
|
||||
t.Log("test#", i+1, "Lookup key", e.key)
|
||||
t.Logf("expected: %+v\n", e.expectedValue)
|
||||
t.Logf("result : %+v\n", v)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func configLookupHelper(map[string]string) []string {
|
||||
return []string{"this/thing/does/not/exist.json", ".testconfig.json"}
|
||||
}
|
||||
|
||||
func complicatedLookupHelper(map[string]string) []string {
|
||||
return []string{
|
||||
".to-be-removed.json",
|
||||
".to-be-replaced-with-gibberish.json",
|
||||
".to-be-chmoded-000.json",
|
||||
".to-be-replaced.json",
|
||||
}
|
||||
}
|
||||
|
||||
var testEvents = []struct {
|
||||
desc string
|
||||
action func()
|
||||
ev int
|
||||
}{
|
||||
{
|
||||
desc: "remove file",
|
||||
action: func() { _ = os.Remove(".to-be-removed.json") },
|
||||
ev: 1,
|
||||
},
|
||||
{
|
||||
desc: "replace with gibberish",
|
||||
action: func() {
|
||||
_ = ioutil.WriteFile(".to-be-replaced-with-gibberish.json", []byte("!@#%^&!%@$&*#@@%*@^#$^&*@&(!"), 0644)
|
||||
},
|
||||
ev: 2,
|
||||
},
|
||||
{
|
||||
desc: "chmod 000",
|
||||
action: func() {
|
||||
_ = os.Chmod(".to-be-chmoded-000.json", 0000)
|
||||
_ = os.Chtimes(".to-be-chmoded-000.json", time.Now().Local(), time.Now().Local())
|
||||
},
|
||||
ev: 3,
|
||||
},
|
||||
{
|
||||
desc: "replace",
|
||||
action: func() { _ = ioutil.WriteFile(".to-be-replaced.json", []byte("{\"testkey\":99}"), 0644) },
|
||||
ev: 99,
|
||||
},
|
||||
}
|
||||
|
||||
func TestLookupvarConditions(t *testing.T) {
|
||||
// create files and put their parsed contents in cache
|
||||
for i := len(complicatedLookupHelper(emptyContext)) - 1; i >= 0; i-- {
|
||||
path := complicatedLookupHelper(emptyContext)[i]
|
||||
err := ioutil.WriteFile(path, []byte(fmt.Sprintf("{\"testkey\":%d}", i)), 0644)
|
||||
if err != nil {
|
||||
t.Log("file write failed")
|
||||
}
|
||||
|
||||
if i != c2.LookupInt(emptyContext, "testkey") {
|
||||
t.Log("LookupInt did not return correct value", i)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(time.Second) // make sure we get to update cache
|
||||
|
||||
for _, e := range testEvents {
|
||||
e.action()
|
||||
v := c2.LookupInt(emptyContext, "testkey")
|
||||
if e.ev != v {
|
||||
t.Log("Test failed:", e.desc, "expected:", e.ev, "got:", v)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range complicatedLookupHelper(emptyContext) {
|
||||
_ = os.Remove(p)
|
||||
}
|
||||
}
|
||||
|
||||
var c *Config = New(configLookupHelper)
|
||||
var c2 *Config = New(complicatedLookupHelper)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
os.Exit(m.Run())
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
// 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 is a hiera-inspired configuration library.
|
||||
package config
|
|
@ -10,8 +10,8 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/arachnist/dyncfg"
|
||||
"github.com/arachnist/gorepost/bot"
|
||||
"github.com/arachnist/gorepost/config"
|
||||
"github.com/arachnist/gorepost/irc"
|
||||
)
|
||||
|
||||
|
@ -50,7 +50,7 @@ func main() {
|
|||
log.Fatalln("Not a directory:", os.Args[1])
|
||||
}
|
||||
|
||||
cfg := config.New(fileListFuncBuilder(os.Args[1], "common.json"))
|
||||
cfg := dyncfg.New(fileListFuncBuilder(os.Args[1], "common.json"))
|
||||
|
||||
logfile, err := os.OpenFile(cfg.LookupString(context, "Logpath"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/arachnist/gorepost/config"
|
||||
"github.com/arachnist/dyncfg"
|
||||
)
|
||||
|
||||
const delim byte = '\n'
|
||||
|
@ -32,7 +32,7 @@ type Connection struct {
|
|||
quitrecv chan struct{}
|
||||
quitkeeper chan struct{}
|
||||
l sync.Mutex
|
||||
cfg *config.Config
|
||||
cfg *dyncfg.Dyncfg
|
||||
}
|
||||
|
||||
// Sender sends IRC messages to server and logs their contents.
|
||||
|
@ -179,7 +179,7 @@ func (c *Connection) Keeper() {
|
|||
}
|
||||
|
||||
// Setup performs initialization tasks.
|
||||
func (c *Connection) Setup(dispatcher func(func(Message), Message), network string, config *config.Config) {
|
||||
func (c *Connection) Setup(dispatcher func(func(Message), Message), network string, config *dyncfg.Dyncfg) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
c.reconnect = make(chan struct{}, 1)
|
||||
|
|
Loading…
Reference in New Issue