diff --git a/pkg/yqlib/json_converter.go b/pkg/marshal/json_converter.go similarity index 54% rename from pkg/yqlib/json_converter.go rename to pkg/marshal/json_converter.go index e837188..d42a001 100644 --- a/pkg/yqlib/json_converter.go +++ b/pkg/marshal/json_converter.go @@ -1,4 +1,4 @@ -package yqlib +package marshal import ( "encoding/json" @@ -8,21 +8,31 @@ import ( yaml "github.com/mikefarah/yaml/v2" ) -func JsonToString(context interface{}) (string, error) { - out, err := json.Marshal(toJSON(context)) +type JsonConverter interface { + JsonToString(context interface{}) (string, error) +} + +type jsonConverter struct {} + +func NewJsonConverter() JsonConverter { + return &jsonConverter{} +} + +func (j *jsonConverter) JsonToString(context interface{}) (string, error) { + out, err := json.Marshal(j.toJSON(context)) if err != nil { return "", fmt.Errorf("error printing yaml as json: %v", err) } return string(out), nil } -func toJSON(context interface{}) interface{} { +func (j *jsonConverter) toJSON(context interface{}) interface{} { switch context := context.(type) { case []interface{}: oldArray := context newArray := make([]interface{}, len(oldArray)) for index, value := range oldArray { - newArray[index] = toJSON(value) + newArray[index] = j.toJSON(value) } return newArray case yaml.MapSlice: @@ -30,11 +40,11 @@ func toJSON(context interface{}) interface{} { newMap := make(map[string]interface{}) for _, entry := range oldMap { if str, ok := entry.Key.(string); ok { - newMap[str] = toJSON(entry.Value) + newMap[str] = j.toJSON(entry.Value) } else if i, ok := entry.Key.(int); ok { - newMap[strconv.Itoa(i)] = toJSON(entry.Value) + newMap[strconv.Itoa(i)] = j.toJSON(entry.Value) } else if b, ok := entry.Key.(bool); ok { - newMap[strconv.FormatBool(b)] = toJSON(entry.Value) + newMap[strconv.FormatBool(b)] = j.toJSON(entry.Value) } } return newMap diff --git a/pkg/yqlib/json_converter_test.go b/pkg/marshal/json_converter_test.go similarity index 76% rename from pkg/yqlib/json_converter_test.go rename to pkg/marshal/json_converter_test.go index ae1c08d..e568902 100644 --- a/pkg/yqlib/json_converter_test.go +++ b/pkg/marshal/json_converter_test.go @@ -1,4 +1,4 @@ -package yqlib +package marshal import ( "testing" @@ -11,7 +11,7 @@ func TestJsonToString(t *testing.T) { b: c: 2 `) - got, _ := JsonToString(data) + got, _ := NewJsonConverter().JsonToString(data) test.AssertResult(t, "{\"b\":{\"c\":2}}", got) } @@ -21,7 +21,7 @@ func TestJsonToString_withIntKey(t *testing.T) { b: 2: c `) - got, _ := JsonToString(data) + got, _ := NewJsonConverter().JsonToString(data) test.AssertResult(t, `{"b":{"2":"c"}}`, got) } @@ -31,7 +31,7 @@ func TestJsonToString_withBoolKey(t *testing.T) { b: false: c `) - got, _ := JsonToString(data) + got, _ := NewJsonConverter().JsonToString(data) test.AssertResult(t, `{"b":{"false":"c"}}`, got) } @@ -42,6 +42,6 @@ b: - item: one - item: two `) - got, _ := JsonToString(data) + got, _ := NewJsonConverter().JsonToString(data) test.AssertResult(t, "{\"b\":[{\"item\":\"one\"},{\"item\":\"two\"}]}", got) } diff --git a/pkg/yqlib/yaml_converter.go b/pkg/marshal/yaml_converter.go similarity index 52% rename from pkg/yqlib/yaml_converter.go rename to pkg/marshal/yaml_converter.go index 08eec17..a632ae9 100644 --- a/pkg/yqlib/yaml_converter.go +++ b/pkg/marshal/yaml_converter.go @@ -1,4 +1,4 @@ -package yqlib +package marshal import ( yaml "github.com/mikefarah/yaml/v2" @@ -6,16 +6,26 @@ import ( "strings" ) -func YamlToString(context interface{}, trimOutput bool) (string, error) { +type YamlConverter interface { + YamlToString(context interface{}, trimOutput bool) (string, error) +} + +type yamlConverter struct {} + +func NewYamlConverter() YamlConverter { + return &yamlConverter{} +} + +func (y *yamlConverter) YamlToString(context interface{}, trimOutput bool) (string, error) { switch context := context.(type) { case string: return context, nil default: - return marshalContext(context, trimOutput) + return y.marshalContext(context, trimOutput) } } -func marshalContext(context interface{}, trimOutput bool) (string, error) { +func (y *yamlConverter) marshalContext(context interface{}, trimOutput bool) (string, error) { out, err := yaml.Marshal(context) if err != nil { diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 2b66dee..c0e4c58 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -7,9 +7,96 @@ import ( "strings" yaml "github.com/mikefarah/yaml/v2" + logging "gopkg.in/op/go-logging.v1" ) -func matchesKey(key string, actual interface{}) bool { +type DataNavigator interface { + ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error) + UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} + DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) +} + +type navigator struct { + log *logging.Logger +} + +func NewDataNavigator(l *logging.Logger) DataNavigator { + return &navigator { + log: l, + } +} + +func (n *navigator) ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error) { + if len(remainingPaths) == 0 { + return child, nil + } + return n.recurse(child, remainingPaths[0], remainingPaths[1:]) +} + +func (n *navigator) UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} { + if len(remainingPaths) == 0 { + return value + } + n.log.Debugf("UpdatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value) + n.log.Debugf("type of child is %v", reflect.TypeOf(child)) + + switch child := child.(type) { + case nil: + if remainingPaths[0] == "+" || remainingPaths[0] == "*" { + return n.writeArray(child, remainingPaths, value) + } + case []interface{}: + _, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64) + arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*" + if arrayCommand { + return n.writeArray(child, remainingPaths, value) + } + } + return n.writeMap(child, remainingPaths, value) +} + +func (n *navigator) DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) { + n.log.Debugf("DeleteChildValue for %v for %v\n", remainingPaths, child) + if len(remainingPaths) == 0 { + return child, nil + } + var head = remainingPaths[0] + var tail = remainingPaths[1:] + switch child := child.(type) { + case yaml.MapSlice: + return n.deleteMap(child, remainingPaths) + case []interface{}: + if head == "*" { + return n.deleteArraySplat(child, tail) + } + index, err := strconv.ParseInt(head, 10, 64) + if err != nil { + return nil, fmt.Errorf("error accessing array: %v", err) + } + return n.deleteArray(child, remainingPaths, index) + } + return child, nil +} + +func (n *navigator) recurse(value interface{}, head string, tail []string) (interface{}, error) { + switch value := value.(type) { + case []interface{}: + if head == "*" { + return n.readArraySplat(value, tail) + } + index, err := strconv.ParseInt(head, 10, 64) + if err != nil { + return nil, fmt.Errorf("error accessing array: %v", err) + } + return n.readArray(value, index, tail) + case yaml.MapSlice: + return n.readMap(value, head, tail) + default: + return nil, nil + } +} + +func (n *navigator) matchesKey(key string, actual interface{}) bool { var actualString = fmt.Sprintf("%v", actual) var prefixMatch = strings.TrimSuffix(key, "*") if prefixMatch != key { @@ -18,18 +105,18 @@ func matchesKey(key string, actual interface{}) bool { return actualString == key } -func entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem { +func (n *navigator) entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem { var matches = make([]*yaml.MapItem, 0) for idx := range context { var entry = &context[idx] - if matchesKey(key, entry.Key) { + if n.matchesKey(key, entry.Key) { matches = append(matches, entry) } } return matches } -func getMapSlice(context interface{}) yaml.MapSlice { +func (n *navigator) getMapSlice(context interface{}) yaml.MapSlice { var mapSlice yaml.MapSlice switch context := context.(type) { case yaml.MapSlice: @@ -40,7 +127,7 @@ func getMapSlice(context interface{}) yaml.MapSlice { return mapSlice } -func getArray(context interface{}) (array []interface{}, ok bool) { +func (n *navigator) getArray(context interface{}) (array []interface{}, ok bool) { switch context := context.(type) { case []interface{}: array = context @@ -52,68 +139,46 @@ func getArray(context interface{}) (array []interface{}, ok bool) { return } -func writeMap(context interface{}, paths []string, value interface{}) interface{} { - log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value) +func (n *navigator) writeMap(context interface{}, paths []string, value interface{}) interface{} { + n.log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value) - mapSlice := getMapSlice(context) + mapSlice := n.getMapSlice(context) if len(paths) == 0 { return context } - children := entriesInSlice(mapSlice, paths[0]) + children := n.entriesInSlice(mapSlice, paths[0]) if len(children) == 0 && paths[0] == "*" { - log.Debugf("\tNo matches, return map as is") + n.log.Debugf("\tNo matches, return map as is") return context } if len(children) == 0 { newChild := yaml.MapItem{Key: paths[0]} mapSlice = append(mapSlice, newChild) - children = entriesInSlice(mapSlice, paths[0]) - log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice) + children = n.entriesInSlice(mapSlice, paths[0]) + n.log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice) } remainingPaths := paths[1:] for _, child := range children { - child.Value = UpdatedChildValue(child.Value, remainingPaths, value) + child.Value = n.UpdatedChildValue(child.Value, remainingPaths, value) } - log.Debugf("\tReturning mapSlice %v\n", mapSlice) + n.log.Debugf("\tReturning mapSlice %v\n", mapSlice) return mapSlice } -func UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} { - if len(remainingPaths) == 0 { - return value - } - log.Debugf("UpdatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value) - log.Debugf("type of child is %v", reflect.TypeOf(child)) - - switch child := child.(type) { - case nil: - if remainingPaths[0] == "+" || remainingPaths[0] == "*" { - return writeArray(child, remainingPaths, value) - } - case []interface{}: - _, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64) - arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*" - if arrayCommand { - return writeArray(child, remainingPaths, value) - } - } - return writeMap(child, remainingPaths, value) -} - -func writeArray(context interface{}, paths []string, value interface{}) []interface{} { - log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value) - array, _ := getArray(context) +func (n *navigator) writeArray(context interface{}, paths []string, value interface{}) []interface{} { + n.log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value) + array, _ := n.getArray(context) if len(paths) == 0 { return array } - log.Debugf("\tarray %v\n", array) + n.log.Debugf("\tarray %v\n", array) rawIndex := paths[0] remainingPaths := paths[1:] @@ -123,7 +188,7 @@ func writeArray(context interface{}, paths []string, value interface{}) []interf index = int64(len(array)) } else if rawIndex == "*" { for index, oldChild := range array { - array[index] = UpdatedChildValue(oldChild, remainingPaths, value) + array[index] = n.UpdatedChildValue(oldChild, remainingPaths, value) } return array } else { @@ -137,31 +202,31 @@ func writeArray(context interface{}, paths []string, value interface{}) []interf } currentChild := array[index] - log.Debugf("\tcurrentChild %v\n", currentChild) + n.log.Debugf("\tcurrentChild %v\n", currentChild) - array[index] = UpdatedChildValue(currentChild, remainingPaths, value) - log.Debugf("\tReturning array %v\n", array) + array[index] = n.UpdatedChildValue(currentChild, remainingPaths, value) + n.log.Debugf("\tReturning array %v\n", array) return array } -func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) { - log.Debugf("readingMap %v with key %v\n", context, head) +func (n *navigator) readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) { + n.log.Debugf("readingMap %v with key %v\n", context, head) if head == "*" { - return readMapSplat(context, tail) + return n.readMapSplat(context, tail) } - entries := entriesInSlice(context, head) + entries := n.entriesInSlice(context, head) if len(entries) == 1 { - return calculateValue(entries[0].Value, tail) + return n.calculateValue(entries[0].Value, tail) } else if len(entries) == 0 { return nil, nil } var errInIdx error values := make([]interface{}, len(entries)) for idx, entry := range entries { - values[idx], errInIdx = calculateValue(entry.Value, tail) + values[idx], errInIdx = n.calculateValue(entry.Value, tail) if errInIdx != nil { - log.Errorf("Error updating index %v in %v", idx, context) + n.log.Errorf("Error updating index %v in %v", idx, context) return nil, errInIdx } @@ -169,12 +234,12 @@ func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, er return values, nil } -func readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) { +func (n *navigator) readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) { var newArray = make([]interface{}, len(context)) var i = 0 for _, entry := range context { if len(tail) > 0 { - val, err := Recurse(entry.Value, tail[0], tail[1:]) + val, err := n.recurse(entry.Value, tail[0], tail[1:]) if err != nil { return nil, err } @@ -187,37 +252,19 @@ func readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) { return newArray, nil } -func Recurse(value interface{}, head string, tail []string) (interface{}, error) { - switch value := value.(type) { - case []interface{}: - if head == "*" { - return readArraySplat(value, tail) - } - index, err := strconv.ParseInt(head, 10, 64) - if err != nil { - return nil, fmt.Errorf("error accessing array: %v", err) - } - return readArray(value, index, tail) - case yaml.MapSlice: - return readMap(value, head, tail) - default: - return nil, nil - } -} - -func readArray(array []interface{}, head int64, tail []string) (interface{}, error) { +func (n *navigator) readArray(array []interface{}, head int64, tail []string) (interface{}, error) { if head >= int64(len(array)) { return nil, nil } value := array[head] - return calculateValue(value, tail) + return n.calculateValue(value, tail) } -func readArraySplat(array []interface{}, tail []string) (interface{}, error) { +func (n *navigator) readArraySplat(array []interface{}, tail []string) (interface{}, error) { var newArray = make([]interface{}, len(array)) for index, value := range array { - val, err := calculateValue(value, tail) + val, err := n.calculateValue(value, tail) if err != nil { return nil, err } @@ -226,17 +273,17 @@ func readArraySplat(array []interface{}, tail []string) (interface{}, error) { return newArray, nil } -func calculateValue(value interface{}, tail []string) (interface{}, error) { +func (n *navigator) calculateValue(value interface{}, tail []string) (interface{}, error) { if len(tail) > 0 { - return Recurse(value, tail[0], tail[1:]) + return n.recurse(value, tail[0], tail[1:]) } return value, nil } -func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) { - log.Debugf("deleteMap for %v for %v\n", paths, context) +func (n *navigator) deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) { + n.log.Debugf("deleteMap for %v for %v\n", paths, context) - mapSlice := getMapSlice(context) + mapSlice := n.getMapSlice(context) if len(paths) == 0 { return mapSlice, nil @@ -245,10 +292,10 @@ func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) { var index int var child yaml.MapItem for index, child = range mapSlice { - if matchesKey(paths[0], child.Key) { - log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index) + if n.matchesKey(paths[0], child.Key) { + n.log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index) var badDelete error - mapSlice, badDelete = deleteEntryInMap(mapSlice, child, index, paths) + mapSlice, badDelete = n.deleteEntryInMap(mapSlice, child, index, paths) if badDelete != nil { return nil, badDelete } @@ -259,14 +306,14 @@ func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) { } -func deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) { +func (n *navigator) deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) { remainingPaths := paths[1:] var newSlice yaml.MapSlice if len(remainingPaths) > 0 { newChild := yaml.MapItem{Key: child.Key} var errorDeleting error - newChild.Value, errorDeleting = DeleteChildValue(child.Value, remainingPaths) + newChild.Value, errorDeleting = n.DeleteChildValue(child.Value, remainingPaths) if errorDeleting != nil { return nil, errorDeleting } @@ -282,18 +329,18 @@ func deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, pat } else { // Delete item from slice at index newSlice = append(original[:index], original[index+1:]...) - log.Debugf("\tDeleted item index %d from original", index) + n.log.Debugf("\tDeleted item index %d from original", index) } - log.Debugf("\tReturning original %v\n", original) + n.log.Debugf("\tReturning original %v\n", original) return newSlice, nil } -func deleteArraySplat(array []interface{}, tail []string) (interface{}, error) { - log.Debugf("deleteArraySplat for %v for %v\n", tail, array) +func (n *navigator) deleteArraySplat(array []interface{}, tail []string) (interface{}, error) { + n.log.Debugf("deleteArraySplat for %v for %v\n", tail, array) var newArray = make([]interface{}, len(array)) for index, value := range array { - val, err := DeleteChildValue(value, tail) + val, err := n.DeleteChildValue(value, tail) if err != nil { return nil, err } @@ -302,8 +349,8 @@ func deleteArraySplat(array []interface{}, tail []string) (interface{}, error) { return newArray, nil } -func deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) { - log.Debugf("deleteArray for %v for %v\n", paths, array) +func (n *navigator) deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) { + n.log.Debugf("deleteArray for %v for %v\n", paths, array) if index >= int64(len(array)) { return array, nil @@ -311,9 +358,9 @@ func deleteArray(array []interface{}, paths []string, index int64) (interface{}, remainingPaths := paths[1:] if len(remainingPaths) > 0 { - // Recurse into the array element at index + // recurse into the array element at index var errorDeleting error - array[index], errorDeleting = deleteMap(array[index], remainingPaths) + array[index], errorDeleting = n.deleteMap(array[index], remainingPaths) if errorDeleting != nil { return nil, errorDeleting } @@ -321,29 +368,9 @@ func deleteArray(array []interface{}, paths []string, index int64) (interface{}, } else { // Delete the array element at index array = append(array[:index], array[index+1:]...) - log.Debugf("\tDeleted item index %d from array, leaving %v", index, array) + n.log.Debugf("\tDeleted item index %d from array, leaving %v", index, array) } - log.Debugf("\tReturning array: %v\n", array) + n.log.Debugf("\tReturning array: %v\n", array) return array, nil } - -func DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) { - log.Debugf("DeleteChildValue for %v for %v\n", remainingPaths, child) - var head = remainingPaths[0] - var tail = remainingPaths[1:] - switch child := child.(type) { - case yaml.MapSlice: - return deleteMap(child, remainingPaths) - case []interface{}: - if head == "*" { - return deleteArraySplat(child, tail) - } - index, err := strconv.ParseInt(head, 10, 64) - if err != nil { - return nil, fmt.Errorf("error accessing array: %v", err) - } - return deleteArray(child, remainingPaths, index) - } - return child, nil -} diff --git a/pkg/yqlib/data_navigator_test.go b/pkg/yqlib/data_navigator_test.go index ff772e1..439ff73 100644 --- a/pkg/yqlib/data_navigator_test.go +++ b/pkg/yqlib/data_navigator_test.go @@ -5,53 +5,58 @@ import ( "sort" "testing" "github.com/mikefarah/yq/test" + logging "gopkg.in/op/go-logging.v1" ) -func TestReadMap_simple(t *testing.T) { - var data = test.ParseData(` +func TestDataNavigator(t *testing.T) { + var log = logging.MustGetLogger("yq") + subject := NewDataNavigator(log) + + t.Run("TestReadMap_simple", func(t *testing.T) { + var data = test.ParseData(` --- b: c: 2 `) - got, _ := readMap(data, "b", []string{"c"}) - test.AssertResult(t, 2, got) -} + got, _ := subject.ReadChildValue(data, []string{"b", "c"}) + test.AssertResult(t, 2, got) + }) -func TestReadMap_numberKey(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_numberKey", func(t *testing.T) { + var data = test.ParseData(` --- 200: things `) - got, _ := readMap(data, "200", []string{}) - test.AssertResult(t, "things", got) -} + got, _ := subject.ReadChildValue(data, []string{"200"}) + test.AssertResult(t, "things", got) + }) -func TestReadMap_splat(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_splat", func(t *testing.T) { + var data = test.ParseData(` --- mapSplat: item1: things item2: whatever otherThing: cat `) - res, _ := readMap(data, "mapSplat", []string{"*"}) - test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res)) -} + res, _ := subject.ReadChildValue(data, []string{"mapSplat", "*"}) + test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res)) + }) -func TestReadMap_prefixSplat(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_prefixSplat", func(t *testing.T) { + var data = test.ParseData(` --- mapSplat: item1: things item2: whatever otherThing: cat `) - res, _ := readMap(data, "mapSplat", []string{"item*"}) - test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res)) -} + res, _ := subject.ReadChildValue(data, []string{"mapSplat", "item*"}) + test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res)) + }) -func TestReadMap_deep_splat(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_deep_splat", func(t *testing.T) { + var data = test.ParseData(` --- mapSplatDeep: item1: @@ -60,62 +65,62 @@ mapSplatDeep: cats: apples `) - res, _ := readMap(data, "mapSplatDeep", []string{"*", "cats"}) - result := res.([]interface{}) - var actual = []string{result[0].(string), result[1].(string)} - sort.Strings(actual) - test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual)) -} + res, _ := subject.ReadChildValue(data, []string{"mapSplatDeep", "*", "cats"}) + result := res.([]interface{}) + var actual = []string{result[0].(string), result[1].(string)} + sort.Strings(actual) + test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual)) + }) -func TestReadMap_key_doesnt_exist(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_key_doesnt_exist", func(t *testing.T) { + var data = test.ParseData(` --- b: c: 2 `) - got, _ := readMap(data, "b.x.f", []string{"c"}) - test.AssertResult(t, nil, got) -} + got, _ := subject.ReadChildValue(data, []string{"b", "x", "f", "c"}) + test.AssertResult(t, nil, got) + }) -func TestReadMap_recurse_against_string(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_recurse_against_string", func(t *testing.T) { + var data = test.ParseData(` --- a: cat `) - got, _ := readMap(data, "a", []string{"b"}) - test.AssertResult(t, nil, got) -} + got, _ := subject.ReadChildValue(data, []string{"a", "b"}) + test.AssertResult(t, nil, got) + }) -func TestReadMap_with_array(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_with_array", func(t *testing.T) { + var data = test.ParseData(` --- b: d: - 3 - 4 `) - got, _ := readMap(data, "b", []string{"d", "1"}) - test.AssertResult(t, 4, got) -} + got, _ := subject.ReadChildValue(data, []string{"b", "d", "1"}) + test.AssertResult(t, 4, got) + }) -func TestReadMap_with_array_and_bad_index(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_with_array_and_bad_index", func(t *testing.T) { + var data = test.ParseData(` --- b: d: - 3 - 4 `) - _, err := readMap(data, "b", []string{"d", "x"}) - if err == nil { - t.Fatal("Expected error due to invalid path") - } - expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` - test.AssertResult(t, expectedOutput, err.Error()) -} + _, err := subject.ReadChildValue(data, []string{"b", "d", "x"}) + if err == nil { + t.Fatal("Expected error due to invalid path") + } + expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` + test.AssertResult(t, expectedOutput, err.Error()) + }) -func TestReadMap_with_mapsplat_array_and_bad_index(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_with_mapsplat_array_and_bad_index", func(t *testing.T) { + var data = test.ParseData(` --- b: d: @@ -126,16 +131,16 @@ b: - 1 - 2 `) - _, err := readMap(data, "b", []string{"d", "*", "x"}) - if err == nil { - t.Fatal("Expected error due to invalid path") - } - expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` - test.AssertResult(t, expectedOutput, err.Error()) -} + _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "x"}) + if err == nil { + t.Fatal("Expected error due to invalid path") + } + expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` + test.AssertResult(t, expectedOutput, err.Error()) + }) -func TestReadMap_with_arraysplat_map_array_and_bad_index(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_with_arraysplat_map_array_and_bad_index", func(t *testing.T) { + var data = test.ParseData(` --- b: d: @@ -146,40 +151,40 @@ b: - sam - bo `) - _, err := readMap(data, "b", []string{"d", "*", "names", "x"}) - if err == nil { - t.Fatal("Expected error due to invalid path") - } - expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` - test.AssertResult(t, expectedOutput, err.Error()) -} + _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "names", "x"}) + if err == nil { + t.Fatal("Expected error due to invalid path") + } + expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` + test.AssertResult(t, expectedOutput, err.Error()) + }) -func TestReadMap_with_array_out_of_bounds(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_with_array_out_of_bounds", func(t *testing.T) { + var data = test.ParseData(` --- b: d: - 3 - 4 `) - got, _ := readMap(data, "b", []string{"d", "3"}) - test.AssertResult(t, nil, got) -} + got, _ := subject.ReadChildValue(data, []string{"b", "d", "3"}) + test.AssertResult(t, nil, got) + }) -func TestReadMap_with_array_out_of_bounds_by_1(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_with_array_out_of_bounds_by_1", func(t *testing.T) { + var data = test.ParseData(` --- b: d: - 3 - 4 `) - got, _ := readMap(data, "b", []string{"d", "2"}) - test.AssertResult(t, nil, got) -} + got, _ := subject.ReadChildValue(data, []string{"b", "d", "2"}) + test.AssertResult(t, nil, got) + }) -func TestReadMap_with_array_splat(t *testing.T) { - var data = test.ParseData(` + t.Run("TestReadMap_with_array_splat", func(t *testing.T) { + var data = test.ParseData(` e: - name: Fred @@ -188,213 +193,204 @@ e: name: Sam thing: dog `) - got, _ := readMap(data, "e", []string{"*", "name"}) - test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got)) -} + got, _ := subject.ReadChildValue(data, []string{"e", "*", "name"}) + test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got)) + }) -func TestWrite_really_simple(t *testing.T) { - var data = test.ParseData(` - b: 2 + t.Run("TestWrite_really_simple", func(t *testing.T) { + var data = test.ParseData(` +b: 2 `) - updated := writeMap(data, []string{"b"}, "4") - test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) -} + updated := subject.UpdatedChildValue(data, []string{"b"}, "4") + test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) + }) -func TestWrite_simple(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWrite_simple", func(t *testing.T) { + var data = test.ParseData(` b: c: 2 `) - updated := writeMap(data, []string{"b", "c"}, "4") - test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated)) -} + updated := subject.UpdatedChildValue(data, []string{"b", "c"}, "4") + test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated)) + }) -func TestWrite_new(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWrite_new", func(t *testing.T) { + var data = test.ParseData(` b: c: 2 `) - updated := writeMap(data, []string{"b", "d"}, "4") - test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated)) -} + updated := subject.UpdatedChildValue(data, []string{"b", "d"}, "4") + test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated)) + }) -func TestWrite_new_deep(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWrite_new_deep", func(t *testing.T) { + var data = test.ParseData(` b: c: 2 `) - updated := writeMap(data, []string{"b", "d", "f"}, "4") - test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated)) -} + updated := subject.UpdatedChildValue(data, []string{"b", "d", "f"}, "4") + test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated)) + }) -func TestWrite_array(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWrite_array", func(t *testing.T) { + var data = test.ParseData(` b: - aa `) - updated := writeMap(data, []string{"b", "0"}, "bb") + updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "bb") - test.AssertResult(t, "[{b [bb]}]", fmt.Sprintf("%v", updated)) -} + test.AssertResult(t, "[{b [bb]}]", fmt.Sprintf("%v", updated)) + }) -func TestWrite_new_array(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWrite_new_array", func(t *testing.T) { + var data = test.ParseData(` b: c: 2 `) - updated := writeMap(data, []string{"b", "0"}, "4") - test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated)) -} + updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "4") + test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated)) + }) -func TestWrite_new_array_deep(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWrite_new_array_deep", func(t *testing.T) { + var data = test.ParseData(` a: apple `) - var expected = `a: apple -b: -- c: "4"` + updated := subject.UpdatedChildValue(data, []string{"b", "+", "c"}, "4") + test.AssertResult(t, "[{a apple} {b [[{c 4}]]}]", fmt.Sprintf("%v", updated)) + }) - updated := writeMap(data, []string{"b", "+", "c"}, "4") - got, _ := YamlToString(updated, true) - test.AssertResult(t, expected, got) -} - -func TestWrite_new_map_array_deep(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWrite_new_map_array_deep", func(t *testing.T) { + var data = test.ParseData(` b: c: 2 `) - var expected = `b: - c: 2 - d: - - "4"` - updated := writeMap(data, []string{"b", "d", "+"}, "4") - got, _ := YamlToString(updated, true) - test.AssertResult(t, expected, got) -} + updated := subject.UpdatedChildValue(data, []string{"b", "d", "+"}, "4") + test.AssertResult(t, "[{b [{c 2} {d [4]}]}]", fmt.Sprintf("%v", updated)) + }) -func TestWrite_add_to_array(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWrite_add_to_array", func(t *testing.T) { + var data = test.ParseData(` b: - aa `) - var expected = `b: -- aa -- bb` + updated := subject.UpdatedChildValue(data, []string{"b", "1"}, "bb") + test.AssertResult(t, "[{b [aa bb]}]", fmt.Sprintf("%v", updated)) + }) - updated := writeMap(data, []string{"b", "1"}, "bb") - got, _ := YamlToString(updated, true) - test.AssertResult(t, expected, got) -} - -func TestWrite_with_no_tail(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWrite_with_no_tail", func(t *testing.T) { + var data = test.ParseData(` b: c: 2 `) - updated := writeMap(data, []string{"b"}, "4") + updated := subject.UpdatedChildValue(data, []string{"b"}, "4") - test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) -} + test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated)) + }) -func TestWriteMap_no_paths(t *testing.T) { - var data = test.ParseData(` + t.Run("TestWriteMap_no_paths", func(t *testing.T) { + var data = test.ParseData(` b: 5 `) + var new = test.ParseData(` +c: 4 +`) + result := subject.UpdatedChildValue(data, []string{}, new) + test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result)) + }) - result := writeMap(data, []string{}, 4) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -} + t.Run("TestWriteArray_no_paths", func(t *testing.T) { + var data = make([]interface{}, 1) + data[0] = "mike" + var new = test.ParseData(` +c: 4 +`) + result := subject.UpdatedChildValue(data, []string{}, new) + test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result)) + }) -func TestWriteArray_no_paths(t *testing.T) { - var data = make([]interface{}, 1) - data[0] = "mike" - result := writeArray(data, []string{}, 4) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -} - -func TestDelete_MapItem(t *testing.T) { - var data = test.ParseData(` + t.Run("TestDelete_MapItem", func(t *testing.T) { + var data = test.ParseData(` a: 123 b: 456 `) - var expected = test.ParseData(` + var expected = test.ParseData(` b: 456 `) - result, _ := deleteMap(data, []string{"a"}) - test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) -} + result, _ := subject.DeleteChildValue(data, []string{"a"}) + test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) + }) -// Ensure deleting an index into a string does nothing -func TestDelete_index_to_string(t *testing.T) { - var data = test.ParseData(` + // Ensure deleting an index into a string does nothing + t.Run("TestDelete_index_to_string", func(t *testing.T) { + var data = test.ParseData(` a: mystring `) - result, _ := deleteMap(data, []string{"a", "0"}) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -} + result, _ := subject.DeleteChildValue(data, []string{"a", "0"}) + test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) + }) -func TestDelete_list_index(t *testing.T) { - var data = test.ParseData(` + t.Run("TestDelete_list_index", func(t *testing.T) { + var data = test.ParseData(` a: [3, 4] `) - var expected = test.ParseData(` + var expected = test.ParseData(` a: [3] `) - result, _ := deleteMap(data, []string{"a", "1"}) - test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) -} + result, _ := subject.DeleteChildValue(data, []string{"a", "1"}) + test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) + }) -func TestDelete_list_index_beyond_bounds(t *testing.T) { - var data = test.ParseData(` + t.Run("TestDelete_list_index_beyond_bounds", func(t *testing.T) { + var data = test.ParseData(` a: [3, 4] `) - result, _ := deleteMap(data, []string{"a", "5"}) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -} + result, _ := subject.DeleteChildValue(data, []string{"a", "5"}) + test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) + }) -func TestDelete_list_index_out_of_bounds_by_1(t *testing.T) { - var data = test.ParseData(` + t.Run("TestDelete_list_index_out_of_bounds_by_1", func(t *testing.T) { + var data = test.ParseData(` a: [3, 4] `) - result, _ := deleteMap(data, []string{"a", "2"}) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -} + result, _ := subject.DeleteChildValue(data, []string{"a", "2"}) + test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) + }) -func TestDelete_no_paths(t *testing.T) { - var data = test.ParseData(` + t.Run("TestDelete_no_paths", func(t *testing.T) { + var data = test.ParseData(` a: [3, 4] b: - name: test `) - result, _ := deleteMap(data, []string{}) - test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) -} + result, _ := subject.DeleteChildValue(data, []string{}) + test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) + }) -func TestDelete_array_map_item(t *testing.T) { - var data = test.ParseData(` + t.Run("TestDelete_array_map_item", func(t *testing.T) { + var data = test.ParseData(` b: - name: fred value: blah - name: john value: test `) - var expected = test.ParseData(` + var expected = test.ParseData(` b: - value: blah - name: john value: test `) - result, _ := deleteMap(data, []string{"b", "0", "name"}) - test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) -} + result, _ := subject.DeleteChildValue(data, []string{"b", "0", "name"}) + test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) + }) +} \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index fe3a986..eb4f350 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -5,24 +5,38 @@ import ( logging "gopkg.in/op/go-logging.v1" ) -var log = logging.MustGetLogger("yq") - -func SetLogger(l *logging.Logger) { - log = l +type YqLib interface { + ReadPath(dataBucket interface{}, path string) (interface{}, error) + WritePath(dataBucket interface{}, path string, value interface{}) (interface{}) + PrefixPath(dataBucket interface{}, prefix string) (interface{}) + DeletePath(dataBucket interface{}, path string) (interface{}, error) + Merge(dst interface{}, src interface{}, overwrite bool, append bool) error } -func ReadPath(dataBucket interface{}, path string) (interface{}, error) { - var paths = ParsePath(path) - return Recurse(dataBucket, paths[0], paths[1:]) +type lib struct { + navigator DataNavigator + parser PathParser } -func WritePath(dataBucket interface{}, path string, value interface{}) (interface{}) { - var paths = ParsePath(path) - return UpdatedChildValue(dataBucket, paths, value) +func NewYqLib(l *logging.Logger) YqLib { + return &lib { + navigator: NewDataNavigator(l), + parser: NewPathParser(), + } } -func PrefixPath(dataBucket interface{}, prefix string) (interface{}) { - var paths = ParsePath(prefix) +func (l *lib) ReadPath(dataBucket interface{}, path string) (interface{}, error) { + var paths = l.parser.ParsePath(path) + return l.navigator.ReadChildValue(dataBucket, paths) +} + +func (l *lib) WritePath(dataBucket interface{}, path string, value interface{}) (interface{}) { + var paths = l.parser.ParsePath(path) + return l.navigator.UpdatedChildValue(dataBucket, paths, value) +} + +func (l *lib) PrefixPath(dataBucket interface{}, prefix string) (interface{}) { + var paths = l.parser.ParsePath(prefix) // Inverse order for i := len(paths)/2 - 1; i >= 0; i-- { @@ -33,18 +47,18 @@ func PrefixPath(dataBucket interface{}, prefix string) (interface{}) { var mapDataBucket = dataBucket for _, key := range paths { singlePath := []string{key} - mapDataBucket = UpdatedChildValue(nil, singlePath, mapDataBucket) + mapDataBucket = l.navigator.UpdatedChildValue(nil, singlePath, mapDataBucket) } return mapDataBucket } -func DeletePath(dataBucket interface{}, path string) (interface{}, error) { - var paths = ParsePath(path) - return DeleteChildValue(dataBucket, paths) +func (l *lib) DeletePath(dataBucket interface{}, path string) (interface{}, error) { + var paths = l.parser.ParsePath(path) + return l.navigator.DeleteChildValue(dataBucket, paths) } -func Merge(dst interface{}, src interface{}, overwrite bool, append bool) error { +func (l *lib) Merge(dst interface{}, src interface{}, overwrite bool, append bool) error { if overwrite { return mergo.Merge(dst, src, mergo.WithOverride) } else if append { diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go index f0511f1..6eba4fc 100644 --- a/pkg/yqlib/lib_test.go +++ b/pkg/yqlib/lib_test.go @@ -4,145 +4,153 @@ import ( "fmt" "testing" "github.com/mikefarah/yq/test" + logging "gopkg.in/op/go-logging.v1" ) -func TestReadPath(t *testing.T) { - var data = test.ParseData(` +func TestLib(t *testing.T) { + + var log = logging.MustGetLogger("yq") + subject := NewYqLib(log) + + t.Run("TestReadPath", func(t *testing.T) { + var data = test.ParseData(` --- b: 2: c `) - - got, _ := ReadPath(data, "b.2") - test.AssertResult(t, `c`, got) -} - -func TestReadPath_WithError(t *testing.T) { - var data = test.ParseData(` + + got, _ := subject.ReadPath(data, "b.2") + test.AssertResult(t, `c`, got) + }) + + t.Run("TestReadPath_WithError", func(t *testing.T) { + var data = test.ParseData(` --- b: - c `) - - _, err := ReadPath(data, "b.[a]") - if err == nil { - t.Fatal("Expected error due to invalid path") - } -} - -func TestWritePath(t *testing.T) { - var data = test.ParseData(` + + _, err := subject.ReadPath(data, "b.[a]") + if err == nil { + t.Fatal("Expected error due to invalid path") + } + }) + + t.Run("TestWritePath", func(t *testing.T) { + var data = test.ParseData(` --- b: 2: c `) - - got := WritePath(data, "b.3", "a") - test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got)) -} - -func TestPrefixPath(t *testing.T) { - var data = test.ParseData(` + + got := subject.WritePath(data, "b.3", "a") + test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got)) + }) + + t.Run("TestPrefixPath", func(t *testing.T) { + var data = test.ParseData(` --- b: 2: c `) - - got := PrefixPath(data, "d") - test.AssertResult(t, `[{d [{b [{2 c}]}]}]`, fmt.Sprintf("%v", got)) -} - -func TestDeletePath(t *testing.T) { - var data = test.ParseData(` + + got := subject.PrefixPath(data, "d") + test.AssertResult(t, `[{d [{b [{2 c}]}]}]`, fmt.Sprintf("%v", got)) + }) + + t.Run("TestDeletePath", func(t *testing.T) { + var data = test.ParseData(` --- b: 2: c 3: a `) - - got, _ := DeletePath(data, "b.2") - test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got)) -} - -func TestDeletePath_WithError(t *testing.T) { - var data = test.ParseData(` + + got, _ := subject.DeletePath(data, "b.2") + test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got)) + }) + + t.Run("TestDeletePath_WithError", func(t *testing.T) { + var data = test.ParseData(` --- b: - c `) - - _, err := DeletePath(data, "b.[a]") - if err == nil { - t.Fatal("Expected error due to invalid path") - } -} - -func TestMerge(t *testing.T) { - var dst = test.ParseData(` + + _, err := subject.DeletePath(data, "b.[a]") + if err == nil { + t.Fatal("Expected error due to invalid path") + } + }) + + t.Run("TestMerge", func(t *testing.T) { + var dst = test.ParseData(` --- a: b c: d `) - var src = test.ParseData(` + var src = test.ParseData(` --- a: 1 b: 2 `) - - var mergedData = make(map[interface{}]interface{}) - mergedData["root"] = dst - var mapDataBucket = make(map[interface{}]interface{}) - mapDataBucket["root"] = src - - Merge(&mergedData, mapDataBucket, false, false) - test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"])) -} - -func TestMerge_WithOverwrite(t *testing.T) { - var dst = test.ParseData(` + + var mergedData = make(map[interface{}]interface{}) + mergedData["root"] = dst + var mapDataBucket = make(map[interface{}]interface{}) + mapDataBucket["root"] = src + + subject.Merge(&mergedData, mapDataBucket, false, false) + test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"])) + }) + + t.Run("TestMerge_WithOverwrite", func(t *testing.T) { + var dst = test.ParseData(` --- a: b c: d `) - var src = test.ParseData(` + var src = test.ParseData(` --- a: 1 b: 2 `) - - var mergedData = make(map[interface{}]interface{}) - mergedData["root"] = dst - var mapDataBucket = make(map[interface{}]interface{}) - mapDataBucket["root"] = src - - Merge(&mergedData, mapDataBucket, true, false) - test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) -} - -func TestMerge_WithAppend(t *testing.T) { - var dst = test.ParseData(` + + var mergedData = make(map[interface{}]interface{}) + mergedData["root"] = dst + var mapDataBucket = make(map[interface{}]interface{}) + mapDataBucket["root"] = src + + subject.Merge(&mergedData, mapDataBucket, true, false) + test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) + }) + + t.Run("TestMerge_WithAppend", func(t *testing.T) { + var dst = test.ParseData(` --- a: b c: d `) - var src = test.ParseData(` + var src = test.ParseData(` --- a: 1 b: 2 `) + + var mergedData = make(map[interface{}]interface{}) + mergedData["root"] = dst + var mapDataBucket = make(map[interface{}]interface{}) + mapDataBucket["root"] = src + + subject.Merge(&mergedData, mapDataBucket, false, true) + test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) + }) + + t.Run("TestMerge_WithError", func(t *testing.T) { + err := subject.Merge(nil, nil, false, false) + if err == nil { + t.Fatal("Expected error due to nil") + } + }) - var mergedData = make(map[interface{}]interface{}) - mergedData["root"] = dst - var mapDataBucket = make(map[interface{}]interface{}) - mapDataBucket["root"] = src - - Merge(&mergedData, mapDataBucket, false, true) - test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) -} - -func TestMerge_WithError(t *testing.T) { - err := Merge(nil, nil, false, false) - if err == nil { - t.Fatal("Expected error due to nil") - } } \ No newline at end of file diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index a416b72..a1628ec 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -1,35 +1,45 @@ package yqlib -func ParsePath(path string) []string { - return parsePathAccum([]string{}, path) +type PathParser interface { + ParsePath(path string) []string } -func parsePathAccum(paths []string, remaining string) []string { - head, tail := nextYamlPath(remaining) +type parser struct {} + +func NewPathParser() PathParser { + return &parser{} +} + +func (p *parser) ParsePath(path string) []string { + return p.parsePathAccum([]string{}, path) +} + +func (p *parser) parsePathAccum(paths []string, remaining string) []string { + head, tail := p.nextYamlPath(remaining) if tail == "" { return append(paths, head) } - return parsePathAccum(append(paths, head), tail) + return p.parsePathAccum(append(paths, head), tail) } -func nextYamlPath(path string) (pathElement string, remaining string) { +func (p *parser) nextYamlPath(path string) (pathElement string, remaining string) { switch path[0] { case '[': // e.g [0].blah.cat -> we need to return "0" and "blah.cat" - return search(path[1:], []uint8{']'}, true) + return p.search(path[1:], []uint8{']'}, true) case '"': // e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat" - return search(path[1:], []uint8{'"'}, true) + return p.search(path[1:], []uint8{'"'}, true) default: // e.g "a.blah.cat" -> return "a" and "blah.cat" - return search(path[0:], []uint8{'.', '['}, false) + return p.search(path[0:], []uint8{'.', '['}, false) } } -func search(path string, matchingChars []uint8, skipNext bool) (pathElement string, remaining string) { +func (p *parser) search(path string, matchingChars []uint8, skipNext bool) (pathElement string, remaining string) { for i := 0; i < len(path); i++ { var char = path[i] - if contains(matchingChars, char) { + if p.contains(matchingChars, char) { var remainingStart = i + 1 if skipNext { remainingStart = remainingStart + 1 @@ -45,7 +55,7 @@ func search(path string, matchingChars []uint8, skipNext bool) (pathElement stri return path, "" } -func contains(matchingChars []uint8, candidate uint8) bool { +func (p *parser) contains(matchingChars []uint8, candidate uint8) bool { for _, a := range matchingChars { if a == candidate { return true diff --git a/pkg/yqlib/path_parser_test.go b/pkg/yqlib/path_parser_test.go index 7680aa8..000d214 100644 --- a/pkg/yqlib/path_parser_test.go +++ b/pkg/yqlib/path_parser_test.go @@ -12,33 +12,17 @@ var parsePathsTests = []struct { {"a.b", []string{"a", "b"}}, {"a.b[0]", []string{"a", "b", "0"}}, {"a.b.d[+]", []string{"a", "b", "d", "+"}}, + {"a", []string{"a"}}, + {"a.b.c", []string{"a", "b", "c"}}, + {"\"a.b\".c", []string{"a.b", "c"}}, + {"a.\"b.c\".d", []string{"a", "b.c", "d"}}, + {"[1].a.d", []string{"1", "a", "d"}}, + {"a[0].c", []string{"a", "0", "c"}}, + {"[0]", []string{"0"}}, } func TestParsePath(t *testing.T) { for _, tt := range parsePathsTests { - test.AssertResultComplex(t, tt.expectedPaths, ParsePath(tt.path)) + test.AssertResultComplex(t, tt.expectedPaths, NewPathParser().ParsePath(tt.path)) } -} - -var nextYamlPathTests = []struct { - path string - expectedElement string - expectedRemaining string -}{ - {"a.b", "a", "b"}, - {"a", "a", ""}, - {"a.b.c", "a", "b.c"}, - {"\"a.b\".c", "a.b", "c"}, - {"a.\"b.c\".d", "a", "\"b.c\".d"}, - {"[1].a.d", "1", "a.d"}, - {"a[0].c", "a", "[0].c"}, - {"[0]", "0", ""}, -} - -func TestNextYamlPath(t *testing.T) { - for _, tt := range nextYamlPathTests { - var element, remaining = nextYamlPath(tt.path) - test.AssertResultWithContext(t, tt.expectedElement, element, tt) - test.AssertResultWithContext(t, tt.expectedRemaining, remaining, tt) - } -} +} \ No newline at end of file diff --git a/yq.go b/yq.go index c065edc..89623ec 100644 --- a/yq.go +++ b/yq.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "github.com/mikefarah/yq/pkg/yqlib" + "github.com/mikefarah/yq/pkg/marshal" errors "github.com/pkg/errors" @@ -29,6 +30,9 @@ var verbose = false var version = false var docIndex = "0" var log = logging.MustGetLogger("yq") +var lib = yqlib.NewYqLib(log) +var jsonConverter = marshal.NewJsonConverter() +var yamlConverter = marshal.NewYamlConverter() func main() { cmd := newCommandCLI() @@ -39,7 +43,6 @@ func main() { } func newCommandCLI() *cobra.Command { - yqlib.SetLogger(log) yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{}) var rootCmd = &cobra.Command{ Use: "yq", @@ -277,7 +280,7 @@ func readProperty(cmd *cobra.Command, args []string) error { log.Debugf("%v", dataBucket) mappedDocs = append(mappedDocs, dataBucket) } else { - mappedDoc, errorParsing := yqlib.ReadPath(dataBucket, path) + mappedDoc, errorParsing := lib.ReadPath(dataBucket, path) log.Debugf("%v", mappedDoc) if errorParsing != nil { return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) @@ -338,7 +341,7 @@ func newYaml(args []string) (interface{}, error) { path := entry.Key.(string) value := entry.Value log.Debugf("setting %v to %v", path, value) - dataBucket = yqlib.WritePath(dataBucket, path, value) + dataBucket = lib.WritePath(dataBucket, path, value) } return dataBucket, nil @@ -414,7 +417,7 @@ func writeProperty(cmd *cobra.Command, args []string) error { path := entry.Key.(string) value := entry.Value log.Debugf("setting %v to %v", path, value) - dataBucket = yqlib.WritePath(dataBucket, path, value) + dataBucket = lib.WritePath(dataBucket, path, value) } } return dataBucket, nil @@ -437,7 +440,7 @@ func prefixProperty(cmd *cobra.Command, args []string) error { if updateAll || currentIndex == docIndexInt { log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex) - var mapDataBucket = yqlib.PrefixPath(dataBucket, prefixPath) + var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath) return mapDataBucket, nil } return dataBucket, nil @@ -491,7 +494,7 @@ func deleteProperty(cmd *cobra.Command, args []string) error { var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { if updateAll || currentIndex == docIndexInt { log.Debugf("Deleting path in doc %v", currentIndex) - return yqlib.DeletePath(dataBucket, deletePath) + return lib.DeletePath(dataBucket, deletePath) } return dataBucket, nil } @@ -518,7 +521,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error { // map var mapDataBucket = make(map[interface{}]interface{}) mapDataBucket["root"] = dataBucket - if err := yqlib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { + if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { return nil, err } for _, f := range filesToMerge { @@ -530,7 +533,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error { return nil, err } mapDataBucket["root"] = fileToMerge - if err := yqlib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { + if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil { return nil, err } } @@ -580,9 +583,9 @@ func parseValue(argument string) interface{} { func toString(context interface{}) (string, error) { if outputToJSON { - return yqlib.JsonToString(context) + return jsonConverter.JsonToString(context) } - return yqlib.YamlToString(context, trimOutput) + return yamlConverter.YamlToString(context, trimOutput) } func safelyRenameFile(from string, to string) { diff --git a/yq_test.go b/yq_test.go index 21d816b..2ae665f 100644 --- a/yq_test.go +++ b/yq_test.go @@ -5,7 +5,7 @@ import ( "runtime" "testing" "github.com/mikefarah/yq/test" - "github.com/mikefarah/yq/pkg/yqlib" + "github.com/mikefarah/yq/pkg/marshal" ) var parseValueTests = []struct { @@ -30,7 +30,7 @@ func TestMultilineString(t *testing.T) { testString := ` abcd efg` - formattedResult, _ := yqlib.YamlToString(testString, false) + formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false) test.AssertResult(t, testString, formattedResult) } @@ -57,7 +57,7 @@ func TestNewYaml_WithScript(t *testing.T) { e: - name: Mike Farah` result, _ := newYaml([]string{""}) - actualResult, _ := yqlib.YamlToString(result, true) + actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true) test.AssertResult(t, expectedResult, actualResult) }