Support local managment for embedded agent on nginx

This commit is contained in:
davidga
2022-11-13 13:29:35 +02:00
parent 8b01396eca
commit 1b4b7d17e0
406 changed files with 37980 additions and 35 deletions

View File

@@ -0,0 +1,83 @@
package yqlib
import (
"container/list"
yaml "gopkg.in/yaml.v3"
)
// A yaml expression evaluator that runs the expression once against all files/nodes in memory.
type Evaluator interface {
EvaluateFiles(expression string, filenames []string, printer Printer, decoder Decoder) error
// EvaluateNodes takes an expression and one or more yaml nodes, returning a list of matching candidate nodes
EvaluateNodes(expression string, nodes ...*yaml.Node) (*list.List, error)
// EvaluateCandidateNodes takes an expression and list of candidate nodes, returning a list of matching candidate nodes
EvaluateCandidateNodes(expression string, inputCandidateNodes *list.List) (*list.List, error)
}
type allAtOnceEvaluator struct {
treeNavigator DataTreeNavigator
}
func NewAllAtOnceEvaluator() Evaluator {
InitExpressionParser()
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator()}
}
func (e *allAtOnceEvaluator) EvaluateNodes(expression string, nodes ...*yaml.Node) (*list.List, error) {
inputCandidates := list.New()
for _, node := range nodes {
inputCandidates.PushBack(&CandidateNode{Node: node})
}
return e.EvaluateCandidateNodes(expression, inputCandidates)
}
func (e *allAtOnceEvaluator) EvaluateCandidateNodes(expression string, inputCandidates *list.List) (*list.List, error) {
node, err := ExpressionParser.ParseExpression(expression)
if err != nil {
return nil, err
}
context, err := e.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputCandidates}, node)
if err != nil {
return nil, err
}
return context.MatchingNodes, nil
}
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer, decoder Decoder) error {
fileIndex := 0
var allDocuments = list.New()
for _, filename := range filenames {
reader, err := readStream(filename)
if err != nil {
return err
}
fileDocuments, err := readDocuments(reader, filename, fileIndex, decoder)
if err != nil {
return err
}
allDocuments.PushBackList(fileDocuments)
fileIndex = fileIndex + 1
}
if allDocuments.Len() == 0 {
candidateNode := &CandidateNode{
Document: 0,
Filename: "",
Node: &yaml.Node{Kind: yaml.DocumentNode, Content: []*yaml.Node{{Tag: "!!null", Kind: yaml.ScalarNode}}},
FileIndex: 0,
LeadingContent: "",
}
allDocuments.PushBack(candidateNode)
}
matches, err := e.EvaluateCandidateNodes(expression, allDocuments)
if err != nil {
return err
}
return printer.PrintResults(matches)
}

View File

@@ -0,0 +1,40 @@
package yqlib
import (
"testing"
"github.com/mikefarah/yq/v4/test"
)
var evaluateNodesScenario = []expressionScenario{
{
document: `a: hello`,
expression: `.a`,
expected: []string{
"D0, P[a], (!!str)::hello\n",
},
},
{
document: `a: hello`,
expression: `.`,
expected: []string{
"D0, P[], (doc)::a: hello\n",
},
},
{
document: `- a: "yes"`,
expression: `.[] | has("a")`,
expected: []string{
"D0, P[0], (!!bool)::true\n",
},
},
}
func TestAllAtOnceEvaluateNodes(t *testing.T) {
var evaluator = NewAllAtOnceEvaluator()
for _, tt := range evaluateNodesScenario {
node := test.ParseData(tt.document)
list, _ := evaluator.EvaluateNodes(tt.expression, &node)
test.AssertResultComplex(t, tt.expected, resultsToString(t, list))
}
}

189
external/yq/pkg/yqlib/candidate_node.go vendored Executable file
View File

@@ -0,0 +1,189 @@
package yqlib
import (
"container/list"
"fmt"
"strings"
"github.com/jinzhu/copier"
yaml "gopkg.in/yaml.v3"
)
type CandidateNode struct {
Node *yaml.Node // the actual node
Parent *CandidateNode // parent node
Key *yaml.Node // node key, if this is a value from a map (or index in an array)
LeadingContent string
TrailingContent string
Path []interface{} /// the path we took to get to this node
Document uint // the document index of this node
Filename string
FileIndex int
// when performing op against all nodes given, this will treat all the nodes as one
// (e.g. top level cross document merge). This property does not propagate to child nodes.
EvaluateTogether bool
IsMapKey bool
}
func (n *CandidateNode) GetKey() string {
keyPrefix := ""
if n.IsMapKey {
keyPrefix = "key-"
}
return fmt.Sprintf("%v%v - %v", keyPrefix, n.Document, n.Path)
}
func (n *CandidateNode) GetNiceTag() string {
return unwrapDoc(n.Node).Tag
}
func (n *CandidateNode) GetNicePath() string {
if n.Path != nil && len(n.Path) >= 0 {
pathStr := make([]string, len(n.Path))
for i, v := range n.Path {
pathStr[i] = fmt.Sprintf("%v", v)
}
return strings.Join(pathStr, ".")
}
return ""
}
func (n *CandidateNode) AsList() *list.List {
elMap := list.New()
elMap.PushBack(n)
return elMap
}
func (n *CandidateNode) CreateChildInMap(key *yaml.Node, node *yaml.Node) *CandidateNode {
var value interface{}
if key != nil {
value = key.Value
}
return &CandidateNode{
Node: node,
Path: n.createChildPath(value),
Parent: n,
Key: key,
Document: n.Document,
Filename: n.Filename,
FileIndex: n.FileIndex,
}
}
func (n *CandidateNode) CreateChildInArray(index int, node *yaml.Node) *CandidateNode {
return &CandidateNode{
Node: node,
Path: n.createChildPath(index),
Parent: n,
Key: &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", index), Tag: "!!int"},
Document: n.Document,
Filename: n.Filename,
FileIndex: n.FileIndex,
}
}
func (n *CandidateNode) CreateReplacement(node *yaml.Node) *CandidateNode {
return &CandidateNode{
Node: node,
Path: n.createChildPath(nil),
Parent: n.Parent,
Key: n.Key,
IsMapKey: n.IsMapKey,
Document: n.Document,
Filename: n.Filename,
FileIndex: n.FileIndex,
}
}
func (n *CandidateNode) CreateReplacementWithDocWrappers(node *yaml.Node) *CandidateNode {
replacement := n.CreateReplacement(node)
replacement.LeadingContent = n.LeadingContent
replacement.TrailingContent = n.TrailingContent
return replacement
}
func (n *CandidateNode) createChildPath(path interface{}) []interface{} {
if path == nil {
newPath := make([]interface{}, len(n.Path))
copy(newPath, n.Path)
return newPath
}
//don't use append as they may actually modify the path of the orignal node!
newPath := make([]interface{}, len(n.Path)+1)
copy(newPath, n.Path)
newPath[len(n.Path)] = path
return newPath
}
func (n *CandidateNode) Copy() (*CandidateNode, error) {
clone := &CandidateNode{}
err := copier.Copy(clone, n)
if err != nil {
return nil, err
}
clone.Node = deepClone(n.Node)
return clone, nil
}
// updates this candidate from the given candidate node
func (n *CandidateNode) UpdateFrom(other *CandidateNode, prefs assignPreferences) {
// if this is an empty map or empty array, use the style of other node.
if (n.Node.Kind != yaml.ScalarNode && len(n.Node.Content) == 0) ||
// if the tag has changed (e.g. from str to bool)
(guessTagFromCustomType(n.Node) != guessTagFromCustomType(other.Node)) {
n.Node.Style = other.Node.Style
}
n.Node.Content = deepCloneContent(other.Node.Content)
n.Node.Kind = other.Node.Kind
n.Node.Value = other.Node.Value
n.UpdateAttributesFrom(other, prefs)
}
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignPreferences) {
log.Debug("UpdateAttributesFrom: n: %v other: %v", n.GetKey(), other.GetKey())
if n.Node.Kind != other.Node.Kind {
// clear out the contents when switching to a different type
// e.g. map to array
n.Node.Content = make([]*yaml.Node, 0)
n.Node.Value = ""
}
n.Node.Kind = other.Node.Kind
// don't clobber custom tags...
if prefs.ClobberCustomTags || strings.HasPrefix(n.Node.Tag, "!!") || n.Node.Tag == "" {
n.Node.Tag = other.Node.Tag
}
n.Node.Alias = other.Node.Alias
if !prefs.DontOverWriteAnchor {
n.Node.Anchor = other.Node.Anchor
}
// merge will pickup the style of the new thing
// when autocreating nodes
if n.Node.Style == 0 {
n.Node.Style = other.Node.Style
}
if other.Node.FootComment != "" {
n.Node.FootComment = other.Node.FootComment
}
if other.TrailingContent != "" {
n.TrailingContent = other.TrailingContent
}
if other.Node.HeadComment != "" {
n.Node.HeadComment = other.Node.HeadComment
}
if other.Node.LineComment != "" {
n.Node.LineComment = other.Node.LineComment
}
}

61
external/yq/pkg/yqlib/color_print.go vendored Executable file
View File

@@ -0,0 +1,61 @@
package yqlib
import (
"fmt"
"io"
"github.com/fatih/color"
"github.com/goccy/go-yaml/lexer"
"github.com/goccy/go-yaml/printer"
)
// Thanks @risentveber!
const escape = "\x1b"
func format(attr color.Attribute) string {
return fmt.Sprintf("%s[%dm", escape, attr)
}
func colorizeAndPrint(yamlBytes []byte, writer io.Writer) error {
tokens := lexer.Tokenize(string(yamlBytes))
var p printer.Printer
p.Bool = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiMagenta),
Suffix: format(color.Reset),
}
}
p.Number = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiMagenta),
Suffix: format(color.Reset),
}
}
p.MapKey = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgCyan),
Suffix: format(color.Reset),
}
}
p.Anchor = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiYellow),
Suffix: format(color.Reset),
}
}
p.Alias = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgHiYellow),
Suffix: format(color.Reset),
}
}
p.String = func() *printer.Property {
return &printer.Property{
Prefix: format(color.FgGreen),
Suffix: format(color.Reset),
}
}
_, err := writer.Write([]byte(p.PrintTokens(tokens) + "\n"))
return err
}

133
external/yq/pkg/yqlib/compare_operators.go vendored Executable file
View File

@@ -0,0 +1,133 @@
package yqlib
import (
"fmt"
"strconv"
"time"
yaml "gopkg.in/yaml.v3"
)
type compareTypePref struct {
OrEqual bool
Greater bool
}
func compareOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- compareOperator")
prefs := expressionNode.Operation.Preferences.(compareTypePref)
return crossFunction(d, context.ReadOnlyClone(), expressionNode, compare(prefs), true)
}
func compare(prefs compareTypePref) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
log.Debugf("-- compare cross function")
if lhs == nil && rhs == nil {
owner := &CandidateNode{}
return createBooleanCandidate(owner, prefs.OrEqual), nil
} else if lhs == nil {
log.Debugf("lhs nil, but rhs is not")
return createBooleanCandidate(rhs, false), nil
} else if rhs == nil {
log.Debugf("rhs nil, but rhs is not")
return createBooleanCandidate(lhs, false), nil
}
lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node)
switch lhs.Node.Kind {
case yaml.MappingNode:
return nil, fmt.Errorf("maps not yet supported for comparison")
case yaml.SequenceNode:
return nil, fmt.Errorf("arrays not yet supported for comparison")
default:
if rhs.Node.Kind != yaml.ScalarNode {
return nil, fmt.Errorf("%v (%v) cannot be subtracted from %v", rhs.Node.Tag, rhs.Path, lhs.Node.Tag)
}
target := lhs.CreateReplacement(&yaml.Node{})
boolV, err := compareScalars(context, prefs, lhs.Node, rhs.Node)
return createBooleanCandidate(target, boolV), err
}
}
}
func compareDateTime(layout string, prefs compareTypePref, lhs *yaml.Node, rhs *yaml.Node) (bool, error) {
lhsTime, err := parseDateTime(layout, lhs.Value)
if err != nil {
return false, err
}
rhsTime, err := parseDateTime(layout, rhs.Value)
if err != nil {
return false, err
}
if prefs.OrEqual && lhsTime.Equal(rhsTime) {
return true, nil
}
if prefs.Greater {
return lhsTime.After(rhsTime), nil
}
return lhsTime.Before(rhsTime), nil
}
func compareScalars(context Context, prefs compareTypePref, lhs *yaml.Node, rhs *yaml.Node) (bool, error) {
lhsTag := guessTagFromCustomType(lhs)
rhsTag := guessTagFromCustomType(rhs)
isDateTime := lhs.Tag == "!!timestamp"
// if the lhs is a string, it might be a timestamp in a custom format.
if lhsTag == "!!str" && context.GetDateTimeLayout() != time.RFC3339 {
_, err := parseDateTime(context.GetDateTimeLayout(), lhs.Value)
isDateTime = err == nil
}
if isDateTime {
return compareDateTime(context.GetDateTimeLayout(), prefs, lhs, rhs)
} else if lhsTag == "!!int" && rhsTag == "!!int" {
_, lhsNum, err := parseInt64(lhs.Value)
if err != nil {
return false, err
}
_, rhsNum, err := parseInt64(rhs.Value)
if err != nil {
return false, err
}
if prefs.OrEqual && lhsNum == rhsNum {
return true, nil
}
if prefs.Greater {
return lhsNum > rhsNum, nil
}
return lhsNum < rhsNum, nil
} else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") {
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
if err != nil {
return false, err
}
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
if err != nil {
return false, err
}
if prefs.OrEqual && lhsNum == rhsNum {
return true, nil
}
if prefs.Greater {
return lhsNum > rhsNum, nil
}
return lhsNum < rhsNum, nil
} else if lhsTag == "!!str" && rhsTag == "!!str" {
if prefs.OrEqual && lhs.Value == rhs.Value {
return true, nil
}
if prefs.Greater {
return lhs.Value > rhs.Value, nil
}
return lhs.Value < rhs.Value, nil
}
return false, fmt.Errorf("%v not yet supported for comparison", lhs.Tag)
}

View File

@@ -0,0 +1,371 @@
package yqlib
import (
"testing"
)
var compareOperatorScenarios = []expressionScenario{
// ints, not equal
{
description: "Compare numbers (>)",
document: "a: 5\nb: 4",
expression: ".a > .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: 5\nb: 4",
expression: ".a < .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
description: "Compare integers (>=)",
document: "a: 5\nb: 4",
expression: ".a >= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: 5\nb: 4",
expression: ".a <= .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
// ints, equal
{
skipDoc: true,
description: "Compare equal numbers (>)",
document: "a: 5\nb: 5",
expression: ".a > .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: 5\nb: 5",
expression: ".a < .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
description: "Compare equal numbers (>=)",
document: "a: 5\nb: 5",
expression: ".a >= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: 5\nb: 5",
expression: ".a <= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
// floats, not equal
{
skipDoc: true,
document: "a: 5.2\nb: 4.1",
expression: ".a > .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: 5.2\nb: 4.1",
expression: ".a < .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: 5.2\nb: 4.1",
expression: ".a >= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: 5.5\nb: 4.1",
expression: ".a <= .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
// floats, equal
{
skipDoc: true,
document: "a: 5.5\nb: 5.5",
expression: ".a > .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: 5.5\nb: 5.5",
expression: ".a < .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: 5.1\nb: 5.1",
expression: ".a >= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: 5.1\nb: 5.1",
expression: ".a <= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
// strings, not equal
{
description: "Compare strings",
subdescription: "Compares strings by their bytecode.",
document: "a: zoo\nb: apple",
expression: ".a > .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: zoo\nb: apple",
expression: ".a < .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: zoo\nb: apple",
expression: ".a >= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: zoo\nb: apple",
expression: ".a <= .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
// strings, equal
{
skipDoc: true,
document: "a: cat\nb: cat",
expression: ".a > .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: cat\nb: cat",
expression: ".a < .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: cat\nb: cat",
expression: ".a >= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: cat\nb: cat",
expression: ".a <= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
// datetime, not equal
{
description: "Compare date times",
subdescription: "You can compare date times. Assumes RFC3339 date time format, see [date-time operators](https://mikefarah.gitbook.io/yq/operators/date-time-operators) for more information.",
document: "a: 2021-01-01T03:10:00Z\nb: 2020-01-01T03:10:00Z",
expression: ".a > .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: 2021-01-01T03:10:00Z\nb: 2020-01-01T03:10:00Z",
expression: ".a < .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: 2021-01-01T03:10:00Z\nb: 2020-01-01T03:10:00Z",
expression: ".a >= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: 2021-01-01T03:10:00Z\nb: 2020-01-01T03:10:00Z",
expression: ".a <= .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
// datetime, equal
{
skipDoc: true,
document: "a: 2021-01-01T03:10:00Z\nb: 2021-01-01T03:10:00Z",
expression: ".a > .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: 2021-01-01T03:10:00Z\nb: 2021-01-01T03:10:00Z",
expression: ".a < .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "a: 2021-01-01T03:10:00Z\nb: 2021-01-01T03:10:00Z",
expression: ".a >= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: "a: 2021-01-01T03:10:00Z\nb: 2021-01-01T03:10:00Z",
expression: ".a <= .b",
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
},
// both null
{
description: "Both sides are null: > is false",
expression: ".a > .b",
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
skipDoc: true,
expression: ".a < .b",
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Both sides are null: >= is true",
expression: ".a >= .b",
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
{
skipDoc: true,
expression: ".a <= .b",
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
// one null
{
skipDoc: true,
description: "One side is null: > is false",
document: `a: 5`,
expression: ".a > .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: `a: 5`,
expression: ".a < .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
description: "One side is null: >= is false",
document: `a: 5`,
expression: ".a >= .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: `a: 5`,
expression: ".a <= .b",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: `a: 5`,
expression: ".b <= .a",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: `a: 5`,
expression: ".b < .a",
expected: []string{
"D0, P[a], (!!bool)::false\n",
},
},
}
func TestCompareOperatorScenarios(t *testing.T) {
for _, tt := range compareOperatorScenarios {
testScenario(t, &tt)
}
documentOperatorScenarios(t, "compare", compareOperatorScenarios)
}

122
external/yq/pkg/yqlib/context.go vendored Executable file
View File

@@ -0,0 +1,122 @@
package yqlib
import (
"container/list"
"fmt"
"time"
"github.com/jinzhu/copier"
logging "gopkg.in/op/go-logging.v1"
)
type Context struct {
MatchingNodes *list.List
Variables map[string]*list.List
DontAutoCreate bool
datetimeLayout string
}
func (n *Context) SingleReadonlyChildContext(candidate *CandidateNode) Context {
list := list.New()
list.PushBack(candidate)
newContext := n.ChildContext(list)
newContext.DontAutoCreate = true
return newContext
}
func (n *Context) SingleChildContext(candidate *CandidateNode) Context {
list := list.New()
list.PushBack(candidate)
return n.ChildContext(list)
}
func (n *Context) SetDateTimeLayout(newDateTimeLayout string) {
n.datetimeLayout = newDateTimeLayout
}
func (n *Context) GetDateTimeLayout() string {
if n.datetimeLayout != "" {
return n.datetimeLayout
}
return time.RFC3339
}
func (n *Context) GetVariable(name string) *list.List {
if n.Variables == nil {
return nil
}
return n.Variables[name]
}
func (n *Context) SetVariable(name string, value *list.List) {
if n.Variables == nil {
n.Variables = make(map[string]*list.List)
}
n.Variables[name] = value
}
func (n *Context) ChildContext(results *list.List) Context {
clone := Context{DontAutoCreate: n.DontAutoCreate, datetimeLayout: n.datetimeLayout}
clone.Variables = make(map[string]*list.List)
if len(n.Variables) > 0 {
err := copier.Copy(&clone.Variables, n.Variables)
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
}
clone.MatchingNodes = results
return clone
}
func (n *Context) ToString() string {
if !log.IsEnabledFor(logging.DEBUG) {
return ""
}
result := fmt.Sprintf("Context\nDontAutoCreate: %v\n", n.DontAutoCreate)
return result + NodesToString(n.MatchingNodes)
}
func (n *Context) DeepClone() Context {
clone := Context{}
err := copier.Copy(&clone, n)
// copier doesn't do lists properly for some reason
clone.MatchingNodes = list.New()
for el := n.MatchingNodes.Front(); el != nil; el = el.Next() {
clonedNode, err := el.Value.(*CandidateNode).Copy()
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
clone.MatchingNodes.PushBack(clonedNode)
}
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
return clone
}
func (n *Context) Clone() Context {
clone := Context{}
err := copier.Copy(&clone, n)
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
return clone
}
func (n *Context) ReadOnlyClone() Context {
clone := n.Clone()
clone.DontAutoCreate = true
return clone
}
func (n *Context) WritableClone() Context {
clone := n.Clone()
clone.DontAutoCreate = false
return clone
}

282
external/yq/pkg/yqlib/csv_test.go vendored Executable file
View File

@@ -0,0 +1,282 @@
package yqlib
import (
"bufio"
"fmt"
"testing"
"github.com/mikefarah/yq/v4/test"
)
const csvSimple = `name,numberOfCats,likesApples,height
Gary,1,true,168.8
Samantha's Rabbit,2,false,-188.8
`
const csvMissing = `name,numberOfCats,likesApples,height
,null,,168.8
`
const expectedUpdatedSimpleCsv = `name,numberOfCats,likesApples,height
Gary,3,true,168.8
Samantha's Rabbit,2,false,-188.8
`
const csvSimpleShort = `Name,Number of Cats
Gary,1
Samantha's Rabbit,2
`
const tsvSimple = `name numberOfCats likesApples height
Gary 1 true 168.8
Samantha's Rabbit 2 false -188.8
`
const expectedYamlFromCSV = `- name: Gary
numberOfCats: 1
likesApples: true
height: 168.8
- name: Samantha's Rabbit
numberOfCats: 2
likesApples: false
height: -188.8
`
const expectedYamlFromCSVMissingData = `- name: Gary
numberOfCats: 1
height: 168.8
- name: Samantha's Rabbit
height: -188.8
likesApples: false
`
const csvSimpleMissingData = `name,numberOfCats,height
Gary,1,168.8
Samantha's Rabbit,,-188.8
`
const csvTestSimpleYaml = `- [i, like, csv]
- [because, excel, is, cool]`
const expectedSimpleCsv = `i,like,csv
because,excel,is,cool
`
const tsvTestExpectedSimpleCsv = `i like csv
because excel is cool
`
var csvScenarios = []formatScenario{
{
description: "Encode CSV simple",
input: csvTestSimpleYaml,
expected: expectedSimpleCsv,
scenarioType: "encode-csv",
},
{
description: "Encode TSV simple",
input: csvTestSimpleYaml,
expected: tsvTestExpectedSimpleCsv,
scenarioType: "encode-tsv",
},
{
description: "Encode Empty",
skipDoc: true,
input: `[]`,
expected: "",
scenarioType: "encode-csv",
},
{
description: "Comma in value",
skipDoc: true,
input: `["comma, in, value", things]`,
expected: "\"comma, in, value\",things\n",
scenarioType: "encode-csv",
},
{
description: "Encode array of objects to csv",
input: expectedYamlFromCSV,
expected: csvSimple,
scenarioType: "encode-csv",
},
{
description: "Encode array of objects to custom csv format",
subdescription: "Add the header row manually, then the we convert each object into an array of values - resulting in an array of arrays. Pick the columns and call the header whatever you like.",
input: expectedYamlFromCSV,
expected: csvSimpleShort,
expression: `[["Name", "Number of Cats"]] + [.[] | [.name, .numberOfCats ]]`,
scenarioType: "encode-csv",
},
{
description: "Encode array of objects to csv - missing fields behaviour",
subdescription: "First entry is used to determine the headers, and it is missing 'likesApples', so it is not included in the csv. Second entry does not have 'numberOfCats' so that is blank",
input: expectedYamlFromCSVMissingData,
expected: csvSimpleMissingData,
scenarioType: "encode-csv",
},
{
description: "decode csv missing",
skipDoc: true,
input: csvMissing,
expected: csvMissing,
scenarioType: "roundtrip-csv",
},
{
description: "Parse CSV into an array of objects",
subdescription: "First row is assumed to be the header row.",
input: csvSimple,
expected: expectedYamlFromCSV,
scenarioType: "decode-csv-object",
},
{
description: "Parse TSV into an array of objects",
subdescription: "First row is assumed to be the header row.",
input: tsvSimple,
expected: expectedYamlFromCSV,
scenarioType: "decode-tsv-object",
},
{
description: "Round trip",
input: csvSimple,
expected: expectedUpdatedSimpleCsv,
expression: `(.[] | select(.name == "Gary") | .numberOfCats) = 3`,
scenarioType: "roundtrip-csv",
},
}
func testCSVScenario(t *testing.T, s formatScenario) {
switch s.scenarioType {
case "encode-csv":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewCsvEncoder(',')), s.description)
case "encode-tsv":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewCsvEncoder('\t')), s.description)
case "decode-csv-object":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewCSVObjectDecoder(','), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
case "decode-tsv-object":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewCSVObjectDecoder('\t'), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
case "roundtrip-csv":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewCSVObjectDecoder(','), NewCsvEncoder(',')), s.description)
default:
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
}
}
func documentCSVDecodeObjectScenario(w *bufio.Writer, s formatScenario, formatType string) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, fmt.Sprintf("Given a sample.%v file of:\n", formatType))
writeOrPanic(w, fmt.Sprintf("```%v\n%v\n```\n", formatType, s.input))
writeOrPanic(w, "then\n")
writeOrPanic(w, fmt.Sprintf("```bash\nyq -p=%v sample.%v\n```\n", formatType, formatType))
writeOrPanic(w, "will output\n")
separator := ','
if formatType == "tsv" {
separator = '\t'
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n",
mustProcessFormatScenario(s, NewCSVObjectDecoder(separator), NewYamlEncoder(s.indent, false, ConfiguredYamlPreferences))),
)
}
func documentCSVEncodeScenario(w *bufio.Writer, s formatScenario, formatType string) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
expression := s.expression
if expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -o=%v '%v' sample.yml\n```\n", formatType, expression))
} else {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -o=%v sample.yml\n```\n", formatType))
}
writeOrPanic(w, "will output\n")
separator := ','
if formatType == "tsv" {
separator = '\t'
}
writeOrPanic(w, fmt.Sprintf("```%v\n%v```\n\n", formatType,
mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewCsvEncoder(separator))),
)
}
func documentCSVRoundTripScenario(w *bufio.Writer, s formatScenario, formatType string) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, fmt.Sprintf("Given a sample.%v file of:\n", formatType))
writeOrPanic(w, fmt.Sprintf("```%v\n%v\n```\n", formatType, s.input))
writeOrPanic(w, "then\n")
expression := s.expression
if expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -p=%v -o=%v '%v' sample.%v\n```\n", formatType, formatType, expression, formatType))
} else {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -p=%v -o=%v sample.%v\n```\n", formatType, formatType, formatType))
}
writeOrPanic(w, "will output\n")
separator := ','
if formatType == "tsv" {
separator = '\t'
}
writeOrPanic(w, fmt.Sprintf("```%v\n%v```\n\n", formatType,
mustProcessFormatScenario(s, NewCSVObjectDecoder(separator), NewCsvEncoder(separator))),
)
}
func documentCSVScenario(t *testing.T, w *bufio.Writer, i interface{}) {
s := i.(formatScenario)
if s.skipDoc {
return
}
switch s.scenarioType {
case "encode-csv":
documentCSVEncodeScenario(w, s, "csv")
case "encode-tsv":
documentCSVEncodeScenario(w, s, "tsv")
case "decode-csv-object":
documentCSVDecodeObjectScenario(w, s, "csv")
case "decode-tsv-object":
documentCSVDecodeObjectScenario(w, s, "tsv")
case "roundtrip-csv":
documentCSVRoundTripScenario(w, s, "csv")
default:
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
}
}
func TestCSVScenarios(t *testing.T) {
for _, tt := range csvScenarios {
testCSVScenario(t, tt)
}
genericScenarios := make([]interface{}, len(csvScenarios))
for i, s := range csvScenarios {
genericScenarios[i] = s
}
documentScenarios(t, "usage", "csv-tsv", genericScenarios, documentCSVScenario)
}

41
external/yq/pkg/yqlib/data_tree_navigator.go vendored Executable file
View File

@@ -0,0 +1,41 @@
package yqlib
import (
"fmt"
logging "gopkg.in/op/go-logging.v1"
)
type DataTreeNavigator interface {
// given the context and a expressionNode,
// this will process the against the given expressionNode and return
// a new context of matching candidates
GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error)
}
type dataTreeNavigator struct {
}
func NewDataTreeNavigator() DataTreeNavigator {
return &dataTreeNavigator{}
}
func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error) {
if expressionNode == nil {
log.Debugf("getMatchingNodes - nothing to do")
return context, nil
}
log.Debugf("Processing Op: %v", expressionNode.Operation.toString())
if log.IsEnabledFor(logging.DEBUG) {
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
log.Debug(NodeToString(el.Value.(*CandidateNode)))
}
}
log.Debug(">>")
handler := expressionNode.Operation.OperationType.Handler
if handler != nil {
return handler(d, context, expressionNode)
}
return Context{}, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
}

42
external/yq/pkg/yqlib/decoder.go vendored Executable file
View File

@@ -0,0 +1,42 @@
package yqlib
import (
"fmt"
"io"
)
type InputFormat uint
const (
YamlInputFormat = 1 << iota
XMLInputFormat
PropertiesInputFormat
Base64InputFormat
JsonInputFormat
CSVObjectInputFormat
TSVObjectInputFormat
)
type Decoder interface {
Init(reader io.Reader) error
Decode() (*CandidateNode, error)
}
func InputFormatFromString(format string) (InputFormat, error) {
switch format {
case "yaml", "y":
return YamlInputFormat, nil
case "xml", "x":
return XMLInputFormat, nil
case "props", "p":
return PropertiesInputFormat, nil
case "json", "ndjson", "j":
return JsonInputFormat, nil
case "csv", "c":
return CSVObjectInputFormat, nil
case "tsv", "t":
return TSVObjectInputFormat, nil
default:
return 0, fmt.Errorf("unknown format '%v' please use [yaml|xml|props]", format)
}
}

57
external/yq/pkg/yqlib/decoder_base64.go vendored Executable file
View File

@@ -0,0 +1,57 @@
package yqlib
import (
"bytes"
"encoding/base64"
"io"
yaml "gopkg.in/yaml.v3"
)
type base64Decoder struct {
reader io.Reader
finished bool
readAnything bool
encoding base64.Encoding
}
func NewBase64Decoder() Decoder {
return &base64Decoder{finished: false, encoding: *base64.StdEncoding}
}
func (dec *base64Decoder) Init(reader io.Reader) error {
dec.reader = reader
dec.readAnything = false
dec.finished = false
return nil
}
func (dec *base64Decoder) Decode() (*CandidateNode, error) {
if dec.finished {
return nil, io.EOF
}
base64Reader := base64.NewDecoder(&dec.encoding, dec.reader)
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(base64Reader); err != nil {
return nil, err
}
if buf.Len() == 0 {
dec.finished = true
// if we've read _only_ an empty string, lets return that
// otherwise if we've already read some bytes, and now we get
// an empty string, then we are done.
if dec.readAnything {
return nil, io.EOF
}
}
dec.readAnything = true
return &CandidateNode{
Node: &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: buf.String(),
},
}, nil
}

81
external/yq/pkg/yqlib/decoder_csv_object.go vendored Executable file
View File

@@ -0,0 +1,81 @@
package yqlib
import (
"encoding/csv"
"errors"
"io"
"github.com/dimchansky/utfbom"
yaml "gopkg.in/yaml.v3"
)
type csvObjectDecoder struct {
separator rune
reader csv.Reader
finished bool
}
func NewCSVObjectDecoder(separator rune) Decoder {
return &csvObjectDecoder{separator: separator}
}
func (dec *csvObjectDecoder) Init(reader io.Reader) error {
cleanReader, enc := utfbom.Skip(reader)
log.Debugf("Detected encoding: %s\n", enc)
dec.reader = *csv.NewReader(cleanReader)
dec.reader.Comma = dec.separator
dec.finished = false
return nil
}
func (dec *csvObjectDecoder) convertToYamlNode(content string) *yaml.Node {
node, err := parseSnippet(content)
if err != nil {
return createScalarNode(content, content)
}
return node
}
func (dec *csvObjectDecoder) createObject(headerRow []string, contentRow []string) *yaml.Node {
objectNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
for i, header := range headerRow {
objectNode.Content = append(
objectNode.Content,
createScalarNode(header, header),
dec.convertToYamlNode(contentRow[i]))
}
return objectNode
}
func (dec *csvObjectDecoder) Decode() (*CandidateNode, error) {
if dec.finished {
return nil, io.EOF
}
headerRow, err := dec.reader.Read()
log.Debugf(": headerRow%v", headerRow)
if err != nil {
return nil, err
}
rootArray := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
contentRow, err := dec.reader.Read()
for err == nil && len(contentRow) > 0 {
log.Debugf("Adding contentRow: %v", contentRow)
rootArray.Content = append(rootArray.Content, dec.createObject(headerRow, contentRow))
contentRow, err = dec.reader.Read()
log.Debugf("Read next contentRow: %v, %v", contentRow, err)
}
if !errors.Is(err, io.EOF) {
return nil, err
}
return &CandidateNode{
Node: &yaml.Node{
Kind: yaml.DocumentNode,
Content: []*yaml.Node{rootArray},
},
}, nil
}

87
external/yq/pkg/yqlib/decoder_json.go vendored Executable file
View File

@@ -0,0 +1,87 @@
package yqlib
import (
"fmt"
"io"
"github.com/goccy/go-json"
yaml "gopkg.in/yaml.v3"
)
type jsonDecoder struct {
decoder json.Decoder
}
func NewJSONDecoder() Decoder {
return &jsonDecoder{}
}
func (dec *jsonDecoder) Init(reader io.Reader) error {
dec.decoder = *json.NewDecoder(reader)
return nil
}
func (dec *jsonDecoder) Decode() (*CandidateNode, error) {
var dataBucket orderedMap
log.Debug("going to decode")
err := dec.decoder.Decode(&dataBucket)
if err != nil {
return nil, err
}
node, err := dec.convertToYamlNode(&dataBucket)
if err != nil {
return nil, err
}
return &CandidateNode{
Node: &yaml.Node{
Kind: yaml.DocumentNode,
Content: []*yaml.Node{node},
},
}, nil
}
func (dec *jsonDecoder) convertToYamlNode(data *orderedMap) (*yaml.Node, error) {
if data.kv == nil {
switch rawData := data.altVal.(type) {
case nil:
return createScalarNode(nil, "null"), nil
case float64, float32:
// json decoder returns ints as float.
return parseSnippet(fmt.Sprintf("%v", rawData))
case int, int64, int32, string, bool:
return createScalarNode(rawData, fmt.Sprintf("%v", rawData)), nil
case []*orderedMap:
return dec.parseArray(rawData)
default:
return nil, fmt.Errorf("unrecognised type :( %v", rawData)
}
}
var yamlMap = &yaml.Node{Kind: yaml.MappingNode}
for _, keyValuePair := range data.kv {
yamlValue, err := dec.convertToYamlNode(&keyValuePair.V)
if err != nil {
return nil, err
}
yamlMap.Content = append(yamlMap.Content, createScalarNode(keyValuePair.K, keyValuePair.K), yamlValue)
}
return yamlMap, nil
}
func (dec *jsonDecoder) parseArray(dataArray []*orderedMap) (*yaml.Node, error) {
var yamlMap = &yaml.Node{Kind: yaml.SequenceNode}
for _, value := range dataArray {
yamlValue, err := dec.convertToYamlNode(value)
if err != nil {
return nil, err
}
yamlMap.Content = append(yamlMap.Content, yamlValue)
}
return yamlMap, nil
}

161
external/yq/pkg/yqlib/decoder_properties.go vendored Executable file
View File

@@ -0,0 +1,161 @@
package yqlib
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
"github.com/magiconair/properties"
"gopkg.in/yaml.v3"
)
type propertiesDecoder struct {
reader io.Reader
finished bool
d DataTreeNavigator
}
func NewPropertiesDecoder() Decoder {
return &propertiesDecoder{d: NewDataTreeNavigator(), finished: false}
}
func (dec *propertiesDecoder) Init(reader io.Reader) error {
dec.reader = reader
dec.finished = false
return nil
}
func parsePropKey(key string) []interface{} {
pathStrArray := strings.Split(key, ".")
path := make([]interface{}, len(pathStrArray))
for i, pathStr := range pathStrArray {
num, err := strconv.ParseInt(pathStr, 10, 32)
if err == nil {
path[i] = num
} else {
path[i] = pathStr
}
}
return path
}
func (dec *propertiesDecoder) processComment(c string) string {
if c == "" {
return ""
}
return "# " + c
}
func (dec *propertiesDecoder) applyPropertyComments(context Context, path []interface{}, comments []string) error {
assignmentOp := &Operation{OperationType: assignOpType, Preferences: assignPreferences{}}
rhsCandidateNode := &CandidateNode{
Path: path,
Node: &yaml.Node{
Tag: "!!str",
Value: fmt.Sprintf("%v", path[len(path)-1]),
HeadComment: dec.processComment(strings.Join(comments, "\n")),
Kind: yaml.ScalarNode,
},
}
rhsCandidateNode.Node.Tag = guessTagFromCustomType(rhsCandidateNode.Node)
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode}
assignmentOpNode := &ExpressionNode{
Operation: assignmentOp,
LHS: createTraversalTree(path, traversePreferences{}, true),
RHS: &ExpressionNode{Operation: rhsOp},
}
_, err := dec.d.GetMatchingNodes(context, assignmentOpNode)
return err
}
func (dec *propertiesDecoder) applyProperty(context Context, properties *properties.Properties, key string) error {
value, _ := properties.Get(key)
path := parsePropKey(key)
propertyComments := properties.GetComments(key)
if len(propertyComments) > 0 {
err := dec.applyPropertyComments(context, path, propertyComments)
if err != nil {
return nil
}
}
rhsNode := &yaml.Node{
Value: value,
Tag: "!!str",
Kind: yaml.ScalarNode,
}
rhsNode.Tag = guessTagFromCustomType(rhsNode)
rhsCandidateNode := &CandidateNode{
Path: path,
Node: rhsNode,
}
assignmentOp := &Operation{OperationType: assignOpType, Preferences: assignPreferences{}}
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode}
assignmentOpNode := &ExpressionNode{
Operation: assignmentOp,
LHS: createTraversalTree(path, traversePreferences{}, false),
RHS: &ExpressionNode{Operation: rhsOp},
}
_, err := dec.d.GetMatchingNodes(context, assignmentOpNode)
return err
}
func (dec *propertiesDecoder) Decode() (*CandidateNode, error) {
if dec.finished {
return nil, io.EOF
}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(dec.reader); err != nil {
return nil, err
}
if buf.Len() == 0 {
dec.finished = true
return nil, io.EOF
}
properties, err := properties.LoadString(buf.String())
if err != nil {
return nil, err
}
properties.DisableExpansion = true
rootMap := &CandidateNode{
Node: &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
},
}
context := Context{}
context = context.SingleChildContext(rootMap)
for _, key := range properties.Keys() {
if err := dec.applyProperty(context, properties, key); err != nil {
return nil, err
}
}
dec.finished = true
return &CandidateNode{
Node: &yaml.Node{
Kind: yaml.DocumentNode,
Content: []*yaml.Node{rootMap.Node},
},
}, nil
}

69
external/yq/pkg/yqlib/decoder_test.go vendored Executable file
View File

@@ -0,0 +1,69 @@
package yqlib
import (
"bufio"
"bytes"
"strings"
)
type formatScenario struct {
input string
indent int
expression string
expected string
description string
subdescription string
skipDoc bool
scenarioType string
expectedError string
}
func processFormatScenario(s formatScenario, decoder Decoder, encoder Encoder) (string, error) {
var output bytes.Buffer
writer := bufio.NewWriter(&output)
if decoder == nil {
decoder = NewYamlDecoder(ConfiguredYamlPreferences)
}
inputs, err := readDocuments(strings.NewReader(s.input), "sample.yml", 0, decoder)
if err != nil {
return "", err
}
expression := s.expression
if expression == "" {
expression = "."
}
exp, err := getExpressionParser().ParseExpression(expression)
if err != nil {
return "", err
}
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, exp)
if err != nil {
return "", err
}
printer := NewPrinter(encoder, NewSinglePrinterWriter(writer))
err = printer.PrintResults(context.MatchingNodes)
if err != nil {
return "", err
}
writer.Flush()
return output.String(), nil
}
func mustProcessFormatScenario(s formatScenario, decoder Decoder, encoder Encoder) string {
result, err := processFormatScenario(s, decoder, encoder)
if err != nil {
panic(err)
}
return result
}

348
external/yq/pkg/yqlib/decoder_xml.go vendored Executable file
View File

@@ -0,0 +1,348 @@
package yqlib
import (
"encoding/xml"
"errors"
"io"
"strings"
"unicode"
"golang.org/x/net/html/charset"
yaml "gopkg.in/yaml.v3"
)
type xmlDecoder struct {
reader io.Reader
readAnything bool
finished bool
prefs XmlPreferences
}
func NewXMLDecoder(prefs XmlPreferences) Decoder {
return &xmlDecoder{
finished: false,
prefs: prefs,
}
}
func (dec *xmlDecoder) Init(reader io.Reader) error {
dec.reader = reader
dec.readAnything = false
dec.finished = false
return nil
}
func (dec *xmlDecoder) createSequence(nodes []*xmlNode) (*yaml.Node, error) {
yamlNode := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
for _, child := range nodes {
yamlChild, err := dec.convertToYamlNode(child)
if err != nil {
return nil, err
}
yamlNode.Content = append(yamlNode.Content, yamlChild)
}
return yamlNode, nil
}
func (dec *xmlDecoder) processComment(c string) string {
if c == "" {
return ""
}
return "#" + strings.TrimRight(c, " ")
}
func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
log.Debug("createMap: headC: %v, footC: %v", n.HeadComment, n.FootComment)
yamlNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
if len(n.Data) > 0 {
label := dec.prefs.ContentName
labelNode := createScalarNode(label, label)
labelNode.HeadComment = dec.processComment(n.HeadComment)
labelNode.FootComment = dec.processComment(n.FootComment)
yamlNode.Content = append(yamlNode.Content, labelNode, createScalarNode(n.Data, n.Data))
}
for i, keyValuePair := range n.Children {
label := keyValuePair.K
children := keyValuePair.V
labelNode := createScalarNode(label, label)
var valueNode *yaml.Node
var err error
if i == 0 {
labelNode.HeadComment = dec.processComment(n.HeadComment)
}
labelNode.FootComment = dec.processComment(keyValuePair.FootComment)
log.Debug("len of children in %v is %v", label, len(children))
if len(children) > 1 {
valueNode, err = dec.createSequence(children)
if err != nil {
return nil, err
}
} else {
// comment hack for maps of scalars
// if the value is a scalar, the head comment of the scalar needs to go on the key?
// add tests for <z/> as well as multiple <ds> of inputXmlWithComments > yaml
if len(children[0].Children) == 0 && children[0].HeadComment != "" {
labelNode.HeadComment = labelNode.HeadComment + "\n" + strings.TrimSpace(children[0].HeadComment)
children[0].HeadComment = ""
}
valueNode, err = dec.convertToYamlNode(children[0])
if err != nil {
return nil, err
}
}
yamlNode.Content = append(yamlNode.Content, labelNode, valueNode)
}
return yamlNode, nil
}
func (dec *xmlDecoder) convertToYamlNode(n *xmlNode) (*yaml.Node, error) {
if len(n.Children) > 0 {
return dec.createMap(n)
}
scalar := createScalarNode(n.Data, n.Data)
if n.Data == "" {
scalar = createScalarNode(nil, "")
}
log.Debug("scalar headC: %v, footC: %v", n.HeadComment, n.FootComment)
scalar.HeadComment = dec.processComment(n.HeadComment)
scalar.LineComment = dec.processComment(n.LineComment)
scalar.FootComment = dec.processComment(n.FootComment)
return scalar, nil
}
func (dec *xmlDecoder) Decode() (*CandidateNode, error) {
if dec.finished {
return nil, io.EOF
}
root := &xmlNode{}
// cant use xj - it doesn't keep map order.
err := dec.decodeXML(root)
if err != nil {
return nil, err
}
firstNode, err := dec.convertToYamlNode(root)
if err != nil {
return nil, err
} else if firstNode.Tag == "!!null" {
dec.finished = true
if dec.readAnything {
return nil, io.EOF
}
}
dec.readAnything = true
dec.finished = true
return &CandidateNode{
Node: &yaml.Node{
Kind: yaml.DocumentNode,
Content: []*yaml.Node{firstNode},
},
}, nil
}
type xmlNode struct {
Children []*xmlChildrenKv
HeadComment string
FootComment string
LineComment string
Data string
}
type xmlChildrenKv struct {
K string
V []*xmlNode
FootComment string
}
// AddChild appends a node to the list of children
func (n *xmlNode) AddChild(s string, c *xmlNode) {
if n.Children == nil {
n.Children = make([]*xmlChildrenKv, 0)
}
log.Debug("looking for %s", s)
// see if we can find an existing entry to add to
for _, childEntry := range n.Children {
if childEntry.K == s {
log.Debug("found it, appending an entry%s", s)
childEntry.V = append(childEntry.V, c)
log.Debug("yay len of children in %v is %v", s, len(childEntry.V))
return
}
}
log.Debug("not there, making a new one %s", s)
n.Children = append(n.Children, &xmlChildrenKv{K: s, V: []*xmlNode{c}})
}
type element struct {
parent *element
n *xmlNode
label string
state string
}
// this code is heavily based on https://github.com/basgys/goxml2json
// main changes are to decode into a structure that preserves the original order
// of the map keys.
func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
xmlDec := xml.NewDecoder(dec.reader)
xmlDec.Strict = dec.prefs.StrictMode
// That will convert the charset if the provided XML is non-UTF-8
xmlDec.CharsetReader = charset.NewReaderLabel
// Create first element from the root node
elem := &element{
parent: nil,
n: root,
}
getToken := func() (xml.Token, error) {
if dec.prefs.UseRawToken {
return xmlDec.RawToken()
}
return xmlDec.Token()
}
for {
t, e := getToken()
if e != nil && !errors.Is(e, io.EOF) {
return e
}
if t == nil {
break
}
switch se := t.(type) {
case xml.StartElement:
log.Debug("start element %v", se.Name.Local)
elem.state = "started"
// Build new a new current element and link it to its parent
elem = &element{
parent: elem,
n: &xmlNode{},
label: se.Name.Local,
}
// Extract attributes as children
for _, a := range se.Attr {
if dec.prefs.KeepNamespace {
if a.Name.Space != "" {
a.Name.Local = a.Name.Space + ":" + a.Name.Local
}
}
elem.n.AddChild(dec.prefs.AttributePrefix+a.Name.Local, &xmlNode{Data: a.Value})
}
case xml.CharData:
// Extract XML data (if any)
elem.n.Data = trimNonGraphic(string(se))
if elem.n.Data != "" {
elem.state = "chardata"
log.Debug("chardata [%v] for %v", elem.n.Data, elem.label)
}
case xml.EndElement:
log.Debug("end element %v", elem.label)
elem.state = "finished"
// And add it to its parent list
if elem.parent != nil {
elem.parent.n.AddChild(elem.label, elem.n)
}
// Then change the current element to its parent
elem = elem.parent
case xml.Comment:
commentStr := string(xml.CharData(se))
if elem.state == "started" {
applyFootComment(elem, commentStr)
} else if elem.state == "chardata" {
log.Debug("got a line comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
elem.n.LineComment = joinFilter([]string{elem.n.LineComment, commentStr})
} else {
log.Debug("got a head comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
elem.n.HeadComment = joinFilter([]string{elem.n.HeadComment, commentStr})
}
case xml.ProcInst:
if !dec.prefs.SkipProcInst {
elem.n.AddChild(dec.prefs.ProcInstPrefix+se.Target, &xmlNode{Data: string(se.Inst)})
}
case xml.Directive:
if !dec.prefs.SkipDirectives {
elem.n.AddChild(dec.prefs.DirectiveName, &xmlNode{Data: string(se)})
}
}
}
return nil
}
func applyFootComment(elem *element, commentStr string) {
// first lets try to put the comment on the last child
if len(elem.n.Children) > 0 {
lastChildIndex := len(elem.n.Children) - 1
childKv := elem.n.Children[lastChildIndex]
log.Debug("got a foot comment for %v: [%v]", childKv.K, commentStr)
childKv.FootComment = joinFilter([]string{elem.n.FootComment, commentStr})
} else {
log.Debug("got a foot comment for %v: [%v]", elem.label, commentStr)
elem.n.FootComment = joinFilter([]string{elem.n.FootComment, commentStr})
}
}
func joinFilter(rawStrings []string) string {
stringsToJoin := make([]string, 0)
for _, str := range rawStrings {
if str != "" {
stringsToJoin = append(stringsToJoin, str)
}
}
return strings.Join(stringsToJoin, " ")
}
// trimNonGraphic returns a slice of the string s, with all leading and trailing
// non graphic characters and spaces removed.
//
// Graphic characters include letters, marks, numbers, punctuation, symbols,
// and spaces, from categories L, M, N, P, S, Zs.
// Spacing characters are set by category Z and property Pattern_White_Space.
func trimNonGraphic(s string) string {
if s == "" {
return s
}
var first *int
var last int
for i, r := range []rune(s) {
if !unicode.IsGraphic(r) || unicode.IsSpace(r) {
continue
}
if first == nil {
f := i // copy i
first = &f
last = i
} else {
last = i
}
}
// If first is nil, it means there are no graphic characters
if first == nil {
return ""
}
return string([]rune(s)[*first : last+1])
}

114
external/yq/pkg/yqlib/decoder_yaml.go vendored Executable file
View File

@@ -0,0 +1,114 @@
package yqlib
import (
"bufio"
"errors"
"io"
"regexp"
"strings"
yaml "gopkg.in/yaml.v3"
)
type yamlDecoder struct {
decoder yaml.Decoder
// work around of various parsing issues by yaml.v3 with document headers
prefs YamlPreferences
leadingContent string
readAnything bool
firstFile bool
}
func NewYamlDecoder(prefs YamlPreferences) Decoder {
return &yamlDecoder{prefs: prefs, firstFile: true}
}
func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, string, error) {
var commentLineRegEx = regexp.MustCompile(`^\s*#`)
var sb strings.Builder
for {
peekBytes, err := reader.Peek(3)
if errors.Is(err, io.EOF) {
// EOF are handled else where..
return reader, sb.String(), nil
} else if err != nil {
return reader, sb.String(), err
} else if string(peekBytes) == "---" {
_, err := reader.ReadString('\n')
sb.WriteString("$yqDocSeperator$\n")
if errors.Is(err, io.EOF) {
return reader, sb.String(), nil
} else if err != nil {
return reader, sb.String(), err
}
} else if commentLineRegEx.MatchString(string(peekBytes)) {
line, err := reader.ReadString('\n')
sb.WriteString(line)
if errors.Is(err, io.EOF) {
return reader, sb.String(), nil
} else if err != nil {
return reader, sb.String(), err
}
} else {
return reader, sb.String(), nil
}
}
}
func (dec *yamlDecoder) Init(reader io.Reader) error {
readerToUse := reader
leadingContent := ""
var err error
// if we 'evaluating together' - we only process the leading content
// of the first file - this ensures comments from subsequent files are
// merged together correctly.
if dec.prefs.LeadingContentPreProcessing && (!dec.prefs.EvaluateTogether || dec.firstFile) {
readerToUse, leadingContent, err = dec.processReadStream(bufio.NewReader(reader))
if err != nil {
return err
}
}
dec.leadingContent = leadingContent
dec.readAnything = false
dec.decoder = *yaml.NewDecoder(readerToUse)
dec.firstFile = false
return nil
}
func (dec *yamlDecoder) Decode() (*CandidateNode, error) {
var dataBucket yaml.Node
err := dec.decoder.Decode(&dataBucket)
if errors.Is(err, io.EOF) && dec.leadingContent != "" && !dec.readAnything {
// force returning an empty node with a comment.
dec.readAnything = true
return dec.blankNodeWithComment(), nil
} else if err != nil {
return nil, err
}
candidateNode := &CandidateNode{
Node: &dataBucket,
}
if dec.leadingContent != "" {
candidateNode.LeadingContent = dec.leadingContent
dec.leadingContent = ""
}
// move document comments into candidate node
// otherwise unwrap drops them.
candidateNode.TrailingContent = dataBucket.FootComment
dataBucket.FootComment = ""
return candidateNode, nil
}
func (dec *yamlDecoder) blankNodeWithComment() *CandidateNode {
return &CandidateNode{
Document: 0,
Filename: "",
Node: &yaml.Node{Kind: yaml.DocumentNode, Content: []*yaml.Node{{Tag: "!!null", Kind: yaml.ScalarNode}}},
FileIndex: 0,
LeadingContent: dec.leadingContent,
}
}

View File

319
external/yq/pkg/yqlib/doc/operators/add.md vendored Executable file
View File

@@ -0,0 +1,319 @@
# Add
Add behaves differently according to the type of the LHS:
* arrays: concatenate
* number scalars: arithmetic addition
* string scalars: concatenate
* maps: shallow merge (use the multiply operator (`*`) to deeply merge)
Use `+=` as a relative append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
## Concatenate arrays
Given a sample.yml file of:
```yaml
a:
- 1
- 2
b:
- 3
- 4
```
then
```bash
yq '.a + .b' sample.yml
```
will output
```yaml
- 1
- 2
- 3
- 4
```
## Concatenate to existing array
Note that the styling of `a` is kept.
Given a sample.yml file of:
```yaml
a: [1,2]
b:
- 3
- 4
```
then
```bash
yq '.a += .b' sample.yml
```
will output
```yaml
a: [1, 2, 3, 4]
b:
- 3
- 4
```
## Concatenate null to array
Given a sample.yml file of:
```yaml
a:
- 1
- 2
```
then
```bash
yq '.a + null' sample.yml
```
will output
```yaml
- 1
- 2
```
## Append to existing array
Note that the styling is copied from existing array elements
Given a sample.yml file of:
```yaml
a: ['dog']
```
then
```bash
yq '.a += "cat"' sample.yml
```
will output
```yaml
a: ['dog', 'cat']
```
## Add new object to array
Given a sample.yml file of:
```yaml
a:
- dog: woof
```
then
```bash
yq '.a + {"cat": "meow"}' sample.yml
```
will output
```yaml
- dog: woof
- cat: meow
```
## Relative append
Given a sample.yml file of:
```yaml
a:
a1:
b:
- cat
a2:
b:
- dog
a3: {}
```
then
```bash
yq '.a[].b += ["mouse"]' sample.yml
```
will output
```yaml
a:
a1:
b:
- cat
- mouse
a2:
b:
- dog
- mouse
a3:
b:
- mouse
```
## String concatenation
Given a sample.yml file of:
```yaml
a: cat
b: meow
```
then
```bash
yq '.a += .b' sample.yml
```
will output
```yaml
a: catmeow
b: meow
```
## Number addition - float
If the lhs or rhs are floats then the expression will be calculated with floats.
Given a sample.yml file of:
```yaml
a: 3
b: 4.9
```
then
```bash
yq '.a = .a + .b' sample.yml
```
will output
```yaml
a: 7.9
b: 4.9
```
## Number addition - int
If both the lhs and rhs are ints then the expression will be calculated with ints.
Given a sample.yml file of:
```yaml
a: 3
b: 4
```
then
```bash
yq '.a = .a + .b' sample.yml
```
will output
```yaml
a: 7
b: 4
```
## Increment numbers
Given a sample.yml file of:
```yaml
a: 3
b: 5
```
then
```bash
yq '.[] += 1' sample.yml
```
will output
```yaml
a: 4
b: 6
```
## Date addition
You can add durations to dates. Assumes RFC3339 date time format, see [date-time operators](https://mikefarah.gitbook.io/yq/operators/date-time-operators) for more information.
Given a sample.yml file of:
```yaml
a: 2021-01-01T00:00:00Z
```
then
```bash
yq '.a += "3h10m"' sample.yml
```
will output
```yaml
a: 2021-01-01T03:10:00Z
```
## Date addition - custom format
You can add durations to dates. See [date-time operators](https://mikefarah.gitbook.io/yq/operators/date-time-operators) for more information.
Given a sample.yml file of:
```yaml
a: Saturday, 15-Dec-01 at 2:59AM GMT
```
then
```bash
yq 'with_dtf("Monday, 02-Jan-06 at 3:04PM MST", .a += "3h1m")' sample.yml
```
will output
```yaml
a: Saturday, 15-Dec-01 at 6:00AM GMT
```
## Add to null
Adding to null simply returns the rhs
Running
```bash
yq --null-input 'null + "cat"'
```
will output
```yaml
cat
```
## Add maps to shallow merge
Adding objects together shallow merges them. Use `*` to deeply merge.
Given a sample.yml file of:
```yaml
a:
thing:
name: Astuff
value: x
a1: cool
b:
thing:
name: Bstuff
legs: 3
b1: neat
```
then
```bash
yq '.a += .b' sample.yml
```
will output
```yaml
a:
thing:
name: Bstuff
legs: 3
a1: cool
b1: neat
b:
thing:
name: Bstuff
legs: 3
b1: neat
```
## Custom types: that are really strings
When custom tags are encountered, yq will try to decode the underlying type.
Given a sample.yml file of:
```yaml
a: !horse cat
b: !goat _meow
```
then
```bash
yq '.a += .b' sample.yml
```
will output
```yaml
a: !horse cat_meow
b: !goat _meow
```
## Custom types: that are really numbers
When custom tags are encountered, yq will try to decode the underlying type.
Given a sample.yml file of:
```yaml
a: !horse 1.2
b: !goat 2.3
```
then
```bash
yq '.a += .b' sample.yml
```
will output
```yaml
a: !horse 3.5
b: !goat 2.3
```

View File

@@ -0,0 +1,108 @@
# Alternative (Default value)
This operator is used to provide alternative (or default) values when a particular expression is either null or false.
## LHS is defined
Given a sample.yml file of:
```yaml
a: bridge
```
then
```bash
yq '.a // "hello"' sample.yml
```
will output
```yaml
bridge
```
## LHS is not defined
Given a sample.yml file of:
```yaml
{}
```
then
```bash
yq '.a // "hello"' sample.yml
```
will output
```yaml
hello
```
## LHS is null
Given a sample.yml file of:
```yaml
a: ~
```
then
```bash
yq '.a // "hello"' sample.yml
```
will output
```yaml
hello
```
## LHS is false
Given a sample.yml file of:
```yaml
a: false
```
then
```bash
yq '.a // "hello"' sample.yml
```
will output
```yaml
hello
```
## RHS is an expression
Given a sample.yml file of:
```yaml
a: false
b: cat
```
then
```bash
yq '.a // .b' sample.yml
```
will output
```yaml
cat
```
## Update or create - entity exists
This initialises `a` if it's not present
Given a sample.yml file of:
```yaml
a: 1
```
then
```bash
yq '(.a // (.a = 0)) += 1' sample.yml
```
will output
```yaml
a: 2
```
## Update or create - entity does not exist
This initialises `a` if it's not present
Given a sample.yml file of:
```yaml
b: camel
```
then
```bash
yq '(.a // (.a = 0)) += 1' sample.yml
```
will output
```yaml
b: camel
a: 1
```

View File

@@ -0,0 +1,333 @@
# Anchor and Alias Operators
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
## Merge one map
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<: *CENTER
r: 10
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
x: 1
y: 2
r: 10
```
## Merge multiple maps
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *CENTER
- *BIG
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## Override
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *BIG
- *LEFT
- *SMALL
x: 1
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## Get anchor
Given a sample.yml file of:
```yaml
a: &billyBob cat
```
then
```bash
yq '.a | anchor' sample.yml
```
will output
```yaml
billyBob
```
## Set anchor
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '.a anchor = "foobar"' sample.yml
```
will output
```yaml
a: &foobar cat
```
## Set anchor relatively using assign-update
Given a sample.yml file of:
```yaml
a:
b: cat
```
then
```bash
yq '.a anchor |= .b' sample.yml
```
will output
```yaml
a: &cat
b: cat
```
## Get alias
Given a sample.yml file of:
```yaml
b: &billyBob meow
a: *billyBob
```
then
```bash
yq '.a | alias' sample.yml
```
will output
```yaml
billyBob
```
## Set alias
Given a sample.yml file of:
```yaml
b: &meow purr
a: cat
```
then
```bash
yq '.a alias = "meow"' sample.yml
```
will output
```yaml
b: &meow purr
a: *meow
```
## Set alias to blank does nothing
Given a sample.yml file of:
```yaml
b: &meow purr
a: cat
```
then
```bash
yq '.a alias = ""' sample.yml
```
will output
```yaml
b: &meow purr
a: cat
```
## Set alias relatively using assign-update
Given a sample.yml file of:
```yaml
b: &meow purr
a:
f: meow
```
then
```bash
yq '.a alias |= .f' sample.yml
```
will output
```yaml
b: &meow purr
a: *meow
```
## Explode alias and anchor
Given a sample.yml file of:
```yaml
f:
a: &a cat
b: *a
```
then
```bash
yq 'explode(.f)' sample.yml
```
will output
```yaml
f:
a: cat
b: cat
```
## Explode with no aliases or anchors
Given a sample.yml file of:
```yaml
a: mike
```
then
```bash
yq 'explode(.a)' sample.yml
```
will output
```yaml
a: mike
```
## Explode with alias keys
Given a sample.yml file of:
```yaml
f:
a: &a cat
*a: b
```
then
```bash
yq 'explode(.f)' sample.yml
```
will output
```yaml
f:
a: cat
cat: b
```
## Explode with merge anchors
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq 'explode(.)' sample.yml
```
will output
```yaml
foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: bar_b
thing: foo_thing
c: foobarList_c
a: foo_a
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
```
## Dereference and update a field
`Use explode with multiply to dereference an object
Given a sample.yml file of:
```yaml
item_value: &item_value
value: true
thingOne:
name: item_1
!!merge <<: *item_value
thingTwo:
name: item_2
!!merge <<: *item_value
```
then
```bash
yq '.thingOne |= explode(.) * {"value": false}' sample.yml
```
will output
```yaml
item_value: &item_value
value: true
thingOne:
name: item_1
value: false
thingTwo:
name: item_2
!!merge <<: *item_value
```

View File

@@ -0,0 +1,28 @@
# Array to Map
Use this operator to convert an array to..a map. The indices are used as map keys, null values in the array are skipped over.
Behind the scenes, this is implemented using reduce:
```
(.[] | select(. != null) ) as $i ireduce({}; .[$i | key] = $i)
```
## Simple example
Given a sample.yml file of:
```yaml
cool:
- null
- null
- hello
```
then
```bash
yq '.cool |= array_to_map' sample.yml
```
will output
```yaml
cool:
2: hello
```

View File

@@ -0,0 +1,270 @@
# Assign (Update)
This operator is used to update node values. It can be used in either the:
### plain form: `=`
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
### relative form: `|=`
This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment.
### Flags
- `c` clobber custom tags
## Create yaml file
Running
```bash
yq --null-input '.a.b = "cat" | .x = "frog"'
```
will output
```yaml
a:
b: cat
x: frog
```
## Update node to be the child value
Given a sample.yml file of:
```yaml
a:
b:
g: foof
```
then
```bash
yq '.a |= .b' sample.yml
```
will output
```yaml
a:
g: foof
```
## Double elements in an array
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq '.[] |= . * 2' sample.yml
```
will output
```yaml
- 2
- 4
- 6
```
## Update node from another file
Note this will also work when the second file is a scalar (string/number)
Given a sample.yml file of:
```yaml
a: apples
```
And another sample another.yml file of:
```yaml
b: bob
```
then
```bash
yq eval-all 'select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)' sample.yml another.yml
```
will output
```yaml
a:
b: bob
```
## Update node to be the sibling value
Given a sample.yml file of:
```yaml
a:
b: child
b: sibling
```
then
```bash
yq '.a = .b' sample.yml
```
will output
```yaml
a: sibling
b: sibling
```
## Updated multiple paths
Given a sample.yml file of:
```yaml
a: fieldA
b: fieldB
c: fieldC
```
then
```bash
yq '(.a, .c) = "potato"' sample.yml
```
will output
```yaml
a: potato
b: fieldB
c: potato
```
## Update string value
Given a sample.yml file of:
```yaml
a:
b: apple
```
then
```bash
yq '.a.b = "frog"' sample.yml
```
will output
```yaml
a:
b: frog
```
## Update string value via |=
Note there is no difference between `=` and `|=` when the RHS is a scalar
Given a sample.yml file of:
```yaml
a:
b: apple
```
then
```bash
yq '.a.b |= "frog"' sample.yml
```
will output
```yaml
a:
b: frog
```
## Update deeply selected results
Note that the LHS is wrapped in brackets! This is to ensure we don't first filter out the yaml and then update the snippet.
Given a sample.yml file of:
```yaml
a:
b: apple
c: cactus
```
then
```bash
yq '(.a[] | select(. == "apple")) = "frog"' sample.yml
```
will output
```yaml
a:
b: frog
c: cactus
```
## Update array values
Given a sample.yml file of:
```yaml
- candy
- apple
- sandy
```
then
```bash
yq '(.[] | select(. == "*andy")) = "bogs"' sample.yml
```
will output
```yaml
- bogs
- apple
- bogs
```
## Update empty object
Given a sample.yml file of:
```yaml
{}
```
then
```bash
yq '.a.b |= "bogs"' sample.yml
```
will output
```yaml
a:
b: bogs
```
## Update node value that has an anchor
Anchor will remaple
Given a sample.yml file of:
```yaml
a: &cool cat
```
then
```bash
yq '.a = "dog"' sample.yml
```
will output
```yaml
a: &cool dog
```
## Update empty object and array
Given a sample.yml file of:
```yaml
{}
```
then
```bash
yq '.a.b.[0] |= "bogs"' sample.yml
```
will output
```yaml
a:
b:
- bogs
```
## Custom types are maintained by default
Given a sample.yml file of:
```yaml
a: !cat meow
b: !dog woof
```
then
```bash
yq '.a = .b' sample.yml
```
will output
```yaml
a: !cat woof
b: !dog woof
```
## Custom types: clovver
Use the `c` option to clobber custom tags
Given a sample.yml file of:
```yaml
a: !cat meow
b: !dog woof
```
then
```bash
yq '.a =c .b' sample.yml
```
will output
```yaml
a: !dog woof
b: !dog woof
```

View File

@@ -0,0 +1,228 @@
# Boolean Operators
The `or` and `and` operators take two parameters and return a boolean result.
`not` flips a boolean from true to false, or vice versa.
`any` will return `true` if there are any `true` values in a array sequence, and `all` will return true if _all_ elements in an array are true.
`any_c(condition)` and `all_c(condition)` are like `any` and `all` but they take a condition expression that is used against each element to determine if it's `true`. Note: in `jq` you can simply pass a condition to `any` or `all` and it simply works - `yq` isn't that clever..yet
These are most commonly used with the `select` operator to filter particular nodes.
## Related Operators
- equals / not equals (`==`, `!=`) operators [here](https://mikefarah.gitbook.io/yq/operators/equals)
- comparison (`>=`, `<` etc) operators [here](https://mikefarah.gitbook.io/yq/operators/compare)
- select operator [here](https://mikefarah.gitbook.io/yq/operators/select)
## `or` example
Running
```bash
yq --null-input 'true or false'
```
will output
```yaml
true
```
## `and` example
Running
```bash
yq --null-input 'true and false'
```
will output
```yaml
false
```
## Matching nodes with select, equals and or
Given a sample.yml file of:
```yaml
- a: bird
b: dog
- a: frog
b: bird
- a: cat
b: fly
```
then
```bash
yq '[.[] | select(.a == "cat" or .b == "dog")]' sample.yml
```
will output
```yaml
- a: bird
b: dog
- a: cat
b: fly
```
## `any` returns true if any boolean in a given array is true
Given a sample.yml file of:
```yaml
- false
- true
```
then
```bash
yq 'any' sample.yml
```
will output
```yaml
true
```
## `any` returns false for an empty array
Given a sample.yml file of:
```yaml
[]
```
then
```bash
yq 'any' sample.yml
```
will output
```yaml
false
```
## `any_c` returns true if any element in the array is true for the given condition.
Given a sample.yml file of:
```yaml
a:
- rad
- awesome
b:
- meh
- whatever
```
then
```bash
yq '.[] |= any_c(. == "awesome")' sample.yml
```
will output
```yaml
a: true
b: false
```
## `all` returns true if all booleans in a given array are true
Given a sample.yml file of:
```yaml
- true
- true
```
then
```bash
yq 'all' sample.yml
```
will output
```yaml
true
```
## `all` returns true for an empty array
Given a sample.yml file of:
```yaml
[]
```
then
```bash
yq 'all' sample.yml
```
will output
```yaml
true
```
## `all_c` returns true if all elements in the array are true for the given condition.
Given a sample.yml file of:
```yaml
a:
- rad
- awesome
b:
- meh
- 12
```
then
```bash
yq '.[] |= all_c(tag == "!!str")' sample.yml
```
will output
```yaml
a: true
b: false
```
## Not true is false
Running
```bash
yq --null-input 'true | not'
```
will output
```yaml
false
```
## Not false is true
Running
```bash
yq --null-input 'false | not'
```
will output
```yaml
true
```
## String values considered to be true
Running
```bash
yq --null-input '"cat" | not'
```
will output
```yaml
false
```
## Empty string value considered to be true
Running
```bash
yq --null-input '"" | not'
```
will output
```yaml
false
```
## Numbers are considered to be true
Running
```bash
yq --null-input '1 | not'
```
will output
```yaml
false
```
## Zero is considered to be true
Running
```bash
yq --null-input '0 | not'
```
will output
```yaml
false
```
## Null is considered to be false
Running
```bash
yq --null-input '~ | not'
```
will output
```yaml
true
```

View File

@@ -0,0 +1,41 @@
# Collect into Array
This creates an array using the expression between the square brackets.
## Collect empty
Running
```bash
yq --null-input '[]'
```
will output
```yaml
[]
```
## Collect single
Running
```bash
yq --null-input '["cat"]'
```
will output
```yaml
- cat
```
## Collect many
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq '[.a, .b]' sample.yml
```
will output
```yaml
- cat
- dog
```

60
external/yq/pkg/yqlib/doc/operators/column.md vendored Executable file
View File

@@ -0,0 +1,60 @@
# Column
Returns the column of the matching node. Starts from 1, 0 indicates there was no column data.
## Returns column of _value_ node
Given a sample.yml file of:
```yaml
a: cat
b: bob
```
then
```bash
yq '.b | column' sample.yml
```
will output
```yaml
4
```
## Returns column of _key_ node
Pipe through the key operator to get the column of the key
Given a sample.yml file of:
```yaml
a: cat
b: bob
```
then
```bash
yq '.b | key | column' sample.yml
```
will output
```yaml
1
```
## First column is 1
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '.a | key | column' sample.yml
```
will output
```yaml
1
```
## No column data is 0
Running
```bash
yq --null-input '{"a": "new entry"} | column'
```
will output
```yaml
0
```

View File

@@ -0,0 +1,337 @@
# Comment Operators
Use these comment operators to set or retrieve comments. Note that line comments on maps/arrays are actually set on the _key_ node as opposed to the _value_ (map/array). See below for examples.
Like the `=` and `|=` assign operators, the same syntax applies when updating comments:
### plain form: `=`
This will assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
### relative form: `|=`
Similar to the plain form, however the RHS evaluates against each matching LHS node! This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
## Set line comment
Set the comment on the key node for more reliability (see below).
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '.a line_comment="single"' sample.yml
```
will output
```yaml
a: cat # single
```
## Set line comment of a maps/arrays
For maps and arrays, you need to set the line comment on the _key_ node. This will also work for scalars.
Given a sample.yml file of:
```yaml
a:
b: things
```
then
```bash
yq '(.a | key) line_comment="single"' sample.yml
```
will output
```yaml
a: # single
b: things
```
## Use update assign to perform relative updates
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq '.. line_comment |= .' sample.yml
```
will output
```yaml
a: cat # cat
b: dog # dog
```
## Where is the comment - map key example
The underlying yaml parser can assign comments in a document to surprising nodes. Use an expression like this to find where you comment is. 'p' indicates the path, 'isKey' is if the node is a map key (as opposed to a map value).
From this, you can see the 'hello-world-comment' is actually on the 'hello' key
Given a sample.yml file of:
```yaml
hello: # hello-world-comment
message: world
```
then
```bash
yq '[... | {"p": path | join("."), "isKey": is_key, "hc": headComment, "lc": lineComment, "fc": footComment}]' sample.yml
```
will output
```yaml
- p: ""
isKey: false
hc: ""
lc: ""
fc: ""
- p: hello
isKey: true
hc: ""
lc: hello-world-comment
fc: ""
- p: hello
isKey: false
hc: ""
lc: ""
fc: ""
- p: hello.message
isKey: true
hc: ""
lc: ""
fc: ""
- p: hello.message
isKey: false
hc: ""
lc: ""
fc: ""
```
## Retrieve comment - map key example
From the previous example, we know that the comment is on the 'hello' _key_ as a lineComment
Given a sample.yml file of:
```yaml
hello: # hello-world-comment
message: world
```
then
```bash
yq '.hello | key | line_comment' sample.yml
```
will output
```yaml
hello-world-comment
```
## Where is the comment - array example
The underlying yaml parser can assign comments in a document to surprising nodes. Use an expression like this to find where you comment is. 'p' indicates the path, 'isKey' is if the node is a map key (as opposed to a map value).
From this, you can see the 'under-name-comment' is actually on the first child
Given a sample.yml file of:
```yaml
name:
# under-name-comment
- first-array-child
```
then
```bash
yq '[... | {"p": path | join("."), "isKey": is_key, "hc": headComment, "lc": lineComment, "fc": footComment}]' sample.yml
```
will output
```yaml
- p: ""
isKey: false
hc: ""
lc: ""
fc: ""
- p: name
isKey: true
hc: ""
lc: ""
fc: ""
- p: name
isKey: false
hc: ""
lc: ""
fc: ""
- p: name.0
isKey: false
hc: under-name-comment
lc: ""
fc: ""
```
## Retrieve comment - array example
From the previous example, we know that the comment is on the first child as a headComment
Given a sample.yml file of:
```yaml
name:
# under-name-comment
- first-array-child
```
then
```bash
yq '.name[0] | headComment' sample.yml
```
will output
```yaml
under-name-comment
```
## Set head comment
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '. head_comment="single"' sample.yml
```
will output
```yaml
# single
a: cat
```
## Set head comment of a map entry
Given a sample.yml file of:
```yaml
f: foo
a:
b: cat
```
then
```bash
yq '(.a | key) head_comment="single"' sample.yml
```
will output
```yaml
f: foo
# single
a:
b: cat
```
## Set foot comment, using an expression
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '. foot_comment=.a' sample.yml
```
will output
```yaml
a: cat
# cat
```
## Remove comment
Given a sample.yml file of:
```yaml
a: cat # comment
b: dog # leave this
```
then
```bash
yq '.a line_comment=""' sample.yml
```
will output
```yaml
a: cat
b: dog # leave this
```
## Remove (strip) all comments
Note the use of `...` to ensure key nodes are included.
Given a sample.yml file of:
```yaml
# hi
a: cat # comment
# great
b: # key comment
```
then
```bash
yq '... comments=""' sample.yml
```
will output
```yaml
a: cat
b:
```
## Get line comment
Given a sample.yml file of:
```yaml
# welcome!
a: cat # meow
# have a great day
```
then
```bash
yq '.a | line_comment' sample.yml
```
will output
```yaml
meow
```
## Get head comment
Given a sample.yml file of:
```yaml
# welcome!
a: cat # meow
# have a great day
```
then
```bash
yq '. | head_comment' sample.yml
```
will output
```yaml
welcome!
```
## Head comment with document split
Given a sample.yml file of:
```yaml
# welcome!
---
# bob
a: cat # meow
# have a great day
```
then
```bash
yq 'head_comment' sample.yml
```
will output
```yaml
welcome!
bob
```
## Get foot comment
Given a sample.yml file of:
```yaml
# welcome!
a: cat # meow
# have a great day
# no really
```
then
```bash
yq '. | foot_comment' sample.yml
```
will output
```yaml
have a great day
no really
```

100
external/yq/pkg/yqlib/doc/operators/compare.md vendored Executable file
View File

@@ -0,0 +1,100 @@
# Compare Operators
Comparison operators (`>`, `>=`, `<`, `<=`) can be used for comparing scalar values of the same time.
The following types are currently supported:
- numbers
- strings
- datetimes
## Related Operators
- equals / not equals (`==`, `!=`) operators [here](https://mikefarah.gitbook.io/yq/operators/equals)
- boolean operators (`and`, `or`, `any` etc) [here](https://mikefarah.gitbook.io/yq/operators/boolean-operators)
- select operator [here](https://mikefarah.gitbook.io/yq/operators/select)
## Compare numbers (>)
Given a sample.yml file of:
```yaml
a: 5
b: 4
```
then
```bash
yq '.a > .b' sample.yml
```
will output
```yaml
true
```
## Compare equal numbers (>=)
Given a sample.yml file of:
```yaml
a: 5
b: 5
```
then
```bash
yq '.a >= .b' sample.yml
```
will output
```yaml
true
```
## Compare strings
Compares strings by their bytecode.
Given a sample.yml file of:
```yaml
a: zoo
b: apple
```
then
```bash
yq '.a > .b' sample.yml
```
will output
```yaml
true
```
## Compare date times
You can compare date times. Assumes RFC3339 date time format, see [date-time operators](https://mikefarah.gitbook.io/yq/operators/date-time-operators) for more information.
Given a sample.yml file of:
```yaml
a: 2021-01-01T03:10:00Z
b: 2020-01-01T03:10:00Z
```
then
```bash
yq '.a > .b' sample.yml
```
will output
```yaml
true
```
## Both sides are null: > is false
Running
```bash
yq --null-input '.a > .b'
```
will output
```yaml
false
```
## Both sides are null: >= is true
Running
```bash
yq --null-input '.a >= .b'
```
will output
```yaml
true
```

View File

@@ -0,0 +1,114 @@
# Contains
This returns `true` if the context contains the passed in parameter, and false otherwise. For arrays, this will return true if the passed in array is contained within the array. For strings, it will return true if the string is a substring.
{% hint style="warning" %}
_Note_ that, just like jq, when checking if an array of strings `contains` another, this will use `contains` and _not_ equals to check each string. This means an array like `contains(["cat"])` will return true for an array `["cats"]`.
See the "Array has a subset array" example below on how to check for a subset.
{% endhint %}
## Array contains array
Array is equal or subset of
Given a sample.yml file of:
```yaml
- foobar
- foobaz
- blarp
```
then
```bash
yq 'contains(["baz", "bar"])' sample.yml
```
will output
```yaml
true
```
## Array has a subset array
Subtract the superset array from the subset, if there's anything left, it's not a subset
Given a sample.yml file of:
```yaml
- foobar
- foobaz
- blarp
```
then
```bash
yq '["baz", "bar"] - . | length == 0' sample.yml
```
will output
```yaml
false
```
## Object included in array
Given a sample.yml file of:
```yaml
"foo": 12
"bar":
- 1
- 2
- "barp": 12
"blip": 13
```
then
```bash
yq 'contains({"bar": [{"barp": 12}]})' sample.yml
```
will output
```yaml
true
```
## Object not included in array
Given a sample.yml file of:
```yaml
"foo": 12
"bar":
- 1
- 2
- "barp": 12
"blip": 13
```
then
```bash
yq 'contains({"foo": 12, "bar": [{"barp": 15}]})' sample.yml
```
will output
```yaml
false
```
## String contains substring
Given a sample.yml file of:
```yaml
foobar
```
then
```bash
yq 'contains("bar")' sample.yml
```
will output
```yaml
true
```
## String equals string
Given a sample.yml file of:
```yaml
meow
```
then
```bash
yq 'contains("meow")' sample.yml
```
will output
```yaml
true
```

View File

@@ -0,0 +1,95 @@
# Create, Collect into Object
This is used to construct objects (or maps). This can be used against existing yaml, or to create fresh yaml documents.
## Collect empty object
Running
```bash
yq --null-input '{}'
```
will output
```yaml
{}
```
## Wrap (prefix) existing object
Given a sample.yml file of:
```yaml
name: Mike
```
then
```bash
yq '{"wrap": .}' sample.yml
```
will output
```yaml
wrap:
name: Mike
```
## Using splat to create multiple objects
Given a sample.yml file of:
```yaml
name: Mike
pets:
- cat
- dog
```
then
```bash
yq '{.name: .pets.[]}' sample.yml
```
will output
```yaml
Mike: cat
Mike: dog
```
## Working with multiple documents
Given a sample.yml file of:
```yaml
name: Mike
pets:
- cat
- dog
---
name: Rosey
pets:
- monkey
- sheep
```
then
```bash
yq '{.name: .pets.[]}' sample.yml
```
will output
```yaml
Mike: cat
Mike: dog
Rosey: monkey
Rosey: sheep
```
## Creating yaml from scratch
Running
```bash
yq --null-input '{"wrap": "frog"}'
```
will output
```yaml
wrap: frog
```
## Creating yaml from scratch with multiple objects
Running
```bash
yq --null-input '(.a.b = "foo") | (.d.e = "bar")'
```
will output
```yaml
a:
b: foo
d:
e: bar
```

View File

@@ -0,0 +1,197 @@
# Date Time
Various operators for parsing and manipulating dates.
## Date time formattings
This uses the golangs built in time library for parsing and formatting date times.
When not specified, the RFC3339 standard is assumed `2006-01-02T15:04:05Z07:00` for parsing.
To specify a custom parsing format, use the `with_dtf` operator. The first parameter sets the datetime parsing format for the expression in the second parameter. The expression can be any valid `yq` expression tree.
```bash
yq 'with_dtf("myformat"; .a + "3h" | tz("Australia/Melbourne"))'
```
See the [library docs](https://pkg.go.dev/time#pkg-constants) for examples of formatting options.
## Timezones
This uses golangs built in LoadLocation function to parse timezones strings. See the [library docs](https://pkg.go.dev/time#LoadLocation) for more details.
## Durations
Durations are parsed using golangs built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
You can durations to time using the `+` operator.
## Format: from standard RFC3339 format
Providing a single parameter assumes a standard RFC3339 datetime format. If the target format is not a valid yaml datetime format, the result will be a string tagged node.
Given a sample.yml file of:
```yaml
a: 2001-12-15T02:59:43.1Z
```
then
```bash
yq '.a |= format_datetime("Monday, 02-Jan-06 at 3:04PM")' sample.yml
```
will output
```yaml
a: Saturday, 15-Dec-01 at 2:59AM
```
## Format: from custom date time
Use with_dtf to set a custom datetime format for parsing.
Given a sample.yml file of:
```yaml
a: Saturday, 15-Dec-01 at 2:59AM
```
then
```bash
yq '.a |= with_dtf("Monday, 02-Jan-06 at 3:04PM"; format_datetime("2006-01-02"))' sample.yml
```
will output
```yaml
a: 2001-12-15
```
## Format: get the day of the week
Given a sample.yml file of:
```yaml
a: 2001-12-15
```
then
```bash
yq '.a | format_datetime("Monday")' sample.yml
```
will output
```yaml
Saturday
```
## Now
Given a sample.yml file of:
```yaml
a: cool
```
then
```bash
yq '.updated = now' sample.yml
```
will output
```yaml
a: cool
updated: 2021-05-19T01:02:03Z
```
## Timezone: from standard RFC3339 format
Returns a new datetime in the specified timezone. Specify standard IANA Time Zone format or 'utc', 'local'. When given a single parameter, this assumes the datetime is in RFC3339 format.
Given a sample.yml file of:
```yaml
a: cool
```
then
```bash
yq '.updated = (now | tz("Australia/Sydney"))' sample.yml
```
will output
```yaml
a: cool
updated: 2021-05-19T11:02:03+10:00
```
## Timezone: with custom format
Specify standard IANA Time Zone format or 'utc', 'local'
Given a sample.yml file of:
```yaml
a: Saturday, 15-Dec-01 at 2:59AM GMT
```
then
```bash
yq '.a |= with_dtf("Monday, 02-Jan-06 at 3:04PM MST"; tz("Australia/Sydney"))' sample.yml
```
will output
```yaml
a: Saturday, 15-Dec-01 at 1:59PM AEDT
```
## Add and tz custom format
Specify standard IANA Time Zone format or 'utc', 'local'
Given a sample.yml file of:
```yaml
a: Saturday, 15-Dec-01 at 2:59AM GMT
```
then
```bash
yq '.a |= with_dtf("Monday, 02-Jan-06 at 3:04PM MST"; tz("Australia/Sydney"))' sample.yml
```
will output
```yaml
a: Saturday, 15-Dec-01 at 1:59PM AEDT
```
## Date addition
Given a sample.yml file of:
```yaml
a: 2021-01-01T00:00:00Z
```
then
```bash
yq '.a += "3h10m"' sample.yml
```
will output
```yaml
a: 2021-01-01T03:10:00Z
```
## Date subtraction
You can subtract durations from dates. Assumes RFC3339 date time format, see [date-time operators](https://mikefarah.gitbook.io/yq/operators/datetime#date-time-formattings) for more information.
Given a sample.yml file of:
```yaml
a: 2021-01-01T03:10:00Z
```
then
```bash
yq '.a -= "3h10m"' sample.yml
```
will output
```yaml
a: 2021-01-01T00:00:00Z
```
## Date addition - custom format
Given a sample.yml file of:
```yaml
a: Saturday, 15-Dec-01 at 2:59AM GMT
```
then
```bash
yq 'with_dtf("Monday, 02-Jan-06 at 3:04PM MST"; .a += "3h1m")' sample.yml
```
will output
```yaml
a: Saturday, 15-Dec-01 at 6:00AM GMT
```
## Date script with custom format
You can embed full expressions in with_dtf if needed.
Given a sample.yml file of:
```yaml
a: Saturday, 15-Dec-01 at 2:59AM GMT
```
then
```bash
yq 'with_dtf("Monday, 02-Jan-06 at 3:04PM MST"; .a = (.a + "3h1m" | tz("Australia/Perth")))' sample.yml
```
will output
```yaml
a: Saturday, 15-Dec-01 at 2:00PM AWST
```

120
external/yq/pkg/yqlib/doc/operators/delete.md vendored Executable file
View File

@@ -0,0 +1,120 @@
# Delete
Deletes matching entries in maps or arrays.
## Delete entry in map
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq 'del(.b)' sample.yml
```
will output
```yaml
a: cat
```
## Delete nested entry in map
Given a sample.yml file of:
```yaml
a:
a1: fred
a2: frood
```
then
```bash
yq 'del(.a.a1)' sample.yml
```
will output
```yaml
a:
a2: frood
```
## Delete entry in array
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq 'del(.[1])' sample.yml
```
will output
```yaml
- 1
- 3
```
## Delete nested entry in array
Given a sample.yml file of:
```yaml
- a: cat
b: dog
```
then
```bash
yq 'del(.[0].a)' sample.yml
```
will output
```yaml
- b: dog
```
## Delete no matches
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq 'del(.c)' sample.yml
```
will output
```yaml
a: cat
b: dog
```
## Delete matching entries
Given a sample.yml file of:
```yaml
a: cat
b: dog
c: bat
```
then
```bash
yq 'del( .[] | select(. == "*at") )' sample.yml
```
will output
```yaml
b: dog
```
## Recursively delete matching keys
Given a sample.yml file of:
```yaml
a:
name: frog
b:
name: blog
age: 12
```
then
```bash
yq 'del(.. | select(has("name")).name)' sample.yml
```
will output
```yaml
a:
b:
age: 12
```

View File

@@ -0,0 +1,91 @@
# Document Index
Use the `documentIndex` operator (or the `di` shorthand) to select nodes of a particular document.
## Retrieve a document index
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq '.a | document_index' sample.yml
```
will output
```yaml
0
---
1
```
## Retrieve a document index, shorthand
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq '.a | di' sample.yml
```
will output
```yaml
0
---
1
```
## Filter by document index
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq 'select(document_index == 1)' sample.yml
```
will output
```yaml
a: frog
```
## Filter by document index shorthand
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq 'select(di == 1)' sample.yml
```
will output
```yaml
a: frog
```
## Print Document Index with matches
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq '.a | ({"match": ., "doc": document_index})' sample.yml
```
will output
```yaml
match: cat
doc: 0
match: frog
doc: 1
```

View File

@@ -0,0 +1,470 @@
# Encoder / Decoder
Encode operators will take the piped in object structure and encode it as a string in the desired format. The decode operators do the opposite, they take a formatted string and decode it into the relevant object structure.
Note that you can optionally pass an indent value to the encode functions (see below).
These operators are useful to process yaml documents that have stringified embedded yaml/json/props in them.
| Format | Decode (from string) | Encode (to string) |
| --- | -- | --|
| Yaml | from_yaml/@yamld | to_yaml(i)/@yaml |
| JSON | from_json/@jsond | to_json(i)/@json |
| Properties | from_props/@propsd | to_props/@props |
| CSV | from_csv/@csvd | to_csv/@csv |
| TSV | from_tsv/@tsvd | to_tsv/@tsv |
| XML | from_xml/@xmld | to_xml(i)/@xml |
| Base64 | @base64d | @base64 |
See CSV and TSV [documentation](https://mikefarah.gitbook.io/yq/usage/csv-tsv) for accepted formats.
XML uses the `--xml-attribute-prefix` and `xml-content-name` flags to identify attributes and content fields.
Base64 assumes [rfc4648](https://rfc-editor.org/rfc/rfc4648.html) encoding. Encoding and decoding both assume that the content is a string.
## Encode value as json string
Given a sample.yml file of:
```yaml
a:
cool: thing
```
then
```bash
yq '.b = (.a | to_json)' sample.yml
```
will output
```yaml
a:
cool: thing
b: |
{
"cool": "thing"
}
```
## Encode value as json string, on one line
Pass in a 0 indent to print json on a single line.
Given a sample.yml file of:
```yaml
a:
cool: thing
```
then
```bash
yq '.b = (.a | to_json(0))' sample.yml
```
will output
```yaml
a:
cool: thing
b: '{"cool":"thing"}'
```
## Encode value as json string, on one line shorthand
Pass in a 0 indent to print json on a single line.
Given a sample.yml file of:
```yaml
a:
cool: thing
```
then
```bash
yq '.b = (.a | @json)' sample.yml
```
will output
```yaml
a:
cool: thing
b: '{"cool":"thing"}'
```
## Decode a json encoded string
Keep in mind JSON is a subset of YAML. If you want idiomatic yaml, pipe through the style operator to clear out the JSON styling.
Given a sample.yml file of:
```yaml
a: '{"cool":"thing"}'
```
then
```bash
yq '.a | from_json | ... style=""' sample.yml
```
will output
```yaml
cool: thing
```
## Encode value as props string
Given a sample.yml file of:
```yaml
a:
cool: thing
```
then
```bash
yq '.b = (.a | @props)' sample.yml
```
will output
```yaml
a:
cool: thing
b: |
cool = thing
```
## Decode props encoded string
Given a sample.yml file of:
```yaml
a: |-
cats=great
dogs=cool as well
```
then
```bash
yq '.a |= @propsd' sample.yml
```
will output
```yaml
a:
cats: great
dogs: cool as well
```
## Decode csv encoded string
Given a sample.yml file of:
```yaml
a: |-
cats,dogs
great,cool as well
```
then
```bash
yq '.a |= @csvd' sample.yml
```
will output
```yaml
a:
- cats: great
dogs: cool as well
```
## Decode tsv encoded string
Given a sample.yml file of:
```yaml
a: |-
cats dogs
great cool as well
```
then
```bash
yq '.a |= @tsvd' sample.yml
```
will output
```yaml
a:
- cats: great
dogs: cool as well
```
## Encode value as yaml string
Indent defaults to 2
Given a sample.yml file of:
```yaml
a:
cool:
bob: dylan
```
then
```bash
yq '.b = (.a | to_yaml)' sample.yml
```
will output
```yaml
a:
cool:
bob: dylan
b: |
cool:
bob: dylan
```
## Encode value as yaml string, with custom indentation
You can specify the indentation level as the first parameter.
Given a sample.yml file of:
```yaml
a:
cool:
bob: dylan
```
then
```bash
yq '.b = (.a | to_yaml(8))' sample.yml
```
will output
```yaml
a:
cool:
bob: dylan
b: |
cool:
bob: dylan
```
## Decode a yaml encoded string
Given a sample.yml file of:
```yaml
a: 'foo: bar'
```
then
```bash
yq '.b = (.a | from_yaml)' sample.yml
```
will output
```yaml
a: 'foo: bar'
b:
foo: bar
```
## Update a multiline encoded yaml string
Given a sample.yml file of:
```yaml
a: |
foo: bar
baz: dog
```
then
```bash
yq '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml
```
will output
```yaml
a: |
foo: cat
baz: dog
```
## Update a single line encoded yaml string
Given a sample.yml file of:
```yaml
a: 'foo: bar'
```
then
```bash
yq '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml
```
will output
```yaml
a: 'foo: cat'
```
## Encode array of scalars as csv string
Scalars are strings, numbers and booleans.
Given a sample.yml file of:
```yaml
- cat
- thing1,thing2
- true
- 3.40
```
then
```bash
yq '@csv' sample.yml
```
will output
```yaml
cat,"thing1,thing2",true,3.40
```
## Encode array of arrays as csv string
Given a sample.yml file of:
```yaml
- - cat
- thing1,thing2
- true
- 3.40
- - dog
- thing3
- false
- 12
```
then
```bash
yq '@csv' sample.yml
```
will output
```yaml
cat,"thing1,thing2",true,3.40
dog,thing3,false,12
```
## Encode array of array scalars as tsv string
Scalars are strings, numbers and booleans.
Given a sample.yml file of:
```yaml
- - cat
- thing1,thing2
- true
- 3.40
- - dog
- thing3
- false
- 12
```
then
```bash
yq '@tsv' sample.yml
```
will output
```yaml
cat thing1,thing2 true 3.40
dog thing3 false 12
```
## Encode value as xml string
Given a sample.yml file of:
```yaml
a:
cool:
foo: bar
+@id: hi
```
then
```bash
yq '.a | to_xml' sample.yml
```
will output
```yaml
<cool id="hi">
<foo>bar</foo>
</cool>
```
## Encode value as xml string on a single line
Given a sample.yml file of:
```yaml
a:
cool:
foo: bar
+@id: hi
```
then
```bash
yq '.a | @xml' sample.yml
```
will output
```yaml
<cool id="hi"><foo>bar</foo></cool>
```
## Encode value as xml string with custom indentation
Given a sample.yml file of:
```yaml
a:
cool:
foo: bar
+@id: hi
```
then
```bash
yq '{"cat": .a | to_xml(1)}' sample.yml
```
will output
```yaml
cat: |
<cool id="hi">
<foo>bar</foo>
</cool>
```
## Decode a xml encoded string
Given a sample.yml file of:
```yaml
a: <foo>bar</foo>
```
then
```bash
yq '.b = (.a | from_xml)' sample.yml
```
will output
```yaml
a: <foo>bar</foo>
b:
foo: bar
```
## Encode a string to base64
Given a sample.yml file of:
```yaml
coolData: a special string
```
then
```bash
yq '.coolData | @base64' sample.yml
```
will output
```yaml
YSBzcGVjaWFsIHN0cmluZw==
```
## Encode a yaml document to base64
Pipe through @yaml first to convert to a string, then use @base64 to encode it.
Given a sample.yml file of:
```yaml
a: apple
```
then
```bash
yq '@yaml | @base64' sample.yml
```
will output
```yaml
YTogYXBwbGUK
```
## Decode a base64 encoded string
Decoded data is assumed to be a string.
Given a sample.yml file of:
```yaml
coolData: V29ya3Mgd2l0aCBVVEYtMTYg8J+Yig==
```
then
```bash
yq '.coolData | @base64d' sample.yml
```
will output
```yaml
Works with UTF-16 😊
```
## Decode a base64 encoded yaml document
Pipe through `from_yaml` to parse the decoded base64 string as a yaml document.
Given a sample.yml file of:
```yaml
coolData: YTogYXBwbGUK
```
then
```bash
yq '.coolData |= (@base64d | from_yaml)' sample.yml
```
will output
```yaml
coolData:
a: apple
```

141
external/yq/pkg/yqlib/doc/operators/entries.md vendored Executable file
View File

@@ -0,0 +1,141 @@
# Entries
Similar to the same named functions in `jq` these functions convert to/from an object and an array of key-value pairs. This is most useful for performing operations on keys of maps.
## to_entries Map
Given a sample.yml file of:
```yaml
a: 1
b: 2
```
then
```bash
yq 'to_entries' sample.yml
```
will output
```yaml
- key: a
value: 1
- key: b
value: 2
```
## to_entries Array
Given a sample.yml file of:
```yaml
- a
- b
```
then
```bash
yq 'to_entries' sample.yml
```
will output
```yaml
- key: 0
value: a
- key: 1
value: b
```
## to_entries null
Given a sample.yml file of:
```yaml
null
```
then
```bash
yq 'to_entries' sample.yml
```
will output
```yaml
```
## from_entries map
Given a sample.yml file of:
```yaml
a: 1
b: 2
```
then
```bash
yq 'to_entries | from_entries' sample.yml
```
will output
```yaml
a: 1
b: 2
```
## from_entries with numeric key indexes
from_entries always creates a map, even for numeric keys
Given a sample.yml file of:
```yaml
- a
- b
```
then
```bash
yq 'to_entries | from_entries' sample.yml
```
will output
```yaml
0: a
1: b
```
## Use with_entries to update keys
Given a sample.yml file of:
```yaml
a: 1
b: 2
```
then
```bash
yq 'with_entries(.key |= "KEY_" + .)' sample.yml
```
will output
```yaml
KEY_a: 1
KEY_b: 2
```
## Custom sort map keys
Use to_entries to convert to an array of key/value pairs, sort the array using sort/sort_by/etc, and convert it back.
Given a sample.yml file of:
```yaml
a: 1
c: 3
b: 2
```
then
```bash
yq 'to_entries | sort_by(.key) | reverse | from_entries' sample.yml
```
will output
```yaml
c: 3
b: 2
a: 1
```
## Use with_entries to filter the map
Given a sample.yml file of:
```yaml
a:
b: bird
c:
d: dog
```
then
```bash
yq 'with_entries(select(.value | has("b")))' sample.yml
```
will output
```yaml
a:
b: bird
```

View File

@@ -0,0 +1,256 @@
# Env Variable Operators
These operators are used to handle environment variables usage in expressions and documents. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read.
There are three operators:
- `env` which takes a single environment variable name and parse the variable as a yaml node (be it a map, array, string, number of boolean)
- `strenv` which also takes a single environment variable name, and always parses the variable as a string.
- `envsubst` which you pipe strings into and it interpolates environment variables in strings using [envsubst](https://github.com/a8m/envsubst).
## EnvSubst Options
You can optionally pass envsubst any of the following options:
- nu: NoUnset, this will fail if there are any referenced variables that are not set
- ne: NoEmpty, this will fail if there are any referenced variables that are empty
- ff: FailFast, this will abort on the first failure (rather than collect all the errors)
E.g:
`envsubst(ne, ff)` will fail on the first empty variable.
See [Imposing Restrictions](https://github.com/a8m/envsubst#imposing-restrictions) in the `envsubst` documentation for more information, and below for examples.
## Tip
To replace environment variables across all values in a document, `envsubst` can be used with the recursive descent operator
as follows:
```bash
yq '(.. | select(tag == "!!str")) |= envsubst' file.yaml
```
## Read string environment variable
Running
```bash
myenv="cat meow" yq --null-input '.a = env(myenv)'
```
will output
```yaml
a: cat meow
```
## Read boolean environment variable
Running
```bash
myenv="true" yq --null-input '.a = env(myenv)'
```
will output
```yaml
a: true
```
## Read numeric environment variable
Running
```bash
myenv="12" yq --null-input '.a = env(myenv)'
```
will output
```yaml
a: 12
```
## Read yaml environment variable
Running
```bash
myenv="{b: fish}" yq --null-input '.a = env(myenv)'
```
will output
```yaml
a: {b: fish}
```
## Read boolean environment variable as a string
Running
```bash
myenv="true" yq --null-input '.a = strenv(myenv)'
```
will output
```yaml
a: "true"
```
## Read numeric environment variable as a string
Running
```bash
myenv="12" yq --null-input '.a = strenv(myenv)'
```
will output
```yaml
a: "12"
```
## Dynamically update a path from an environment variable
The env variable can be any valid yq expression.
Given a sample.yml file of:
```yaml
a:
b:
- name: dog
- name: cat
```
then
```bash
pathEnv=".a.b[0].name" valueEnv="moo" yq 'eval(strenv(pathEnv)) = strenv(valueEnv)' sample.yml
```
will output
```yaml
a:
b:
- name: moo
- name: cat
```
## Dynamic key lookup with environment variable
Given a sample.yml file of:
```yaml
cat: meow
dog: woof
```
then
```bash
myenv="cat" yq '.[env(myenv)]' sample.yml
```
will output
```yaml
meow
```
## Replace strings with envsubst
Running
```bash
myenv="cat" yq --null-input '"the ${myenv} meows" | envsubst'
```
will output
```yaml
the cat meows
```
## Replace strings with envsubst, missing variables
Running
```bash
yq --null-input '"the ${myenvnonexisting} meows" | envsubst'
```
will output
```yaml
the meows
```
## Replace strings with envsubst(nu), missing variables
(nu) not unset, will fail if there are unset (missing) variables
Running
```bash
yq --null-input '"the ${myenvnonexisting} meows" | envsubst(nu)'
```
will output
```bash
Error: variable ${myenvnonexisting} not set
```
## Replace strings with envsubst(ne), missing variables
(ne) not empty, only validates set variables
Running
```bash
yq --null-input '"the ${myenvnonexisting} meows" | envsubst(ne)'
```
will output
```yaml
the meows
```
## Replace strings with envsubst(ne), empty variable
(ne) not empty, will fail if a references variable is empty
Running
```bash
myenv="" yq --null-input '"the ${myenv} meows" | envsubst(ne)'
```
will output
```bash
Error: variable ${myenv} set but empty
```
## Replace strings with envsubst, missing variables with defaults
Running
```bash
yq --null-input '"the ${myenvnonexisting-dog} meows" | envsubst'
```
will output
```yaml
the dog meows
```
## Replace strings with envsubst(nu), missing variables with defaults
Having a default specified skips over the missing variable.
Running
```bash
yq --null-input '"the ${myenvnonexisting-dog} meows" | envsubst(nu)'
```
will output
```yaml
the dog meows
```
## Replace strings with envsubst(ne), missing variables with defaults
Fails, because the variable is explicitly set to blank.
Running
```bash
myEmptyEnv="" yq --null-input '"the ${myEmptyEnv-dog} meows" | envsubst(ne)'
```
will output
```bash
Error: variable ${myEmptyEnv} set but empty
```
## Replace string environment variable in document
Given a sample.yml file of:
```yaml
v: ${myenv}
```
then
```bash
myenv="cat meow" yq '.v |= envsubst' sample.yml
```
will output
```yaml
v: cat meow
```
## (Default) Return all envsubst errors
By default, all errors are returned at once.
Running
```bash
yq --null-input '"the ${notThere} ${alsoNotThere}" | envsubst(nu)'
```
will output
```bash
Error: variable ${notThere} not set
variable ${alsoNotThere} not set
```
## Fail fast, return the first envsubst error (and abort)
Running
```bash
yq --null-input '"the ${notThere} ${alsoNotThere}" | envsubst(nu,ff)'
```
will output
```bash
Error: variable ${notThere} not set
```

133
external/yq/pkg/yqlib/doc/operators/equals.md vendored Executable file
View File

@@ -0,0 +1,133 @@
# Equals / Not Equals
This is a boolean operator that will return `true` if the LHS is equal to the RHS and `false` otherwise.
```
.a == .b
```
It is most often used with the select operator to find particular nodes:
```
select(.a == .b)
```
The not equals `!=` operator returns `false` if the LHS is equal to the RHS.
## Related Operators
- comparison (`>=`, `<` etc) operators [here](https://mikefarah.gitbook.io/yq/operators/compare)
- boolean operators (`and`, `or`, `any` etc) [here](https://mikefarah.gitbook.io/yq/operators/boolean-operators)
- select operator [here](https://mikefarah.gitbook.io/yq/operators/select)
## Match string
Given a sample.yml file of:
```yaml
- cat
- goat
- dog
```
then
```bash
yq '.[] | (. == "*at")' sample.yml
```
will output
```yaml
true
true
false
```
## Don't match string
Given a sample.yml file of:
```yaml
- cat
- goat
- dog
```
then
```bash
yq '.[] | (. != "*at")' sample.yml
```
will output
```yaml
false
false
true
```
## Match number
Given a sample.yml file of:
```yaml
- 3
- 4
- 5
```
then
```bash
yq '.[] | (. == 4)' sample.yml
```
will output
```yaml
false
true
false
```
## Don't match number
Given a sample.yml file of:
```yaml
- 3
- 4
- 5
```
then
```bash
yq '.[] | (. != 4)' sample.yml
```
will output
```yaml
true
false
true
```
## Match nulls
Running
```bash
yq --null-input 'null == ~'
```
will output
```yaml
true
```
## Non existent key doesn't equal a value
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq 'select(.b != "thing")' sample.yml
```
will output
```yaml
a: frog
```
## Two non existent keys are equal
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq 'select(.b == .c)' sample.yml
```
will output
```yaml
a: frog
```

50
external/yq/pkg/yqlib/doc/operators/error.md vendored Executable file
View File

@@ -0,0 +1,50 @@
# Error
Use this operation to short-circuit expressions. Useful for validation.
## Validate a particular value
Given a sample.yml file of:
```yaml
a: hello
```
then
```bash
yq 'select(.a == "howdy") or error(".a [" + .a + "] is not howdy!")' sample.yml
```
will output
```bash
Error: .a [hello] is not howdy!
```
## Validate the environment variable is a number - invalid
Running
```bash
numberOfCats="please" yq --null-input 'env(numberOfCats) | select(tag == "!!int") or error("numberOfCats is not a number :(")'
```
will output
```bash
Error: numberOfCats is not a number :(
```
## Validate the environment variable is a number - valid
`with` can be a convenient way of encapsulating validation.
Given a sample.yml file of:
```yaml
name: Bob
favouriteAnimal: cat
```
then
```bash
numberOfCats="3" yq '
with(env(numberOfCats); select(tag == "!!int") or error("numberOfCats is not a number :(")) |
.numPets = env(numberOfCats)
' sample.yml
```
will output
```yaml
name: Bob
favouriteAnimal: cat
numPets: 3
```

48
external/yq/pkg/yqlib/doc/operators/eval.md vendored Executable file
View File

@@ -0,0 +1,48 @@
# Eval
Use `eval` to dynamically process an expression - for instance from an environment variable.
`eval` takes a single argument, and evaluates that as a `yq` expression. Any valid expression can be used, beit a path `.a.b.c | select(. == "cat")`, or an update `.a.b.c = "gogo"`.
Tip: This can be useful way parameterise complex scripts.
## Dynamically evaluate a path
Given a sample.yml file of:
```yaml
pathExp: .a.b[] | select(.name == "cat")
a:
b:
- name: dog
- name: cat
```
then
```bash
yq 'eval(.pathExp)' sample.yml
```
will output
```yaml
name: cat
```
## Dynamically update a path from an environment variable
The env variable can be any valid yq expression.
Given a sample.yml file of:
```yaml
a:
b:
- name: dog
- name: cat
```
then
```bash
pathEnv=".a.b[0].name" valueEnv="moo" yq 'eval(strenv(pathEnv)) = strenv(valueEnv)' sample.yml
```
will output
```yaml
a:
b:
- name: moo
- name: cat
```

View File

@@ -0,0 +1,74 @@
# File Operators
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
Note that the `fileIndex` operator has a short alias of `fi`.
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
```
## Get filename
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq 'filename' sample.yml
```
will output
```yaml
sample.yml
```
## Get file index
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq 'file_index' sample.yml
```
will output
```yaml
0
```
## Get file indices of multiple documents
Given a sample.yml file of:
```yaml
a: cat
```
And another sample another.yml file of:
```yaml
a: cat
```
then
```bash
yq eval-all 'file_index' sample.yml another.yml
```
will output
```yaml
0
---
1
```
## Get file index alias
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq 'fi' sample.yml
```
will output
```yaml
0
```

View File

@@ -0,0 +1,71 @@
# Flatten
This recursively flattens arrays.
## Flatten
Recursively flattens all arrays
Given a sample.yml file of:
```yaml
- 1
- - 2
- - - 3
```
then
```bash
yq 'flatten' sample.yml
```
will output
```yaml
- 1
- 2
- 3
```
## Flatten with depth of one
Given a sample.yml file of:
```yaml
- 1
- - 2
- - - 3
```
then
```bash
yq 'flatten(1)' sample.yml
```
will output
```yaml
- 1
- 2
- - 3
```
## Flatten empty array
Given a sample.yml file of:
```yaml
- []
```
then
```bash
yq 'flatten' sample.yml
```
will output
```yaml
[]
```
## Flatten array of objects
Given a sample.yml file of:
```yaml
- foo: bar
- - foo: baz
```
then
```bash
yq 'flatten' sample.yml
```
will output
```yaml
- foo: bar
- foo: baz
```

View File

@@ -0,0 +1,56 @@
# Group By
This is used to group items in an array by an expression.
## Group by field
Given a sample.yml file of:
```yaml
- foo: 1
bar: 10
- foo: 3
bar: 100
- foo: 1
bar: 1
```
then
```bash
yq 'group_by(.foo)' sample.yml
```
will output
```yaml
- - foo: 1
bar: 10
- foo: 1
bar: 1
- - foo: 3
bar: 100
```
## Group by field, with nuls
Given a sample.yml file of:
```yaml
- cat: dog
- foo: 1
bar: 10
- foo: 3
bar: 100
- no: foo for you
- foo: 1
bar: 1
```
then
```bash
yq 'group_by(.foo)' sample.yml
```
will output
```yaml
- - cat: dog
- no: foo for you
- - foo: 1
bar: 10
- foo: 1
bar: 1
- - foo: 3
bar: 100
```

70
external/yq/pkg/yqlib/doc/operators/has.md vendored Executable file
View File

@@ -0,0 +1,70 @@
# Has
This is operation that returns true if the key exists in a map (or index in an array), false otherwise.
## Has map key
Given a sample.yml file of:
```yaml
- a: yes
- a: ~
- a:
- b: nope
```
then
```bash
yq '.[] | has("a")' sample.yml
```
will output
```yaml
true
true
true
false
```
## Select, checking for existence of deep paths
Simply pipe in parent expressions into `has`
Given a sample.yml file of:
```yaml
- a:
b:
c: cat
- a:
b:
d: dog
```
then
```bash
yq '.[] | select(.a.b | has("c"))' sample.yml
```
will output
```yaml
a:
b:
c: cat
```
## Has array index
Given a sample.yml file of:
```yaml
- []
- [1]
- [1, 2]
- [1, null]
- [1, 2, 3]
```
then
```bash
yq '.[] | has(1)' sample.yml
```
will output
```yaml
false
false
true
true
true
```

View File

@@ -0,0 +1,63 @@
# NAME
*yq* is a portable command-line YAML processor
# SYNOPSIS
yq [eval/eval-all] [expression] files..
eval/e - (default) Apply the expression to each document in each yaml file in sequence
eval-all/ea - Loads all yaml documents of all yaml files and runs expression once
# DESCRIPTION
a lightweight and portable command-line YAML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
This documentation is also available at https://mikefarah.gitbook.io/yq/
# QUICK GUIDE
## Read a value:
```bash
yq '.a.b[0].c' file.yaml
```
## Pipe from STDIN:
```bash
cat file.yaml | yq '.a.b[0].c'
```
## Update a yaml file, inplace
```bash
yq -i '.a.b[0].c = "cool"' file.yaml
```
## Update using environment variables
```bash
NAME=mike yq -i '.a.b[0].c = strenv(NAME)' file.yaml
```
## Merge multiple files
```
yq ea '. as $item ireduce ({}; . * $item )' path/to/*.yml
```
Note the use of `ea` to evaluate all files at once (instead of in sequence.)
## Multiple updates to a yaml file
```bash
yq -i '
.a.b[0].c = "cool" |
.x.y.z = "foobar" |
.person.name = strenv(NAME)
' file.yaml
```
See the [documentation](https://mikefarah.gitbook.io/yq/) for more.
# KNOWN ISSUES / MISSING FEATURES
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
- Powershell has its own...opinions: https://mikefarah.gitbook.io/yq/usage/tips-and-tricks#quotes-in-windows-powershell
# BUGS / ISSUES / FEATURE REQUESTS
Please visit the GitHub page https://github.com/mikefarah/yq/.

View File

@@ -0,0 +1,10 @@
# Add
Add behaves differently according to the type of the LHS:
* arrays: concatenate
* number scalars: arithmetic addition
* string scalars: concatenate
* maps: shallow merge (use the multiply operator (`*`) to deeply merge)
Use `+=` as a relative append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.

View File

@@ -0,0 +1,3 @@
# Alternative (Default value)
This operator is used to provide alternative (or default) values when a particular expression is either null or false.

View File

@@ -0,0 +1,6 @@
# Anchor and Alias Operators
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.

View File

@@ -0,0 +1,9 @@
# Array to Map
Use this operator to convert an array to..a map. The indices are used as map keys, null values in the array are skipped over.
Behind the scenes, this is implemented using reduce:
```
(.[] | select(. != null) ) as $i ireduce({}; .[$i | key] = $i)
```

View File

@@ -0,0 +1,13 @@
# Assign (Update)
This operator is used to update node values. It can be used in either the:
### plain form: `=`
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
### relative form: `|=`
This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment.
### Flags
- `c` clobber custom tags

View File

@@ -0,0 +1,17 @@
# Boolean Operators
The `or` and `and` operators take two parameters and return a boolean result.
`not` flips a boolean from true to false, or vice versa.
`any` will return `true` if there are any `true` values in a array sequence, and `all` will return true if _all_ elements in an array are true.
`any_c(condition)` and `all_c(condition)` are like `any` and `all` but they take a condition expression that is used against each element to determine if it's `true`. Note: in `jq` you can simply pass a condition to `any` or `all` and it simply works - `yq` isn't that clever..yet
These are most commonly used with the `select` operator to filter particular nodes.
## Related Operators
- equals / not equals (`==`, `!=`) operators [here](https://mikefarah.gitbook.io/yq/operators/equals)
- comparison (`>=`, `<` etc) operators [here](https://mikefarah.gitbook.io/yq/operators/compare)
- select operator [here](https://mikefarah.gitbook.io/yq/operators/select)

View File

@@ -0,0 +1,4 @@
# Collect into Array
This creates an array using the expression between the square brackets.

View File

@@ -0,0 +1,3 @@
# Column
Returns the column of the matching node. Starts from 1, 0 indicates there was no column data.

View File

@@ -0,0 +1,11 @@
# Comment Operators
Use these comment operators to set or retrieve comments. Note that line comments on maps/arrays are actually set on the _key_ node as opposed to the _value_ (map/array). See below for examples.
Like the `=` and `|=` assign operators, the same syntax applies when updating comments:
### plain form: `=`
This will assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
### relative form: `|=`
Similar to the plain form, however the RHS evaluates against each matching LHS node! This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.

View File

@@ -0,0 +1,15 @@
# Compare Operators
Comparison operators (`>`, `>=`, `<`, `<=`) can be used for comparing scalar values of the same time.
The following types are currently supported:
- numbers
- strings
- datetimes
## Related Operators
- equals / not equals (`==`, `!=`) operators [here](https://mikefarah.gitbook.io/yq/operators/equals)
- boolean operators (`and`, `or`, `any` etc) [here](https://mikefarah.gitbook.io/yq/operators/boolean-operators)
- select operator [here](https://mikefarah.gitbook.io/yq/operators/select)

View File

@@ -0,0 +1,11 @@
# Contains
This returns `true` if the context contains the passed in parameter, and false otherwise. For arrays, this will return true if the passed in array is contained within the array. For strings, it will return true if the string is a substring.
{% hint style="warning" %}
_Note_ that, just like jq, when checking if an array of strings `contains` another, this will use `contains` and _not_ equals to check each string. This means an array like `contains(["cat"])` will return true for an array `["cats"]`.
See the "Array has a subset array" example below on how to check for a subset.
{% endhint %}

View File

@@ -0,0 +1,3 @@
# Create, Collect into Object
This is used to construct objects (or maps). This can be used against existing yaml, or to create fresh yaml documents.

View File

@@ -0,0 +1,26 @@
# Date Time
Various operators for parsing and manipulating dates.
## Date time formattings
This uses the golangs built in time library for parsing and formatting date times.
When not specified, the RFC3339 standard is assumed `2006-01-02T15:04:05Z07:00` for parsing.
To specify a custom parsing format, use the `with_dtf` operator. The first parameter sets the datetime parsing format for the expression in the second parameter. The expression can be any valid `yq` expression tree.
```bash
yq 'with_dtf("myformat"; .a + "3h" | tz("Australia/Melbourne"))'
```
See the [library docs](https://pkg.go.dev/time#pkg-constants) for examples of formatting options.
## Timezones
This uses golangs built in LoadLocation function to parse timezones strings. See the [library docs](https://pkg.go.dev/time#LoadLocation) for more details.
## Durations
Durations are parsed using golangs built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
You can durations to time using the `+` operator.

View File

@@ -0,0 +1,3 @@
# Delete
Deletes matching entries in maps or arrays.

View File

@@ -0,0 +1,3 @@
# Document Index
Use the `documentIndex` operator (or the `di` shorthand) to select nodes of a particular document.

View File

@@ -0,0 +1,26 @@
# Encoder / Decoder
Encode operators will take the piped in object structure and encode it as a string in the desired format. The decode operators do the opposite, they take a formatted string and decode it into the relevant object structure.
Note that you can optionally pass an indent value to the encode functions (see below).
These operators are useful to process yaml documents that have stringified embedded yaml/json/props in them.
| Format | Decode (from string) | Encode (to string) |
| --- | -- | --|
| Yaml | from_yaml/@yamld | to_yaml(i)/@yaml |
| JSON | from_json/@jsond | to_json(i)/@json |
| Properties | from_props/@propsd | to_props/@props |
| CSV | from_csv/@csvd | to_csv/@csv |
| TSV | from_tsv/@tsvd | to_tsv/@tsv |
| XML | from_xml/@xmld | to_xml(i)/@xml |
| Base64 | @base64d | @base64 |
See CSV and TSV [documentation](https://mikefarah.gitbook.io/yq/usage/csv-tsv) for accepted formats.
XML uses the `--xml-attribute-prefix` and `xml-content-name` flags to identify attributes and content fields.
Base64 assumes [rfc4648](https://rfc-editor.org/rfc/rfc4648.html) encoding. Encoding and decoding both assume that the content is a string.

View File

@@ -0,0 +1,3 @@
# Entries
Similar to the same named functions in `jq` these functions convert to/from an object and an array of key-value pairs. This is most useful for performing operations on keys of maps.

View File

@@ -0,0 +1,31 @@
# Env Variable Operators
These operators are used to handle environment variables usage in expressions and documents. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read.
There are three operators:
- `env` which takes a single environment variable name and parse the variable as a yaml node (be it a map, array, string, number of boolean)
- `strenv` which also takes a single environment variable name, and always parses the variable as a string.
- `envsubst` which you pipe strings into and it interpolates environment variables in strings using [envsubst](https://github.com/a8m/envsubst).
## EnvSubst Options
You can optionally pass envsubst any of the following options:
- nu: NoUnset, this will fail if there are any referenced variables that are not set
- ne: NoEmpty, this will fail if there are any referenced variables that are empty
- ff: FailFast, this will abort on the first failure (rather than collect all the errors)
E.g:
`envsubst(ne, ff)` will fail on the first empty variable.
See [Imposing Restrictions](https://github.com/a8m/envsubst#imposing-restrictions) in the `envsubst` documentation for more information, and below for examples.
## Tip
To replace environment variables across all values in a document, `envsubst` can be used with the recursive descent operator
as follows:
```bash
yq '(.. | select(tag == "!!str")) |= envsubst' file.yaml
```

View File

@@ -0,0 +1,22 @@
# Equals / Not Equals
This is a boolean operator that will return `true` if the LHS is equal to the RHS and `false` otherwise.
```
.a == .b
```
It is most often used with the select operator to find particular nodes:
```
select(.a == .b)
```
The not equals `!=` operator returns `false` if the LHS is equal to the RHS.
## Related Operators
- comparison (`>=`, `<` etc) operators [here](https://mikefarah.gitbook.io/yq/operators/compare)
- boolean operators (`and`, `or`, `any` etc) [here](https://mikefarah.gitbook.io/yq/operators/boolean-operators)
- select operator [here](https://mikefarah.gitbook.io/yq/operators/select)

View File

@@ -0,0 +1,3 @@
# Error
Use this operation to short-circuit expressions. Useful for validation.

View File

@@ -0,0 +1,7 @@
# Eval
Use `eval` to dynamically process an expression - for instance from an environment variable.
`eval` takes a single argument, and evaluates that as a `yq` expression. Any valid expression can be used, beit a path `.a.b.c | select(. == "cat")`, or an update `.a.b.c = "gogo"`.
Tip: This can be useful way parameterise complex scripts.

View File

@@ -0,0 +1,11 @@
# File Operators
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
Note that the `fileIndex` operator has a short alias of `fi`.
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
```

View File

@@ -0,0 +1,2 @@
# Flatten
This recursively flattens arrays.

View File

@@ -0,0 +1,3 @@
# Group By
This is used to group items in an array by an expression.

View File

@@ -0,0 +1,3 @@
# Has
This is operation that returns true if the key exists in a map (or index in an array), false otherwise.

View File

@@ -0,0 +1,3 @@
# Keys
Use the `keys` operator to return map keys or array indices.

View File

@@ -0,0 +1,3 @@
# Length
Returns the lengths of the nodes. Length is defined according to the type of the node.

View File

@@ -0,0 +1,3 @@
# Line
Returns the line of the matching node. Starts from 1, 0 indicates there was no line data.

View File

@@ -0,0 +1,46 @@
# Load
The load operators allows you to load in content from another file.
Note that you can use string operators like `+` and `sub` to modify the value in the yaml file to a path that exists in your system.
You can load files of the following supported types:
|Format | Load Operator |
| --- | --- |
| Yaml | load |
| XML | load_xml |
| Properties | load_props |
| Plain String | load_str |
| Base64 | load_base64 |
## Samples files for tests:
### yaml
`../../examples/thing.yml`:
```yaml
a: apple is included
b: cool
```
### xml
`small.xml`:
```xml
<this>is some xml</this>
```
### properties
`small.properties`:
```properties
this.is = a properties file
```
### base64
`base64.txt`:
```
bXkgc2VjcmV0IGNoaWxsaSByZWNpcGUgaXMuLi4u
```

View File

@@ -0,0 +1,3 @@
# Map
Maps values of an array. Use `map_values` to map values of an object.

View File

@@ -0,0 +1,37 @@
# Multiply (Merge)
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
## Objects and arrays - merging
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
### Merge Flags
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
- `+` append arrays
- `d` deeply merge arrays
- `?` only merge _existing_ fields
- `n` only merge _new_ fields
- `c` clobber custom tags
### Merge two files together
This uses the load operator to merge file2 into file1.
```bash
yq '. *= load("file2.yml")' file1.yml
```
### Merging all files
Note the use of `eval-all` to ensure all documents are loaded into memory.
```bash
yq eval-all '. as $item ireduce ({}; . * $item )' *.yml
```
# Merging complex arrays together by a key field
By default - `yq` merge is naive. It merges maps when they match the key name, and arrays are merged either by appending them together, or merging the entries by their position in the array.
For more complex array merging (e.g. merging items that match on a certain key) please see the example [here](https://mikefarah.gitbook.io/yq/operators/multiply-merge#merge-arrays-of-objects-together-matching-on-a-key)

View File

@@ -0,0 +1,3 @@
# Parent
Parent simply returns the parent nodes of the matching nodes.

View File

@@ -0,0 +1,8 @@
# Path
The `path` operator can be used to get the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.
You can get the key/index of matching nodes by using the `path` operator to return the path array then piping that through `.[-1]` to get the last element of that array, the key.
Use `setpath` to set a value to the path array returned by `path`, and similarly `delpaths` for an array of path arrays.

View File

@@ -0,0 +1,5 @@
# Pick
Filter a map by the specified list of keys. Map is returned with the key in the order of the pick list.
Similarly, filter an array by the specified list of indices.

View File

@@ -0,0 +1,3 @@
# Pipe
Pipe the results of an expression into another. Like the bash operator.

View File

@@ -0,0 +1,21 @@
# Recursive Descent (Glob)
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
## match values form `..`
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
```bash
yq '.. style= "flow"' file.yaml
```
## match values and map keys form `...`
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
```bash
yq '... style= "flow"' file.yaml
```

View File

@@ -0,0 +1,22 @@
# Reduce
Reduce is a powerful way to process a collection of data into a new form.
```
<exp> as $<name> ireduce (<init>; <block>)
```
e.g.
```
.[] as $item ireduce (0; . + $item)
```
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
## yq vs jq syntax
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
To that end, the reduce operator is called `ireduce` for backwards compatibility if a `jq` like prefix version of `reduce` is ever added.

View File

@@ -0,0 +1,3 @@
# Reverse
Reverses the order of the items in an array

View File

@@ -0,0 +1,9 @@
# Select
Select is used to filter arrays and maps by a boolean expression.
## Related Operators
- equals / not equals (`==`, `!=`) operators [here](https://mikefarah.gitbook.io/yq/operators/equals)
- comparison (`>=`, `<` etc) operators [here](https://mikefarah.gitbook.io/yq/operators/compare)
- boolean operators (`and`, `or`, `any` etc) [here](https://mikefarah.gitbook.io/yq/operators/boolean-operators)

View File

@@ -0,0 +1,5 @@
# Slice/Splice Array
The slice array operator takes an array as input and returns a subarray. Like the `jq` equivalent, `.[10:15]` will return an array of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array.
You may leave out the first or second number, which will will refer to the start or end of the array respectively.

View File

@@ -0,0 +1,16 @@
# Sort Keys
The Sort Keys operator sorts maps by their keys (based on their string value). This operator does not do anything to arrays or scalars (so you can easily recursively apply it to all maps).
Sort is particularly useful for diffing two different yaml documents:
```bash
yq -i -P 'sort_keys(..)' file1.yml
yq -i -P 'sort_keys(..)' file2.yml
diff file1.yml file2.yml
```
Note that `yq` does not yet consider anchors when sorting by keys - this may result in invalid yaml documents if your are using merge anchors.
For more advanced sorting, using `to_entries` to convert the map to an array, then sort/process the array as you like (e.g. using `sort_by`) and convert back to a map using `from_entries`.
See [here](https://mikefarah.gitbook.io/yq/operators/entries#custom-sort-map-keys) for an example.

View File

@@ -0,0 +1,8 @@
# Sort
Sorts an array. Use `sort` to sort an array as is, or `sort_by(exp)` to sort by a particular expression (e.g. subfield).
To sort by descending order, pipe the results through the `reverse` operator after sorting.
Note that at this stage, `yq` only sorts scalar fields.

View File

@@ -0,0 +1,3 @@
# Split into Documents
This operator splits all matches into separate documents

View File

@@ -0,0 +1,57 @@
# String Operators
## RegEx
This uses golangs native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats)"`.
### match(regEx)
This operator returns the substring match details of the given regEx.
### capture(regEx)
Capture returns named RegEx capture groups in a map. Can be more convenient than `match` depending on what you are doing.
## test(regEx)
Returns true if the string matches the RegEx, false otherwise.
## sub(regEx, replacement)
Substitutes matched substrings. The first parameter is the regEx to match substrings within the original string. The second is a what to replace those matches with. This can refer to capture groups from the first RegEx.
## String blocks, bash and newlines
Bash is notorious for chomping on precious trailing newline characters, making it tricky to set strings with newlines properly. In particular, the `$( exp )` _will trim trailing newlines_.
For instance to get this yaml:
```
a: |
cat
```
Using `$( exp )` wont work, as it will trim the trailing new line.
```
m=$(echo "cat\n") yq -n '.a = strenv(m)'
a: cat
```
However, using printf works:
```
printf -v m "cat\n" ; m="$m" yq -n '.a = strenv(m)'
a: |
cat
```
As well as having multiline expressions:
```
m="cat
" yq -n '.a = strenv(m)'
a: |
cat
```
Similarly, if you're trying to set the content from a file, and want a trailing new line:
```
IFS= read -rd '' output < <(cat my_file)
output=$output ./yq '.data.values = strenv(output)' first.yml
```

View File

@@ -0,0 +1,3 @@
# Style
The style operator can be used to get or set the style of nodes (e.g. string style, yaml style)

View File

@@ -0,0 +1,3 @@
# Subtract
You can use subtract to subtract numbers, as well as removing elements from an array.

View File

@@ -0,0 +1,3 @@
# Tag
The tag operator can be used to get or set the tag of nodes (e.g. `!!str`, `!!int`, `!!bool`).

View File

@@ -0,0 +1,3 @@
# Traverse (Read)
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structures.

View File

@@ -0,0 +1,3 @@
# Union
This operator is used to combine different results together.

View File

@@ -0,0 +1,4 @@
# Unique
This is used to filter out duplicated items in an array. Note that the original order of the array is maintained.

View File

@@ -0,0 +1,5 @@
# Variable Operators
Like the `jq` equivalents, variables are sometimes required for the more complex expressions (or swapping values between fields).
Note that there is also an additional `ref` operator that holds a reference (instead of a copy) of the path, allowing you to make multiple changes to the same path.

View File

@@ -0,0 +1,3 @@
# With
Use the `with` operator to conveniently make multiple updates to a deeply nested path, or to update array elements relatively to each other. The first argument expression sets the root context, and the second expression runs against that root context.

154
external/yq/pkg/yqlib/doc/operators/keys.md vendored Executable file
View File

@@ -0,0 +1,154 @@
# Keys
Use the `keys` operator to return map keys or array indices.
## Map keys
Given a sample.yml file of:
```yaml
dog: woof
cat: meow
```
then
```bash
yq 'keys' sample.yml
```
will output
```yaml
- dog
- cat
```
## Array keys
Given a sample.yml file of:
```yaml
- apple
- banana
```
then
```bash
yq 'keys' sample.yml
```
will output
```yaml
- 0
- 1
```
## Retrieve array key
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq '.[1] | key' sample.yml
```
will output
```yaml
1
```
## Retrieve map key
Given a sample.yml file of:
```yaml
a: thing
```
then
```bash
yq '.a | key' sample.yml
```
will output
```yaml
a
```
## No key
Given a sample.yml file of:
```yaml
{}
```
then
```bash
yq 'key' sample.yml
```
will output
```yaml
```
## Update map key
Given a sample.yml file of:
```yaml
a:
x: 3
y: 4
```
then
```bash
yq '(.a.x | key) = "meow"' sample.yml
```
will output
```yaml
a:
meow: 3
y: 4
```
## Get comment from map key
Given a sample.yml file of:
```yaml
a:
# comment on key
x: 3
y: 4
```
then
```bash
yq '.a.x | key | headComment' sample.yml
```
will output
```yaml
comment on key
```
## Check node is a key
Given a sample.yml file of:
```yaml
a:
b:
- cat
c: frog
```
then
```bash
yq '[... | { "p": path | join("."), "isKey": is_key, "tag": tag }]' sample.yml
```
will output
```yaml
- p: ""
isKey: false
tag: '!!map'
- p: a
isKey: true
tag: '!!str'
- p: a
isKey: false
tag: '!!map'
- p: a.b
isKey: true
tag: '!!str'
- p: a.b
isKey: false
tag: '!!seq'
- p: a.b.0
isKey: false
tag: '!!str'
- p: a.c
isKey: true
tag: '!!str'
- p: a.c
isKey: false
tag: '!!str'
```

70
external/yq/pkg/yqlib/doc/operators/length.md vendored Executable file
View File

@@ -0,0 +1,70 @@
# Length
Returns the lengths of the nodes. Length is defined according to the type of the node.
## String length
returns length of string
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '.a | length' sample.yml
```
will output
```yaml
3
```
## null length
Given a sample.yml file of:
```yaml
a: null
```
then
```bash
yq '.a | length' sample.yml
```
will output
```yaml
0
```
## Map length
returns number of entries
Given a sample.yml file of:
```yaml
a: cat
c: dog
```
then
```bash
yq 'length' sample.yml
```
will output
```yaml
2
```
## Array length
returns number of elements
Given a sample.yml file of:
```yaml
- 2
- 4
- 6
- 8
```
then
```bash
yq 'length' sample.yml
```
will output
```yaml
4
```

62
external/yq/pkg/yqlib/doc/operators/line.md vendored Executable file
View File

@@ -0,0 +1,62 @@
# Line
Returns the line of the matching node. Starts from 1, 0 indicates there was no line data.
## Returns line of _value_ node
Given a sample.yml file of:
```yaml
a: cat
b:
c: cat
```
then
```bash
yq '.b | line' sample.yml
```
will output
```yaml
3
```
## Returns line of _key_ node
Pipe through the key operator to get the line of the key
Given a sample.yml file of:
```yaml
a: cat
b:
c: cat
```
then
```bash
yq '.b | key| line' sample.yml
```
will output
```yaml
2
```
## First line is 1
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '.a | line' sample.yml
```
will output
```yaml
1
```
## No line data is 0
Running
```bash
yq --null-input '{"a": "new entry"} | line'
```
will output
```yaml
0
```

194
external/yq/pkg/yqlib/doc/operators/load.md vendored Executable file
View File

@@ -0,0 +1,194 @@
# Load
The load operators allows you to load in content from another file.
Note that you can use string operators like `+` and `sub` to modify the value in the yaml file to a path that exists in your system.
You can load files of the following supported types:
|Format | Load Operator |
| --- | --- |
| Yaml | load |
| XML | load_xml |
| Properties | load_props |
| Plain String | load_str |
| Base64 | load_base64 |
## Samples files for tests:
### yaml
`../../examples/thing.yml`:
```yaml
a: apple is included
b: cool
```
### xml
`small.xml`:
```xml
<this>is some xml</this>
```
### properties
`small.properties`:
```properties
this.is = a properties file
```
### base64
`base64.txt`:
```
bXkgc2VjcmV0IGNoaWxsaSByZWNpcGUgaXMuLi4u
```
## Simple example
Given a sample.yml file of:
```yaml
myFile: ../../examples/thing.yml
```
then
```bash
yq 'load(.myFile)' sample.yml
```
will output
```yaml
a: apple is included
b: cool.
```
## Replace node with referenced file
Note that you can modify the filename in the load operator if needed.
Given a sample.yml file of:
```yaml
something:
file: thing.yml
```
then
```bash
yq '.something |= load("../../examples/" + .file)' sample.yml
```
will output
```yaml
something:
a: apple is included
b: cool.
```
## Replace _all_ nodes with referenced file
Recursively match all the nodes (`..`) and then filter the ones that have a 'file' attribute.
Given a sample.yml file of:
```yaml
something:
file: thing.yml
over:
here:
- file: thing.yml
```
then
```bash
yq '(.. | select(has("file"))) |= load("../../examples/" + .file)' sample.yml
```
will output
```yaml
something:
a: apple is included
b: cool.
over:
here:
- a: apple is included
b: cool.
```
## Replace node with referenced file as string
This will work for any text based file
Given a sample.yml file of:
```yaml
something:
file: thing.yml
```
then
```bash
yq '.something |= load_str("../../examples/" + .file)' sample.yml
```
will output
```yaml
something: |-
a: apple is included
b: cool.
```
## Load from XML
Given a sample.yml file of:
```yaml
cool: things
```
then
```bash
yq '.more_stuff = load_xml("../../examples/small.xml")' sample.yml
```
will output
```yaml
cool: things
more_stuff:
this: is some xml
```
## Load from Properties
Given a sample.yml file of:
```yaml
cool: things
```
then
```bash
yq '.more_stuff = load_props("../../examples/small.properties")' sample.yml
```
will output
```yaml
cool: things
more_stuff:
this:
is: a properties file
```
## Merge from properties
This can be used as a convenient way to update a yaml document
Given a sample.yml file of:
```yaml
this:
is: from yaml
cool: ay
```
then
```bash
yq '. *= load_props("../../examples/small.properties")' sample.yml
```
will output
```yaml
this:
is: a properties file
cool: ay
```
## Load from base64 encoded file
Given a sample.yml file of:
```yaml
cool: things
```
then
```bash
yq '.more_stuff = load_base64("../../examples/base64.txt")' sample.yml
```
will output
```yaml
cool: things
more_stuff: my secret chilli recipe is....
```

Some files were not shown because too many files have changed in this diff Show More