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

Compare commits

..

9 Commits
1.3 ... 1.7

Author SHA1 Message Date
Mike Farah
a3190f1504 Refactor write to allow new entries 2017-04-12 20:25:13 +10:00
Mike Farah
44ee869e47 Maintain order 2017-02-27 09:09:29 +11:00
Mike Farah
5d2d37a5f8 Fixed travis script, added Travis banner to readme 2017-02-13 07:55:06 +11:00
Mike Farah
e3fc944709 Added travis config 2017-02-13 07:28:48 +11:00
Mike Farah
6ee9db9a70 Added License (MIT) 2017-02-10 16:00:25 +11:00
Mike Farah
0d75edfb67 Fixed updating array bug (Issue 3 in git) 2017-02-09 13:58:47 +11:00
Mike Farah
037314af8a Merge pull request #2 from chaos95/master
Handle 'index out of range' errors more gracefully
2016-03-19 13:05:57 +11:00
Morgan Larosa
2a283b4ef7 Fix #1 2016-03-16 16:11:06 +11:00
Mike Farah
7ef556f22b Update README.md 2015-11-20 21:42:45 +11:00
13 changed files with 301 additions and 52 deletions

4
.travis.yml Normal file
View File

@@ -0,0 +1,4 @@
language: go
go:
- 1.7.x
script: ./ci.sh

19
LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2017 Mike Farah
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,8 +1,14 @@
# yaml
# yaml [![Build Status](https://travis-ci.org/mikefarah/yaml.svg?branch=master)](https://travis-ci.org/mikefarah/yaml)
yaml is a lightweight and flexible command-line YAML processor
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.
## Install
[Download latest binary](https://github.com/mikefarah/yaml/releases/latest) or alternatively:
```
go get github.com/mikefarah/yaml
```
## Features
- Written in portable go, so you can download a lovely dependency free binary
- Deep read a yaml file with a given path
@@ -11,11 +17,6 @@ The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed
- Convert from json to yaml
- Convert from yaml to json
[Download latest binary](https://github.com/mikefarah/yaml/releases/latest) or alternatively:
```
go get github.com/mikefarah/yaml
```
## Read examples
```
yaml r <yaml file> <path>

15
ci.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
set -e
go test
go build
# acceptance test
X=$(./yaml w sample.yaml b.c 3 | ./yaml r - b.c)
if [ $X != 3 ]
then
echo "Failed acceptance test: expected 2 but was $X"
exit 1
fi

View File

@@ -1,38 +1,118 @@
package main
import (
// "fmt"
"github.com/mikefarah/yaml/Godeps/_workspace/src/gopkg.in/yaml.v2"
"strconv"
)
func write(context map[interface{}]interface{}, head string, tail []string, value interface{}) {
if len(tail) == 0 {
context[head] = value
} else {
// e.g. if updating a.b.c, we need to get the 'b' map...
toUpdate := readMap(context, head, tail[0:len(tail)-1]).(map[interface{}]interface{})
// and then set the 'c' key.
key := (tail[len(tail)-1])
toUpdate[key] = value
func entryInSlice(context yaml.MapSlice, key interface{}) *yaml.MapItem {
for idx := range context {
var entry = &context[idx]
if entry.Key == key {
return entry
}
}
return nil
}
func readMap(context map[interface{}]interface{}, head string, tail []string) interface{} {
func writeMap(context interface{}, paths []string, value interface{}) yaml.MapSlice {
log.Debugf("writeMap for %v for %v with value %v\n", paths, context, value)
var mapSlice yaml.MapSlice
switch context.(type) {
case yaml.MapSlice:
mapSlice = context.(yaml.MapSlice)
default:
mapSlice = make(yaml.MapSlice, 0)
}
if len(paths) == 0 {
return mapSlice
}
child := entryInSlice(mapSlice, paths[0])
if child == nil {
newChild := yaml.MapItem{Key: paths[0]}
mapSlice = append(mapSlice, newChild)
child = entryInSlice(mapSlice, paths[0])
log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice)
}
log.Debugf("\tchild.Value %v\n", child.Value)
remainingPaths := paths[1:len(paths)]
child.Value = updatedChildValue(child.Value, remainingPaths, value)
log.Debugf("\tReturning mapSlice %v\n", mapSlice)
return mapSlice
}
func updatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} {
if len(remainingPaths) == 0 {
return value
}
_, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64)
if nextIndexErr != nil {
// must be a map
return writeMap(child, remainingPaths, value)
}
// must be an array
return writeArray(child, remainingPaths, value)
}
func writeArray(context interface{}, paths []string, value interface{}) []interface{} {
log.Debugf("writeArray for %v for %v with value %v\n", paths, context, value)
var array []interface{}
switch context.(type) {
case []interface{}:
array = context.([]interface{})
default:
array = make([]interface{}, 1)
}
if len(paths) == 0 {
return array
}
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)
}
currentChild := array[index]
log.Debugf("\tcurrentChild %v\n", currentChild)
remainingPaths := paths[1:len(paths)]
array[index] = updatedChildValue(currentChild, remainingPaths, value)
log.Debugf("\tReturning array %v\n", array)
return array
}
func readMap(context yaml.MapSlice, head string, tail []string) interface{} {
if head == "*" {
return readMapSplat(context, tail)
}
value := context[head]
var value interface{}
entry := entryInSlice(context, head)
if entry != nil {
value = entry.Value
}
return calculateValue(value, tail)
}
func readMapSplat(context map[interface{}]interface{}, tail []string) interface{} {
func readMapSplat(context yaml.MapSlice, tail []string) interface{} {
var newArray = make([]interface{}, len(context))
var i = 0
for _, value := range context {
for _, entry := range context {
if len(tail) > 0 {
newArray[i] = recurse(value, tail[0], tail[1:len(tail)])
newArray[i] = recurse(entry.Value, tail[0], tail[1:len(tail)])
} else {
newArray[i] = value
newArray[i] = entry.Value
}
i++
}
@@ -50,15 +130,15 @@ func recurse(value interface{}, head string, tail []string) interface{} {
die("Error accessing array: %v", err)
}
return readArray(value.([]interface{}), index, tail)
case map[interface{}]interface{}:
return readMap(value.(map[interface{}]interface{}), head, tail)
case yaml.MapSlice:
return readMap(value.(yaml.MapSlice), head, tail)
default:
return nil
}
}
func readArray(array []interface{}, head int64, tail []string) interface{} {
if head > int64(len(array)) {
if head >= int64(len(array)) {
return nil
}

View File

@@ -2,10 +2,19 @@ package main
import (
"fmt"
"github.com/mikefarah/yaml/Godeps/_workspace/src/gopkg.in/yaml.v2"
"github.com/op/go-logging"
"os"
"sort"
"testing"
)
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(`
---
@@ -83,6 +92,17 @@ b:
assertResult(t, nil, readMap(data, "b", []string{"d", "3"}))
}
func TestReadMap_with_array_out_of_bounds_by_1(t *testing.T) {
var data = parseData(`
---
b:
d:
- 3
- 4
`)
assertResult(t, nil, readMap(data, "b", []string{"d", "2"}))
}
func TestReadMap_with_array_splat(t *testing.T) {
var data = parseData(`
e:
@@ -96,16 +116,93 @@ e:
assertResult(t, "[Fred Sam]", fmt.Sprintf("%v", readMap(data, "e", []string{"*", "name"})))
}
func TestWrite_really_simple(t *testing.T) {
var data = parseData(`
b: 2
`)
updated := writeMap(data, []string{"b"}, "4")
b := entryInSlice(updated, "b").Value
assertResult(t, "4", b)
}
func TestWrite_simple(t *testing.T) {
var data = parseData(`
b:
c: 2
`)
write(data, "b", []string{"c"}, "4")
updated := writeMap(data, []string{"b", "c"}, "4")
b := entryInSlice(updated, "b").Value.(yaml.MapSlice)
c := entryInSlice(b, "c").Value
assertResult(t, "4", c)
}
b := data["b"].(map[interface{}]interface{})
assertResult(t, "4", b["c"].(string))
func TestWrite_new(t *testing.T) {
var data = parseData(`
b:
c: 2
`)
updated := writeMap(data, []string{"b", "d"}, "4")
b := entryInSlice(updated, "b").Value.(yaml.MapSlice)
d := entryInSlice(b, "d").Value
assertResult(t, "4", d)
}
func TestWrite_new_deep(t *testing.T) {
var data = parseData(`
b:
c: 2
`)
updated := writeMap(data, []string{"b", "d", "f"}, "4")
assertResult(t, "4", readMap(updated, "b", []string{"d", "f"}))
}
func TestWrite_array(t *testing.T) {
var data = parseData(`
b:
- aa
`)
updated := writeMap(data, []string{"b", "0"}, "bb")
b := entryInSlice(updated, "b").Value.([]interface{})
assertResult(t, "bb", b[0].(string))
}
func TestWrite_new_array(t *testing.T) {
var data = parseData(`
b:
c: 2
`)
updated := writeMap(data, []string{"b", "0"}, "4")
assertResult(t, "4", readMap(updated, "b", []string{"0"}))
}
func TestWrite_new_array_deep(t *testing.T) {
var data = parseData(`
b:
c: 2
`)
var expected = `b:
- c: "4"`
updated := writeMap(data, []string{"b", "0", "c"}, "4")
assertResult(t, expected, yamlToString(updated))
}
func TestWrite_new_map_array_deep(t *testing.T) {
var data = parseData(`
b:
c: 2
`)
updated := writeMap(data, []string{"b", "d", "0"}, "4")
assertResult(t, "4", readMap(updated, "b", []string{"d", "0"}))
}
func TestWrite_with_no_tail(t *testing.T) {
@@ -113,8 +210,8 @@ func TestWrite_with_no_tail(t *testing.T) {
b:
c: 2
`)
write(data, "b", []string{}, "4")
updated := writeMap(data, []string{"b"}, "4")
b := data["b"]
b := entryInSlice(updated, "b").Value
assertResult(t, "4", fmt.Sprintf("%v", b))
}

View File

@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"github.com/mikefarah/yaml/Godeps/_workspace/src/gopkg.in/yaml.v2"
)
func fromJSONBytes(jsonBytes []byte, parsedData *map[interface{}]interface{}) {
@@ -55,11 +56,11 @@ func toJSON(context interface{}) interface{} {
newArray[index] = toJSON(value)
}
return newArray
case map[interface{}]interface{}:
oldMap := context.(map[interface{}]interface{})
case yaml.MapSlice:
oldMap := context.(yaml.MapSlice)
newMap := make(map[string]interface{})
for key, value := range oldMap {
newMap[key.(string)] = toJSON(value)
for _, entry := range oldMap {
newMap[entry.Key.(string)] = toJSON(entry.Value)
}
return newMap
default:

2
order.yaml Normal file
View File

@@ -0,0 +1,2 @@
version: 3
application: MyApp

6
order.yml Normal file
View File

@@ -0,0 +1,6 @@
version: '2'
services:
test:
image: ubuntu:14.04
stdin_open: true
tty: true

View File

@@ -1,17 +1,9 @@
#!/bin/bash
set -e
gofmt -w .
golint
go test
go build
# acceptance test
X=$(./yaml w sample.yaml b.c 3 | ./yaml r - b.c)
if [ $X != 3 ]
then
echo "Failed acceptance test: expected 2 but was $X"
exit 1
fi
./ci.sh
go install

View File

@@ -7,8 +7,8 @@ import (
"testing"
)
func parseData(rawData string) map[interface{}]interface{} {
var parsedData map[interface{}]interface{}
func parseData(rawData string) yaml.MapSlice {
var parsedData yaml.MapSlice
err := yaml.Unmarshal([]byte(rawData), &parsedData)
if err != nil {
fmt.Println("Error parsing yaml: %v", err)

26
yaml.go
View File

@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/mikefarah/yaml/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/mikefarah/yaml/Godeps/_workspace/src/gopkg.in/yaml.v2"
"github.com/op/go-logging"
"io/ioutil"
"os"
"strconv"
@@ -15,8 +16,18 @@ 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() {
backend.SetLevel(logging.ERROR, "")
logging.SetBackend(backend)
var cmdRead = createReadCmd()
var cmdWrite = createWriteCmd()
@@ -24,7 +35,7 @@ func main() {
rootCmd.PersistentFlags().BoolVarP(&trimOutput, "trim", "t", true, "trim yaml output")
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
rootCmd.PersistentFlags().BoolVarP(&inputJSON, "fromjson", "J", false, "input as json")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
rootCmd.AddCommand(cmdRead, cmdWrite)
rootCmd.Execute()
}
@@ -77,11 +88,14 @@ a.b.e:
}
func readProperty(cmd *cobra.Command, args []string) {
if verbose {
backend.SetLevel(logging.DEBUG, "")
}
print(read(args))
}
func read(args []string) interface{} {
var parsedData map[interface{}]interface{}
var parsedData yaml.MapSlice
readData(args[0], &parsedData, inputJSON)
@@ -95,6 +109,9 @@ func read(args []string) interface{} {
}
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)
@@ -114,13 +131,14 @@ func updateYaml(args []string) interface{} {
writeCommands[args[1]] = parseValue(args[2])
}
var parsedData map[interface{}]interface{}
var parsedData yaml.MapSlice
readData(args[0], &parsedData, inputJSON)
for path, value := range writeCommands {
var paths = parsePath(path)
write(parsedData, paths[0], paths[1:len(paths)], value)
parsedData = writeMap(parsedData, paths, value)
}
return parsedData
}

View File

@@ -1,6 +1,7 @@
package main
import (
"fmt"
"testing"
)
@@ -26,8 +27,21 @@ func TestRead(t *testing.T) {
assertResult(t, 2, result)
}
func TestOrder(t *testing.T) {
result := read([]string{"order.yaml"})
formattedResult := yamlToString(result)
assertResult(t,
`version: 3
application: MyApp`,
formattedResult)
}
func TestUpdateYaml(t *testing.T) {
updateYaml([]string{"sample.yaml", "b.c", "3"})
result := updateYaml([]string{"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}]]}]}]",
formattedResult)
}
func TestUpdateYaml_WithScript(t *testing.T) {