1
0
mirror of https://github.com/taigrr/yq synced 2025-01-18 04:53:17 -08:00

Compare commits

..

5 Commits
2.0.1 ... 2.1.0

Author SHA1 Message Date
Mike Farah
d84254c30f moar tests 2018-07-08 22:04:31 +10:00
Mike Farah
18bb4eee96 moar tests 2018-07-08 21:57:56 +10:00
Mike Farah
86b9fe3ef9 Can read from all documents 2018-07-08 21:47:01 +10:00
Mike Farah
2c15048ddb Added merge with append 2018-07-07 15:26:56 +10:00
Mike Farah
ce2ee42f71 Release instructions for gopkg 2018-07-01 14:45:19 +10:00
9 changed files with 195 additions and 74 deletions

View File

@@ -104,6 +104,39 @@ func TestReadCmd(t *testing.T) {
assertResult(t, "2\n", result.Output) assertResult(t, "2\n", result.Output)
} }
func TestReadInvalidDocumentIndexCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "read -df examples/sample.yaml b.c")
if result.Error == nil {
t.Error("Expected command to fail due to invalid path")
}
expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestReadBadDocumentIndexCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "read -d1 examples/sample.yaml b.c")
if result.Error == nil {
t.Error("Expected command to fail due to invalid path")
}
expectedOutput := `Asked to process document index 1 but there are only 1 document(s)`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestReadOrderCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "read examples/order.yaml")
if result.Error != nil {
t.Error(result.Error)
}
assertResult(t,
`version: 3
application: MyApp
`,
result.Output)
}
func TestReadMultiCmd(t *testing.T) { func TestReadMultiCmd(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := runCmd(cmd, "read -d 1 examples/multiple_docs.yaml another.document") result := runCmd(cmd, "read -d 1 examples/multiple_docs.yaml another.document")
@@ -113,6 +146,19 @@ func TestReadMultiCmd(t *testing.T) {
assertResult(t, "here\n", result.Output) assertResult(t, "here\n", result.Output)
} }
func TestReadMultiAllCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "read -d* examples/multiple_docs.yaml commonKey")
if result.Error != nil {
t.Error(result.Error)
}
assertResult(t,
`- first document
- second document
- third document
`, result.Output)
}
func TestReadCmd_ArrayYaml(t *testing.T) { func TestReadCmd_ArrayYaml(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := runCmd(cmd, "read examples/array.yaml [0].gather_facts") result := runCmd(cmd, "read examples/array.yaml [0].gather_facts")
@@ -192,7 +238,7 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
if result.Error == nil { if result.Error == nil {
t.Error("Expected command to fail due to invalid path") t.Error("Expected command to fail due to invalid path")
} }
expectedOutput := `Error accessing array: strconv.ParseInt: parsing "x": invalid syntax` expectedOutput := `Error reading path in document index 0: Error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
assertResult(t, expectedOutput, result.Error.Error()) assertResult(t, expectedOutput, result.Error.Error())
} }
@@ -202,7 +248,7 @@ func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) {
if result.Error == nil { if result.Error == nil {
t.Error("Expected command to fail due to invalid path") t.Error("Expected command to fail due to invalid path")
} }
expectedOutput := `Error accessing array: strconv.ParseInt: parsing "x": invalid syntax` expectedOutput := `Error reading path in document index 0: Error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
assertResult(t, expectedOutput, result.Error.Error()) assertResult(t, expectedOutput, result.Error.Error())
} }
@@ -254,7 +300,7 @@ func TestReadCmd_ErrorBadPath(t *testing.T) {
if result.Error == nil { if result.Error == nil {
t.Fatal("Expected command to fail due to invalid path") t.Fatal("Expected command to fail due to invalid path")
} }
expectedOutput := `Error accessing array: strconv.ParseInt: parsing "x": invalid syntax` expectedOutput := `Error reading path in document index 0: Error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
assertResult(t, expectedOutput, result.Error.Error()) assertResult(t, expectedOutput, result.Error.Error())
} }
@@ -367,7 +413,37 @@ apples: ok
` `
assertResult(t, expectedOutput, result.Output) assertResult(t, expectedOutput, result.Output)
} }
func TestWriteInvalidDocumentIndexCmd(t *testing.T) {
content := `b:
c: 3
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename))
if result.Error == nil {
t.Error("Expected command to fail due to invalid path")
}
expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestWriteBadDocumentIndexCmd(t *testing.T) {
content := `b:
c: 3
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
if result.Error == nil {
t.Error("Expected command to fail due to invalid path")
}
expectedOutput := `Asked to process document index 1 but there are only 1 document(s)`
assertResult(t, expectedOutput, result.Error.Error())
}
func TestWriteMultiAllCmd(t *testing.T) { func TestWriteMultiAllCmd(t *testing.T) {
content := `b: content := `b:
c: 3 c: 3
@@ -612,14 +688,46 @@ func TestMergeOverwriteCmd(t *testing.T) {
} }
expectedOutput := `a: other expectedOutput := `a: other
b: b:
- 1 - 3
- 2 - 4
c: c:
test: 1 test: 1
` `
assertResult(t, expectedOutput, result.Output) assertResult(t, expectedOutput, result.Output)
} }
func TestMergeAppendCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "merge --append examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: simple
b:
- 1
- 2
- 3
- 4
c:
test: 1
`
assertResult(t, expectedOutput, result.Output)
}
func TestMergeArraysCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `- 1
- 2
- 3
- 4
- 5
`
assertResult(t, expectedOutput, result.Output)
}
func TestMergeCmd_Multi(t *testing.T) { func TestMergeCmd_Multi(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := runCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data2.yaml") result := runCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data2.yaml")
@@ -631,6 +739,9 @@ func TestMergeCmd_Multi(t *testing.T) {
a: other a: other
another: another:
document: here document: here
b:
- 3
- 4
c: c:
test: 1 test: 1
--- ---

View File

@@ -1,3 +1,4 @@
a: other a: other
b: [3, 4]
c: c:
test: 1 test: 1

View File

@@ -16,7 +16,3 @@ another:
commonKey: third document commonKey: third document
wow: wow:
- here is another - here is another
---
- 1
- 2
- 3

View File

@@ -0,0 +1 @@
[4,5]

View File

@@ -2,9 +2,11 @@ package main
import "gopkg.in/imdario/mergo.v0" import "gopkg.in/imdario/mergo.v0"
func merge(dst, src interface{}, overwrite bool) error { func merge(dst interface{}, src interface{}, overwrite bool, append bool) error {
if overwrite { if overwrite {
return mergo.MergeWithOverwrite(dst, src) return mergo.Merge(dst, src, mergo.WithOverride)
} else if append {
return mergo.Merge(dst, src, mergo.WithAppendSlice)
} }
return mergo.Merge(dst, src) return mergo.Merge(dst, src)
} }

View File

@@ -1,6 +1,6 @@
- increment version in version.go - increment version in version.go
- increment version in snapcraft.yaml - increment version in snapcraft.yaml
- tag git with same version number - tag git with same version number, be sure to start with 'v' for gopkg.in
- make sure local build passes - make sure local build passes
- push tag to git - push tag to git
- make local xcompile (builds binaries for all platforms) - make local xcompile (builds binaries for all platforms)

View File

@@ -11,7 +11,7 @@ var (
GitDescribe string GitDescribe string
// Version is main version number that is being run at the moment. // Version is main version number that is being run at the moment.
Version = "2.0.1" Version = "2.1.0"
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release // then it means that it is a final release. Otherwise, this is a pre-release

101
yq.go
View File

@@ -22,6 +22,7 @@ var writeInplace = false
var writeScript = "" var writeScript = ""
var outputToJSON = false var outputToJSON = false
var overwriteFlag = false var overwriteFlag = false
var appendFlag = false
var verbose = false var verbose = false
var version = false var version = false
var docIndex = "0" var docIndex = "0"
@@ -96,7 +97,7 @@ yq r things.yaml a.array[*].blah
Long: "Outputs the value of the given path in the yaml file to STDOUT", Long: "Outputs the value of the given path in the yaml file to STDOUT",
RunE: readProperty, RunE: readProperty,
} }
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number, 0 based") cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
cmdRead.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json") cmdRead.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
return cmdRead return cmdRead
} }
@@ -183,33 +184,83 @@ func createMergeCmd() *cobra.Command {
var cmdMerge = &cobra.Command{ var cmdMerge = &cobra.Command{
Use: "merge [initial_yaml_file] [additional_yaml_file]...", Use: "merge [initial_yaml_file] [additional_yaml_file]...",
Aliases: []string{"m"}, Aliases: []string{"m"},
Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] sample.yaml sample2.yaml", Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml",
Example: ` Example: `
yq merge things.yaml other.yaml yq merge things.yaml other.yaml
yq merge --inplace things.yaml other.yaml yq merge --inplace things.yaml other.yaml
yq m -i things.yaml other.yaml yq m -i things.yaml other.yaml
yq m --overwrite things.yaml other.yaml yq m --overwrite things.yaml other.yaml
yq m -i -x things.yaml other.yaml yq m -i -x things.yaml other.yaml
yq m -i -a things.yaml other.yaml
`, `,
Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
Note that if you set both flags only overwrite will take effect.
`, `,
RunE: mergeProperties, RunE: mergeProperties,
} }
cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdMerge return cmdMerge
} }
func readProperty(cmd *cobra.Command, args []string) error { func readProperty(cmd *cobra.Command, args []string) error {
data, err := read(args) var path = ""
if err != nil {
return err if len(args) < 1 {
return errors.New("Must provide filename")
} else if len(args) > 1 {
path = args[1]
} }
dataStr, err := toString(data)
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
var mappedDocs []interface{}
var dataBucket interface{}
var currentIndex = 0
var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error {
for {
errorReading := decoder.Decode(&dataBucket)
if errorReading == io.EOF {
log.Debugf("done %v / %v", currentIndex, docIndexInt)
if !updateAll && currentIndex <= docIndexInt {
return fmt.Errorf("Asked to process document index %v but there are only %v document(s)", docIndex, currentIndex)
}
return nil
}
log.Debugf("processing %v - requested index %v", currentIndex, docIndexInt)
if updateAll || currentIndex == docIndexInt {
log.Debugf("reading %v in index %v", path, currentIndex)
mappedDoc, errorParsing := readPath(dataBucket, path)
log.Debugf("%v", mappedDoc)
if errorParsing != nil {
return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
}
mappedDocs = append(mappedDocs, mappedDoc)
}
currentIndex = currentIndex + 1
}
})
if errorReadingStream != nil {
return errorReadingStream
}
if !updateAll {
dataBucket = mappedDocs[0]
} else {
dataBucket = mappedDocs
}
dataStr, err := toString(dataBucket)
if err != nil { if err != nil {
return err return err
} }
@@ -217,28 +268,13 @@ func readProperty(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func read(args []string) (interface{}, error) { func readPath(dataBucket interface{}, path string) (interface{}, error) {
var path = ""
if len(args) < 1 {
return nil, errors.New("Must provide filename")
} else if len(args) > 1 {
path = args[1]
}
var generalData interface{}
var docIndexInt, errorParsingDocumentIndex = strconv.ParseInt(docIndex, 10, 32)
if errorParsingDocumentIndex != nil {
return nil, errors.Wrapf(errorParsingDocumentIndex, "Document index %v is not a integer", docIndex)
}
if err := readData(args[0], int(docIndexInt), &generalData); err != nil {
return nil, err
}
if path == "" { if path == "" {
return generalData, nil log.Debug("no path")
return dataBucket, nil
} }
var paths = parsePath(path) var paths = parsePath(path)
return recurse(generalData, paths[0], paths[1:]) return recurse(dataBucket, paths[0], paths[1:])
} }
func newProperty(cmd *cobra.Command, args []string) error { func newProperty(cmd *cobra.Command, args []string) error {
@@ -310,8 +346,8 @@ func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderF
errorReading = decoder.Decode(&dataBucket) errorReading = decoder.Decode(&dataBucket)
if errorReading == io.EOF { if errorReading == io.EOF {
if !updateAll && currentIndex < docIndexInt { if !updateAll && currentIndex <= docIndexInt {
return fmt.Errorf("Asked to process document %v but there are only %v document(s)", docIndex, currentIndex) return fmt.Errorf("Asked to process document index %v but there are only %v document(s)", docIndex, currentIndex)
} }
return nil return nil
} else if errorReading != nil { } else if errorReading != nil {
@@ -420,7 +456,11 @@ func mergeProperties(cmd *cobra.Command, args []string) error {
if updateAll || currentIndex == docIndexInt { if updateAll || currentIndex == docIndexInt {
log.Debugf("Merging doc %v", currentIndex) log.Debugf("Merging doc %v", currentIndex)
var mergedData map[interface{}]interface{} var mergedData map[interface{}]interface{}
if err := merge(&mergedData, dataBucket, overwriteFlag); err != nil { // merge only works for maps, so put everything in a temporary
// map
var mapDataBucket = make(map[interface{}]interface{})
mapDataBucket["root"] = dataBucket
if err := merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
return nil, err return nil, err
} }
for _, f := range filesToMerge { for _, f := range filesToMerge {
@@ -428,11 +468,12 @@ func mergeProperties(cmd *cobra.Command, args []string) error {
if err := readData(f, 0, &fileToMerge); err != nil { if err := readData(f, 0, &fileToMerge); err != nil {
return nil, err return nil, err
} }
if err := merge(&mergedData, fileToMerge, overwriteFlag); err != nil { mapDataBucket["root"] = fileToMerge
if err := merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
return nil, err return nil, err
} }
} }
return mergedData, nil return mergedData["root"], nil
} }
return dataBucket, nil return dataBucket, nil
} }

View File

@@ -23,37 +23,6 @@ func TestParseValue(t *testing.T) {
} }
} }
func TestRead(t *testing.T) {
result, _ := read([]string{"examples/sample.yaml", "b.c"})
assertResult(t, 2, result)
}
func TestReadMulti(t *testing.T) {
docIndex = "1"
result, _ := read([]string{"examples/multiple_docs.yaml", "another.document"})
assertResult(t, "here", result)
docIndex = "0"
}
func TestReadArray(t *testing.T) {
result, _ := read([]string{"examples/sample_array.yaml", "[1]"})
assertResult(t, 2, result)
}
func TestReadString(t *testing.T) {
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)
assertResult(t,
`version: 3
application: MyApp`,
formattedResult)
}
func TestMultilineString(t *testing.T) { func TestMultilineString(t *testing.T) {
testString := ` testString := `
abcd abcd