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

Compare commits

..

31 Commits

Author SHA1 Message Date
Mike Farah
d46d555b07 Incrementing version 2018-06-20 14:29:10 +10:00
Mike Farah
fb87f638f2 Multi doc supports updating all docs 2018-06-20 11:45:51 +10:00
Mike Farah
facc81d1f4 github version of mousetrap required for xcompile 2018-06-20 08:14:14 +10:00
Mike Farah
c1f9065c68 pflag has to be github :eye_roll: 2018-06-18 20:12:09 +10:00
Mike Farah
be84cc3082 Add pflag back in 2018-06-18 20:08:41 +10:00
Mike Farah
a2571da1a1 Updated docs to refer to gopkg.in when using go get 2018-06-18 11:45:46 +10:00
Mike Farah
6d6e476ac8 Use gopkg managed versions of dependencies, for better go get support 2018-06-18 11:37:42 +10:00
Mike Farah
ae0c042ae6 Use gopkg managed version of yaml to properly support go get 2018-06-18 11:12:52 +10:00
Mike Farah
867ec92d3a Version bump 2018-06-15 20:50:20 +10:00
Mike Farah
113586b5e0 Updating help for multi doc 2018-06-15 20:31:29 +10:00
Mike Farah
c38f19e0a9 Enabled multi document support for merge (first document only) 2018-06-15 16:48:36 +10:00
Mike Farah
8ca85b1c64 Simplified merge command 2018-06-15 16:40:52 +10:00
Mike Farah
08870f8ec9 Simplified 'new' command 2018-06-15 16:21:18 +10:00
Mike Farah
94b217984c Better error handling 2018-06-15 16:11:13 +10:00
Mike Farah
2f5a481cc3 Detect when there is no document X to update 2018-06-15 09:54:11 +10:00
Mike Farah
1a4064429d Delete now supports multi docs! 2018-06-15 09:43:20 +10:00
Mike Farah
1b22e1d812 Fixed delete command for arrays 2018-06-15 09:03:42 +10:00
Mike Farah
297522cbdd Write supports multidoc yaml, better use of yaml library streaming 2018-06-15 08:39:59 +10:00
Mike Farah
be991fdacd Read test 2018-06-15 08:39:29 +10:00
Mike Farah
be08214773 fixed version test 2018-06-13 10:00:01 +10:00
Mike Farah
9e971ebeae Beta version of multiple document support for read, write coming soon 2018-06-13 09:26:52 +10:00
Mike Farah
f340db5795 Extract out reading of write commands 2018-06-13 09:24:37 +10:00
Mike Farah
ab852ceafa Separate reading stream from processing 2018-06-13 09:11:54 +10:00
Mike Farah
06a843e9b2 Read now handles multiple documents 2018-06-12 15:41:09 +10:00
Mike Farah
ebdc092688 Updating user docs 2018-06-12 10:17:09 +10:00
Roberto Mier Escandon
27089d1ca1 Updated some documentation pointing to deb install option 2018-06-12 10:12:55 +10:00
Roberto Mier Escandon
1853585d22 Add debian packaging 2018-06-12 10:12:55 +10:00
Mike Farah
0124d26086 Added mkdocs instructions to Contribute 2018-06-12 10:11:15 +10:00
Mike Farah
0d68bea3dc Release instructions 2018-05-08 10:55:16 +10:00
Mike Farah
54603b3607 Updated version in snapcraft.yml 2018-05-08 10:06:41 +10:00
Mike Farah
c86aeca325 Fixed script for publishing from a mac 2018-05-07 20:39:18 +10:00
30 changed files with 721 additions and 341 deletions

View File

@@ -12,9 +12,15 @@ On Ubuntu and other Linux distros supporting `snap` packages:
``` ```
snap install yq snap install yq
``` ```
On Ubuntu 16.04 or higher from Debian package:
```
sudo add-apt-repository ppa:rmescandon/yq
sudo apt update
sudo apt install yq -y
```
or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively: or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively:
``` ```
go get github.com/mikefarah/yq go get gopkg.in/mikefarah/yq.v1
``` ```
## Run with Docker ## Run with Docker
@@ -75,6 +81,10 @@ Use "yq [command] --help" for more information about a command.
## Contribute ## Contribute
1. `make [local] vendor` 1. `make [local] vendor`
2. add unit tests 2. add unit tests
3. apply changes 3. apply changes (use govendor with a preference to [gopkg](https://gopkg.in/) for package dependencies)
4. `make [local] build` 4. `make [local] build`
5. profit 5. If required, update the user documentation
- Update README.md and/or documentation under the mkdocs folder
- `make [local] build-docs`
- browse to docs/index.html and check your changes
6. profit

View File

@@ -5,7 +5,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/spf13/cobra" "gopkg.in/spf13/cobra.v0"
) )
func getRootCommand() *cobra.Command { func getRootCommand() *cobra.Command {
@@ -128,6 +128,15 @@ func TestReadCmd(t *testing.T) {
assertResult(t, "2\n", result.Output) assertResult(t, "2\n", result.Output)
} }
func TestReadMultiCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "read -d 1 examples/multiple_docs.yaml another.document")
if result.Error != nil {
t.Error(result.Error)
}
assertResult(t, "here\n", 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")
@@ -363,6 +372,50 @@ func TestWriteCmd(t *testing.T) {
assertResult(t, expectedOutput, result.Output) assertResult(t, expectedOutput, result.Output)
} }
func TestWriteMultiCmd(t *testing.T) {
content := `b:
c: 3
---
apples: great
`
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(result.Error)
}
expectedOutput := `b:
c: 3
---
apples: ok
`
assertResult(t, expectedOutput, result.Output)
}
func TestWriteMultiAllCmd(t *testing.T) {
content := `b:
c: 3
---
apples: great
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 3
apples: ok
---
apples: ok`
assertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
func TestWriteCmd_EmptyArray(t *testing.T) { func TestWriteCmd_EmptyArray(t *testing.T) {
content := `b: 3` content := `b: 3`
filename := writeTempYamlFile(content) filename := writeTempYamlFile(content)
@@ -432,7 +485,7 @@ func TestWriteCmd_Inplace(t *testing.T) {
gotOutput := readTempYamlFile(filename) gotOutput := readTempYamlFile(filename)
expectedOutput := `b: expectedOutput := `b:
c: 7` c: 7`
assertResult(t, expectedOutput, gotOutput) assertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
} }
func TestWriteCmd_Append(t *testing.T) { func TestWriteCmd_Append(t *testing.T) {
@@ -472,6 +525,95 @@ b:
assertResult(t, expectedOutput, result.Output) assertResult(t, expectedOutput, result.Output)
} }
func TestDeleteYaml(t *testing.T) {
content := `a: 2
b:
c: things
d: something else
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: 2
b:
d: something else
`
assertResult(t, expectedOutput, result.Output)
}
func TestDeleteYamlArray(t *testing.T) {
content := `- 1
- 2
- 3
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("delete %s [1]", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `- 1
- 3
`
assertResult(t, expectedOutput, result.Output)
}
func TestDeleteYamlMulti(t *testing.T) {
content := `apples: great
---
- 1
- 2
- 3
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("delete -d 1 %s [1]", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `apples: great
---
- 1
- 3
`
assertResult(t, expectedOutput, result.Output)
}
func TestDeleteYamlMultiAllCmd(t *testing.T) {
content := `b:
c: 3
apples: great
---
apples: great
something: else
`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("delete %s -d * apples", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 3
---
something: else`
assertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
func TestMergeCmd(t *testing.T) { func TestMergeCmd(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := runCmd(cmd, "merge examples/data1.yaml examples/data2.yaml") result := runCmd(cmd, "merge examples/data1.yaml examples/data2.yaml")
@@ -488,6 +630,99 @@ c:
assertResult(t, expectedOutput, result.Output) assertResult(t, expectedOutput, result.Output)
} }
func TestMergeOverwriteCmd(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "merge --overwrite examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: other
b:
- 1
- 2
c:
test: 1
`
assertResult(t, expectedOutput, result.Output)
}
func TestMergeCmd_Multi(t *testing.T) {
cmd := getRootCommand()
result := runCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: Easy! as one two three
---
a: other
another:
document: here
c:
test: 1
---
- 1
- 2`
assertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
func TestMergeYamlMultiAllCmd(t *testing.T) {
content := `b:
c: 3
apples: green
---
something: else`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
mergeContent := `apples: red
something: good`
mergeFilename := writeTempYamlFile(mergeContent)
defer removeTempYamlFile(mergeFilename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("merge -d* %s %s", filename, mergeFilename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `apples: green
b:
c: 3
something: good
---
apples: red
something: else`
assertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) {
content := `b:
c: 3
apples: green
---
something: else`
filename := writeTempYamlFile(content)
defer removeTempYamlFile(filename)
mergeContent := `apples: red
something: good`
mergeFilename := writeTempYamlFile(mergeContent)
defer removeTempYamlFile(mergeFilename)
cmd := getRootCommand()
result := runCmd(cmd, fmt.Sprintf("merge --overwrite -d* %s %s", filename, mergeFilename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `apples: red
b:
c: 3
something: good
---
apples: red
something: good`
assertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
func TestMergeCmd_Error(t *testing.T) { func TestMergeCmd_Error(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := runCmd(cmd, "merge examples/data1.yaml") result := runCmd(cmd, "merge examples/data1.yaml")
@@ -504,7 +739,7 @@ func TestMergeCmd_ErrorUnreadableFile(t *testing.T) {
if result.Error == nil { if result.Error == nil {
t.Error("Expected command to fail due to unknown file") t.Error("Expected command to fail due to unknown file")
} }
expectedOutput := `open fake-unknown: no such file or directory` expectedOutput := `Error updating document at index 0: open fake-unknown: no such file or directory`
assertResult(t, expectedOutput, result.Error.Error()) assertResult(t, expectedOutput, result.Error.Error())
} }
@@ -540,5 +775,5 @@ b:
- 2 - 2
c: c:
test: 1` test: 1`
assertResult(t, expectedOutput, gotOutput) assertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
} }

View File

@@ -2,10 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"sort"
"strconv" "strconv"
"gopkg.in/yaml.v2" yaml "gopkg.in/mikefarah/yaml.v2"
) )
func entryInSlice(context yaml.MapSlice, key interface{}) *yaml.MapItem { func entryInSlice(context yaml.MapSlice, key interface{}) *yaml.MapItem {
@@ -194,23 +193,6 @@ func calculateValue(value interface{}, tail []string) (interface{}, error) {
return value, nil return value, nil
} }
func mapToMapSlice(data map[interface{}]interface{}) yaml.MapSlice {
var mapSlice yaml.MapSlice
for k, v := range data {
if mv, ok := v.(map[interface{}]interface{}); ok {
v = mapToMapSlice(mv)
}
item := yaml.MapItem{Key: k, Value: v}
mapSlice = append(mapSlice, item)
}
// because the parsing of the yaml was done via a map the order will be inconsistent
// apply order to allow a consistent output
sort.SliceStable(mapSlice, func(i, j int) bool { return mapSlice[i].Key.(string) < mapSlice[j].Key.(string) })
return mapSlice
}
func deleteMap(context interface{}, paths []string) yaml.MapSlice { func deleteMap(context interface{}, paths []string) yaml.MapSlice {
log.Debugf("deleteMap for %v for %v\n", paths, context) log.Debugf("deleteMap for %v for %v\n", paths, context)

View File

@@ -5,7 +5,7 @@ import (
"sort" "sort"
"testing" "testing"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/mikefarah/yaml.v2"
) )
func TestReadMap_simple(t *testing.T) { func TestReadMap_simple(t *testing.T) {

6
debian/changelog vendored Normal file
View File

@@ -0,0 +1,6 @@
yq (1.15-0) bionic; urgency=medium
* Release 1.15
-- Roberto Mier EscandĂłn <rmescandon@gmail.com> Wed, 06 Jun 2018 11:32:03 +0200

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
9

22
debian/control vendored Normal file
View File

@@ -0,0 +1,22 @@
Source: yq
Section: devel
Priority: extra
Maintainer: Roberto Mier EscandĂłn <rmescandon@gmail.com>
Build-Depends: debhelper (>= 9),
dh-golang,
golang-1.10-go,
rsync
Standards-Version: 3.9.6
Homepage: https://github.com/mikefarah/yq.git
Vcs-Browser: https://github.com/mikefarah/yq.git
Vcs-Git: https://github.com/mikefarah/yq.git
Package: yq
Architecture: all
Built-Using: ${misc:Built-Using}
Depends: ${shlibs:Depends},
${misc:Depends}
Description:
a lightweight and portable command-line YAML processor
.
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.

7
debian/copyright vendored Normal file
View File

@@ -0,0 +1,7 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: yq
Source: https://github.com/mikefarah/yq.git
Files: *
Copyright: 2017 Mike Farah Ltd. All rights reserved
License: Proprietary

2
debian/gbp.conf vendored Normal file
View File

@@ -0,0 +1,2 @@
[DEFAULT]
pristine-tar = True

59
debian/rules vendored Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/make -f
#
# Copyright (C) 2018 Roberto Mier EscandĂłn <rmescandon@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
PROJECT := yq
OWNER := mikefarah
REPO := github.com
GOVERSION := 1.10
export DH_OPTIONS
export DH_GOPKG := ${REPO}/${OWNER}/${PROJECT}
export GOROOT := /usr/lib/go-${GOVERSION}
export GOPATH := ${CURDIR}/_build
export GOBIN := ${GOPATH}/bin
export PATH := ${GOROOT}/bin:${GOBIN}:${PATH}
BLDPATH := $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)
SRCDIR := ${CURDIR}/_build/src/${DH_GOPKG}
DESTDIR := ${CURDIR}/debian/${PROJECT}
BINDIR := /usr/bin
ASSETSDIR := /usr/share/${PROJECT}
%:
dh $@ --buildsystem=golang --with=golang
override_dh_auto_build:
mkdir -p ${SRCDIR}
mkdir -p ${GOBIN}
# copy project to local srcdir to build from there
rsync -avz --progress --exclude=obj-${BLDPATH} --exclude=debian . $(SRCDIR)
# build go code
(cd ${SRCDIR} && go install ./...)
override_dh_auto_test:
(cd ${SRCDIR} && go test -v ./...)
override_dh_auto_install:
mkdir -p ${DESTDIR}/${BINDIR}
mkdir -p ${DESTDIR}/${ASSETSDIR}
cp ${CURDIR}/_build/bin/yq ${DESTDIR}/${BINDIR}
cp -rf ${SRCDIR}/LICENSE ${DESTDIR}/${ASSETSDIR}
cp -rf ${SRCDIR}/README.md ${DESTDIR}/${PLUGINSDIR}
chmod a+x ${DESTDIR}/${BINDIR}/yq
override_dh_auto_clean:
dh_clean
rm -rf ${CURDIR}/obj-${BLDPATH}
rm -rf ${CURDIR}/_build

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (native)

View File

@@ -373,8 +373,14 @@
<pre><code>snap install yq <pre><code>snap install yq
</code></pre> </code></pre>
<p>On Ubuntu 16.04 or higher from Debian package:</p>
<pre><code>sudo add-apt-repository ppa:rmescandon/yq
sudo apt update
sudo apt install yq -y
</code></pre>
<p>or, <a href="https://github.com/mikefarah/yq/releases/latest">Download latest binary</a> or alternatively:</p> <p>or, <a href="https://github.com/mikefarah/yq/releases/latest">Download latest binary</a> or alternatively:</p>
<pre><code>go get github.com/mikefarah/yq <pre><code>go get gopkg.in/mikefarah/yq.v1
</code></pre> </code></pre>
<p><a href="https://github.com/mikefarah/yq">View on GitHub</a></p> <p><a href="https://github.com/mikefarah/yq">View on GitHub</a></p>

View File

@@ -2,7 +2,7 @@
"docs": [ "docs": [
{ {
"location": "/", "location": "/",
"text": "yq\n\u00b6\n\n\nyq is a lightweight and portable command-line YAML processor\n\n\nThe aim of the project is to be the \njq\n or sed of yaml files.\n\n\nInstall\n\u00b6\n\n\nOn MacOS:\n\n\nbrew install yq\n\n\n\n\nOn Ubuntu and other Linux distros supporting \nsnap\n packages:\n\n\nsnap install yq\n\n\n\n\nor, \nDownload latest binary\n or alternatively:\n\n\ngo get github.com/mikefarah/yq\n\n\n\n\nView on GitHub", "text": "yq\n\u00b6\n\n\nyq is a lightweight and portable command-line YAML processor\n\n\nThe aim of the project is to be the \njq\n or sed of yaml files.\n\n\nInstall\n\u00b6\n\n\nOn MacOS:\n\n\nbrew install yq\n\n\n\n\nOn Ubuntu and other Linux distros supporting \nsnap\n packages:\n\n\nsnap install yq\n\n\n\n\nOn Ubuntu 16.04 or higher from Debian package:\n\n\nsudo add-apt-repository ppa:rmescandon/yq\nsudo apt update\nsudo apt install yq -y\n\n\n\n\nor, \nDownload latest binary\n or alternatively:\n\n\ngo get gopkg.in/mikefarah/yq.v1\n\n\n\n\nView on GitHub",
"title": "Install" "title": "Install"
}, },
{ {
@@ -12,7 +12,7 @@
}, },
{ {
"location": "/#install", "location": "/#install",
"text": "On MacOS: brew install yq On Ubuntu and other Linux distros supporting snap packages: snap install yq or, Download latest binary or alternatively: go get github.com/mikefarah/yq View on GitHub", "text": "On MacOS: brew install yq On Ubuntu and other Linux distros supporting snap packages: snap install yq On Ubuntu 16.04 or higher from Debian package: sudo add-apt-repository ppa:rmescandon/yq\nsudo apt update\nsudo apt install yq -y or, Download latest binary or alternatively: go get gopkg.in/mikefarah/yq.v1 View on GitHub",
"title": "Install" "title": "Install"
}, },
{ {

View File

@@ -4,7 +4,7 @@
<url> <url>
<loc>/</loc> <loc>/</loc>
<lastmod>2018-05-07</lastmod> <lastmod>2018-06-18</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
@@ -12,7 +12,7 @@
<url> <url>
<loc>/read/</loc> <loc>/read/</loc>
<lastmod>2018-05-07</lastmod> <lastmod>2018-06-18</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
@@ -20,7 +20,7 @@
<url> <url>
<loc>/write/</loc> <loc>/write/</loc>
<lastmod>2018-05-07</lastmod> <lastmod>2018-06-18</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
@@ -28,7 +28,7 @@
<url> <url>
<loc>/delete/</loc> <loc>/delete/</loc>
<lastmod>2018-05-07</lastmod> <lastmod>2018-06-18</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
@@ -36,7 +36,7 @@
<url> <url>
<loc>/create/</loc> <loc>/create/</loc>
<lastmod>2018-05-07</lastmod> <lastmod>2018-06-18</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
@@ -44,7 +44,7 @@
<url> <url>
<loc>/convert/</loc> <loc>/convert/</loc>
<lastmod>2018-05-07</lastmod> <lastmod>2018-06-18</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
@@ -52,7 +52,7 @@
<url> <url>
<loc>/merge/</loc> <loc>/merge/</loc>
<lastmod>2018-05-07</lastmod> <lastmod>2018-06-18</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>

View File

@@ -0,0 +1,22 @@
commonKey: first document
a: Easy! as one two three
b:
c: 2
d: [3, 4]
e:
- name: fred
value: 3
- name: sam
value: 4
---
commonKey: second document
another:
document: here
---
commonKey: third document
wow:
- here is another
---
- 1
- 2
- 3

View File

@@ -0,0 +1,7 @@
a: Easy! as one two three
---
another:
document: here
---
- 1
- 2

View File

@@ -6,4 +6,4 @@ b:
- name: fred - name: fred
value: 3 value: 3
- name: sam - name: sam
value: 4 value: 4

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/mikefarah/yaml.v2"
) )
func jsonToString(context interface{}) (string, error) { func jsonToString(context interface{}) (string, error) {

View File

@@ -1,8 +1,6 @@
package main package main
import ( import "gopkg.in/imdario/mergo.v0"
"github.com/imdario/mergo"
)
func merge(dst, src interface{}, overwrite bool) error { func merge(dst, src interface{}, overwrite bool) error {
if overwrite { if overwrite {

View File

@@ -1,30 +0,0 @@
package main
import (
"testing"
yaml "gopkg.in/yaml.v2"
)
func TestMerge(t *testing.T) {
result, _ := mergeYaml([]string{"examples/data1.yaml", "examples/data2.yaml", "examples/data3.yaml"})
expected := yaml.MapSlice{
yaml.MapItem{Key: "a", Value: "simple"},
yaml.MapItem{Key: "b", Value: []interface{}{1, 2}},
yaml.MapItem{Key: "c", Value: yaml.MapSlice{yaml.MapItem{Key: "other", Value: true}, yaml.MapItem{Key: "test", Value: 1}}},
yaml.MapItem{Key: "d", Value: false},
}
assertResultComplex(t, expected, result)
}
func TestMergeWithOverwrite(t *testing.T) {
overwriteFlag = true
result, _ := mergeYaml([]string{"examples/data1.yaml", "examples/data2.yaml", "examples/data3.yaml"})
expected := yaml.MapSlice{
yaml.MapItem{Key: "a", Value: "other"},
yaml.MapItem{Key: "b", Value: []interface{}{2, 3, 4}},
yaml.MapItem{Key: "c", Value: yaml.MapSlice{yaml.MapItem{Key: "other", Value: true}, yaml.MapItem{Key: "test", Value: 2}}},
yaml.MapItem{Key: "d", Value: false},
}
assertResultComplex(t, expected, result)
}

View File

@@ -12,9 +12,15 @@ On Ubuntu and other Linux distros supporting `snap` packages:
``` ```
snap install yq snap install yq
``` ```
On Ubuntu 16.04 or higher from Debian package:
```
sudo add-apt-repository ppa:rmescandon/yq
sudo apt update
sudo apt install yq -y
```
or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively: or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively:
``` ```
go get github.com/mikefarah/yq go get gopkg.in/mikefarah/yq.v1
``` ```
[View on GitHub](https://github.com/mikefarah/yq) [View on GitHub](https://github.com/mikefarah/yq)

30
release_instructions.txt Normal file
View File

@@ -0,0 +1,30 @@
- increment version in version.go
- increment version in snapcraft.yaml
- tag git with same version number
- make sure local build passes
- push tag to git
- make local xcompile (builds binaries for all platforms)
- git release
./scripts/publish.sh
- snapcraft
- will auto create a candidate, test it works then promote
- brew
- create pull request pointing to latest git release
- docker
- build and push latest and new version tag
- debian package
- execute
```dch -i```
- fill debian/changelog with changes from last version
- build the package sources
```debuild -i -I -S -sa```
(signing with gpg key is required in order to put it to ppa)
- put to PPA
```dput ppa:<REPOSITORY> ../yq_<VERSION>_source.changes```
(current distro repository is ppa:rmescandon/yq. In case that a new version
is released, please contact rmescandon@gmail.com to bump debian package)

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
set -ex
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}" GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
CURRENT="$(git describe --tags --abbrev=0)" CURRENT="$(git describe --tags --abbrev=0)"
@@ -8,32 +8,25 @@ OWNER="mikefarah"
REPO="yq" REPO="yq"
release() { release() {
mapfile -t logs < <(git log --pretty=oneline --abbrev-commit "${PREVIOUS}".."${CURRENT}")
description="$(printf '%s\n' "${logs[@]}")"
github-release release \ github-release release \
--user "$OWNER" \ --user "$OWNER" \
--repo "$REPO" \ --repo "$REPO" \
--tag "$CURRENT" \ --tag "$CURRENT"
--description "$description" ||
github-release edit \
--user "$OWNER" \
--repo "$REPO" \
--tag "$CURRENT" \
--description "$description"
} }
upload() { upload() {
mapfile -t files < <(find ./build -mindepth 1 -maxdepth 1) while IFS= read -r -d $'\0'; do
for file in "${files[@]}"; do file=$REPLY
BINARY=$(basename "${file}") BINARY=$(basename "${file}")
echo "--> ${BINARY}" echo "--> ${BINARY}"
github-release upload \ github-release upload \
--replace \
--user "$OWNER" \ --user "$OWNER" \
--repo "$REPO" \ --repo "$REPO" \
--tag "$CURRENT" \ --tag "$CURRENT" \
--name "${BINARY}" \ --name "${BINARY}" \
--file "$file" --file "$file"
done done < <(find ./build -mindepth 1 -maxdepth 1 -print0)
} }
release release

View File

@@ -1,5 +1,5 @@
name: yq name: yq
version: 1.14.1 version: 1.15.0
summary: A lightweight and portable command-line YAML processor summary: A lightweight and portable command-line YAML processor
description: | description: |
The aim of the project is to be the jq or sed of yaml files. The aim of the project is to be the jq or sed of yaml files.

View File

@@ -9,8 +9,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/spf13/cobra" yaml "gopkg.in/mikefarah/yaml.v2"
yaml "gopkg.in/yaml.v2" "gopkg.in/spf13/cobra.v0"
) )
type resulter struct { type resulter struct {

52
vendor/vendor.json vendored
View File

@@ -2,12 +2,6 @@
"comment": "", "comment": "",
"ignore": "test", "ignore": "test",
"package": [ "package": [
{
"checksumSHA1": "66lykxpWgSmQodnhkADqn6tnroQ=",
"path": "github.com/imdario/mergo",
"revision": "e3000cb3d28c72b837601cac94debd91032d19fe",
"revisionTime": "2017-06-20T10:47:01Z"
},
{ {
"checksumSHA1": "40vJyUB4ezQSn/NSadsKEOrudMc=", "checksumSHA1": "40vJyUB4ezQSn/NSadsKEOrudMc=",
"path": "github.com/inconshreveable/mousetrap", "path": "github.com/inconshreveable/mousetrap",
@@ -15,28 +9,40 @@
"revisionTime": "2014-10-17T20:07:13Z" "revisionTime": "2014-10-17T20:07:13Z"
}, },
{ {
"checksumSHA1": "BoXdUBWB8UnSlFlbnuTQaPqfCGk=", "checksumSHA1": "ljd3FhYRJ91cLZz3wsH9BQQ2JbA=",
"path": "github.com/op/go-logging", "path": "github.com/pkg/errors",
"revision": "970db520ece77730c7e4724c61121037378659d9", "revision": "816c9085562cd7ee03e7f8188a1cfd942858cded",
"revisionTime": "2016-03-15T20:05:05Z" "revisionTime": "2018-03-11T21:45:15Z"
}, },
{ {
"checksumSHA1": "xPKgXygsORkmXnLdtFaFmipYKaA=", "checksumSHA1": "OJI0OgC5V8gZtfS1e0CDYMhkDNc=",
"path": "github.com/spf13/cobra",
"revision": "b78744579491c1ceeaaa3b40205e56b0591b93a3",
"revisionTime": "2017-09-05T17:20:51Z"
},
{
"checksumSHA1": "Q52Y7t0lEtk/wcDn5q7tS7B+jqs=",
"path": "github.com/spf13/pflag", "path": "github.com/spf13/pflag",
"revision": "7aff26db30c1be810f9de5038ec5ef96ac41fd7c", "revision": "3ebe029320b2676d667ae88da602a5f854788a8a",
"revisionTime": "2017-08-24T17:57:12Z" "revisionTime": "2018-06-01T13:25:42Z"
}, },
{ {
"checksumSHA1": "RDJpJQwkF012L6m/2BJizyOksNw=", "checksumSHA1": "RwlkCZz8VFXAE4aHQQOSC0hLu5k=",
"path": "gopkg.in/yaml.v2", "path": "gopkg.in/imdario/mergo.v0",
"revision": "eb3733d160e74a9c7e442f435eb3bea458e1d19f", "revision": "9316a62528ac99aaecb4e47eadd6dc8aa6533d58",
"revisionTime": "2017-08-12T16:00:11Z" "revisionTime": "2018-06-08T14:01:56Z"
},
{
"checksumSHA1": "7wtGubs4v7+RZovtlmyT9KwA/gE=",
"path": "gopkg.in/mikefarah/yaml.v2",
"revision": "e175af14aaa1d0eff2ee04b691e4a4827a111416",
"revisionTime": "2018-06-13T04:05:11Z"
},
{
"checksumSHA1": "rL5r44ASTGubGW88gqQwlvVQshw=",
"path": "gopkg.in/op/go-logging.v1",
"revision": "b2cb9fa56473e98db8caba80237377e83fe44db5",
"revisionTime": "2016-02-11T21:21:56Z"
},
{
"checksumSHA1": "xsZjAbfLrXcMtY6fyQ8QC6EvJD0=",
"path": "gopkg.in/spf13/cobra.v0",
"revision": "ef82de70bb3f60c65fb8eebacbb2d122ef517385",
"revisionTime": "2018-04-27T13:45:50Z"
} }
], ],
"rootPath": "github.com/mikefarah/yq" "rootPath": "github.com/mikefarah/yq"

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 = "1.15.0" Version = "2.0.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

View File

@@ -3,13 +3,18 @@ package main
import "testing" import "testing"
func TestGetVersionDisplay(t *testing.T) { func TestGetVersionDisplay(t *testing.T) {
var expectedVersion = ProductName + " version " + Version
if VersionPrerelease != "" {
expectedVersion = expectedVersion + "-" + VersionPrerelease
}
expectedVersion = expectedVersion + "\n"
tests := []struct { tests := []struct {
name string name string
want string want string
}{ }{
{ {
name: "Display Version", name: "Display Version",
want: ProductName + " version " + Version + "\n", want: expectedVersion,
}, },
} }
for _, tt := range tests { for _, tt := range tests {

420
yq.go
View File

@@ -1,16 +1,20 @@
package main package main
import ( import (
"errors" "bufio"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect"
"strconv" "strconv"
"strings" "strings"
logging "github.com/op/go-logging" errors "github.com/pkg/errors"
"github.com/spf13/cobra" "gopkg.in/spf13/cobra.v0"
yaml "gopkg.in/yaml.v2"
yaml "gopkg.in/mikefarah/yaml.v2"
logging "gopkg.in/op/go-logging.v1"
) )
var trimOutput = true var trimOutput = true
@@ -20,17 +24,19 @@ var outputToJSON = false
var overwriteFlag = false var overwriteFlag = false
var verbose = false var verbose = false
var version = false var version = false
var docIndex = "0"
var log = logging.MustGetLogger("yq") var log = logging.MustGetLogger("yq")
func main() { func main() {
cmd := newCommandCLI() cmd := newCommandCLI()
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
fmt.Println(err.Error()) log.Error(err.Error())
os.Exit(1) os.Exit(1)
} }
} }
func newCommandCLI() *cobra.Command { func newCommandCLI() *cobra.Command {
yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{})
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "yq", Use: "yq",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
@@ -77,35 +83,37 @@ func newCommandCLI() *cobra.Command {
} }
func createReadCmd() *cobra.Command { func createReadCmd() *cobra.Command {
return &cobra.Command{ var cmdRead = &cobra.Command{
Use: "read [yaml_file] [path]", Use: "read [yaml_file] [path]",
Aliases: []string{"r"}, Aliases: []string{"r"},
Short: "yq r sample.yaml a.b.c", Short: "yq r [--doc/-d document_index] sample.yaml a.b.c",
Example: ` Example: `
yq read things.yaml a.b.c yq read things.yaml a.b.c
yq r - a.b.c (reads from stdin) yq r - a.b.c (reads from stdin)
yq r things.yaml a.*.c yq r things.yaml a.*.c
yq r things.yaml a.array[0].blah yq r -d1 things.yaml a.array[0].blah
yq r things.yaml a.array[*].blah 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")
return cmdRead
} }
func createWriteCmd() *cobra.Command { func createWriteCmd() *cobra.Command {
var cmdWrite = &cobra.Command{ var cmdWrite = &cobra.Command{
Use: "write [yaml_file] [path] [value]", Use: "write [yaml_file] [path] [value]",
Aliases: []string{"w"}, Aliases: []string{"w"},
Short: "yq w [--inplace/-i] [--script/-s script_file] sample.yaml a.b.c newValueForC", Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d document_index] sample.yaml a.b.c newValueForC",
Example: ` Example: `
yq write things.yaml a.b.c cat yq write things.yaml a.b.c cat
yq write --inplace things.yaml a.b.c cat yq write --inplace things.yaml a.b.c cat
yq w -i things.yaml a.b.c cat yq w -i things.yaml a.b.c cat
yq w --script update_script.yaml things.yaml yq w --script update_script.yaml things.yaml
yq w -i -s update_script.yaml things.yaml yq w -i -s update_script.yaml things.yaml
yq w things.yaml a.b.d[+] foo yq w --doc 2 things.yaml a.b.d[+] foo
yq w things.yaml a.b.d[+] foo yq w -d2 things.yaml a.b.d[+] foo
`, `,
Long: `Updates the yaml file w.r.t the given path and value. Long: `Updates the yaml file w.r.t the given path and value.
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.
@@ -124,6 +132,7 @@ a.b.e:
} }
cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdWrite return cmdWrite
} }
@@ -131,7 +140,7 @@ func createDeleteCmd() *cobra.Command {
var cmdDelete = &cobra.Command{ var cmdDelete = &cobra.Command{
Use: "delete [yaml_file] [path]", Use: "delete [yaml_file] [path]",
Aliases: []string{"d"}, Aliases: []string{"d"},
Short: "yq d [--inplace/-i] sample.yaml a.b.c", Short: "yq d [--inplace/-i] [--doc/-d document_index] sample.yaml a.b.c",
Example: ` Example: `
yq delete things.yaml a.b.c yq delete things.yaml a.b.c
yq delete --inplace things.yaml a.b.c yq delete --inplace things.yaml a.b.c
@@ -144,6 +153,7 @@ Outputs to STDOUT unless the inplace flag is used, in which case the file is upd
RunE: deleteProperty, RunE: deleteProperty,
} }
cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdDelete return cmdDelete
} }
@@ -173,7 +183,7 @@ 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] [--overwrite/-x] sample.yaml sample2.yaml", Short: "yq m [--inplace/-i] [--doc/-d document_index] [--overwrite/-x] 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
@@ -190,6 +200,7 @@ If overwrite flag is set then existing values will be overwritten using the valu
} }
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().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdMerge return cmdMerge
} }
@@ -207,7 +218,6 @@ func readProperty(cmd *cobra.Command, args []string) error {
} }
func read(args []string) (interface{}, error) { func read(args []string) (interface{}, error) {
var parsedData yaml.MapSlice
var path = "" var path = ""
if len(args) < 1 { if len(args) < 1 {
@@ -215,67 +225,20 @@ func read(args []string) (interface{}, error) {
} else if len(args) > 1 { } else if len(args) > 1 {
path = args[1] path = args[1]
} }
var generalData interface{}
if err := readData(args[0], &parsedData); err != nil { var docIndexInt, errorParsingDocumentIndex = strconv.ParseInt(docIndex, 10, 32)
var generalData interface{} if errorParsingDocumentIndex != nil {
if err = readData(args[0], &generalData); err != nil { return nil, errors.Wrapf(errorParsingDocumentIndex, "Document index %v is not a integer", docIndex)
return nil, err
}
item := yaml.MapItem{Key: "thing", Value: generalData}
parsedData = yaml.MapSlice{item}
path = "thing." + path
} }
if err := readData(args[0], int(docIndexInt), &generalData); err != nil {
if parsedData != nil && parsedData[0].Key == nil { return nil, err
var interfaceData []map[interface{}]interface{}
if err := readData(args[0], &interfaceData); err == nil {
var listMap []yaml.MapSlice
for _, item := range interfaceData {
listMap = append(listMap, mapToMapSlice(item))
}
return readYamlArray(listMap, path)
}
} }
if path == "" { if path == "" {
return parsedData, nil return generalData, nil
} }
var paths = parsePath(path) var paths = parsePath(path)
return recurse(generalData, paths[0], paths[1:])
return readMap(parsedData, paths[0], paths[1:])
}
func readYamlArray(listMap []yaml.MapSlice, path string) (interface{}, error) {
if path == "" {
return listMap, nil
}
var paths = parsePath(path)
if paths[0] == "*" {
if len(paths[1:]) == 0 {
return listMap, nil
}
var results []interface{}
for _, m := range listMap {
value, err := readMap(m, paths[1], paths[2:])
if err != nil {
return nil, err
}
results = append(results, value)
}
return results, nil
}
index, err := strconv.ParseInt(paths[0], 10, 64)
if err != nil {
return nil, fmt.Errorf("Error accessing array: %v", err)
}
if len(paths[1:]) == 0 {
return listMap[index], nil
}
return readMap(listMap[index], paths[1], paths[2:])
} }
func newProperty(cmd *cobra.Command, args []string) error { func newProperty(cmd *cobra.Command, args []string) error {
@@ -292,159 +255,205 @@ func newProperty(cmd *cobra.Command, args []string) error {
} }
func newYaml(args []string) (interface{}, error) { func newYaml(args []string) (interface{}, error) {
var writeCommands yaml.MapSlice var writeCommands, writeCommandsError = readWriteCommands(args, 2, "Must provide <path_to_update> <value>")
if writeScript != "" { if writeCommandsError != nil {
if err := readData(writeScript, &writeCommands); err != nil { return nil, writeCommandsError
return nil, err
}
} else if len(args) < 2 {
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])}
} }
var parsedData yaml.MapSlice var dataBucket interface{}
var prependCommand = ""
var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[") var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[")
if isArray { if isArray {
item := yaml.MapItem{Key: "thing", Value: make(yaml.MapSlice, 0)} dataBucket = make([]interface{}, 0)
parsedData = yaml.MapSlice{item}
prependCommand = "thing"
} else { } else {
parsedData = make(yaml.MapSlice, 0) dataBucket = make(yaml.MapSlice, 0)
} }
return updateParsedData(parsedData, writeCommands, prependCommand) for _, entry := range writeCommands {
path := entry.Key.(string)
value := entry.Value
log.Debugf("setting %v to %v", path, value)
var paths = parsePath(path)
dataBucket = updatedChildValue(dataBucket, paths, value)
}
return dataBucket, nil
}
func parseDocumentIndex() (bool, int, error) {
if docIndex == "*" {
return true, -1, nil
}
docIndexInt64, err := strconv.ParseInt(docIndex, 10, 32)
if err != nil {
return false, -1, errors.Wrapf(err, "Document index %v is not a integer or *", docIndex)
}
return false, int(docIndexInt64), nil
}
type updateDataFn func(dataBucket interface{}, currentIndex int) (interface{}, error)
func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderFn {
return func(decoder *yaml.Decoder) error {
var dataBucket interface{}
var errorReading error
var errorWriting error
var errorUpdating error
var currentIndex = 0
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
for {
log.Debugf("Read doc %v", currentIndex)
errorReading = decoder.Decode(&dataBucket)
if errorReading == io.EOF {
if !updateAll && currentIndex < docIndexInt {
return fmt.Errorf("Asked to process document %v but there are only %v document(s)", docIndex, currentIndex)
}
return nil
} else if errorReading != nil {
return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading)
}
dataBucket, errorUpdating = updateData(dataBucket, currentIndex)
if errorUpdating != nil {
return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex)
}
errorWriting = encoder.Encode(dataBucket)
if errorWriting != nil {
return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting)
}
currentIndex = currentIndex + 1
}
}
} }
func writeProperty(cmd *cobra.Command, args []string) error { func writeProperty(cmd *cobra.Command, args []string) error {
updatedData, err := updateYaml(args) var writeCommands, writeCommandsError = readWriteCommands(args, 3, "Must provide <filename> <path_to_update> <value>")
if err != nil { if writeCommandsError != nil {
return err return writeCommandsError
} }
return write(cmd, args[0], updatedData) var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Updating doc %v", currentIndex)
for _, entry := range writeCommands {
path := entry.Key.(string)
value := entry.Value
log.Debugf("setting %v to %v", path, value)
var paths = parsePath(path)
dataBucket = updatedChildValue(dataBucket, paths, value)
}
}
return dataBucket, nil
}
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
} }
func write(cmd *cobra.Command, filename string, updatedData interface{}) error { func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error {
var destination io.Writer
var destinationName string
if writeInplace { if writeInplace {
dataStr, err := yamlToString(updatedData) var tempFile, err = ioutil.TempFile("", "temp")
if err != nil { if err != nil {
return err return err
} }
return ioutil.WriteFile(filename, []byte(dataStr), 0644) destinationName = tempFile.Name()
destination = tempFile
defer func() {
safelyCloseFile(tempFile)
safelyRenameFile(tempFile.Name(), inputFile)
}()
} else {
var writer = bufio.NewWriter(stdOut)
destination = writer
destinationName = "Stdout"
defer safelyFlush(writer)
} }
dataStr, err := toString(updatedData) var encoder = yaml.NewEncoder(destination)
if err != nil { log.Debugf("Writing to %v from %v", destinationName, inputFile)
return err return readStream(inputFile, mapYamlDecoder(updateData, encoder))
}
cmd.Println(dataStr)
return nil
} }
func deleteProperty(cmd *cobra.Command, args []string) error { func deleteProperty(cmd *cobra.Command, args []string) error {
updatedData, err := deleteYaml(args)
if err != nil {
return err
}
return write(cmd, args[0], updatedData)
}
func deleteYaml(args []string) (interface{}, error) {
var parsedData yaml.MapSlice
var deletePath string
if len(args) < 2 { if len(args) < 2 {
return nil, errors.New("Must provide <filename> <path_to_delete>") return errors.New("Must provide <filename> <path_to_delete>")
}
var deletePath = args[1]
var paths = parsePath(deletePath)
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
} }
deletePath = args[1] var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
if updateAll || currentIndex == docIndexInt {
if err := readData(args[0], &parsedData); err != nil { log.Debugf("Deleting path in doc %v", currentIndex)
var generalData interface{} return deleteChildValue(dataBucket, paths), nil
if err = readData(args[0], &generalData); err != nil {
return nil, err
} }
item := yaml.MapItem{Key: "thing", Value: generalData} return dataBucket, nil
parsedData = yaml.MapSlice{item}
deletePath = "thing." + deletePath
} }
path := parsePath(deletePath) return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
return deleteMap(parsedData, path), nil
} }
func mergeProperties(cmd *cobra.Command, args []string) error { func mergeProperties(cmd *cobra.Command, args []string) error {
if len(args) < 2 { if len(args) < 2 {
return errors.New("Must provide at least 2 yaml files") return errors.New("Must provide at least 2 yaml files")
} }
var input = args[0]
updatedData, err := mergeYaml(args) var filesToMerge = args[1:]
if err != nil { var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
return err if errorParsingDocIndex != nil {
return errorParsingDocIndex
} }
return write(cmd, args[0], updatedData)
}
func mergeYaml(args []string) (interface{}, error) { var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
var updatedData map[interface{}]interface{} if updateAll || currentIndex == docIndexInt {
log.Debugf("Merging doc %v", currentIndex)
for _, f := range args { var mergedData map[interface{}]interface{}
var parsedData map[interface{}]interface{} if err := merge(&mergedData, dataBucket, overwriteFlag); err != nil {
if err := readData(f, &parsedData); err != nil { return nil, err
return nil, err }
} for _, f := range filesToMerge {
if err := merge(&updatedData, parsedData, overwriteFlag); err != nil { var fileToMerge interface{}
return nil, err if err := readData(f, 0, &fileToMerge); err != nil {
return nil, err
}
if err := merge(&mergedData, fileToMerge, overwriteFlag); err != nil {
return nil, err
}
}
return mergedData, nil
} }
return dataBucket, nil
} }
yaml.DefaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
return mapToMapSlice(updatedData), nil defer func() { yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{}) }()
return readAndUpdate(cmd.OutOrStdout(), input, updateData)
} }
func updateParsedData(parsedData yaml.MapSlice, writeCommands yaml.MapSlice, prependCommand string) (interface{}, error) { func readWriteCommands(args []string, expectedArgs int, badArgsMessage string) (yaml.MapSlice, error) {
var prefix = ""
if prependCommand != "" {
prefix = prependCommand + "."
}
for _, entry := range writeCommands {
path := prefix + entry.Key.(string)
value := entry.Value
var paths = parsePath(path)
parsedData = writeMap(parsedData, paths, value)
}
if prependCommand != "" {
return readMap(parsedData, prependCommand, make([]string, 0))
}
return parsedData, nil
}
func updateYaml(args []string) (interface{}, error) {
var writeCommands yaml.MapSlice var writeCommands yaml.MapSlice
var prependCommand = ""
if writeScript != "" { if writeScript != "" {
if err := readData(writeScript, &writeCommands); err != nil { if err := readData(writeScript, 0, &writeCommands); err != nil {
return nil, err return nil, err
} }
} else if len(args) < 3 { } else if len(args) < expectedArgs {
return nil, errors.New("Must provide <filename> <path_to_update> <value>") return nil, errors.New(badArgsMessage)
} else { } else {
writeCommands = make(yaml.MapSlice, 1) writeCommands = make(yaml.MapSlice, 1)
writeCommands[0] = yaml.MapItem{Key: args[1], Value: parseValue(args[2])} writeCommands[0] = yaml.MapItem{Key: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])}
} }
return writeCommands, nil
var parsedData yaml.MapSlice
if err := readData(args[0], &parsedData); err != nil {
var generalData interface{}
if err = readData(args[0], &generalData); err != nil {
return nil, err
}
item := yaml.MapItem{Key: "thing", Value: generalData}
parsedData = yaml.MapSlice{item}
prependCommand = "thing"
}
return updateParsedData(parsedData, writeCommands, prependCommand)
} }
func parseValue(argument string) interface{} { func parseValue(argument string) interface{} {
@@ -487,7 +496,7 @@ func marshalContext(context interface{}) (string, error) {
out, err := yaml.Marshal(context) out, err := yaml.Marshal(context)
if err != nil { if err != nil {
return "", fmt.Errorf("error printing yaml: %v", err) return "", errors.Wrap(err, "error printing yaml")
} }
outStr := string(out) outStr := string(out)
@@ -499,22 +508,57 @@ func marshalContext(context interface{}) (string, error) {
return outStr, nil return outStr, nil
} }
func readData(filename string, parsedData interface{}) error { func safelyRenameFile(from string, to string) {
if err := os.Rename(from, to); err != nil {
log.Errorf("Error renaming from %v to %v", from, to)
log.Error(err.Error())
}
}
func safelyFlush(writer *bufio.Writer) {
if err := writer.Flush(); err != nil {
log.Error("Error flushing writer!")
log.Error(err.Error())
}
}
func safelyCloseFile(file *os.File) {
err := file.Close()
if err != nil {
log.Error("Error closing file!")
log.Error(err.Error())
}
}
type yamlDecoderFn func(*yaml.Decoder) error
func readStream(filename string, yamlDecoder yamlDecoderFn) error {
if filename == "" { if filename == "" {
return errors.New("Must provide filename") return errors.New("Must provide filename")
} }
var rawData []byte var stream io.Reader
var err error
if filename == "-" { if filename == "-" {
rawData, err = ioutil.ReadAll(os.Stdin) stream = bufio.NewReader(os.Stdin)
} else { } else {
rawData, err = ioutil.ReadFile(filename) file, err := os.Open(filename)
if err != nil {
return err
}
defer safelyCloseFile(file)
stream = file
} }
return yamlDecoder(yaml.NewDecoder(stream))
if err != nil { }
return err
} func readData(filename string, indexToRead int, parsedData interface{}) error {
return readStream(filename, func(decoder *yaml.Decoder) error {
return yaml.Unmarshal(rawData, parsedData) for currentIndex := 0; currentIndex < indexToRead; currentIndex++ {
errorSkipping := decoder.Decode(parsedData)
if errorSkipping != nil {
return errors.Wrapf(errorSkipping, "Error processing document at index %v, %v", currentIndex, errorSkipping)
}
}
return decoder.Decode(parsedData)
})
} }

View File

@@ -28,6 +28,13 @@ func TestRead(t *testing.T) {
assertResult(t, 2, result) 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) { 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) assertResult(t, 2, result)
@@ -71,37 +78,6 @@ func TestNewYamlArray(t *testing.T) {
formattedResult) formattedResult)
} }
func TestUpdateYaml(t *testing.T) {
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}]]}]}]",
formattedResult)
}
func TestUpdateYamlArray(t *testing.T) {
result, _ := updateYaml([]string{"examples/sample_array.yaml", "[0]", "3"})
formattedResult := fmt.Sprintf("%v", result)
assertResult(t,
"[3 2 3]",
formattedResult)
}
func TestUpdateYaml_WithScript(t *testing.T) {
writeScript = "examples/instruction_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) { func TestNewYaml_WithScript(t *testing.T) {
writeScript = "examples/instruction_sample.yaml" writeScript = "examples/instruction_sample.yaml"
expectedResult := `b: expectedResult := `b:
@@ -122,11 +98,3 @@ func TestNewYaml_WithUnknownScript(t *testing.T) {
expectedOutput := `open fake-unknown: no such file or directory` expectedOutput := `open fake-unknown: no such file or directory`
assertResult(t, expectedOutput, err.Error()) assertResult(t, expectedOutput, err.Error())
} }
func TestDeleteYaml(t *testing.T) {
result, _ := deleteYaml([]string{"examples/sample.yaml", "b.c"})
formattedResult := fmt.Sprintf("%v", result)
assertResult(t,
"[{a Easy! as one two three} {b [{d [3 4]} {e [[{name fred} {value 3}] [{name sam} {value 4}]]}]}]",
formattedResult)
}