From 28d07dd5351194a0ba240211d647d7340846ba2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20B=C3=A5rsaune?= Date: Fri, 12 Aug 2022 15:28:30 +0200 Subject: [PATCH] new implementation of Extend (#22) --- config.go | 122 +++++++++++++++++++++++++++++++++++++++++++------ config_test.go | 32 ++++++------- 2 files changed, 124 insertions(+), 30 deletions(-) diff --git a/config.go b/config.go index 1a7532b..f8147fc 100644 --- a/config.go +++ b/config.go @@ -10,6 +10,7 @@ import ( "flag" "fmt" "io/ioutil" + "reflect" "strconv" "strings" "syscall" @@ -350,28 +351,123 @@ func (c *Config) Copy(dottedPath ...string) (*Config, error) { return ParseYaml(root) } -// Extend returns extended copy of current config with applied -// values from the given config instance. Note that if you extend -// with different structure you will get an error. See: `.Set()` method -// for details. -func (c *Config) Extend(cfg *Config) (*Config, error) { - n, err := c.Copy() +// Extend returns an extended copy of given instance (cfgA) with applied +// values from the current instance (cfgB). The new Config will have +// all the keys and items of cfgA. Items from cfgB are added to the new +// config if they satisfy the following conditions: +// 1. Their key does not already exist in the new Config +// 2. They can be appended to the new Config without changing the type +// if items already in the Config. +// The function should never return an error if the Config structs have +// the correct structure +func (cfgB *Config) Extend(cfgA *Config) (*Config, error) { + + newCfgInterface, err := extend(*cfgA, *cfgB) if err != nil { return nil, err } - keys := getKeys(cfg.Root) - for _, key := range keys { - k := strings.Join(key, ".") - i, err := Get(cfg.Root, k) + newCfg, ok := newCfgInterface.(Config) + if !ok { + return nil, err + } + + return &newCfg, nil +} + +// Recursively extends ca with cb and returns a copy +func extend(ca interface{}, cb interface{}) (interface{}, error) { + + // make sure types of ca and cb are correct + + if reflect.TypeOf(ca) != reflect.TypeOf(cb) { + switch ca.(type) { + case map[string]interface{}: + cb = map[string]interface{}{} + case []interface{}: + cb = []interface{}{} + case float64, string, int, bool: + return ca, nil + // do nothing + default: + return nil, fmt.Errorf("Invalid input. ca and cb must be of same type. They are %T %T\n", ca, cb) + } + } + + switch vala := ca.(type) { + case Config: + valb, _ := cb.(Config) + cfg, err := extend(vala.Root, valb.Root) if err != nil { return nil, err } - if err := n.Set(k, i); err != nil { - return nil, err + return Config{cfg, nil}, nil + + case map[string]interface{}: + mapb, _ := cb.(map[string]interface{}) + mapa := vala + + // Create a map and fill it with items from ca and cb + newmap := map[string]interface{}{} + var err error + + for key, elementa := range mapa { + elementb, ok := mapb[key] + if ok { + newmap[key], err = extend(elementa, elementb) + if err != nil { + return newmap, err + } + } else { + newmap[key], err = extend(elementa, nil) + if err != nil { + return newmap, err + } + } + } + + for key, elementb := range mapb { + _, ok := mapa[key] + if !ok { + // only add element if it does not exist in ca + val, err := extend(elementb, nil) + if err != nil { + return nil, err + } + newmap[key] = val + } } + return newmap, nil + + case []interface{}: + ifaceb, _ := cb.([]interface{}) + ifacea := vala + + // create new slice capable of holding all items from both ca and cb + newiface := make([]interface{}, 0, cap(ifacea)+cap(ifaceb)) + maxlen := len(ifacea) + + if len(ifaceb) > len(ifacea) { + maxlen = len(ifaceb) + } + for i := 0; i < maxlen; i++ { + + // only add elements from cb if their index does not exist in ca + + if i < len(ifacea) { + newiface = append(newiface, ifacea[i]) + } else if i < len(ifaceb) { + newiface = append(newiface, ifaceb[i]) + } else { + break + } + } + return newiface, nil + case float64, string, bool, int: + return ca, nil } - return n, nil + + return nil, fmt.Errorf("Got invalid type: %T %T\n", ca, cb) } // typeMismatch returns an error for an expected type. diff --git a/config_test.go b/config_test.go index 2c6beee..ac794f1 100644 --- a/config_test.go +++ b/config_test.go @@ -387,23 +387,6 @@ func TestCopy(t *testing.T) { expect(t, yaml3, yaml4) } -func TestExtendError(t *testing.T) { - cfg, err := ParseYaml(yamlString) - if err != nil { - t.Fatal(err) - } - cfg2, err := ParseYaml(` -list: - key0: true -map: - - true -`) - var nilCfg *Config - extended, err := cfg.Extend(cfg2) - expect(t, extended, nilCfg) - expect(t, err.Error(), "Invalid list index at \"list.key0\"") -} - func TestExtend(t *testing.T) { cfg, err := ParseYaml(yamlString) if err != nil { @@ -430,6 +413,21 @@ list: expect(t, extended.UString("map.key8"), "value8") expect(t, extended.UString("list.0"), "extend") expect(t, extended.UString("list.8"), "item8") + + // #22 Extending lists + + cfgList1, err := ParseJson(`{"list":[1,2]}`) + if err != nil { + t.Fatal(err) + } + cfgList2, err := ParseJson(`{"list":[1,2,3]}`) + if err != nil { + t.Fatal(err) + } + + cfgListExtended, err := cfgList1.Extend(cfgList2) + expect(t, err, nil) + expect(t, cfgListExtended.UInt("list.2"), 3) } func TestComplexYamlKeys(t *testing.T) {