From 97390eb722919fe90174ad0f779e5c6114cbc7ce Mon Sep 17 00:00:00 2001 From: urso Date: Mon, 25 Jul 2016 18:10:26 +0200 Subject: [PATCH] Add IsArray and IsDict for checking config type - Update fields structure to provide accessors. - Initialize dict in fields struct lazily when setting some key --- CHANGELOG.md | 1 + getset.go | 4 ++-- getset_test.go | 5 +++++ merge.go | 12 +++++------ path.go | 19 +++++++----------- reify.go | 9 +++++---- types.go | 33 +++++++++++++++++------------- ucfg.go | 54 +++++++++++++++++++++++++++++++++++++++++++++----- 8 files changed, 94 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b687266..d1f8b64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- Add `(*Config).IsArray` and `(*Config).IsDict`. #44 ### Changed diff --git a/getset.go b/getset.go index 42bc42d..e41db69 100644 --- a/getset.go +++ b/getset.go @@ -15,10 +15,10 @@ func convertErr(opts *options, v value, err error, to string) Error { // of elements in list func (c *Config) CountField(name string, opts ...Option) (int, error) { if name == "" { - return len(c.fields.arr) + len(c.fields.fields), nil + return len(c.fields.array()) + len(c.fields.dict()), nil } - if v, ok := c.fields.fields[name]; ok { + if v, ok := c.fields.get(name); ok { return v.Len(makeOptions(opts)) } return -1, raiseMissing(c, name) diff --git a/getset_test.go b/getset_test.go index fa6ae44..9be47fa 100644 --- a/getset_test.go +++ b/getset_test.go @@ -15,6 +15,9 @@ func TestSetGetPrimitives(t *testing.T) { c.SetFloat("float", -1, 2.3) c.SetString("str", -1, "abc") + assert.True(t, c.IsDict()) + assert.False(t, c.IsArray()) + assert.True(t, c.HasField("bool")) assert.True(t, c.HasField("int")) assert.True(t, c.HasField("uint")) @@ -127,6 +130,8 @@ func TestSetGetArray(t *testing.T) { a, err := c.Child("a", -1) assert.NoError(t, err) + assert.True(t, a.IsArray()) + assert.False(t, a.IsDict()) l, err = a.CountField("") assert.NoError(t, err) diff --git a/merge.go b/merge.go index 336f7c7..f3085c8 100644 --- a/merge.go +++ b/merge.go @@ -17,27 +17,27 @@ func (c *Config) Merge(from interface{}, options ...Option) error { } func mergeConfig(opts *options, to, from *Config) Error { - for k, v := range from.fields.fields { + for k, v := range from.fields.dict() { ctx := context{ parent: cfgSub{to}, field: k, } - old, ok := to.fields.fields[k] + old, ok := to.fields.get(k) if !ok { - to.fields.fields[k] = v.cpy(ctx) + to.fields.set(k, v.cpy(ctx)) continue } subOld, err := old.toConfig(opts) if err != nil { - to.fields.fields[k] = v.cpy(ctx) + to.fields.set(k, v.cpy(ctx)) continue } subFrom, err := v.toConfig(opts) if err != nil { - to.fields.fields[k] = v.cpy(ctx) + to.fields.set(k, v.cpy(ctx)) continue } @@ -223,7 +223,7 @@ func normalizeArray( out = append(out, tmp) } - cfg.fields.arr = out + cfg.fields.a = out return val, nil } diff --git a/path.go b/path.go index bf01eac..2efc606 100644 --- a/path.go +++ b/path.go @@ -123,7 +123,8 @@ func (n namedField) GetValue(opts *options, elem value) (value, Error) { return nil, raiseExpectedObject(opts, elem) } - return cfg.fields.fields[n.name], nil + v, _ := cfg.fields.get(n.name) + return v, nil } func (i idxField) GetValue(opts *options, elem value) (value, Error) { @@ -136,11 +137,11 @@ func (i idxField) GetValue(opts *options, elem value) (value, Error) { return nil, raiseExpectedObject(opts, elem) } - if i.i >= len(cfg.fields.arr) { + arr := cfg.fields.array() + if i.i >= len(arr) { return nil, raiseMissing(cfg, i.String()) } - - return cfg.fields.arr[i.i], nil + return arr[i.i], nil } func (p cfgPath) SetValue(cfg *Config, opt *options, val value) Error { @@ -188,7 +189,7 @@ func (n namedField) SetValue(opts *options, elem value, v value) Error { return raiseExpectedObject(opts, elem) } - sub.c.fields.fields[n.name] = v + sub.c.fields.set(n.name, v) v.SetContext(context{parent: elem, field: n.name}) return nil } @@ -199,13 +200,7 @@ func (i idxField) SetValue(opts *options, elem value, v value) Error { return raiseExpectedObject(opts, elem) } - if i.i >= len(sub.c.fields.arr) { - tmp := make([]value, i.i+1) - copy(tmp, sub.c.fields.arr) - sub.c.fields.arr = tmp - } - - sub.c.fields.arr[i.i] = v + sub.c.fields.setAt(i.i, v) v.SetContext(context{parent: elem, field: i.String()}) return nil } diff --git a/reify.go b/reify.go index b5677b1..3ce0390 100644 --- a/reify.go +++ b/reify.go @@ -60,14 +60,15 @@ func reifyMap(opts *options, to reflect.Value, from *Config) Error { return raiseKeyInvalidTypeUnpack(to.Type(), from) } - if len(from.fields.fields) == 0 { + fields := from.fields.dict() + if len(fields) == 0 { return nil } if to.IsNil() { to.Set(reflect.MakeMap(to.Type())) } - for k, value := range from.fields.fields { + for k, value := range fields { key := reflect.ValueOf(k) old := to.MapIndex(key) @@ -398,7 +399,7 @@ func reifyDoArray( func castArr(opts *options, v value) ([]value, Error) { if sub, ok := v.(cfgSub); ok { - return sub.c.fields.arr, nil + return sub.c.fields.array(), nil } if ref, ok := v.(*cfgRef); ok { unrefed, err := ref.resolve(opts) @@ -407,7 +408,7 @@ func castArr(opts *options, v value) ([]value, Error) { } if sub, ok := unrefed.(cfgSub); ok { - return sub.c.fields.arr, nil + return sub.c.fields.array(), nil } } diff --git a/types.go b/types.go index 79ee39a..230262f 100644 --- a/types.go +++ b/types.go @@ -260,7 +260,7 @@ func (cfgSub) toFloat(*options) (float64, error) { return 0, ErrTypeMismatch func (c cfgSub) toConfig(*options) (*Config, error) { return c.c, nil } func (c cfgSub) Len(*options) (int, error) { - arr := c.c.fields.arr + arr := c.c.fields.array() if arr != nil { return len(arr), nil } @@ -282,18 +282,23 @@ func (c cfgSub) cpy(ctx context) value { c: &Config{ctx: ctx, metadata: c.c.metadata}, } - fields := &fields{ - fields: map[string]value{}, - arr: make([]value, len(c.c.fields.arr)), - } + dict := c.c.fields.dict() + arr := c.c.fields.array() + fields := &fields{} - for name, f := range c.c.fields.fields { + for name, f := range dict { ctx := f.Context() - fields.fields[name] = f.cpy(context{field: ctx.field, parent: newC}) + v := f.cpy(context{field: ctx.field, parent: newC}) + fields.set(name, v) } - for i, f := range c.c.fields.arr { - ctx := f.Context() - fields.arr[i] = f.cpy(context{field: ctx.field, parent: newC}) + + if arr != nil { + fields.a = make([]value, len(arr)) + for i, f := range arr { + ctx := f.Context() + v := f.cpy(context{field: ctx.field, parent: newC}) + fields.setAt(i, v) + } } newC.c.fields = fields @@ -312,15 +317,15 @@ func (c cfgSub) SetContext(ctx context) { } func (c cfgSub) reify(opts *options) (interface{}, error) { - fields := c.c.fields.fields - arr := c.c.fields.arr + fields := c.c.fields.dict() + arr := c.c.fields.array() switch { case len(fields) == 0 && len(arr) == 0: return nil, nil case len(fields) > 0 && len(arr) == 0: m := make(map[string]interface{}) - for k, v := range c.c.fields.fields { + for k, v := range fields { var err error if m[k], err = v.reify(opts); err != nil { return nil, err @@ -338,7 +343,7 @@ func (c cfgSub) reify(opts *options) (interface{}, error) { return m, nil default: m := make(map[string]interface{}) - for k, v := range c.c.fields.fields { + for k, v := range fields { var err error if m[k], err = v.reify(opts); err != nil { return nil, err diff --git a/ucfg.go b/ucfg.go index 781fbf4..9e44d4b 100644 --- a/ucfg.go +++ b/ucfg.go @@ -19,8 +19,8 @@ type fieldOptions struct { } type fields struct { - fields map[string]value - arr []value + d map[string]value + a []value } // Meta holds additional meta data per config value @@ -54,7 +54,7 @@ var ( func New() *Config { return &Config{ - fields: &fields{map[string]value{}, nil}, + fields: &fields{nil, nil}, } } @@ -66,16 +66,24 @@ func NewFrom(from interface{}, opts ...Option) (*Config, error) { return c, nil } +func (c *Config) IsDict() bool { + return c.fields.dict() != nil +} + +func (c *Config) IsArray() bool { + return c.fields.array() != nil +} + func (c *Config) GetFields() []string { var names []string - for k := range c.fields.fields { + for k := range c.fields.dict() { names = append(names, k) } return names } func (c *Config) HasField(name string) bool { - _, ok := c.fields.fields[name] + _, ok := c.fields.get(name) return ok } @@ -102,3 +110,39 @@ func (c *Config) Parent() *Config { } } } + +func (f *fields) get(name string) (value, bool) { + if f.d == nil { + return nil, false + } + v, found := f.d[name] + return v, found +} + +func (f *fields) dict() map[string]value { + return f.d +} + +func (f *fields) array() []value { + return f.a +} + +func (f *fields) set(name string, v value) { + if f.d == nil { + f.d = map[string]value{} + } + f.d[name] = v +} + +func (f *fields) add(v value) { + f.a = append(f.a, v) +} + +func (f *fields) setAt(idx int, v value) { + if idx >= len(f.a) { + tmp := make([]value, idx+1) + copy(tmp, f.a) + f.a = tmp + } + f.a[idx] = v +}