mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-12 05:38:04 +00:00
refactored
This commit is contained in:
parent
0cb2ff5b2e
commit
b1f139c965
@ -11,19 +11,18 @@ var unwrapScalar = true
|
||||
var customStyle = ""
|
||||
var anchorName = ""
|
||||
var makeAlias = false
|
||||
var stripComments = false
|
||||
var writeInplace = false
|
||||
var writeScript = ""
|
||||
var sourceYamlFile = ""
|
||||
var outputToJSON = false
|
||||
var exitStatus = false
|
||||
var prettyPrint = false
|
||||
var explodeAnchors = false
|
||||
var forceColor = false
|
||||
var forceNoColor = false
|
||||
var colorsEnabled = false
|
||||
var defaultValue = ""
|
||||
var indent = 2
|
||||
var printDocSeparators = true
|
||||
var overwriteFlag = false
|
||||
var autoCreateFlag = true
|
||||
var arrayMergeStrategyFlag = "update"
|
||||
|
26
cmd/root.go
26
cmd/root.go
@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib/treeops"
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||
"github.com/spf13/cobra"
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
@ -40,7 +40,7 @@ func New() *cobra.Command {
|
||||
// }
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
var treeCreator = treeops.NewPathTreeCreator()
|
||||
var treeCreator = yqlib.NewPathTreeCreator()
|
||||
|
||||
expression := ""
|
||||
if len(args) > 0 {
|
||||
@ -53,13 +53,13 @@ func New() *cobra.Command {
|
||||
}
|
||||
|
||||
if outputToJSON {
|
||||
explodeOp := treeops.Operation{OperationType: treeops.Explode}
|
||||
explodeNode := treeops.PathTreeNode{Operation: &explodeOp}
|
||||
pipeOp := treeops.Operation{OperationType: treeops.Pipe}
|
||||
pathNode = &treeops.PathTreeNode{Operation: &pipeOp, Lhs: pathNode, Rhs: &explodeNode}
|
||||
explodeOp := yqlib.Operation{OperationType: yqlib.Explode}
|
||||
explodeNode := yqlib.PathTreeNode{Operation: &explodeOp}
|
||||
pipeOp := yqlib.Operation{OperationType: yqlib.Pipe}
|
||||
pathNode = &yqlib.PathTreeNode{Operation: &pipeOp, Lhs: pathNode, Rhs: &explodeNode}
|
||||
}
|
||||
|
||||
matchingNodes, err := evaluate("-", pathNode)
|
||||
matchingNodes, err := yqlib.Evaluate("-", pathNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -71,7 +71,14 @@ func New() *cobra.Command {
|
||||
|
||||
out := cmd.OutOrStdout()
|
||||
|
||||
return printResults(matchingNodes, out)
|
||||
fileInfo, _ := os.Stdout.Stat()
|
||||
|
||||
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
||||
colorsEnabled = true
|
||||
}
|
||||
printer := yqlib.NewPrinter(outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators)
|
||||
|
||||
return printer.PrintResults(matchingNodes, out)
|
||||
},
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
cmd.SetOut(cmd.OutOrStdout())
|
||||
@ -92,8 +99,7 @@ func New() *cobra.Command {
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
|
||||
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json. By default it prints a json document in one line, use the prettyPrint flag to print a formatted doc.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print")
|
||||
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json. Set indent to 0 to print json in one line.")
|
||||
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||
|
||||
|
232
cmd/utils.go
232
cmd/utils.go
@ -1,232 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib/treeops"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func readStream(filename string) (*yaml.Decoder, error) {
|
||||
if filename == "" {
|
||||
return nil, errors.New("Must provide filename")
|
||||
}
|
||||
|
||||
var stream io.Reader
|
||||
if filename == "-" {
|
||||
stream = bufio.NewReader(os.Stdin)
|
||||
} else {
|
||||
file, err := os.Open(filename) // nolint gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safelyCloseFile(file)
|
||||
stream = file
|
||||
}
|
||||
return yaml.NewDecoder(stream), nil
|
||||
}
|
||||
|
||||
func evaluate(filename string, node *treeops.PathTreeNode) (*list.List, error) {
|
||||
|
||||
var treeNavigator = treeops.NewDataTreeNavigator(treeops.NavigationPrefs{})
|
||||
|
||||
var matchingNodes = list.New()
|
||||
|
||||
var currentIndex uint = 0
|
||||
var decoder, err = readStream(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
var dataBucket yaml.Node
|
||||
errorReading := decoder.Decode(&dataBucket)
|
||||
|
||||
if errorReading == io.EOF {
|
||||
return matchingNodes, nil
|
||||
} else if errorReading != nil {
|
||||
return nil, errorReading
|
||||
}
|
||||
candidateNode := &treeops.CandidateNode{
|
||||
Document: currentIndex,
|
||||
Filename: filename,
|
||||
Node: &dataBucket,
|
||||
}
|
||||
inputList := list.New()
|
||||
inputList.PushBack(candidateNode)
|
||||
|
||||
newMatches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node)
|
||||
if errorParsing != nil {
|
||||
return nil, errorParsing
|
||||
}
|
||||
matchingNodes.PushBackList(newMatches)
|
||||
currentIndex = currentIndex + 1
|
||||
}
|
||||
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func printNode(node *yaml.Node, writer io.Writer) error {
|
||||
var encoder yqlib.Encoder
|
||||
if node.Kind == yaml.ScalarNode && unwrapScalar && !outputToJSON {
|
||||
return writeString(writer, node.Value+"\n")
|
||||
}
|
||||
if outputToJSON {
|
||||
encoder = yqlib.NewJsonEncoder(writer, prettyPrint, indent)
|
||||
} else {
|
||||
encoder = yqlib.NewYamlEncoder(writer, indent, colorsEnabled)
|
||||
}
|
||||
return encoder.Encode(node)
|
||||
}
|
||||
|
||||
func removeComments(matchingNodes *list.List) {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*treeops.CandidateNode)
|
||||
removeCommentOfNode(candidate.Node)
|
||||
}
|
||||
}
|
||||
|
||||
func removeCommentOfNode(node *yaml.Node) {
|
||||
node.HeadComment = ""
|
||||
node.LineComment = ""
|
||||
node.FootComment = ""
|
||||
|
||||
for _, child := range node.Content {
|
||||
removeCommentOfNode(child)
|
||||
}
|
||||
}
|
||||
|
||||
func setStyle(matchingNodes *list.List, style yaml.Style) {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*treeops.CandidateNode)
|
||||
updateStyleOfNode(candidate.Node, style)
|
||||
}
|
||||
}
|
||||
|
||||
func updateStyleOfNode(node *yaml.Node, style yaml.Style) {
|
||||
node.Style = style
|
||||
|
||||
for _, child := range node.Content {
|
||||
updateStyleOfNode(child, style)
|
||||
}
|
||||
}
|
||||
|
||||
func writeString(writer io.Writer, txt string) error {
|
||||
_, errorWriting := writer.Write([]byte(txt))
|
||||
return errorWriting
|
||||
}
|
||||
|
||||
func printResults(matchingNodes *list.List, writer io.Writer) error {
|
||||
if prettyPrint {
|
||||
setStyle(matchingNodes, 0)
|
||||
}
|
||||
|
||||
if stripComments {
|
||||
removeComments(matchingNodes)
|
||||
}
|
||||
|
||||
fileInfo, _ := os.Stdout.Stat()
|
||||
|
||||
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
||||
colorsEnabled = true
|
||||
}
|
||||
|
||||
bufferedWriter := bufio.NewWriter(writer)
|
||||
defer safelyFlush(bufferedWriter)
|
||||
|
||||
if matchingNodes.Len() == 0 {
|
||||
log.Debug("no matching results, nothing to print")
|
||||
if defaultValue != "" {
|
||||
return writeString(bufferedWriter, defaultValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var errorWriting error
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
mappedDoc := el.Value.(*treeops.CandidateNode)
|
||||
|
||||
switch printMode {
|
||||
case "p":
|
||||
errorWriting = writeString(bufferedWriter, mappedDoc.PathStackToString()+"\n")
|
||||
if errorWriting != nil {
|
||||
return errorWriting
|
||||
}
|
||||
case "pv", "vp":
|
||||
// put it into a node and print that.
|
||||
var parentNode = yaml.Node{Kind: yaml.MappingNode}
|
||||
parentNode.Content = make([]*yaml.Node, 2)
|
||||
parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: mappedDoc.PathStackToString()}
|
||||
if mappedDoc.Node.Kind == yaml.DocumentNode {
|
||||
parentNode.Content[1] = mappedDoc.Node.Content[0]
|
||||
} else {
|
||||
parentNode.Content[1] = mappedDoc.Node
|
||||
}
|
||||
if err := printNode(&parentNode, bufferedWriter); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := printNode(mappedDoc.Node, bufferedWriter); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func safelyRenameFile(from string, to string) {
|
||||
if renameError := os.Rename(from, to); renameError != nil {
|
||||
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
|
||||
log.Debug(renameError.Error())
|
||||
// can't do this rename when running in docker to a file targeted in a mounted volume,
|
||||
// so gracefully degrade to copying the entire contents.
|
||||
if copyError := copyFileContents(from, to); copyError != nil {
|
||||
log.Errorf("Failed copying from %v to %v", from, to)
|
||||
log.Error(copyError.Error())
|
||||
} else {
|
||||
removeErr := os.Remove(from)
|
||||
if removeErr != nil {
|
||||
log.Errorf("failed removing original file: %s", from)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
|
||||
func copyFileContents(src, dst string) (err error) {
|
||||
in, err := os.Open(src) // nolint gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer safelyCloseFile(in)
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer safelyCloseFile(out)
|
||||
if _, err = io.Copy(out, in); err != nil {
|
||||
return err
|
||||
}
|
||||
return out.Sync()
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
64
pkg/yqlib/doc/Equal Operator.md
Normal file
64
pkg/yqlib/doc/Equal Operator.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Equal Operator
|
||||
## Examples
|
||||
### Example 0
|
||||
sample.yml:
|
||||
```yaml
|
||||
[cat,goat,dog]
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '.[] | (. == "*at")' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
true
|
||||
true
|
||||
false
|
||||
```
|
||||
### Example 1
|
||||
sample.yml:
|
||||
```yaml
|
||||
[3, 4, 5]
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '.[] | (. == 4)' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
false
|
||||
true
|
||||
false
|
||||
```
|
||||
### Example 2
|
||||
sample.yml:
|
||||
```yaml
|
||||
a: { cat: {b: apple, c: whatever}, pat: {b: banana} }
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '.a | (.[].b == "apple")' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
true
|
||||
false
|
||||
```
|
||||
### Example 3
|
||||
Expression
|
||||
```bash
|
||||
yq 'null == null' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
true
|
||||
```
|
||||
### Example 4
|
||||
Expression
|
||||
```bash
|
||||
yq 'null == ~' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
true
|
||||
```
|
@ -74,16 +74,14 @@ func mapKeysToStrings(node *yaml.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func NewJsonEncoder(destination io.Writer, prettyPrint bool, indent int) Encoder {
|
||||
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
|
||||
var encoder = json.NewEncoder(destination)
|
||||
var indentString = ""
|
||||
|
||||
for index := 0; index < indent; index++ {
|
||||
indentString = indentString + " "
|
||||
}
|
||||
if prettyPrint {
|
||||
encoder.SetIndent("", indentString)
|
||||
}
|
||||
return &jsonEncoder{encoder}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -9,8 +9,6 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var log = logging.MustGetLogger("yq-treeops")
|
||||
|
||||
type OperationType struct {
|
||||
Type string
|
||||
NumArgs uint // number of arguments to the op
|
||||
@ -25,6 +23,9 @@ type OperationType struct {
|
||||
// - mergeAppend (merges and appends arrays)
|
||||
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
||||
// - updateTag - not recursive
|
||||
// - select by tag (tag==)
|
||||
// - get tag (tag)
|
||||
// - select by style (style==)
|
||||
// - compare ??
|
||||
// - validate ??
|
||||
// - exists
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
func Match(name string, pattern string) (matched bool) {
|
||||
if pattern == "" {
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -49,4 +49,5 @@ func TestEqualOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range equalsOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Equal Operator", equalsOperatorScenarios)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -186,6 +186,8 @@ func traverseArray(candidate *CandidateNode, operation *Operation) ([]*Candidate
|
||||
|
||||
}
|
||||
|
||||
switch operation.Value.(type) {
|
||||
case int64:
|
||||
index := operation.Value.(int64)
|
||||
indexToUse := index
|
||||
contentLength := int64(len(candidate.Node.Content))
|
||||
@ -207,5 +209,9 @@ func traverseArray(candidate *CandidateNode, operation *Operation) ([]*Candidate
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
}}, nil
|
||||
default:
|
||||
log.Debug("argument not an int (%v), no array matches", operation.Value)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -126,6 +126,26 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[b c], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `[1,2,3]`,
|
||||
expression: `.b`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
document: `[1,2,3]`,
|
||||
expression: `[0]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: `Maps can have numbers as keys, so this default to a non-exisiting key behaviour.`,
|
||||
document: `{a: b}`,
|
||||
expression: `[0]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar`,
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
85
pkg/yqlib/operators_test.go
Normal file
85
pkg/yqlib/operators_test.go
Normal file
@ -0,0 +1,85 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
)
|
||||
|
||||
type expressionScenario struct {
|
||||
description string
|
||||
document string
|
||||
expression string
|
||||
expected []string
|
||||
}
|
||||
|
||||
func testScenario(t *testing.T, s *expressionScenario) {
|
||||
|
||||
nodes := readDoc(t, s.document)
|
||||
path, errPath := treeCreator.ParsePath(s.expression)
|
||||
if errPath != nil {
|
||||
t.Error(errPath)
|
||||
return
|
||||
}
|
||||
results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
if errNav != nil {
|
||||
t.Error(errNav)
|
||||
return
|
||||
}
|
||||
test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
|
||||
}
|
||||
|
||||
func documentScenarios(t *testing.T, title string, scenarios []expressionScenario) {
|
||||
f, err := os.Create(fmt.Sprintf("doc/%v.md", title))
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
w := bufio.NewWriter(f)
|
||||
w.WriteString(fmt.Sprintf("# %v\n", title))
|
||||
w.WriteString(fmt.Sprintf("## Examples\n"))
|
||||
|
||||
printer := NewPrinter(false, true, false, 2, true)
|
||||
|
||||
for index, s := range scenarios {
|
||||
if s.description != "" {
|
||||
w.WriteString(fmt.Sprintf("### %v\n", s.description))
|
||||
} else {
|
||||
w.WriteString(fmt.Sprintf("### Example %v\n", index))
|
||||
}
|
||||
if s.document != "" {
|
||||
w.WriteString(fmt.Sprintf("sample.yml:\n"))
|
||||
w.WriteString(fmt.Sprintf("```yaml\n%v\n```\n", s.document))
|
||||
}
|
||||
if s.expression != "" {
|
||||
w.WriteString(fmt.Sprintf("Expression\n"))
|
||||
w.WriteString(fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression))
|
||||
}
|
||||
|
||||
w.WriteString(fmt.Sprintf("Result\n"))
|
||||
|
||||
nodes := readDoc(t, s.document)
|
||||
path, errPath := treeCreator.ParsePath(s.expression)
|
||||
if errPath != nil {
|
||||
t.Error(errPath)
|
||||
return
|
||||
}
|
||||
var output bytes.Buffer
|
||||
results, err := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
printer.PrintResults(results, bufio.NewWriter(&output))
|
||||
|
||||
w.WriteString(fmt.Sprintf("```yaml\n%v```\n", output.String()))
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
w.Flush()
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"errors"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package treeops
|
||||
package yqlib
|
||||
|
||||
import "fmt"
|
||||
|
72
pkg/yqlib/printer.go
Normal file
72
pkg/yqlib/printer.go
Normal file
@ -0,0 +1,72 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"io"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Printer interface {
|
||||
PrintResults(matchingNodes *list.List, writer io.Writer) error
|
||||
}
|
||||
|
||||
type resultsPrinter struct {
|
||||
outputToJSON bool
|
||||
unwrapScalar bool
|
||||
colorsEnabled bool
|
||||
indent int
|
||||
printDocSeparators bool
|
||||
}
|
||||
|
||||
func NewPrinter(outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer {
|
||||
return &resultsPrinter{outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators}
|
||||
}
|
||||
|
||||
func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error {
|
||||
var encoder Encoder
|
||||
if node.Kind == yaml.ScalarNode && p.unwrapScalar && !p.outputToJSON {
|
||||
return p.writeString(writer, node.Value+"\n")
|
||||
}
|
||||
if p.outputToJSON {
|
||||
encoder = NewJsonEncoder(writer, p.indent)
|
||||
} else {
|
||||
encoder = NewYamlEncoder(writer, p.indent, p.colorsEnabled)
|
||||
}
|
||||
return encoder.Encode(node)
|
||||
}
|
||||
|
||||
func (p *resultsPrinter) writeString(writer io.Writer, txt string) error {
|
||||
_, errorWriting := writer.Write([]byte(txt))
|
||||
return errorWriting
|
||||
}
|
||||
|
||||
func (p *resultsPrinter) PrintResults(matchingNodes *list.List, writer io.Writer) error {
|
||||
|
||||
bufferedWriter := bufio.NewWriter(writer)
|
||||
defer safelyFlush(bufferedWriter)
|
||||
|
||||
if matchingNodes.Len() == 0 {
|
||||
log.Debug("no matching results, nothing to print")
|
||||
return nil
|
||||
}
|
||||
|
||||
var previousDocIndex uint = 0
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
mappedDoc := el.Value.(*CandidateNode)
|
||||
|
||||
if previousDocIndex != mappedDoc.Document && p.printDocSeparators {
|
||||
p.writeString(bufferedWriter, "---\n")
|
||||
}
|
||||
|
||||
if err := p.printNode(mappedDoc.Node, bufferedWriter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
previousDocIndex = mappedDoc.Document
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package treeops
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
)
|
||||
|
||||
type expressionScenario struct {
|
||||
document string
|
||||
expression string
|
||||
expected []string
|
||||
}
|
||||
|
||||
func testScenario(t *testing.T, s *expressionScenario) {
|
||||
|
||||
nodes := readDoc(t, s.document)
|
||||
path, errPath := treeCreator.ParsePath(s.expression)
|
||||
if errPath != nil {
|
||||
t.Error(errPath)
|
||||
return
|
||||
}
|
||||
results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
if errNav != nil {
|
||||
t.Error(errNav)
|
||||
return
|
||||
}
|
||||
test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
|
||||
}
|
@ -1 +0,0 @@
|
||||
{name: Mike, pets: [cat, dog]}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "Mike",
|
||||
"pets": [
|
||||
"cat",
|
||||
"dog"
|
||||
]
|
||||
}
|
122
pkg/yqlib/utils.go
Normal file
122
pkg/yqlib/utils.go
Normal file
@ -0,0 +1,122 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func readStream(filename string) (*yaml.Decoder, error) {
|
||||
if filename == "" {
|
||||
return nil, errors.New("Must provide filename")
|
||||
}
|
||||
|
||||
var stream io.Reader
|
||||
if filename == "-" {
|
||||
stream = bufio.NewReader(os.Stdin)
|
||||
} else {
|
||||
file, err := os.Open(filename) // nolint gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safelyCloseFile(file)
|
||||
stream = file
|
||||
}
|
||||
return yaml.NewDecoder(stream), nil
|
||||
}
|
||||
|
||||
// put this in lib
|
||||
func Evaluate(filename string, node *PathTreeNode) (*list.List, error) {
|
||||
|
||||
var treeNavigator = NewDataTreeNavigator(NavigationPrefs{})
|
||||
|
||||
var matchingNodes = list.New()
|
||||
|
||||
var currentIndex uint = 0
|
||||
var decoder, err = readStream(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
var dataBucket yaml.Node
|
||||
errorReading := decoder.Decode(&dataBucket)
|
||||
|
||||
if errorReading == io.EOF {
|
||||
return matchingNodes, nil
|
||||
} else if errorReading != nil {
|
||||
return nil, errorReading
|
||||
}
|
||||
candidateNode := &CandidateNode{
|
||||
Document: currentIndex,
|
||||
Filename: filename,
|
||||
Node: &dataBucket,
|
||||
}
|
||||
inputList := list.New()
|
||||
inputList.PushBack(candidateNode)
|
||||
|
||||
newMatches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node)
|
||||
if errorParsing != nil {
|
||||
return nil, errorParsing
|
||||
}
|
||||
matchingNodes.PushBackList(newMatches)
|
||||
currentIndex = currentIndex + 1
|
||||
}
|
||||
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func safelyRenameFile(from string, to string) {
|
||||
if renameError := os.Rename(from, to); renameError != nil {
|
||||
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
|
||||
log.Debug(renameError.Error())
|
||||
// can't do this rename when running in docker to a file targeted in a mounted volume,
|
||||
// so gracefully degrade to copying the entire contents.
|
||||
if copyError := copyFileContents(from, to); copyError != nil {
|
||||
log.Errorf("Failed copying from %v to %v", from, to)
|
||||
log.Error(copyError.Error())
|
||||
} else {
|
||||
removeErr := os.Remove(from)
|
||||
if removeErr != nil {
|
||||
log.Errorf("failed removing original file: %s", from)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
|
||||
func copyFileContents(src, dst string) (err error) {
|
||||
in, err := os.Open(src) // nolint gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer safelyCloseFile(in)
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer safelyCloseFile(out)
|
||||
if _, err = io.Copy(out, in); err != nil {
|
||||
return err
|
||||
}
|
||||
return out.Sync()
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user