mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-13 22:38:04 +00:00
refactored
This commit is contained in:
parent
0cb2ff5b2e
commit
b1f139c965
@ -11,19 +11,18 @@ var unwrapScalar = true
|
|||||||
var customStyle = ""
|
var customStyle = ""
|
||||||
var anchorName = ""
|
var anchorName = ""
|
||||||
var makeAlias = false
|
var makeAlias = false
|
||||||
var stripComments = false
|
|
||||||
var writeInplace = false
|
var writeInplace = false
|
||||||
var writeScript = ""
|
var writeScript = ""
|
||||||
var sourceYamlFile = ""
|
var sourceYamlFile = ""
|
||||||
var outputToJSON = false
|
var outputToJSON = false
|
||||||
var exitStatus = false
|
var exitStatus = false
|
||||||
var prettyPrint = false
|
|
||||||
var explodeAnchors = false
|
var explodeAnchors = false
|
||||||
var forceColor = false
|
var forceColor = false
|
||||||
var forceNoColor = false
|
var forceNoColor = false
|
||||||
var colorsEnabled = false
|
var colorsEnabled = false
|
||||||
var defaultValue = ""
|
var defaultValue = ""
|
||||||
var indent = 2
|
var indent = 2
|
||||||
|
var printDocSeparators = true
|
||||||
var overwriteFlag = false
|
var overwriteFlag = false
|
||||||
var autoCreateFlag = true
|
var autoCreateFlag = true
|
||||||
var arrayMergeStrategyFlag = "update"
|
var arrayMergeStrategyFlag = "update"
|
||||||
|
26
cmd/root.go
26
cmd/root.go
@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/pkg/yqlib/treeops"
|
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
logging "gopkg.in/op/go-logging.v1"
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
@ -40,7 +40,7 @@ func New() *cobra.Command {
|
|||||||
// }
|
// }
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
|
|
||||||
var treeCreator = treeops.NewPathTreeCreator()
|
var treeCreator = yqlib.NewPathTreeCreator()
|
||||||
|
|
||||||
expression := ""
|
expression := ""
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
@ -53,13 +53,13 @@ func New() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if outputToJSON {
|
if outputToJSON {
|
||||||
explodeOp := treeops.Operation{OperationType: treeops.Explode}
|
explodeOp := yqlib.Operation{OperationType: yqlib.Explode}
|
||||||
explodeNode := treeops.PathTreeNode{Operation: &explodeOp}
|
explodeNode := yqlib.PathTreeNode{Operation: &explodeOp}
|
||||||
pipeOp := treeops.Operation{OperationType: treeops.Pipe}
|
pipeOp := yqlib.Operation{OperationType: yqlib.Pipe}
|
||||||
pathNode = &treeops.PathTreeNode{Operation: &pipeOp, Lhs: pathNode, Rhs: &explodeNode}
|
pathNode = &yqlib.PathTreeNode{Operation: &pipeOp, Lhs: pathNode, Rhs: &explodeNode}
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingNodes, err := evaluate("-", pathNode)
|
matchingNodes, err := yqlib.Evaluate("-", pathNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -71,7 +71,14 @@ func New() *cobra.Command {
|
|||||||
|
|
||||||
out := cmd.OutOrStdout()
|
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) {
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
cmd.SetOut(cmd.OutOrStdout())
|
cmd.SetOut(cmd.OutOrStdout())
|
||||||
@ -92,8 +99,7 @@ func New() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
|
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(&outputToJSON, "tojson", "j", false, "output as json. Set indent to 0 to print json in one line.")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print")
|
|
||||||
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
||||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"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 encoder = json.NewEncoder(destination)
|
||||||
var indentString = ""
|
var indentString = ""
|
||||||
|
|
||||||
for index := 0; index < indent; index++ {
|
for index := 0; index < indent; index++ {
|
||||||
indentString = indentString + " "
|
indentString = indentString + " "
|
||||||
}
|
}
|
||||||
if prettyPrint {
|
|
||||||
encoder.SetIndent("", indentString)
|
encoder.SetIndent("", indentString)
|
||||||
}
|
|
||||||
return &jsonEncoder{encoder}
|
return &jsonEncoder{encoder}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -9,8 +9,6 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logging.MustGetLogger("yq-treeops")
|
|
||||||
|
|
||||||
type OperationType struct {
|
type OperationType struct {
|
||||||
Type string
|
Type string
|
||||||
NumArgs uint // number of arguments to the op
|
NumArgs uint // number of arguments to the op
|
||||||
@ -25,6 +23,9 @@ type OperationType struct {
|
|||||||
// - mergeAppend (merges and appends arrays)
|
// - mergeAppend (merges and appends arrays)
|
||||||
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
||||||
// - updateTag - not recursive
|
// - updateTag - not recursive
|
||||||
|
// - select by tag (tag==)
|
||||||
|
// - get tag (tag)
|
||||||
|
// - select by style (style==)
|
||||||
// - compare ??
|
// - compare ??
|
||||||
// - validate ??
|
// - validate ??
|
||||||
// - exists
|
// - exists
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
func Match(name string, pattern string) (matched bool) {
|
func Match(name string, pattern string) (matched bool) {
|
||||||
if pattern == "" {
|
if pattern == "" {
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import "container/list"
|
import "container/list"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -49,4 +49,5 @@ func TestEqualOperatorScenarios(t *testing.T) {
|
|||||||
for _, tt := range equalsOperatorScenarios {
|
for _, tt := range equalsOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
|
documentScenarios(t, "Equal Operator", equalsOperatorScenarios)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import "container/list"
|
import "container/list"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import "container/list"
|
import "container/list"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -186,6 +186,8 @@ func traverseArray(candidate *CandidateNode, operation *Operation) ([]*Candidate
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch operation.Value.(type) {
|
||||||
|
case int64:
|
||||||
index := operation.Value.(int64)
|
index := operation.Value.(int64)
|
||||||
indexToUse := index
|
indexToUse := index
|
||||||
contentLength := int64(len(candidate.Node.Content))
|
contentLength := int64(len(candidate.Node.Content))
|
||||||
@ -207,5 +209,9 @@ func traverseArray(candidate *CandidateNode, operation *Operation) ([]*Candidate
|
|||||||
Document: candidate.Document,
|
Document: candidate.Document,
|
||||||
Path: append(candidate.Path, index),
|
Path: append(candidate.Path, index),
|
||||||
}}, nil
|
}}, 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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -126,6 +126,26 @@ var traversePathOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[b c], (!!str)::frog\n",
|
"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,
|
document: mergeDocSample,
|
||||||
expression: `.foobar`,
|
expression: `.foobar`,
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import "container/list"
|
import "container/list"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import "container/list"
|
import "container/list"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"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 (
|
import (
|
||||||
"container/list"
|
"container/list"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package treeops
|
package yqlib
|
||||||
|
|
||||||
import "fmt"
|
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