Task: Increase test coverage, includes refactor

Adds test cases to increase test coverage.
Refactors code to enable adding tests by reducing the number of
locations where `os.Exit()` is called from.
This commit is contained in:
kenjones 2017-09-22 15:58:12 -04:00
parent 359ca5a117
commit 53b2c64747
8 changed files with 620 additions and 157 deletions

305
commands_test.go Normal file
View File

@ -0,0 +1,305 @@
package main
import (
"fmt"
"strings"
"testing"
"github.com/spf13/cobra"
)
func getRootCommand() *cobra.Command {
return newCommandCLI()
}
func TestRootCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "")
if result.Error != nil {
t.Error(result.Error)
}
if !strings.Contains(result.Output, "Usage:") {
t.Error("Expected usage message to be printed out, but the usage message was not found.")
}
}
func TestRootCmd_VerboseLong(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "--verbose")
if result.Error != nil {
t.Error(result.Error)
}
if !verbose {
t.Error("Expected verbose to be true")
}
}
func TestRootCmd_VerboseShort(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "-v")
if result.Error != nil {
t.Error(result.Error)
}
if !verbose {
t.Error("Expected verbose to be true")
}
}
func TestRootCmd_TrimLong(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "--trim")
if result.Error != nil {
t.Error(result.Error)
}
if !trimOutput {
t.Error("Expected trimOutput to be true")
}
}
func TestRootCmd_TrimShort(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "-t")
if result.Error != nil {
t.Error(result.Error)
}
if !trimOutput {
t.Error("Expected trimOutput to be true")
}
}
func TestRootCmd_ToJsonLong(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "--tojson")
if result.Error != nil {
t.Error(result.Error)
}
if !outputToJSON {
t.Error("Expected outputToJSON to be true")
}
}
func TestRootCmd_ToJsonShort(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "-j")
if result.Error != nil {
t.Error(result.Error)
}
if !outputToJSON {
t.Error("Expected outputToJSON to be true")
}
}
func TestReadCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "read examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
assertResult(t, "2\n", result.Output)
}
func TestReadCmd_Error(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "read")
if result.Error == nil {
t.Error("Expected command to fail due to missing arg")
}
expectedOutput := `Must provide filename`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestReadCmd_ErrorEmptyFilename(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "read ")
if result.Error == nil {
t.Error("Expected command to fail due to missing arg")
}
expectedOutput := `Must provide filename`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestReadCmd_ErrorUnreadableFile(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "read fake-unknown")
if result.Error == nil {
t.Error("Expected command to fail due to unknown file")
}
expectedOutput := `open fake-unknown: no such file or directory`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestReadCmd_ErrorBadPath(t *testing.T) {
content := `b:
d:
e:
- 3
- 4
f:
- 1
- 2
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename))
if result.Error == nil {
t.Fatal("Expected command to fail due to invalid path")
}
expectedOutput := `Error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestReadCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "-v read examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
assertResult(t, "2\n", result.Output)
}
func TestReadCmd_NoTrim(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "--trim=false read examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
assertResult(t, "2\n\n", result.Output)
}
func TestReadCmd_ToJson(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "-j read examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
assertResult(t, "2\n", result.Output)
}
func TestNewCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "new b.c 3")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 3
`
assertResult(t, expectedOutput, result.Output)
}
func TestNewCmd_Error(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "new b.c")
if result.Error == nil {
t.Error("Expected command to fail due to missing arg")
}
expectedOutput := `Must provide <path_to_update> <value>`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestNewCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "-v new b.c 3")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 3
`
assertResult(t, expectedOutput, result.Output)
}
func TestNewCmd_ToJson(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "-j new b.c 3")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `{"b":{"c":3}}
`
assertResult(t, expectedOutput, result.Output)
}
func TestWriteCmd(t *testing.T) {
content := `b:
c: 3
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 7
`
assertResult(t, expectedOutput, result.Output)
}
func TestWriteCmd_Error(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "write")
if result.Error == nil {
t.Error("Expected command to fail due to missing arg")
}
expectedOutput := `Must provide <filename> <path_to_update> <value>`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestWriteCmd_ErrorUnreadableFile(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "write fake-unknown a.b 3")
if result.Error == nil {
t.Error("Expected command to fail due to unknown file")
}
expectedOutput := `open fake-unknown: no such file or directory`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestWriteCmd_Verbose(t *testing.T) {
content := `b:
c: 3
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("-v write %s b.c 7", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 7
`
assertResult(t, expectedOutput, result.Output)
}
func TestWriteCmd_Inplace(t *testing.T) {
content := `b:
c: 3
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
if result.Error != nil {
t.Error(result.Error)
}
gotOutput := readTempYamlFile(filename)
expectedOutput := `b:
c: 7`
assertResult(t, expectedOutput, gotOutput)
}

View File

@ -1,9 +1,10 @@
package main
import (
"fmt"
"strconv"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"
)
func entryInSlice(context yaml.MapSlice, key interface{}) *yaml.MapItem {
@ -79,10 +80,12 @@ func writeArray(context interface{}, paths []string, value interface{}) []interf
log.Debugf("\tarray %v\n", array)
rawIndex := paths[0]
index, err := strconv.ParseInt(rawIndex, 10, 64)
if err != nil {
die("Error accessing array: %v", err)
}
index, _ := strconv.ParseInt(rawIndex, 10, 64)
// writeArray is only called by updatedChildValue which handles parsing the
// index, as such this renders this dead code.
// if err != nil {
// return array, fmt.Errorf("Error accessing array: %v", err)
// }
for index >= int64(len(array)) {
array = append(array, nil)
}
@ -96,7 +99,7 @@ func writeArray(context interface{}, paths []string, value interface{}) []interf
return array
}
func readMap(context yaml.MapSlice, head string, tail []string) interface{} {
func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) {
if head == "*" {
return readMapSplat(context, tail)
}
@ -109,21 +112,25 @@ func readMap(context yaml.MapSlice, head string, tail []string) interface{} {
return calculateValue(value, tail)
}
func readMapSplat(context yaml.MapSlice, tail []string) interface{} {
func 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 {
newArray[i] = recurse(entry.Value, tail[0], tail[1:])
val, err := recurse(entry.Value, tail[0], tail[1:])
if err != nil {
return nil, err
}
newArray[i] = val
} else {
newArray[i] = entry.Value
}
i++
}
return newArray
return newArray, nil
}
func recurse(value interface{}, head string, tail []string) interface{} {
func recurse(value interface{}, head string, tail []string) (interface{}, error) {
switch value.(type) {
case []interface{}:
if head == "*" {
@ -131,37 +138,40 @@ func recurse(value interface{}, head string, tail []string) interface{} {
}
index, err := strconv.ParseInt(head, 10, 64)
if err != nil {
die("Error accessing array: %v", err)
return nil, fmt.Errorf("Error accessing array: %v", err)
}
return readArray(value.([]interface{}), index, tail)
case yaml.MapSlice:
return readMap(value.(yaml.MapSlice), head, tail)
default:
return nil
return nil, nil
}
}
func readArray(array []interface{}, head int64, tail []string) interface{} {
func readArray(array []interface{}, head int64, tail []string) (interface{}, error) {
if head >= int64(len(array)) {
return nil
return nil, nil
}
value := array[head]
return calculateValue(value, tail)
}
func readArraySplat(array []interface{}, tail []string) interface{} {
func readArraySplat(array []interface{}, tail []string) (interface{}, error) {
var newArray = make([]interface{}, len(array))
for index, value := range array {
newArray[index] = calculateValue(value, tail)
val, err := calculateValue(value, tail)
if err != nil {
return nil, err
}
return newArray
newArray[index] = val
}
return newArray, nil
}
func calculateValue(value interface{}, tail []string) interface{} {
func calculateValue(value interface{}, tail []string) (interface{}, error) {
if len(tail) > 0 {
return recurse(value, tail[0], tail[1:])
}
return value
return value, nil
}

View File

@ -2,27 +2,20 @@ package main
import (
"fmt"
"os"
"sort"
"testing"
logging "github.com/op/go-logging"
yaml "gopkg.in/yaml.v2"
)
func TestMain(m *testing.M) {
backend.SetLevel(logging.ERROR, "")
logging.SetBackend(backend)
os.Exit(m.Run())
}
func TestReadMap_simple(t *testing.T) {
var data = parseData(`
---
b:
c: 2
`)
assertResult(t, 2, readMap(data, "b", []string{"c"}))
got, _ := readMap(data, "b", []string{"c"})
assertResult(t, 2, got)
}
func TestReadMap_splat(t *testing.T) {
@ -32,7 +25,8 @@ mapSplat:
item1: things
item2: whatever
`)
var result = readMap(data, "mapSplat", []string{"*"}).([]interface{})
res, _ := readMap(data, "mapSplat", []string{"*"})
result := res.([]interface{})
var actual = []string{result[0].(string), result[1].(string)}
sort.Strings(actual)
assertResult(t, "[things whatever]", fmt.Sprintf("%v", actual))
@ -48,7 +42,8 @@ mapSplatDeep:
cats: apples
`)
var result = readMap(data, "mapSplatDeep", []string{"*", "cats"}).([]interface{})
res, _ := readMap(data, "mapSplatDeep", []string{"*", "cats"})
result := res.([]interface{})
var actual = []string{result[0].(string), result[1].(string)}
sort.Strings(actual)
assertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual))
@ -60,7 +55,8 @@ func TestReadMap_key_doesnt_exist(t *testing.T) {
b:
c: 2
`)
assertResult(t, nil, readMap(data, "b.x.f", []string{"c"}))
got, _ := readMap(data, "b.x.f", []string{"c"})
assertResult(t, nil, got)
}
func TestReadMap_recurse_against_string(t *testing.T) {
@ -68,7 +64,8 @@ func TestReadMap_recurse_against_string(t *testing.T) {
---
a: cat
`)
assertResult(t, nil, readMap(data, "a", []string{"b"}))
got, _ := readMap(data, "a", []string{"b"})
assertResult(t, nil, got)
}
func TestReadMap_with_array(t *testing.T) {
@ -79,7 +76,64 @@ b:
- 3
- 4
`)
assertResult(t, 4, readMap(data, "b", []string{"d", "1"}))
got, _ := readMap(data, "b", []string{"d", "1"})
assertResult(t, 4, got)
}
func TestReadMap_with_array_and_bad_index(t *testing.T) {
var data = 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`
assertResult(t, expectedOutput, err.Error())
}
func TestReadMap_with_mapsplat_array_and_bad_index(t *testing.T) {
var data = parseData(`
---
b:
d:
e:
- 3
- 4
f:
- 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`
assertResult(t, expectedOutput, err.Error())
}
func TestReadMap_with_arraysplat_map_array_and_bad_index(t *testing.T) {
var data = parseData(`
---
b:
d:
- names:
- fred
- smith
- names:
- 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`
assertResult(t, expectedOutput, err.Error())
}
func TestReadMap_with_array_out_of_bounds(t *testing.T) {
@ -90,7 +144,8 @@ b:
- 3
- 4
`)
assertResult(t, nil, readMap(data, "b", []string{"d", "3"}))
got, _ := readMap(data, "b", []string{"d", "3"})
assertResult(t, nil, got)
}
func TestReadMap_with_array_out_of_bounds_by_1(t *testing.T) {
@ -101,7 +156,8 @@ b:
- 3
- 4
`)
assertResult(t, nil, readMap(data, "b", []string{"d", "2"}))
got, _ := readMap(data, "b", []string{"d", "2"})
assertResult(t, nil, got)
}
func TestReadMap_with_array_splat(t *testing.T) {
@ -114,7 +170,8 @@ e:
name: Sam
thing: dog
`)
assertResult(t, "[Fred Sam]", fmt.Sprintf("%v", readMap(data, "e", []string{"*", "name"})))
got, _ := readMap(data, "e", []string{"*", "name"})
assertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got))
}
func TestWrite_really_simple(t *testing.T) {
@ -158,7 +215,8 @@ b:
`)
updated := writeMap(data, []string{"b", "d", "f"}, "4")
assertResult(t, "4", readMap(updated, "b", []string{"d", "f"}))
got, _ := readMap(updated, "b", []string{"d", "f"})
assertResult(t, "4", got)
}
func TestWrite_array(t *testing.T) {
@ -180,7 +238,8 @@ b:
`)
updated := writeMap(data, []string{"b", "0"}, "4")
assertResult(t, "4", readMap(updated, "b", []string{"0"}))
got, _ := readMap(updated, "b", []string{"0"})
assertResult(t, "4", got)
}
func TestWrite_new_array_deep(t *testing.T) {
@ -193,7 +252,8 @@ b:
- c: "4"`
updated := writeMap(data, []string{"b", "0", "c"}, "4")
assertResult(t, expected, yamlToString(updated))
got, _ := yamlToString(updated)
assertResult(t, expected, got)
}
func TestWrite_new_map_array_deep(t *testing.T) {
@ -203,7 +263,8 @@ b:
`)
updated := writeMap(data, []string{"b", "d", "0"}, "4")
assertResult(t, "4", readMap(updated, "b", []string{"d", "0"}))
got, _ := readMap(updated, "b", []string{"d", "0"})
assertResult(t, "4", got)
}
func TestWrite_add_to_array(t *testing.T) {
@ -217,8 +278,8 @@ b:
- bb`
updated := writeMap(data, []string{"b", "1"}, "bb")
assertResult(t, expected, yamlToString(updated))
got, _ := yamlToString(updated)
assertResult(t, expected, got)
}
func TestWrite_with_no_tail(t *testing.T) {

View File

@ -2,17 +2,18 @@ package main
import (
"encoding/json"
"fmt"
"strconv"
yaml "gopkg.in/yaml.v2"
)
func jsonToString(context interface{}) string {
func jsonToString(context interface{}) (string, error) {
out, err := json.Marshal(toJSON(context))
if err != nil {
die("error printing yaml as json: ", err)
return "", fmt.Errorf("error printing yaml as json: %v", err)
}
return string(out)
return string(out), nil
}
func toJSON(context interface{}) interface{} {

View File

@ -10,7 +10,8 @@ func TestJsonToString(t *testing.T) {
b:
c: 2
`)
assertResult(t, "{\"b\":{\"c\":2}}", jsonToString(data))
got, _ := jsonToString(data)
assertResult(t, "{\"b\":{\"c\":2}}", got)
}
func TestJsonToString_withIntKey(t *testing.T) {
@ -19,7 +20,8 @@ func TestJsonToString_withIntKey(t *testing.T) {
b:
2: c
`)
assertResult(t, `{"b":{"2":"c"}}`, jsonToString(data))
got, _ := jsonToString(data)
assertResult(t, `{"b":{"2":"c"}}`, got)
}
func TestJsonToString_withBoolKey(t *testing.T) {
@ -28,7 +30,8 @@ func TestJsonToString_withBoolKey(t *testing.T) {
b:
false: c
`)
assertResult(t, `{"b":{"false":"c"}}`, jsonToString(data))
got, _ := jsonToString(data)
assertResult(t, `{"b":{"false":"c"}}`, got)
}
func TestJsonToString_withArray(t *testing.T) {
@ -38,5 +41,6 @@ b:
- item: one
- item: two
`)
assertResult(t, "{\"b\":[{\"item\":\"one\"},{\"item\":\"two\"}]}", jsonToString(data))
got, _ := jsonToString(data)
assertResult(t, "{\"b\":[{\"item\":\"one\"},{\"item\":\"two\"}]}", got)
}

View File

@ -1,14 +1,35 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
"github.com/spf13/cobra"
yaml "gopkg.in/yaml.v2"
)
type resulter struct {
Error error
Output string
Command *cobra.Command
}
func runCmd(c *cobra.Command, input string) resulter {
buf := new(bytes.Buffer)
c.SetOutput(buf)
c.SetArgs(strings.Split(input, " "))
err := c.Execute()
output := buf.String()
return resulter{err, output, c}
}
func parseData(rawData string) yaml.MapSlice {
var parsedData yaml.MapSlice
err := yaml.Unmarshal([]byte(rawData), &parsedData)
@ -38,3 +59,22 @@ func assertResultWithContext(t *testing.T, expectedValue interface{}, actualValu
t.Error(": expected <", expectedValue, "> but got <", actualValue, ">")
}
}
func writeTempYamlFile(content string) string {
tmpfile, _ := ioutil.TempFile("", "testyaml")
defer func() {
_ = tmpfile.Close()
}()
_, _ = tmpfile.Write([]byte(content))
return tmpfile.Name()
}
func readTempYamlFile(name string) string {
content, _ := ioutil.ReadFile(name)
return string(content)
}
func removeTempYamlFile(name string) {
_ = os.Remove(name)
}

210
yaml.go
View File

@ -1,6 +1,7 @@
package main
import (
"errors"
"fmt"
"io/ioutil"
"os"
@ -15,30 +16,45 @@ import (
var trimOutput = true
var writeInplace = false
var writeScript = ""
var inputJSON = false
var outputToJSON = false
var verbose = false
var log = logging.MustGetLogger("yaml")
var format = logging.MustStringFormatter(
`%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`,
)
var backend = logging.AddModuleLevel(
logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format))
func main() {
cmd := newCommandCLI()
if err := cmd.Execute(); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
func newCommandCLI() *cobra.Command {
var rootCmd = &cobra.Command{
Use: "yaml",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
var format = logging.MustStringFormatter(
`%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`,
)
var backend = logging.AddModuleLevel(
logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format))
if verbose {
backend.SetLevel(logging.DEBUG, "")
} else {
backend.SetLevel(logging.ERROR, "")
}
logging.SetBackend(backend)
},
}
var cmdRead = createReadCmd()
var cmdWrite = createWriteCmd()
var cmdNew = createNewCmd()
var rootCmd = &cobra.Command{Use: "yaml"}
rootCmd.PersistentFlags().BoolVarP(&trimOutput, "trim", "t", true, "trim yaml output")
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
rootCmd.AddCommand(cmdRead, cmdWrite, cmdNew)
_ = rootCmd.Execute()
rootCmd.AddCommand(createReadCmd(), createWriteCmd(), createNewCmd())
return rootCmd
}
func createReadCmd() *cobra.Command {
@ -54,7 +70,7 @@ yaml r things.yaml a.array[0].blah
yaml r things.yaml a.array[*].blah
`,
Long: "Outputs the value of the given path in the yaml file to STDOUT",
Run: readProperty,
RunE: readProperty,
}
}
@ -81,7 +97,7 @@ a.b.c: true,
a.b.e:
- name: bob
`,
Run: writeProperty,
RunE: writeProperty,
}
cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
@ -104,37 +120,47 @@ Outputs to STDOUT
Create Scripts:
Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script.
`,
Run: newProperty,
RunE: newProperty,
}
cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
return cmdNew
}
func readProperty(cmd *cobra.Command, args []string) {
if verbose {
backend.SetLevel(logging.DEBUG, "")
func readProperty(cmd *cobra.Command, args []string) error {
data, err := read(args)
if err != nil {
return err
}
print(read(args))
dataStr, err := toString(data)
if err != nil {
return err
}
cmd.Println(dataStr)
return nil
}
func read(args []string) interface{} {
func read(args []string) (interface{}, error) {
var parsedData yaml.MapSlice
var path = ""
if len(args) > 1 {
if len(args) < 1 {
return nil, errors.New("Must provide filename")
} else if len(args) > 1 {
path = args[1]
}
err := readData(args[0], &parsedData, inputJSON)
if err != nil {
if err := readData(args[0], &parsedData); err != nil {
var generalData interface{}
readDataOrDie(args[0], &generalData, inputJSON)
if err = readData(args[0], &generalData); err != nil {
return nil, err
}
item := yaml.MapItem{Key: "thing", Value: generalData}
parsedData = yaml.MapSlice{item}
path = "thing." + path
}
if path == "" {
return parsedData
return parsedData, nil
}
var paths = parsePath(path)
@ -142,20 +168,27 @@ func read(args []string) interface{} {
return readMap(parsedData, paths[0], paths[1:])
}
func newProperty(cmd *cobra.Command, args []string) {
if verbose {
backend.SetLevel(logging.DEBUG, "")
func newProperty(cmd *cobra.Command, args []string) error {
updatedData, err := newYaml(args)
if err != nil {
return err
}
updatedData := newYaml(args)
print(updatedData)
dataStr, err := toString(updatedData)
if err != nil {
return err
}
cmd.Println(dataStr)
return nil
}
func newYaml(args []string) interface{} {
func newYaml(args []string) (interface{}, error) {
var writeCommands yaml.MapSlice
if writeScript != "" {
readDataOrDie(writeScript, &writeCommands, false)
if err := readData(writeScript, &writeCommands); err != nil {
return nil, err
}
} else if len(args) < 2 {
die("Must provide <path_to_update> <value>")
return nil, errors.New("Must provide <path_to_update> <value>")
} else {
writeCommands = make(yaml.MapSlice, 1)
writeCommands[0] = yaml.MapItem{Key: args[0], Value: parseValue(args[1])}
@ -175,19 +208,31 @@ func newYaml(args []string) interface{} {
return updateParsedData(parsedData, writeCommands, prependCommand)
}
func writeProperty(cmd *cobra.Command, args []string) {
if verbose {
backend.SetLevel(logging.DEBUG, "")
}
updatedData := updateYaml(args)
if writeInplace {
_ = ioutil.WriteFile(args[0], []byte(yamlToString(updatedData)), 0644)
} else {
print(updatedData)
func writeProperty(cmd *cobra.Command, args []string) error {
updatedData, err := updateYaml(args)
if err != nil {
return err
}
return write(cmd, args[0], updatedData)
}
func updateParsedData(parsedData yaml.MapSlice, writeCommands yaml.MapSlice, prependCommand string) interface{} {
func write(cmd *cobra.Command, filename string, updatedData interface{}) error {
if writeInplace {
dataStr, err := yamlToString(updatedData)
if err != nil {
return err
}
return ioutil.WriteFile(filename, []byte(dataStr), 0644)
}
dataStr, err := toString(updatedData)
if err != nil {
return err
}
cmd.Println(dataStr)
return nil
}
func updateParsedData(parsedData yaml.MapSlice, writeCommands yaml.MapSlice, prependCommand string) (interface{}, error) {
var prefix = ""
if prependCommand != "" {
prefix = prependCommand + "."
@ -201,26 +246,29 @@ func updateParsedData(parsedData yaml.MapSlice, writeCommands yaml.MapSlice, pre
if prependCommand != "" {
return readMap(parsedData, prependCommand, make([]string, 0))
}
return parsedData
return parsedData, nil
}
func updateYaml(args []string) interface{} {
func updateYaml(args []string) (interface{}, error) {
var writeCommands yaml.MapSlice
var prependCommand = ""
if writeScript != "" {
readDataOrDie(writeScript, &writeCommands, false)
if err := readData(writeScript, &writeCommands); err != nil {
return nil, err
}
} else if len(args) < 3 {
die("Must provide <filename> <path_to_update> <value>")
return nil, errors.New("Must provide <filename> <path_to_update> <value>")
} else {
writeCommands = make(yaml.MapSlice, 1)
writeCommands[0] = yaml.MapItem{Key: args[1], Value: parseValue(args[2])}
}
var parsedData yaml.MapSlice
err := readData(args[0], &parsedData, inputJSON)
if err != nil {
if err := readData(args[0], &parsedData); err != nil {
var generalData interface{}
readDataOrDie(args[0], &generalData, inputJSON)
if err = readData(args[0], &generalData); err != nil {
return nil, err
}
item := yaml.MapItem{Key: "thing", Value: generalData}
parsedData = yaml.MapSlice{item}
prependCommand = "thing"
@ -246,69 +294,43 @@ func parseValue(argument string) interface{} {
return argument[1 : len(argument)-1]
}
func print(context interface{}) {
var out string
func toString(context interface{}) (string, error) {
if outputToJSON {
out = jsonToString(context)
} else {
out = yamlToString(context)
return jsonToString(context)
}
fmt.Println(out)
return yamlToString(context)
}
func yamlToString(context interface{}) string {
func yamlToString(context interface{}) (string, error) {
out, err := yaml.Marshal(context)
if err != nil {
die("error printing yaml: %v", err)
return "", fmt.Errorf("error printing yaml: %v", err)
}
outStr := string(out)
// trim the trailing new line as it's easier for a script to add
// it in if required than to remove it
if trimOutput {
return strings.Trim(outStr, "\n ")
return strings.Trim(outStr, "\n "), nil
}
return outStr
return outStr, nil
}
func readDataOrDie(filename string, parsedData interface{}, readAsJSON bool) {
err := readData(filename, parsedData, readAsJSON)
if err != nil {
die("error parsing data: ", err)
}
}
func readData(filename string, parsedData interface{}, readAsJSON bool) error {
func readData(filename string, parsedData interface{}) error {
if filename == "" {
die("Must provide filename")
return errors.New("Must provide filename")
}
var rawData []byte
var err error
if filename == "-" {
rawData = readStdin()
rawData, err = ioutil.ReadAll(os.Stdin)
} else {
rawData = readFile(filename)
rawData, err = ioutil.ReadFile(filename)
}
if err != nil {
return err
}
return yaml.Unmarshal(rawData, parsedData)
}
func readStdin() []byte {
bytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
die("error reading stdin", err)
}
return bytes
}
func readFile(filename string) []byte {
var rawData, readError = ioutil.ReadFile(filename)
if readError != nil {
die("error: %v", readError)
}
return rawData
}
func die(message ...interface{}) {
fmt.Println(message)
os.Exit(1)
}

View File

@ -24,23 +24,23 @@ func TestParseValue(t *testing.T) {
}
func TestRead(t *testing.T) {
result := read([]string{"examples/sample.yaml", "b.c"})
result, _ := read([]string{"examples/sample.yaml", "b.c"})
assertResult(t, 2, result)
}
func TestReadArray(t *testing.T) {
result := read([]string{"examples/sample_array.yaml", "[1]"})
result, _ := read([]string{"examples/sample_array.yaml", "[1]"})
assertResult(t, 2, result)
}
func TestReadString(t *testing.T) {
result := read([]string{"examples/sample_text.yaml"})
result, _ := read([]string{"examples/sample_text.yaml"})
assertResult(t, "hi", result)
}
func TestOrder(t *testing.T) {
result := read([]string{"examples/order.yaml"})
formattedResult := yamlToString(result)
result, _ := read([]string{"examples/order.yaml"})
formattedResult, _ := yamlToString(result)
assertResult(t,
`version: 3
application: MyApp`,
@ -48,7 +48,7 @@ application: MyApp`,
}
func TestNewYaml(t *testing.T) {
result := newYaml([]string{"b.c", "3"})
result, _ := newYaml([]string{"b.c", "3"})
formattedResult := fmt.Sprintf("%v", result)
assertResult(t,
"[{b [{c 3}]}]",
@ -56,7 +56,7 @@ func TestNewYaml(t *testing.T) {
}
func TestNewYamlArray(t *testing.T) {
result := newYaml([]string{"[0].cat", "meow"})
result, _ := newYaml([]string{"[0].cat", "meow"})
formattedResult := fmt.Sprintf("%v", result)
assertResult(t,
"[[{cat meow}]]",
@ -64,7 +64,7 @@ func TestNewYamlArray(t *testing.T) {
}
func TestUpdateYaml(t *testing.T) {
result := updateYaml([]string{"examples/sample.yaml", "b.c", "3"})
result, _ := updateYaml([]string{"examples/sample.yaml", "b.c", "3"})
formattedResult := fmt.Sprintf("%v", result)
assertResult(t,
"[{a Easy! as one two three} {b [{c 3} {d [3 4]} {e [[{name fred} {value 3}] [{name sam} {value 4}]]}]}]",
@ -72,7 +72,7 @@ func TestUpdateYaml(t *testing.T) {
}
func TestUpdateYamlArray(t *testing.T) {
result := updateYaml([]string{"examples/sample_array.yaml", "[0]", "3"})
result, _ := updateYaml([]string{"examples/sample_array.yaml", "[0]", "3"})
formattedResult := fmt.Sprintf("%v", result)
assertResult(t,
"[3 2 3]",
@ -81,7 +81,17 @@ func TestUpdateYamlArray(t *testing.T) {
func TestUpdateYaml_WithScript(t *testing.T) {
writeScript = "examples/instruction_sample.yaml"
updateYaml([]string{"examples/sample.yaml"})
_, _ = updateYaml([]string{"examples/sample.yaml"})
}
func TestUpdateYaml_WithUnknownScript(t *testing.T) {
writeScript = "fake-unknown"
_, err := updateYaml([]string{"examples/sample.yaml"})
if err == nil {
t.Error("Expected error due to unknown file")
}
expectedOutput := `open fake-unknown: no such file or directory`
assertResult(t, expectedOutput, err.Error())
}
func TestNewYaml_WithScript(t *testing.T) {
@ -90,7 +100,17 @@ func TestNewYaml_WithScript(t *testing.T) {
c: cat
e:
- name: Mike Farah`
result := newYaml([]string{""})
actualResult := yamlToString(result)
result, _ := newYaml([]string{""})
actualResult, _ := yamlToString(result)
assertResult(t, expectedResult, actualResult)
}
func TestNewYaml_WithUnknownScript(t *testing.T) {
writeScript = "fake-unknown"
_, err := newYaml([]string{""})
if err == nil {
t.Error("Expected error due to unknown file")
}
expectedOutput := `open fake-unknown: no such file or directory`
assertResult(t, expectedOutput, err.Error())
}