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

Compare commits

..

21 Commits
3.3.0 ... 3.3.2

Author SHA1 Message Date
RyderXia
07cd3d4b8b return error 2020-06-13 16:45:52 +10:00
RyderXia
b7b6988e76 mk TempDir 2020-06-13 16:45:52 +10:00
Mike Farah
9de2039c31 Version bump 2020-06-12 12:23:18 +10:00
Mike Farah
767709fef5 Fixed error flag 2020-06-12 12:21:46 +10:00
Mike Farah
d9ae8e1e5a Updated readme 2020-06-12 09:30:05 +10:00
Mike Farah
b0fa0e5b86 added shell completion instructions 2020-06-11 18:50:38 +10:00
Mike Farah
6777d639c0 Fixed error handling 2020-06-11 18:30:45 +10:00
Mike Farah
de8dcff803 Added shell completions 2020-06-11 18:27:01 +10:00
Mike Farah
765ada4dc6 Bumping version 2020-06-11 15:01:18 +10:00
Mike Farah
1405584892 New,Update now support anchors and aliases 2020-06-11 13:57:13 +10:00
Mike Farah
8c9c326342 Usage error messages now go to StdErr 2020-06-11 10:14:33 +10:00
Mike Farah
e90b00957b Added missing flow style 2020-06-11 09:58:10 +10:00
Mike Farah
71f5f76213 Delete now works with deep splat 2020-06-11 09:53:36 +10:00
Mike Farah
b9e304e7a4 Can stripComments and explodeAnchors for compare 2020-06-11 09:13:55 +10:00
Mike Farah
d473c39a44 Added exit flag 2020-06-10 16:55:20 +10:00
Mike Farah
9624410add Significantly improved performance of exploding anchors (improves to json speed) 2020-06-10 16:36:33 +10:00
Mike Farah
b55fe48bd8 Update to latest go-yaml library, updated test w.r.t. formatting and comment handling fixes 2020-06-10 16:02:12 +10:00
adripo
721dd57ed4 Fixed typo
removed repeated word
2020-06-02 09:02:20 +10:00
Roberto Mier Escandon
6fc3566acd Changelog updated for version 3.3-0 2020-05-01 10:31:53 +10:00
Mike Farah
4b63d92a3c Added choco install instructions 2020-04-22 23:29:44 +10:00
M. Minot
3ccd32a47e docs(readme): protect parameter expansions
Avoid unwanted word-splitting, including in the working directory path.
2020-04-19 00:18:08 +10:00
34 changed files with 471 additions and 115 deletions

View File

@@ -18,13 +18,20 @@ V3 is officially out - if you've been using v2 and want/need to upgrade, checkou
```
brew install yq
```
### Windows:
```
choco install yq
```
Supported by @chillum
### Ubuntu and other Linux distros supporting `snap` packages:
```
snap install yq
```
#### Snap notes
`yq` installs with with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can:
`yq` installs with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can:
```
sudo cat /etc/myfile | yq r - a.path
@@ -60,20 +67,20 @@ GO111MODULE=on go get github.com/mikefarah/yq/v3
Oneshot use:
```bash
docker run --rm -v ${PWD}:/workdir mikefarah/yq yq [flags] <command> FILE...
docker run --rm -v "${PWD}":/workdir mikefarah/yq yq [flags] <command> FILE...
```
Run commands interactively:
```bash
docker run --rm -it -v ${PWD}:/workdir mikefarah/yq sh
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
```
It can be useful to have a bash function to avoid typing the whole docker command:
```bash
yq() {
docker run --rm -i -v ${PWD}:/workdir mikefarah/yq yq $@
docker run --rm -i -v "${PWD}":/workdir mikefarah/yq yq "$@"
}
```
@@ -94,6 +101,7 @@ yq() {
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/commands/read#from-stdin)
- [Merge](https://mikefarah.gitbook.io/yq/commands/merge) multiple yaml files with various options for [overriding](https://mikefarah.gitbook.io/yq/commands/merge#overwrite-values) and [appending](https://mikefarah.gitbook.io/yq/commands/merge#append-values-with-arrays)
- Supports multiple documents in a single yaml file for [reading](https://mikefarah.gitbook.io/yq/commands/read#multiple-documents), [writing](https://mikefarah.gitbook.io/yq/commands/write-update#multiple-documents) and [merging](https://mikefarah.gitbook.io/yq/commands/merge#multiple-documents)
- General shell completion scripts (bash/zsh/fish/powershell) (https://mikefarah.gitbook.io/yq/commands/shell-completion)
## [Usage](https://mikefarah.gitbook.io/yq/)
@@ -105,18 +113,19 @@ Usage:
yq [command]
Available Commands:
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
help Help about any command
merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml
new yq n [--script/-s script_file] a.b.c newValue
prefix yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c
read yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value'
validate yq v sample.yaml
write yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
help Help about any command
merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml
new yq n [--script/-s script_file] a.b.c newValue
prefix yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c
read yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value'
shell-completion Generates shell completion scripts
validate yq v sample.yaml
write yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue
Flags:
-C, --colors print using colors
-C, --colors print with colors
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-P, --prettyPrint pretty print

View File

@@ -31,6 +31,8 @@ yq x -d1 dataA.yaml dataB.yaml 'a.b.c'
cmdCompare.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
cmdCompare.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)")
cmdCompare.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results")
cmdCompare.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "strip comments out before comparing")
cmdCompare.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors")
return cmdCompare
}

View File

@@ -16,6 +16,55 @@ func TestCompareSameCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
func TestCompareIgnoreCommentsCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare --stripComments ../examples/data1.yaml ../examples/data1-no-comments.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := ``
test.AssertResult(t, expectedOutput, result.Output)
}
func TestCompareDontIgnoreCommentsCmd(t *testing.T) {
forceOsExit = false
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1-no-comments.yaml")
expectedOutput := `-a: simple # just the best
+a: simple
b: [1, 2]
c:
test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestCompareExplodeAnchorsCommentsCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare --explodeAnchors ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := ``
test.AssertResult(t, expectedOutput, result.Output)
}
func TestCompareDontExplodeAnchorsCmd(t *testing.T) {
forceOsExit = false
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
expectedOutput := `-foo: &foo
+foo:
a: 1
foobar:
- !!merge <<: *foo
+ a: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestCompareDifferentCmd(t *testing.T) {
forceOsExit = false
cmd := getRootCommand()
@@ -40,9 +89,9 @@ func TestComparePrettyCmd(t *testing.T) {
}
expectedOutput := ` a: simple # just the best
b:
- 1
-- 2
+- 3
- 1
- - 2
+ - 3
c:
test: 1
`

View File

@@ -10,12 +10,15 @@ var printMode = "v"
var printLength = false
var unwrapScalar = true
var customStyle = ""
var anchorName = ""
var makeAlias = false
var stripComments = false
var collectIntoArray = false
var writeInplace = false
var writeScript = ""
var sourceYamlFile = ""
var outputToJSON = false
var exitStatus = false
var prettyPrint = false
var explodeAnchors = false
var colorsEnabled = false

View File

@@ -94,8 +94,31 @@ b:
expectedOutput := `a: 2
b:
hi:
- name: fred
- name: sam
- name: fred
- name: sam
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestDeleteDeepSplatArrayYaml(t *testing.T) {
content := `thing: 123
b:
hi:
- thing: item1
name: fred
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
hi:
- name: fred
`
test.AssertResult(t, expectedOutput, result.Output)
}

View File

@@ -101,10 +101,10 @@ func TestMergeAppendArraysCmd(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `people:
- name: Barry
age: 21
- name: Roger
age: 44
- name: Barry
age: 21
- name: Roger
age: 44
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -146,8 +146,8 @@ another:
document: here
a: simple # just the best
b:
- 1
- 2
- 1
- 2
c:
test: 1
---
@@ -317,8 +317,8 @@ func TestMergeAllowEmptyTargetCmd(t *testing.T) {
}
expectedOutput := `a: simple # just the best
b:
- 1
- 2
- 1
- 2
c:
test: 1
`

View File

@@ -29,6 +29,8 @@ Note that you can give a create script to perform more sophisticated yaml. This
cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml")
cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
cmdNew.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
cmdNew.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
cmdNew.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
return cmdNew
}

View File

@@ -18,6 +18,30 @@ func TestNewCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
func TestNewAnchorCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c 3 --anchorName=fred")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: &fred 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestNewAliasCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c foo --makeAlias")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: *foo
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestNewArrayCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b[0] 3")
@@ -25,7 +49,7 @@ func TestNewArrayCmd(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `b:
- 3
- 3
`
test.AssertResult(t, expectedOutput, result.Output)
}

View File

@@ -41,8 +41,8 @@ func TestPrefixCmdArray(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `- d:
- b:
c: 3
- b:
c: 3
`
test.AssertResult(t, expectedOutput, result.Output)
}

View File

@@ -31,6 +31,7 @@ yq r -- things.yaml '--key-starting-with-dashes.blah'
cmdRead.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments")
cmdRead.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "print yaml without any comments")
cmdRead.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors")
cmdRead.PersistentFlags().BoolVarP(&exitStatus, "exitStatus", "e", false, "set exit status if no matches are found")
return cmdRead
}
@@ -50,7 +51,13 @@ func readProperty(cmd *cobra.Command, args []string) error {
matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt)
if exitStatus && len(matchingNodes) == 0 {
cmd.SilenceUsage = true
return errors.New("No matches found")
}
if errorReadingStream != nil {
cmd.SilenceUsage = true
return errorReadingStream
}
out := cmd.OutOrStdout()

View File

@@ -17,6 +17,30 @@ func TestReadCmd(t *testing.T) {
test.AssertResult(t, "2\n", result.Output)
}
func TestReadCmdWithExitStatus(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read ../examples/sample.yaml b.c -e")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "2\n", result.Output)
}
func TestReadCmdWithExitStatusNotExist(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read ../examples/sample.yaml caterpillar -e")
test.AssertResult(t, "No matches found", result.Error.Error())
}
func TestReadCmdNotExist(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read ../examples/sample.yaml caterpillar")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "", result.Output)
}
func TestReadUnwrapCmd(t *testing.T) {
content := `b: 'frog' # my favourite`
@@ -871,17 +895,35 @@ func TestReadPrettyPrintCmd(t *testing.T) {
b:
c: 2
d:
- 3
- 4
- 3
- 4
e:
- name: fred
value: 3
- name: sam
value: 4
- name: fred
value: 3
- name: sam
value: 4
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadNotFoundWithExitStatus(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read ../examples/sample.yaml adsf -e")
if result.Error == nil {
t.Error("Expected command to fail")
}
expectedOutput := `No matches found`
test.AssertResult(t, expectedOutput, result.Error.Error())
}
func TestReadNotFoundWithoutExitStatus(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read ../examples/sample.yaml adsf")
if result.Error != nil {
t.Error("Expected command to succeed!")
}
}
func TestReadPrettyPrintWithIndentCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -P -I4 ../examples/sample.json")
@@ -892,13 +934,13 @@ func TestReadPrettyPrintWithIndentCmd(t *testing.T) {
b:
c: 2
d:
- 3
- 4
- 3
- 4
e:
- name: fred
value: 3
- name: sam
value: 4
- name: fred
value: 3
- name: sam
value: 4
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -914,8 +956,8 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) {
hosts: lalaland
name: "Apply smth"
roles:
- lala
- land
- lala
- land
serial: 1
- become: false
gather_facts: true
@@ -934,8 +976,8 @@ gather_facts: false
hosts: lalaland
name: "Apply smth"
roles:
- lala
- land
- lala
- land
serial: 1
`
test.AssertResult(t, expectedOutput, result.Output)
@@ -952,8 +994,8 @@ gather_facts: false
hosts: lalaland
name: "Apply smth"
roles:
- lala
- land
- lala
- land
serial: 1
become: false
gather_facts: true
@@ -973,8 +1015,8 @@ func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) {
hosts: lalaland
name: "Apply smth"
roles:
- lala
- land
- lala
- land
serial: 1
'[1]':
become: false

View File

@@ -54,8 +54,7 @@ func New() *cobra.Command {
createDeleteCmd(),
createNewCmd(),
createMergeCmd(),
createBashCompletionCmd(rootCmd),
)
rootCmd.SetOutput(os.Stdout)
return rootCmd
}

57
cmd/shell_completion.go Normal file
View File

@@ -0,0 +1,57 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var shellVariant = "bash"
func createBashCompletionCmd(rootCmd *cobra.Command) *cobra.Command {
var completionCmd = &cobra.Command{
Use: "shell-completion",
Short: "Generates shell completion scripts",
Long: `To load completion for:
bash:
Run
. <(yq shell-completion)
To configure your bash shell to load completions for each session add to
your bashrc
# ~/.bashrc or ~/.profile
. <(yq shell-completion)
zsh:
The generated completion script should be put somewhere in your $fpath named _yq
powershell:
Users need PowerShell version 5.0 or above, which comes with Windows 10 and
can be downloaded separately for Windows 7 or 8.1. They can then write the
completions to a file and source this file from their PowerShell profile,
which is referenced by the $Profile environment variable.
fish:
Save the output to a fish file and add it to your completions directory.
`,
RunE: func(cmd *cobra.Command, args []string) error {
switch shellVariant {
case "bash", "":
return rootCmd.GenBashCompletion(os.Stdout)
case "zsh":
return rootCmd.GenZshCompletion(os.Stdout)
case "fish":
return rootCmd.GenFishCompletion(os.Stdout, true)
case "powershell":
return rootCmd.GenPowerShellCompletion(os.Stdout)
default:
return fmt.Errorf("Unknown variant %v", shellVariant)
}
},
}
completionCmd.PersistentFlags().StringVarP(&shellVariant, "variation", "V", "", "shell variation: bash (default), zsh, fish, powershell")
return completionCmd
}

View File

@@ -149,25 +149,91 @@ func writeString(writer io.Writer, txt string) error {
return errorWriting
}
func setIfNotThere(node *yaml.Node, key string, value *yaml.Node) {
for index := 0; index < len(node.Content); index = index + 2 {
keyNode := node.Content[index]
if keyNode.Value == key {
return
}
}
// need to add it to the map
mapEntryKey := yaml.Node{Value: key, Kind: yaml.ScalarNode}
node.Content = append(node.Content, &mapEntryKey)
node.Content = append(node.Content, value)
}
func applyAlias(node *yaml.Node, alias *yaml.Node) {
for index := 0; index < len(alias.Content); index = index + 2 {
keyNode := alias.Content[index]
log.Debugf("applying alias key %v", keyNode.Value)
valueNode := alias.Content[index+1]
setIfNotThere(node, keyNode.Value, valueNode)
}
}
func explodeNode(node *yaml.Node) error {
node.Anchor = ""
switch node.Kind {
case yaml.SequenceNode, yaml.DocumentNode:
for index, contentNode := range node.Content {
log.Debugf("exploding index %v", index)
errorInContent := explodeNode(contentNode)
if errorInContent != nil {
return errorInContent
}
}
return nil
case yaml.AliasNode:
log.Debugf("its an alias!")
node.Kind = node.Alias.Kind
node.Style = node.Alias.Style
node.Tag = node.Alias.Tag
node.Content = node.Alias.Content
node.Value = node.Alias.Value
node.Alias = nil
return nil
case yaml.MappingNode:
for index := 0; index < len(node.Content); index = index + 2 {
keyNode := node.Content[index]
valueNode := node.Content[index+1]
log.Debugf("traversing %v", keyNode.Value)
if keyNode.Value != "<<" {
errorInContent := explodeNode(valueNode)
if errorInContent != nil {
return errorInContent
}
} else {
if valueNode.Kind == yaml.SequenceNode {
log.Debugf("an alias merge list!")
for index := len(valueNode.Content) - 1; index >= 0; index = index - 1 {
aliasNode := valueNode.Content[index]
applyAlias(node, aliasNode.Alias)
}
} else {
log.Debugf("an alias merge!")
applyAlias(node, valueNode.Alias)
}
node.Content = append(node.Content[:index], node.Content[index+2:]...)
//replay that index, since the array is shorter now.
index = index - 2
}
}
return nil
default:
return nil
}
}
func explode(matchingNodes []*yqlib.NodeContext) error {
log.Debug("exploding nodes")
for _, nodeContext := range matchingNodes {
var targetNode = yaml.Node{Kind: nodeContext.Node.Kind}
explodedNodes, errorRetrieving := lib.Get(nodeContext.Node, "**", true)
if errorRetrieving != nil {
return errorRetrieving
log.Debugf("exploding %v", nodeContext.Head)
errorExplodingNode := explodeNode(nodeContext.Node)
if errorExplodingNode != nil {
return errorExplodingNode
}
for _, matchingNode := range explodedNodes {
mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag)
updateCommand := yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag}
errorUpdating := lib.Update(&targetNode, updateCommand, true)
if errorUpdating != nil {
return errorUpdating
}
}
nodeContext.Node = &targetNode
}
log.Debug("done exploding nodes")
return nil
}
@@ -357,6 +423,16 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
if err != nil {
return err
}
// mkdir temp dir as some docker images does not have temp dir
_, err = os.Stat(os.TempDir())
if os.IsNotExist(err) {
err = os.Mkdir(os.TempDir(), 0700)
if err != nil {
return err
}
} else if err != nil {
return err
}
tempFile, err := ioutil.TempFile("", "temp")
if err != nil {
return err
@@ -435,13 +511,13 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string)
log.Debug("args %v", args)
log.Debug("path %v", args[expectedArgs-2])
log.Debug("Value %v", args[expectedArgs-1])
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag, customStyle), Overwrite: true}
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag, customStyle, anchorName, makeAlias), Overwrite: true}
} else if len(args) == expectedArgs-1 {
// don't update the value
updateCommands = make([]yqlib.UpdateCommand, 1)
log.Debug("args %v", args)
log.Debug("path %v", args[expectedArgs-2])
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse("", customTag, customStyle), Overwrite: true, DontUpdateNodeValue: true}
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse("", customTag, customStyle, anchorName, makeAlias), Overwrite: true, DontUpdateNodeValue: true}
} else {
return nil, errors.New(badArgsMessage)
}

View File

@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "3.3.0"
Version = "3.3.2"
// 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

View File

@@ -47,6 +47,8 @@ format is list of update commands (update or delete) like so:
cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
return cmdWrite
}

View File

@@ -261,9 +261,9 @@ func TestWriteAutoCreateCmd(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `applications:
- name: app
env:
hello: world
- name: app
env:
hello: world
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -476,8 +476,8 @@ func TestWriteCmd_Append(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `b:
- foo
- 7
- foo
- 7
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -508,8 +508,8 @@ func TestWriteCmd_AppendInlinePretty(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `b:
- foo
- 7
- foo
- 7
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -527,7 +527,7 @@ func TestWriteCmd_AppendEmptyArray(t *testing.T) {
}
expectedOutput := `a: 2
b:
- v
- v
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -546,8 +546,8 @@ func TestWriteCmd_SplatArray(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `b:
- c: new
- c: new
- c: new
- c: new
`
test.AssertResult(t, expectedOutput, result.Output)
}

10
debian/changelog vendored
View File

@@ -1,3 +1,13 @@
yq (3.3-0) focal; urgency=medium
* You can control string styles (quotes) using the new --style flag
* String values now always have quotes when outputting to json
* Negative array indices now traverse the array backwards
* Added a --stripComments flag to print yaml without any comments
* Bumped go to version 1.14
-- Roberto Mier Escandon <rmescandon@gmail.com> Thu, 30 Apr 2020 20:45:44 +0200
yq (3.1-2) eoan; urgency=medium
* Bug fix: yq 3 was removing empty inline-style objects and arrays (#355)

1
debian/files vendored
View File

@@ -1 +0,0 @@
yq_3.1-2_source.buildinfo devel optional

View File

@@ -0,0 +1,4 @@
a: simple
b: [1, 2]
c:
test: 1

View File

@@ -1,2 +0,0 @@
b:
c: things

View File

@@ -0,0 +1,5 @@
foo:
a: 1
foobar:
a: 1

6
go.mod
View File

@@ -2,16 +2,16 @@ module github.com/mikefarah/yq/v3
require (
github.com/fatih/color v1.9.0
github.com/goccy/go-yaml v1.4.3
github.com/goccy/go-yaml v1.7.5
github.com/kylelemons/godebug v1.1.0
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c
)
go 1.14

13
go.sum
View File

@@ -29,8 +29,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-yaml v1.4.3 h1:+1jK1ost1TBEfWjciIMU8rciBq0poxurgS7XvLgQInM=
github.com/goccy/go-yaml v1.4.3/go.mod h1:PsEEJ29nIFZL07P/c8dv4P6rQkVFFXafQee85U+ERHA=
github.com/goccy/go-yaml v1.7.5 h1:dWvj+p3BG11S/GlUzwzt1WZz0lhBTzTIDtmXT/ZOaPY=
github.com/goccy/go-yaml v1.7.5/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -144,8 +144,8 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -175,7 +175,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -24,6 +24,7 @@ func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator {
func (n *navigator) Traverse(value *yaml.Node, path []interface{}) error {
realValue := value
emptyArray := make([]interface{}, 0)
log.Debugf("Traversing path %v", pathStackToString(path))
if realValue.Kind == yaml.DocumentNode {
log.Debugf("its a document! returning the first child")
return n.doTraverse(value.Content[0], "", path, emptyArray)
@@ -68,6 +69,16 @@ func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *y
func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error {
log.Debug("recursing, processing %v, pathStack %v", head, pathStackToString(pathStack))
nodeContext := NewNodeContext(value, head, tail, pathStack)
if head == "**" && !n.navigationStrategy.ShouldOnlyDeeplyVisitLeaves(nodeContext) {
errorVisitingDeeply := n.navigationStrategy.Visit(nodeContext)
if errorVisitingDeeply != nil {
return errorVisitingDeeply
}
}
switch value.Kind {
case yaml.MappingNode:
log.Debug("its a map with %v entries", len(value.Content)/2)
@@ -85,20 +96,20 @@ func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface
if head == "+" {
return n.appendArray(value, head, tail, pathStack)
} else if len(value.Content) == 0 && head == "**" {
return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack))
return n.navigationStrategy.Visit(nodeContext)
}
return n.splatArray(value, head, tail, pathStack)
}
case yaml.AliasNode:
log.Debug("its an alias!")
DebugNode(value.Alias)
if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) {
if n.navigationStrategy.FollowAlias(nodeContext) {
log.Debug("following the alias")
return n.recurse(value.Alias, head, tail, pathStack)
}
return nil
default:
return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack))
return n.navigationStrategy.Visit(nodeContext)
}
}

View File

@@ -18,9 +18,12 @@ func DeleteNavigationStrategy(pathElementToDelete interface{}) NavigationStrateg
shouldDeeplyTraverse: func(nodeContext NodeContext) bool {
return true
},
shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool {
return false
},
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
log.Debug("need to find and delete %v in here", pathElementToDelete)
log.Debug("need to find and delete %v in here (%v)", pathElementToDelete, pathStackToString(nodeContext.PathStack))
DebugNode(node)
if node.Kind == yaml.SequenceNode {
newContent := deleteFromArray(parser, node.Content, nodeContext.PathStack, pathElementToDelete)

View File

@@ -48,7 +48,7 @@ func DebugNode(value *yaml.Node) {
log.Error("Error debugging node, %v", errorEncoding.Error())
}
encoder.Close()
log.Debug("Tag: %v, Kind: %v", value.Tag, KindString(value.Kind))
log.Debug("Tag: %v, Kind: %v, Anchor: %v", value.Tag, KindString(value.Kind), value.Anchor)
log.Debug("%v", buf.String())
}
}

View File

@@ -35,19 +35,22 @@ type NavigationStrategy interface {
// we use it to match against the pathExpression in head.
ShouldTraverse(nodeContext NodeContext, nodeKey string) bool
ShouldDeeplyTraverse(nodeContext NodeContext) bool
// when deeply traversing, should we visit all matching nodes, or just leaves?
ShouldOnlyDeeplyVisitLeaves(NodeContext) bool
GetVisitedNodes() []*NodeContext
DebugVisitedNodes()
GetPathParser() PathParser
}
type NavigationStrategyImpl struct {
followAlias func(nodeContext NodeContext) bool
autoCreateMap func(nodeContext NodeContext) bool
visit func(nodeContext NodeContext) error
shouldVisitExtraFn func(nodeContext NodeContext) bool
shouldDeeplyTraverse func(nodeContext NodeContext) bool
visitedNodes []*NodeContext
pathParser PathParser
followAlias func(nodeContext NodeContext) bool
autoCreateMap func(nodeContext NodeContext) bool
visit func(nodeContext NodeContext) error
shouldVisitExtraFn func(nodeContext NodeContext) bool
shouldDeeplyTraverse func(nodeContext NodeContext) bool
shouldOnlyDeeplyVisitLeaves func(nodeContext NodeContext) bool
visitedNodes []*NodeContext
pathParser PathParser
}
func (ns *NavigationStrategyImpl) GetPathParser() PathParser {
@@ -70,6 +73,10 @@ func (ns *NavigationStrategyImpl) ShouldDeeplyTraverse(nodeContext NodeContext)
return ns.shouldDeeplyTraverse(nodeContext)
}
func (ns *NavigationStrategyImpl) ShouldOnlyDeeplyVisitLeaves(nodeContext NodeContext) bool {
return ns.shouldOnlyDeeplyVisitLeaves(nodeContext)
}
func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKey string) bool {
// we should traverse aliases (if enabled), but not visit them :/
if len(nodeContext.PathStack) == 0 {

View File

@@ -26,5 +26,8 @@ func ReadNavigationStrategy(deeplyTraverseArrays bool) NavigationStrategy {
}
return deeplyTraverseArrays || !isInArray
},
shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool {
return true
},
}
}

View File

@@ -13,6 +13,9 @@ func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) Navi
shouldDeeplyTraverse: func(nodeContext NodeContext) bool {
return true
},
shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool {
return true
},
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
changesToApply := updateCommand.Value
@@ -28,6 +31,8 @@ func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) Navi
node.Kind = changesToApply.Kind
node.Style = changesToApply.Style
node.Content = changesToApply.Content
node.Anchor = changesToApply.Anchor
node.Alias = changesToApply.Alias
node.HeadComment = changesToApply.HeadComment
node.LineComment = changesToApply.LineComment
node.FootComment = changesToApply.FootComment

View File

@@ -5,7 +5,7 @@ import (
)
type ValueParser interface {
Parse(argument string, customTag string, customStyle string) *yaml.Node
Parse(argument string, customTag string, customStyle string, anchorName string, createAlias bool) *yaml.Node
}
type valueParser struct {
@@ -15,7 +15,7 @@ func NewValueParser() ValueParser {
return &valueParser{}
}
func (v *valueParser) Parse(argument string, customTag string, customStyle string) *yaml.Node {
func (v *valueParser) Parse(argument string, customTag string, customStyle string, anchorName string, createAlias bool) *yaml.Node {
var style yaml.Style
if customStyle == "tagged" {
style = yaml.TaggedStyle
@@ -27,12 +27,20 @@ func (v *valueParser) Parse(argument string, customTag string, customStyle strin
style = yaml.LiteralStyle
} else if customStyle == "folded" {
style = yaml.FoldedStyle
} else if customStyle == "flow" {
style = yaml.FlowStyle
} else if customStyle != "" {
log.Error("Unknown style %v, ignoring", customStyle)
}
if argument == "[]" {
return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode, Style: style}
}
return &yaml.Node{Value: argument, Tag: customTag, Kind: yaml.ScalarNode, Style: style}
kind := yaml.ScalarNode
if createAlias {
kind = yaml.AliasNode
}
return &yaml.Node{Value: argument, Tag: customTag, Kind: kind, Style: style, Anchor: anchorName}
}

View File

@@ -16,12 +16,13 @@ var parseStyleTests = []struct {
{"double", yaml.DoubleQuotedStyle},
{"single", yaml.SingleQuotedStyle},
{"folded", yaml.FoldedStyle},
{"flow", yaml.FlowStyle},
{"literal", yaml.LiteralStyle},
}
func TestValueParserStyleTag(t *testing.T) {
for _, tt := range parseStyleTests {
actual := NewValueParser().Parse("cat", "", tt.customStyle)
actual := NewValueParser().Parse("cat", "", tt.customStyle, "", false)
test.AssertResultWithContext(t, tt.expectedStyle, actual.Style, tt.customStyle)
}
}
@@ -39,7 +40,7 @@ var parseValueTests = []struct {
func TestValueParserParse(t *testing.T) {
for _, tt := range parseValueTests {
actual := NewValueParser().Parse(tt.argument, tt.customTag, "")
actual := NewValueParser().Parse(tt.argument, tt.customTag, "", "", false)
test.AssertResultWithContext(t, tt.argument, actual.Value, tt.testDescription)
test.AssertResultWithContext(t, tt.expectedTag, actual.Tag, tt.testDescription)
test.AssertResult(t, yaml.ScalarNode, actual.Kind)
@@ -47,7 +48,18 @@ func TestValueParserParse(t *testing.T) {
}
func TestValueParserParseEmptyArray(t *testing.T) {
actual := NewValueParser().Parse("[]", "", "")
actual := NewValueParser().Parse("[]", "", "", "", false)
test.AssertResult(t, "!!seq", actual.Tag)
test.AssertResult(t, yaml.SequenceNode, actual.Kind)
}
func TestValueParserParseAlias(t *testing.T) {
actual := NewValueParser().Parse("bob", "", "", "", true)
test.AssertResult(t, "bob", actual.Value)
test.AssertResult(t, yaml.AliasNode, actual.Kind)
}
func TestValueParserAnchorname(t *testing.T) {
actual := NewValueParser().Parse("caterpillar", "", "", "foo", false)
test.AssertResult(t, "foo", actual.Anchor)
}

View File

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

3
yq.go
View File

@@ -4,14 +4,11 @@ import (
"os"
command "github.com/mikefarah/yq/v3/cmd"
logging "gopkg.in/op/go-logging.v1"
)
func main() {
cmd := command.New()
log := logging.MustGetLogger("yq")
if err := cmd.Execute(); err != nil {
log.Error(err.Error())
os.Exit(1)
}
}