Skip to content

Commit a3190f1

Browse files
author
Mike Farah
committed
Refactor write to allow new entries
1 parent 44ee869 commit a3190f1

File tree

3 files changed

+165
-36
lines changed

3 files changed

+165
-36
lines changed

data_navigator.go

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package main
22

33
import (
4-
// "fmt"
54
"github.com/mikefarah/yaml/Godeps/_workspace/src/gopkg.in/yaml.v2"
65
"strconv"
76
)
@@ -16,40 +15,90 @@ func entryInSlice(context yaml.MapSlice, key interface{}) *yaml.MapItem {
1615
return nil
1716
}
1817

19-
func write(context yaml.MapSlice, head string, tail []string, value interface{}) {
20-
if len(tail) == 0 {
21-
var entry = entryInSlice(context, head)
22-
entry.Value = value
23-
} else {
24-
// e.g. if updating a.b.c, we need to get the 'b', this could be a map or an array
25-
var parent = readMap(context, head, tail[0:len(tail)-1])
26-
switch parent.(type) {
27-
case yaml.MapSlice:
28-
toUpdate := parent.(yaml.MapSlice)
29-
// b is a map, update the key 'c' to the supplied value
30-
key := (tail[len(tail)-1])
31-
toUpdateEntry := entryInSlice(toUpdate, key)
32-
toUpdateEntry.Value = value
33-
case []interface{}:
34-
toUpdate := parent.([]interface{})
35-
// b is an array, update it at index 'c' to the supplied value
36-
rawIndex := (tail[len(tail)-1])
37-
index, err := strconv.ParseInt(rawIndex, 10, 64)
38-
if err != nil {
39-
die("Error accessing array: %v", err)
40-
}
41-
toUpdate[index] = value
42-
}
18+
func writeMap(context interface{}, paths []string, value interface{}) yaml.MapSlice {
19+
log.Debugf("writeMap for %v for %v with value %v\n", paths, context, value)
20+
21+
var mapSlice yaml.MapSlice
22+
switch context.(type) {
23+
case yaml.MapSlice:
24+
mapSlice = context.(yaml.MapSlice)
25+
default:
26+
mapSlice = make(yaml.MapSlice, 0)
27+
}
28+
29+
if len(paths) == 0 {
30+
return mapSlice
31+
}
32+
33+
child := entryInSlice(mapSlice, paths[0])
34+
if child == nil {
35+
newChild := yaml.MapItem{Key: paths[0]}
36+
mapSlice = append(mapSlice, newChild)
37+
child = entryInSlice(mapSlice, paths[0])
38+
log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice)
39+
}
40+
41+
log.Debugf("\tchild.Value %v\n", child.Value)
42+
43+
remainingPaths := paths[1:len(paths)]
44+
child.Value = updatedChildValue(child.Value, remainingPaths, value)
45+
log.Debugf("\tReturning mapSlice %v\n", mapSlice)
46+
return mapSlice
47+
}
48+
49+
func updatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} {
50+
if len(remainingPaths) == 0 {
51+
return value
52+
}
53+
54+
_, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64)
55+
if nextIndexErr != nil {
56+
// must be a map
57+
return writeMap(child, remainingPaths, value)
58+
}
59+
60+
// must be an array
61+
return writeArray(child, remainingPaths, value)
62+
}
63+
64+
func writeArray(context interface{}, paths []string, value interface{}) []interface{} {
65+
log.Debugf("writeArray for %v for %v with value %v\n", paths, context, value)
66+
var array []interface{}
67+
switch context.(type) {
68+
case []interface{}:
69+
array = context.([]interface{})
70+
default:
71+
array = make([]interface{}, 1)
72+
}
4373

74+
if len(paths) == 0 {
75+
return array
4476
}
77+
78+
log.Debugf("\tarray %v\n", array)
79+
80+
rawIndex := paths[0]
81+
index, err := strconv.ParseInt(rawIndex, 10, 64)
82+
if err != nil {
83+
die("Error accessing array: %v", err)
84+
}
85+
currentChild := array[index]
86+
87+
log.Debugf("\tcurrentChild %v\n", currentChild)
88+
89+
remainingPaths := paths[1:len(paths)]
90+
array[index] = updatedChildValue(currentChild, remainingPaths, value)
91+
log.Debugf("\tReturning array %v\n", array)
92+
return array
4593
}
4694

4795
func readMap(context yaml.MapSlice, head string, tail []string) interface{} {
4896
if head == "*" {
4997
return readMapSplat(context, tail)
5098
}
51-
entry := entryInSlice(context, head)
5299
var value interface{}
100+
101+
entry := entryInSlice(context, head)
53102
if entry != nil {
54103
value = entry.Value
55104
}

data_navigator_test.go

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@ package main
33
import (
44
"fmt"
55
"github.com/mikefarah/yaml/Godeps/_workspace/src/gopkg.in/yaml.v2"
6+
"github.com/op/go-logging"
7+
"os"
68
"sort"
79
"testing"
810
)
911

12+
func TestMain(m *testing.M) {
13+
backend.SetLevel(logging.ERROR, "")
14+
logging.SetBackend(backend)
15+
os.Exit(m.Run())
16+
}
17+
1018
func TestReadMap_simple(t *testing.T) {
1119
var data = parseData(`
1220
---
@@ -113,8 +121,8 @@ func TestWrite_really_simple(t *testing.T) {
113121
b: 2
114122
`)
115123

116-
write(data, "b", []string{}, "4")
117-
b := entryInSlice(data, "b").Value
124+
updated := writeMap(data, []string{"b"}, "4")
125+
b := entryInSlice(updated, "b").Value
118126
assertResult(t, "4", b)
119127
}
120128

@@ -124,31 +132,86 @@ b:
124132
c: 2
125133
`)
126134

127-
write(data, "b", []string{"c"}, "4")
128-
b := entryInSlice(data, "b").Value.(yaml.MapSlice)
135+
updated := writeMap(data, []string{"b", "c"}, "4")
136+
b := entryInSlice(updated, "b").Value.(yaml.MapSlice)
129137
c := entryInSlice(b, "c").Value
130138
assertResult(t, "4", c)
131139
}
132140

141+
func TestWrite_new(t *testing.T) {
142+
var data = parseData(`
143+
b:
144+
c: 2
145+
`)
146+
147+
updated := writeMap(data, []string{"b", "d"}, "4")
148+
b := entryInSlice(updated, "b").Value.(yaml.MapSlice)
149+
d := entryInSlice(b, "d").Value
150+
assertResult(t, "4", d)
151+
}
152+
153+
func TestWrite_new_deep(t *testing.T) {
154+
var data = parseData(`
155+
b:
156+
c: 2
157+
`)
158+
159+
updated := writeMap(data, []string{"b", "d", "f"}, "4")
160+
assertResult(t, "4", readMap(updated, "b", []string{"d", "f"}))
161+
}
162+
133163
func TestWrite_array(t *testing.T) {
134164
var data = parseData(`
135165
b:
136166
- aa
137167
`)
138168

139-
write(data, "b", []string{"0"}, "bb")
169+
updated := writeMap(data, []string{"b", "0"}, "bb")
140170

141-
b := entryInSlice(data, "b").Value.([]interface{})
171+
b := entryInSlice(updated, "b").Value.([]interface{})
142172
assertResult(t, "bb", b[0].(string))
143173
}
144174

175+
func TestWrite_new_array(t *testing.T) {
176+
var data = parseData(`
177+
b:
178+
c: 2
179+
`)
180+
181+
updated := writeMap(data, []string{"b", "0"}, "4")
182+
assertResult(t, "4", readMap(updated, "b", []string{"0"}))
183+
}
184+
185+
func TestWrite_new_array_deep(t *testing.T) {
186+
var data = parseData(`
187+
b:
188+
c: 2
189+
`)
190+
191+
var expected = `b:
192+
- c: "4"`
193+
194+
updated := writeMap(data, []string{"b", "0", "c"}, "4")
195+
assertResult(t, expected, yamlToString(updated))
196+
}
197+
198+
func TestWrite_new_map_array_deep(t *testing.T) {
199+
var data = parseData(`
200+
b:
201+
c: 2
202+
`)
203+
204+
updated := writeMap(data, []string{"b", "d", "0"}, "4")
205+
assertResult(t, "4", readMap(updated, "b", []string{"d", "0"}))
206+
}
207+
145208
func TestWrite_with_no_tail(t *testing.T) {
146209
var data = parseData(`
147210
b:
148211
c: 2
149212
`)
150-
write(data, "b", []string{}, "4")
213+
updated := writeMap(data, []string{"b"}, "4")
151214

152-
b := entryInSlice(data, "b").Value
215+
b := entryInSlice(updated, "b").Value
153216
assertResult(t, "4", fmt.Sprintf("%v", b))
154217
}

yaml.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"github.com/mikefarah/yaml/Godeps/_workspace/src/github.com/spf13/cobra"
66
"github.com/mikefarah/yaml/Godeps/_workspace/src/gopkg.in/yaml.v2"
7+
"github.com/op/go-logging"
78
"io/ioutil"
89
"os"
910
"strconv"
@@ -15,16 +16,26 @@ var writeInplace = false
1516
var writeScript = ""
1617
var inputJSON = false
1718
var outputToJSON = false
19+
var verbose = false
20+
var log = logging.MustGetLogger("yaml")
21+
var format = logging.MustStringFormatter(
22+
`%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`,
23+
)
24+
var backend = logging.AddModuleLevel(
25+
logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format))
1826

1927
func main() {
28+
backend.SetLevel(logging.ERROR, "")
29+
logging.SetBackend(backend)
30+
2031
var cmdRead = createReadCmd()
2132
var cmdWrite = createWriteCmd()
2233

2334
var rootCmd = &cobra.Command{Use: "yaml"}
2435
rootCmd.PersistentFlags().BoolVarP(&trimOutput, "trim", "t", true, "trim yaml output")
2536
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
2637
rootCmd.PersistentFlags().BoolVarP(&inputJSON, "fromjson", "J", false, "input as json")
27-
38+
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
2839
rootCmd.AddCommand(cmdRead, cmdWrite)
2940
rootCmd.Execute()
3041
}
@@ -77,6 +88,9 @@ a.b.e:
7788
}
7889

7990
func readProperty(cmd *cobra.Command, args []string) {
91+
if verbose {
92+
backend.SetLevel(logging.DEBUG, "")
93+
}
8094
print(read(args))
8195
}
8296

@@ -95,6 +109,9 @@ func read(args []string) interface{} {
95109
}
96110

97111
func writeProperty(cmd *cobra.Command, args []string) {
112+
if verbose {
113+
backend.SetLevel(logging.DEBUG, "")
114+
}
98115
updatedData := updateYaml(args)
99116
if writeInplace {
100117
ioutil.WriteFile(args[0], []byte(yamlToString(updatedData)), 0644)
@@ -119,7 +136,7 @@ func updateYaml(args []string) interface{} {
119136

120137
for path, value := range writeCommands {
121138
var paths = parsePath(path)
122-
write(parsedData, paths[0], paths[1:len(paths)], value)
139+
parsedData = writeMap(parsedData, paths, value)
123140
}
124141

125142
return parsedData

0 commit comments

Comments
 (0)