♲ Refactor configuration to use viper with context propagation
- Replace global ConfigPath and Overrides with viper-based configuration - Add viper.New() to create configurable viper instances - Store viper and unmarshaled Config struct in context for testability - Add RetrieveFromContext and AddToContext helper functions - Update files.Append to accept context and retrieve config from it - Update formatters.Preferred and formatters.New to accept context - Add PersistentPreRunE in CLI to create and configure viper instance - Support -c flag for custom config file path - Support -v flag for config value overrides - Update all test files to create viper and add to context - Remove unused config types and load functions - Add viper as dependency with automatic env var support (MYLOG_*)
This commit is contained in:
parent
d34363b8c0
commit
9f05f933dd
21 changed files with 338 additions and 360 deletions
|
|
@ -5,12 +5,17 @@ import (
|
|||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"codeberg.org/danjones000/my-log/config"
|
||||
"codeberg.org/danjones000/my-log/models"
|
||||
)
|
||||
|
||||
func newJson(ff config.Formatters) (Formatter, error) {
|
||||
return &Json{ff.Json().PrettyPrint}, nil
|
||||
func newJson(ff map[string]any) (Formatter, error) {
|
||||
prettyPrint := false
|
||||
if jf, ok := ff["json"].(map[string]any); ok {
|
||||
if pp, ok := jf["pretty_print"].(bool); ok {
|
||||
prettyPrint = pp
|
||||
}
|
||||
}
|
||||
return &Json{prettyPrint}, nil
|
||||
}
|
||||
|
||||
type Json struct {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,35 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"codeberg.org/danjones000/my-log/config"
|
||||
"codeberg.org/danjones000/my-log/internal/testutil/bep"
|
||||
"codeberg.org/danjones000/my-log/models"
|
||||
"github.com/nalgeon/be"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func setupJsonTestContext(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
v := viper.New()
|
||||
v.SetConfigType("toml")
|
||||
v.Set("formatters.json.pretty_print", false)
|
||||
return config.AddToContext(t.Context(), v)
|
||||
}
|
||||
|
||||
func TestJsonName(t *testing.T) {
|
||||
f, _ := New("json")
|
||||
ctx := setupJsonTestContext(t)
|
||||
f, _ := New(ctx, "json")
|
||||
be.Equal(t, f.Name(), "json")
|
||||
}
|
||||
|
||||
func TestJsonMeta(t *testing.T) {
|
||||
f, _ := New("json")
|
||||
ctx := setupJsonTestContext(t)
|
||||
f, _ := New(ctx, "json")
|
||||
m := models.Meta{Key: "foo", Value: 42}
|
||||
exp := `{"foo":42}`
|
||||
o, err := f.Meta(m)
|
||||
|
|
@ -25,8 +38,9 @@ func TestJsonMeta(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestJsonEntry(t *testing.T) {
|
||||
ctx := setupJsonTestContext(t)
|
||||
when := time.Now()
|
||||
f, _ := New("json")
|
||||
f, _ := New(ctx, "json")
|
||||
m := models.Meta{Key: "foo", Value: 42}
|
||||
e := models.Entry{
|
||||
Title: "Homer",
|
||||
|
|
@ -40,8 +54,9 @@ func TestJsonEntry(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestJsonLog(t *testing.T) {
|
||||
ctx := setupJsonTestContext(t)
|
||||
when := time.Now()
|
||||
f, _ := New("json")
|
||||
f, _ := New(ctx, "json")
|
||||
m := models.Meta{Key: "foo", Value: 42}
|
||||
e := models.Entry{
|
||||
Title: "Homer",
|
||||
|
|
@ -56,7 +71,8 @@ func TestJsonLog(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestJsonNoLogs(t *testing.T) {
|
||||
f, _ := New("json")
|
||||
ctx := setupJsonTestContext(t)
|
||||
f, _ := New(ctx, "json")
|
||||
o, err := f.Logs([]models.Log{})
|
||||
var exp []byte
|
||||
be.Err(t, err, nil)
|
||||
|
|
@ -64,7 +80,8 @@ func TestJsonNoLogs(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestJsonErr(t *testing.T) {
|
||||
f, _ := New("json")
|
||||
ctx := setupJsonTestContext(t)
|
||||
f, _ := New(ctx, "json")
|
||||
o, err := f.Meta(models.Meta{Key: "foo", Value: make(chan bool)})
|
||||
var exp []byte
|
||||
be.Err(t, err)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"codeberg.org/danjones000/my-log/config"
|
||||
)
|
||||
|
||||
type formatMaker func(config.Formatters) (Formatter, error)
|
||||
type formatMaker func(config map[string]any) (Formatter, error)
|
||||
|
||||
var formatterMap = map[string]formatMaker{
|
||||
"plain": newPlain,
|
||||
|
|
@ -14,23 +15,22 @@ var formatterMap = map[string]formatMaker{
|
|||
"zero": newNull,
|
||||
}
|
||||
|
||||
func Preferred() (f Formatter, err error) {
|
||||
conf, err := config.Load()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
std, _ := conf.Outputs.Stdout()
|
||||
return New(std.Format)
|
||||
func Preferred(ctx context.Context) (f Formatter, err error) {
|
||||
v, _ := config.RetrieveFromContext(ctx)
|
||||
format := v.GetString("output.stdout.config.format")
|
||||
return New(ctx, format)
|
||||
}
|
||||
|
||||
func New(kind string) (f Formatter, err error) {
|
||||
conf, err := config.Load()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
func New(ctx context.Context, kind string) (f Formatter, err error) {
|
||||
_, c := config.RetrieveFromContext(ctx)
|
||||
conf := c.Formatters
|
||||
|
||||
if make, ok := formatterMap[kind]; ok {
|
||||
return make(conf.Formatters)
|
||||
var formatterConf map[string]any
|
||||
if cf, ok := conf[kind]; ok {
|
||||
formatterConf = cf
|
||||
}
|
||||
return make(formatterConf)
|
||||
}
|
||||
|
||||
return nil, errors.New("unimplemented")
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"context"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"codeberg.org/danjones000/my-log/config"
|
||||
"github.com/nalgeon/be"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func TestKinds(t *testing.T) {
|
||||
|
|
@ -17,33 +17,25 @@ func TestKinds(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func setupNewTest(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
v := viper.New()
|
||||
v.SetConfigType("toml")
|
||||
v.Set("output.stdout.config.format", "plain")
|
||||
v.Set("formatters.json.pretty_print", false)
|
||||
return config.AddToContext(t.Context(), v)
|
||||
}
|
||||
|
||||
func TestNewUnsupported(t *testing.T) {
|
||||
f, err := New("nope")
|
||||
ctx := setupNewTest(t)
|
||||
f, err := New(ctx, "nope")
|
||||
be.Equal(t, f, nil)
|
||||
be.Err(t, err)
|
||||
}
|
||||
|
||||
func TestNewCantGetConfig(t *testing.T) {
|
||||
f, _ := os.CreateTemp("", "test")
|
||||
oldConf := config.ConfigPath
|
||||
config.ConfigPath = f.Name()
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
config.ConfigPath = oldConf
|
||||
}()
|
||||
|
||||
fmt.Fprint(f, `{"not":"toml"}`)
|
||||
form, err := New("plain")
|
||||
be.Equal(t, form, nil)
|
||||
be.Err(t, err)
|
||||
|
||||
form, err = Preferred()
|
||||
be.Equal(t, form, nil)
|
||||
be.Err(t, err)
|
||||
}
|
||||
|
||||
func TestPreferred(t *testing.T) {
|
||||
form, err := Preferred()
|
||||
ctx := setupNewTest(t)
|
||||
form, err := Preferred(ctx)
|
||||
be.Err(t, err, nil)
|
||||
be.True(t, form != nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"codeberg.org/danjones000/my-log/config"
|
||||
"codeberg.org/danjones000/my-log/models"
|
||||
)
|
||||
|
||||
func newNull(ff config.Formatters) (Formatter, error) {
|
||||
func newNull(ff map[string]any) (Formatter, error) {
|
||||
return &Null{}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,44 +1,59 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"codeberg.org/danjones000/my-log/config"
|
||||
"codeberg.org/danjones000/my-log/models"
|
||||
"github.com/nalgeon/be"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var empty []byte
|
||||
|
||||
func setupNullTestContext(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
v := viper.New()
|
||||
v.SetConfigType("toml")
|
||||
return config.AddToContext(t.Context(), v)
|
||||
}
|
||||
|
||||
func TestNullName(t *testing.T) {
|
||||
f, err := New("zero")
|
||||
ctx := setupNullTestContext(t)
|
||||
f, err := New(ctx, "zero")
|
||||
be.Err(t, err, nil)
|
||||
be.Equal(t, f.Name(), "zero")
|
||||
}
|
||||
|
||||
func TestNullMeta(t *testing.T) {
|
||||
f, _ := New("zero")
|
||||
ctx := setupNullTestContext(t)
|
||||
f, _ := New(ctx, "zero")
|
||||
o, err := f.Meta(models.Meta{Key: "foo", Value: 42})
|
||||
be.Err(t, err, nil)
|
||||
be.Equal(t, o, empty)
|
||||
}
|
||||
|
||||
func TestNullEntry(t *testing.T) {
|
||||
f, _ := New("zero")
|
||||
ctx := setupNullTestContext(t)
|
||||
f, _ := New(ctx, "zero")
|
||||
o, err := f.Entry(models.Entry{Title: "title", Date: time.Now()})
|
||||
be.Err(t, err, nil)
|
||||
be.Equal(t, o, empty)
|
||||
}
|
||||
|
||||
func TestNullLog(t *testing.T) {
|
||||
f, _ := New("zero")
|
||||
ctx := setupNullTestContext(t)
|
||||
f, _ := New(ctx, "zero")
|
||||
o, err := f.Log(models.Log{Name: "jim", Entries: []models.Entry{{Title: "title", Date: time.Now()}}})
|
||||
be.Err(t, err, nil)
|
||||
be.Equal(t, o, empty)
|
||||
}
|
||||
|
||||
func TestNullLogs(t *testing.T) {
|
||||
f, _ := New("zero")
|
||||
ctx := setupNullTestContext(t)
|
||||
f, _ := New(ctx, "zero")
|
||||
o, err := f.Logs([]models.Log{{Name: "jim", Entries: []models.Entry{{Title: "title", Date: time.Now()}}}})
|
||||
be.Err(t, err, nil)
|
||||
be.Equal(t, o, empty)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ package formatters
|
|||
import (
|
||||
"bytes"
|
||||
|
||||
"codeberg.org/danjones000/my-log/config"
|
||||
"codeberg.org/danjones000/my-log/models"
|
||||
"codeberg.org/danjones000/my-log/tools"
|
||||
)
|
||||
|
||||
func newPlain(ff config.Formatters) (Formatter, error) {
|
||||
func newPlain(ff map[string]any) (Formatter, error) {
|
||||
return &PlainText{}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,27 @@ package formatters
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"codeberg.org/danjones000/my-log/config"
|
||||
"codeberg.org/danjones000/my-log/models"
|
||||
"codeberg.org/danjones000/my-log/tools"
|
||||
"github.com/nalgeon/be"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func setupPlainTestContext(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
v := viper.New()
|
||||
v.SetConfigType("toml")
|
||||
return config.AddToContext(t.Context(), v)
|
||||
}
|
||||
|
||||
func TestPlainLogs(t *testing.T) {
|
||||
ctx := setupPlainTestContext(t)
|
||||
m := []models.Meta{
|
||||
{Key: "foo", Value: "bar"},
|
||||
{Key: "baz", Value: 42},
|
||||
|
|
@ -29,7 +40,7 @@ func TestPlainLogs(t *testing.T) {
|
|||
l2 := models.Log{Name: "more-stuff", Entries: []models.Entry{e2}}
|
||||
logs := []models.Log{l, l2}
|
||||
|
||||
f, err := New("plain")
|
||||
f, err := New(ctx, "plain")
|
||||
be.Err(t, err, nil)
|
||||
|
||||
out, err := f.Logs(logs)
|
||||
|
|
@ -95,40 +106,46 @@ func TestPlainLogs(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPlainName(t *testing.T) {
|
||||
f, _ := New("plain")
|
||||
ctx := setupPlainTestContext(t)
|
||||
f, _ := New(ctx, "plain")
|
||||
be.Equal(t, f.Name(), "plain")
|
||||
}
|
||||
|
||||
func TestPlainLogNone(t *testing.T) {
|
||||
f, _ := New("plain")
|
||||
ctx := setupPlainTestContext(t)
|
||||
f, _ := New(ctx, "plain")
|
||||
out, err := f.Logs([]models.Log{})
|
||||
be.Err(t, err, nil)
|
||||
be.Equal(t, len(out), 0)
|
||||
}
|
||||
|
||||
func TestPlainLogNoEntries(t *testing.T) {
|
||||
f, _ := New("plain")
|
||||
ctx := setupPlainTestContext(t)
|
||||
f, _ := New(ctx, "plain")
|
||||
out, err := f.Log(models.Log{Name: "foo"})
|
||||
be.Err(t, err, nil)
|
||||
be.Equal(t, len(out), 0)
|
||||
}
|
||||
|
||||
func TestPlainMetaEmpty(t *testing.T) {
|
||||
f, _ := New("plain")
|
||||
ctx := setupPlainTestContext(t)
|
||||
f, _ := New(ctx, "plain")
|
||||
out, err := f.Meta(models.Meta{Key: "foo", Value: ""})
|
||||
be.Err(t, err, nil)
|
||||
be.Equal(t, len(out), 0)
|
||||
}
|
||||
|
||||
func TestPlainMetaError(t *testing.T) {
|
||||
f, _ := New("plain")
|
||||
ctx := setupPlainTestContext(t)
|
||||
f, _ := New(ctx, "plain")
|
||||
out, err := f.Meta(models.Meta{Key: "foo", Value: make(chan bool)})
|
||||
be.Err(t, err)
|
||||
be.Equal(t, len(out), 0)
|
||||
}
|
||||
|
||||
func TestPlainEntry(t *testing.T) {
|
||||
f, _ := New("plain")
|
||||
ctx := setupPlainTestContext(t)
|
||||
f, _ := New(ctx, "plain")
|
||||
now := time.Now()
|
||||
out, err := f.Entry(models.Entry{
|
||||
Title: "foo",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue