Split marshal package from yqlib, implement interfaces

This commit is contained in:
Conor Nosal 2019-12-01 14:44:44 -05:00 committed by Mike Farah
parent 26a09e6ec0
commit 4b3fbb878f
11 changed files with 553 additions and 491 deletions

View File

@ -1,4 +1,4 @@
package yqlib package marshal
import ( import (
"encoding/json" "encoding/json"
@ -8,21 +8,31 @@ import (
yaml "github.com/mikefarah/yaml/v2" yaml "github.com/mikefarah/yaml/v2"
) )
func JsonToString(context interface{}) (string, error) { type JsonConverter interface {
out, err := json.Marshal(toJSON(context)) 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 { if err != nil {
return "", fmt.Errorf("error printing yaml as json: %v", err) return "", fmt.Errorf("error printing yaml as json: %v", err)
} }
return string(out), nil return string(out), nil
} }
func toJSON(context interface{}) interface{} { func (j *jsonConverter) toJSON(context interface{}) interface{} {
switch context := context.(type) { switch context := context.(type) {
case []interface{}: case []interface{}:
oldArray := context oldArray := context
newArray := make([]interface{}, len(oldArray)) newArray := make([]interface{}, len(oldArray))
for index, value := range oldArray { for index, value := range oldArray {
newArray[index] = toJSON(value) newArray[index] = j.toJSON(value)
} }
return newArray return newArray
case yaml.MapSlice: case yaml.MapSlice:
@ -30,11 +40,11 @@ func toJSON(context interface{}) interface{} {
newMap := make(map[string]interface{}) newMap := make(map[string]interface{})
for _, entry := range oldMap { for _, entry := range oldMap {
if str, ok := entry.Key.(string); ok { 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 { } 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 { } 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 return newMap

View File

@ -1,4 +1,4 @@
package yqlib package marshal
import ( import (
"testing" "testing"
@ -11,7 +11,7 @@ func TestJsonToString(t *testing.T) {
b: b:
c: 2 c: 2
`) `)
got, _ := JsonToString(data) got, _ := NewJsonConverter().JsonToString(data)
test.AssertResult(t, "{\"b\":{\"c\":2}}", got) test.AssertResult(t, "{\"b\":{\"c\":2}}", got)
} }
@ -21,7 +21,7 @@ func TestJsonToString_withIntKey(t *testing.T) {
b: b:
2: c 2: c
`) `)
got, _ := JsonToString(data) got, _ := NewJsonConverter().JsonToString(data)
test.AssertResult(t, `{"b":{"2":"c"}}`, got) test.AssertResult(t, `{"b":{"2":"c"}}`, got)
} }
@ -31,7 +31,7 @@ func TestJsonToString_withBoolKey(t *testing.T) {
b: b:
false: c false: c
`) `)
got, _ := JsonToString(data) got, _ := NewJsonConverter().JsonToString(data)
test.AssertResult(t, `{"b":{"false":"c"}}`, got) test.AssertResult(t, `{"b":{"false":"c"}}`, got)
} }
@ -42,6 +42,6 @@ b:
- item: one - item: one
- item: two - item: two
`) `)
got, _ := JsonToString(data) got, _ := NewJsonConverter().JsonToString(data)
test.AssertResult(t, "{\"b\":[{\"item\":\"one\"},{\"item\":\"two\"}]}", got) test.AssertResult(t, "{\"b\":[{\"item\":\"one\"},{\"item\":\"two\"}]}", got)
} }

View File

@ -1,4 +1,4 @@
package yqlib package marshal
import ( import (
yaml "github.com/mikefarah/yaml/v2" yaml "github.com/mikefarah/yaml/v2"
@ -6,16 +6,26 @@ import (
"strings" "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) { switch context := context.(type) {
case string: case string:
return context, nil return context, nil
default: 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) out, err := yaml.Marshal(context)
if err != nil { if err != nil {

View File

@ -7,9 +7,96 @@ import (
"strings" "strings"
yaml "github.com/mikefarah/yaml/v2" 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 actualString = fmt.Sprintf("%v", actual)
var prefixMatch = strings.TrimSuffix(key, "*") var prefixMatch = strings.TrimSuffix(key, "*")
if prefixMatch != key { if prefixMatch != key {
@ -18,18 +105,18 @@ func matchesKey(key string, actual interface{}) bool {
return actualString == key 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) var matches = make([]*yaml.MapItem, 0)
for idx := range context { for idx := range context {
var entry = &context[idx] var entry = &context[idx]
if matchesKey(key, entry.Key) { if n.matchesKey(key, entry.Key) {
matches = append(matches, entry) matches = append(matches, entry)
} }
} }
return matches return matches
} }
func getMapSlice(context interface{}) yaml.MapSlice { func (n *navigator) getMapSlice(context interface{}) yaml.MapSlice {
var mapSlice yaml.MapSlice var mapSlice yaml.MapSlice
switch context := context.(type) { switch context := context.(type) {
case yaml.MapSlice: case yaml.MapSlice:
@ -40,7 +127,7 @@ func getMapSlice(context interface{}) yaml.MapSlice {
return 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) { switch context := context.(type) {
case []interface{}: case []interface{}:
array = context array = context
@ -52,68 +139,46 @@ func getArray(context interface{}) (array []interface{}, ok bool) {
return return
} }
func writeMap(context interface{}, paths []string, value interface{}) interface{} { func (n *navigator) writeMap(context interface{}, paths []string, value interface{}) interface{} {
log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value) 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 { if len(paths) == 0 {
return context return context
} }
children := entriesInSlice(mapSlice, paths[0]) children := n.entriesInSlice(mapSlice, paths[0])
if len(children) == 0 && 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 return context
} }
if len(children) == 0 { if len(children) == 0 {
newChild := yaml.MapItem{Key: paths[0]} newChild := yaml.MapItem{Key: paths[0]}
mapSlice = append(mapSlice, newChild) mapSlice = append(mapSlice, newChild)
children = entriesInSlice(mapSlice, paths[0]) children = n.entriesInSlice(mapSlice, paths[0])
log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice) n.log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice)
} }
remainingPaths := paths[1:] remainingPaths := paths[1:]
for _, child := range children { 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 return mapSlice
} }
func UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} { func (n *navigator) writeArray(context interface{}, paths []string, value interface{}) []interface{} {
if len(remainingPaths) == 0 { n.log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value)
return value array, _ := n.getArray(context)
}
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)
if len(paths) == 0 { if len(paths) == 0 {
return array return array
} }
log.Debugf("\tarray %v\n", array) n.log.Debugf("\tarray %v\n", array)
rawIndex := paths[0] rawIndex := paths[0]
remainingPaths := paths[1:] remainingPaths := paths[1:]
@ -123,7 +188,7 @@ func writeArray(context interface{}, paths []string, value interface{}) []interf
index = int64(len(array)) index = int64(len(array))
} else if rawIndex == "*" { } else if rawIndex == "*" {
for index, oldChild := range array { for index, oldChild := range array {
array[index] = UpdatedChildValue(oldChild, remainingPaths, value) array[index] = n.UpdatedChildValue(oldChild, remainingPaths, value)
} }
return array return array
} else { } else {
@ -137,31 +202,31 @@ func writeArray(context interface{}, paths []string, value interface{}) []interf
} }
currentChild := array[index] currentChild := array[index]
log.Debugf("\tcurrentChild %v\n", currentChild) n.log.Debugf("\tcurrentChild %v\n", currentChild)
array[index] = UpdatedChildValue(currentChild, remainingPaths, value) array[index] = n.UpdatedChildValue(currentChild, remainingPaths, value)
log.Debugf("\tReturning array %v\n", array) n.log.Debugf("\tReturning array %v\n", array)
return array return array
} }
func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) { func (n *navigator) readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) {
log.Debugf("readingMap %v with key %v\n", context, head) n.log.Debugf("readingMap %v with key %v\n", context, head)
if 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 { if len(entries) == 1 {
return calculateValue(entries[0].Value, tail) return n.calculateValue(entries[0].Value, tail)
} else if len(entries) == 0 { } else if len(entries) == 0 {
return nil, nil return nil, nil
} }
var errInIdx error var errInIdx error
values := make([]interface{}, len(entries)) values := make([]interface{}, len(entries))
for idx, entry := range entries { for idx, entry := range entries {
values[idx], errInIdx = calculateValue(entry.Value, tail) values[idx], errInIdx = n.calculateValue(entry.Value, tail)
if errInIdx != nil { 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 return nil, errInIdx
} }
@ -169,12 +234,12 @@ func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, er
return values, nil 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 newArray = make([]interface{}, len(context))
var i = 0 var i = 0
for _, entry := range context { for _, entry := range context {
if len(tail) > 0 { 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 { if err != nil {
return nil, err return nil, err
} }
@ -187,37 +252,19 @@ func readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) {
return newArray, nil return newArray, nil
} }
func Recurse(value interface{}, head string, tail []string) (interface{}, error) { func (n *navigator) readArray(array []interface{}, head int64, 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) {
if head >= int64(len(array)) { if head >= int64(len(array)) {
return nil, nil return nil, nil
} }
value := array[head] 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)) var newArray = make([]interface{}, len(array))
for index, value := range array { for index, value := range array {
val, err := calculateValue(value, tail) val, err := n.calculateValue(value, tail)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -226,17 +273,17 @@ func readArraySplat(array []interface{}, tail []string) (interface{}, error) {
return newArray, nil 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 { if len(tail) > 0 {
return Recurse(value, tail[0], tail[1:]) return n.recurse(value, tail[0], tail[1:])
} }
return value, nil return value, nil
} }
func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) { func (n *navigator) deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) {
log.Debugf("deleteMap for %v for %v\n", paths, context) n.log.Debugf("deleteMap for %v for %v\n", paths, context)
mapSlice := getMapSlice(context) mapSlice := n.getMapSlice(context)
if len(paths) == 0 { if len(paths) == 0 {
return mapSlice, nil return mapSlice, nil
@ -245,10 +292,10 @@ func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) {
var index int var index int
var child yaml.MapItem var child yaml.MapItem
for index, child = range mapSlice { for index, child = range mapSlice {
if matchesKey(paths[0], child.Key) { if n.matchesKey(paths[0], child.Key) {
log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index) n.log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index)
var badDelete error var badDelete error
mapSlice, badDelete = deleteEntryInMap(mapSlice, child, index, paths) mapSlice, badDelete = n.deleteEntryInMap(mapSlice, child, index, paths)
if badDelete != nil { if badDelete != nil {
return nil, badDelete 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:] remainingPaths := paths[1:]
var newSlice yaml.MapSlice var newSlice yaml.MapSlice
if len(remainingPaths) > 0 { if len(remainingPaths) > 0 {
newChild := yaml.MapItem{Key: child.Key} newChild := yaml.MapItem{Key: child.Key}
var errorDeleting error var errorDeleting error
newChild.Value, errorDeleting = DeleteChildValue(child.Value, remainingPaths) newChild.Value, errorDeleting = n.DeleteChildValue(child.Value, remainingPaths)
if errorDeleting != nil { if errorDeleting != nil {
return nil, errorDeleting return nil, errorDeleting
} }
@ -282,18 +329,18 @@ func deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, pat
} else { } else {
// Delete item from slice at index // Delete item from slice at index
newSlice = append(original[:index], original[index+1:]...) 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 return newSlice, nil
} }
func deleteArraySplat(array []interface{}, tail []string) (interface{}, error) { func (n *navigator) deleteArraySplat(array []interface{}, tail []string) (interface{}, error) {
log.Debugf("deleteArraySplat for %v for %v\n", tail, array) n.log.Debugf("deleteArraySplat for %v for %v\n", tail, array)
var newArray = make([]interface{}, len(array)) var newArray = make([]interface{}, len(array))
for index, value := range array { for index, value := range array {
val, err := DeleteChildValue(value, tail) val, err := n.DeleteChildValue(value, tail)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -302,8 +349,8 @@ func deleteArraySplat(array []interface{}, tail []string) (interface{}, error) {
return newArray, nil return newArray, nil
} }
func deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) { func (n *navigator) deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) {
log.Debugf("deleteArray for %v for %v\n", paths, array) n.log.Debugf("deleteArray for %v for %v\n", paths, array)
if index >= int64(len(array)) { if index >= int64(len(array)) {
return array, nil return array, nil
@ -311,9 +358,9 @@ func deleteArray(array []interface{}, paths []string, index int64) (interface{},
remainingPaths := paths[1:] remainingPaths := paths[1:]
if len(remainingPaths) > 0 { if len(remainingPaths) > 0 {
// Recurse into the array element at index // recurse into the array element at index
var errorDeleting error var errorDeleting error
array[index], errorDeleting = deleteMap(array[index], remainingPaths) array[index], errorDeleting = n.deleteMap(array[index], remainingPaths)
if errorDeleting != nil { if errorDeleting != nil {
return nil, errorDeleting return nil, errorDeleting
} }
@ -321,29 +368,9 @@ func deleteArray(array []interface{}, paths []string, index int64) (interface{},
} else { } else {
// Delete the array element at index // Delete the array element at index
array = append(array[:index], array[index+1:]...) 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 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
}

View File

@ -5,28 +5,33 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/mikefarah/yq/test" "github.com/mikefarah/yq/test"
logging "gopkg.in/op/go-logging.v1"
) )
func TestReadMap_simple(t *testing.T) { 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(` var data = test.ParseData(`
--- ---
b: b:
c: 2 c: 2
`) `)
got, _ := readMap(data, "b", []string{"c"}) got, _ := subject.ReadChildValue(data, []string{"b", "c"})
test.AssertResult(t, 2, got) test.AssertResult(t, 2, got)
} })
func TestReadMap_numberKey(t *testing.T) { t.Run("TestReadMap_numberKey", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
200: things 200: things
`) `)
got, _ := readMap(data, "200", []string{}) got, _ := subject.ReadChildValue(data, []string{"200"})
test.AssertResult(t, "things", got) test.AssertResult(t, "things", got)
} })
func TestReadMap_splat(t *testing.T) { t.Run("TestReadMap_splat", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
mapSplat: mapSplat:
@ -34,11 +39,11 @@ mapSplat:
item2: whatever item2: whatever
otherThing: cat otherThing: cat
`) `)
res, _ := readMap(data, "mapSplat", []string{"*"}) res, _ := subject.ReadChildValue(data, []string{"mapSplat", "*"})
test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res)) test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res))
} })
func TestReadMap_prefixSplat(t *testing.T) { t.Run("TestReadMap_prefixSplat", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
mapSplat: mapSplat:
@ -46,11 +51,11 @@ mapSplat:
item2: whatever item2: whatever
otherThing: cat otherThing: cat
`) `)
res, _ := readMap(data, "mapSplat", []string{"item*"}) res, _ := subject.ReadChildValue(data, []string{"mapSplat", "item*"})
test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res)) test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res))
} })
func TestReadMap_deep_splat(t *testing.T) { t.Run("TestReadMap_deep_splat", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
mapSplatDeep: mapSplatDeep:
@ -60,33 +65,33 @@ mapSplatDeep:
cats: apples cats: apples
`) `)
res, _ := readMap(data, "mapSplatDeep", []string{"*", "cats"}) res, _ := subject.ReadChildValue(data, []string{"mapSplatDeep", "*", "cats"})
result := res.([]interface{}) result := res.([]interface{})
var actual = []string{result[0].(string), result[1].(string)} var actual = []string{result[0].(string), result[1].(string)}
sort.Strings(actual) sort.Strings(actual)
test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual)) test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual))
} })
func TestReadMap_key_doesnt_exist(t *testing.T) { t.Run("TestReadMap_key_doesnt_exist", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
c: 2 c: 2
`) `)
got, _ := readMap(data, "b.x.f", []string{"c"}) got, _ := subject.ReadChildValue(data, []string{"b", "x", "f", "c"})
test.AssertResult(t, nil, got) test.AssertResult(t, nil, got)
} })
func TestReadMap_recurse_against_string(t *testing.T) { t.Run("TestReadMap_recurse_against_string", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
a: cat a: cat
`) `)
got, _ := readMap(data, "a", []string{"b"}) got, _ := subject.ReadChildValue(data, []string{"a", "b"})
test.AssertResult(t, nil, got) test.AssertResult(t, nil, got)
} })
func TestReadMap_with_array(t *testing.T) { t.Run("TestReadMap_with_array", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
@ -94,11 +99,11 @@ b:
- 3 - 3
- 4 - 4
`) `)
got, _ := readMap(data, "b", []string{"d", "1"}) got, _ := subject.ReadChildValue(data, []string{"b", "d", "1"})
test.AssertResult(t, 4, got) test.AssertResult(t, 4, got)
} })
func TestReadMap_with_array_and_bad_index(t *testing.T) { t.Run("TestReadMap_with_array_and_bad_index", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
@ -106,15 +111,15 @@ b:
- 3 - 3
- 4 - 4
`) `)
_, err := readMap(data, "b", []string{"d", "x"}) _, err := subject.ReadChildValue(data, []string{"b", "d", "x"})
if err == nil { if err == nil {
t.Fatal("Expected error due to invalid path") t.Fatal("Expected error due to invalid path")
} }
expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, err.Error()) test.AssertResult(t, expectedOutput, err.Error())
} })
func TestReadMap_with_mapsplat_array_and_bad_index(t *testing.T) { t.Run("TestReadMap_with_mapsplat_array_and_bad_index", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
@ -126,15 +131,15 @@ b:
- 1 - 1
- 2 - 2
`) `)
_, err := readMap(data, "b", []string{"d", "*", "x"}) _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "x"})
if err == nil { if err == nil {
t.Fatal("Expected error due to invalid path") t.Fatal("Expected error due to invalid path")
} }
expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, err.Error()) test.AssertResult(t, expectedOutput, err.Error())
} })
func TestReadMap_with_arraysplat_map_array_and_bad_index(t *testing.T) { t.Run("TestReadMap_with_arraysplat_map_array_and_bad_index", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
@ -146,15 +151,15 @@ b:
- sam - sam
- bo - bo
`) `)
_, err := readMap(data, "b", []string{"d", "*", "names", "x"}) _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "names", "x"})
if err == nil { if err == nil {
t.Fatal("Expected error due to invalid path") t.Fatal("Expected error due to invalid path")
} }
expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax` expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, err.Error()) test.AssertResult(t, expectedOutput, err.Error())
} })
func TestReadMap_with_array_out_of_bounds(t *testing.T) { t.Run("TestReadMap_with_array_out_of_bounds", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
@ -162,11 +167,11 @@ b:
- 3 - 3
- 4 - 4
`) `)
got, _ := readMap(data, "b", []string{"d", "3"}) got, _ := subject.ReadChildValue(data, []string{"b", "d", "3"})
test.AssertResult(t, nil, got) test.AssertResult(t, nil, got)
} })
func TestReadMap_with_array_out_of_bounds_by_1(t *testing.T) { t.Run("TestReadMap_with_array_out_of_bounds_by_1", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
@ -174,11 +179,11 @@ b:
- 3 - 3
- 4 - 4
`) `)
got, _ := readMap(data, "b", []string{"d", "2"}) got, _ := subject.ReadChildValue(data, []string{"b", "d", "2"})
test.AssertResult(t, nil, got) test.AssertResult(t, nil, got)
} })
func TestReadMap_with_array_splat(t *testing.T) { t.Run("TestReadMap_with_array_splat", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
e: e:
- -
@ -188,141 +193,131 @@ e:
name: Sam name: Sam
thing: dog thing: dog
`) `)
got, _ := readMap(data, "e", []string{"*", "name"}) got, _ := subject.ReadChildValue(data, []string{"e", "*", "name"})
test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got)) test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got))
} })
func TestWrite_really_simple(t *testing.T) { t.Run("TestWrite_really_simple", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: 2 b: 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 TestWrite_simple(t *testing.T) { t.Run("TestWrite_simple", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: b:
c: 2 c: 2
`) `)
updated := writeMap(data, []string{"b", "c"}, "4") updated := subject.UpdatedChildValue(data, []string{"b", "c"}, "4")
test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated)) test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated))
} })
func TestWrite_new(t *testing.T) { t.Run("TestWrite_new", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: b:
c: 2 c: 2
`) `)
updated := writeMap(data, []string{"b", "d"}, "4") updated := subject.UpdatedChildValue(data, []string{"b", "d"}, "4")
test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated)) test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated))
} })
func TestWrite_new_deep(t *testing.T) { t.Run("TestWrite_new_deep", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: b:
c: 2 c: 2
`) `)
updated := writeMap(data, []string{"b", "d", "f"}, "4") updated := subject.UpdatedChildValue(data, []string{"b", "d", "f"}, "4")
test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated)) test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated))
} })
func TestWrite_array(t *testing.T) { t.Run("TestWrite_array", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: b:
- aa - 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) { t.Run("TestWrite_new_array", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: b:
c: 2 c: 2
`) `)
updated := writeMap(data, []string{"b", "0"}, "4") updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "4")
test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated)) test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated))
} })
func TestWrite_new_array_deep(t *testing.T) { t.Run("TestWrite_new_array_deep", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
a: apple a: apple
`) `)
var expected = `a: apple updated := subject.UpdatedChildValue(data, []string{"b", "+", "c"}, "4")
b: test.AssertResult(t, "[{a apple} {b [[{c 4}]]}]", fmt.Sprintf("%v", updated))
- c: "4"` })
updated := writeMap(data, []string{"b", "+", "c"}, "4") t.Run("TestWrite_new_map_array_deep", func(t *testing.T) {
got, _ := YamlToString(updated, true)
test.AssertResult(t, expected, got)
}
func TestWrite_new_map_array_deep(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: b:
c: 2 c: 2
`) `)
var expected = `b:
c: 2
d:
- "4"`
updated := writeMap(data, []string{"b", "d", "+"}, "4") updated := subject.UpdatedChildValue(data, []string{"b", "d", "+"}, "4")
got, _ := YamlToString(updated, true) test.AssertResult(t, "[{b [{c 2} {d [4]}]}]", fmt.Sprintf("%v", updated))
test.AssertResult(t, expected, got) })
}
func TestWrite_add_to_array(t *testing.T) { t.Run("TestWrite_add_to_array", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: b:
- aa - aa
`) `)
var expected = `b: updated := subject.UpdatedChildValue(data, []string{"b", "1"}, "bb")
- aa test.AssertResult(t, "[{b [aa bb]}]", fmt.Sprintf("%v", updated))
- bb` })
updated := writeMap(data, []string{"b", "1"}, "bb") t.Run("TestWrite_with_no_tail", func(t *testing.T) {
got, _ := YamlToString(updated, true)
test.AssertResult(t, expected, got)
}
func TestWrite_with_no_tail(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: b:
c: 2 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) { t.Run("TestWriteMap_no_paths", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: 5 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) t.Run("TestWriteArray_no_paths", func(t *testing.T) {
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
}
func TestWriteArray_no_paths(t *testing.T) {
var data = make([]interface{}, 1) var data = make([]interface{}, 1)
data[0] = "mike" data[0] = "mike"
result := writeArray(data, []string{}, 4) var new = test.ParseData(`
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) c: 4
} `)
result := subject.UpdatedChildValue(data, []string{}, new)
test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result))
})
func TestDelete_MapItem(t *testing.T) { t.Run("TestDelete_MapItem", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
a: 123 a: 123
b: 456 b: 456
@ -331,57 +326,57 @@ b: 456
b: 456 b: 456
`) `)
result, _ := deleteMap(data, []string{"a"}) result, _ := subject.DeleteChildValue(data, []string{"a"})
test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
} })
// Ensure deleting an index into a string does nothing // Ensure deleting an index into a string does nothing
func TestDelete_index_to_string(t *testing.T) { t.Run("TestDelete_index_to_string", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
a: mystring a: mystring
`) `)
result, _ := deleteMap(data, []string{"a", "0"}) result, _ := subject.DeleteChildValue(data, []string{"a", "0"})
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
} })
func TestDelete_list_index(t *testing.T) { t.Run("TestDelete_list_index", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
a: [3, 4] a: [3, 4]
`) `)
var expected = test.ParseData(` var expected = test.ParseData(`
a: [3] a: [3]
`) `)
result, _ := deleteMap(data, []string{"a", "1"}) result, _ := subject.DeleteChildValue(data, []string{"a", "1"})
test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
} })
func TestDelete_list_index_beyond_bounds(t *testing.T) { t.Run("TestDelete_list_index_beyond_bounds", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
a: [3, 4] a: [3, 4]
`) `)
result, _ := deleteMap(data, []string{"a", "5"}) result, _ := subject.DeleteChildValue(data, []string{"a", "5"})
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
} })
func TestDelete_list_index_out_of_bounds_by_1(t *testing.T) { t.Run("TestDelete_list_index_out_of_bounds_by_1", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
a: [3, 4] a: [3, 4]
`) `)
result, _ := deleteMap(data, []string{"a", "2"}) result, _ := subject.DeleteChildValue(data, []string{"a", "2"})
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
} })
func TestDelete_no_paths(t *testing.T) { t.Run("TestDelete_no_paths", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
a: [3, 4] a: [3, 4]
b: b:
- name: test - name: test
`) `)
result, _ := deleteMap(data, []string{}) result, _ := subject.DeleteChildValue(data, []string{})
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result)) test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
} })
func TestDelete_array_map_item(t *testing.T) { t.Run("TestDelete_array_map_item", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
b: b:
- name: fred - name: fred
@ -395,6 +390,7 @@ b:
- name: john - name: john
value: test value: test
`) `)
result, _ := deleteMap(data, []string{"b", "0", "name"}) result, _ := subject.DeleteChildValue(data, []string{"b", "0", "name"})
test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result)) test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
})
} }

View File

@ -5,24 +5,38 @@ import (
logging "gopkg.in/op/go-logging.v1" logging "gopkg.in/op/go-logging.v1"
) )
var log = logging.MustGetLogger("yq") type YqLib interface {
ReadPath(dataBucket interface{}, path string) (interface{}, error)
func SetLogger(l *logging.Logger) { WritePath(dataBucket interface{}, path string, value interface{}) (interface{})
log = l 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) { type lib struct {
var paths = ParsePath(path) navigator DataNavigator
return Recurse(dataBucket, paths[0], paths[1:]) parser PathParser
} }
func WritePath(dataBucket interface{}, path string, value interface{}) (interface{}) { func NewYqLib(l *logging.Logger) YqLib {
var paths = ParsePath(path) return &lib {
return UpdatedChildValue(dataBucket, paths, value) navigator: NewDataNavigator(l),
parser: NewPathParser(),
}
} }
func PrefixPath(dataBucket interface{}, prefix string) (interface{}) { func (l *lib) ReadPath(dataBucket interface{}, path string) (interface{}, error) {
var paths = ParsePath(prefix) 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 // Inverse order
for i := len(paths)/2 - 1; i >= 0; i-- { for i := len(paths)/2 - 1; i >= 0; i-- {
@ -33,18 +47,18 @@ func PrefixPath(dataBucket interface{}, prefix string) (interface{}) {
var mapDataBucket = dataBucket var mapDataBucket = dataBucket
for _, key := range paths { for _, key := range paths {
singlePath := []string{key} singlePath := []string{key}
mapDataBucket = UpdatedChildValue(nil, singlePath, mapDataBucket) mapDataBucket = l.navigator.UpdatedChildValue(nil, singlePath, mapDataBucket)
} }
return mapDataBucket return mapDataBucket
} }
func DeletePath(dataBucket interface{}, path string) (interface{}, error) { func (l *lib) DeletePath(dataBucket interface{}, path string) (interface{}, error) {
var paths = ParsePath(path) var paths = l.parser.ParsePath(path)
return DeleteChildValue(dataBucket, paths) 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 { if overwrite {
return mergo.Merge(dst, src, mergo.WithOverride) return mergo.Merge(dst, src, mergo.WithOverride)
} else if append { } else if append {

View File

@ -4,55 +4,61 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/mikefarah/yq/test" "github.com/mikefarah/yq/test"
logging "gopkg.in/op/go-logging.v1"
) )
func TestReadPath(t *testing.T) { func TestLib(t *testing.T) {
var log = logging.MustGetLogger("yq")
subject := NewYqLib(log)
t.Run("TestReadPath", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
2: c 2: c
`) `)
got, _ := ReadPath(data, "b.2") got, _ := subject.ReadPath(data, "b.2")
test.AssertResult(t, `c`, got) test.AssertResult(t, `c`, got)
} })
func TestReadPath_WithError(t *testing.T) { t.Run("TestReadPath_WithError", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
- c - c
`) `)
_, err := ReadPath(data, "b.[a]") _, err := subject.ReadPath(data, "b.[a]")
if err == nil { if err == nil {
t.Fatal("Expected error due to invalid path") t.Fatal("Expected error due to invalid path")
} }
} })
func TestWritePath(t *testing.T) { t.Run("TestWritePath", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
2: c 2: c
`) `)
got := WritePath(data, "b.3", "a") got := subject.WritePath(data, "b.3", "a")
test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got)) test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got))
} })
func TestPrefixPath(t *testing.T) { t.Run("TestPrefixPath", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
2: c 2: c
`) `)
got := PrefixPath(data, "d") got := subject.PrefixPath(data, "d")
test.AssertResult(t, `[{d [{b [{2 c}]}]}]`, fmt.Sprintf("%v", got)) test.AssertResult(t, `[{d [{b [{2 c}]}]}]`, fmt.Sprintf("%v", got))
} })
func TestDeletePath(t *testing.T) { t.Run("TestDeletePath", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
@ -60,24 +66,24 @@ b:
3: a 3: a
`) `)
got, _ := DeletePath(data, "b.2") got, _ := subject.DeletePath(data, "b.2")
test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got)) test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got))
} })
func TestDeletePath_WithError(t *testing.T) { t.Run("TestDeletePath_WithError", func(t *testing.T) {
var data = test.ParseData(` var data = test.ParseData(`
--- ---
b: b:
- c - c
`) `)
_, err := DeletePath(data, "b.[a]") _, err := subject.DeletePath(data, "b.[a]")
if err == nil { if err == nil {
t.Fatal("Expected error due to invalid path") t.Fatal("Expected error due to invalid path")
} }
} })
func TestMerge(t *testing.T) { t.Run("TestMerge", func(t *testing.T) {
var dst = test.ParseData(` var dst = test.ParseData(`
--- ---
a: b a: b
@ -94,11 +100,11 @@ b: 2
var mapDataBucket = make(map[interface{}]interface{}) var mapDataBucket = make(map[interface{}]interface{})
mapDataBucket["root"] = src mapDataBucket["root"] = src
Merge(&mergedData, mapDataBucket, false, false) subject.Merge(&mergedData, mapDataBucket, false, false)
test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"])) test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"]))
} })
func TestMerge_WithOverwrite(t *testing.T) { t.Run("TestMerge_WithOverwrite", func(t *testing.T) {
var dst = test.ParseData(` var dst = test.ParseData(`
--- ---
a: b a: b
@ -115,11 +121,11 @@ b: 2
var mapDataBucket = make(map[interface{}]interface{}) var mapDataBucket = make(map[interface{}]interface{})
mapDataBucket["root"] = src mapDataBucket["root"] = src
Merge(&mergedData, mapDataBucket, true, false) subject.Merge(&mergedData, mapDataBucket, true, false)
test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
} })
func TestMerge_WithAppend(t *testing.T) { t.Run("TestMerge_WithAppend", func(t *testing.T) {
var dst = test.ParseData(` var dst = test.ParseData(`
--- ---
a: b a: b
@ -136,13 +142,15 @@ b: 2
var mapDataBucket = make(map[interface{}]interface{}) var mapDataBucket = make(map[interface{}]interface{})
mapDataBucket["root"] = src mapDataBucket["root"] = src
Merge(&mergedData, mapDataBucket, false, true) subject.Merge(&mergedData, mapDataBucket, false, true)
test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
} })
func TestMerge_WithError(t *testing.T) { t.Run("TestMerge_WithError", func(t *testing.T) {
err := Merge(nil, nil, false, false) err := subject.Merge(nil, nil, false, false)
if err == nil { if err == nil {
t.Fatal("Expected error due to nil") t.Fatal("Expected error due to nil")
} }
})
} }

View File

@ -1,35 +1,45 @@
package yqlib package yqlib
func ParsePath(path string) []string { type PathParser interface {
return parsePathAccum([]string{}, path) ParsePath(path string) []string
} }
func parsePathAccum(paths []string, remaining string) []string { type parser struct {}
head, tail := nextYamlPath(remaining)
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 == "" { if tail == "" {
return append(paths, head) 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] { switch path[0] {
case '[': case '[':
// e.g [0].blah.cat -> we need to return "0" and "blah.cat" // 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 '"': case '"':
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat" // 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: default:
// e.g "a.blah.cat" -> return "a" and "blah.cat" // 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++ { for i := 0; i < len(path); i++ {
var char = path[i] var char = path[i]
if contains(matchingChars, char) { if p.contains(matchingChars, char) {
var remainingStart = i + 1 var remainingStart = i + 1
if skipNext { if skipNext {
remainingStart = remainingStart + 1 remainingStart = remainingStart + 1
@ -45,7 +55,7 @@ func search(path string, matchingChars []uint8, skipNext bool) (pathElement stri
return path, "" return path, ""
} }
func contains(matchingChars []uint8, candidate uint8) bool { func (p *parser) contains(matchingChars []uint8, candidate uint8) bool {
for _, a := range matchingChars { for _, a := range matchingChars {
if a == candidate { if a == candidate {
return true return true

View File

@ -12,33 +12,17 @@ var parsePathsTests = []struct {
{"a.b", []string{"a", "b"}}, {"a.b", []string{"a", "b"}},
{"a.b[0]", []string{"a", "b", "0"}}, {"a.b[0]", []string{"a", "b", "0"}},
{"a.b.d[+]", []string{"a", "b", "d", "+"}}, {"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) { func TestParsePath(t *testing.T) {
for _, tt := range parsePathsTests { 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)
} }
} }

23
yq.go
View File

@ -10,6 +10,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/mikefarah/yq/pkg/yqlib" "github.com/mikefarah/yq/pkg/yqlib"
"github.com/mikefarah/yq/pkg/marshal"
errors "github.com/pkg/errors" errors "github.com/pkg/errors"
@ -29,6 +30,9 @@ var verbose = false
var version = false var version = false
var docIndex = "0" var docIndex = "0"
var log = logging.MustGetLogger("yq") var log = logging.MustGetLogger("yq")
var lib = yqlib.NewYqLib(log)
var jsonConverter = marshal.NewJsonConverter()
var yamlConverter = marshal.NewYamlConverter()
func main() { func main() {
cmd := newCommandCLI() cmd := newCommandCLI()
@ -39,7 +43,6 @@ func main() {
} }
func newCommandCLI() *cobra.Command { func newCommandCLI() *cobra.Command {
yqlib.SetLogger(log)
yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{}) yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{})
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "yq", Use: "yq",
@ -277,7 +280,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
log.Debugf("%v", dataBucket) log.Debugf("%v", dataBucket)
mappedDocs = append(mappedDocs, dataBucket) mappedDocs = append(mappedDocs, dataBucket)
} else { } else {
mappedDoc, errorParsing := yqlib.ReadPath(dataBucket, path) mappedDoc, errorParsing := lib.ReadPath(dataBucket, path)
log.Debugf("%v", mappedDoc) log.Debugf("%v", mappedDoc)
if errorParsing != nil { if errorParsing != nil {
return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) 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) path := entry.Key.(string)
value := entry.Value value := entry.Value
log.Debugf("setting %v to %v", path, value) log.Debugf("setting %v to %v", path, value)
dataBucket = yqlib.WritePath(dataBucket, path, value) dataBucket = lib.WritePath(dataBucket, path, value)
} }
return dataBucket, nil return dataBucket, nil
@ -414,7 +417,7 @@ func writeProperty(cmd *cobra.Command, args []string) error {
path := entry.Key.(string) path := entry.Key.(string)
value := entry.Value value := entry.Value
log.Debugf("setting %v to %v", path, value) log.Debugf("setting %v to %v", path, value)
dataBucket = yqlib.WritePath(dataBucket, path, value) dataBucket = lib.WritePath(dataBucket, path, value)
} }
} }
return dataBucket, nil return dataBucket, nil
@ -437,7 +440,7 @@ func prefixProperty(cmd *cobra.Command, args []string) error {
if updateAll || currentIndex == docIndexInt { if updateAll || currentIndex == docIndexInt {
log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex) 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 mapDataBucket, nil
} }
return dataBucket, 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) { var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
if updateAll || currentIndex == docIndexInt { if updateAll || currentIndex == docIndexInt {
log.Debugf("Deleting path in doc %v", currentIndex) log.Debugf("Deleting path in doc %v", currentIndex)
return yqlib.DeletePath(dataBucket, deletePath) return lib.DeletePath(dataBucket, deletePath)
} }
return dataBucket, nil return dataBucket, nil
} }
@ -518,7 +521,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error {
// map // map
var mapDataBucket = make(map[interface{}]interface{}) var mapDataBucket = make(map[interface{}]interface{})
mapDataBucket["root"] = dataBucket 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 return nil, err
} }
for _, f := range filesToMerge { for _, f := range filesToMerge {
@ -530,7 +533,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error {
return nil, err return nil, err
} }
mapDataBucket["root"] = fileToMerge 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 return nil, err
} }
} }
@ -580,9 +583,9 @@ func parseValue(argument string) interface{} {
func toString(context interface{}) (string, error) { func toString(context interface{}) (string, error) {
if outputToJSON { 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) { func safelyRenameFile(from string, to string) {

View File

@ -5,7 +5,7 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/mikefarah/yq/test" "github.com/mikefarah/yq/test"
"github.com/mikefarah/yq/pkg/yqlib" "github.com/mikefarah/yq/pkg/marshal"
) )
var parseValueTests = []struct { var parseValueTests = []struct {
@ -30,7 +30,7 @@ func TestMultilineString(t *testing.T) {
testString := ` testString := `
abcd abcd
efg` efg`
formattedResult, _ := yqlib.YamlToString(testString, false) formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false)
test.AssertResult(t, testString, formattedResult) test.AssertResult(t, testString, formattedResult)
} }
@ -57,7 +57,7 @@ func TestNewYaml_WithScript(t *testing.T) {
e: e:
- name: Mike Farah` - name: Mike Farah`
result, _ := newYaml([]string{""}) result, _ := newYaml([]string{""})
actualResult, _ := yqlib.YamlToString(result, true) actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true)
test.AssertResult(t, expectedResult, actualResult) test.AssertResult(t, expectedResult, actualResult)
} }