From 4c95efa46907e09f2b68754c8e40e78375eec661 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 17 Sep 2020 21:58:01 +1000 Subject: [PATCH 001/129] wip --- go.mod | 2 + go.sum | 4 ++ pkg/yqlib/path_tokeniser.go | 103 +++++++++++++++++++++++++++++++ pkg/yqlib/path_tokeniser_test.go | 52 ++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 pkg/yqlib/path_tokeniser.go create mode 100644 pkg/yqlib/path_tokeniser_test.go diff --git a/go.mod b/go.mod index 9253a567..89d1d959 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 // indirect + github.com/timtadh/data-structures v0.5.3 // indirect + github.com/timtadh/lexmachine v0.2.2 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 diff --git a/go.sum b/go.sum index 7672e2c1..4710d6b5 100644 --- a/go.sum +++ b/go.sum @@ -111,6 +111,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ= +github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU= +github.com/timtadh/lexmachine v0.2.2 h1:g55RnjdYazm5wnKv59pwFcBJHOyvTPfDEoz21s4PHmY= +github.com/timtadh/lexmachine v0.2.2/go.mod h1:GBJvD5OAfRn/gnp92zb9KTgHLB7akKyxmVivoYCcjQI= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go new file mode 100644 index 00000000..2283aed9 --- /dev/null +++ b/pkg/yqlib/path_tokeniser.go @@ -0,0 +1,103 @@ +package yqlib + +import ( + "strings" + + lex "github.com/timtadh/lexmachine" + "github.com/timtadh/lexmachine/machines" +) + +var Literals []string // The tokens representing literal strings +var Keywords []string // The keyword tokens +var Tokens []string // All of the tokens (including literals and keywords) +var TokenIds map[string]int // A map from the token names to their int ids + +func initTokens() { + Literals = []string{ + "(", + ")", + "[+]", + "[*]", + "**", + } + Tokens = []string{ + "OPERATION", // ==, OR, AND + "PATH", // a.b.c + "ARRAY_INDEX", // 1234 + "PATH_JOIN", // "." + } + Tokens = append(Tokens, Literals...) + TokenIds = make(map[string]int) + for i, tok := range Tokens { + TokenIds[tok] = i + } +} + +func skip(*lex.Scanner, *machines.Match) (interface{}, error) { + return nil, nil +} + +func token(name string) lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + return s.Token(TokenIds[name], string(m.Bytes), m), nil + } +} + +// Creates the lexer object and compiles the NFA. +func initLexer() (*lex.Lexer, error) { + lexer := lex.NewLexer() + for _, lit := range Literals { + r := "\\" + strings.Join(strings.Split(lit, ""), "\\") + lexer.Add([]byte(r), token(lit)) + } + lexer.Add([]byte(`([Oo][Rr]|[Aa][Nn][Dd]|==)`), token("OPERATION")) + lexer.Add([]byte(`\[-?[0-9]+\]`), token("ARRAY_INDEX")) + lexer.Add([]byte("( |\t|\n|\r)+"), skip) + lexer.Add([]byte(`"[^ "]+"`), token("PATH")) + lexer.Add([]byte(`[^ \.\[\(\)=]+`), token("PATH")) + lexer.Add([]byte(`\.`), skip) + err := lexer.Compile() + if err != nil { + return nil, err + } + return lexer, nil +} + +type PathTokeniser interface { + Tokenise(path string) ([]*lex.Token, error) +} + +type pathTokeniser struct { + lexer *lex.Lexer +} + +func NewPathTokeniser() PathTokeniser { + initTokens() + var lexer, err = initLexer() + if err != nil { + panic(err) + } + return &pathTokeniser{lexer} +} + +func (p *pathTokeniser) Tokenise(path string) ([]*lex.Token, error) { + scanner, err := p.lexer.Scanner([]byte(path)) + + if err != nil { + return nil, err + } + var tokens []*lex.Token + for tok, err, eof := scanner.Next(); !eof; tok, err, eof = scanner.Next() { + + if tok != nil { + token := tok.(*lex.Token) + log.Debugf("Processing %v - %v", token.Value, Tokens[token.Type]) + tokens = append(tokens, token) + } + if err != nil { + return nil, err + } + } + + return tokens, nil +} diff --git a/pkg/yqlib/path_tokeniser_test.go b/pkg/yqlib/path_tokeniser_test.go new file mode 100644 index 00000000..b12aca67 --- /dev/null +++ b/pkg/yqlib/path_tokeniser_test.go @@ -0,0 +1,52 @@ +package yqlib + +import ( + "testing" + + "github.com/mikefarah/yq/v3/test" +) + +var tokeniserTests = []struct { + path string + expectedTokens []interface{} +}{ // TODO: Ensure ALL documented examples have tests! sheesh + + // {"apples.BANANAS", append(make([]interface{}, 0), "apples", "BANANAS")}, + // {"a.b.**", append(make([]interface{}, 0), "a", "b", "**")}, + // {"a.\"=\".frog", append(make([]interface{}, 0), "a", "=", "frog")}, + // {"a.b.*", append(make([]interface{}, 0), "a", "b", "*")}, + // {"a.b.thin*", append(make([]interface{}, 0), "a", "b", "thin*")}, + // {"a.b[0]", append(make([]interface{}, 0), "a", "b", "0")}, + // {"a.b[*]", append(make([]interface{}, 0), "a", "b", "[*]")}, + // {"a.b[-12]", append(make([]interface{}, 0), "a", "b", "-12")}, + // {"a.b.0", append(make([]interface{}, 0), "a", "b", "0")}, + // {"a.b.d[+]", append(make([]interface{}, 0), "a", "b", "d", "[+]")}, + // {"a", append(make([]interface{}, 0), "a")}, + // {"\"a.b\".c", append(make([]interface{}, 0), "a.b", "c")}, + // {`b."foo.bar"`, append(make([]interface{}, 0), "b", "foo.bar")}, + // {"animals(.==cat)", append(make([]interface{}, 0), "animals", "(", "==", "cat", ")")}, // TODO validate this dot is not a join? + // {"animals(.==c*)", append(make([]interface{}, 0), "animals", "(", "==", "c*", ")")}, // TODO validate this dot is not a join? + // {"[1].a.d", append(make([]interface{}, 0), int64(1), "a", "d")}, + // {"a[0].c", append(make([]interface{}, 0), "a", int64(0), "c")}, + // {"[0]", append(make([]interface{}, 0), int64(0))}, + // {"a.cool(s.d.f==cool)", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", ")")}, + {"a.cool(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", "OR", "t", "b", "h", "==", "frog", ")", "caterpillar")}, + {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", "and", "t", "b", "h", "==", "frog", ")", "*")}, + {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", "and", "t", "b", "h", "==", "frog", ")", "th*")}, +} + +var tokeniser = NewPathTokeniser() + +func TestTokeniser(t *testing.T) { + for _, tt := range tokeniserTests { + tokens, err := tokeniser.Tokenise(tt.path) + if err != nil { + t.Error(tt.path, err) + } + var tokenValues []interface{} + for _, token := range tokens { + tokenValues = append(tokenValues, token.Value) + } + test.AssertResultComplex(t, tt.expectedTokens, tokenValues) + } +} From c321600afa4cb7dc6c3b92ad38440b60ac30a47f Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 17 Sep 2020 22:12:56 +1000 Subject: [PATCH 002/129] fixed wrapping! --- pkg/yqlib/path_tokeniser.go | 30 +++++++++++++++++++++++-- pkg/yqlib/path_tokeniser_test.go | 38 ++++++++++++++++---------------- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 2283aed9..a76cf75e 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -1,6 +1,7 @@ package yqlib import ( + "strconv" "strings" lex "github.com/timtadh/lexmachine" @@ -43,6 +44,30 @@ func token(name string) lex.Action { } } +func unwrap(value string) string { + return value[1 : len(value)-1] +} + +func wrappedToken(name string) lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + return s.Token(TokenIds[name], unwrap(string(m.Bytes)), m), nil + } +} + +func numberToken(name string, wrapped bool) lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + var numberString = string(m.Bytes) + if wrapped { + numberString = unwrap(numberString) + } + var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint + if errParsingInt != nil { + return nil, errParsingInt + } + return s.Token(TokenIds[name], number, m), nil + } +} + // Creates the lexer object and compiles the NFA. func initLexer() (*lex.Lexer, error) { lexer := lex.NewLexer() @@ -51,9 +76,10 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(r), token(lit)) } lexer.Add([]byte(`([Oo][Rr]|[Aa][Nn][Dd]|==)`), token("OPERATION")) - lexer.Add([]byte(`\[-?[0-9]+\]`), token("ARRAY_INDEX")) + lexer.Add([]byte(`\[-?[0-9]+\]`), numberToken("ARRAY_INDEX", true)) + lexer.Add([]byte(`-?[0-9]+`), numberToken("ARRAY_INDEX", false)) lexer.Add([]byte("( |\t|\n|\r)+"), skip) - lexer.Add([]byte(`"[^ "]+"`), token("PATH")) + lexer.Add([]byte(`"[^ "]+"`), wrappedToken("PATH")) lexer.Add([]byte(`[^ \.\[\(\)=]+`), token("PATH")) lexer.Add([]byte(`\.`), skip) err := lexer.Compile() diff --git a/pkg/yqlib/path_tokeniser_test.go b/pkg/yqlib/path_tokeniser_test.go index b12aca67..710a98a6 100644 --- a/pkg/yqlib/path_tokeniser_test.go +++ b/pkg/yqlib/path_tokeniser_test.go @@ -11,25 +11,25 @@ var tokeniserTests = []struct { expectedTokens []interface{} }{ // TODO: Ensure ALL documented examples have tests! sheesh - // {"apples.BANANAS", append(make([]interface{}, 0), "apples", "BANANAS")}, - // {"a.b.**", append(make([]interface{}, 0), "a", "b", "**")}, - // {"a.\"=\".frog", append(make([]interface{}, 0), "a", "=", "frog")}, - // {"a.b.*", append(make([]interface{}, 0), "a", "b", "*")}, - // {"a.b.thin*", append(make([]interface{}, 0), "a", "b", "thin*")}, - // {"a.b[0]", append(make([]interface{}, 0), "a", "b", "0")}, - // {"a.b[*]", append(make([]interface{}, 0), "a", "b", "[*]")}, - // {"a.b[-12]", append(make([]interface{}, 0), "a", "b", "-12")}, - // {"a.b.0", append(make([]interface{}, 0), "a", "b", "0")}, - // {"a.b.d[+]", append(make([]interface{}, 0), "a", "b", "d", "[+]")}, - // {"a", append(make([]interface{}, 0), "a")}, - // {"\"a.b\".c", append(make([]interface{}, 0), "a.b", "c")}, - // {`b."foo.bar"`, append(make([]interface{}, 0), "b", "foo.bar")}, - // {"animals(.==cat)", append(make([]interface{}, 0), "animals", "(", "==", "cat", ")")}, // TODO validate this dot is not a join? - // {"animals(.==c*)", append(make([]interface{}, 0), "animals", "(", "==", "c*", ")")}, // TODO validate this dot is not a join? - // {"[1].a.d", append(make([]interface{}, 0), int64(1), "a", "d")}, - // {"a[0].c", append(make([]interface{}, 0), "a", int64(0), "c")}, - // {"[0]", append(make([]interface{}, 0), int64(0))}, - // {"a.cool(s.d.f==cool)", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", ")")}, + {"apples.BANANAS", append(make([]interface{}, 0), "apples", "BANANAS")}, + {"a.b.**", append(make([]interface{}, 0), "a", "b", "**")}, + {"a.\"=\".frog", append(make([]interface{}, 0), "a", "=", "frog")}, + {"a.b.*", append(make([]interface{}, 0), "a", "b", "*")}, + {"a.b.thin*", append(make([]interface{}, 0), "a", "b", "thin*")}, + {"a.b[0]", append(make([]interface{}, 0), "a", "b", int64(0))}, + {"a.b[*]", append(make([]interface{}, 0), "a", "b", "[*]")}, + {"a.b[-12]", append(make([]interface{}, 0), "a", "b", int64(-12))}, + {"a.b.0", append(make([]interface{}, 0), "a", "b", int64(0))}, + {"a.b.d[+]", append(make([]interface{}, 0), "a", "b", "d", "[+]")}, + {"a", append(make([]interface{}, 0), "a")}, + {"\"a.b\".c", append(make([]interface{}, 0), "a.b", "c")}, + {`b."foo.bar"`, append(make([]interface{}, 0), "b", "foo.bar")}, + {"animals(.==cat)", append(make([]interface{}, 0), "animals", "(", "==", "cat", ")")}, // TODO validate this dot is not a join? + {"animals(.==c*)", append(make([]interface{}, 0), "animals", "(", "==", "c*", ")")}, // TODO validate this dot is not a join? + {"[1].a.d", append(make([]interface{}, 0), int64(1), "a", "d")}, + {"a[0].c", append(make([]interface{}, 0), "a", int64(0), "c")}, + {"[0]", append(make([]interface{}, 0), int64(0))}, + {"a.cool(s.d.f==cool)", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", ")")}, {"a.cool(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", "OR", "t", "b", "h", "==", "frog", ")", "caterpillar")}, {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", "and", "t", "b", "h", "==", "frog", ")", "*")}, {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", "and", "t", "b", "h", "==", "frog", ")", "th*")}, From ae59ad57f45cadcd2a568fb96ff0db6b3e821960 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 18 Sep 2020 17:08:33 +1000 Subject: [PATCH 003/129] tree wip --- pkg/yqlib/path_tokeniser.go | 7 +++-- pkg/yqlib/path_tree.go | 51 +++++++++++++++++++++++++++++++++++++ pkg/yqlib/path_tree_test.go | 1 + 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 pkg/yqlib/path_tree.go create mode 100644 pkg/yqlib/path_tree_test.go diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index a76cf75e..848ee149 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -23,9 +23,8 @@ func initTokens() { } Tokens = []string{ "OPERATION", // ==, OR, AND - "PATH", // a.b.c + "PATH_KEY", // apples "ARRAY_INDEX", // 1234 - "PATH_JOIN", // "." } Tokens = append(Tokens, Literals...) TokenIds = make(map[string]int) @@ -79,8 +78,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\[-?[0-9]+\]`), numberToken("ARRAY_INDEX", true)) lexer.Add([]byte(`-?[0-9]+`), numberToken("ARRAY_INDEX", false)) lexer.Add([]byte("( |\t|\n|\r)+"), skip) - lexer.Add([]byte(`"[^ "]+"`), wrappedToken("PATH")) - lexer.Add([]byte(`[^ \.\[\(\)=]+`), token("PATH")) + lexer.Add([]byte(`"[^ "]+"`), wrappedToken("PATH_KEY")) + lexer.Add([]byte(`[^ \.\[\(\)=]+`), token("PATH_KEY")) lexer.Add([]byte(`\.`), skip) err := lexer.Compile() if err != nil { diff --git a/pkg/yqlib/path_tree.go b/pkg/yqlib/path_tree.go new file mode 100644 index 00000000..038d4015 --- /dev/null +++ b/pkg/yqlib/path_tree.go @@ -0,0 +1,51 @@ +package yqlib + +import lex "github.com/timtadh/lexmachine" + +type PathElementType uint32 + +const ( + PathKey PathElementType = 1 << iota + ArrayIndex + Operation +) + +type OperationType uint32 + +const ( + None OperationType = 1 << iota + Or + And + ChildEquals +) + +type PathElement struct { + PathElementType PathElementType + OperationType OperationType + Value interface{} + ChildElements [][]*PathElement +} + +func parseTree(tokens []*lex.Token, currentElement *PathElement, allElements []*PathElement) []*PathElement { + currentToken, remainingTokens := tokens[0], tokens[1:] + + switch currentToken.Type { + case TokenIds["PATH_KEY"]: + currentElement.PathElementType = PathKey + currentElement.OperationType = None + currentElement.Value = currentToken.Value + } + + if len(remainingTokens) == 0 { + return append(allElements, currentElement) + } + return parseTree(remainingTokens, &PathElement{}, append(allElements, currentElement)) + +} + +func ParseTree(tokens []*lex.Token) []*PathElement { + if len(tokens) == 0 { + return make([]*PathElement, 0) + } + return parseTree(tokens, &PathElement{}, make([]*PathElement, 0)) +} diff --git a/pkg/yqlib/path_tree_test.go b/pkg/yqlib/path_tree_test.go new file mode 100644 index 00000000..88c44e97 --- /dev/null +++ b/pkg/yqlib/path_tree_test.go @@ -0,0 +1 @@ +package yqlib From a8bdc12d8341264ffe254a6f2472107e3d6e35e4 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 20 Sep 2020 22:40:09 +1000 Subject: [PATCH 004/129] to postfix wip --- pkg/yqlib/path_postfix.go | 152 +++++++++++++++++++++++++++++++ pkg/yqlib/path_postfix_test.go | 82 +++++++++++++++++ pkg/yqlib/path_tokeniser.go | 10 +- pkg/yqlib/path_tokeniser_test.go | 1 + pkg/yqlib/path_tree.go | 24 ----- 5 files changed, 243 insertions(+), 26 deletions(-) create mode 100644 pkg/yqlib/path_postfix.go create mode 100644 pkg/yqlib/path_postfix_test.go diff --git a/pkg/yqlib/path_postfix.go b/pkg/yqlib/path_postfix.go new file mode 100644 index 00000000..7f40efab --- /dev/null +++ b/pkg/yqlib/path_postfix.go @@ -0,0 +1,152 @@ +package yqlib + +import ( + "errors" + "fmt" + + lex "github.com/timtadh/lexmachine" +) + +var precedenceMap map[int]int + +type PathElementType uint32 + +const ( + PathKey PathElementType = 1 << iota + ArrayIndex + Operation +) + +type OperationType uint32 + +const ( + None OperationType = 1 << iota + Or + And + Equals +) + +type PathElement struct { + PathElementType PathElementType + OperationType OperationType + Value interface{} + ChildElements [][]*PathElement + Finished bool +} + +// debugging purposes only +func (p *PathElement) toString() string { + var result string = `Type: ` + switch p.PathElementType { + case PathKey: + result = result + fmt.Sprintf("PathKey - %v\n", p.Value) + case ArrayIndex: + result = result + fmt.Sprintf("ArrayIndex - %v\n", p.Value) + case Operation: + result = result + "Operation - " + switch p.OperationType { + case Or: + result = result + "OR\n" + case And: + result = result + "AND\n" + case Equals: + result = result + "EQUALS\n" + } + } + return result +} + +var operationTypeMapper map[int]OperationType + +func initMaps() { + precedenceMap = make(map[int]int) + operationTypeMapper = make(map[int]OperationType) + + precedenceMap[TokenIds["("]] = 0 + + precedenceMap[TokenIds["OR_OPERATOR"]] = 10 + operationTypeMapper[TokenIds["OR_OPERATOR"]] = Or + + precedenceMap[TokenIds["AND_OPERATOR"]] = 20 + operationTypeMapper[TokenIds["AND_OPERATOR"]] = And + + precedenceMap[TokenIds["EQUALS_OPERATOR"]] = 30 + operationTypeMapper[TokenIds["EQUALS_OPERATOR"]] = Equals +} + +func createOperationPathElement(opToken *lex.Token) PathElement { + var childElements = make([][]*PathElement, 2) + var pathElement = PathElement{PathElementType: Operation, OperationType: operationTypeMapper[opToken.Type], ChildElements: childElements} + return pathElement +} + +type PathPostFixer interface { + ConvertToPostfix([]*lex.Token) ([]*PathElement, error) +} + +type pathPostFixer struct { +} + +func NewPathPostFixer() PathPostFixer { + return &pathPostFixer{} +} + +func popOpToResult(opStack []*lex.Token, result []*PathElement) ([]*lex.Token, []*PathElement) { + var operatorToPushToPostFix *lex.Token + opStack, operatorToPushToPostFix = opStack[0:len(opStack)-1], opStack[len(opStack)-1] + var pathElement = createOperationPathElement(operatorToPushToPostFix) + return opStack, append(result, &pathElement) +} + +func finishPathKey(result []*PathElement) { + if len(result) > 0 { + //need to mark PathKey elements as finished so we + //stop appending PathKeys as children + result[len(result)-1].Finished = true + } +} + +func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathElement, error) { + var result []*PathElement + // surround the whole thing with quotes + var opStack = []*lex.Token{&lex.Token{Type: TokenIds["("]}} + var tokens = append(infixTokens, &lex.Token{Type: TokenIds[")"]}) + + for _, token := range tokens { + switch token.Type { + case TokenIds["PATH_KEY"]: // handle splats and array appends here too + var emptyArray = [][]*PathElement{make([]*PathElement, 0)} + var pathElement = PathElement{PathElementType: PathKey, Value: token.Value, ChildElements: emptyArray} + + if len(result) > 0 && result[len(result)-1].PathElementType == PathKey && !result[len(result)-1].Finished { + var lastElement = result[len(result)-1] + lastElement.ChildElements[0] = append(lastElement.ChildElements[0], &pathElement) + } else { + result = append(result, &pathElement) + } + case TokenIds["("]: + opStack = append(opStack, token) + finishPathKey(result) + case TokenIds["OR_OPERATOR"], TokenIds["AND_OPERATOR"], TokenIds["EQUALS_OPERATOR"]: + var currentPrecedence = precedenceMap[token.Type] + // pop off higher precedent operators onto the result + for len(opStack) > 0 && precedenceMap[opStack[len(opStack)-1].Type] > currentPrecedence { + opStack, result = popOpToResult(opStack, result) + } + // add this operator to the opStack + opStack = append(opStack, token) + finishPathKey(result) + case TokenIds[")"]: + for len(opStack) > 0 && opStack[len(opStack)-1].Type != TokenIds["("] { + opStack, result = popOpToResult(opStack, result) + } + if len(opStack) == 0 { + return nil, errors.New("Bad path expression, got close brackets without matching opening bracket") + } + // now we should have ( as the last element on the opStack, get rid of it + opStack = opStack[0 : len(opStack)-1] + finishPathKey(result) + } + } + return result, nil +} diff --git a/pkg/yqlib/path_postfix_test.go b/pkg/yqlib/path_postfix_test.go new file mode 100644 index 00000000..b377619b --- /dev/null +++ b/pkg/yqlib/path_postfix_test.go @@ -0,0 +1,82 @@ +package yqlib + +import ( + "testing" + + "github.com/mikefarah/yq/v3/test" +) + +// var tokeniser = NewPathTokeniser() +var postFixer = NewPathPostFixer() + +func testExpression(expression string) (string, error) { + tokens, err := tokeniser.Tokenise(expression) + if err != nil { + return "", err + } + results, errorP := postFixer.ConvertToPostfix(tokens) + if errorP != nil { + return "", errorP + } + formatted := "" + for _, path := range results { + formatted = formatted + path.toString() + "--------\n" + } + return formatted, nil +} + +func TestPostFixSimple(t *testing.T) { + var infix = "a" + var expectedOutput = "Type: PathKey - a\n" + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + +func TestPostFixOr(t *testing.T) { + var infix = "a OR b" + var expectedOutput = `Type: PathKey - a +-------- +Type: PathKey - b +-------- +Type: Operation - OR +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + +func TestPostFixOrWithEquals(t *testing.T) { + var infix = "a==thing OR b==thongs" + var expectedOutput = `Type: PathKey - a +-------- +Type: PathKey - thing +-------- +Type: Operation - EQUALS +-------- +Type: PathKey - b +-------- +Type: PathKey - thongs +-------- +Type: Operation - EQUALS +-------- +Type: Operation - OR +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 848ee149..009cdf60 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -22,7 +22,9 @@ func initTokens() { "**", } Tokens = []string{ - "OPERATION", // ==, OR, AND + "OR_OPERATOR", + "AND_OPERATOR", + "EQUALS_OPERATOR", "PATH_KEY", // apples "ARRAY_INDEX", // 1234 } @@ -31,6 +33,8 @@ func initTokens() { for i, tok := range Tokens { TokenIds[tok] = i } + + initMaps() } func skip(*lex.Scanner, *machines.Match) (interface{}, error) { @@ -74,7 +78,9 @@ func initLexer() (*lex.Lexer, error) { r := "\\" + strings.Join(strings.Split(lit, ""), "\\") lexer.Add([]byte(r), token(lit)) } - lexer.Add([]byte(`([Oo][Rr]|[Aa][Nn][Dd]|==)`), token("OPERATION")) + lexer.Add([]byte(`([Oo][Rr])`), token("OR_OPERATOR")) + lexer.Add([]byte(`([Aa][Nn][Dd])`), token("AND_OPERATOR")) + lexer.Add([]byte(`(==)`), token("EQUALS_OPERATOR")) lexer.Add([]byte(`\[-?[0-9]+\]`), numberToken("ARRAY_INDEX", true)) lexer.Add([]byte(`-?[0-9]+`), numberToken("ARRAY_INDEX", false)) lexer.Add([]byte("( |\t|\n|\r)+"), skip) diff --git a/pkg/yqlib/path_tokeniser_test.go b/pkg/yqlib/path_tokeniser_test.go index 710a98a6..75b00609 100644 --- a/pkg/yqlib/path_tokeniser_test.go +++ b/pkg/yqlib/path_tokeniser_test.go @@ -12,6 +12,7 @@ var tokeniserTests = []struct { }{ // TODO: Ensure ALL documented examples have tests! sheesh {"apples.BANANAS", append(make([]interface{}, 0), "apples", "BANANAS")}, + {"appl*.BANA*", append(make([]interface{}, 0), "appl*", "BANA*")}, {"a.b.**", append(make([]interface{}, 0), "a", "b", "**")}, {"a.\"=\".frog", append(make([]interface{}, 0), "a", "=", "frog")}, {"a.b.*", append(make([]interface{}, 0), "a", "b", "*")}, diff --git a/pkg/yqlib/path_tree.go b/pkg/yqlib/path_tree.go index 038d4015..cf0b1eaf 100644 --- a/pkg/yqlib/path_tree.go +++ b/pkg/yqlib/path_tree.go @@ -2,30 +2,6 @@ package yqlib import lex "github.com/timtadh/lexmachine" -type PathElementType uint32 - -const ( - PathKey PathElementType = 1 << iota - ArrayIndex - Operation -) - -type OperationType uint32 - -const ( - None OperationType = 1 << iota - Or - And - ChildEquals -) - -type PathElement struct { - PathElementType PathElementType - OperationType OperationType - Value interface{} - ChildElements [][]*PathElement -} - func parseTree(tokens []*lex.Token, currentElement *PathElement, allElements []*PathElement) []*PathElement { currentToken, remainingTokens := tokens[0], tokens[1:] From e32bc43c4e827cf75b191def19de50dd78cb5d3f Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 20 Sep 2020 22:47:53 +1000 Subject: [PATCH 005/129] postfix wip! --- pkg/yqlib/path_postfix.go | 6 ++++- pkg/yqlib/path_postfix_test.go | 44 +++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/pkg/yqlib/path_postfix.go b/pkg/yqlib/path_postfix.go index 7f40efab..9a036c62 100644 --- a/pkg/yqlib/path_postfix.go +++ b/pkg/yqlib/path_postfix.go @@ -39,7 +39,11 @@ func (p *PathElement) toString() string { var result string = `Type: ` switch p.PathElementType { case PathKey: - result = result + fmt.Sprintf("PathKey - %v\n", p.Value) + result = result + fmt.Sprintf("PathKey - %v", p.Value) + for _, next := range p.ChildElements[0] { + result = result + fmt.Sprintf(".%v", next.Value) + } + result = result + "\n" case ArrayIndex: result = result + fmt.Sprintf("ArrayIndex - %v\n", p.Value) case Operation: diff --git a/pkg/yqlib/path_postfix_test.go b/pkg/yqlib/path_postfix_test.go index b377619b..d3c6d729 100644 --- a/pkg/yqlib/path_postfix_test.go +++ b/pkg/yqlib/path_postfix_test.go @@ -27,7 +27,23 @@ func testExpression(expression string) (string, error) { func TestPostFixSimple(t *testing.T) { var infix = "a" - var expectedOutput = "Type: PathKey - a\n" + var expectedOutput = `Type: PathKey - a +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + +func TestPostFixSimplePath(t *testing.T) { + var infix = "apples.bananas*.cat" + var expectedOutput = `Type: PathKey - apples.bananas*.cat +-------- +` actual, err := testExpression(infix) if err != nil { @@ -80,3 +96,29 @@ Type: Operation - OR test.AssertResultComplex(t, expectedOutput, actual) } + +func TestPostFixOrWithEqualsPath(t *testing.T) { + var infix = "apples.monkeys==thing OR bogs.bobos==thongs" + var expectedOutput = `Type: PathKey - apples.monkeys +-------- +Type: PathKey - thing +-------- +Type: Operation - EQUALS +-------- +Type: PathKey - bogs.bobos +-------- +Type: PathKey - thongs +-------- +Type: Operation - EQUALS +-------- +Type: Operation - OR +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} From e037c57725fe7d0b5d333b6bec6e750e49da1539 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 20 Sep 2020 22:47:57 +1000 Subject: [PATCH 006/129] postfix wip! --- test/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils.go b/test/utils.go index 25093e2f..de621519 100644 --- a/test/utils.go +++ b/test/utils.go @@ -50,7 +50,7 @@ func AssertResult(t *testing.T, expectedValue interface{}, actualValue interface func AssertResultComplex(t *testing.T, expectedValue interface{}, actualValue interface{}) { t.Helper() if !reflect.DeepEqual(expectedValue, actualValue) { - t.Error("Expected <", expectedValue, "> but got <", actualValue, ">", fmt.Sprintf("%T", actualValue)) + t.Error("\nExpected <", expectedValue, ">\nbut got <", actualValue, ">", fmt.Sprintf("%T", actualValue)) } } From 95bc1e1599ab60c9121ea33425eee6a1f5fee54c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 24 Sep 2020 10:52:45 +1000 Subject: [PATCH 007/129] include traverse as a operator token --- pkg/yqlib/data_tree_navigator.go | 23 +++++++++++ pkg/yqlib/data_tree_navigator_test.go | 1 + pkg/yqlib/path_tokeniser.go | 49 +++++++++++++++++++----- pkg/yqlib/path_tokeniser_test.go | 55 +++++++++++++++++---------- 4 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 pkg/yqlib/data_tree_navigator.go create mode 100644 pkg/yqlib/data_tree_navigator_test.go diff --git a/pkg/yqlib/data_tree_navigator.go b/pkg/yqlib/data_tree_navigator.go new file mode 100644 index 00000000..203a45e1 --- /dev/null +++ b/pkg/yqlib/data_tree_navigator.go @@ -0,0 +1,23 @@ +package yqlib + +// import yaml "gopkg.in/yaml.v3" + +// type NodeLeafContext struct { +// Node *yaml.Node +// Head interface{} +// PathStack []interface{} +// } + +// func newNodeLeafContext(node *yaml.Node, head interface{}, tailpathStack []interface{}) NodeLeafContext { +// newPathStack := make([]interface{}, len(pathStack)) +// copy(newPathStack, pathStack) +// return NodeContext{ +// Node: node, +// Head: head, +// PathStack: newPathStack, +// } +// } + +// type DataTreeNavigator interface { +// Traverse(value *NodeLeafContext) +// } diff --git a/pkg/yqlib/data_tree_navigator_test.go b/pkg/yqlib/data_tree_navigator_test.go new file mode 100644 index 00000000..88c44e97 --- /dev/null +++ b/pkg/yqlib/data_tree_navigator_test.go @@ -0,0 +1 @@ +package yqlib diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 009cdf60..ab2da6cd 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -8,27 +8,35 @@ import ( "github.com/timtadh/lexmachine/machines" ) -var Literals []string // The tokens representing literal strings -var Keywords []string // The keyword tokens -var Tokens []string // All of the tokens (including literals and keywords) -var TokenIds map[string]int // A map from the token names to their int ids +var Literals []string // The tokens representing literal strings +var ClosingLiterals []string // The tokens representing literal strings +var Keywords []string // The keyword tokens +var Tokens []string // All of the tokens (including literals and keywords) +var TokenIds map[string]int // A map from the token names to their int ids func initTokens() { - Literals = []string{ + Literals = []string{ // these need a traverse operator infront "(", - ")", "[+]", "[*]", "**", } + ClosingLiterals = []string{ // these need a traverse operator after + ")", + } Tokens = []string{ + "BEGIN_SUB_EXPRESSION", + "END_SUB_EXPRESSION", "OR_OPERATOR", "AND_OPERATOR", "EQUALS_OPERATOR", + "EQUALS_SELF_OPERATOR", + "TRAVERSE_OPERATOR", "PATH_KEY", // apples - "ARRAY_INDEX", // 1234 + "ARRAY_INDEX", // 123 } Tokens = append(Tokens, Literals...) + Tokens = append(Tokens, ClosingLiterals...) TokenIds = make(map[string]int) for i, tok := range Tokens { TokenIds[tok] = i @@ -78,15 +86,20 @@ func initLexer() (*lex.Lexer, error) { r := "\\" + strings.Join(strings.Split(lit, ""), "\\") lexer.Add([]byte(r), token(lit)) } + for _, lit := range ClosingLiterals { + r := "\\" + strings.Join(strings.Split(lit, ""), "\\") + lexer.Add([]byte(r), token(lit)) + } lexer.Add([]byte(`([Oo][Rr])`), token("OR_OPERATOR")) lexer.Add([]byte(`([Aa][Nn][Dd])`), token("AND_OPERATOR")) - lexer.Add([]byte(`(==)`), token("EQUALS_OPERATOR")) + lexer.Add([]byte(`\.\s*==\s*`), token("EQUALS_SELF_OPERATOR")) + lexer.Add([]byte(`\s*==\s*`), token("EQUALS_OPERATOR")) lexer.Add([]byte(`\[-?[0-9]+\]`), numberToken("ARRAY_INDEX", true)) lexer.Add([]byte(`-?[0-9]+`), numberToken("ARRAY_INDEX", false)) lexer.Add([]byte("( |\t|\n|\r)+"), skip) lexer.Add([]byte(`"[^ "]+"`), wrappedToken("PATH_KEY")) lexer.Add([]byte(`[^ \.\[\(\)=]+`), token("PATH_KEY")) - lexer.Add([]byte(`\.`), skip) + lexer.Add([]byte(`\.`), token("TRAVERSE_OPERATOR")) err := lexer.Compile() if err != nil { return nil, err @@ -129,6 +142,22 @@ func (p *pathTokeniser) Tokenise(path string) ([]*lex.Token, error) { return nil, err } } + var postProcessedTokens []*lex.Token = make([]*lex.Token, 0) - return tokens, nil + for index, token := range tokens { + for _, literalTokenDef := range append(Literals, "ARRAY_INDEX") { + if index > 0 && token.Type == TokenIds[literalTokenDef] && tokens[index-1].Type != TokenIds["TRAVERSE_OPERATOR"] { + postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) + } + } + + postProcessedTokens = append(postProcessedTokens, token) + for _, literalTokenDef := range append(ClosingLiterals, "ARRAY_INDEX") { + if index != len(tokens)-1 && token.Type == TokenIds[literalTokenDef] && tokens[index+1].Type != TokenIds["TRAVERSE_OPERATOR"] { + postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) + } + } + } + + return postProcessedTokens, nil } diff --git a/pkg/yqlib/path_tokeniser_test.go b/pkg/yqlib/path_tokeniser_test.go index 75b00609..d5b24811 100644 --- a/pkg/yqlib/path_tokeniser_test.go +++ b/pkg/yqlib/path_tokeniser_test.go @@ -11,29 +11,42 @@ var tokeniserTests = []struct { expectedTokens []interface{} }{ // TODO: Ensure ALL documented examples have tests! sheesh - {"apples.BANANAS", append(make([]interface{}, 0), "apples", "BANANAS")}, - {"appl*.BANA*", append(make([]interface{}, 0), "appl*", "BANA*")}, - {"a.b.**", append(make([]interface{}, 0), "a", "b", "**")}, - {"a.\"=\".frog", append(make([]interface{}, 0), "a", "=", "frog")}, - {"a.b.*", append(make([]interface{}, 0), "a", "b", "*")}, - {"a.b.thin*", append(make([]interface{}, 0), "a", "b", "thin*")}, - {"a.b[0]", append(make([]interface{}, 0), "a", "b", int64(0))}, - {"a.b[*]", append(make([]interface{}, 0), "a", "b", "[*]")}, - {"a.b[-12]", append(make([]interface{}, 0), "a", "b", int64(-12))}, - {"a.b.0", append(make([]interface{}, 0), "a", "b", int64(0))}, - {"a.b.d[+]", append(make([]interface{}, 0), "a", "b", "d", "[+]")}, + {"apples.BANANAS", append(make([]interface{}, 0), "apples", ".", "BANANAS")}, + {"appl*.BANA*", append(make([]interface{}, 0), "appl*", ".", "BANA*")}, + {"a.b.**", append(make([]interface{}, 0), "a", ".", "b", ".", "**")}, + {"a.\"=\".frog", append(make([]interface{}, 0), "a", ".", "=", ".", "frog")}, + {"a.b.*", append(make([]interface{}, 0), "a", ".", "b", ".", "*")}, + {"a.b.thin*", append(make([]interface{}, 0), "a", ".", "b", ".", "thin*")}, + {"a.b[0]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, + {"a.b.[0]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, + {"a.b[*]", append(make([]interface{}, 0), "a", ".", "b", ".", "[*]")}, + {"a.b.[*]", append(make([]interface{}, 0), "a", ".", "b", ".", "[*]")}, + {"a.b[+]", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]")}, + {"a.b.[+]", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]")}, + {"a.b[-12]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, + {"a.b.0", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, + {"a.b.-12", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, {"a", append(make([]interface{}, 0), "a")}, - {"\"a.b\".c", append(make([]interface{}, 0), "a.b", "c")}, - {`b."foo.bar"`, append(make([]interface{}, 0), "b", "foo.bar")}, - {"animals(.==cat)", append(make([]interface{}, 0), "animals", "(", "==", "cat", ")")}, // TODO validate this dot is not a join? - {"animals(.==c*)", append(make([]interface{}, 0), "animals", "(", "==", "c*", ")")}, // TODO validate this dot is not a join? - {"[1].a.d", append(make([]interface{}, 0), int64(1), "a", "d")}, - {"a[0].c", append(make([]interface{}, 0), "a", int64(0), "c")}, + {"\"a.b\".c", append(make([]interface{}, 0), "a.b", ".", "c")}, + {`b."foo.bar"`, append(make([]interface{}, 0), "b", ".", "foo.bar")}, + {"animals(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", ".==", "cat", ")")}, + {"animals.(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", ".==", "cat", ")")}, + {"animals(. == cat)", append(make([]interface{}, 0), "animals", ".", "(", ". == ", "cat", ")")}, + {"animals(.==c*)", append(make([]interface{}, 0), "animals", ".", "(", ".==", "c*", ")")}, + {"animals(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, + {"animals.(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, + {"(a.b==c*).animals", append(make([]interface{}, 0), "(", "a", ".", "b", "==", "c*", ")", ".", "animals")}, + {"(a.b==c*)animals", append(make([]interface{}, 0), "(", "a", ".", "b", "==", "c*", ")", ".", "animals")}, + {"[1].a.d", append(make([]interface{}, 0), int64(1), ".", "a", ".", "d")}, + {"[1]a.d", append(make([]interface{}, 0), int64(1), ".", "a", ".", "d")}, + {"a[0]c", append(make([]interface{}, 0), "a", ".", int64(0), ".", "c")}, + {"a.[0].c", append(make([]interface{}, 0), "a", ".", int64(0), ".", "c")}, {"[0]", append(make([]interface{}, 0), int64(0))}, - {"a.cool(s.d.f==cool)", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", ")")}, - {"a.cool(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", "OR", "t", "b", "h", "==", "frog", ")", "caterpillar")}, - {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", "and", "t", "b", "h", "==", "frog", ")", "*")}, - {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", "cool", "(", "s", "d", "f", "==", "cool", "and", "t", "b", "h", "==", "frog", ")", "th*")}, + {"0", append(make([]interface{}, 0), int64(0))}, + {"a.cool(s.d.f == cool)", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", " == ", "cool", ")")}, + {"a.cool.(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "OR", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "caterpillar")}, + {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "and", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "*")}, + {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "and", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "th*")}, } var tokeniser = NewPathTokeniser() From c2159d98610256aac198895035a6860091423fe2 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 24 Sep 2020 13:20:02 +1000 Subject: [PATCH 008/129] postfix with traverse op --- pkg/yqlib/path_postfix.go | 42 +++++++++++----------- pkg/yqlib/path_postfix_test.go | 64 +++++++++++++++++++++------------- pkg/yqlib/path_tokeniser.go | 2 -- 3 files changed, 61 insertions(+), 47 deletions(-) diff --git a/pkg/yqlib/path_postfix.go b/pkg/yqlib/path_postfix.go index 9a036c62..50eddd03 100644 --- a/pkg/yqlib/path_postfix.go +++ b/pkg/yqlib/path_postfix.go @@ -21,31 +21,28 @@ type OperationType uint32 const ( None OperationType = 1 << iota + Traverse Or And Equals + EqualsSelf ) type PathElement struct { PathElementType PathElementType OperationType OperationType Value interface{} - ChildElements [][]*PathElement Finished bool } // debugging purposes only func (p *PathElement) toString() string { - var result string = `Type: ` + var result string = `` switch p.PathElementType { case PathKey: - result = result + fmt.Sprintf("PathKey - %v", p.Value) - for _, next := range p.ChildElements[0] { - result = result + fmt.Sprintf(".%v", next.Value) - } - result = result + "\n" + result = result + fmt.Sprintf("PathKey - '%v'\n", p.Value) case ArrayIndex: - result = result + fmt.Sprintf("ArrayIndex - %v\n", p.Value) + result = result + fmt.Sprintf("ArrayIndex - '%v'\n", p.Value) case Operation: result = result + "Operation - " switch p.OperationType { @@ -55,7 +52,12 @@ func (p *PathElement) toString() string { result = result + "AND\n" case Equals: result = result + "EQUALS\n" + case EqualsSelf: + result = result + "EQUALS SELF\n" + case Traverse: + result = result + "TRAVERSE\n" } + } return result } @@ -76,11 +78,16 @@ func initMaps() { precedenceMap[TokenIds["EQUALS_OPERATOR"]] = 30 operationTypeMapper[TokenIds["EQUALS_OPERATOR"]] = Equals + + precedenceMap[TokenIds["EQUALS_SELF_OPERATOR"]] = 30 + operationTypeMapper[TokenIds["EQUALS_SELF_OPERATOR"]] = EqualsSelf + + precedenceMap[TokenIds["TRAVERSE_OPERATOR"]] = 40 + operationTypeMapper[TokenIds["TRAVERSE_OPERATOR"]] = Traverse } func createOperationPathElement(opToken *lex.Token) PathElement { - var childElements = make([][]*PathElement, 2) - var pathElement = PathElement{PathElementType: Operation, OperationType: operationTypeMapper[opToken.Type], ChildElements: childElements} + var pathElement = PathElement{PathElementType: Operation, OperationType: operationTypeMapper[opToken.Type]} return pathElement } @@ -119,22 +126,15 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme for _, token := range tokens { switch token.Type { case TokenIds["PATH_KEY"]: // handle splats and array appends here too - var emptyArray = [][]*PathElement{make([]*PathElement, 0)} - var pathElement = PathElement{PathElementType: PathKey, Value: token.Value, ChildElements: emptyArray} - - if len(result) > 0 && result[len(result)-1].PathElementType == PathKey && !result[len(result)-1].Finished { - var lastElement = result[len(result)-1] - lastElement.ChildElements[0] = append(lastElement.ChildElements[0], &pathElement) - } else { - result = append(result, &pathElement) - } + var pathElement = PathElement{PathElementType: PathKey, Value: token.Value} + result = append(result, &pathElement) case TokenIds["("]: opStack = append(opStack, token) finishPathKey(result) - case TokenIds["OR_OPERATOR"], TokenIds["AND_OPERATOR"], TokenIds["EQUALS_OPERATOR"]: + case TokenIds["OR_OPERATOR"], TokenIds["AND_OPERATOR"], TokenIds["EQUALS_OPERATOR"], TokenIds["EQUALS_SELF_OPERATOR"], TokenIds["TRAVERSE_OPERATOR"]: var currentPrecedence = precedenceMap[token.Type] // pop off higher precedent operators onto the result - for len(opStack) > 0 && precedenceMap[opStack[len(opStack)-1].Type] > currentPrecedence { + for len(opStack) > 0 && precedenceMap[opStack[len(opStack)-1].Type] >= currentPrecedence { opStack, result = popOpToResult(opStack, result) } // add this operator to the opStack diff --git a/pkg/yqlib/path_postfix_test.go b/pkg/yqlib/path_postfix_test.go index d3c6d729..39ab7b7a 100644 --- a/pkg/yqlib/path_postfix_test.go +++ b/pkg/yqlib/path_postfix_test.go @@ -25,9 +25,9 @@ func testExpression(expression string) (string, error) { return formatted, nil } -func TestPostFixSimple(t *testing.T) { +func TestPostFixSimpleExample(t *testing.T) { var infix = "a" - var expectedOutput = `Type: PathKey - a + var expectedOutput = `PathKey - 'a' -------- ` @@ -39,9 +39,17 @@ func TestPostFixSimple(t *testing.T) { test.AssertResultComplex(t, expectedOutput, actual) } -func TestPostFixSimplePath(t *testing.T) { +func TestPostFixSimplePathExample(t *testing.T) { var infix = "apples.bananas*.cat" - var expectedOutput = `Type: PathKey - apples.bananas*.cat + var expectedOutput = `PathKey - 'apples' +-------- +PathKey - 'bananas*' +-------- +Operation - TRAVERSE +-------- +PathKey - 'cat' +-------- +Operation - TRAVERSE -------- ` @@ -53,13 +61,13 @@ func TestPostFixSimplePath(t *testing.T) { test.AssertResultComplex(t, expectedOutput, actual) } -func TestPostFixOr(t *testing.T) { +func TestPostFixOrExample(t *testing.T) { var infix = "a OR b" - var expectedOutput = `Type: PathKey - a + var expectedOutput = `PathKey - 'a' -------- -Type: PathKey - b +PathKey - 'b' -------- -Type: Operation - OR +Operation - OR -------- ` @@ -71,21 +79,21 @@ Type: Operation - OR test.AssertResultComplex(t, expectedOutput, actual) } -func TestPostFixOrWithEquals(t *testing.T) { +func TestPostFixOrWithEqualsExample(t *testing.T) { var infix = "a==thing OR b==thongs" - var expectedOutput = `Type: PathKey - a + var expectedOutput = `PathKey - 'a' -------- -Type: PathKey - thing +PathKey - 'thing' -------- -Type: Operation - EQUALS +Operation - EQUALS -------- -Type: PathKey - b +PathKey - 'b' -------- -Type: PathKey - thongs +PathKey - 'thongs' -------- -Type: Operation - EQUALS +Operation - EQUALS -------- -Type: Operation - OR +Operation - OR -------- ` @@ -97,21 +105,29 @@ Type: Operation - OR test.AssertResultComplex(t, expectedOutput, actual) } -func TestPostFixOrWithEqualsPath(t *testing.T) { +func TestPostFixOrWithEqualsPathExample(t *testing.T) { var infix = "apples.monkeys==thing OR bogs.bobos==thongs" - var expectedOutput = `Type: PathKey - apples.monkeys + var expectedOutput = `PathKey - 'apples' -------- -Type: PathKey - thing +PathKey - 'monkeys' -------- -Type: Operation - EQUALS +Operation - TRAVERSE -------- -Type: PathKey - bogs.bobos +PathKey - 'thing' -------- -Type: PathKey - thongs +Operation - EQUALS -------- -Type: Operation - EQUALS +PathKey - 'bogs' -------- -Type: Operation - OR +PathKey - 'bobos' +-------- +Operation - TRAVERSE +-------- +PathKey - 'thongs' +-------- +Operation - EQUALS +-------- +Operation - OR -------- ` diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index ab2da6cd..d7e22932 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -25,8 +25,6 @@ func initTokens() { ")", } Tokens = []string{ - "BEGIN_SUB_EXPRESSION", - "END_SUB_EXPRESSION", "OR_OPERATOR", "AND_OPERATOR", "EQUALS_OPERATOR", From 5ee52f950646f08e4363957d7862b4b390930ec3 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 24 Sep 2020 13:28:47 +1000 Subject: [PATCH 009/129] wip --- pkg/yqlib/path_postfix.go | 2 +- pkg/yqlib/path_postfix_test.go | 88 ++++++++++++++++++++++++++++++++ pkg/yqlib/path_tokeniser.go | 26 +++++----- pkg/yqlib/path_tokeniser_test.go | 1 + 4 files changed, 102 insertions(+), 15 deletions(-) diff --git a/pkg/yqlib/path_postfix.go b/pkg/yqlib/path_postfix.go index 50eddd03..631076b1 100644 --- a/pkg/yqlib/path_postfix.go +++ b/pkg/yqlib/path_postfix.go @@ -125,7 +125,7 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme for _, token := range tokens { switch token.Type { - case TokenIds["PATH_KEY"]: // handle splats and array appends here too + case TokenIds["PATH_KEY"], TokenIds["ARRAY_INDEX"], TokenIds["[+]"], TokenIds["[*]"], TokenIds["**"]: var pathElement = PathElement{PathElementType: PathKey, Value: token.Value} result = append(result, &pathElement) case TokenIds["("]: diff --git a/pkg/yqlib/path_postfix_test.go b/pkg/yqlib/path_postfix_test.go index 39ab7b7a..60ba9060 100644 --- a/pkg/yqlib/path_postfix_test.go +++ b/pkg/yqlib/path_postfix_test.go @@ -61,6 +61,94 @@ Operation - TRAVERSE test.AssertResultComplex(t, expectedOutput, actual) } +func TestPostFixSimplePathNumbersExample(t *testing.T) { + var infix = "apples[0].cat" + var expectedOutput = `PathKey - 'apples' +-------- +PathKey - '0' +-------- +Operation - TRAVERSE +-------- +PathKey - 'cat' +-------- +Operation - TRAVERSE +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + +func TestPostFixSimplePathAppendArrayExample(t *testing.T) { + var infix = "apples[+].cat" + var expectedOutput = `PathKey - 'apples' +-------- +PathKey - '[+]' +-------- +Operation - TRAVERSE +-------- +PathKey - 'cat' +-------- +Operation - TRAVERSE +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + +func TestPostFixSimplePathSplatArrayExample(t *testing.T) { + var infix = "apples.[*]cat" + var expectedOutput = `PathKey - 'apples' +-------- +PathKey - '[*]' +-------- +Operation - TRAVERSE +-------- +PathKey - 'cat' +-------- +Operation - TRAVERSE +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + +func TestPostFixDeepMatchExample(t *testing.T) { + var infix = "apples.**.cat" + var expectedOutput = `PathKey - 'apples' +-------- +PathKey - '**' +-------- +Operation - TRAVERSE +-------- +PathKey - 'cat' +-------- +Operation - TRAVERSE +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + func TestPostFixOrExample(t *testing.T) { var infix = "a OR b" var expectedOutput = `PathKey - 'a' diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index d7e22932..cbd6e5e2 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -8,22 +8,20 @@ import ( "github.com/timtadh/lexmachine/machines" ) -var Literals []string // The tokens representing literal strings -var ClosingLiterals []string // The tokens representing literal strings -var Keywords []string // The keyword tokens -var Tokens []string // All of the tokens (including literals and keywords) -var TokenIds map[string]int // A map from the token names to their int ids +var Literals []string // The tokens representing literal strings +var Keywords []string // The keyword tokens +var Tokens []string // All of the tokens (including literals and keywords) +var TokenIds map[string]int // A map from the token names to their int ids + +var bracketLiterals []string func initTokens() { + bracketLiterals = []string{"(", ")"} Literals = []string{ // these need a traverse operator infront - "(", "[+]", "[*]", "**", } - ClosingLiterals = []string{ // these need a traverse operator after - ")", - } Tokens = []string{ "OR_OPERATOR", "AND_OPERATOR", @@ -33,8 +31,8 @@ func initTokens() { "PATH_KEY", // apples "ARRAY_INDEX", // 123 } + Tokens = append(Tokens, bracketLiterals...) Tokens = append(Tokens, Literals...) - Tokens = append(Tokens, ClosingLiterals...) TokenIds = make(map[string]int) for i, tok := range Tokens { TokenIds[tok] = i @@ -80,11 +78,11 @@ func numberToken(name string, wrapped bool) lex.Action { // Creates the lexer object and compiles the NFA. func initLexer() (*lex.Lexer, error) { lexer := lex.NewLexer() - for _, lit := range Literals { + for _, lit := range bracketLiterals { r := "\\" + strings.Join(strings.Split(lit, ""), "\\") lexer.Add([]byte(r), token(lit)) } - for _, lit := range ClosingLiterals { + for _, lit := range Literals { r := "\\" + strings.Join(strings.Split(lit, ""), "\\") lexer.Add([]byte(r), token(lit)) } @@ -143,14 +141,14 @@ func (p *pathTokeniser) Tokenise(path string) ([]*lex.Token, error) { var postProcessedTokens []*lex.Token = make([]*lex.Token, 0) for index, token := range tokens { - for _, literalTokenDef := range append(Literals, "ARRAY_INDEX") { + for _, literalTokenDef := range append(Literals, "ARRAY_INDEX", "(") { if index > 0 && token.Type == TokenIds[literalTokenDef] && tokens[index-1].Type != TokenIds["TRAVERSE_OPERATOR"] { postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) } } postProcessedTokens = append(postProcessedTokens, token) - for _, literalTokenDef := range append(ClosingLiterals, "ARRAY_INDEX") { + for _, literalTokenDef := range append(Literals, "ARRAY_INDEX", ")") { if index != len(tokens)-1 && token.Type == TokenIds[literalTokenDef] && tokens[index+1].Type != TokenIds["TRAVERSE_OPERATOR"] { postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) } diff --git a/pkg/yqlib/path_tokeniser_test.go b/pkg/yqlib/path_tokeniser_test.go index d5b24811..31b6379e 100644 --- a/pkg/yqlib/path_tokeniser_test.go +++ b/pkg/yqlib/path_tokeniser_test.go @@ -43,6 +43,7 @@ var tokeniserTests = []struct { {"a.[0].c", append(make([]interface{}, 0), "a", ".", int64(0), ".", "c")}, {"[0]", append(make([]interface{}, 0), int64(0))}, {"0", append(make([]interface{}, 0), int64(0))}, + {"a.b[+]c", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]", ".", "c")}, {"a.cool(s.d.f == cool)", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", " == ", "cool", ")")}, {"a.cool.(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "OR", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "caterpillar")}, {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "and", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "*")}, From f7d46958376fa7bcedbb7f3dc101c258ce8812c4 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 25 Sep 2020 16:23:33 +1000 Subject: [PATCH 010/129] binary tree ftw --- pkg/yqlib/data_tree_navigator.go | 86 +++++++++++++++++++++++++------- pkg/yqlib/path_postfix.go | 12 ----- pkg/yqlib/path_tree.go | 54 ++++++++++++-------- 3 files changed, 101 insertions(+), 51 deletions(-) diff --git a/pkg/yqlib/data_tree_navigator.go b/pkg/yqlib/data_tree_navigator.go index 203a45e1..b67ea409 100644 --- a/pkg/yqlib/data_tree_navigator.go +++ b/pkg/yqlib/data_tree_navigator.go @@ -1,23 +1,73 @@ package yqlib -// import yaml "gopkg.in/yaml.v3" +type dataTreeNavigator struct { +} -// type NodeLeafContext struct { -// Node *yaml.Node -// Head interface{} -// PathStack []interface{} -// } +type DataTreeNavigator interface { + GetMatchingNodes(matchingNodes []*NodeContext, pathNode *PathTreeNode) ([]*NodeContext, error) +} -// func newNodeLeafContext(node *yaml.Node, head interface{}, tailpathStack []interface{}) NodeLeafContext { -// newPathStack := make([]interface{}, len(pathStack)) -// copy(newPathStack, pathStack) -// return NodeContext{ -// Node: node, -// Head: head, -// PathStack: newPathStack, -// } -// } +func NewTreeNavigator() DataTreeNavigator { + return &dataTreeNavigator{} +} -// type DataTreeNavigator interface { -// Traverse(value *NodeLeafContext) -// } +func (d *dataTreeNavigator) traverseSingle(matchingNode *NodeContext, pathNode *PathElement) ([]*NodeContext, error) { + var value = matchingNode.Node + // match all for splat + // match all and recurse for deep + // etc and so forth + +} + +func (d *dataTreeNavigator) traverse(matchingNodes []*NodeContext, pathNode *PathElement) ([]*NodeContext, error) { + var newMatchingNodes = make([]*NodeContext, 0) + var newNodes []*NodeContext + var err error + for _, node := range matchingNodes { + + newNodes, err = d.traverseSingle(node, pathNode) + if err != nil { + return nil, err + } + newMatchingNodes = append(newMatchingNodes, newNodes...) + } + + return newMatchingNodes, nil +} + +func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*NodeContext, pathNode *PathTreeNode) ([]*NodeContext, error) { + if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { + return d.traverse(matchingNodes, pathNode.PathElement) + } else { + var lhs, rhs []*NodeContext + var err error + switch pathNode.PathElement.OperationType { + case Traverse: + lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + return d.GetMatchingNodes(lhs, pathNode.Rhs) + case Or, And: + lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + return d.setFunction(pathNode.PathElement, lhs, rhs), nil + case Equals: + lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + return d.findMatchingValues(lhs, pathNode.Rhs) + case EqualsSelf: + return d.findMatchingValues(matchingNodes, pathNode.Rhs) + } + + } + +} diff --git a/pkg/yqlib/path_postfix.go b/pkg/yqlib/path_postfix.go index 631076b1..30de1290 100644 --- a/pkg/yqlib/path_postfix.go +++ b/pkg/yqlib/path_postfix.go @@ -32,7 +32,6 @@ type PathElement struct { PathElementType PathElementType OperationType OperationType Value interface{} - Finished bool } // debugging purposes only @@ -109,14 +108,6 @@ func popOpToResult(opStack []*lex.Token, result []*PathElement) ([]*lex.Token, [ return opStack, append(result, &pathElement) } -func finishPathKey(result []*PathElement) { - if len(result) > 0 { - //need to mark PathKey elements as finished so we - //stop appending PathKeys as children - result[len(result)-1].Finished = true - } -} - func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathElement, error) { var result []*PathElement // surround the whole thing with quotes @@ -130,7 +121,6 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme result = append(result, &pathElement) case TokenIds["("]: opStack = append(opStack, token) - finishPathKey(result) case TokenIds["OR_OPERATOR"], TokenIds["AND_OPERATOR"], TokenIds["EQUALS_OPERATOR"], TokenIds["EQUALS_SELF_OPERATOR"], TokenIds["TRAVERSE_OPERATOR"]: var currentPrecedence = precedenceMap[token.Type] // pop off higher precedent operators onto the result @@ -139,7 +129,6 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme } // add this operator to the opStack opStack = append(opStack, token) - finishPathKey(result) case TokenIds[")"]: for len(opStack) > 0 && opStack[len(opStack)-1].Type != TokenIds["("] { opStack, result = popOpToResult(opStack, result) @@ -149,7 +138,6 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme } // now we should have ( as the last element on the opStack, get rid of it opStack = opStack[0 : len(opStack)-1] - finishPathKey(result) } } return result, nil diff --git a/pkg/yqlib/path_tree.go b/pkg/yqlib/path_tree.go index cf0b1eaf..070ee89d 100644 --- a/pkg/yqlib/path_tree.go +++ b/pkg/yqlib/path_tree.go @@ -1,27 +1,39 @@ package yqlib -import lex "github.com/timtadh/lexmachine" - -func parseTree(tokens []*lex.Token, currentElement *PathElement, allElements []*PathElement) []*PathElement { - currentToken, remainingTokens := tokens[0], tokens[1:] - - switch currentToken.Type { - case TokenIds["PATH_KEY"]: - currentElement.PathElementType = PathKey - currentElement.OperationType = None - currentElement.Value = currentToken.Value - } - - if len(remainingTokens) == 0 { - return append(allElements, currentElement) - } - return parseTree(remainingTokens, &PathElement{}, append(allElements, currentElement)) +import "fmt" +type PathTreeNode struct { + PathElement *PathElement + Lhs *PathTreeNode + Rhs *PathTreeNode } -func ParseTree(tokens []*lex.Token) []*PathElement { - if len(tokens) == 0 { - return make([]*PathElement, 0) - } - return parseTree(tokens, &PathElement{}, make([]*PathElement, 0)) +type PathTreeCreator interface { + CreatePathTree([]*PathElement) (*PathTreeNode, error) +} + +type pathTreeCreator struct { +} + +func NewPathTreeCreator() PathTreeCreator { + return &pathTreeCreator{} +} + +func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeNode, error) { + var stack = make([]*PathTreeNode, 0) + + for _, pathElement := range postFixPath { + var newNode = PathTreeNode{PathElement: pathElement} + if pathElement.PathElementType == Operation { + remaining, lhs, rhs := stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1] + newNode.Lhs = lhs + newNode.Rhs = rhs + stack = remaining + } + stack = append(stack, &newNode) + } + if len(stack) != 1 { + return nil, fmt.Errorf("expected stack to have 1 thing but its %v", stack) + } + return stack[0], nil } From f479a7e8e3d28fe149dbf565994d531269096cf9 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 10:59:03 +1100 Subject: [PATCH 011/129] wip --- pkg/yqlib/data_tree_navigator.go | 73 ---------- pkg/yqlib/data_tree_navigator_test.go | 1 - pkg/yqlib/path_tree_test.go | 1 - pkg/yqlib/treeops/data_tree_navigator.go | 75 +++++++++++ pkg/yqlib/treeops/data_tree_navigator_test.go | 125 ++++++++++++++++++ pkg/yqlib/treeops/lib.go | 59 +++++++++ pkg/yqlib/treeops/matchKeyString.go | 34 +++++ pkg/yqlib/{ => treeops}/path_postfix.go | 2 +- pkg/yqlib/{ => treeops}/path_postfix_test.go | 2 +- pkg/yqlib/{ => treeops}/path_tokeniser.go | 2 +- .../{ => treeops}/path_tokeniser_test.go | 2 +- pkg/yqlib/{ => treeops}/path_tree.go | 21 ++- pkg/yqlib/treeops/path_tree_test.go | 1 + pkg/yqlib/treeops/traverse.go | 94 +++++++++++++ 14 files changed, 411 insertions(+), 81 deletions(-) delete mode 100644 pkg/yqlib/data_tree_navigator.go delete mode 100644 pkg/yqlib/data_tree_navigator_test.go delete mode 100644 pkg/yqlib/path_tree_test.go create mode 100644 pkg/yqlib/treeops/data_tree_navigator.go create mode 100644 pkg/yqlib/treeops/data_tree_navigator_test.go create mode 100644 pkg/yqlib/treeops/lib.go create mode 100644 pkg/yqlib/treeops/matchKeyString.go rename pkg/yqlib/{ => treeops}/path_postfix.go (99%) rename pkg/yqlib/{ => treeops}/path_postfix_test.go (99%) rename pkg/yqlib/{ => treeops}/path_tokeniser.go (99%) rename pkg/yqlib/{ => treeops}/path_tokeniser_test.go (99%) rename pkg/yqlib/{ => treeops}/path_tree.go (61%) create mode 100644 pkg/yqlib/treeops/path_tree_test.go create mode 100644 pkg/yqlib/treeops/traverse.go diff --git a/pkg/yqlib/data_tree_navigator.go b/pkg/yqlib/data_tree_navigator.go deleted file mode 100644 index b67ea409..00000000 --- a/pkg/yqlib/data_tree_navigator.go +++ /dev/null @@ -1,73 +0,0 @@ -package yqlib - -type dataTreeNavigator struct { -} - -type DataTreeNavigator interface { - GetMatchingNodes(matchingNodes []*NodeContext, pathNode *PathTreeNode) ([]*NodeContext, error) -} - -func NewTreeNavigator() DataTreeNavigator { - return &dataTreeNavigator{} -} - -func (d *dataTreeNavigator) traverseSingle(matchingNode *NodeContext, pathNode *PathElement) ([]*NodeContext, error) { - var value = matchingNode.Node - // match all for splat - // match all and recurse for deep - // etc and so forth - -} - -func (d *dataTreeNavigator) traverse(matchingNodes []*NodeContext, pathNode *PathElement) ([]*NodeContext, error) { - var newMatchingNodes = make([]*NodeContext, 0) - var newNodes []*NodeContext - var err error - for _, node := range matchingNodes { - - newNodes, err = d.traverseSingle(node, pathNode) - if err != nil { - return nil, err - } - newMatchingNodes = append(newMatchingNodes, newNodes...) - } - - return newMatchingNodes, nil -} - -func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*NodeContext, pathNode *PathTreeNode) ([]*NodeContext, error) { - if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { - return d.traverse(matchingNodes, pathNode.PathElement) - } else { - var lhs, rhs []*NodeContext - var err error - switch pathNode.PathElement.OperationType { - case Traverse: - lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - return d.GetMatchingNodes(lhs, pathNode.Rhs) - case Or, And: - lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) - if err != nil { - return nil, err - } - return d.setFunction(pathNode.PathElement, lhs, rhs), nil - case Equals: - lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - return d.findMatchingValues(lhs, pathNode.Rhs) - case EqualsSelf: - return d.findMatchingValues(matchingNodes, pathNode.Rhs) - } - - } - -} diff --git a/pkg/yqlib/data_tree_navigator_test.go b/pkg/yqlib/data_tree_navigator_test.go deleted file mode 100644 index 88c44e97..00000000 --- a/pkg/yqlib/data_tree_navigator_test.go +++ /dev/null @@ -1 +0,0 @@ -package yqlib diff --git a/pkg/yqlib/path_tree_test.go b/pkg/yqlib/path_tree_test.go deleted file mode 100644 index 88c44e97..00000000 --- a/pkg/yqlib/path_tree_test.go +++ /dev/null @@ -1 +0,0 @@ -package yqlib diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go new file mode 100644 index 00000000..1b14490e --- /dev/null +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -0,0 +1,75 @@ +package treeops + +type dataTreeNavigator struct { + traverser Traverser +} + +type NavigationPrefs struct { + FollowAlias bool +} + +type DataTreeNavigator interface { + GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) +} + +func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { + traverse := NewTraverser(navigationPrefs) + return &dataTreeNavigator{traverse} +} + +func (d *dataTreeNavigator) traverse(matchingNodes []*CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { + log.Debugf("-- Traversing") + var newMatchingNodes = make([]*CandidateNode, 0) + var newNodes []*CandidateNode + var err error + for _, node := range matchingNodes { + + newNodes, err = d.traverser.Traverse(node, pathNode) + if err != nil { + return nil, err + } + newMatchingNodes = append(newMatchingNodes, newNodes...) + } + + return newMatchingNodes, nil +} + +func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { + log.Debugf("Processing Path: %v", pathNode.PathElement.toString()) + if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { + return d.traverse(matchingNodes, pathNode.PathElement) + } else { + var lhs []*CandidateNode //, rhs + var err error + switch pathNode.PathElement.OperationType { + case Traverse: + lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + return d.GetMatchingNodes(lhs, pathNode.Rhs) + // case Or, And: + // lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + // if err != nil { + // return nil, err + // } + // rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + // if err != nil { + // return nil, err + // } + // return d.setFunction(pathNode.PathElement, lhs, rhs), nil + // case Equals: + // lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + // if err != nil { + // return nil, err + // } + // return d.findMatchingValues(lhs, pathNode.Rhs) + // case EqualsSelf: + // return d.findMatchingValues(matchingNodes, pathNode.Rhs) + default: + return nil, nil + } + + } + +} diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go new file mode 100644 index 00000000..0a377379 --- /dev/null +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -0,0 +1,125 @@ +package treeops + +import ( + "strings" + "testing" + + "github.com/mikefarah/yq/v3/test" + yaml "gopkg.in/yaml.v3" +) + +var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) +var treeCreator = NewPathTreeCreator() + +func readDoc(t *testing.T, content string) []*CandidateNode { + decoder := yaml.NewDecoder(strings.NewReader(content)) + var dataBucket yaml.Node + err := decoder.Decode(&dataBucket) + if err != nil { + t.Error(err) + } + return []*CandidateNode{&CandidateNode{Node: &dataBucket, Document: 0}} +} + +func resultsToString(results []*CandidateNode) string { + var pretty string = "" + for _, n := range results { + pretty = pretty + "\n" + NodeToString(n) + } + return pretty +} + +func TestDataTreeNavigatorSimple(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple`) + + path, errPath := treeCreator.ParsePath("a") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!map, Kind: MappingNode, Anchor: + b: apple +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorSimpleDeep(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple`) + + path, errPath := treeCreator.ParsePath("a.b") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a b] + Tag: !!str, Kind: ScalarNode, Anchor: + apple +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorSimpleMismatch(t *testing.T) { + + nodes := readDoc(t, `a: + c: apple`) + + path, errPath := treeCreator.ParsePath("a.b") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := `` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorWild(t *testing.T) { + + nodes := readDoc(t, `a: + cat: apple`) + + path, errPath := treeCreator.ParsePath("a.*a*") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a cat] + Tag: !!str, Kind: ScalarNode, Anchor: + apple +` + + test.AssertResult(t, expected, resultsToString(results)) +} diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go new file mode 100644 index 00000000..b175dc09 --- /dev/null +++ b/pkg/yqlib/treeops/lib.go @@ -0,0 +1,59 @@ +package treeops + +import ( + "bytes" + "fmt" + + "gopkg.in/op/go-logging.v1" + "gopkg.in/yaml.v3" +) + +type CandidateNode struct { + Node *yaml.Node // the actual node + Path []interface{} /// the path we took to get to this node + Document uint // the document index of this node + + // middle nodes are nodes that match along the original path, but not a + // target match of the path. This is only relevant when ShouldOnlyDeeplyVisitLeaves is false. + IsMiddleNode bool +} + +var log = logging.MustGetLogger("yq-treeops") + +func NodeToString(node *CandidateNode) string { + if !log.IsEnabledFor(logging.DEBUG) { + return "" + } + value := node.Node + if value == nil { + return "-- node is nil --" + } + buf := new(bytes.Buffer) + encoder := yaml.NewEncoder(buf) + errorEncoding := encoder.Encode(value) + if errorEncoding != nil { + log.Error("Error debugging node, %v", errorEncoding.Error()) + } + encoder.Close() + return fmt.Sprintf(`-- Node -- + Document %v, path: %v + Tag: %v, Kind: %v, Anchor: %v + %v`, node.Document, node.Path, value.Tag, KindString(value.Kind), value.Anchor, buf.String()) +} + +func KindString(kind yaml.Kind) string { + switch kind { + case yaml.ScalarNode: + return "ScalarNode" + case yaml.SequenceNode: + return "SequenceNode" + case yaml.MappingNode: + return "MappingNode" + case yaml.DocumentNode: + return "DocumentNode" + case yaml.AliasNode: + return "AliasNode" + default: + return "unknown!" + } +} diff --git a/pkg/yqlib/treeops/matchKeyString.go b/pkg/yqlib/treeops/matchKeyString.go new file mode 100644 index 00000000..34714fd5 --- /dev/null +++ b/pkg/yqlib/treeops/matchKeyString.go @@ -0,0 +1,34 @@ +package treeops + +func Match(name string, pattern string) (matched bool) { + if pattern == "" { + return name == pattern + } + log.Debug("pattern: %v", pattern) + if pattern == "*" { + log.Debug("wild!") + return true + } + return deepMatch([]rune(name), []rune(pattern)) +} + +func deepMatch(str, pattern []rune) bool { + for len(pattern) > 0 { + switch pattern[0] { + default: + if len(str) == 0 || str[0] != pattern[0] { + return false + } + case '?': + if len(str) == 0 { + return false + } + case '*': + return deepMatch(str, pattern[1:]) || + (len(str) > 0 && deepMatch(str[1:], pattern)) + } + str = str[1:] + pattern = pattern[1:] + } + return len(str) == 0 && len(pattern) == 0 +} diff --git a/pkg/yqlib/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go similarity index 99% rename from pkg/yqlib/path_postfix.go rename to pkg/yqlib/treeops/path_postfix.go index 30de1290..3dd4af69 100644 --- a/pkg/yqlib/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -1,4 +1,4 @@ -package yqlib +package treeops import ( "errors" diff --git a/pkg/yqlib/path_postfix_test.go b/pkg/yqlib/treeops/path_postfix_test.go similarity index 99% rename from pkg/yqlib/path_postfix_test.go rename to pkg/yqlib/treeops/path_postfix_test.go index 60ba9060..2e9d6361 100644 --- a/pkg/yqlib/path_postfix_test.go +++ b/pkg/yqlib/treeops/path_postfix_test.go @@ -1,4 +1,4 @@ -package yqlib +package treeops import ( "testing" diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go similarity index 99% rename from pkg/yqlib/path_tokeniser.go rename to pkg/yqlib/treeops/path_tokeniser.go index cbd6e5e2..acabe798 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -1,4 +1,4 @@ -package yqlib +package treeops import ( "strconv" diff --git a/pkg/yqlib/path_tokeniser_test.go b/pkg/yqlib/treeops/path_tokeniser_test.go similarity index 99% rename from pkg/yqlib/path_tokeniser_test.go rename to pkg/yqlib/treeops/path_tokeniser_test.go index 31b6379e..4e7405ef 100644 --- a/pkg/yqlib/path_tokeniser_test.go +++ b/pkg/yqlib/treeops/path_tokeniser_test.go @@ -1,4 +1,4 @@ -package yqlib +package treeops import ( "testing" diff --git a/pkg/yqlib/path_tree.go b/pkg/yqlib/treeops/path_tree.go similarity index 61% rename from pkg/yqlib/path_tree.go rename to pkg/yqlib/treeops/path_tree.go index 070ee89d..a42b06f5 100644 --- a/pkg/yqlib/path_tree.go +++ b/pkg/yqlib/treeops/path_tree.go @@ -1,7 +1,10 @@ -package yqlib +package treeops import "fmt" +var myPathTokeniser = NewPathTokeniser() +var myPathPostfixer = NewPathPostFixer() + type PathTreeNode struct { PathElement *PathElement Lhs *PathTreeNode @@ -9,7 +12,8 @@ type PathTreeNode struct { } type PathTreeCreator interface { - CreatePathTree([]*PathElement) (*PathTreeNode, error) + ParsePath(path string) (*PathTreeNode, error) + CreatePathTree(postFixPath []*PathElement) (*PathTreeNode, error) } type pathTreeCreator struct { @@ -19,6 +23,19 @@ func NewPathTreeCreator() PathTreeCreator { return &pathTreeCreator{} } +func (p *pathTreeCreator) ParsePath(path string) (*PathTreeNode, error) { + tokens, err := myPathTokeniser.Tokenise(path) + if err != nil { + return nil, err + } + var pathElements []*PathElement + pathElements, err = myPathPostfixer.ConvertToPostfix(tokens) + if err != nil { + return nil, err + } + return p.CreatePathTree(pathElements) +} + func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeNode, error) { var stack = make([]*PathTreeNode, 0) diff --git a/pkg/yqlib/treeops/path_tree_test.go b/pkg/yqlib/treeops/path_tree_test.go new file mode 100644 index 00000000..005dee7e --- /dev/null +++ b/pkg/yqlib/treeops/path_tree_test.go @@ -0,0 +1 @@ +package treeops diff --git a/pkg/yqlib/treeops/traverse.go b/pkg/yqlib/treeops/traverse.go new file mode 100644 index 00000000..df9928c5 --- /dev/null +++ b/pkg/yqlib/treeops/traverse.go @@ -0,0 +1,94 @@ +package treeops + +import ( + "fmt" + + "gopkg.in/yaml.v3" +) + +type traverser struct { + prefs NavigationPrefs +} + +type Traverser interface { + Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) +} + +func NewTraverser(navigationPrefs NavigationPrefs) Traverser { + return &traverser{navigationPrefs} +} + +func (t *traverser) keyMatches(key *yaml.Node, pathNode *PathElement) bool { + return Match(key.Value, fmt.Sprintf("%v", pathNode.Value)) +} + +func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { + // value.Content is a concatenated array of key, value, + // so keys are in the even indexes, values in odd. + // merge aliases are defined first, but we only want to traverse them + // if we don't find a match directly on this node first. + //TODO ALIASES, auto creation? + + var newMatches = make([]*CandidateNode, 0) + + node := candidate.Node + + var contents = node.Content + for index := 0; index < len(contents); index = index + 2 { + key := contents[index] + value := contents[index+1] + + log.Debug("checking %v (%v)", key.Value, key.Tag) + if t.keyMatches(key, pathNode) { + log.Debug("MATCHED") + newMatches = append(newMatches, &CandidateNode{ + Node: value, + Path: append(candidate.Path, key.Value), + Document: candidate.Document, + }) + } + } + return newMatches, nil + +} + +func (t *traverser) Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { + log.Debug(NodeToString(matchingNode)) + value := matchingNode.Node + switch value.Kind { + case yaml.MappingNode: + log.Debug("its a map with %v entries", len(value.Content)/2) + return t.traverseMap(matchingNode, pathNode) + + // case yaml.SequenceNode: + // log.Debug("its a sequence of %v things!", len(value.Content)) + + // switch head := head.(type) { + // case int64: + // return n.recurseArray(value, head, head, tail, pathStack) + // default: + + // if head == "+" { + // return n.appendArray(value, head, tail, pathStack) + // } else if len(value.Content) == 0 && head == "**" { + // return n.navigationStrategy.Visit(nodeContext) + // } + // return n.splatArray(value, head, tail, pathStack) + // } + // case yaml.AliasNode: + // log.Debug("its an alias!") + // DebugNode(value.Alias) + // if n.navigationStrategy.FollowAlias(nodeContext) { + // log.Debug("following the alias") + // return n.recurse(value.Alias, head, tail, pathStack) + // } + // return nil + case yaml.DocumentNode: + log.Debug("digging into doc node") + return t.Traverse(&CandidateNode{ + Node: matchingNode.Node.Content[0], + Document: matchingNode.Document}, pathNode) + default: + return nil, nil + } +} From f95226e2671b9f02dab72ad8e3dd64a853e6c4ee Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 11:10:37 +1100 Subject: [PATCH 012/129] ops work in theory! --- pkg/yqlib/treeops/data_tree_navigator.go | 26 +++-- pkg/yqlib/treeops/data_tree_navigator_test.go | 102 +++++++++++++++++- 2 files changed, 116 insertions(+), 12 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 1b14490e..4bfa873a 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -34,12 +34,16 @@ func (d *dataTreeNavigator) traverse(matchingNodes []*CandidateNode, pathNode *P return newMatchingNodes, nil } +func (d *dataTreeNavigator) setFunction(op OperationType, lhs []*CandidateNode, rhs []*CandidateNode) ([]*CandidateNode, error) { + return append(lhs, rhs...), nil +} + func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { log.Debugf("Processing Path: %v", pathNode.PathElement.toString()) if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { return d.traverse(matchingNodes, pathNode.PathElement) } else { - var lhs []*CandidateNode //, rhs + var lhs, rhs []*CandidateNode var err error switch pathNode.PathElement.OperationType { case Traverse: @@ -48,16 +52,16 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat return nil, err } return d.GetMatchingNodes(lhs, pathNode.Rhs) - // case Or, And: - // lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) - // if err != nil { - // return nil, err - // } - // rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) - // if err != nil { - // return nil, err - // } - // return d.setFunction(pathNode.PathElement, lhs, rhs), nil + case Or, And: + lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + return d.setFunction(pathNode.PathElement.OperationType, lhs, rhs) // case Equals: // lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) // if err != nil { diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 0a377379..30742de6 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -102,7 +102,8 @@ func TestDataTreeNavigatorSimpleMismatch(t *testing.T) { func TestDataTreeNavigatorWild(t *testing.T) { nodes := readDoc(t, `a: - cat: apple`) + cat: apple + mad: things`) path, errPath := treeCreator.ParsePath("a.*a*") if errPath != nil { @@ -119,7 +120,106 @@ func TestDataTreeNavigatorWild(t *testing.T) { Document 0, path: [a cat] Tag: !!str, Kind: ScalarNode, Anchor: apple + +-- Node -- + Document 0, path: [a mad] + Tag: !!str, Kind: ScalarNode, Anchor: + things ` test.AssertResult(t, expected, resultsToString(results)) } + +func TestDataTreeNavigatorWildDeepish(t *testing.T) { + + nodes := readDoc(t, `a: + cat: {b: 3} + mad: {b: 4} + fad: {c: t}`) + + path, errPath := treeCreator.ParsePath("a.*a*.b") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a cat b] + Tag: !!int, Kind: ScalarNode, Anchor: + 3 + +-- Node -- + Document 0, path: [a mad b] + Tag: !!int, Kind: ScalarNode, Anchor: + 4 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorOrSimple(t *testing.T) { + + nodes := readDoc(t, `a: + cat: apple + mad: things`) + + path, errPath := treeCreator.ParsePath("a.(cat or mad)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a cat] + Tag: !!str, Kind: ScalarNode, Anchor: + apple + +-- Node -- + Document 0, path: [a mad] + Tag: !!str, Kind: ScalarNode, Anchor: + things +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorOrSimpleWithDepth(t *testing.T) { + + nodes := readDoc(t, `a: + cat: {b: 3} + mad: {b: 4} + fad: {c: t}`) + + path, errPath := treeCreator.ParsePath("a.(cat.* or fad.*)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a cat b] + Tag: !!int, Kind: ScalarNode, Anchor: + 3 + +-- Node -- + Document 0, path: [a fad c] + Tag: !!str, Kind: ScalarNode, Anchor: + t +` + test.AssertResult(t, expected, resultsToString(results)) +} From c09513803a355dd7333d0b58bfa3d8640a14dd16 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 11:37:47 +1100 Subject: [PATCH 013/129] wip --- pkg/yqlib/treeops/data_tree_navigator.go | 7 ++--- pkg/yqlib/treeops/data_tree_navigator_test.go | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 4bfa873a..bee49863 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -34,8 +34,9 @@ func (d *dataTreeNavigator) traverse(matchingNodes []*CandidateNode, pathNode *P return newMatchingNodes, nil } -func (d *dataTreeNavigator) setFunction(op OperationType, lhs []*CandidateNode, rhs []*CandidateNode) ([]*CandidateNode, error) { - return append(lhs, rhs...), nil +func (d *dataTreeNavigator) setFunction(op OperationType, lhs []*CandidateNode, rhs []*CandidateNode) []*CandidateNode { + + return append(lhs, rhs...) } func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { @@ -61,7 +62,7 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat if err != nil { return nil, err } - return d.setFunction(pathNode.PathElement.OperationType, lhs, rhs) + return d.setFunction(pathNode.PathElement.OperationType, lhs, rhs), nil // case Equals: // lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) // if err != nil { diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 30742de6..73414224 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -223,3 +223,29 @@ func TestDataTreeNavigatorOrSimpleWithDepth(t *testing.T) { ` test.AssertResult(t, expected, resultsToString(results)) } + +func TestDataTreeNavigatorOrDeDupes(t *testing.T) { + + nodes := readDoc(t, `a: + cat: apple + mad: things`) + + path, errPath := treeCreator.ParsePath("a.(cat or cat)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a cat] + Tag: !!str, Kind: ScalarNode, Anchor: + apple +` + + test.AssertResult(t, expected, resultsToString(results)) +} From a0d940638c3ab94cfab1563cb1bb1ec082e8c82e Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 12:04:19 +1100 Subject: [PATCH 014/129] use orderermap --- pkg/yqlib/treeops/data_tree_navigator.go | 59 ++++++++++++++++++------ pkg/yqlib/treeops/lib.go | 4 ++ 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index bee49863..057f564f 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -1,5 +1,9 @@ package treeops +import ( + "github.com/elliotchance/orderedmap" +) + type dataTreeNavigator struct { traverser Traverser } @@ -17,54 +21,81 @@ func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { return &dataTreeNavigator{traverse} } -func (d *dataTreeNavigator) traverse(matchingNodes []*CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { +func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *PathElement) (*orderedmap.OrderedMap, error) { log.Debugf("-- Traversing") - var newMatchingNodes = make([]*CandidateNode, 0) + var matchingNodeMap = orderedmap.NewOrderedMap() var newNodes []*CandidateNode var err error - for _, node := range matchingNodes { - newNodes, err = d.traverser.Traverse(node, pathNode) + for el := matchMap.Front(); el != nil; el = el.Next() { + newNodes, err = d.traverser.Traverse(el.Value.(*CandidateNode), pathNode) if err != nil { return nil, err } - newMatchingNodes = append(newMatchingNodes, newNodes...) + for _, n := range newNodes { + matchingNodeMap.Set(n.getKey(), n) + } } - return newMatchingNodes, nil + return matchingNodeMap, nil } -func (d *dataTreeNavigator) setFunction(op OperationType, lhs []*CandidateNode, rhs []*CandidateNode) []*CandidateNode { +func (d *dataTreeNavigator) setFunction(op OperationType, lhs *orderedmap.OrderedMap, rhs *orderedmap.OrderedMap) *orderedmap.OrderedMap { + + for el := rhs.Front(); el != nil; el = el.Next() { + node := el.Value.(*CandidateNode) + lhs.Set(node.getKey(), node) + } + return lhs - return append(lhs, rhs...) } func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { + var matchingNodeMap = orderedmap.NewOrderedMap() + + for _, n := range matchingNodes { + matchingNodeMap.Set(n.getKey(), n) + } + + matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode) + if err != nil { + return nil, err + } + + values := make([]*CandidateNode, 0, matchedNodes.Len()) + + for el := matchedNodes.Front(); el != nil; el = el.Next() { + values = append(values, el.Value.(*CandidateNode)) + } + return values, nil +} + +func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { log.Debugf("Processing Path: %v", pathNode.PathElement.toString()) if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { return d.traverse(matchingNodes, pathNode.PathElement) } else { - var lhs, rhs []*CandidateNode + var lhs, rhs *orderedmap.OrderedMap var err error switch pathNode.PathElement.OperationType { case Traverse: - lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + lhs, err = d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } - return d.GetMatchingNodes(lhs, pathNode.Rhs) + return d.getMatchingNodes(lhs, pathNode.Rhs) case Or, And: - lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + lhs, err = d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } - rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + rhs, err = d.getMatchingNodes(matchingNodes, pathNode.Rhs) if err != nil { return nil, err } return d.setFunction(pathNode.PathElement.OperationType, lhs, rhs), nil // case Equals: - // lhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + // lhs, err = d.getMatchingNodes(matchingNodes, pathNode.Lhs) // if err != nil { // return nil, err // } diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index b175dc09..0fbc7db8 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -18,6 +18,10 @@ type CandidateNode struct { IsMiddleNode bool } +func (n *CandidateNode) getKey() string { + return fmt.Sprintf("%v - %v", n.Document, n.Path) +} + var log = logging.MustGetLogger("yq-treeops") func NodeToString(node *CandidateNode) string { From c7ebdda5305392deac0c8227cbc820b4616af858 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 12:10:46 +1100 Subject: [PATCH 015/129] added AND op --- pkg/yqlib/treeops/data_tree_navigator.go | 18 +++++++++--- pkg/yqlib/treeops/data_tree_navigator_test.go | 28 +++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 057f564f..4d76e083 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -42,11 +42,21 @@ func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode * func (d *dataTreeNavigator) setFunction(op OperationType, lhs *orderedmap.OrderedMap, rhs *orderedmap.OrderedMap) *orderedmap.OrderedMap { - for el := rhs.Front(); el != nil; el = el.Next() { - node := el.Value.(*CandidateNode) - lhs.Set(node.getKey(), node) + if op == Or { + for el := rhs.Front(); el != nil; el = el.Next() { + node := el.Value.(*CandidateNode) + lhs.Set(node.getKey(), node) + } + return lhs } - return lhs + var matchingNodeMap = orderedmap.NewOrderedMap() + for el := lhs.Front(); el != nil; el = el.Next() { + _, exists := rhs.Get(el.Key) + if exists { + matchingNodeMap.Set(el.Key, el.Value) + } + } + return matchingNodeMap } diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 73414224..65611b81 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -249,3 +249,31 @@ func TestDataTreeNavigatorOrDeDupes(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } + +func TestDataTreeNavigatorAnd(t *testing.T) { + + nodes := readDoc(t, `a: + cat: apple + pat: apple + cow: apple + mad: things`) + + path, errPath := treeCreator.ParsePath("a.(*t and c*)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a cat] + Tag: !!str, Kind: ScalarNode, Anchor: + apple +` + + test.AssertResult(t, expected, resultsToString(results)) +} From a6d4dbb8b8ae7be10456e2645fed00503dad3ba2 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 15:05:45 +1100 Subject: [PATCH 016/129] equal! --- pkg/yqlib/treeops/data_tree_navigator.go | 57 ++++++++++++++--- pkg/yqlib/treeops/data_tree_navigator_test.go | 61 +++++++++++++++++++ pkg/yqlib/treeops/path_postfix.go | 3 +- pkg/yqlib/treeops/traverse.go | 4 +- 4 files changed, 114 insertions(+), 11 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 4d76e083..61ccad25 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -40,6 +40,54 @@ func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode * return matchingNodeMap, nil } +func (d *dataTreeNavigator) equalsOperation(matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- equalsOperation") + var results = orderedmap.NewOrderedMap() + + for el := matchMap.Front(); el != nil; el = el.Next() { + elMap := orderedmap.NewOrderedMap() + elMap.Set(el.Key, el.Value) + //need to splat matching nodes, then search through them + splatter := &PathTreeNode{PathElement: &PathElement{ + PathElementType: PathKey, + Value: "*", + StringValue: "*", + }} + children, err := d.getMatchingNodes(elMap, splatter) + log.Debugf("-- splatted matches, ") + if err != nil { + return nil, err + } + for childEl := children.Front(); childEl != nil; childEl = childEl.Next() { + childMap := orderedmap.NewOrderedMap() + childMap.Set(childEl.Key, childEl.Value) + childMatches, errChild := d.getMatchingNodes(childMap, pathNode.Lhs) + if errChild != nil { + return nil, errChild + } + + if d.containsMatchingValue(childMatches, pathNode.Rhs.PathElement.StringValue) { + results.Set(childEl.Key, childEl.Value) + } + } + } + + return results, nil +} + +func (d *dataTreeNavigator) containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool { + log.Debugf("-- findMatchingValues") + + for el := matchMap.Front(); el != nil; el = el.Next() { + node := el.Value.(*CandidateNode) + if Match(node.Node.Value, valuePattern) { + return true + } + } + + return false +} + func (d *dataTreeNavigator) setFunction(op OperationType, lhs *orderedmap.OrderedMap, rhs *orderedmap.OrderedMap) *orderedmap.OrderedMap { if op == Or { @@ -57,7 +105,6 @@ func (d *dataTreeNavigator) setFunction(op OperationType, lhs *orderedmap.Ordere } } return matchingNodeMap - } func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { @@ -104,12 +151,8 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa return nil, err } return d.setFunction(pathNode.PathElement.OperationType, lhs, rhs), nil - // case Equals: - // lhs, err = d.getMatchingNodes(matchingNodes, pathNode.Lhs) - // if err != nil { - // return nil, err - // } - // return d.findMatchingValues(lhs, pathNode.Rhs) + case Equals: + return d.equalsOperation(matchingNodes, pathNode) // case EqualsSelf: // return d.findMatchingValues(matchingNodes, pathNode.Rhs) default: diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 65611b81..c3c3165c 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -277,3 +277,64 @@ func TestDataTreeNavigatorAnd(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } + +func TestDataTreeNavigatorEquals(t *testing.T) { + + nodes := readDoc(t, `a: + cat: {b: apple, c: yes} + pat: {b: banana} +`) + + path, errPath := treeCreator.ParsePath("a.(b == apple)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a cat] + Tag: !!map, Kind: MappingNode, Anchor: + {b: apple, c: yes} +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorEqualsTrickey(t *testing.T) { + + nodes := readDoc(t, `a: + cat: {b: apso, c: {d : yes}} + pat: {b: apple, c: {d : no}} + sat: {b: apsy, c: {d : yes}} + fat: {b: apple} +`) + + path, errPath := treeCreator.ParsePath("a.(b == ap* and c.d == yes)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a cat] + Tag: !!map, Kind: MappingNode, Anchor: + {b: apso, c: {d: yes}} + +-- Node -- + Document 0, path: [a sat] + Tag: !!map, Kind: MappingNode, Anchor: + {b: apsy, c: {d: yes}} +` + + test.AssertResult(t, expected, resultsToString(results)) +} diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index 3dd4af69..ba41f9a0 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -32,6 +32,7 @@ type PathElement struct { PathElementType PathElementType OperationType OperationType Value interface{} + StringValue string } // debugging purposes only @@ -117,7 +118,7 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme for _, token := range tokens { switch token.Type { case TokenIds["PATH_KEY"], TokenIds["ARRAY_INDEX"], TokenIds["[+]"], TokenIds["[*]"], TokenIds["**"]: - var pathElement = PathElement{PathElementType: PathKey, Value: token.Value} + var pathElement = PathElement{PathElementType: PathKey, Value: token.Value, StringValue: fmt.Sprintf("%v", token.Value)} result = append(result, &pathElement) case TokenIds["("]: opStack = append(opStack, token) diff --git a/pkg/yqlib/treeops/traverse.go b/pkg/yqlib/treeops/traverse.go index df9928c5..45200e78 100644 --- a/pkg/yqlib/treeops/traverse.go +++ b/pkg/yqlib/treeops/traverse.go @@ -1,8 +1,6 @@ package treeops import ( - "fmt" - "gopkg.in/yaml.v3" ) @@ -19,7 +17,7 @@ func NewTraverser(navigationPrefs NavigationPrefs) Traverser { } func (t *traverser) keyMatches(key *yaml.Node, pathNode *PathElement) bool { - return Match(key.Value, fmt.Sprintf("%v", pathNode.Value)) + return Match(key.Value, pathNode.StringValue) } func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { From c791e5c663224f4dae1d95687641de42abe25a93 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 15:06:01 +1100 Subject: [PATCH 017/129] equal! --- cmd/read_test.go | 2 +- go.mod | 1 + go.sum | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/read_test.go b/cmd/read_test.go index 544fdb89..e193ff20 100644 --- a/cmd/read_test.go +++ b/cmd/read_test.go @@ -127,7 +127,7 @@ func TestReadWithAdvancedFilterCmd(t *testing.T) { func TestReadWithAdvancedFilterMapCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml b.e[name==fr*]") + result := test.RunCmd(cmd, "read ../examples/sample.yaml b.e[name`==`fr*]") if result.Error != nil { t.Error(result.Error) } diff --git a/go.mod b/go.mod index 89d1d959..19831915 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/mikefarah/yq/v3 require ( + github.com/elliotchance/orderedmap v1.3.0 github.com/fatih/color v1.9.0 github.com/goccy/go-yaml v1.8.1 github.com/kylelemons/godebug v1.1.0 diff --git a/go.sum b/go.sum index 4710d6b5..c12b43cc 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0= +github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= From d7716551cfb0f5de6b09d216274d5d57aee95655 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 16:38:07 +1100 Subject: [PATCH 018/129] arrays --- pkg/yqlib/treeops/data_tree_navigator_test.go | 54 +++++++++++++++++++ pkg/yqlib/treeops/traverse.go | 36 ++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index c3c3165c..35e04742 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -54,6 +54,60 @@ func TestDataTreeNavigatorSimple(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorArraySimple(t *testing.T) { + + nodes := readDoc(t, `- b: apple`) + + path, errPath := treeCreator.ParsePath("[0]") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [0] + Tag: !!map, Kind: MappingNode, Anchor: + b: apple +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorArraySplat(t *testing.T) { + + nodes := readDoc(t, `- b: apple +- c: banana`) + + path, errPath := treeCreator.ParsePath("[*]") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [0] + Tag: !!map, Kind: MappingNode, Anchor: + b: apple + +-- Node -- + Document 0, path: [1] + Tag: !!map, Kind: MappingNode, Anchor: + c: banana +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorSimpleDeep(t *testing.T) { nodes := readDoc(t, `a: diff --git a/pkg/yqlib/treeops/traverse.go b/pkg/yqlib/treeops/traverse.go index 45200e78..e980e731 100644 --- a/pkg/yqlib/treeops/traverse.go +++ b/pkg/yqlib/treeops/traverse.go @@ -47,6 +47,37 @@ func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) } } return newMatches, nil +} + +func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { + log.Debug("pathNode Value %v", pathNode.Value) + if pathNode.Value == "[*]" { + + var contents = candidate.Node.Content + var newMatches = make([]*CandidateNode, len(contents)) + + for index := 0; index < len(contents); index = index + 1 { + newMatches[index] = &CandidateNode{ + Document: candidate.Document, + Path: append(candidate.Path, index), + Node: contents[index], + } + } + return newMatches, nil + + } + + index := pathNode.Value.(int64) + if int64(len(candidate.Node.Content)) < index { + // handle auto append here + return make([]*CandidateNode, 0), nil + } + + return []*CandidateNode{&CandidateNode{ + Node: candidate.Node.Content[index], + Document: candidate.Document, + Path: append(candidate.Path, index), + }}, nil } @@ -58,8 +89,9 @@ func (t *traverser) Traverse(matchingNode *CandidateNode, pathNode *PathElement) log.Debug("its a map with %v entries", len(value.Content)/2) return t.traverseMap(matchingNode, pathNode) - // case yaml.SequenceNode: - // log.Debug("its a sequence of %v things!", len(value.Content)) + case yaml.SequenceNode: + log.Debug("its a sequence of %v things!", len(value.Content)) + return t.traverseArray(matchingNode, pathNode) // switch head := head.(type) { // case int64: From 93aaa8ccee910d90e91c28a05d90a8c92b25975d Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 16:43:43 +1100 Subject: [PATCH 019/129] array equals! --- pkg/yqlib/treeops/data_tree_navigator_test.go | 31 +++++++++++++++++++ pkg/yqlib/treeops/traverse.go | 6 +--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 35e04742..55585ed5 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -359,6 +359,37 @@ func TestDataTreeNavigatorEquals(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorArrayEquals(t *testing.T) { + + nodes := readDoc(t, `- { b: apple, animal: rabbit } +- { b: banana, animal: cat } +- { b: corn, animal: dog }`) + + path, errPath := treeCreator.ParsePath("(b == apple or animal == dog)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [0] + Tag: !!map, Kind: MappingNode, Anchor: + {b: apple, animal: rabbit} + +-- Node -- + Document 0, path: [2] + Tag: !!map, Kind: MappingNode, Anchor: + {b: corn, animal: dog} +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorEqualsTrickey(t *testing.T) { nodes := readDoc(t, `a: diff --git a/pkg/yqlib/treeops/traverse.go b/pkg/yqlib/treeops/traverse.go index e980e731..7b3790d5 100644 --- a/pkg/yqlib/treeops/traverse.go +++ b/pkg/yqlib/treeops/traverse.go @@ -51,7 +51,7 @@ func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { log.Debug("pathNode Value %v", pathNode.Value) - if pathNode.Value == "[*]" { + if pathNode.Value == "[*]" || pathNode.Value == "*" { var contents = candidate.Node.Content var newMatches = make([]*CandidateNode, len(contents)) @@ -92,10 +92,6 @@ func (t *traverser) Traverse(matchingNode *CandidateNode, pathNode *PathElement) case yaml.SequenceNode: log.Debug("its a sequence of %v things!", len(value.Content)) return t.traverseArray(matchingNode, pathNode) - - // switch head := head.(type) { - // case int64: - // return n.recurseArray(value, head, head, tail, pathStack) // default: // if head == "+" { From 23083ed974223f3565194452789a057ac2dd1245 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 9 Oct 2020 17:07:53 +1100 Subject: [PATCH 020/129] fixed equals number issue --- pkg/yqlib/treeops/data_tree_navigator_test.go | 28 ++++++++++++++++ pkg/yqlib/treeops/path_postfix_test.go | 32 +++++++++++++++++++ pkg/yqlib/treeops/path_tokeniser.go | 4 +-- pkg/yqlib/treeops/path_tokeniser_test.go | 2 ++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 55585ed5..0ffc9f3b 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -390,6 +390,34 @@ func TestDataTreeNavigatorArrayEquals(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorArrayEqualsDeep(t *testing.T) { + + nodes := readDoc(t, `apples: + - { b: apple, animal: {legs: 2} } + - { b: banana, animal: {legs: 4} } + - { b: corn, animal: {legs: 6} } +`) + + path, errPath := treeCreator.ParsePath("apples(animal.legs == 4)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [apples 1] + Tag: !!map, Kind: MappingNode, Anchor: + {b: banana, animal: {legs: 4}} +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorEqualsTrickey(t *testing.T) { nodes := readDoc(t, `a: diff --git a/pkg/yqlib/treeops/path_postfix_test.go b/pkg/yqlib/treeops/path_postfix_test.go index 2e9d6361..220ef6a0 100644 --- a/pkg/yqlib/treeops/path_postfix_test.go +++ b/pkg/yqlib/treeops/path_postfix_test.go @@ -25,6 +25,20 @@ func testExpression(expression string) (string, error) { return formatted, nil } +func TestPostFixArrayEquals(t *testing.T) { + var infix = "a" + var expectedOutput = `PathKey - 'a' +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + func TestPostFixSimpleExample(t *testing.T) { var infix = "a" var expectedOutput = `PathKey - 'a' @@ -167,6 +181,24 @@ Operation - OR test.AssertResultComplex(t, expectedOutput, actual) } +func TestPostFixEqualsNumberExample(t *testing.T) { + var infix = "(animal == 3)" + var expectedOutput = `PathKey - 'animal' +-------- +PathKey - '3' +-------- +Operation - EQUALS +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + func TestPostFixOrWithEqualsExample(t *testing.T) { var infix = "a==thing OR b==thongs" var expectedOutput = `PathKey - 'a' diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index acabe798..e1c42ec3 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -142,14 +142,14 @@ func (p *pathTokeniser) Tokenise(path string) ([]*lex.Token, error) { for index, token := range tokens { for _, literalTokenDef := range append(Literals, "ARRAY_INDEX", "(") { - if index > 0 && token.Type == TokenIds[literalTokenDef] && tokens[index-1].Type != TokenIds["TRAVERSE_OPERATOR"] { + if index > 0 && token.Type == TokenIds[literalTokenDef] && tokens[index-1].Type != TokenIds["TRAVERSE_OPERATOR"] && tokens[index-1].Type != TokenIds["EQUALS_OPERATOR"] && tokens[index-1].Type != TokenIds["EQUALS_SELF_OPERATOR"] { postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) } } postProcessedTokens = append(postProcessedTokens, token) for _, literalTokenDef := range append(Literals, "ARRAY_INDEX", ")") { - if index != len(tokens)-1 && token.Type == TokenIds[literalTokenDef] && tokens[index+1].Type != TokenIds["TRAVERSE_OPERATOR"] { + if index != len(tokens)-1 && token.Type == TokenIds[literalTokenDef] && tokens[index+1].Type != TokenIds["TRAVERSE_OPERATOR"] && tokens[index+1].Type != TokenIds[")"] { postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) } } diff --git a/pkg/yqlib/treeops/path_tokeniser_test.go b/pkg/yqlib/treeops/path_tokeniser_test.go index 4e7405ef..7c673f3d 100644 --- a/pkg/yqlib/treeops/path_tokeniser_test.go +++ b/pkg/yqlib/treeops/path_tokeniser_test.go @@ -11,6 +11,8 @@ var tokeniserTests = []struct { expectedTokens []interface{} }{ // TODO: Ensure ALL documented examples have tests! sheesh + {"(animal==3)", append(make([]interface{}, 0), "(", "animal", "==", int64(3), ")")}, + {"(animal==f3)", append(make([]interface{}, 0), "(", "animal", "==", "f3", ")")}, {"apples.BANANAS", append(make([]interface{}, 0), "apples", ".", "BANANAS")}, {"appl*.BANA*", append(make([]interface{}, 0), "appl*", ".", "BANA*")}, {"a.b.**", append(make([]interface{}, 0), "a", ".", "b", ".", "**")}, From 8170eec6d14f03dab640c9cf33238830f99cb7f4 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 10 Oct 2020 15:00:39 +1100 Subject: [PATCH 021/129] extracted out operators --- pkg/yqlib/treeops/data_tree_navigator.go | 116 +++--------------- .../{traverse.go => leaf_traverser.go} | 4 +- pkg/yqlib/treeops/lib.go | 16 ++- pkg/yqlib/treeops/operators.go | 96 +++++++++++++++ 4 files changed, 132 insertions(+), 100 deletions(-) rename pkg/yqlib/treeops/{traverse.go => leaf_traverser.go} (97%) create mode 100644 pkg/yqlib/treeops/operators.go diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 61ccad25..f2e72df0 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -1,11 +1,14 @@ package treeops import ( + "fmt" + "github.com/elliotchance/orderedmap" ) type dataTreeNavigator struct { - traverser Traverser + leafTraverser LeafTraverser + operatorHandlers map[OperationType]OperatorHandler } type NavigationPrefs struct { @@ -17,8 +20,15 @@ type DataTreeNavigator interface { } func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { - traverse := NewTraverser(navigationPrefs) - return &dataTreeNavigator{traverse} + leafTraverser := NewLeafTraverser(navigationPrefs) + operatorHandlers := make(map[OperationType]OperatorHandler) + + operatorHandlers[Traverse] = TraverseOperator + operatorHandlers[Equals] = EqualsOperator + operatorHandlers[Or] = UnionOperator + operatorHandlers[And] = IntersectionOperator + + return &dataTreeNavigator{leafTraverser, operatorHandlers} } func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *PathElement) (*orderedmap.OrderedMap, error) { @@ -28,7 +38,7 @@ func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode * var err error for el := matchMap.Front(); el != nil; el = el.Next() { - newNodes, err = d.traverser.Traverse(el.Value.(*CandidateNode), pathNode) + newNodes, err = d.leafTraverser.Traverse(el.Value.(*CandidateNode), pathNode) if err != nil { return nil, err } @@ -40,73 +50,6 @@ func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode * return matchingNodeMap, nil } -func (d *dataTreeNavigator) equalsOperation(matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - log.Debugf("-- equalsOperation") - var results = orderedmap.NewOrderedMap() - - for el := matchMap.Front(); el != nil; el = el.Next() { - elMap := orderedmap.NewOrderedMap() - elMap.Set(el.Key, el.Value) - //need to splat matching nodes, then search through them - splatter := &PathTreeNode{PathElement: &PathElement{ - PathElementType: PathKey, - Value: "*", - StringValue: "*", - }} - children, err := d.getMatchingNodes(elMap, splatter) - log.Debugf("-- splatted matches, ") - if err != nil { - return nil, err - } - for childEl := children.Front(); childEl != nil; childEl = childEl.Next() { - childMap := orderedmap.NewOrderedMap() - childMap.Set(childEl.Key, childEl.Value) - childMatches, errChild := d.getMatchingNodes(childMap, pathNode.Lhs) - if errChild != nil { - return nil, errChild - } - - if d.containsMatchingValue(childMatches, pathNode.Rhs.PathElement.StringValue) { - results.Set(childEl.Key, childEl.Value) - } - } - } - - return results, nil -} - -func (d *dataTreeNavigator) containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool { - log.Debugf("-- findMatchingValues") - - for el := matchMap.Front(); el != nil; el = el.Next() { - node := el.Value.(*CandidateNode) - if Match(node.Node.Value, valuePattern) { - return true - } - } - - return false -} - -func (d *dataTreeNavigator) setFunction(op OperationType, lhs *orderedmap.OrderedMap, rhs *orderedmap.OrderedMap) *orderedmap.OrderedMap { - - if op == Or { - for el := rhs.Front(); el != nil; el = el.Next() { - node := el.Value.(*CandidateNode) - lhs.Set(node.getKey(), node) - } - return lhs - } - var matchingNodeMap = orderedmap.NewOrderedMap() - for el := lhs.Front(); el != nil; el = el.Next() { - _, exists := rhs.Get(el.Key) - if exists { - matchingNodeMap.Set(el.Key, el.Value) - } - } - return matchingNodeMap -} - func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { var matchingNodeMap = orderedmap.NewOrderedMap() @@ -132,33 +75,12 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { return d.traverse(matchingNodes, pathNode.PathElement) } else { - var lhs, rhs *orderedmap.OrderedMap - var err error - switch pathNode.PathElement.OperationType { - case Traverse: - lhs, err = d.getMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - return d.getMatchingNodes(lhs, pathNode.Rhs) - case Or, And: - lhs, err = d.getMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - rhs, err = d.getMatchingNodes(matchingNodes, pathNode.Rhs) - if err != nil { - return nil, err - } - return d.setFunction(pathNode.PathElement.OperationType, lhs, rhs), nil - case Equals: - return d.equalsOperation(matchingNodes, pathNode) - // case EqualsSelf: - // return d.findMatchingValues(matchingNodes, pathNode.Rhs) - default: - return nil, nil + handler := d.operatorHandlers[pathNode.PathElement.OperationType] + if handler != nil { + return handler(d, matchingNodes, pathNode) + } else { + return nil, fmt.Errorf("Unknown operator %v", pathNode.PathElement.OperationType) } - } } diff --git a/pkg/yqlib/treeops/traverse.go b/pkg/yqlib/treeops/leaf_traverser.go similarity index 97% rename from pkg/yqlib/treeops/traverse.go rename to pkg/yqlib/treeops/leaf_traverser.go index 7b3790d5..bb855b2a 100644 --- a/pkg/yqlib/treeops/traverse.go +++ b/pkg/yqlib/treeops/leaf_traverser.go @@ -8,11 +8,11 @@ type traverser struct { prefs NavigationPrefs } -type Traverser interface { +type LeafTraverser interface { Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) } -func NewTraverser(navigationPrefs NavigationPrefs) Traverser { +func NewLeafTraverser(navigationPrefs NavigationPrefs) LeafTraverser { return &traverser{navigationPrefs} } diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 0fbc7db8..81eb9e12 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -8,6 +8,8 @@ import ( "gopkg.in/yaml.v3" ) +var log = logging.MustGetLogger("yq-treeops") + type CandidateNode struct { Node *yaml.Node // the actual node Path []interface{} /// the path we took to get to this node @@ -22,7 +24,19 @@ func (n *CandidateNode) getKey() string { return fmt.Sprintf("%v - %v", n.Document, n.Path) } -var log = logging.MustGetLogger("yq-treeops") +type YqTreeLib interface { + Get(rootNode *yaml.Node, path string) ([]*CandidateNode, error) + // GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) + // Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error + // New(path string) yaml.Node + + // PathStackToString(pathStack []interface{}) string + // MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string +} + +type lib struct { + treeCreator PathTreeCreator +} func NodeToString(node *CandidateNode) string { if !log.IsEnabledFor(logging.DEBUG) { diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go new file mode 100644 index 00000000..f3879c12 --- /dev/null +++ b/pkg/yqlib/treeops/operators.go @@ -0,0 +1,96 @@ +package treeops + +import "github.com/elliotchance/orderedmap" + +type OperatorHandler func(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) + +func TraverseOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + return d.getMatchingNodes(lhs, pathNode.Rhs) +} + +func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + for el := rhs.Front(); el != nil; el = el.Next() { + node := el.Value.(*CandidateNode) + lhs.Set(node.getKey(), node) + } + return lhs, nil +} + +func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + var matchingNodeMap = orderedmap.NewOrderedMap() + for el := lhs.Front(); el != nil; el = el.Next() { + _, exists := rhs.Get(el.Key) + if exists { + matchingNodeMap.Set(el.Key, el.Value) + } + } + return matchingNodeMap, nil +} + +func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- equalsOperation") + var results = orderedmap.NewOrderedMap() + + for el := matchMap.Front(); el != nil; el = el.Next() { + elMap := orderedmap.NewOrderedMap() + elMap.Set(el.Key, el.Value) + //need to splat matching nodes, then search through them + splatter := &PathTreeNode{PathElement: &PathElement{ + PathElementType: PathKey, + Value: "*", + StringValue: "*", + }} + children, err := d.getMatchingNodes(elMap, splatter) + log.Debugf("-- splatted matches, ") + if err != nil { + return nil, err + } + for childEl := children.Front(); childEl != nil; childEl = childEl.Next() { + childMap := orderedmap.NewOrderedMap() + childMap.Set(childEl.Key, childEl.Value) + childMatches, errChild := d.getMatchingNodes(childMap, pathNode.Lhs) + if errChild != nil { + return nil, errChild + } + + if containsMatchingValue(childMatches, pathNode.Rhs.PathElement.StringValue) { + results.Set(childEl.Key, childEl.Value) + } + } + } + + return results, nil +} + +func containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool { + log.Debugf("-- findMatchingValues") + + for el := matchMap.Front(); el != nil; el = el.Next() { + node := el.Value.(*CandidateNode) + if Match(node.Node.Value, valuePattern) { + return true + } + } + + return false +} From ac076cd34af37412bf6a9440f1921dfd98fad467 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 10 Oct 2020 15:24:37 +1100 Subject: [PATCH 022/129] assign operator --- pkg/yqlib/treeops/data_tree_navigator.go | 1 + pkg/yqlib/treeops/data_tree_navigator_test.go | 25 +++++++++++++++++++ pkg/yqlib/treeops/operators.go | 12 +++++++++ pkg/yqlib/treeops/path_postfix.go | 22 ++++++++++------ pkg/yqlib/treeops/path_postfix_test.go | 22 ++++++++++++++++ pkg/yqlib/treeops/path_tokeniser.go | 2 ++ 6 files changed, 76 insertions(+), 8 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index f2e72df0..6979ee08 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -27,6 +27,7 @@ func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { operatorHandlers[Equals] = EqualsOperator operatorHandlers[Or] = UnionOperator operatorHandlers[And] = IntersectionOperator + operatorHandlers[Assign] = AssignOperator return &dataTreeNavigator{leafTraverser, operatorHandlers} } diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 0ffc9f3b..be45bcb9 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -78,6 +78,31 @@ func TestDataTreeNavigatorArraySimple(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorSimpleAssign(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple`) + + path, errPath := treeCreator.ParsePath("a.b := frog") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a b] + Tag: !!str, Kind: ScalarNode, Anchor: + frog +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorArraySplat(t *testing.T) { nodes := readDoc(t, `- b: apple diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index f3879c12..fa84e77d 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -12,6 +12,18 @@ func TraverseOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap return d.getMatchingNodes(lhs, pathNode.Rhs) } +func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + for el := lhs.Front(); el != nil; el = el.Next() { + node := el.Value.(*CandidateNode) + node.Node.Value = pathNode.Rhs.PathElement.StringValue + } + return lhs, nil +} + func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index ba41f9a0..ffe83e45 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -26,6 +26,7 @@ const ( And Equals EqualsSelf + Assign ) type PathElement struct { @@ -54,6 +55,8 @@ func (p *PathElement) toString() string { result = result + "EQUALS\n" case EqualsSelf: result = result + "EQUALS SELF\n" + case Assign: + result = result + "ASSIGN\n" case Traverse: result = result + "TRAVERSE\n" } @@ -82,6 +85,9 @@ func initMaps() { precedenceMap[TokenIds["EQUALS_SELF_OPERATOR"]] = 30 operationTypeMapper[TokenIds["EQUALS_SELF_OPERATOR"]] = EqualsSelf + precedenceMap[TokenIds["ASSIGN_OPERATOR"]] = 35 + operationTypeMapper[TokenIds["ASSIGN_OPERATOR"]] = Assign + precedenceMap[TokenIds["TRAVERSE_OPERATOR"]] = 40 operationTypeMapper[TokenIds["TRAVERSE_OPERATOR"]] = Traverse } @@ -122,14 +128,6 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme result = append(result, &pathElement) case TokenIds["("]: opStack = append(opStack, token) - case TokenIds["OR_OPERATOR"], TokenIds["AND_OPERATOR"], TokenIds["EQUALS_OPERATOR"], TokenIds["EQUALS_SELF_OPERATOR"], TokenIds["TRAVERSE_OPERATOR"]: - var currentPrecedence = precedenceMap[token.Type] - // pop off higher precedent operators onto the result - for len(opStack) > 0 && precedenceMap[opStack[len(opStack)-1].Type] >= currentPrecedence { - opStack, result = popOpToResult(opStack, result) - } - // add this operator to the opStack - opStack = append(opStack, token) case TokenIds[")"]: for len(opStack) > 0 && opStack[len(opStack)-1].Type != TokenIds["("] { opStack, result = popOpToResult(opStack, result) @@ -139,6 +137,14 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme } // now we should have ( as the last element on the opStack, get rid of it opStack = opStack[0 : len(opStack)-1] + default: + var currentPrecedence = precedenceMap[token.Type] + // pop off higher precedent operators onto the result + for len(opStack) > 0 && precedenceMap[opStack[len(opStack)-1].Type] >= currentPrecedence { + opStack, result = popOpToResult(opStack, result) + } + // add this operator to the opStack + opStack = append(opStack, token) } } return result, nil diff --git a/pkg/yqlib/treeops/path_postfix_test.go b/pkg/yqlib/treeops/path_postfix_test.go index 220ef6a0..e27962e8 100644 --- a/pkg/yqlib/treeops/path_postfix_test.go +++ b/pkg/yqlib/treeops/path_postfix_test.go @@ -75,6 +75,28 @@ Operation - TRAVERSE test.AssertResultComplex(t, expectedOutput, actual) } +func TestPostFixSimpleAssign(t *testing.T) { + var infix = "a.b := frog" + var expectedOutput = `PathKey - 'a' +-------- +PathKey - 'b' +-------- +Operation - TRAVERSE +-------- +PathKey - 'frog' +-------- +Operation - ASSIGN +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + func TestPostFixSimplePathNumbersExample(t *testing.T) { var infix = "apples[0].cat" var expectedOutput = `PathKey - 'apples' diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index e1c42ec3..d4a7c554 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -27,6 +27,7 @@ func initTokens() { "AND_OPERATOR", "EQUALS_OPERATOR", "EQUALS_SELF_OPERATOR", + "ASSIGN_OPERATOR", "TRAVERSE_OPERATOR", "PATH_KEY", // apples "ARRAY_INDEX", // 123 @@ -90,6 +91,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`([Aa][Nn][Dd])`), token("AND_OPERATOR")) lexer.Add([]byte(`\.\s*==\s*`), token("EQUALS_SELF_OPERATOR")) lexer.Add([]byte(`\s*==\s*`), token("EQUALS_OPERATOR")) + lexer.Add([]byte(`\s*:=\s*`), token("ASSIGN_OPERATOR")) lexer.Add([]byte(`\[-?[0-9]+\]`), numberToken("ARRAY_INDEX", true)) lexer.Add([]byte(`-?[0-9]+`), numberToken("ARRAY_INDEX", false)) lexer.Add([]byte("( |\t|\n|\r)+"), skip) From 0a2a3c43740ec432e2abe2d5b471451dea809f4d Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 10 Oct 2020 22:42:09 +1100 Subject: [PATCH 023/129] can delete --- pkg/yqlib/treeops/data_tree_navigator.go | 1 + pkg/yqlib/treeops/data_tree_navigator_test.go | 106 ++++++++++++++++++ pkg/yqlib/treeops/delete_operator.go | 83 ++++++++++++++ pkg/yqlib/treeops/lib.go | 18 ++- pkg/yqlib/treeops/operators.go | 22 ++-- pkg/yqlib/treeops/path_postfix.go | 7 ++ pkg/yqlib/treeops/path_tokeniser.go | 8 +- pkg/yqlib/treeops/path_tokeniser_test.go | 4 +- 8 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 pkg/yqlib/treeops/delete_operator.go diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 6979ee08..6cffe9ce 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -28,6 +28,7 @@ func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { operatorHandlers[Or] = UnionOperator operatorHandlers[And] = IntersectionOperator operatorHandlers[Assign] = AssignOperator + operatorHandlers[DeleteChild] = DeleteChildOperator return &dataTreeNavigator{leafTraverser, operatorHandlers} } diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index be45bcb9..c48b5b54 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -54,6 +54,112 @@ func TestDataTreeNavigatorSimple(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorSubtractSimple(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple + c: camel`) + + path, errPath := treeCreator.ParsePath("a .- b") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!map, Kind: MappingNode, Anchor: + c: camel +` + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorSubtractTwice(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple + c: camel + d: dingo`) + + path, errPath := treeCreator.ParsePath("a .- b OR a .- c") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!map, Kind: MappingNode, Anchor: + d: dingo +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorSubtractWithUnion(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple + c: camel + d: dingo`) + + path, errPath := treeCreator.ParsePath("a .- (b OR c)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!map, Kind: MappingNode, Anchor: + d: dingo +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorSubtractArray(t *testing.T) { + + nodes := readDoc(t, `a: + - b: apple + - b: sdfsd + - b: apple`) + + path, errPath := treeCreator.ParsePath("a .- (b == a*)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!seq, Kind: SequenceNode, Anchor: + - b: sdfsd +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorArraySimple(t *testing.T) { nodes := readDoc(t, `- b: apple`) diff --git a/pkg/yqlib/treeops/delete_operator.go b/pkg/yqlib/treeops/delete_operator.go new file mode 100644 index 00000000..de3faf1e --- /dev/null +++ b/pkg/yqlib/treeops/delete_operator.go @@ -0,0 +1,83 @@ +package treeops + +import ( + "github.com/elliotchance/orderedmap" + "gopkg.in/yaml.v3" +) + +func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + // for each lhs, splat the node, + // the intersect it against the rhs expression + // recreate the contents using only the intersection result. + + for el := lhs.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + elMap := orderedmap.NewOrderedMap() + elMap.Set(candidate.getKey(), candidate) + nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs) + log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete)) + if err != nil { + return nil, err + } + + if candidate.Node.Kind == yaml.SequenceNode { + deleteFromArray(candidate, nodesToDelete) + } else { + deleteFromMap(candidate, nodesToDelete) + } + } + return lhs, nil +} + +func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMap) { + log.Debug("deleteFromMap") + node := candidate.Node + contents := node.Content + newContents := make([]*yaml.Node, 0) + + for index := 0; index < len(contents); index = index + 2 { + key := contents[index] + value := contents[index+1] + + childCandidate := &CandidateNode{ + Node: value, + Document: candidate.Document, + Path: append(candidate.Path, key.Value), + } + _, shouldDelete := nodesToDelete.Get(childCandidate.getKey()) + + log.Debugf("shouldDelete %v ? %v", childCandidate.getKey(), shouldDelete) + + if !shouldDelete { + newContents = append(newContents, key, value) + } + } + node.Content = newContents +} + +func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMap) { + log.Debug("deleteFromArray") + node := candidate.Node + contents := node.Content + newContents := make([]*yaml.Node, 0) + + for index := 0; index < len(contents); index = index + 1 { + value := contents[index] + + childCandidate := &CandidateNode{ + Node: value, + Document: candidate.Document, + Path: append(candidate.Path, index), + } + + _, shouldDelete := nodesToDelete.Get(childCandidate.getKey()) + if !shouldDelete { + newContents = append(newContents, value) + } + } + node.Content = newContents +} diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 81eb9e12..2f068c47 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" + "github.com/elliotchance/orderedmap" "gopkg.in/op/go-logging.v1" "gopkg.in/yaml.v3" ) @@ -14,10 +15,6 @@ type CandidateNode struct { Node *yaml.Node // the actual node Path []interface{} /// the path we took to get to this node Document uint // the document index of this node - - // middle nodes are nodes that match along the original path, but not a - // target match of the path. This is only relevant when ShouldOnlyDeeplyVisitLeaves is false. - IsMiddleNode bool } func (n *CandidateNode) getKey() string { @@ -38,6 +35,19 @@ type lib struct { treeCreator PathTreeCreator } +//use for debugging only +func NodesToString(collection *orderedmap.OrderedMap) string { + if !log.IsEnabledFor(logging.DEBUG) { + return "" + } + + result := "" + for el := collection.Front(); el != nil; el = el.Next() { + result = result + "\n" + NodeToString(el.Value.(*CandidateNode)) + } + return result +} + func NodeToString(node *CandidateNode) string { if !log.IsEnabledFor(logging.DEBUG) { return "" diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index fa84e77d..aae4d89d 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -59,20 +59,24 @@ func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordere return matchingNodeMap, nil } +func splatNode(d *dataTreeNavigator, candidate *CandidateNode) (*orderedmap.OrderedMap, error) { + elMap := orderedmap.NewOrderedMap() + elMap.Set(candidate.getKey(), candidate) + //need to splat matching nodes, then search through them + splatter := &PathTreeNode{PathElement: &PathElement{ + PathElementType: PathKey, + Value: "*", + StringValue: "*", + }} + return d.getMatchingNodes(elMap, splatter) +} + func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { log.Debugf("-- equalsOperation") var results = orderedmap.NewOrderedMap() for el := matchMap.Front(); el != nil; el = el.Next() { - elMap := orderedmap.NewOrderedMap() - elMap.Set(el.Key, el.Value) - //need to splat matching nodes, then search through them - splatter := &PathTreeNode{PathElement: &PathElement{ - PathElementType: PathKey, - Value: "*", - StringValue: "*", - }} - children, err := d.getMatchingNodes(elMap, splatter) + children, err := splatNode(d, el.Value.(*CandidateNode)) log.Debugf("-- splatted matches, ") if err != nil { return nil, err diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index ffe83e45..42cb523e 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -27,6 +27,7 @@ const ( Equals EqualsSelf Assign + DeleteChild ) type PathElement struct { @@ -59,6 +60,9 @@ func (p *PathElement) toString() string { result = result + "ASSIGN\n" case Traverse: result = result + "TRAVERSE\n" + case DeleteChild: + result = result + "DELETE CHILD\n" + } } @@ -85,6 +89,9 @@ func initMaps() { precedenceMap[TokenIds["EQUALS_SELF_OPERATOR"]] = 30 operationTypeMapper[TokenIds["EQUALS_SELF_OPERATOR"]] = EqualsSelf + precedenceMap[TokenIds["DELETE_CHILD_OPERATOR"]] = 30 + operationTypeMapper[TokenIds["DELETE_CHILD_OPERATOR"]] = DeleteChild + precedenceMap[TokenIds["ASSIGN_OPERATOR"]] = 35 operationTypeMapper[TokenIds["ASSIGN_OPERATOR"]] = Assign diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index d4a7c554..44b29313 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -28,6 +28,7 @@ func initTokens() { "EQUALS_OPERATOR", "EQUALS_SELF_OPERATOR", "ASSIGN_OPERATOR", + "DELETE_CHILD_OPERATOR", "TRAVERSE_OPERATOR", "PATH_KEY", // apples "ARRAY_INDEX", // 123 @@ -91,6 +92,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`([Aa][Nn][Dd])`), token("AND_OPERATOR")) lexer.Add([]byte(`\.\s*==\s*`), token("EQUALS_SELF_OPERATOR")) lexer.Add([]byte(`\s*==\s*`), token("EQUALS_OPERATOR")) + lexer.Add([]byte(`\s*.-\s*`), token("DELETE_CHILD_OPERATOR")) lexer.Add([]byte(`\s*:=\s*`), token("ASSIGN_OPERATOR")) lexer.Add([]byte(`\[-?[0-9]+\]`), numberToken("ARRAY_INDEX", true)) lexer.Add([]byte(`-?[0-9]+`), numberToken("ARRAY_INDEX", false)) @@ -133,7 +135,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*lex.Token, error) { if tok != nil { token := tok.(*lex.Token) - log.Debugf("Processing %v - %v", token.Value, Tokens[token.Type]) + log.Debugf("Tokenising %v - %v", token.Value, Tokens[token.Type]) tokens = append(tokens, token) } if err != nil { @@ -144,14 +146,14 @@ func (p *pathTokeniser) Tokenise(path string) ([]*lex.Token, error) { for index, token := range tokens { for _, literalTokenDef := range append(Literals, "ARRAY_INDEX", "(") { - if index > 0 && token.Type == TokenIds[literalTokenDef] && tokens[index-1].Type != TokenIds["TRAVERSE_OPERATOR"] && tokens[index-1].Type != TokenIds["EQUALS_OPERATOR"] && tokens[index-1].Type != TokenIds["EQUALS_SELF_OPERATOR"] { + if index > 0 && token.Type == TokenIds[literalTokenDef] && tokens[index-1].Type == TokenIds["PATH_KEY"] { postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) } } postProcessedTokens = append(postProcessedTokens, token) for _, literalTokenDef := range append(Literals, "ARRAY_INDEX", ")") { - if index != len(tokens)-1 && token.Type == TokenIds[literalTokenDef] && tokens[index+1].Type != TokenIds["TRAVERSE_OPERATOR"] && tokens[index+1].Type != TokenIds[")"] { + if index != len(tokens)-1 && token.Type == TokenIds[literalTokenDef] && tokens[index+1].Type == TokenIds["PATH_KEY"] { postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) } } diff --git a/pkg/yqlib/treeops/path_tokeniser_test.go b/pkg/yqlib/treeops/path_tokeniser_test.go index 7c673f3d..edfd25dd 100644 --- a/pkg/yqlib/treeops/path_tokeniser_test.go +++ b/pkg/yqlib/treeops/path_tokeniser_test.go @@ -11,6 +11,8 @@ var tokeniserTests = []struct { expectedTokens []interface{} }{ // TODO: Ensure ALL documented examples have tests! sheesh + {"a OR (b OR c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")}, + {"a .- (b OR c)", append(make([]interface{}, 0), "a", " .- ", "(", "b", "OR", "c", ")")}, {"(animal==3)", append(make([]interface{}, 0), "(", "animal", "==", int64(3), ")")}, {"(animal==f3)", append(make([]interface{}, 0), "(", "animal", "==", "f3", ")")}, {"apples.BANANAS", append(make([]interface{}, 0), "apples", ".", "BANANAS")}, @@ -27,7 +29,7 @@ var tokeniserTests = []struct { {"a.b.[+]", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]")}, {"a.b[-12]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, {"a.b.0", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, - {"a.b.-12", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, + // {"a.b.-12", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, {"a", append(make([]interface{}, 0), "a")}, {"\"a.b\".c", append(make([]interface{}, 0), "a.b", ".", "c")}, {`b."foo.bar"`, append(make([]interface{}, 0), "b", ".", "foo.bar")}, From e6fd6905eb2bb80a75094ae75dea64fdb46ae41b Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 10 Oct 2020 23:04:10 +1100 Subject: [PATCH 024/129] wip --- pkg/yqlib/treeops/data_tree_navigator_test.go | 59 +++++++++++++++++++ pkg/yqlib/treeops/operators.go | 1 + 2 files changed, 60 insertions(+) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index c48b5b54..2b183da9 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -133,6 +133,65 @@ func TestDataTreeNavigatorSubtractWithUnion(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorSubtractByIndex(t *testing.T) { + + nodes := readDoc(t, `a: + - b: apple + - b: sdfsd + - b: apple`) + + path, errPath := treeCreator.ParsePath("(a .- (0 or 1))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!seq, Kind: SequenceNode, Anchor: + - b: apple +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorDeleteAndWrite(t *testing.T) { + + nodes := readDoc(t, `a: + - b: apple + - b: sdfsd + - { b: apple, c: cat }`) + + path, errPath := treeCreator.ParsePath("(a .- (0 or 1)) or (a[0].b := frog)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!seq, Kind: SequenceNode, Anchor: + - {b: frog, c: cat} + +-- Node -- + Document 0, path: [a 0 b] + Tag: !!str, Kind: ScalarNode, Anchor: + frog +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorSubtractArray(t *testing.T) { nodes := readDoc(t, `a: diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index aae4d89d..26ea678e 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -19,6 +19,7 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, } for el := lhs.Front(); el != nil; el = el.Next() { node := el.Value.(*CandidateNode) + log.Debugf("Assiging %v to %v", node.getKey(), pathNode.Rhs.PathElement.StringValue) node.Node.Value = pathNode.Rhs.PathElement.StringValue } return lhs, nil From e0d1aed5b97ad4ac59ac433d822720fe90aeea81 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 11 Oct 2020 11:24:22 +1100 Subject: [PATCH 025/129] Refactoring --- pkg/yqlib/treeops/data_tree_navigator_test.go | 90 +++++++++- pkg/yqlib/treeops/operators.go | 50 ++++-- pkg/yqlib/treeops/path_postfix.go | 90 +++++----- pkg/yqlib/treeops/path_postfix_test.go | 12 +- pkg/yqlib/treeops/path_tokeniser.go | 156 +++++++++--------- pkg/yqlib/treeops/path_tokeniser_test.go | 8 +- 6 files changed, 254 insertions(+), 152 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 2b183da9..669cc4fc 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -522,7 +522,7 @@ func TestDataTreeNavigatorAnd(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorEquals(t *testing.T) { +func TestDataTreeNavigatorEqualsSimple(t *testing.T) { nodes := readDoc(t, `a: cat: {b: apple, c: yes} @@ -549,6 +549,94 @@ func TestDataTreeNavigatorEquals(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorEqualsSelf(t *testing.T) { + + nodes := readDoc(t, `a: frog +b: cat +c: frog`) + + path, errPath := treeCreator.ParsePath("(a or b).(. == frog)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!str, Kind: ScalarNode, Anchor: + frog +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorEqualsNested(t *testing.T) { + + nodes := readDoc(t, `a: {t: frog} +b: {t: cat} +c: {t: frog}`) + + path, errPath := treeCreator.ParsePath("(t == frog)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!map, Kind: MappingNode, Anchor: + {t: frog} + +-- Node -- + Document 0, path: [c] + Tag: !!map, Kind: MappingNode, Anchor: + {t: frog} +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorArrayEqualsSelf(t *testing.T) { + + nodes := readDoc(t, `- cat +- dog +- frog`) + + path, errPath := treeCreator.ParsePath("*(. == *og)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [1] + Tag: !!str, Kind: ScalarNode, Anchor: + dog + +-- Node -- + Document 0, path: [2] + Tag: !!str, Kind: ScalarNode, Anchor: + frog +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorArrayEquals(t *testing.T) { nodes := readDoc(t, `- { b: apple, animal: rabbit } diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index 26ea678e..c23ae0f8 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -77,37 +77,59 @@ func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathN var results = orderedmap.NewOrderedMap() for el := matchMap.Front(); el != nil; el = el.Next() { - children, err := splatNode(d, el.Value.(*CandidateNode)) - log.Debugf("-- splatted matches, ") - if err != nil { - return nil, err - } - for childEl := children.Front(); childEl != nil; childEl = childEl.Next() { - childMap := orderedmap.NewOrderedMap() - childMap.Set(childEl.Key, childEl.Value) - childMatches, errChild := d.getMatchingNodes(childMap, pathNode.Lhs) - if errChild != nil { - return nil, errChild - } + candidate := el.Value.(*CandidateNode) + valuePattern := pathNode.Rhs.PathElement.StringValue + log.Debug("checking %v", candidate) - if containsMatchingValue(childMatches, pathNode.Rhs.PathElement.StringValue) { - results.Set(childEl.Key, childEl.Value) + if pathNode.Lhs.PathElement.PathElementType == SelfReference { + if Match(candidate.Node.Value, valuePattern) { + results.Set(el.Key, el.Value) + } + } else { + errInChild := findMatchingChildren(d, results, candidate, pathNode.Lhs, valuePattern) + if errInChild != nil { + return nil, errInChild } } + } return results, nil } +func findMatchingChildren(d *dataTreeNavigator, results *orderedmap.OrderedMap, candidate *CandidateNode, lhs *PathTreeNode, valuePattern string) error { + children, err := splatNode(d, candidate) + log.Debugf("-- splatted matches, ") + if err != nil { + return err + } + for childEl := children.Front(); childEl != nil; childEl = childEl.Next() { + childMap := orderedmap.NewOrderedMap() + childMap.Set(childEl.Key, childEl.Value) + childMatches, errChild := d.getMatchingNodes(childMap, lhs) + log.Debug("got the LHS") + if errChild != nil { + return errChild + } + + if containsMatchingValue(childMatches, valuePattern) { + results.Set(childEl.Key, childEl.Value) + } + } + return nil +} + func containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool { log.Debugf("-- findMatchingValues") for el := matchMap.Front(); el != nil; el = el.Next() { node := el.Value.(*CandidateNode) + log.Debugf("-- compating %v to %v", node.Node.Value, valuePattern) if Match(node.Node.Value, valuePattern) { return true } } + log.Debugf("-- done findMatchingValues") return false } diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index 42cb523e..2d8f1081 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -3,8 +3,6 @@ package treeops import ( "errors" "fmt" - - lex "github.com/timtadh/lexmachine" ) var precedenceMap map[int]int @@ -15,6 +13,9 @@ const ( PathKey PathElementType = 1 << iota ArrayIndex Operation + SelfReference + OpenBracket + CloseBracket ) type OperationType uint32 @@ -25,7 +26,6 @@ const ( Or And Equals - EqualsSelf Assign DeleteChild ) @@ -45,6 +45,8 @@ func (p *PathElement) toString() string { result = result + fmt.Sprintf("PathKey - '%v'\n", p.Value) case ArrayIndex: result = result + fmt.Sprintf("ArrayIndex - '%v'\n", p.Value) + case SelfReference: + result = result + fmt.Sprintf("SELF\n") case Operation: result = result + "Operation - " switch p.OperationType { @@ -54,8 +56,6 @@ func (p *PathElement) toString() string { result = result + "AND\n" case Equals: result = result + "EQUALS\n" - case EqualsSelf: - result = result + "EQUALS SELF\n" case Assign: result = result + "ASSIGN\n" case Traverse: @@ -69,43 +69,13 @@ func (p *PathElement) toString() string { return result } -var operationTypeMapper map[int]OperationType - -func initMaps() { - precedenceMap = make(map[int]int) - operationTypeMapper = make(map[int]OperationType) - - precedenceMap[TokenIds["("]] = 0 - - precedenceMap[TokenIds["OR_OPERATOR"]] = 10 - operationTypeMapper[TokenIds["OR_OPERATOR"]] = Or - - precedenceMap[TokenIds["AND_OPERATOR"]] = 20 - operationTypeMapper[TokenIds["AND_OPERATOR"]] = And - - precedenceMap[TokenIds["EQUALS_OPERATOR"]] = 30 - operationTypeMapper[TokenIds["EQUALS_OPERATOR"]] = Equals - - precedenceMap[TokenIds["EQUALS_SELF_OPERATOR"]] = 30 - operationTypeMapper[TokenIds["EQUALS_SELF_OPERATOR"]] = EqualsSelf - - precedenceMap[TokenIds["DELETE_CHILD_OPERATOR"]] = 30 - operationTypeMapper[TokenIds["DELETE_CHILD_OPERATOR"]] = DeleteChild - - precedenceMap[TokenIds["ASSIGN_OPERATOR"]] = 35 - operationTypeMapper[TokenIds["ASSIGN_OPERATOR"]] = Assign - - precedenceMap[TokenIds["TRAVERSE_OPERATOR"]] = 40 - operationTypeMapper[TokenIds["TRAVERSE_OPERATOR"]] = Traverse -} - -func createOperationPathElement(opToken *lex.Token) PathElement { - var pathElement = PathElement{PathElementType: Operation, OperationType: operationTypeMapper[opToken.Type]} +func createOperationPathElement(opToken *Token) PathElement { + var pathElement = PathElement{PathElementType: Operation, OperationType: opToken.OperationType} return pathElement } type PathPostFixer interface { - ConvertToPostfix([]*lex.Token) ([]*PathElement, error) + ConvertToPostfix([]*Token) ([]*PathElement, error) } type pathPostFixer struct { @@ -115,28 +85,29 @@ func NewPathPostFixer() PathPostFixer { return &pathPostFixer{} } -func popOpToResult(opStack []*lex.Token, result []*PathElement) ([]*lex.Token, []*PathElement) { - var operatorToPushToPostFix *lex.Token +func popOpToResult(opStack []*Token, result []*PathElement) ([]*Token, []*PathElement) { + var operatorToPushToPostFix *Token opStack, operatorToPushToPostFix = opStack[0:len(opStack)-1], opStack[len(opStack)-1] var pathElement = createOperationPathElement(operatorToPushToPostFix) return opStack, append(result, &pathElement) } -func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathElement, error) { +func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, error) { var result []*PathElement // surround the whole thing with quotes - var opStack = []*lex.Token{&lex.Token{Type: TokenIds["("]}} - var tokens = append(infixTokens, &lex.Token{Type: TokenIds[")"]}) + var opStack = []*Token{&Token{PathElementType: OpenBracket}} + var tokens = append(infixTokens, &Token{PathElementType: CloseBracket}) for _, token := range tokens { - switch token.Type { - case TokenIds["PATH_KEY"], TokenIds["ARRAY_INDEX"], TokenIds["[+]"], TokenIds["[*]"], TokenIds["**"]: - var pathElement = PathElement{PathElementType: PathKey, Value: token.Value, StringValue: fmt.Sprintf("%v", token.Value)} + switch token.PathElementType { + case PathKey, ArrayIndex, SelfReference: + var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue} result = append(result, &pathElement) - case TokenIds["("]: + case OpenBracket: opStack = append(opStack, token) - case TokenIds[")"]: - for len(opStack) > 0 && opStack[len(opStack)-1].Type != TokenIds["("] { + + case CloseBracket: + for len(opStack) > 0 && opStack[len(opStack)-1].PathElementType != OpenBracket { opStack, result = popOpToResult(opStack, result) } if len(opStack) == 0 { @@ -144,10 +115,11 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme } // now we should have ( as the last element on the opStack, get rid of it opStack = opStack[0 : len(opStack)-1] + default: - var currentPrecedence = precedenceMap[token.Type] + var currentPrecedence = p.precendenceOf(token) // pop off higher precedent operators onto the result - for len(opStack) > 0 && precedenceMap[opStack[len(opStack)-1].Type] >= currentPrecedence { + for len(opStack) > 0 && p.precendenceOf(opStack[len(opStack)-1]) >= currentPrecedence { opStack, result = popOpToResult(opStack, result) } // add this operator to the opStack @@ -156,3 +128,19 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*lex.Token) ([]*PathEleme } return result, nil } + +func (p *pathPostFixer) precendenceOf(token *Token) int { + switch token.OperationType { + case Or: + return 10 + case And: + return 20 + case Equals, DeleteChild: + return 30 + case Assign: + return 35 + case Traverse: + return 40 + } + return 0 +} diff --git a/pkg/yqlib/treeops/path_postfix_test.go b/pkg/yqlib/treeops/path_postfix_test.go index e27962e8..f9313e45 100644 --- a/pkg/yqlib/treeops/path_postfix_test.go +++ b/pkg/yqlib/treeops/path_postfix_test.go @@ -26,8 +26,16 @@ func testExpression(expression string) (string, error) { } func TestPostFixArrayEquals(t *testing.T) { - var infix = "a" - var expectedOutput = `PathKey - 'a' + var infix = "animals(.== cat)" + var expectedOutput = `PathKey - 'animals' +-------- +SELF +-------- +PathKey - 'cat' +-------- +Operation - EQUALS +-------- +Operation - TRAVERSE -------- ` diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 44b29313..591ca12b 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -2,54 +2,48 @@ package treeops import ( "strconv" - "strings" lex "github.com/timtadh/lexmachine" "github.com/timtadh/lexmachine/machines" ) -var Literals []string // The tokens representing literal strings -var Keywords []string // The keyword tokens -var Tokens []string // All of the tokens (including literals and keywords) -var TokenIds map[string]int // A map from the token names to their int ids - -var bracketLiterals []string - -func initTokens() { - bracketLiterals = []string{"(", ")"} - Literals = []string{ // these need a traverse operator infront - "[+]", - "[*]", - "**", - } - Tokens = []string{ - "OR_OPERATOR", - "AND_OPERATOR", - "EQUALS_OPERATOR", - "EQUALS_SELF_OPERATOR", - "ASSIGN_OPERATOR", - "DELETE_CHILD_OPERATOR", - "TRAVERSE_OPERATOR", - "PATH_KEY", // apples - "ARRAY_INDEX", // 123 - } - Tokens = append(Tokens, bracketLiterals...) - Tokens = append(Tokens, Literals...) - TokenIds = make(map[string]int) - for i, tok := range Tokens { - TokenIds[tok] = i - } - - initMaps() -} - func skip(*lex.Scanner, *machines.Match) (interface{}, error) { return nil, nil } -func token(name string) lex.Action { +type Token struct { + PathElementType PathElementType + OperationType OperationType + Value interface{} + StringValue string + AgainstSelf bool + + CheckForPreTraverse bool // this token can sometimes have the traverse '.' missing in frnot of it + // e.g. a[1] should really be a.[1] + CheckForPostTraverse bool // samething but for post, e.g. [1]cat should really be [1].cat + +} + +func pathToken(wrapped bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return s.Token(TokenIds[name], string(m.Bytes), m), nil + value := string(m.Bytes) + if wrapped { + value = unwrap(value) + } + return &Token{PathElementType: PathKey, OperationType: None, Value: value, StringValue: value}, nil + } +} + +func opToken(op OperationType, againstSelf bool) lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + value := string(m.Bytes) + return &Token{PathElementType: Operation, OperationType: op, Value: value, StringValue: value, AgainstSelf: againstSelf}, nil + } +} + +func literalToken(pType PathElementType, literal string, checkForPre bool, checkForPost bool) lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + return &Token{PathElementType: pType, Value: literal, StringValue: literal, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost}, nil } } @@ -57,13 +51,7 @@ func unwrap(value string) string { return value[1 : len(value)-1] } -func wrappedToken(name string) lex.Action { - return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return s.Token(TokenIds[name], unwrap(string(m.Bytes)), m), nil - } -} - -func numberToken(name string, wrapped bool) lex.Action { +func arrayIndextoken(wrapped bool, checkForPre bool, checkForPost bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { var numberString = string(m.Bytes) if wrapped { @@ -73,33 +61,40 @@ func numberToken(name string, wrapped bool) lex.Action { if errParsingInt != nil { return nil, errParsingInt } - return s.Token(TokenIds[name], number, m), nil + return &Token{PathElementType: ArrayIndex, Value: number, StringValue: numberString, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost}, nil } } // Creates the lexer object and compiles the NFA. func initLexer() (*lex.Lexer, error) { lexer := lex.NewLexer() - for _, lit := range bracketLiterals { - r := "\\" + strings.Join(strings.Split(lit, ""), "\\") - lexer.Add([]byte(r), token(lit)) - } - for _, lit := range Literals { - r := "\\" + strings.Join(strings.Split(lit, ""), "\\") - lexer.Add([]byte(r), token(lit)) - } - lexer.Add([]byte(`([Oo][Rr])`), token("OR_OPERATOR")) - lexer.Add([]byte(`([Aa][Nn][Dd])`), token("AND_OPERATOR")) - lexer.Add([]byte(`\.\s*==\s*`), token("EQUALS_SELF_OPERATOR")) - lexer.Add([]byte(`\s*==\s*`), token("EQUALS_OPERATOR")) - lexer.Add([]byte(`\s*.-\s*`), token("DELETE_CHILD_OPERATOR")) - lexer.Add([]byte(`\s*:=\s*`), token("ASSIGN_OPERATOR")) - lexer.Add([]byte(`\[-?[0-9]+\]`), numberToken("ARRAY_INDEX", true)) - lexer.Add([]byte(`-?[0-9]+`), numberToken("ARRAY_INDEX", false)) + lexer.Add([]byte(`\(`), literalToken(OpenBracket, "(", true, false)) + lexer.Add([]byte(`\)`), literalToken(CloseBracket, ")", false, true)) + + lexer.Add([]byte(`\[\+\]`), literalToken(PathKey, "[+]", true, true)) + lexer.Add([]byte(`\[\*\]`), literalToken(PathKey, "[*]", true, true)) + lexer.Add([]byte(`\*\*`), literalToken(PathKey, "**", false, false)) + + lexer.Add([]byte(`([Oo][Rr])`), opToken(Or, false)) + lexer.Add([]byte(`([Aa][Nn][Dd])`), opToken(And, false)) + + lexer.Add([]byte(`\.\s*==\s*`), opToken(Equals, true)) + lexer.Add([]byte(`\s*==\s*`), opToken(Equals, false)) + + lexer.Add([]byte(`\.\s*.-\s*`), opToken(DeleteChild, true)) + lexer.Add([]byte(`\s*.-\s*`), opToken(DeleteChild, false)) + + lexer.Add([]byte(`\.\s*:=\s*`), opToken(Assign, true)) + lexer.Add([]byte(`\s*:=\s*`), opToken(Assign, false)) + + lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(true, true, true)) + lexer.Add([]byte(`-?[0-9]+`), arrayIndextoken(false, false, false)) lexer.Add([]byte("( |\t|\n|\r)+"), skip) - lexer.Add([]byte(`"[^ "]+"`), wrappedToken("PATH_KEY")) - lexer.Add([]byte(`[^ \.\[\(\)=]+`), token("PATH_KEY")) - lexer.Add([]byte(`\.`), token("TRAVERSE_OPERATOR")) + + lexer.Add([]byte(`"[^ "]+"`), pathToken(true)) + lexer.Add([]byte(`[^ \.\[\(\)=]+`), pathToken(false)) + + lexer.Add([]byte(`\.`), opToken(Traverse, false)) err := lexer.Compile() if err != nil { return nil, err @@ -108,7 +103,7 @@ func initLexer() (*lex.Lexer, error) { } type PathTokeniser interface { - Tokenise(path string) ([]*lex.Token, error) + Tokenise(path string) ([]*Token, error) } type pathTokeniser struct { @@ -116,7 +111,6 @@ type pathTokeniser struct { } func NewPathTokeniser() PathTokeniser { - initTokens() var lexer, err = initLexer() if err != nil { panic(err) @@ -124,38 +118,40 @@ func NewPathTokeniser() PathTokeniser { return &pathTokeniser{lexer} } -func (p *pathTokeniser) Tokenise(path string) ([]*lex.Token, error) { +func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { scanner, err := p.lexer.Scanner([]byte(path)) if err != nil { return nil, err } - var tokens []*lex.Token + var tokens []*Token for tok, err, eof := scanner.Next(); !eof; tok, err, eof = scanner.Next() { if tok != nil { - token := tok.(*lex.Token) - log.Debugf("Tokenising %v - %v", token.Value, Tokens[token.Type]) + token := tok.(*Token) + log.Debugf("Tokenising %v", token.Value) tokens = append(tokens, token) } if err != nil { return nil, err } } - var postProcessedTokens []*lex.Token = make([]*lex.Token, 0) + var postProcessedTokens = make([]*Token, 0) for index, token := range tokens { - for _, literalTokenDef := range append(Literals, "ARRAY_INDEX", "(") { - if index > 0 && token.Type == TokenIds[literalTokenDef] && tokens[index-1].Type == TokenIds["PATH_KEY"] { - postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) - } + if index > 0 && token.CheckForPreTraverse && + (tokens[index-1].PathElementType == PathKey || tokens[index-1].PathElementType == CloseBracket) { + postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: Operation, OperationType: Traverse, Value: "."}) + } + if token.PathElementType == Operation && token.AgainstSelf { + postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: SelfReference, Value: "SELF"}) } postProcessedTokens = append(postProcessedTokens, token) - for _, literalTokenDef := range append(Literals, "ARRAY_INDEX", ")") { - if index != len(tokens)-1 && token.Type == TokenIds[literalTokenDef] && tokens[index+1].Type == TokenIds["PATH_KEY"] { - postProcessedTokens = append(postProcessedTokens, &lex.Token{Type: TokenIds["TRAVERSE_OPERATOR"], Value: "."}) - } + + if index != len(tokens)-1 && token.CheckForPostTraverse && + tokens[index+1].PathElementType == PathKey { + postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: Operation, OperationType: Traverse, Value: "."}) } } diff --git a/pkg/yqlib/treeops/path_tokeniser_test.go b/pkg/yqlib/treeops/path_tokeniser_test.go index edfd25dd..b84d0cc1 100644 --- a/pkg/yqlib/treeops/path_tokeniser_test.go +++ b/pkg/yqlib/treeops/path_tokeniser_test.go @@ -33,10 +33,10 @@ var tokeniserTests = []struct { {"a", append(make([]interface{}, 0), "a")}, {"\"a.b\".c", append(make([]interface{}, 0), "a.b", ".", "c")}, {`b."foo.bar"`, append(make([]interface{}, 0), "b", ".", "foo.bar")}, - {"animals(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", ".==", "cat", ")")}, - {"animals.(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", ".==", "cat", ")")}, - {"animals(. == cat)", append(make([]interface{}, 0), "animals", ".", "(", ". == ", "cat", ")")}, - {"animals(.==c*)", append(make([]interface{}, 0), "animals", ".", "(", ".==", "c*", ")")}, + {"animals(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "cat", ")")}, + {"animals.(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "cat", ")")}, + {"animals(. == cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ". == ", "cat", ")")}, + {"animals(.==c*)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "c*", ")")}, {"animals(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, {"animals.(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, {"(a.b==c*).animals", append(make([]interface{}, 0), "(", "a", ".", "b", "==", "c*", ")", ".", "animals")}, From 1ba1e90e588573c7bdbe05076613189dce04e2bb Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 11 Oct 2020 11:45:20 +1100 Subject: [PATCH 026/129] dont splat scalars --- pkg/yqlib/treeops/data_tree_navigator.go | 7 ++-- pkg/yqlib/treeops/data_tree_navigator_test.go | 2 +- pkg/yqlib/treeops/operators.go | 40 ++++++++++++------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 6cffe9ce..5fb47369 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -74,15 +74,16 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { log.Debugf("Processing Path: %v", pathNode.PathElement.toString()) - if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { + if pathNode.PathElement.PathElementType == SelfReference { + return matchingNodes, nil + } else if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { return d.traverse(matchingNodes, pathNode.PathElement) } else { handler := d.operatorHandlers[pathNode.PathElement.OperationType] if handler != nil { return handler(d, matchingNodes, pathNode) - } else { - return nil, fmt.Errorf("Unknown operator %v", pathNode.PathElement.OperationType) } + return nil, fmt.Errorf("Unknown operator %v", pathNode.PathElement.OperationType) } } diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 669cc4fc..b882ccf5 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -612,7 +612,7 @@ func TestDataTreeNavigatorArrayEqualsSelf(t *testing.T) { - dog - frog`) - path, errPath := treeCreator.ParsePath("*(. == *og)") + path, errPath := treeCreator.ParsePath("(. == *og)") if errPath != nil { t.Error(errPath) } diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index c23ae0f8..35648359 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -1,6 +1,9 @@ package treeops -import "github.com/elliotchance/orderedmap" +import ( + "github.com/elliotchance/orderedmap" + "gopkg.in/yaml.v3" +) type OperatorHandler func(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) @@ -81,16 +84,16 @@ func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathN valuePattern := pathNode.Rhs.PathElement.StringValue log.Debug("checking %v", candidate) - if pathNode.Lhs.PathElement.PathElementType == SelfReference { - if Match(candidate.Node.Value, valuePattern) { - results.Set(el.Key, el.Value) - } - } else { - errInChild := findMatchingChildren(d, results, candidate, pathNode.Lhs, valuePattern) - if errInChild != nil { - return nil, errInChild - } + // if pathNode.Lhs.PathElement.PathElementType == SelfReference { + // if Match(candidate.Node.Value, valuePattern) { + // results.Set(el.Key, el.Value) + // } + // } else { + errInChild := findMatchingChildren(d, results, candidate, pathNode.Lhs, valuePattern) + if errInChild != nil { + return nil, errInChild } + // } } @@ -98,11 +101,20 @@ func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathN } func findMatchingChildren(d *dataTreeNavigator, results *orderedmap.OrderedMap, candidate *CandidateNode, lhs *PathTreeNode, valuePattern string) error { - children, err := splatNode(d, candidate) - log.Debugf("-- splatted matches, ") - if err != nil { - return err + var children *orderedmap.OrderedMap + var err error + // don't splat scalars. + if candidate.Node.Kind != yaml.ScalarNode { + children, err = splatNode(d, candidate) + log.Debugf("-- splatted matches, ") + if err != nil { + return err + } + } else { + children = orderedmap.NewOrderedMap() + children.Set(candidate.getKey(), candidate) } + for childEl := children.Front(); childEl != nil; childEl = childEl.Next() { childMap := orderedmap.NewOrderedMap() childMap.Set(childEl.Key, childEl.Value) From b025000f2065d1f5ae7c1ee416fb24effba0a299 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 11 Oct 2020 11:46:07 +1100 Subject: [PATCH 027/129] cool, both work --- pkg/yqlib/treeops/data_tree_navigator_test.go | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index b882ccf5..7d87ebf9 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -637,6 +637,37 @@ func TestDataTreeNavigatorArrayEqualsSelf(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorArrayEqualsSelfSplatFirst(t *testing.T) { + + nodes := readDoc(t, `- cat +- dog +- frog`) + + path, errPath := treeCreator.ParsePath("*(. == *og)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [1] + Tag: !!str, Kind: ScalarNode, Anchor: + dog + +-- Node -- + Document 0, path: [2] + Tag: !!str, Kind: ScalarNode, Anchor: + frog +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorArrayEquals(t *testing.T) { nodes := readDoc(t, `- { b: apple, animal: rabbit } From 7c4cf724687d8e86bb39ca5accccd1289d03e343 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 12 Oct 2020 10:09:13 +1100 Subject: [PATCH 028/129] wip --- pkg/yqlib/treeops/data_tree_navigator_test.go | 142 +++++++++++++++++- pkg/yqlib/treeops/delete_operator.go | 4 +- pkg/yqlib/treeops/operators.go | 7 - pkg/yqlib/treeops/path_tokeniser.go | 21 +-- 4 files changed, 150 insertions(+), 24 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 7d87ebf9..0753eae5 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -18,7 +18,7 @@ func readDoc(t *testing.T, content string) []*CandidateNode { if err != nil { t.Error(err) } - return []*CandidateNode{&CandidateNode{Node: &dataBucket, Document: 0}} + return []*CandidateNode{&CandidateNode{Node: dataBucket.Content[0], Document: 0}} } func resultsToString(results []*CandidateNode) string { @@ -54,7 +54,7 @@ func TestDataTreeNavigatorSimple(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorSubtractSimple(t *testing.T) { +func TestDataTreeNavigatorDeleteSimple(t *testing.T) { nodes := readDoc(t, `a: b: apple @@ -79,7 +79,7 @@ func TestDataTreeNavigatorSubtractSimple(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorSubtractTwice(t *testing.T) { +func TestDataTreeNavigatorDeleteTwice(t *testing.T) { nodes := readDoc(t, `a: b: apple @@ -106,7 +106,7 @@ func TestDataTreeNavigatorSubtractTwice(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorSubtractWithUnion(t *testing.T) { +func TestDataTreeNavigatorDeleteWithUnion(t *testing.T) { nodes := readDoc(t, `a: b: apple @@ -133,7 +133,7 @@ func TestDataTreeNavigatorSubtractWithUnion(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorSubtractByIndex(t *testing.T) { +func TestDataTreeNavigatorDeleteByIndex(t *testing.T) { nodes := readDoc(t, `a: - b: apple @@ -160,6 +160,86 @@ func TestDataTreeNavigatorSubtractByIndex(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorDeleteByFind(t *testing.T) { + + nodes := readDoc(t, `a: + - b: apple + - b: sdfsd + - b: apple`) + + path, errPath := treeCreator.ParsePath("(a .- (* == apple))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!seq, Kind: SequenceNode, Anchor: + - b: sdfsd +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorDeleteArrayByFind(t *testing.T) { + + nodes := readDoc(t, `a: + - apple + - sdfsd + - apple`) + + path, errPath := treeCreator.ParsePath("(a .- (. == apple))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!seq, Kind: SequenceNode, Anchor: + - sdfsd +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorDeleteViaSelf(t *testing.T) { + + nodes := readDoc(t, `- apple +- sdfsd +- apple`) + + path, errPath := treeCreator.ParsePath("(. .- .)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [] + Tag: !!seq, Kind: SequenceNode, Anchor: + - sdfsd +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorDeleteAndWrite(t *testing.T) { nodes := readDoc(t, `a: @@ -192,7 +272,7 @@ func TestDataTreeNavigatorDeleteAndWrite(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorSubtractArray(t *testing.T) { +func TestDataTreeNavigatorDeleteArray(t *testing.T) { nodes := readDoc(t, `a: - b: apple @@ -268,6 +348,56 @@ func TestDataTreeNavigatorSimpleAssign(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorSimpleAssignSelf(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple`) + + path, errPath := treeCreator.ParsePath("a(. == apple)(. := frog)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a b] + Tag: !!str, Kind: ScalarNode, Anchor: + frog +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorSimpleAssignByFind(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple`) + + path, errPath := treeCreator.ParsePath("(b == apple) := frog)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a b] + Tag: !!str, Kind: ScalarNode, Anchor: + frog +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorArraySplat(t *testing.T) { nodes := readDoc(t, `- b: apple diff --git a/pkg/yqlib/treeops/delete_operator.go b/pkg/yqlib/treeops/delete_operator.go index de3faf1e..c86354a5 100644 --- a/pkg/yqlib/treeops/delete_operator.go +++ b/pkg/yqlib/treeops/delete_operator.go @@ -26,8 +26,10 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered if candidate.Node.Kind == yaml.SequenceNode { deleteFromArray(candidate, nodesToDelete) - } else { + } else if candidate.Node.Kind == yaml.MappingNode { deleteFromMap(candidate, nodesToDelete) + } else { + log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate)) } } return lhs, nil diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index 35648359..c2de7180 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -84,17 +84,10 @@ func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathN valuePattern := pathNode.Rhs.PathElement.StringValue log.Debug("checking %v", candidate) - // if pathNode.Lhs.PathElement.PathElementType == SelfReference { - // if Match(candidate.Node.Value, valuePattern) { - // results.Set(el.Key, el.Value) - // } - // } else { errInChild := findMatchingChildren(d, results, candidate, pathNode.Lhs, valuePattern) if errInChild != nil { return nil, errInChild } - // } - } return results, nil diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 591ca12b..57bca21c 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -16,7 +16,7 @@ type Token struct { OperationType OperationType Value interface{} StringValue string - AgainstSelf bool + PrefixSelf bool CheckForPreTraverse bool // this token can sometimes have the traverse '.' missing in frnot of it // e.g. a[1] should really be a.[1] @@ -37,13 +37,13 @@ func pathToken(wrapped bool) lex.Action { func opToken(op OperationType, againstSelf bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) - return &Token{PathElementType: Operation, OperationType: op, Value: value, StringValue: value, AgainstSelf: againstSelf}, nil + return &Token{PathElementType: Operation, OperationType: op, Value: value, StringValue: value, PrefixSelf: againstSelf}, nil } } -func literalToken(pType PathElementType, literal string, checkForPre bool, checkForPost bool) lex.Action { +func literalToken(pType PathElementType, literal string, checkForPre bool, checkForPost bool, againstSelf bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{PathElementType: pType, Value: literal, StringValue: literal, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost}, nil + return &Token{PathElementType: pType, Value: literal, StringValue: literal, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost, PrefixSelf: againstSelf}, nil } } @@ -68,12 +68,13 @@ func arrayIndextoken(wrapped bool, checkForPre bool, checkForPost bool) lex.Acti // Creates the lexer object and compiles the NFA. func initLexer() (*lex.Lexer, error) { lexer := lex.NewLexer() - lexer.Add([]byte(`\(`), literalToken(OpenBracket, "(", true, false)) - lexer.Add([]byte(`\)`), literalToken(CloseBracket, ")", false, true)) + lexer.Add([]byte(`\(`), literalToken(OpenBracket, "(", true, false, false)) + lexer.Add([]byte(`\)`), literalToken(CloseBracket, ")", false, true, false)) + lexer.Add([]byte(`\.\s*\)`), literalToken(CloseBracket, ")", false, true, true)) - lexer.Add([]byte(`\[\+\]`), literalToken(PathKey, "[+]", true, true)) - lexer.Add([]byte(`\[\*\]`), literalToken(PathKey, "[*]", true, true)) - lexer.Add([]byte(`\*\*`), literalToken(PathKey, "**", false, false)) + lexer.Add([]byte(`\[\+\]`), literalToken(PathKey, "[+]", true, true, false)) + lexer.Add([]byte(`\[\*\]`), literalToken(PathKey, "[*]", true, true, false)) + lexer.Add([]byte(`\*\*`), literalToken(PathKey, "**", false, false, false)) lexer.Add([]byte(`([Oo][Rr])`), opToken(Or, false)) lexer.Add([]byte(`([Aa][Nn][Dd])`), opToken(And, false)) @@ -143,7 +144,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { (tokens[index-1].PathElementType == PathKey || tokens[index-1].PathElementType == CloseBracket) { postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: Operation, OperationType: Traverse, Value: "."}) } - if token.PathElementType == Operation && token.AgainstSelf { + if token.PrefixSelf { postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: SelfReference, Value: "SELF"}) } From 288aec942c2259370545f5e285e2a4dc149eba93 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 12 Oct 2020 10:44:33 +1100 Subject: [PATCH 029/129] ops first class --- pkg/yqlib/treeops/data_tree_navigator.go | 16 +--- pkg/yqlib/treeops/data_tree_navigator_test.go | 4 +- pkg/yqlib/treeops/lib.go | 51 ++++++++++ pkg/yqlib/treeops/operators.go | 2 +- pkg/yqlib/treeops/path_postfix.go | 96 +------------------ pkg/yqlib/treeops/path_tokeniser.go | 11 +-- 6 files changed, 67 insertions(+), 113 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 5fb47369..1a33c1e8 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -7,8 +7,7 @@ import ( ) type dataTreeNavigator struct { - leafTraverser LeafTraverser - operatorHandlers map[OperationType]OperatorHandler + leafTraverser LeafTraverser } type NavigationPrefs struct { @@ -21,16 +20,7 @@ type DataTreeNavigator interface { func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { leafTraverser := NewLeafTraverser(navigationPrefs) - operatorHandlers := make(map[OperationType]OperatorHandler) - - operatorHandlers[Traverse] = TraverseOperator - operatorHandlers[Equals] = EqualsOperator - operatorHandlers[Or] = UnionOperator - operatorHandlers[And] = IntersectionOperator - operatorHandlers[Assign] = AssignOperator - operatorHandlers[DeleteChild] = DeleteChildOperator - - return &dataTreeNavigator{leafTraverser, operatorHandlers} + return &dataTreeNavigator{leafTraverser} } func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *PathElement) (*orderedmap.OrderedMap, error) { @@ -79,7 +69,7 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa } else if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { return d.traverse(matchingNodes, pathNode.PathElement) } else { - handler := d.operatorHandlers[pathNode.PathElement.OperationType] + handler := pathNode.PathElement.OperationType.Handler if handler != nil { return handler(d, matchingNodes, pathNode) } diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 0753eae5..ddead453 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -220,7 +220,7 @@ func TestDataTreeNavigatorDeleteViaSelf(t *testing.T) { - sdfsd - apple`) - path, errPath := treeCreator.ParsePath("(. .- .)") + path, errPath := treeCreator.ParsePath(". .- (. == apple)") if errPath != nil { t.Error(errPath) } @@ -378,7 +378,7 @@ func TestDataTreeNavigatorSimpleAssignByFind(t *testing.T) { nodes := readDoc(t, `a: b: apple`) - path, errPath := treeCreator.ParsePath("(b == apple) := frog)") + path, errPath := treeCreator.ParsePath("a(. == apple) := frog") if errPath != nil { t.Error(errPath) } diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 2f068c47..663871bc 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -21,6 +21,57 @@ func (n *CandidateNode) getKey() string { return fmt.Sprintf("%v - %v", n.Document, n.Path) } +type PathElementType uint32 + +const ( + PathKey PathElementType = 1 << iota + ArrayIndex + Operation + SelfReference + OpenBracket + CloseBracket +) + +type OperationType struct { + Type string + NumArgs uint // number of arguments to the op + Precedence uint + Handler OperatorHandler +} + +var None = &OperationType{Type: "NONE", NumArgs: 0, Precedence: 0} +var Traverse = &OperationType{Type: "TRAVERSE", NumArgs: 2, Precedence: 40, Handler: TraverseOperator} +var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 10, Handler: UnionOperator} +var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator} +var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 30, Handler: EqualsOperator} +var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 35, Handler: AssignOperator} +var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 30, Handler: DeleteChildOperator} + +// var Length = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} + +type PathElement struct { + PathElementType PathElementType + OperationType *OperationType + Value interface{} + StringValue string +} + +// debugging purposes only +func (p *PathElement) toString() string { + var result string = `` + switch p.PathElementType { + case PathKey: + result = result + fmt.Sprintf("PathKey - '%v'\n", p.Value) + case ArrayIndex: + result = result + fmt.Sprintf("ArrayIndex - '%v'\n", p.Value) + case SelfReference: + result = result + fmt.Sprintf("SELF\n") + case Operation: + result = result + fmt.Sprintf("Operation - %v\n", p.OperationType.Type) + } + return result +} + type YqTreeLib interface { Get(rootNode *yaml.Node, path string) ([]*CandidateNode, error) // GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index c2de7180..e8ebd272 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -129,7 +129,7 @@ func containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) for el := matchMap.Front(); el != nil; el = el.Next() { node := el.Value.(*CandidateNode) - log.Debugf("-- compating %v to %v", node.Node.Value, valuePattern) + log.Debugf("-- comparing %v to %v", node.Node.Value, valuePattern) if Match(node.Node.Value, valuePattern) { return true } diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index 2d8f1081..40419959 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -2,78 +2,8 @@ package treeops import ( "errors" - "fmt" ) -var precedenceMap map[int]int - -type PathElementType uint32 - -const ( - PathKey PathElementType = 1 << iota - ArrayIndex - Operation - SelfReference - OpenBracket - CloseBracket -) - -type OperationType uint32 - -const ( - None OperationType = 1 << iota - Traverse - Or - And - Equals - Assign - DeleteChild -) - -type PathElement struct { - PathElementType PathElementType - OperationType OperationType - Value interface{} - StringValue string -} - -// debugging purposes only -func (p *PathElement) toString() string { - var result string = `` - switch p.PathElementType { - case PathKey: - result = result + fmt.Sprintf("PathKey - '%v'\n", p.Value) - case ArrayIndex: - result = result + fmt.Sprintf("ArrayIndex - '%v'\n", p.Value) - case SelfReference: - result = result + fmt.Sprintf("SELF\n") - case Operation: - result = result + "Operation - " - switch p.OperationType { - case Or: - result = result + "OR\n" - case And: - result = result + "AND\n" - case Equals: - result = result + "EQUALS\n" - case Assign: - result = result + "ASSIGN\n" - case Traverse: - result = result + "TRAVERSE\n" - case DeleteChild: - result = result + "DELETE CHILD\n" - - } - - } - return result -} - -func createOperationPathElement(opToken *Token) PathElement { - var pathElement = PathElement{PathElementType: Operation, OperationType: opToken.OperationType} - return pathElement -} - type PathPostFixer interface { ConvertToPostfix([]*Token) ([]*PathElement, error) } @@ -88,15 +18,15 @@ func NewPathPostFixer() PathPostFixer { func popOpToResult(opStack []*Token, result []*PathElement) ([]*Token, []*PathElement) { var operatorToPushToPostFix *Token opStack, operatorToPushToPostFix = opStack[0:len(opStack)-1], opStack[len(opStack)-1] - var pathElement = createOperationPathElement(operatorToPushToPostFix) + var pathElement = PathElement{PathElementType: Operation, OperationType: operatorToPushToPostFix.OperationType} return opStack, append(result, &pathElement) } func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, error) { var result []*PathElement // surround the whole thing with quotes - var opStack = []*Token{&Token{PathElementType: OpenBracket}} - var tokens = append(infixTokens, &Token{PathElementType: CloseBracket}) + var opStack = []*Token{&Token{PathElementType: OpenBracket, OperationType: None}} + var tokens = append(infixTokens, &Token{PathElementType: CloseBracket, OperationType: None}) for _, token := range tokens { switch token.PathElementType { @@ -117,9 +47,9 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, opStack = opStack[0 : len(opStack)-1] default: - var currentPrecedence = p.precendenceOf(token) + var currentPrecedence = token.OperationType.Precedence // pop off higher precedent operators onto the result - for len(opStack) > 0 && p.precendenceOf(opStack[len(opStack)-1]) >= currentPrecedence { + for len(opStack) > 0 && opStack[len(opStack)-1].OperationType.Precedence >= currentPrecedence { opStack, result = popOpToResult(opStack, result) } // add this operator to the opStack @@ -128,19 +58,3 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, } return result, nil } - -func (p *pathPostFixer) precendenceOf(token *Token) int { - switch token.OperationType { - case Or: - return 10 - case And: - return 20 - case Equals, DeleteChild: - return 30 - case Assign: - return 35 - case Traverse: - return 40 - } - return 0 -} diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 57bca21c..8d6241ae 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -13,7 +13,7 @@ func skip(*lex.Scanner, *machines.Match) (interface{}, error) { type Token struct { PathElementType PathElementType - OperationType OperationType + OperationType *OperationType Value interface{} StringValue string PrefixSelf bool @@ -21,7 +21,6 @@ type Token struct { CheckForPreTraverse bool // this token can sometimes have the traverse '.' missing in frnot of it // e.g. a[1] should really be a.[1] CheckForPostTraverse bool // samething but for post, e.g. [1]cat should really be [1].cat - } func pathToken(wrapped bool) lex.Action { @@ -34,7 +33,7 @@ func pathToken(wrapped bool) lex.Action { } } -func opToken(op OperationType, againstSelf bool) lex.Action { +func opToken(op *OperationType, againstSelf bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) return &Token{PathElementType: Operation, OperationType: op, Value: value, StringValue: value, PrefixSelf: againstSelf}, nil @@ -43,7 +42,7 @@ func opToken(op OperationType, againstSelf bool) lex.Action { func literalToken(pType PathElementType, literal string, checkForPre bool, checkForPost bool, againstSelf bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{PathElementType: pType, Value: literal, StringValue: literal, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost, PrefixSelf: againstSelf}, nil + return &Token{PathElementType: pType, OperationType: None, Value: literal, StringValue: literal, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost, PrefixSelf: againstSelf}, nil } } @@ -61,7 +60,7 @@ func arrayIndextoken(wrapped bool, checkForPre bool, checkForPost bool) lex.Acti if errParsingInt != nil { return nil, errParsingInt } - return &Token{PathElementType: ArrayIndex, Value: number, StringValue: numberString, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost}, nil + return &Token{PathElementType: ArrayIndex, OperationType: None, Value: number, StringValue: numberString, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost}, nil } } @@ -130,7 +129,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { if tok != nil { token := tok.(*Token) - log.Debugf("Tokenising %v", token.Value) + log.Debugf("Tokenising %v - %v", token.Value, token.OperationType.Type) tokens = append(tokens, token) } if err != nil { From 6a0a4efa7b450bda4c742a48a15ea15cfbd5d582 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 12 Oct 2020 12:24:59 +1100 Subject: [PATCH 030/129] added single count operator --- pkg/yqlib/treeops/data_tree_navigator_test.go | 191 ++++++++++++++++++ pkg/yqlib/treeops/lib.go | 5 +- pkg/yqlib/treeops/operators.go | 26 +++ pkg/yqlib/treeops/path_postfix_test.go | 16 ++ pkg/yqlib/treeops/path_tokeniser.go | 3 +- pkg/yqlib/treeops/path_tokeniser_test.go | 86 ++++---- pkg/yqlib/treeops/path_tree.go | 18 +- 7 files changed, 297 insertions(+), 48 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index ddead453..dfc8ae1f 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -240,6 +240,197 @@ func TestDataTreeNavigatorDeleteViaSelf(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorCountWithFilter(t *testing.T) { + + nodes := readDoc(t, `f: + a: frog + b: dally + c: log`) + + path, errPath := treeCreator.ParsePath("f(count(. == *og))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [f] + Tag: !!int, Kind: ScalarNode, Anchor: + 2 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorCountWithFilter2(t *testing.T) { + + nodes := readDoc(t, `f: + a: frog + b: dally + c: log`) + + path, errPath := treeCreator.ParsePath("count(f(. == *og))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [] + Tag: !!int, Kind: ScalarNode, Anchor: + 2 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorCountMultipleMatchesInside(t *testing.T) { + + nodes := readDoc(t, `f: + a: [1,2] + b: dally + c: [3,4,5]`) + + path, errPath := treeCreator.ParsePath("f(count(a or c))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [f] + Tag: !!int, Kind: ScalarNode, Anchor: + 2 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorCountMultipleMatchesInsideSplat(t *testing.T) { + + nodes := readDoc(t, `f: + a: [1,2,3] + b: [1,2,3,4] + c: [1,2,3,4,5]`) + + path, errPath := treeCreator.ParsePath("f(count( (a or c)*))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [f] + Tag: !!int, Kind: ScalarNode, Anchor: + 8 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorCountMultipleMatchesOutside(t *testing.T) { + + nodes := readDoc(t, `f: + a: [1,2,3] + b: [1,2,3,4] + c: [1,2,3,4,5]`) + + path, errPath := treeCreator.ParsePath("f(a or c)(count(*))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [f a] + Tag: !!int, Kind: ScalarNode, Anchor: + 3 +-- Node -- + Document 0, path: [f c] + Tag: !!int, Kind: ScalarNode, Anchor: + 5 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorCountOfResults(t *testing.T) { + + nodes := readDoc(t, `- apple +- sdfsd +- apple`) + + path, errPath := treeCreator.ParsePath("count(*)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [] + Tag: !!int, Kind: ScalarNode, Anchor: + 3 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorCountNoMatches(t *testing.T) { + + nodes := readDoc(t, `- apple +- sdfsd +- apple`) + + path, errPath := treeCreator.ParsePath("count(5)") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [] + Tag: !!int, Kind: ScalarNode, Anchor: + 0 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorDeleteAndWrite(t *testing.T) { nodes := readDoc(t, `a: diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 663871bc..0ec9f272 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -47,7 +47,10 @@ var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 30, Handler: var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 35, Handler: AssignOperator} var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 30, Handler: DeleteChildOperator} -// var Length = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} +var Count = &OperationType{Type: "COUNT", NumArgs: 1, Precedence: 35, Handler: CountOperator} + +// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} +// filters matches if they have the existing path type PathElement struct { PathElementType PathElementType diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index e8ebd272..b6042ce3 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -1,6 +1,8 @@ package treeops import ( + "fmt" + "github.com/elliotchance/orderedmap" "gopkg.in/yaml.v3" ) @@ -93,6 +95,30 @@ func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathN return results, nil } +func CountOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- countOperation") + var results = orderedmap.NewOrderedMap() + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + elMap := orderedmap.NewOrderedMap() + elMap.Set(el.Key, el.Value) + childMatches, errChild := d.getMatchingNodes(elMap, pathNode.Rhs) + + if errChild != nil { + return nil, errChild + } + + length := childMatches.Len() + node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.Set(candidate.getKey(), lengthCand) + + } + + return results, nil +} + func findMatchingChildren(d *dataTreeNavigator, results *orderedmap.OrderedMap, candidate *CandidateNode, lhs *PathTreeNode, valuePattern string) error { var children *orderedmap.OrderedMap var err error diff --git a/pkg/yqlib/treeops/path_postfix_test.go b/pkg/yqlib/treeops/path_postfix_test.go index f9313e45..7559e8e8 100644 --- a/pkg/yqlib/treeops/path_postfix_test.go +++ b/pkg/yqlib/treeops/path_postfix_test.go @@ -47,6 +47,22 @@ Operation - TRAVERSE test.AssertResultComplex(t, expectedOutput, actual) } +func TestPostFixLength(t *testing.T) { + var infix = "len(a)" + var expectedOutput = `PathKey - 'a' +-------- +Operation - Length +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + func TestPostFixSimpleExample(t *testing.T) { var infix = "a" var expectedOutput = `PathKey - 'a' diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 8d6241ae..3dac8652 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -36,7 +36,7 @@ func pathToken(wrapped bool) lex.Action { func opToken(op *OperationType, againstSelf bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) - return &Token{PathElementType: Operation, OperationType: op, Value: value, StringValue: value, PrefixSelf: againstSelf}, nil + return &Token{PathElementType: Operation, OperationType: op, Value: op.Type, StringValue: value, PrefixSelf: againstSelf}, nil } } @@ -77,6 +77,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`([Oo][Rr])`), opToken(Or, false)) lexer.Add([]byte(`([Aa][Nn][Dd])`), opToken(And, false)) + lexer.Add([]byte(`([Cc][Oo][Uu][Nn][Tt])`), opToken(Count, false)) lexer.Add([]byte(`\.\s*==\s*`), opToken(Equals, true)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals, false)) diff --git a/pkg/yqlib/treeops/path_tokeniser_test.go b/pkg/yqlib/treeops/path_tokeniser_test.go index b84d0cc1..6c56a5ea 100644 --- a/pkg/yqlib/treeops/path_tokeniser_test.go +++ b/pkg/yqlib/treeops/path_tokeniser_test.go @@ -10,48 +10,50 @@ var tokeniserTests = []struct { path string expectedTokens []interface{} }{ // TODO: Ensure ALL documented examples have tests! sheesh - - {"a OR (b OR c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")}, - {"a .- (b OR c)", append(make([]interface{}, 0), "a", " .- ", "(", "b", "OR", "c", ")")}, - {"(animal==3)", append(make([]interface{}, 0), "(", "animal", "==", int64(3), ")")}, - {"(animal==f3)", append(make([]interface{}, 0), "(", "animal", "==", "f3", ")")}, - {"apples.BANANAS", append(make([]interface{}, 0), "apples", ".", "BANANAS")}, - {"appl*.BANA*", append(make([]interface{}, 0), "appl*", ".", "BANA*")}, - {"a.b.**", append(make([]interface{}, 0), "a", ".", "b", ".", "**")}, - {"a.\"=\".frog", append(make([]interface{}, 0), "a", ".", "=", ".", "frog")}, - {"a.b.*", append(make([]interface{}, 0), "a", ".", "b", ".", "*")}, - {"a.b.thin*", append(make([]interface{}, 0), "a", ".", "b", ".", "thin*")}, - {"a.b[0]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, - {"a.b.[0]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, - {"a.b[*]", append(make([]interface{}, 0), "a", ".", "b", ".", "[*]")}, - {"a.b.[*]", append(make([]interface{}, 0), "a", ".", "b", ".", "[*]")}, - {"a.b[+]", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]")}, - {"a.b.[+]", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]")}, - {"a.b[-12]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, - {"a.b.0", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, - // {"a.b.-12", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, - {"a", append(make([]interface{}, 0), "a")}, - {"\"a.b\".c", append(make([]interface{}, 0), "a.b", ".", "c")}, - {`b."foo.bar"`, append(make([]interface{}, 0), "b", ".", "foo.bar")}, - {"animals(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "cat", ")")}, - {"animals.(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "cat", ")")}, - {"animals(. == cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ". == ", "cat", ")")}, - {"animals(.==c*)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "c*", ")")}, - {"animals(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, - {"animals.(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, - {"(a.b==c*).animals", append(make([]interface{}, 0), "(", "a", ".", "b", "==", "c*", ")", ".", "animals")}, - {"(a.b==c*)animals", append(make([]interface{}, 0), "(", "a", ".", "b", "==", "c*", ")", ".", "animals")}, - {"[1].a.d", append(make([]interface{}, 0), int64(1), ".", "a", ".", "d")}, - {"[1]a.d", append(make([]interface{}, 0), int64(1), ".", "a", ".", "d")}, - {"a[0]c", append(make([]interface{}, 0), "a", ".", int64(0), ".", "c")}, - {"a.[0].c", append(make([]interface{}, 0), "a", ".", int64(0), ".", "c")}, - {"[0]", append(make([]interface{}, 0), int64(0))}, - {"0", append(make([]interface{}, 0), int64(0))}, - {"a.b[+]c", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]", ".", "c")}, - {"a.cool(s.d.f == cool)", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", " == ", "cool", ")")}, - {"a.cool.(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "OR", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "caterpillar")}, - {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "and", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "*")}, - {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "and", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "th*")}, + {"len(.)", append(make([]interface{}, 0), "LENGTH", "(", "SELF", ")")}, + {"\"len\"(.)", append(make([]interface{}, 0), "len", ".", "(", "SELF", ")")}, + // {"a OR (b OR c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")}, + // {"a OR (b OR c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")}, + // {"a .- (b OR c)", append(make([]interface{}, 0), "a", " .- ", "(", "b", "OR", "c", ")")}, + // {"(animal==3)", append(make([]interface{}, 0), "(", "animal", "==", int64(3), ")")}, + // {"(animal==f3)", append(make([]interface{}, 0), "(", "animal", "==", "f3", ")")}, + // {"apples.BANANAS", append(make([]interface{}, 0), "apples", ".", "BANANAS")}, + // {"appl*.BANA*", append(make([]interface{}, 0), "appl*", ".", "BANA*")}, + // {"a.b.**", append(make([]interface{}, 0), "a", ".", "b", ".", "**")}, + // {"a.\"=\".frog", append(make([]interface{}, 0), "a", ".", "=", ".", "frog")}, + // {"a.b.*", append(make([]interface{}, 0), "a", ".", "b", ".", "*")}, + // {"a.b.thin*", append(make([]interface{}, 0), "a", ".", "b", ".", "thin*")}, + // {"a.b[0]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, + // {"a.b.[0]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, + // {"a.b[*]", append(make([]interface{}, 0), "a", ".", "b", ".", "[*]")}, + // {"a.b.[*]", append(make([]interface{}, 0), "a", ".", "b", ".", "[*]")}, + // {"a.b[+]", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]")}, + // {"a.b.[+]", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]")}, + // {"a.b[-12]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, + // {"a.b.0", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, + // // {"a.b.-12", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, + // {"a", append(make([]interface{}, 0), "a")}, + // {"\"a.b\".c", append(make([]interface{}, 0), "a.b", ".", "c")}, + // {`b."foo.bar"`, append(make([]interface{}, 0), "b", ".", "foo.bar")}, + // {"animals(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "cat", ")")}, + // {"animals.(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "cat", ")")}, + // {"animals(. == cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ". == ", "cat", ")")}, + // {"animals(.==c*)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "c*", ")")}, + // {"animals(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, + // {"animals.(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, + // {"(a.b==c*).animals", append(make([]interface{}, 0), "(", "a", ".", "b", "==", "c*", ")", ".", "animals")}, + // {"(a.b==c*)animals", append(make([]interface{}, 0), "(", "a", ".", "b", "==", "c*", ")", ".", "animals")}, + // {"[1].a.d", append(make([]interface{}, 0), int64(1), ".", "a", ".", "d")}, + // {"[1]a.d", append(make([]interface{}, 0), int64(1), ".", "a", ".", "d")}, + // {"a[0]c", append(make([]interface{}, 0), "a", ".", int64(0), ".", "c")}, + // {"a.[0].c", append(make([]interface{}, 0), "a", ".", int64(0), ".", "c")}, + // {"[0]", append(make([]interface{}, 0), int64(0))}, + // {"0", append(make([]interface{}, 0), int64(0))}, + // {"a.b[+]c", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]", ".", "c")}, + // {"a.cool(s.d.f == cool)", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", " == ", "cool", ")")}, + // {"a.cool.(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "OR", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "caterpillar")}, + // {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "and", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "*")}, + // {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "and", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "th*")}, } var tokeniser = NewPathTokeniser() diff --git a/pkg/yqlib/treeops/path_tree.go b/pkg/yqlib/treeops/path_tree.go index a42b06f5..fe1353fb 100644 --- a/pkg/yqlib/treeops/path_tree.go +++ b/pkg/yqlib/treeops/path_tree.go @@ -42,10 +42,20 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeN for _, pathElement := range postFixPath { var newNode = PathTreeNode{PathElement: pathElement} if pathElement.PathElementType == Operation { - remaining, lhs, rhs := stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1] - newNode.Lhs = lhs - newNode.Rhs = rhs - stack = remaining + numArgs := pathElement.OperationType.NumArgs + if numArgs == 0 { + remaining := stack[:len(stack)-1] + stack = remaining + } else if numArgs == 1 { + remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1] + newNode.Rhs = rhs + stack = remaining + } else { + remaining, lhs, rhs := stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1] + newNode.Lhs = lhs + newNode.Rhs = rhs + stack = remaining + } } stack = append(stack, &newNode) } From d19e9f69172f0d3ac24fb024a349f00ade3ae312 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 13 Oct 2020 12:51:37 +1100 Subject: [PATCH 031/129] read command --- cmd/compare.go | 148 +- cmd/compare_test.go | 202 +-- cmd/constant.go | 3 +- cmd/delete.go | 72 +- cmd/delete_test.go | 428 +++--- cmd/merge.go | 222 +-- cmd/merge_test.go | 970 ++++++------- cmd/new.go | 92 +- cmd/new_test.go | 214 +-- cmd/prefix.go | 86 +- cmd/prefix_test.go | 340 ++--- cmd/read.go | 1 - cmd/read_test.go | 2 +- cmd/root.go | 12 +- cmd/utils.go | 296 ++-- cmd/write.go | 110 +- cmd/write_test.go | 1217 ++++++++--------- pkg/yqlib/constants.go | 5 + pkg/yqlib/data_navigator.go | 295 ---- pkg/yqlib/data_navigator_test.go | 1 - pkg/yqlib/delete_navigation_strategy.go | 73 - ...ilter_matching_node_navigation_strategy.go | 15 - pkg/yqlib/lib.go | 208 --- pkg/yqlib/lib_test.go | 176 --- pkg/yqlib/merge_navigation_strategy.go | 103 -- pkg/yqlib/navigation_strategy.go | 180 --- pkg/yqlib/path_parser.go | 153 --- pkg/yqlib/path_parser_test.go | 79 -- .../read_for_merge_navigation_strategy.go | 37 - pkg/yqlib/read_navigation_strategy.go | 11 - pkg/yqlib/treeops/candidate_node.go | 62 + pkg/yqlib/treeops/data_tree_navigator.go | 8 +- pkg/yqlib/treeops/delete_operator.go | 8 +- pkg/yqlib/treeops/lib.go | 26 +- pkg/yqlib/treeops/operators.go | 10 +- pkg/yqlib/treeops/path_tree.go | 4 + pkg/yqlib/update_navigation_strategy.go | 43 - 37 files changed, 2308 insertions(+), 3604 deletions(-) create mode 100644 pkg/yqlib/constants.go delete mode 100644 pkg/yqlib/data_navigator.go delete mode 100644 pkg/yqlib/data_navigator_test.go delete mode 100644 pkg/yqlib/delete_navigation_strategy.go delete mode 100644 pkg/yqlib/filter_matching_node_navigation_strategy.go delete mode 100644 pkg/yqlib/lib.go delete mode 100644 pkg/yqlib/lib_test.go delete mode 100644 pkg/yqlib/merge_navigation_strategy.go delete mode 100644 pkg/yqlib/navigation_strategy.go delete mode 100644 pkg/yqlib/path_parser.go delete mode 100644 pkg/yqlib/path_parser_test.go delete mode 100644 pkg/yqlib/read_for_merge_navigation_strategy.go delete mode 100644 pkg/yqlib/read_navigation_strategy.go create mode 100644 pkg/yqlib/treeops/candidate_node.go delete mode 100644 pkg/yqlib/update_navigation_strategy.go diff --git a/cmd/compare.go b/cmd/compare.go index f6b3188f..50fa6a9f 100644 --- a/cmd/compare.go +++ b/cmd/compare.go @@ -1,89 +1,89 @@ package cmd -import ( - "bufio" - "bytes" - "os" - "strings" +// import ( +// "bufio" +// "bytes" +// "os" +// "strings" - "github.com/kylelemons/godebug/diff" - "github.com/mikefarah/yq/v3/pkg/yqlib" - errors "github.com/pkg/errors" - "github.com/spf13/cobra" -) +// "github.com/kylelemons/godebug/diff" +// "github.com/mikefarah/yq/v3/pkg/yqlib" +// errors "github.com/pkg/errors" +// "github.com/spf13/cobra" +// ) -// turn off for unit tests :( -var forceOsExit = true +// // turn off for unit tests :( +// var forceOsExit = true -func createCompareCmd() *cobra.Command { - var cmdCompare = &cobra.Command{ - Use: "compare [yaml_file_a] [yaml_file_b]", - Aliases: []string{"x"}, - Short: "yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'", - Example: ` -yq x - data2.yml # reads from stdin -yq x -pp dataA.yaml dataB.yaml '**' # compare paths -yq x -d1 dataA.yaml dataB.yaml 'a.b.c' -`, - Long: "Deeply compares two yaml files, prints the difference. Use with prettyPrint flag to ignore formatting differences.", - RunE: compareDocuments, - } - cmdCompare.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - cmdCompare.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") - cmdCompare.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results") - cmdCompare.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "strip comments out before comparing") - cmdCompare.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors") - return cmdCompare -} +// func createCompareCmd() *cobra.Command { +// var cmdCompare = &cobra.Command{ +// Use: "compare [yaml_file_a] [yaml_file_b]", +// Aliases: []string{"x"}, +// Short: "yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'", +// Example: ` +// yq x - data2.yml # reads from stdin +// yq x -pp dataA.yaml dataB.yaml '**' # compare paths +// yq x -d1 dataA.yaml dataB.yaml 'a.b.c' +// `, +// Long: "Deeply compares two yaml files, prints the difference. Use with prettyPrint flag to ignore formatting differences.", +// RunE: compareDocuments, +// } +// cmdCompare.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") +// cmdCompare.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") +// cmdCompare.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results") +// cmdCompare.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "strip comments out before comparing") +// cmdCompare.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors") +// return cmdCompare +// } -func compareDocuments(cmd *cobra.Command, args []string) error { - var path = "" +// func compareDocuments(cmd *cobra.Command, args []string) error { +// var path = "" - if len(args) < 2 { - return errors.New("Must provide at 2 yaml files") - } else if len(args) > 2 { - path = args[2] - } +// if len(args) < 2 { +// return errors.New("Must provide at 2 yaml files") +// } else if len(args) > 2 { +// path = args[2] +// } - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } +// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() +// if errorParsingDocIndex != nil { +// return errorParsingDocIndex +// } - var matchingNodesA []*yqlib.NodeContext - var matchingNodesB []*yqlib.NodeContext - var errorDoingThings error +// var matchingNodesA []*yqlib.NodeContext +// var matchingNodesB []*yqlib.NodeContext +// var errorDoingThings error - matchingNodesA, errorDoingThings = readYamlFile(args[0], path, updateAll, docIndexInt) +// matchingNodesA, errorDoingThings = readYamlFile(args[0], path, updateAll, docIndexInt) - if errorDoingThings != nil { - return errorDoingThings - } +// if errorDoingThings != nil { +// return errorDoingThings +// } - matchingNodesB, errorDoingThings = readYamlFile(args[1], path, updateAll, docIndexInt) - if errorDoingThings != nil { - return errorDoingThings - } +// matchingNodesB, errorDoingThings = readYamlFile(args[1], path, updateAll, docIndexInt) +// if errorDoingThings != nil { +// return errorDoingThings +// } - var dataBufferA bytes.Buffer - var dataBufferB bytes.Buffer - errorDoingThings = printResults(matchingNodesA, bufio.NewWriter(&dataBufferA)) - if errorDoingThings != nil { - return errorDoingThings - } - errorDoingThings = printResults(matchingNodesB, bufio.NewWriter(&dataBufferB)) - if errorDoingThings != nil { - return errorDoingThings - } +// var dataBufferA bytes.Buffer +// var dataBufferB bytes.Buffer +// errorDoingThings = printResults(matchingNodesA, bufio.NewWriter(&dataBufferA)) +// if errorDoingThings != nil { +// return errorDoingThings +// } +// errorDoingThings = printResults(matchingNodesB, bufio.NewWriter(&dataBufferB)) +// if errorDoingThings != nil { +// return errorDoingThings +// } - diffString := diff.Diff(strings.TrimSuffix(dataBufferA.String(), "\n"), strings.TrimSuffix(dataBufferB.String(), "\n")) +// diffString := diff.Diff(strings.TrimSuffix(dataBufferA.String(), "\n"), strings.TrimSuffix(dataBufferB.String(), "\n")) - if len(diffString) > 1 { - cmd.Print(diffString) - cmd.Print("\n") - if forceOsExit { - os.Exit(1) - } - } - return nil -} +// if len(diffString) > 1 { +// cmd.Print(diffString) +// cmd.Print("\n") +// if forceOsExit { +// os.Exit(1) +// } +// } +// return nil +// } diff --git a/cmd/compare_test.go b/cmd/compare_test.go index 903b1521..670b0bab 100644 --- a/cmd/compare_test.go +++ b/cmd/compare_test.go @@ -1,115 +1,115 @@ package cmd -import ( - "testing" +// import ( +// "testing" - "github.com/mikefarah/yq/v3/test" -) +// "github.com/mikefarah/yq/v3/test" +// ) -func TestCompareSameCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestCompareSameCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestCompareIgnoreCommentsCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "compare --stripComments ../examples/data1.yaml ../examples/data1-no-comments.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestCompareIgnoreCommentsCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "compare --stripComments ../examples/data1.yaml ../examples/data1-no-comments.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestCompareDontIgnoreCommentsCmd(t *testing.T) { - forceOsExit = false - cmd := getRootCommand() - result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1-no-comments.yaml") +// func TestCompareDontIgnoreCommentsCmd(t *testing.T) { +// forceOsExit = false +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1-no-comments.yaml") - expectedOutput := `-a: simple # just the best -+a: simple - b: [1, 2] - c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `-a: simple # just the best +// +a: simple +// b: [1, 2] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestCompareExplodeAnchorsCommentsCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "compare --explodeAnchors ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestCompareExplodeAnchorsCommentsCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "compare --explodeAnchors ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestCompareDontExplodeAnchorsCmd(t *testing.T) { - forceOsExit = false - cmd := getRootCommand() - result := test.RunCmd(cmd, "compare ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml") +// func TestCompareDontExplodeAnchorsCmd(t *testing.T) { +// forceOsExit = false +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "compare ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml") - expectedOutput := `-foo: &foo -+foo: - a: 1 - foobar: -- !!merge <<: *foo -+ a: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `-foo: &foo +// +foo: +// a: 1 +// foobar: +// - !!merge <<: *foo +// + a: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestCompareDifferentCmd(t *testing.T) { - forceOsExit = false - cmd := getRootCommand() - result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data3.yaml") +// func TestCompareDifferentCmd(t *testing.T) { +// forceOsExit = false +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data3.yaml") - expectedOutput := `-a: simple # just the best --b: [1, 2] -+a: "simple" # just the best -+b: [1, 3] - c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `-a: simple # just the best +// -b: [1, 2] +// +a: "simple" # just the best +// +b: [1, 3] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestComparePrettyCmd(t *testing.T) { - forceOsExit = false - cmd := getRootCommand() - result := test.RunCmd(cmd, "compare -P ../examples/data1.yaml ../examples/data3.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := ` a: simple # just the best - b: - - 1 -- - 2 -+ - 3 - c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestComparePrettyCmd(t *testing.T) { +// forceOsExit = false +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "compare -P ../examples/data1.yaml ../examples/data3.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := ` a: simple # just the best +// b: +// - 1 +// - - 2 +// + - 3 +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestComparePathsCmd(t *testing.T) { - forceOsExit = false - cmd := getRootCommand() - result := test.RunCmd(cmd, "compare -P -ppv ../examples/data1.yaml ../examples/data3.yaml **") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := ` a: simple # just the best - b.[0]: 1 --b.[1]: 2 -+b.[1]: 3 - c.test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestComparePathsCmd(t *testing.T) { +// forceOsExit = false +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "compare -P -ppv ../examples/data1.yaml ../examples/data3.yaml **") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := ` a: simple # just the best +// b.[0]: 1 +// -b.[1]: 2 +// +b.[1]: 3 +// c.test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } diff --git a/cmd/constant.go b/cmd/constant.go index a1f63119..9de00524 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -2,6 +2,7 @@ package cmd import ( "github.com/mikefarah/yq/v3/pkg/yqlib" + "github.com/mikefarah/yq/v3/pkg/yqlib/treeops" logging "gopkg.in/op/go-logging.v1" ) @@ -32,5 +33,5 @@ var verbose = false var version = false var docIndex = "0" var log = logging.MustGetLogger("yq") -var lib = yqlib.NewYqLib() +var lib = treeops.NewYqTreeLib() var valueParser = yqlib.NewValueParser() diff --git a/cmd/delete.go b/cmd/delete.go index b1153109..96531e87 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -1,41 +1,41 @@ package cmd -import ( - "github.com/mikefarah/yq/v3/pkg/yqlib" - errors "github.com/pkg/errors" - "github.com/spf13/cobra" -) +// import ( +// "github.com/mikefarah/yq/v3/pkg/yqlib" +// errors "github.com/pkg/errors" +// "github.com/spf13/cobra" +// ) -func createDeleteCmd() *cobra.Command { - var cmdDelete = &cobra.Command{ - Use: "delete [yaml_file] [path_expression]", - Aliases: []string{"d"}, - Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'", - Example: ` -yq delete things.yaml 'a.b.c' -yq delete things.yaml 'a.*.c' -yq delete things.yaml 'a.(child.subchild==co*).c' -yq delete things.yaml 'a.**' -yq delete --inplace things.yaml 'a.b.c' -yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags -yq d -i things.yaml 'a.b.c' - `, - Long: `Deletes the nodes matching the given path expression from the YAML file. -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -`, - RunE: deleteProperty, - } - cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdDelete -} +// func createDeleteCmd() *cobra.Command { +// var cmdDelete = &cobra.Command{ +// Use: "delete [yaml_file] [path_expression]", +// Aliases: []string{"d"}, +// Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'", +// Example: ` +// yq delete things.yaml 'a.b.c' +// yq delete things.yaml 'a.*.c' +// yq delete things.yaml 'a.(child.subchild==co*).c' +// yq delete things.yaml 'a.**' +// yq delete --inplace things.yaml 'a.b.c' +// yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags +// yq d -i things.yaml 'a.b.c' +// `, +// Long: `Deletes the nodes matching the given path expression from the YAML file. +// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +// `, +// RunE: deleteProperty, +// } +// cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") +// cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") +// return cmdDelete +// } -func deleteProperty(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("Must provide ") - } - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) - updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]} +// func deleteProperty(cmd *cobra.Command, args []string) error { +// if len(args) < 2 { +// return errors.New("Must provide ") +// } +// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) +// updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]} - return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -} +// return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +// } diff --git a/cmd/delete_test.go b/cmd/delete_test.go index ae5c6179..b7715e61 100644 --- a/cmd/delete_test.go +++ b/cmd/delete_test.go @@ -1,246 +1,246 @@ package cmd -import ( - "fmt" - "strings" - "testing" +// import ( +// "fmt" +// "strings" +// "testing" - "github.com/mikefarah/yq/v3/test" -) +// "github.com/mikefarah/yq/v3/test" +// ) -func TestDeleteYamlCmd(t *testing.T) { - content := `a: 2 -b: - c: things - d: something else -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteYamlCmd(t *testing.T) { +// content := `a: 2 +// b: +// c: things +// d: something else +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: 2 -b: - d: something else -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: 2 +// b: +// d: something else +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestDeleteDeepDoesNotExistCmd(t *testing.T) { - content := `a: 2` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteDeepDoesNotExistCmd(t *testing.T) { +// content := `a: 2` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: 2 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: 2 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestDeleteSplatYaml(t *testing.T) { - content := `a: other -b: [3, 4] -c: - toast: leave - test: 1 - tell: 1 - tasty.taco: cool -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteSplatYaml(t *testing.T) { +// content := `a: other +// b: [3, 4] +// c: +// toast: leave +// test: 1 +// tell: 1 +// tasty.taco: cool +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: other -b: [3, 4] -c: - toast: leave - tasty.taco: cool -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: other +// b: [3, 4] +// c: +// toast: leave +// tasty.taco: cool +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestDeleteSplatArrayYaml(t *testing.T) { - content := `a: 2 -b: - hi: - - thing: item1 - name: fred - - thing: item2 - name: sam -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteSplatArrayYaml(t *testing.T) { +// content := `a: 2 +// b: +// hi: +// - thing: item1 +// name: fred +// - thing: item2 +// name: sam +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: 2 -b: - hi: - - name: fred - - name: sam -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: 2 +// b: +// hi: +// - name: fred +// - name: sam +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestDeleteDeepSplatArrayYaml(t *testing.T) { - content := `thing: 123 -b: - hi: - - thing: item1 - name: fred -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteDeepSplatArrayYaml(t *testing.T) { +// content := `thing: 123 +// b: +// hi: +// - thing: item1 +// name: fred +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `b: - hi: - - name: fred -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `b: +// hi: +// - name: fred +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestDeleteSplatPrefixYaml(t *testing.T) { - content := `a: 2 -b: - hi: - c: things - d: something else - there: - c: more things - d: more something else - there2: - c: more things also - d: more something else also -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteSplatPrefixYaml(t *testing.T) { +// content := `a: 2 +// b: +// hi: +// c: things +// d: something else +// there: +// c: more things +// d: more something else +// there2: +// c: more things also +// d: more something else also +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: 2 -b: - hi: - c: things - d: something else - there: - d: more something else - there2: - d: more something else also -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: 2 +// b: +// hi: +// c: things +// d: something else +// there: +// d: more something else +// there2: +// d: more something else also +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestDeleteYamlArrayCmd(t *testing.T) { - content := `- 1 -- 2 -- 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteYamlArrayCmd(t *testing.T) { +// content := `- 1 +// - 2 +// - 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete %s [1]", filename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete %s [1]", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `- 1 -- 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `- 1 +// - 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestDeleteYamlArrayExpressionCmd(t *testing.T) { - content := `- name: fred -- name: cat -- name: thing -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteYamlArrayExpressionCmd(t *testing.T) { +// content := `- name: fred +// - name: cat +// - name: thing +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete %s (name==cat)", filename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete %s (name==cat)", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `- name: fred -- name: thing -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `- name: fred +// - name: thing +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestDeleteYamlMulti(t *testing.T) { - content := `apples: great ---- -- 1 -- 2 -- 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteYamlMulti(t *testing.T) { +// content := `apples: great +// --- +// - 1 +// - 2 +// - 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete -d 1 %s [1]", filename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete -d 1 %s [1]", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `apples: great ---- -- 1 -- 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `apples: great +// --- +// - 1 +// - 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestDeleteYamlMultiAllCmd(t *testing.T) { - content := `b: - c: 3 -apples: great ---- -apples: great -something: else -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestDeleteYamlMultiAllCmd(t *testing.T) { +// content := `b: +// c: 3 +// apples: great +// --- +// apples: great +// something: else +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("delete %s -d * apples", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 3 ---- -something: else` - test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("delete %s -d * apples", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 3 +// --- +// something: else` +// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) +// } diff --git a/cmd/merge.go b/cmd/merge.go index ef4ca927..29a6aabd 100644 --- a/cmd/merge.go +++ b/cmd/merge.go @@ -1,124 +1,124 @@ package cmd -import ( - "github.com/mikefarah/yq/v3/pkg/yqlib" - errors "github.com/pkg/errors" - "github.com/spf13/cobra" - yaml "gopkg.in/yaml.v3" -) +// import ( +// "github.com/mikefarah/yq/v3/pkg/yqlib" +// errors "github.com/pkg/errors" +// "github.com/spf13/cobra" +// yaml "gopkg.in/yaml.v3" +// ) -func createMergeCmd() *cobra.Command { - var cmdMerge = &cobra.Command{ - Use: "merge [initial_yaml_file] [additional_yaml_file]...", - Aliases: []string{"m"}, - Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--arrayMerge/-a strategy] sample.yaml sample2.yaml", - Example: ` -yq merge things.yaml other.yaml -yq merge --inplace things.yaml other.yaml -yq m -i things.yaml other.yaml -yq m --overwrite things.yaml other.yaml -yq m -i -x things.yaml other.yaml -yq m -i -a=append things.yaml other.yaml -yq m -i --autocreate=false things.yaml other.yaml - `, - Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +// func createMergeCmd() *cobra.Command { +// var cmdMerge = &cobra.Command{ +// Use: "merge [initial_yaml_file] [additional_yaml_file]...", +// Aliases: []string{"m"}, +// Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--arrayMerge/-a strategy] sample.yaml sample2.yaml", +// Example: ` +// yq merge things.yaml other.yaml +// yq merge --inplace things.yaml other.yaml +// yq m -i things.yaml other.yaml +// yq m --overwrite things.yaml other.yaml +// yq m -i -x things.yaml other.yaml +// yq m -i -a=append things.yaml other.yaml +// yq m -i --autocreate=false things.yaml other.yaml +// `, +// Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). +// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. -If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. -`, - RunE: mergeProperties, - } - cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") - cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") - cmdMerge.PersistentFlags().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", `array merge strategy (update/append/overwrite) -update: recursively update arrays by their index -append: concatenate arrays together -overwrite: replace arrays -`) - cmdMerge.PersistentFlags().StringVarP(&commentsMergeStrategyFlag, "comments", "", "setWhenBlank", `comments merge strategy (setWhenBlank/ignore/append/overwrite) -setWhenBlank: set comment if the original document has no comment at that node -ignore: leave comments as-is in the original -append: append comments together -overwrite: overwrite comments completely -`) - cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdMerge -} +// If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. +// If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. +// `, +// RunE: mergeProperties, +// } +// cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") +// cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") +// cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") +// cmdMerge.PersistentFlags().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", `array merge strategy (update/append/overwrite) +// update: recursively update arrays by their index +// append: concatenate arrays together +// overwrite: replace arrays +// `) +// cmdMerge.PersistentFlags().StringVarP(&commentsMergeStrategyFlag, "comments", "", "setWhenBlank", `comments merge strategy (setWhenBlank/ignore/append/overwrite) +// setWhenBlank: set comment if the original document has no comment at that node +// ignore: leave comments as-is in the original +// append: append comments together +// overwrite: overwrite comments completely +// `) +// cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") +// return cmdMerge +// } -/* -* We don't deeply traverse arrays when appending a merge, instead we want to -* append the entire array element. - */ -func createReadFunctionForMerge(arrayMergeStrategy yqlib.ArrayMergeStrategy) func(*yaml.Node) ([]*yqlib.NodeContext, error) { - return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) { - return lib.GetForMerge(dataBucket, "**", arrayMergeStrategy) - } -} +// /* +// * We don't deeply traverse arrays when appending a merge, instead we want to +// * append the entire array element. +// */ +// func createReadFunctionForMerge(arrayMergeStrategy yqlib.ArrayMergeStrategy) func(*yaml.Node) ([]*yqlib.NodeContext, error) { +// return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) { +// return lib.GetForMerge(dataBucket, "**", arrayMergeStrategy) +// } +// } -func mergeProperties(cmd *cobra.Command, args []string) error { - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) +// func mergeProperties(cmd *cobra.Command, args []string) error { +// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) - if len(args) < 1 { - return errors.New("Must provide at least 1 yaml file") - } - var arrayMergeStrategy yqlib.ArrayMergeStrategy +// if len(args) < 1 { +// return errors.New("Must provide at least 1 yaml file") +// } +// var arrayMergeStrategy yqlib.ArrayMergeStrategy - switch arrayMergeStrategyFlag { - case "update": - arrayMergeStrategy = yqlib.UpdateArrayMergeStrategy - case "append": - arrayMergeStrategy = yqlib.AppendArrayMergeStrategy - case "overwrite": - arrayMergeStrategy = yqlib.OverwriteArrayMergeStrategy - default: - return errors.New("Array merge strategy must be one of: update/append/overwrite") - } +// switch arrayMergeStrategyFlag { +// case "update": +// arrayMergeStrategy = yqlib.UpdateArrayMergeStrategy +// case "append": +// arrayMergeStrategy = yqlib.AppendArrayMergeStrategy +// case "overwrite": +// arrayMergeStrategy = yqlib.OverwriteArrayMergeStrategy +// default: +// return errors.New("Array merge strategy must be one of: update/append/overwrite") +// } - var commentsMergeStrategy yqlib.CommentsMergeStrategy +// var commentsMergeStrategy yqlib.CommentsMergeStrategy - switch commentsMergeStrategyFlag { - case "setWhenBlank": - commentsMergeStrategy = yqlib.SetWhenBlankCommentsMergeStrategy - case "ignore": - commentsMergeStrategy = yqlib.IgnoreCommentsMergeStrategy - case "append": - commentsMergeStrategy = yqlib.AppendCommentsMergeStrategy - case "overwrite": - commentsMergeStrategy = yqlib.OverwriteCommentsMergeStrategy - default: - return errors.New("Comments merge strategy must be one of: setWhenBlank/ignore/append/overwrite") - } +// switch commentsMergeStrategyFlag { +// case "setWhenBlank": +// commentsMergeStrategy = yqlib.SetWhenBlankCommentsMergeStrategy +// case "ignore": +// commentsMergeStrategy = yqlib.IgnoreCommentsMergeStrategy +// case "append": +// commentsMergeStrategy = yqlib.AppendCommentsMergeStrategy +// case "overwrite": +// commentsMergeStrategy = yqlib.OverwriteCommentsMergeStrategy +// default: +// return errors.New("Comments merge strategy must be one of: setWhenBlank/ignore/append/overwrite") +// } - if len(args) > 1 { - // first generate update commands from the file - var filesToMerge = args[1:] +// if len(args) > 1 { +// // first generate update commands from the file +// var filesToMerge = args[1:] - for _, fileToMerge := range filesToMerge { - matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0) - if errorProcessingFile != nil { - return errorProcessingFile - } - log.Debugf("finished reading for merge!") - for _, matchingNode := range matchingNodes { - log.Debugf("matched node %v", lib.PathStackToString(matchingNode.PathStack)) - yqlib.DebugNode(matchingNode.Node) - } - for _, matchingNode := range matchingNodes { - mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy) - updateCommands = append(updateCommands, yqlib.UpdateCommand{ - Command: "merge", - Path: mergePath, - Value: matchingNode.Node, - Overwrite: overwriteFlag, - CommentsMergeStrategy: commentsMergeStrategy, - // dont update the content for nodes midway, only leaf nodes - DontUpdateNodeContent: matchingNode.IsMiddleNode && (arrayMergeStrategy != yqlib.OverwriteArrayMergeStrategy || matchingNode.Node.Kind != yaml.SequenceNode), - }) - } - } - } +// for _, fileToMerge := range filesToMerge { +// matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0) +// if errorProcessingFile != nil { +// return errorProcessingFile +// } +// log.Debugf("finished reading for merge!") +// for _, matchingNode := range matchingNodes { +// log.Debugf("matched node %v", lib.PathStackToString(matchingNode.PathStack)) +// yqlib.DebugNode(matchingNode.Node) +// } +// for _, matchingNode := range matchingNodes { +// mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy) +// updateCommands = append(updateCommands, yqlib.UpdateCommand{ +// Command: "merge", +// Path: mergePath, +// Value: matchingNode.Node, +// Overwrite: overwriteFlag, +// CommentsMergeStrategy: commentsMergeStrategy, +// // dont update the content for nodes midway, only leaf nodes +// DontUpdateNodeContent: matchingNode.IsMiddleNode && (arrayMergeStrategy != yqlib.OverwriteArrayMergeStrategy || matchingNode.Node.Kind != yaml.SequenceNode), +// }) +// } +// } +// } - return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -} +// return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +// } diff --git a/cmd/merge_test.go b/cmd/merge_test.go index a3d5f79c..b69060e3 100644 --- a/cmd/merge_test.go +++ b/cmd/merge_test.go @@ -1,551 +1,551 @@ package cmd -import ( - "fmt" - "os" - "runtime" - "testing" +// import ( +// "fmt" +// "os" +// "runtime" +// "testing" - "github.com/mikefarah/yq/v3/test" -) +// "github.com/mikefarah/yq/v3/test" +// ) -func TestMergeCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/data2.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: simple # just the best -b: [1, 2] -c: - test: 1 - toast: leave - tell: 1 - tasty.taco: cool -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/data2.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `a: simple # just the best +// b: [1, 2] +// c: +// test: 1 +// toast: leave +// tell: 1 +// tasty.taco: cool +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeOneFileCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge ../examples/data1.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: simple # just the best -b: [1, 2] -c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeOneFileCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge ../examples/data1.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `a: simple # just the best +// b: [1, 2] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeNoAutoCreateCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -c=false ../examples/data1.yaml ../examples/data2.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: simple # just the best -b: [1, 2] -c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeNoAutoCreateCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge -c=false ../examples/data1.yaml ../examples/data2.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `a: simple # just the best +// b: [1, 2] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeOverwriteCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -c=false --overwrite ../examples/data1.yaml ../examples/data2.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: other # just the best -b: [3, 4] -c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeOverwriteCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge -c=false --overwrite ../examples/data1.yaml ../examples/data2.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `a: other # just the best +// b: [3, 4] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeOverwriteDeepExampleCmd(t *testing.T) { - content := `c: - test: 1 - thing: whatever -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestMergeOverwriteDeepExampleCmd(t *testing.T) { +// content := `c: +// test: 1 +// thing: whatever +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - mergeContent := `c: - test: 5 -` - mergeFilename := test.WriteTempYamlFile(mergeContent) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeContent := `c: +// test: 5 +// ` +// mergeFilename := test.WriteTempYamlFile(mergeContent) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --overwrite %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --overwrite %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `c: - test: 5 - thing: whatever -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `c: +// test: 5 +// thing: whatever +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeAppendCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --autocreate=false --arrays=append ../examples/data1.yaml ../examples/data2.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: simple # just the best -b: [1, 2, 3, 4] -c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeAppendCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge --autocreate=false --arrays=append ../examples/data1.yaml ../examples/data2.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `a: simple # just the best +// b: [1, 2, 3, 4] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeAppendArraysCmd(t *testing.T) { - content := `people: - - name: Barry - age: 21` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestMergeAppendArraysCmd(t *testing.T) { +// content := `people: +// - name: Barry +// age: 21` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - mergeContent := `people: - - name: Roger - age: 44` - mergeFilename := test.WriteTempYamlFile(mergeContent) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeContent := `people: +// - name: Roger +// age: 44` +// mergeFilename := test.WriteTempYamlFile(mergeContent) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --arrays=append -d* %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `people: - - name: Barry - age: 21 - - name: Roger - age: 44 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge --arrays=append -d* %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `people: +// - name: Barry +// age: 21 +// - name: Roger +// age: 44 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeAliasArraysCmd(t *testing.T) { - content := ` -vars: - variable1: &var1 cat +// func TestMergeAliasArraysCmd(t *testing.T) { +// content := ` +// vars: +// variable1: &var1 cat -usage: - value1: *var1 - valueAnother: *var1 - valuePlain: thing -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// usage: +// value1: *var1 +// valueAnother: *var1 +// valuePlain: thing +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - mergeContent := ` -vars: - variable2: &var2 puppy +// mergeContent := ` +// vars: +// variable2: &var2 puppy -usage: - value2: *var2 - valueAnother: *var2 - valuePlain: *var2 -` +// usage: +// value2: *var2 +// valueAnother: *var2 +// valuePlain: *var2 +// ` - mergeFilename := test.WriteTempYamlFile(mergeContent) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeFilename := test.WriteTempYamlFile(mergeContent) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge -x %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `vars: - variable1: &var1 cat - variable2: &var2 puppy -usage: - value1: *var1 - valueAnother: *var2 - valuePlain: *var2 - value2: *var2 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge -x %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `vars: +// variable1: &var1 cat +// variable2: &var2 puppy +// usage: +// value1: *var1 +// valueAnother: *var2 +// valuePlain: *var2 +// value2: *var2 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeOverwriteAndAppendCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --autocreate=false --arrays=append --overwrite ../examples/data1.yaml ../examples/data2.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: other # just the best -b: [1, 2, 3, 4] -c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeOverwriteAndAppendCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge --autocreate=false --arrays=append --overwrite ../examples/data1.yaml ../examples/data2.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `a: other # just the best +// b: [1, 2, 3, 4] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -var commentContentA = ` -a: valueA1 # commentA1 -b: valueB1 -` +// var commentContentA = ` +// a: valueA1 # commentA1 +// b: valueB1 +// ` -var commentContentB = ` -a: valueA2 # commentA2 -b: valueB2 # commentB2 -c: valueC2 # commentC2 -` +// var commentContentB = ` +// a: valueA2 # commentA2 +// b: valueB2 # commentB2 +// c: valueC2 # commentC2 +// ` -func TestMergeCommentsSetWhenBlankCmd(t *testing.T) { - filename := test.WriteTempYamlFile(commentContentA) - defer test.RemoveTempYamlFile(filename) +// func TestMergeCommentsSetWhenBlankCmd(t *testing.T) { +// filename := test.WriteTempYamlFile(commentContentA) +// defer test.RemoveTempYamlFile(filename) - mergeFilename := test.WriteTempYamlFile(commentContentB) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeFilename := test.WriteTempYamlFile(commentContentB) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=setWhenBlank %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=setWhenBlank %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: valueA1 # commentA1 -b: valueB1 # commentB2 -c: valueC2 # commentC2 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: valueA1 # commentA1 +// b: valueB1 # commentB2 +// c: valueC2 # commentC2 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeCommentsIgnoreCmd(t *testing.T) { - filename := test.WriteTempYamlFile(commentContentA) - defer test.RemoveTempYamlFile(filename) +// func TestMergeCommentsIgnoreCmd(t *testing.T) { +// filename := test.WriteTempYamlFile(commentContentA) +// defer test.RemoveTempYamlFile(filename) - mergeFilename := test.WriteTempYamlFile(commentContentB) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeFilename := test.WriteTempYamlFile(commentContentB) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=ignore %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=ignore %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: valueA1 # commentA1 -b: valueB1 -c: valueC2 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: valueA1 # commentA1 +// b: valueB1 +// c: valueC2 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeCommentsAppendCmd(t *testing.T) { - filename := test.WriteTempYamlFile(commentContentA) - defer test.RemoveTempYamlFile(filename) +// func TestMergeCommentsAppendCmd(t *testing.T) { +// filename := test.WriteTempYamlFile(commentContentA) +// defer test.RemoveTempYamlFile(filename) - mergeFilename := test.WriteTempYamlFile(commentContentB) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeFilename := test.WriteTempYamlFile(commentContentB) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=append %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=append %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: valueA1 # commentA1 # commentA2 -b: valueB1 # commentB2 -c: valueC2 # commentC2 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: valueA1 # commentA1 # commentA2 +// b: valueB1 # commentB2 +// c: valueC2 # commentC2 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeCommentsOverwriteCmd(t *testing.T) { - filename := test.WriteTempYamlFile(commentContentA) - defer test.RemoveTempYamlFile(filename) +// func TestMergeCommentsOverwriteCmd(t *testing.T) { +// filename := test.WriteTempYamlFile(commentContentA) +// defer test.RemoveTempYamlFile(filename) - mergeFilename := test.WriteTempYamlFile(commentContentB) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeFilename := test.WriteTempYamlFile(commentContentB) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=overwrite %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=overwrite %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: valueA1 # commentA2 -b: valueB1 # commentB2 -c: valueC2 # commentC2 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: valueA1 # commentA2 +// b: valueB1 # commentB2 +// c: valueC2 # commentC2 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeOverwriteArraysTooCmd(t *testing.T) { - content := `a: simple # just the best -b: [1, 2] -c: - test: 1 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestMergeOverwriteArraysTooCmd(t *testing.T) { +// content := `a: simple # just the best +// b: [1, 2] +// c: +// test: 1 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - mergeContent := `a: things -b: [6]` - mergeFilename := test.WriteTempYamlFile(mergeContent) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeContent := `a: things +// b: [6]` +// mergeFilename := test.WriteTempYamlFile(mergeContent) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --arrays=overwrite --overwrite %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --arrays=overwrite --overwrite %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } - expectedOutput := `a: things # just the best -b: [6] -c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// expectedOutput := `a: things # just the best +// b: [6] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeRootArraysCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --arrays=append ../examples/sample_array.yaml ../examples/sample_array_2.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `- 1 -- 2 -- 3 -- 4 -- 5 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeRootArraysCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge --arrays=append ../examples/sample_array.yaml ../examples/sample_array_2.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `- 1 +// - 2 +// - 3 +// - 4 +// - 5 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeOverwriteArraysCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --arrays=overwrite ../examples/sample_array.yaml ../examples/sample_array_2.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `- 4 -- 5 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeOverwriteArraysCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge --arrays=overwrite ../examples/sample_array.yaml ../examples/sample_array_2.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `- 4 +// - 5 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeUpdateArraysCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -x --arrays=update ../examples/sample_array.yaml ../examples/sample_array_2.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `- 4 -- 5 -- 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeUpdateArraysCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge -x --arrays=update ../examples/sample_array.yaml ../examples/sample_array_2.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `- 4 +// - 5 +// - 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeCmd_Multi(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge -d1 ../examples/multiple_docs_small.yaml ../examples/data1.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: Easy! as one two three ---- -another: - document: here -a: simple # just the best -b: [1, 2] -c: - test: 1 ---- -- 1 -- 2 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeCmd_Multi(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge -d1 ../examples/multiple_docs_small.yaml ../examples/data1.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `a: Easy! as one two three +// --- +// another: +// document: here +// a: simple # just the best +// b: [1, 2] +// c: +// test: 1 +// --- +// - 1 +// - 2 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeYamlMultiAllCmd(t *testing.T) { - content := `b: - c: 3 -apples: green ---- -something: else` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestMergeYamlMultiAllCmd(t *testing.T) { +// content := `b: +// c: 3 +// apples: green +// --- +// something: else` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - mergeContent := `apples: red -something: good` - mergeFilename := test.WriteTempYamlFile(mergeContent) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeContent := `apples: red +// something: good` +// mergeFilename := test.WriteTempYamlFile(mergeContent) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge -d* %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 3 -apples: green -something: good ---- -something: else -apples: red -` - test.AssertResult(t, expectedOutput, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge -d* %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 3 +// apples: green +// something: good +// --- +// something: else +// apples: red +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeSpecialCharacterKeysCmd(t *testing.T) { - content := `` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestMergeSpecialCharacterKeysCmd(t *testing.T) { +// content := `` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - mergeContent := `key[bracket]: value -key.bracket: value -key"value": value -key'value': value -` - mergeFilename := test.WriteTempYamlFile(mergeContent) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeContent := `key[bracket]: value +// key.bracket: value +// key"value": value +// key'value': value +// ` +// mergeFilename := test.WriteTempYamlFile(mergeContent) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, mergeContent, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// test.AssertResult(t, mergeContent, result.Output) +// } -func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) { - content := `b: - c: 3 -apples: green ---- -something: else` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) { +// content := `b: +// c: 3 +// apples: green +// --- +// something: else` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - mergeContent := `apples: red -something: good` - mergeFilename := test.WriteTempYamlFile(mergeContent) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeContent := `apples: red +// something: good` +// mergeFilename := test.WriteTempYamlFile(mergeContent) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --overwrite -d* %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 3 -apples: red -something: good ---- -something: good -apples: red -` - test.AssertResult(t, expectedOutput, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge --overwrite -d* %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 3 +// apples: red +// something: good +// --- +// something: good +// apples: red +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeYamlNullMapCmd(t *testing.T) { - content := `b:` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestMergeYamlNullMapCmd(t *testing.T) { +// content := `b:` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - mergeContent := `b: - thing: a frog -` - mergeFilename := test.WriteTempYamlFile(mergeContent) - defer test.RemoveTempYamlFile(mergeFilename) +// mergeContent := `b: +// thing: a frog +// ` +// mergeFilename := test.WriteTempYamlFile(mergeContent) +// defer test.RemoveTempYamlFile(mergeFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge %s %s", filename, mergeFilename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, mergeContent, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge %s %s", filename, mergeFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// test.AssertResult(t, mergeContent, result.Output) +// } -func TestMergeCmd_Error(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge") - if result.Error == nil { - t.Error("Expected command to fail due to missing arg") - } - expectedOutput := `Must provide at least 1 yaml file` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} +// func TestMergeCmd_Error(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge") +// if result.Error == nil { +// t.Error("Expected command to fail due to missing arg") +// } +// expectedOutput := `Must provide at least 1 yaml file` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } -func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge ../examples/data1.yaml fake-unknown") - if result.Error == nil { - t.Error("Expected command to fail due to unknown file") - } - var expectedOutput string - if runtime.GOOS == "windows" { - expectedOutput = `open fake-unknown: The system cannot find the file specified.` - } else { - expectedOutput = `open fake-unknown: no such file or directory` - } - test.AssertResult(t, expectedOutput, result.Error.Error()) -} +// func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge ../examples/data1.yaml fake-unknown") +// if result.Error == nil { +// t.Error("Expected command to fail due to unknown file") +// } +// var expectedOutput string +// if runtime.GOOS == "windows" { +// expectedOutput = `open fake-unknown: The system cannot find the file specified.` +// } else { +// expectedOutput = `open fake-unknown: no such file or directory` +// } +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } -func TestMergeCmd_Inplace(t *testing.T) { - filename := test.WriteTempYamlFile(test.ReadTempYamlFile("../examples/data1.yaml")) - err := os.Chmod(filename, os.FileMode(int(0666))) - if err != nil { - t.Error(err) - } - defer test.RemoveTempYamlFile(filename) +// func TestMergeCmd_Inplace(t *testing.T) { +// filename := test.WriteTempYamlFile(test.ReadTempYamlFile("../examples/data1.yaml")) +// err := os.Chmod(filename, os.FileMode(int(0666))) +// if err != nil { +// t.Error(err) +// } +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge -i %s ../examples/data2.yaml", filename)) - if result.Error != nil { - t.Error(result.Error) - } - info, _ := os.Stat(filename) - gotOutput := test.ReadTempYamlFile(filename) - expectedOutput := `a: simple # just the best -b: [1, 2] -c: - test: 1 - toast: leave - tell: 1 - tasty.taco: cool -` - test.AssertResult(t, expectedOutput, gotOutput) - test.AssertResult(t, os.FileMode(int(0666)), info.Mode()) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("merge -i %s ../examples/data2.yaml", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// info, _ := os.Stat(filename) +// gotOutput := test.ReadTempYamlFile(filename) +// expectedOutput := `a: simple # just the best +// b: [1, 2] +// c: +// test: 1 +// toast: leave +// tell: 1 +// tasty.taco: cool +// ` +// test.AssertResult(t, expectedOutput, gotOutput) +// test.AssertResult(t, os.FileMode(int(0666)), info.Mode()) +// } -func TestMergeAllowEmptyTargetCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge ../examples/empty.yaml ../examples/data1.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: simple # just the best -b: [1, 2] -c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeAllowEmptyTargetCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge ../examples/empty.yaml ../examples/data1.yaml") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `a: simple # just the best +// b: [1, 2] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestMergeAllowEmptyMergeCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/empty.yaml") - expectedOutput := `a: simple # just the best -b: [1, 2] -c: - test: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestMergeAllowEmptyMergeCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/empty.yaml") +// expectedOutput := `a: simple # just the best +// b: [1, 2] +// c: +// test: 1 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } diff --git a/cmd/new.go b/cmd/new.go index fede2523..46bc9bd0 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -1,55 +1,55 @@ package cmd -import ( - "github.com/mikefarah/yq/v3/pkg/yqlib" - "github.com/spf13/cobra" -) +// import ( +// "github.com/mikefarah/yq/v3/pkg/yqlib" +// "github.com/spf13/cobra" +// ) -func createNewCmd() *cobra.Command { - var cmdNew = &cobra.Command{ - Use: "new [path] [value]", - Aliases: []string{"n"}, - Short: "yq n [--script/-s script_file] a.b.c newValue", - Example: ` -yq new 'a.b.c' cat -yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool -yq n 'a.b[+]' cat -yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags -yq n --script create_script.yaml - `, - Long: `Creates a new yaml w.r.t the given path and value. -Outputs to STDOUT +// func createNewCmd() *cobra.Command { +// var cmdNew = &cobra.Command{ +// Use: "new [path] [value]", +// Aliases: []string{"n"}, +// Short: "yq n [--script/-s script_file] a.b.c newValue", +// Example: ` +// yq new 'a.b.c' cat +// yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool +// yq n 'a.b[+]' cat +// yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags +// yq n --script create_script.yaml +// `, +// Long: `Creates a new yaml w.r.t the given path and value. +// Outputs to STDOUT -Create Scripts: -Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. -`, - RunE: newProperty, - } - cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml") - cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") - cmdNew.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged") - cmdNew.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name") - cmdNew.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name") - return cmdNew -} +// Create Scripts: +// Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. +// `, +// RunE: newProperty, +// } +// cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml") +// cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") +// cmdNew.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged") +// cmdNew.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name") +// cmdNew.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name") +// return cmdNew +// } -func newProperty(cmd *cobra.Command, args []string) error { - var badArgsMessage = "Must provide " - var updateCommands, updateCommandsError = readUpdateCommands(args, 2, badArgsMessage, false) - if updateCommandsError != nil { - return updateCommandsError - } - newNode := lib.New(updateCommands[0].Path) +// func newProperty(cmd *cobra.Command, args []string) error { +// var badArgsMessage = "Must provide " +// var updateCommands, updateCommandsError = readUpdateCommands(args, 2, badArgsMessage, false) +// if updateCommandsError != nil { +// return updateCommandsError +// } +// newNode := lib.New(updateCommands[0].Path) - for _, updateCommand := range updateCommands { +// for _, updateCommand := range updateCommands { - errorUpdating := lib.Update(&newNode, updateCommand, true) +// errorUpdating := lib.Update(&newNode, updateCommand, true) - if errorUpdating != nil { - return errorUpdating - } - } +// if errorUpdating != nil { +// return errorUpdating +// } +// } - var encoder = yqlib.NewYamlEncoder(cmd.OutOrStdout(), indent, colorsEnabled) - return encoder.Encode(&newNode) -} +// var encoder = yqlib.NewYamlEncoder(cmd.OutOrStdout(), indent, colorsEnabled) +// return encoder.Encode(&newNode) +// } diff --git a/cmd/new_test.go b/cmd/new_test.go index bdd9f274..fc887de3 100644 --- a/cmd/new_test.go +++ b/cmd/new_test.go @@ -1,120 +1,120 @@ package cmd -import ( - "fmt" - "testing" +// import ( +// "fmt" +// "testing" - "github.com/mikefarah/yq/v3/test" -) +// "github.com/mikefarah/yq/v3/test" +// ) -func TestNewCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "new b.c 3") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestNewCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "new b.c 3") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestNewCmdScript(t *testing.T) { - updateScript := `- command: update - path: b.c - value: 7` - scriptFilename := test.WriteTempYamlFile(updateScript) - defer test.RemoveTempYamlFile(scriptFilename) +// func TestNewCmdScript(t *testing.T) { +// updateScript := `- command: update +// path: b.c +// value: 7` +// scriptFilename := test.WriteTempYamlFile(updateScript) +// defer test.RemoveTempYamlFile(scriptFilename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("new --script %s", scriptFilename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 7 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("new --script %s", scriptFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 7 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestNewAnchorCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "new b.c 3 --anchorName=fred") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: &fred 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestNewAnchorCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "new b.c 3 --anchorName=fred") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: &fred 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestNewAliasCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "new b.c foo --makeAlias") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: *foo -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestNewAliasCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "new b.c foo --makeAlias") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: *foo +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestNewArrayCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "new b[0] 3") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - - 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestNewArrayCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "new b[0] 3") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// - 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestNewCmd_Error(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "new b.c") - if result.Error == nil { - t.Error("Expected command to fail due to missing arg") - } - expectedOutput := `Must provide ` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} +// func TestNewCmd_Error(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "new b.c") +// if result.Error == nil { +// t.Error("Expected command to fail due to missing arg") +// } +// expectedOutput := `Must provide ` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } -func TestNewWithTaggedStyleCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "new b.c cat --tag=!!str --style=tagged") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: !!str cat -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestNewWithTaggedStyleCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "new b.c cat --tag=!!str --style=tagged") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: !!str cat +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestNewWithDoubleQuotedStyleCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "new b.c cat --style=double") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: "cat" -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestNewWithDoubleQuotedStyleCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "new b.c cat --style=double") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: "cat" +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestNewWithSingleQuotedStyleCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "new b.c cat --style=single") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 'cat' -` - test.AssertResult(t, expectedOutput, result.Output) -} +// func TestNewWithSingleQuotedStyleCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "new b.c cat --style=single") +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 'cat' +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } diff --git a/cmd/prefix.go b/cmd/prefix.go index ae5c28ae..0fcde77c 100644 --- a/cmd/prefix.go +++ b/cmd/prefix.go @@ -1,50 +1,50 @@ package cmd -import ( - "github.com/mikefarah/yq/v3/pkg/yqlib" - errors "github.com/pkg/errors" - "github.com/spf13/cobra" - yaml "gopkg.in/yaml.v3" -) +// import ( +// "github.com/mikefarah/yq/v3/pkg/yqlib" +// errors "github.com/pkg/errors" +// "github.com/spf13/cobra" +// yaml "gopkg.in/yaml.v3" +// ) -func createPrefixCmd() *cobra.Command { - var cmdPrefix = &cobra.Command{ - Use: "prefix [yaml_file] [path]", - Aliases: []string{"p"}, - Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", - Example: ` -yq prefix things.yaml 'a.b.c' -yq prefix --inplace things.yaml 'a.b.c' -yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags -yq p -i things.yaml 'a.b.c' -yq p --doc 2 things.yaml 'a.b.d' -yq p -d2 things.yaml 'a.b.d' - `, - Long: `Prefixes w.r.t to the yaml file at the given path. -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -`, - RunE: prefixProperty, - } - cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdPrefix -} +// func createPrefixCmd() *cobra.Command { +// var cmdPrefix = &cobra.Command{ +// Use: "prefix [yaml_file] [path]", +// Aliases: []string{"p"}, +// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", +// Example: ` +// yq prefix things.yaml 'a.b.c' +// yq prefix --inplace things.yaml 'a.b.c' +// yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags +// yq p -i things.yaml 'a.b.c' +// yq p --doc 2 things.yaml 'a.b.d' +// yq p -d2 things.yaml 'a.b.d' +// `, +// Long: `Prefixes w.r.t to the yaml file at the given path. +// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +// `, +// RunE: prefixProperty, +// } +// cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") +// cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") +// return cmdPrefix +// } -func prefixProperty(cmd *cobra.Command, args []string) error { +// func prefixProperty(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("Must provide ") - } - updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]} - log.Debugf("args %v", args) +// if len(args) < 2 { +// return errors.New("Must provide ") +// } +// updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]} +// log.Debugf("args %v", args) - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } +// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() +// if errorParsingDocIndex != nil { +// return errorParsingDocIndex +// } - var updateData = func(dataBucket *yaml.Node, currentIndex int) error { - return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand) - } - return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) -} +// var updateData = func(dataBucket *yaml.Node, currentIndex int) error { +// return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand) +// } +// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) +// } diff --git a/cmd/prefix_test.go b/cmd/prefix_test.go index ee5c1989..b23437c4 100644 --- a/cmd/prefix_test.go +++ b/cmd/prefix_test.go @@ -1,189 +1,189 @@ package cmd -import ( - "fmt" - "runtime" - "strings" - "testing" +// import ( +// "fmt" +// "runtime" +// "strings" +// "testing" - "github.com/mikefarah/yq/v3/test" -) +// "github.com/mikefarah/yq/v3/test" +// ) -func TestPrefixCmd(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestPrefixCmd(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `d: - b: - c: 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `d: +// b: +// c: 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestPrefixCmdArray(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestPrefixCmdArray(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("prefix %s [+].d.[+]", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `- d: - - b: - c: 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s [+].d.[+]", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `- d: +// - b: +// c: 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestPrefixCmd_MultiLayer(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestPrefixCmd_MultiLayer(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d.e.f", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `d: - e: - f: - b: - c: 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d.e.f", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `d: +// e: +// f: +// b: +// c: 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } -func TestPrefixMultiCmd(t *testing.T) { - content := `b: - c: 3 ---- -apples: great -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestPrefixMultiCmd(t *testing.T) { +// content := `b: +// c: 3 +// --- +// apples: great +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 3 ---- -d: - apples: great -` - test.AssertResult(t, expectedOutput, result.Output) -} -func TestPrefixInvalidDocumentIndexCmd(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 3 +// --- +// d: +// apples: great +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } +// func TestPrefixInvalidDocumentIndexCmd(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -df d", filename)) - if result.Error == nil { - t.Error("Expected command to fail due to invalid path") - } - expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -df d", filename)) +// if result.Error == nil { +// t.Error("Expected command to fail due to invalid path") +// } +// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } -func TestPrefixBadDocumentIndexCmd(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestPrefixBadDocumentIndexCmd(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename)) - if result.Error == nil { - t.Error("Expected command to fail due to invalid path") - } - expectedOutput := `asked to process document index 1 but there are only 1 document(s)` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} -func TestPrefixMultiAllCmd(t *testing.T) { - content := `b: - c: 3 ---- -apples: great -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename)) +// if result.Error == nil { +// t.Error("Expected command to fail due to invalid path") +// } +// expectedOutput := `asked to process document index 1 but there are only 1 document(s)` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } +// func TestPrefixMultiAllCmd(t *testing.T) { +// content := `b: +// c: 3 +// --- +// apples: great +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d * d", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `d: - b: - c: 3 ---- -d: - apples: great` - test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d * d", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `d: +// b: +// c: 3 +// --- +// d: +// apples: great` +// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) +// } -func TestPrefixCmd_Error(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "prefix") - if result.Error == nil { - t.Error("Expected command to fail due to missing arg") - } - expectedOutput := `Must provide ` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} +// func TestPrefixCmd_Error(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "prefix") +// if result.Error == nil { +// t.Error("Expected command to fail due to missing arg") +// } +// expectedOutput := `Must provide ` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } -func TestPrefixCmd_ErrorUnreadableFile(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "prefix fake-unknown a.b") - if result.Error == nil { - t.Error("Expected command to fail due to unknown file") - } - var expectedOutput string - if runtime.GOOS == "windows" { - expectedOutput = `open fake-unknown: The system cannot find the file specified.` - } else { - expectedOutput = `open fake-unknown: no such file or directory` - } - test.AssertResult(t, expectedOutput, result.Error.Error()) -} +// func TestPrefixCmd_ErrorUnreadableFile(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "prefix fake-unknown a.b") +// if result.Error == nil { +// t.Error("Expected command to fail due to unknown file") +// } +// var expectedOutput string +// if runtime.GOOS == "windows" { +// expectedOutput = `open fake-unknown: The system cannot find the file specified.` +// } else { +// expectedOutput = `open fake-unknown: no such file or directory` +// } +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } -func TestPrefixCmd_Inplace(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) +// func TestPrefixCmd_Inplace(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("prefix -i %s d", filename)) - if result.Error != nil { - t.Error(result.Error) - } - gotOutput := test.ReadTempYamlFile(filename) - expectedOutput := `d: - b: - c: 3` - test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) -} +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("prefix -i %s d", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// gotOutput := test.ReadTempYamlFile(filename) +// expectedOutput := `d: +// b: +// c: 3` +// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) +// } diff --git a/cmd/read.go b/cmd/read.go index 8e68f77c..24530464 100644 --- a/cmd/read.go +++ b/cmd/read.go @@ -26,7 +26,6 @@ yq r -- things.yaml '--key-starting-with-dashes.blah' cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") cmdRead.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results") - cmdRead.PersistentFlags().BoolVarP(&printLength, "length", "l", false, "print length of results") cmdRead.PersistentFlags().BoolVarP(&collectIntoArray, "collect", "c", false, "collect results into array") cmdRead.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments") cmdRead.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "print yaml without any comments") diff --git a/cmd/read_test.go b/cmd/read_test.go index e193ff20..bbbb6e6f 100644 --- a/cmd/read_test.go +++ b/cmd/read_test.go @@ -127,7 +127,7 @@ func TestReadWithAdvancedFilterCmd(t *testing.T) { func TestReadWithAdvancedFilterMapCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml b.e[name`==`fr*]") + result := test.RunCmd(cmd, "read ../examples/sample.yaml b.e(name==fr*)") if result.Error != nil { t.Error(result.Error) } diff --git a/cmd/root.go b/cmd/root.go index a7f9b6bd..b2e3ff47 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,13 +48,13 @@ func New() *cobra.Command { rootCmd.AddCommand( createReadCmd(), - createCompareCmd(), + // createCompareCmd(), createValidateCmd(), - createWriteCmd(), - createPrefixCmd(), - createDeleteCmd(), - createNewCmd(), - createMergeCmd(), + // createWriteCmd(), + // createPrefixCmd(), + // createDeleteCmd(), + // createNewCmd(), + // createMergeCmd(), createBashCompletionCmd(rootCmd), ) return rootCmd diff --git a/cmd/utils.go b/cmd/utils.go index 9b1ca726..9d3e5a7a 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -4,29 +4,29 @@ import ( "bufio" "fmt" "io" - "io/ioutil" "os" "strconv" "github.com/mikefarah/yq/v3/pkg/yqlib" + "github.com/mikefarah/yq/v3/pkg/yqlib/treeops" errors "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" ) -type readDataFn func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) +type readDataFn func(document int, dataBucket *yaml.Node) ([]*treeops.CandidateNode, error) -func createReadFunction(path string) func(*yaml.Node) ([]*yqlib.NodeContext, error) { - return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) { - return lib.Get(dataBucket, path) +func createReadFunction(path string) func(int, *yaml.Node) ([]*treeops.CandidateNode, error) { + return func(document int, dataBucket *yaml.Node) ([]*treeops.CandidateNode, error) { + return lib.Get(document, dataBucket, path) } } -func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) { +func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*treeops.CandidateNode, error) { return doReadYamlFile(filename, createReadFunction(path), updateAll, docIndexInt) } -func doReadYamlFile(filename string, readFn readDataFn, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) { - var matchingNodes []*yqlib.NodeContext +func doReadYamlFile(filename string, readFn readDataFn, updateAll bool, docIndexInt int) ([]*treeops.CandidateNode, error) { + var matchingNodes []*treeops.CandidateNode var currentIndex = 0 var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error { @@ -63,14 +63,14 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { return nil } -func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, readFn readDataFn, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) { +func appendDocument(originalMatchingNodes []*treeops.CandidateNode, dataBucket yaml.Node, readFn readDataFn, updateAll bool, docIndexInt int, currentIndex int) ([]*treeops.CandidateNode, error) { log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) - yqlib.DebugNode(&dataBucket) + // yqlib.DebugNode(&dataBucket) if !updateAll && currentIndex != docIndexInt { return originalMatchingNodes, nil } log.Debugf("reading in document %v", currentIndex) - matchingNodes, errorParsing := readFn(&dataBucket) + matchingNodes, errorParsing := readFn(currentIndex, &dataBucket) if errorParsing != nil { return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) } @@ -114,7 +114,7 @@ func printNode(node *yaml.Node, writer io.Writer) error { return encoder.Encode(node) } -func removeComments(matchingNodes []*yqlib.NodeContext) { +func removeComments(matchingNodes []*treeops.CandidateNode) { for _, nodeContext := range matchingNodes { removeCommentOfNode(nodeContext.Node) } @@ -130,7 +130,7 @@ func removeCommentOfNode(node *yaml.Node) { } } -func setStyle(matchingNodes []*yqlib.NodeContext, style yaml.Style) { +func setStyle(matchingNodes []*treeops.CandidateNode, style yaml.Style) { for _, nodeContext := range matchingNodes { updateStyleOfNode(nodeContext.Node, style) } @@ -234,10 +234,10 @@ func explodeNode(node *yaml.Node) error { } } -func explode(matchingNodes []*yqlib.NodeContext) error { +func explode(matchingNodes []*treeops.CandidateNode) error { log.Debug("exploding nodes") for _, nodeContext := range matchingNodes { - log.Debugf("exploding %v", nodeContext.Head) + log.Debugf("exploding %v", nodeContext.GetKey()) errorExplodingNode := explodeNode(nodeContext.Node) if errorExplodingNode != nil { return errorExplodingNode @@ -246,7 +246,7 @@ func explode(matchingNodes []*yqlib.NodeContext) error { return nil } -func printResults(matchingNodes []*yqlib.NodeContext, writer io.Writer) error { +func printResults(matchingNodes []*treeops.CandidateNode, writer io.Writer) error { if prettyPrint { setStyle(matchingNodes, 0) } @@ -280,7 +280,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, writer io.Writer) error { for _, mappedDoc := range matchingNodes { switch printMode { case "p": - errorWriting = writeString(bufferedWriter, lib.PathStackToString(mappedDoc.PathStack)+"\n") + errorWriting = writeString(bufferedWriter, mappedDoc.PathStackToString()+"\n") if errorWriting != nil { return errorWriting } @@ -288,7 +288,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, writer io.Writer) error { // put it into a node and print that. var parentNode = yaml.Node{Kind: yaml.MappingNode} parentNode.Content = make([]*yaml.Node, 2) - parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)} + parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: mappedDoc.PathStackToString()} parentNode.Content[1] = transformNode(mappedDoc.Node) if collectIntoArray { arrayCollection.Content = append(arrayCollection.Content, &parentNode) @@ -383,102 +383,102 @@ func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderF } } -func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Prefixing document %v", currentIndex) - yqlib.DebugNode(dataBucket) - updateCommand.Value = dataBucket.Content[0] - dataBucket.Content = make([]*yaml.Node, 1) +// func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { +// if updateAll || currentIndex == docIndexInt { +// log.Debugf("Prefixing document %v", currentIndex) +// // yqlib.DebugNode(dataBucket) +// updateCommand.Value = dataBucket.Content[0] +// dataBucket.Content = make([]*yaml.Node, 1) - newNode := lib.New(updateCommand.Path) - dataBucket.Content[0] = &newNode +// newNode := lib.New(updateCommand.Path) +// dataBucket.Content[0] = &newNode - errorUpdating := lib.Update(dataBucket, updateCommand, true) - if errorUpdating != nil { - return errorUpdating - } - } - return nil -} +// errorUpdating := lib.Update(dataBucket, updateCommand, true) +// if errorUpdating != nil { +// return errorUpdating +// } +// } +// return nil +// } -func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error { - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } +// func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error { +// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() +// if errorParsingDocIndex != nil { +// return errorParsingDocIndex +// } - var updateData = func(dataBucket *yaml.Node, currentIndex int) error { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Updating doc %v", currentIndex) - for _, updateCommand := range updateCommands { - log.Debugf("Processing update to Path %v", updateCommand.Path) - errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag) - if errorUpdating != nil { - return errorUpdating - } - } - } - return nil - } - return readAndUpdate(writer, inputFile, updateData) -} +// var updateData = func(dataBucket *yaml.Node, currentIndex int) error { +// if updateAll || currentIndex == docIndexInt { +// log.Debugf("Updating doc %v", currentIndex) +// for _, updateCommand := range updateCommands { +// log.Debugf("Processing update to Path %v", updateCommand.Path) +// errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag) +// if errorUpdating != nil { +// return errorUpdating +// } +// } +// } +// return nil +// } +// return readAndUpdate(writer, inputFile, updateData) +// } -func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { - var destination io.Writer - var destinationName string - var completedSuccessfully = false - if writeInplace { - info, err := os.Stat(inputFile) - if err != nil { - return err - } - // mkdir temp dir as some docker images does not have temp dir - _, err = os.Stat(os.TempDir()) - if os.IsNotExist(err) { - err = os.Mkdir(os.TempDir(), 0700) - if err != nil { - return err - } - } else if err != nil { - return err - } - tempFile, err := ioutil.TempFile("", "temp") - if err != nil { - return err - } - destinationName = tempFile.Name() - err = os.Chmod(destinationName, info.Mode()) - if err != nil { - return err - } - destination = tempFile - defer func() { - safelyCloseFile(tempFile) - if completedSuccessfully { - safelyRenameFile(tempFile.Name(), inputFile) - } - }() - } else { - destination = stdOut - destinationName = "Stdout" - } +// func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { +// var destination io.Writer +// var destinationName string +// var completedSuccessfully = false +// if writeInplace { +// info, err := os.Stat(inputFile) +// if err != nil { +// return err +// } +// // mkdir temp dir as some docker images does not have temp dir +// _, err = os.Stat(os.TempDir()) +// if os.IsNotExist(err) { +// err = os.Mkdir(os.TempDir(), 0700) +// if err != nil { +// return err +// } +// } else if err != nil { +// return err +// } +// tempFile, err := ioutil.TempFile("", "temp") +// if err != nil { +// return err +// } +// destinationName = tempFile.Name() +// err = os.Chmod(destinationName, info.Mode()) +// if err != nil { +// return err +// } +// destination = tempFile +// defer func() { +// safelyCloseFile(tempFile) +// if completedSuccessfully { +// safelyRenameFile(tempFile.Name(), inputFile) +// } +// }() +// } else { +// destination = stdOut +// destinationName = "Stdout" +// } - log.Debugf("Writing to %v from %v", destinationName, inputFile) +// log.Debugf("Writing to %v from %v", destinationName, inputFile) - bufferedWriter := bufio.NewWriter(destination) - defer safelyFlush(bufferedWriter) +// bufferedWriter := bufio.NewWriter(destination) +// defer safelyFlush(bufferedWriter) - var encoder yqlib.Encoder - if outputToJSON { - encoder = yqlib.NewJsonEncoder(bufferedWriter, prettyPrint, indent) - } else { - encoder = yqlib.NewYamlEncoder(bufferedWriter, indent, colorsEnabled) - } +// var encoder yqlib.Encoder +// if outputToJSON { +// encoder = yqlib.NewJsonEncoder(bufferedWriter, prettyPrint, indent) +// } else { +// encoder = yqlib.NewYamlEncoder(bufferedWriter, indent, colorsEnabled) +// } - var errorProcessing = readStream(inputFile, mapYamlDecoder(updateData, encoder)) - completedSuccessfully = errorProcessing == nil - return errorProcessing -} +// var errorProcessing = readStream(inputFile, mapYamlDecoder(updateData, encoder)) +// completedSuccessfully = errorProcessing == nil +// return errorProcessing +// } type updateCommandParsed struct { Command string @@ -486,53 +486,53 @@ type updateCommandParsed struct { Value yaml.Node } -func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string, allowNoValue bool) ([]yqlib.UpdateCommand, error) { - var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) - if writeScript != "" { - var parsedCommands = make([]updateCommandParsed, 0) +// func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string, allowNoValue bool) ([]yqlib.UpdateCommand, error) { +// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) +// if writeScript != "" { +// var parsedCommands = make([]updateCommandParsed, 0) - err := readData(writeScript, 0, &parsedCommands) +// err := readData(writeScript, 0, &parsedCommands) - if err != nil && err != io.EOF { - return nil, err - } +// if err != nil && err != io.EOF { +// return nil, err +// } - log.Debugf("Read write commands file '%v'", parsedCommands) - for index := range parsedCommands { - parsedCommand := parsedCommands[index] - updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true} - updateCommands = append(updateCommands, updateCommand) - } +// log.Debugf("Read write commands file '%v'", parsedCommands) +// for index := range parsedCommands { +// parsedCommand := parsedCommands[index] +// updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true} +// updateCommands = append(updateCommands, updateCommand) +// } - log.Debugf("Read write commands file '%v'", updateCommands) - } else if sourceYamlFile != "" && len(args) == expectedArgs-1 { - log.Debugf("Reading value from %v", sourceYamlFile) - var value yaml.Node - err := readData(sourceYamlFile, 0, &value) - if err != nil && err != io.EOF { - return nil, err - } - log.Debug("args %v", args[expectedArgs-2]) - updateCommands = make([]yqlib.UpdateCommand, 1) - updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value.Content[0], Overwrite: true} - } else if len(args) == expectedArgs { - updateCommands = make([]yqlib.UpdateCommand, 1) - log.Debug("args %v", args) - log.Debug("path %v", args[expectedArgs-2]) - log.Debug("Value %v", args[expectedArgs-1]) - value := valueParser.Parse(args[expectedArgs-1], customTag, customStyle, anchorName, makeAlias) - updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value, Overwrite: true, CommentsMergeStrategy: yqlib.IgnoreCommentsMergeStrategy} - } else if len(args) == expectedArgs-1 && allowNoValue { - // don't update the value - updateCommands = make([]yqlib.UpdateCommand, 1) - log.Debug("args %v", args) - log.Debug("path %v", args[expectedArgs-2]) - updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse("", customTag, customStyle, anchorName, makeAlias), Overwrite: true, DontUpdateNodeValue: true} - } else { - return nil, errors.New(badArgsMessage) - } - return updateCommands, nil -} +// log.Debugf("Read write commands file '%v'", updateCommands) +// } else if sourceYamlFile != "" && len(args) == expectedArgs-1 { +// log.Debugf("Reading value from %v", sourceYamlFile) +// var value yaml.Node +// err := readData(sourceYamlFile, 0, &value) +// if err != nil && err != io.EOF { +// return nil, err +// } +// log.Debug("args %v", args[expectedArgs-2]) +// updateCommands = make([]yqlib.UpdateCommand, 1) +// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value.Content[0], Overwrite: true} +// } else if len(args) == expectedArgs { +// updateCommands = make([]yqlib.UpdateCommand, 1) +// log.Debug("args %v", args) +// log.Debug("path %v", args[expectedArgs-2]) +// log.Debug("Value %v", args[expectedArgs-1]) +// value := valueParser.Parse(args[expectedArgs-1], customTag, customStyle, anchorName, makeAlias) +// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value, Overwrite: true, CommentsMergeStrategy: yqlib.IgnoreCommentsMergeStrategy} +// } else if len(args) == expectedArgs-1 && allowNoValue { +// // don't update the value +// updateCommands = make([]yqlib.UpdateCommand, 1) +// log.Debug("args %v", args) +// log.Debug("path %v", args[expectedArgs-2]) +// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse("", customTag, customStyle, anchorName, makeAlias), Overwrite: true, DontUpdateNodeValue: true} +// } else { +// return nil, errors.New(badArgsMessage) +// } +// return updateCommands, nil +// } func safelyRenameFile(from string, to string) { if renameError := os.Rename(from, to); renameError != nil { diff --git a/cmd/write.go b/cmd/write.go index be14a132..a1c30607 100644 --- a/cmd/write.go +++ b/cmd/write.go @@ -1,61 +1,61 @@ package cmd -import ( - "github.com/spf13/cobra" -) +// import ( +// "github.com/spf13/cobra" +// ) -func createWriteCmd() *cobra.Command { - var cmdWrite = &cobra.Command{ - Use: "write [yaml_file] [path_expression] [value]", - Aliases: []string{"w"}, - Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue", - Example: ` -yq write things.yaml 'a.b.c' true -yq write things.yaml 'a.*.c' true -yq write things.yaml 'a.**' true -yq write things.yaml 'a.(child.subchild==co*).c' true -yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool -yq write things.yaml 'a.b.c' --tag '!!float' 3 -yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags -yq w -i things.yaml 'a.b.c' cat -yq w -i -s update_script.yaml things.yaml -yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array -yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file - `, - Long: `Updates the yaml file w.r.t the given path and value. -Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +// func createWriteCmd() *cobra.Command { +// var cmdWrite = &cobra.Command{ +// Use: "write [yaml_file] [path_expression] [value]", +// Aliases: []string{"w"}, +// Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue", +// Example: ` +// yq write things.yaml 'a.b.c' true +// yq write things.yaml 'a.*.c' true +// yq write things.yaml 'a.**' true +// yq write things.yaml 'a.(child.subchild==co*).c' true +// yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool +// yq write things.yaml 'a.b.c' --tag '!!float' 3 +// yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags +// yq w -i things.yaml 'a.b.c' cat +// yq w -i -s update_script.yaml things.yaml +// yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array +// yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file +// `, +// Long: `Updates the yaml file w.r.t the given path and value. +// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -Append value to array adds the value to the end of array. +// Append value to array adds the value to the end of array. -Update Scripts: -Note that you can give an update script to perform more sophisticated update. Update script -format is list of update commands (update or delete) like so: ---- -- command: update - path: b.c - value: - #great - things: frog # wow! -- command: delete - path: b.d -`, - RunE: writeProperty, - } - cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") - cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") - cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)") - cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") - cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged") - cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name") - cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name") - return cmdWrite -} +// Update Scripts: +// Note that you can give an update script to perform more sophisticated update. Update script +// format is list of update commands (update or delete) like so: +// --- +// - command: update +// path: b.c +// value: +// #great +// things: frog # wow! +// - command: delete +// path: b.d +// `, +// RunE: writeProperty, +// } +// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") +// cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") +// cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)") +// cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") +// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") +// cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged") +// cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name") +// cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name") +// return cmdWrite +// } -func writeProperty(cmd *cobra.Command, args []string) error { - var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ", true) - if updateCommandsError != nil { - return updateCommandsError - } - return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -} +// func writeProperty(cmd *cobra.Command, args []string) error { +// var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ", true) +// if updateCommandsError != nil { +// return updateCommandsError +// } +// return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) +// } diff --git a/cmd/write_test.go b/cmd/write_test.go index 8249a890..bb1ab671 100644 --- a/cmd/write_test.go +++ b/cmd/write_test.go @@ -1,611 +1,610 @@ package cmd -import ( - "fmt" - "runtime" - "strings" - "testing" - - "github.com/mikefarah/yq/v3/test" -) - -func TestWriteCmd(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 7 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteKeepCommentsCmd(t *testing.T) { - content := `b: - c: 3 # comment -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 7 # comment -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteWithTaggedStyleCmd(t *testing.T) { - content := `b: - c: dog -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --tag=!!str --style=tagged", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: !!str cat -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteWithDoubleQuotedStyleCmd(t *testing.T) { - content := `b: - c: dog -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=double", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: "cat" -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteUpdateStyleOnlyCmd(t *testing.T) { - content := `b: - c: dog - d: things -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --style=single", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 'dog' - d: 'things' -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteUpdateTagOnlyCmd(t *testing.T) { - content := `b: - c: true - d: false -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --tag=!!str", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: "true" - d: "false" -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteWithSingleQuotedStyleCmd(t *testing.T) { - content := `b: - c: dog -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=single", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 'cat' -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteWithLiteralStyleCmd(t *testing.T) { - content := `b: - c: dog -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=literal", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: |- - cat -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteWithFoldedStyleCmd(t *testing.T) { - content := `b: - c: dog -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=folded", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: >- - cat -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteEmptyMultiDocCmd(t *testing.T) { - content := `# this is empty ---- -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s c 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `c: 7 - -# this is empty -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteSurroundingEmptyMultiDocCmd(t *testing.T) { - content := `--- -# empty ---- -cat: frog ---- - -# empty -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s -d1 c 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := ` - -# empty ---- -cat: frog -c: 7 ---- - - -# empty -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteFromFileCmd(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - source := `kittens: are cute # sure are!` - fromFilename := test.WriteTempYamlFile(source) - defer test.RemoveTempYamlFile(fromFilename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c -f %s", filename, fromFilename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: - kittens: are cute # sure are! -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteEmptyCmd(t *testing.T) { - content := `` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 7 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteAutoCreateCmd(t *testing.T) { - content := `applications: - - name: app - env:` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s applications[0].env.hello world", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `applications: - - name: app - env: - hello: world -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteCmdScript(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - updateScript := `- command: update - path: b.c - value: 7` - scriptFilename := test.WriteTempYamlFile(updateScript) - defer test.RemoveTempYamlFile(scriptFilename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 7 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteCmdEmptyScript(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - updateScript := `` - scriptFilename := test.WriteTempYamlFile(updateScript) - defer test.RemoveTempYamlFile(scriptFilename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteMultiCmd(t *testing.T) { - content := `b: - c: 3 ---- -apples: great -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 3 ---- -apples: ok -` - test.AssertResult(t, expectedOutput, result.Output) -} -func TestWriteInvalidDocumentIndexCmd(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename)) - if result.Error == nil { - t.Error("Expected command to fail due to invalid path") - } - expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestWriteBadDocumentIndexCmd(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename)) - if result.Error == nil { - t.Error("Expected command to fail due to invalid path") - } - expectedOutput := `asked to process document index 1 but there are only 1 document(s)` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} -func TestWriteMultiAllCmd(t *testing.T) { - content := `b: - c: 3 ---- -apples: great -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 3 -apples: ok ---- -apples: ok` - test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) -} - -func TestWriteCmd_EmptyArray(t *testing.T) { - content := `b: 3` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s a []", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: 3 -a: [] -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteCmd_Error(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "write") - if result.Error == nil { - t.Error("Expected command to fail due to missing arg") - } - expectedOutput := `Must provide ` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestWriteCmd_ErrorUnreadableFile(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "write fake-unknown a.b 3") - if result.Error == nil { - t.Error("Expected command to fail due to unknown file") - } - var expectedOutput string - if runtime.GOOS == "windows" { - expectedOutput = `open fake-unknown: The system cannot find the file specified.` - } else { - expectedOutput = `open fake-unknown: no such file or directory` - } - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestWriteCmd_Inplace(t *testing.T) { - content := `b: - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - gotOutput := test.ReadTempYamlFile(filename) - expectedOutput := `b: - c: 7` - test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) -} - -func TestWriteCmd_InplaceError(t *testing.T) { - content := `b: cat - c: 3 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename)) - if result.Error == nil { - t.Error("Expected Error to occur!") - } - gotOutput := test.ReadTempYamlFile(filename) - test.AssertResult(t, content, gotOutput) -} - -func TestWriteCmd_Append(t *testing.T) { - content := `b: - - foo -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - - foo - - 7 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteCmd_AppendInline(t *testing.T) { - content := `b: [foo]` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: [foo, 7] -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteCmd_AppendInlinePretty(t *testing.T) { - content := `b: [foo]` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s -P b[+] 7", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - - foo - - 7 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteCmd_AppendEmptyArray(t *testing.T) { - content := `a: 2 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: 2 -b: - - v -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteCmd_SplatArray(t *testing.T) { - content := `b: -- c: thing -- c: another thing -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - - c: new - - c: new -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteCmd_SplatMap(t *testing.T) { - content := `b: - c: thing - d: another thing -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: new - d: new -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestWriteCmd_SplatMapEmpty(t *testing.T) { - content := `b: - c: thing - d: another thing -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: {} - d: another thing -` - test.AssertResult(t, expectedOutput, result.Output) -} +// import ( +// "fmt" +// "runtime" +// "strings" +// "testing" + +// "github.com/mikefarah/yq/v3/test" +// ) + +// func TestWriteCmd(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 7 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteKeepCommentsCmd(t *testing.T) { +// content := `b: +// c: 3 # comment +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 7 # comment +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteWithTaggedStyleCmd(t *testing.T) { +// content := `b: +// c: dog +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --tag=!!str --style=tagged", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: !!str cat +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteWithDoubleQuotedStyleCmd(t *testing.T) { +// content := `b: +// c: dog +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=double", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: "cat" +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteUpdateStyleOnlyCmd(t *testing.T) { +// content := `b: +// c: dog +// d: things +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --style=single", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 'dog' +// d: 'things' +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteUpdateTagOnlyCmd(t *testing.T) { +// content := `b: +// c: true +// d: false +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --tag=!!str", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: "true" +// d: "false" +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteWithSingleQuotedStyleCmd(t *testing.T) { +// content := `b: +// c: dog +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=single", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 'cat' +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteWithLiteralStyleCmd(t *testing.T) { +// content := `b: +// c: dog +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=literal", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: |- +// cat +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteWithFoldedStyleCmd(t *testing.T) { +// content := `b: +// c: dog +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=folded", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: >- +// cat +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteEmptyMultiDocCmd(t *testing.T) { +// content := `# this is empty +// --- +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s c 7", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `c: 7 + +// # this is empty +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteSurroundingEmptyMultiDocCmd(t *testing.T) { +// content := `--- +// # empty +// --- +// cat: frog +// --- + +// # empty +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d1 c 7", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := ` + +// # empty +// --- +// cat: frog +// c: 7 +// --- + +// # empty +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteFromFileCmd(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// source := `kittens: are cute # sure are!` +// fromFilename := test.WriteTempYamlFile(source) +// defer test.RemoveTempYamlFile(fromFilename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c -f %s", filename, fromFilename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: +// kittens: are cute # sure are! +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteEmptyCmd(t *testing.T) { +// content := `` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 7 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteAutoCreateCmd(t *testing.T) { +// content := `applications: +// - name: app +// env:` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s applications[0].env.hello world", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `applications: +// - name: app +// env: +// hello: world +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteCmdScript(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// updateScript := `- command: update +// path: b.c +// value: 7` +// scriptFilename := test.WriteTempYamlFile(updateScript) +// defer test.RemoveTempYamlFile(scriptFilename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 7 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteCmdEmptyScript(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// updateScript := `` +// scriptFilename := test.WriteTempYamlFile(updateScript) +// defer test.RemoveTempYamlFile(scriptFilename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 3 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteMultiCmd(t *testing.T) { +// content := `b: +// c: 3 +// --- +// apples: great +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 3 +// --- +// apples: ok +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } +// func TestWriteInvalidDocumentIndexCmd(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename)) +// if result.Error == nil { +// t.Error("Expected command to fail due to invalid path") +// } +// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } + +// func TestWriteBadDocumentIndexCmd(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename)) +// if result.Error == nil { +// t.Error("Expected command to fail due to invalid path") +// } +// expectedOutput := `asked to process document index 1 but there are only 1 document(s)` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } +// func TestWriteMultiAllCmd(t *testing.T) { +// content := `b: +// c: 3 +// --- +// apples: great +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: 3 +// apples: ok +// --- +// apples: ok` +// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) +// } + +// func TestWriteCmd_EmptyArray(t *testing.T) { +// content := `b: 3` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s a []", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: 3 +// a: [] +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteCmd_Error(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "write") +// if result.Error == nil { +// t.Error("Expected command to fail due to missing arg") +// } +// expectedOutput := `Must provide ` +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } + +// func TestWriteCmd_ErrorUnreadableFile(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "write fake-unknown a.b 3") +// if result.Error == nil { +// t.Error("Expected command to fail due to unknown file") +// } +// var expectedOutput string +// if runtime.GOOS == "windows" { +// expectedOutput = `open fake-unknown: The system cannot find the file specified.` +// } else { +// expectedOutput = `open fake-unknown: no such file or directory` +// } +// test.AssertResult(t, expectedOutput, result.Error.Error()) +// } + +// func TestWriteCmd_Inplace(t *testing.T) { +// content := `b: +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// gotOutput := test.ReadTempYamlFile(filename) +// expectedOutput := `b: +// c: 7` +// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) +// } + +// func TestWriteCmd_InplaceError(t *testing.T) { +// content := `b: cat +// c: 3 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename)) +// if result.Error == nil { +// t.Error("Expected Error to occur!") +// } +// gotOutput := test.ReadTempYamlFile(filename) +// test.AssertResult(t, content, gotOutput) +// } + +// func TestWriteCmd_Append(t *testing.T) { +// content := `b: +// - foo +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// - foo +// - 7 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteCmd_AppendInline(t *testing.T) { +// content := `b: [foo]` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: [foo, 7] +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteCmd_AppendInlinePretty(t *testing.T) { +// content := `b: [foo]` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s -P b[+] 7", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// - foo +// - 7 +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteCmd_AppendEmptyArray(t *testing.T) { +// content := `a: 2 +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `a: 2 +// b: +// - v +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteCmd_SplatArray(t *testing.T) { +// content := `b: +// - c: thing +// - c: another thing +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// - c: new +// - c: new +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteCmd_SplatMap(t *testing.T) { +// content := `b: +// c: thing +// d: another thing +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: new +// d: new +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } + +// func TestWriteCmd_SplatMapEmpty(t *testing.T) { +// content := `b: +// c: thing +// d: another thing +// ` +// filename := test.WriteTempYamlFile(content) +// defer test.RemoveTempYamlFile(filename) + +// cmd := getRootCommand() +// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename)) +// if result.Error != nil { +// t.Error(result.Error) +// } +// expectedOutput := `b: +// c: {} +// d: another thing +// ` +// test.AssertResult(t, expectedOutput, result.Output) +// } diff --git a/pkg/yqlib/constants.go b/pkg/yqlib/constants.go new file mode 100644 index 00000000..d8c4f7d6 --- /dev/null +++ b/pkg/yqlib/constants.go @@ -0,0 +1,5 @@ +package yqlib + +import "gopkg.in/op/go-logging.v1" + +var log = logging.MustGetLogger("yq-lib") diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go deleted file mode 100644 index a7c41c64..00000000 --- a/pkg/yqlib/data_navigator.go +++ /dev/null @@ -1,295 +0,0 @@ -package yqlib - -import ( - "fmt" - "strconv" - - yaml "gopkg.in/yaml.v3" -) - -type DataNavigator interface { - Traverse(value *yaml.Node, path []interface{}) error -} - -type navigator struct { - navigationStrategy NavigationStrategy -} - -func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator { - return &navigator{ - navigationStrategy: NavigationStrategy, - } -} - -func (n *navigator) Traverse(value *yaml.Node, path []interface{}) error { - emptyArray := make([]interface{}, 0) - log.Debugf("Traversing path %v", pathStackToString(path)) - return n.doTraverse(value, "", path, emptyArray) -} - -func (n *navigator) doTraverse(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error { - - log.Debug("head %v", head) - DebugNode(value) - var nodeContext = NewNodeContext(value, head, tail, pathStack) - - var errorDeepSplatting error - // no need to deeply traverse the DocumentNode, as it's already covered by its first child. - if head == "**" && value.Kind != yaml.DocumentNode && value.Kind != yaml.ScalarNode && n.navigationStrategy.ShouldDeeplyTraverse(nodeContext) { - if len(pathStack) == 0 || pathStack[len(pathStack)-1] != "<<" { - errorDeepSplatting = n.recurse(value, head, tail, pathStack) - } - // ignore errors here, we are deep splatting so we may accidently give a string key - // to an array sequence - if len(tail) > 0 { - _ = n.recurse(value, tail[0], tail[1:], pathStack) - } - return errorDeepSplatting - } - - if value.Kind == yaml.DocumentNode { - log.Debugf("its a document, diving into %v", head) - DebugNode(value) - return n.recurse(value, head, tail, pathStack) - } else if len(tail) > 0 && value.Kind != yaml.ScalarNode { - log.Debugf("diving into %v", tail[0]) - DebugNode(value) - return n.recurse(value, tail[0], tail[1:], pathStack) - } - return n.navigationStrategy.Visit(nodeContext) -} - -func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node { - if original.Kind != expectedKind { - log.Debug("wanted %v but it was %v, overriding", KindString(expectedKind), KindString(original.Kind)) - return &yaml.Node{Kind: expectedKind} - } - return original -} - -func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error { - log.Debug("recursing, processing %v, pathStack %v", head, pathStackToString(pathStack)) - - nodeContext := NewNodeContext(value, head, tail, pathStack) - - if head == "**" && !n.navigationStrategy.ShouldOnlyDeeplyVisitLeaves(nodeContext) { - nodeContext.IsMiddleNode = true - errorVisitingDeeply := n.navigationStrategy.Visit(nodeContext) - if errorVisitingDeeply != nil { - return errorVisitingDeeply - } - } - - switch value.Kind { - case yaml.MappingNode: - log.Debug("its a map with %v entries", len(value.Content)/2) - headString := fmt.Sprintf("%v", head) - return n.recurseMap(value, headString, tail, pathStack) - - case yaml.SequenceNode: - log.Debug("its a sequence of %v things!", len(value.Content)) - - switch head := head.(type) { - case int64: - return n.recurseArray(value, head, head, tail, pathStack) - default: - - if head == "+" { - return n.appendArray(value, head, tail, pathStack) - } else if len(value.Content) == 0 && head == "**" { - return n.navigationStrategy.Visit(nodeContext) - } - return n.splatArray(value, head, tail, pathStack) - } - case yaml.AliasNode: - log.Debug("its an alias!") - DebugNode(value.Alias) - if n.navigationStrategy.FollowAlias(nodeContext) { - log.Debug("following the alias") - return n.recurse(value.Alias, head, tail, pathStack) - } - return nil - case yaml.DocumentNode: - return n.doTraverse(value.Content[0], head, tail, pathStack) - default: - return n.navigationStrategy.Visit(nodeContext) - } -} - -func (n *navigator) recurseMap(value *yaml.Node, head string, tail []interface{}, pathStack []interface{}) error { - traversedEntry := false - errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error { - log.Debug("recurseMap: visitMatchingEntries for %v", contents[indexInMap].Value) - n.navigationStrategy.DebugVisitedNodes() - newPathStack := append(pathStack, contents[indexInMap].Value) - log.Debug("should I traverse? head: %v, path: %v", head, pathStackToString(newPathStack)) - DebugNode(value) - if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) { - log.Debug("recurseMap: Going to traverse") - traversedEntry = true - contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(head, tail, contents[indexInMap+1].Kind)) - errorTraversing := n.doTraverse(contents[indexInMap+1], head, tail, newPathStack) - log.Debug("recurseMap: Finished traversing") - n.navigationStrategy.DebugVisitedNodes() - return errorTraversing - } else { - log.Debug("nope not traversing") - } - return nil - }) - - if errorVisiting != nil { - return errorVisiting - } - - if len(value.Content) == 0 && head == "**" { - return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack)) - } else if traversedEntry || n.navigationStrategy.GetPathParser().IsPathExpression(head) || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) { - return nil - } - - _, errorParsingInt := strconv.ParseInt(head, 10, 64) - - mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode} - - if errorParsingInt == nil { - // fixes a json encoding problem where keys that look like numbers - // get treated as numbers and cannot be used in a json map - mapEntryKey.Style = yaml.LiteralStyle - } - - value.Content = append(value.Content, &mapEntryKey) - mapEntryValue := yaml.Node{Kind: guessKind(head, tail, 0)} - value.Content = append(value.Content, &mapEntryValue) - log.Debug("adding a new node %v - def a string", head) - return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head)) -} - -// need to pass the node in, as it may be aliased -type mapVisitorFn func(contents []*yaml.Node, index int) error - -func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tail []interface{}, pathStack []interface{}, visit mapVisitorFn) error { - var contents = node.Content - for index := 0; index < len(contents); index = index + 2 { - content := contents[index] - - log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) - n.navigationStrategy.DebugVisitedNodes() - errorVisiting := visit(contents, index) - if errorVisiting != nil { - return errorVisiting - } - } - return nil -} - -func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []interface{}, pathStack []interface{}, visit mapVisitorFn) error { - var contents = node.Content - log.Debug("visitMatchingEntries %v", head) - DebugNode(node) - // value.Content is a concatenated array of key, value, - // so keys are in the even indexes, values in odd. - // merge aliases are defined first, but we only want to traverse them - // if we don't find a match directly on this node first. - errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit) - - if errorVisitedDirectEntries != nil || !n.navigationStrategy.FollowAlias(NewNodeContext(node, head, tail, pathStack)) { - return errorVisitedDirectEntries - } - return n.visitAliases(contents, head, tail, pathStack, visit) -} - -func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []interface{}, pathStack []interface{}, visit mapVisitorFn) error { - // merge aliases are defined first, but we only want to traverse them - // if we don't find a match on this node first. - // traverse them backwards so that the last alias overrides the preceding. - // a node can either be - // an alias to one other node (e.g. <<: *blah) - // or a sequence of aliases (e.g. <<: [*blah, *foo]) - log.Debug("checking for aliases, head: %v, pathstack: %v", head, pathStackToString(pathStack)) - for index := len(contents) - 2; index >= 0; index = index - 2 { - - if contents[index+1].Kind == yaml.AliasNode && contents[index].Value == "<<" { - valueNode := contents[index+1] - log.Debug("found an alias") - DebugNode(contents[index]) - DebugNode(valueNode) - - errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit) - if errorInAlias != nil { - return errorInAlias - } - } else if contents[index+1].Kind == yaml.SequenceNode { - // could be an array of aliases... - errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit) - if errorVisitingAliasSeq != nil { - return errorVisitingAliasSeq - } - } - } - return nil -} - -func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []interface{}, pathStack []interface{}, visit mapVisitorFn) error { - // need to search this backwards too, so that aliases defined last override the preceding. - for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 { - child := possibleAliasArray[aliasIndex] - if child.Kind == yaml.AliasNode { - log.Debug("found an alias") - DebugNode(child) - errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit) - if errorInAlias != nil { - return errorInAlias - } - } - } - return nil -} - -func (n *navigator) splatArray(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error { - for index, childValue := range value.Content { - log.Debug("processing") - DebugNode(childValue) - childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind)) - - newPathStack := append(pathStack, index) - if n.navigationStrategy.ShouldTraverse(NewNodeContext(childValue, head, tail, newPathStack), childValue.Value) { - // here we should not deeply traverse the array if we are appending..not sure how to do that. - // need to visit instead... - // easiest way is to pop off the head and pass the rest of the tail in. - var err = n.doTraverse(childValue, head, tail, newPathStack) - if err != nil { - return err - } - } - } - return nil -} - -func (n *navigator) appendArray(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error { - var newNode = yaml.Node{Kind: guessKind(head, tail, 0)} - value.Content = append(value.Content, &newNode) - log.Debug("appending a new node, %v", value.Content) - return n.doTraverse(&newNode, head, tail, append(pathStack, len(value.Content)-1)) -} - -func (n *navigator) recurseArray(value *yaml.Node, index int64, head interface{}, tail []interface{}, pathStack []interface{}) error { - var contentLength = int64(len(value.Content)) - for contentLength <= index { - value.Content = append(value.Content, &yaml.Node{Kind: guessKind(head, tail, 0)}) - contentLength = int64(len(value.Content)) - } - var indexToUse = index - - if indexToUse < 0 { - indexToUse = contentLength + indexToUse - } - - if indexToUse < 0 { - return fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength) - } - - value.Content[indexToUse] = n.getOrReplace(value.Content[indexToUse], guessKind(head, tail, value.Content[indexToUse].Kind)) - - return n.doTraverse(value.Content[indexToUse], head, tail, append(pathStack, index)) -} diff --git a/pkg/yqlib/data_navigator_test.go b/pkg/yqlib/data_navigator_test.go deleted file mode 100644 index 88c44e97..00000000 --- a/pkg/yqlib/data_navigator_test.go +++ /dev/null @@ -1 +0,0 @@ -package yqlib diff --git a/pkg/yqlib/delete_navigation_strategy.go b/pkg/yqlib/delete_navigation_strategy.go deleted file mode 100644 index a0792c38..00000000 --- a/pkg/yqlib/delete_navigation_strategy.go +++ /dev/null @@ -1,73 +0,0 @@ -package yqlib - -import ( - yaml "gopkg.in/yaml.v3" -) - -func DeleteNavigationStrategy(pathElementToDelete interface{}) NavigationStrategy { - parser := NewPathParser() - return &NavigationStrategyImpl{ - visitedNodes: []*NodeContext{}, - pathParser: parser, - followAlias: func(nodeContext NodeContext) bool { - return false - }, - shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool { - return false - }, - visit: func(nodeContext NodeContext) error { - node := nodeContext.Node - log.Debug("need to find and delete %v in here (%v)", pathElementToDelete, pathStackToString(nodeContext.PathStack)) - DebugNode(node) - if node.Kind == yaml.SequenceNode { - newContent := deleteFromArray(parser, node.Content, nodeContext.PathStack, pathElementToDelete) - node.Content = newContent - } else if node.Kind == yaml.MappingNode { - node.Content = deleteFromMap(parser, node.Content, nodeContext.PathStack, pathElementToDelete) - } - return nil - }, - } -} -func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, pathElementToDelete interface{}) []*yaml.Node { - newContents := make([]*yaml.Node, 0) - for index := 0; index < len(contents); index = index + 2 { - keyNode := contents[index] - valueNode := contents[index+1] - if !pathParser.MatchesNextPathElement(NewNodeContext(keyNode, pathElementToDelete, make([]interface{}, 0), pathStack), keyNode.Value) { - log.Debug("adding node %v", keyNode.Value) - newContents = append(newContents, keyNode, valueNode) - } else { - log.Debug("skipping node %v", keyNode.Value) - } - } - return newContents -} - -func deleteFromArray(pathParser PathParser, content []*yaml.Node, pathStack []interface{}, pathElementToDelete interface{}) []*yaml.Node { - - switch pathElementToDelete := pathElementToDelete.(type) { - case int64: - return deleteIndexInArray(content, pathElementToDelete) - default: - log.Debug("%v is not a numeric index, finding matching patterns", pathElementToDelete) - var newArray = make([]*yaml.Node, 0) - - for _, childValue := range content { - if !pathParser.MatchesNextPathElement(NewNodeContext(childValue, pathElementToDelete, make([]interface{}, 0), pathStack), childValue.Value) { - newArray = append(newArray, childValue) - } - } - return newArray - } - -} - -func deleteIndexInArray(content []*yaml.Node, index int64) []*yaml.Node { - log.Debug("deleting index %v in array", index) - if index >= int64(len(content)) { - log.Debug("index %v is greater than content length %v", index, len(content)) - return content - } - return append(content[:index], content[index+1:]...) -} diff --git a/pkg/yqlib/filter_matching_node_navigation_strategy.go b/pkg/yqlib/filter_matching_node_navigation_strategy.go deleted file mode 100644 index 4c15c4fe..00000000 --- a/pkg/yqlib/filter_matching_node_navigation_strategy.go +++ /dev/null @@ -1,15 +0,0 @@ -package yqlib - -func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy { - return &NavigationStrategyImpl{ - visitedNodes: []*NodeContext{}, - pathParser: NewPathParser(), - visit: func(nodeContext NodeContext) error { - return nil - }, - shouldVisitExtraFn: func(nodeContext NodeContext) bool { - log.Debug("does %v match %v ? %v", nodeContext.Node.Value, value, nodeContext.Node.Value == value) - return matchesString(value, nodeContext.Node.Value) - }, - } -} diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go deleted file mode 100644 index 3f548f6b..00000000 --- a/pkg/yqlib/lib.go +++ /dev/null @@ -1,208 +0,0 @@ -package yqlib - -import ( - "bytes" - "fmt" - "strconv" - "strings" - - logging "gopkg.in/op/go-logging.v1" - yaml "gopkg.in/yaml.v3" -) - -var log = logging.MustGetLogger("yq") - -type UpdateCommand struct { - Command string - Path string - Value *yaml.Node - Overwrite bool - DontUpdateNodeValue bool - DontUpdateNodeContent bool - CommentsMergeStrategy CommentsMergeStrategy -} - -func KindString(kind yaml.Kind) string { - switch kind { - case yaml.ScalarNode: - return "ScalarNode" - case yaml.SequenceNode: - return "SequenceNode" - case yaml.MappingNode: - return "MappingNode" - case yaml.DocumentNode: - return "DocumentNode" - case yaml.AliasNode: - return "AliasNode" - default: - return "unknown!" - } -} - -func DebugNode(value *yaml.Node) { - if value == nil { - log.Debug("-- node is nil --") - } else if log.IsEnabledFor(logging.DEBUG) { - buf := new(bytes.Buffer) - encoder := yaml.NewEncoder(buf) - errorEncoding := encoder.Encode(value) - if errorEncoding != nil { - log.Error("Error debugging node, %v", errorEncoding.Error()) - } - encoder.Close() - log.Debug("Tag: %v, Kind: %v, Anchor: %v", value.Tag, KindString(value.Kind), value.Anchor) - log.Debug("Head Comment: %v", value.HeadComment) - log.Debug("Line Comment: %v", value.LineComment) - log.Debug("FootComment Comment: %v", value.FootComment) - log.Debug("\n%v", buf.String()) - } -} - -func pathStackToString(pathStack []interface{}) string { - return mergePathStackToString(pathStack, UpdateArrayMergeStrategy) -} - -func mergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string { - var sb strings.Builder - for index, path := range pathStack { - switch path.(type) { - case int, int64: - if arrayMergeStrategy == AppendArrayMergeStrategy { - sb.WriteString("[+]") - } else { - sb.WriteString(fmt.Sprintf("[%v]", path)) - } - - default: - s := fmt.Sprintf("%v", path) - var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint - - hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"") - hasDoubleQuotes := strings.Contains(s, "\"") - wrappingCharacterStart := "\"" - wrappingCharacterEnd := "\"" - if hasDoubleQuotes { - wrappingCharacterStart = "(" - wrappingCharacterEnd = ")" - } - if hasSpecial || errParsingInt == nil { - sb.WriteString(wrappingCharacterStart) - } - sb.WriteString(s) - if hasSpecial || errParsingInt == nil { - sb.WriteString(wrappingCharacterEnd) - } - } - - if index < len(pathStack)-1 { - sb.WriteString(".") - } - } - return sb.String() -} - -func guessKind(head interface{}, tail []interface{}, guess yaml.Kind) yaml.Kind { - log.Debug("guessKind: tail %v", tail) - if len(tail) == 0 && guess == 0 { - log.Debug("end of path, must be a scalar") - return yaml.ScalarNode - } else if len(tail) == 0 { - return guess - } - var next = tail[0] - switch next.(type) { - case int64: - return yaml.SequenceNode - default: - var nextString = fmt.Sprintf("%v", next) - if nextString == "+" { - return yaml.SequenceNode - } - pathParser := NewPathParser() - if pathParser.IsPathExpression(nextString) && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { - return guess - } else if guess == yaml.AliasNode { - log.Debug("guess was an alias, okey doke.") - return guess - } else if head == "**" { - log.Debug("deep wildcard, go with the guess") - return guess - } - log.Debug("forcing a mapping node") - log.Debug("yaml.SequenceNode %v", guess == yaml.SequenceNode) - log.Debug("yaml.ScalarNode %v", guess == yaml.ScalarNode) - return yaml.MappingNode - } -} - -type YqLib interface { - Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) - GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) - Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error - New(path string) yaml.Node - - PathStackToString(pathStack []interface{}) string - MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string -} - -type lib struct { - parser PathParser -} - -func NewYqLib() YqLib { - return &lib{ - parser: NewPathParser(), - } -} - -func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) { - var paths = l.parser.ParsePath(path) - navigationStrategy := ReadNavigationStrategy() - navigator := NewDataNavigator(navigationStrategy) - error := navigator.Traverse(rootNode, paths) - return navigationStrategy.GetVisitedNodes(), error -} - -func (l *lib) GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) { - var paths = l.parser.ParsePath(path) - navigationStrategy := ReadForMergeNavigationStrategy(arrayMergeStrategy) - navigator := NewDataNavigator(navigationStrategy) - error := navigator.Traverse(rootNode, paths) - return navigationStrategy.GetVisitedNodes(), error -} - -func (l *lib) PathStackToString(pathStack []interface{}) string { - return pathStackToString(pathStack) -} - -func (l *lib) MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string { - return mergePathStackToString(pathStack, arrayMergeStrategy) -} - -func (l *lib) New(path string) yaml.Node { - var paths = l.parser.ParsePath(path) - newNode := yaml.Node{Kind: guessKind("", paths, 0)} - return newNode -} - -func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error { - log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path) - switch updateCommand.Command { - case "update": - var paths = l.parser.ParsePath(updateCommand.Path) - navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand, autoCreate)) - return navigator.Traverse(rootNode, paths) - case "merge": - var paths = l.parser.ParsePath(updateCommand.Path) - navigator := NewDataNavigator(MergeNavigationStrategy(updateCommand, autoCreate)) - return navigator.Traverse(rootNode, paths) - case "delete": - var paths = l.parser.ParsePath(updateCommand.Path) - lastBit, newTail := paths[len(paths)-1], paths[:len(paths)-1] - navigator := NewDataNavigator(DeleteNavigationStrategy(lastBit)) - return navigator.Traverse(rootNode, newTail) - default: - return fmt.Errorf("Unknown command %v", updateCommand.Command) - } - -} diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go deleted file mode 100644 index 52e7933c..00000000 --- a/pkg/yqlib/lib_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package yqlib - -import ( - "testing" - - "github.com/mikefarah/yq/v3/test" -) - -func TestLib(t *testing.T) { - - subject := NewYqLib() - - t.Run("PathStackToString_Empty", func(t *testing.T) { - emptyArray := make([]interface{}, 0) - got := subject.PathStackToString(emptyArray) - test.AssertResult(t, ``, got) - }) - - t.Run("PathStackToString", func(t *testing.T) { - array := make([]interface{}, 3) - array[0] = "a" - array[1] = 0 - array[2] = "b" - got := subject.PathStackToString(array) - test.AssertResult(t, `a.[0].b`, got) - }) - - t.Run("MergePathStackToString", func(t *testing.T) { - array := make([]interface{}, 3) - array[0] = "a" - array[1] = 0 - array[2] = "b" - got := subject.MergePathStackToString(array, AppendArrayMergeStrategy) - test.AssertResult(t, `a.[+].b`, got) - }) - - // t.Run("TestReadPath_WithError", func(t *testing.T) { - // var data = test.ParseData(` - // --- - // b: - // - c - // `) - - // _, err := subject.ReadPath(data, "b.[a]") - // if err == nil { - // t.Fatal("Expected error due to invalid path") - // } - // }) - - // t.Run("TestWritePath", func(t *testing.T) { - // var data = test.ParseData(` - // --- - // b: - // 2: c - // `) - - // got := subject.WritePath(data, "b.3", "a") - // test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got)) - // }) - - // t.Run("TestPrefixPath", func(t *testing.T) { - // var data = test.ParseData(` - // --- - // b: - // 2: c - // `) - - // got := subject.PrefixPath(data, "a.d") - // test.AssertResult(t, `[{a [{d [{b [{2 c}]}]}]}]`, fmt.Sprintf("%v", got)) - // }) - - // t.Run("TestDeletePath", func(t *testing.T) { - // var data = test.ParseData(` - // --- - // b: - // 2: c - // 3: a - // `) - - // got, _ := subject.DeletePath(data, "b.2") - // test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got)) - // }) - - // t.Run("TestDeletePath_WithError", func(t *testing.T) { - // var data = test.ParseData(` - // --- - // b: - // - c - // `) - - // _, err := subject.DeletePath(data, "b.[a]") - // if err == nil { - // t.Fatal("Expected error due to invalid path") - // } - // }) - - // t.Run("TestMerge", func(t *testing.T) { - // var dst = test.ParseData(` - // --- - // a: b - // c: d - // `) - // var src = test.ParseData(` - // --- - // a: 1 - // b: 2 - // `) - - // var mergedData = make(map[interface{}]interface{}) - // mergedData["root"] = dst - // var mapDataBucket = make(map[interface{}]interface{}) - // mapDataBucket["root"] = src - - // err := subject.Merge(&mergedData, mapDataBucket, false, false) - // if err != nil { - // t.Fatal("Unexpected error") - // } - // test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"])) - // }) - - // t.Run("TestMerge_WithOverwrite", func(t *testing.T) { - // var dst = test.ParseData(` - // --- - // a: b - // c: d - // `) - // var src = test.ParseData(` - // --- - // a: 1 - // b: 2 - // `) - - // var mergedData = make(map[interface{}]interface{}) - // mergedData["root"] = dst - // var mapDataBucket = make(map[interface{}]interface{}) - // mapDataBucket["root"] = src - - // err := subject.Merge(&mergedData, mapDataBucket, true, false) - // if err != nil { - // t.Fatal("Unexpected error") - // } - // test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) - // }) - - // t.Run("TestMerge_WithAppend", func(t *testing.T) { - // var dst = test.ParseData(` - // --- - // a: b - // c: d - // `) - // var src = test.ParseData(` - // --- - // a: 1 - // b: 2 - // `) - - // var mergedData = make(map[interface{}]interface{}) - // mergedData["root"] = dst - // var mapDataBucket = make(map[interface{}]interface{}) - // mapDataBucket["root"] = src - - // err := subject.Merge(&mergedData, mapDataBucket, false, true) - // if err != nil { - // t.Fatal("Unexpected error") - // } - // test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"])) - // }) - - // t.Run("TestMerge_WithError", func(t *testing.T) { - // err := subject.Merge(nil, nil, false, false) - // if err == nil { - // t.Fatal("Expected error due to nil") - // } - // }) - -} diff --git a/pkg/yqlib/merge_navigation_strategy.go b/pkg/yqlib/merge_navigation_strategy.go deleted file mode 100644 index 686ba4eb..00000000 --- a/pkg/yqlib/merge_navigation_strategy.go +++ /dev/null @@ -1,103 +0,0 @@ -package yqlib - -import "gopkg.in/yaml.v3" - -type ArrayMergeStrategy uint32 - -const ( - UpdateArrayMergeStrategy ArrayMergeStrategy = 1 << iota - OverwriteArrayMergeStrategy - AppendArrayMergeStrategy -) - -type CommentsMergeStrategy uint32 - -const ( - SetWhenBlankCommentsMergeStrategy CommentsMergeStrategy = 1 << iota - IgnoreCommentsMergeStrategy - OverwriteCommentsMergeStrategy - AppendCommentsMergeStrategy -) - -func MergeNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy { - return &NavigationStrategyImpl{ - visitedNodes: []*NodeContext{}, - pathParser: NewPathParser(), - followAlias: func(nodeContext NodeContext) bool { - return false - }, - autoCreateMap: func(nodeContext NodeContext) bool { - return autoCreate - }, - visit: func(nodeContext NodeContext) error { - node := nodeContext.Node - changesToApply := updateCommand.Value - - if node.Kind == yaml.DocumentNode && changesToApply.Kind != yaml.DocumentNode { - // when the path is empty, it matches both the top level pseudo document node - // and the actual top level node (e.g. map/sequence/whatever) - // so when we are updating with no path, make sure we update the right node. - node = node.Content[0] - } - - log.Debug("going to update") - DebugNode(node) - log.Debug("with") - DebugNode(changesToApply) - - if updateCommand.Overwrite || node.Value == "" { - node.Value = changesToApply.Value - node.Tag = changesToApply.Tag - node.Kind = changesToApply.Kind - node.Style = changesToApply.Style - node.Anchor = changesToApply.Anchor - node.Alias = changesToApply.Alias - - if !updateCommand.DontUpdateNodeContent { - node.Content = changesToApply.Content - } - } else { - log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite) - } - - switch updateCommand.CommentsMergeStrategy { - case OverwriteCommentsMergeStrategy: - node.HeadComment = changesToApply.HeadComment - node.LineComment = changesToApply.LineComment - node.FootComment = changesToApply.FootComment - case SetWhenBlankCommentsMergeStrategy: - if node.HeadComment == "" { - node.HeadComment = changesToApply.HeadComment - } - if node.LineComment == "" { - node.LineComment = changesToApply.LineComment - } - if node.FootComment == "" { - node.FootComment = changesToApply.FootComment - } - case AppendCommentsMergeStrategy: - if node.HeadComment == "" { - node.HeadComment = changesToApply.HeadComment - } else { - node.HeadComment = node.HeadComment + "\n" + changesToApply.HeadComment - } - if node.LineComment == "" { - node.LineComment = changesToApply.LineComment - } else { - node.LineComment = node.LineComment + " " + changesToApply.LineComment - } - if node.FootComment == "" { - node.FootComment = changesToApply.FootComment - } else { - node.FootComment = node.FootComment + "\n" + changesToApply.FootComment - } - default: - } - - log.Debug("result") - DebugNode(node) - - return nil - }, - } -} diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go deleted file mode 100644 index e88641d0..00000000 --- a/pkg/yqlib/navigation_strategy.go +++ /dev/null @@ -1,180 +0,0 @@ -package yqlib - -import ( - "fmt" - - yaml "gopkg.in/yaml.v3" -) - -type NodeContext struct { - Node *yaml.Node - Head interface{} - Tail []interface{} - PathStack []interface{} - // middle nodes are nodes that match along the original path, but not a - // target match of the path. This is only relevant when ShouldOnlyDeeplyVisitLeaves is false. - IsMiddleNode bool -} - -func NewNodeContext(node *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) NodeContext { - newTail := make([]interface{}, len(tail)) - copy(newTail, tail) - - newPathStack := make([]interface{}, len(pathStack)) - copy(newPathStack, pathStack) - return NodeContext{ - Node: node, - Head: head, - Tail: newTail, - PathStack: newPathStack, - } -} - -type NavigationStrategy interface { - FollowAlias(nodeContext NodeContext) bool - AutoCreateMap(nodeContext NodeContext) bool - Visit(nodeContext NodeContext) error - // node key is the string value of the last element in the path stack - // we use it to match against the pathExpression in head. - ShouldTraverse(nodeContext NodeContext, nodeKey string) bool - ShouldDeeplyTraverse(nodeContext NodeContext) bool - // when deeply traversing, should we visit all matching nodes, or just leaves? - ShouldOnlyDeeplyVisitLeaves(NodeContext) bool - GetVisitedNodes() []*NodeContext - DebugVisitedNodes() - GetPathParser() PathParser -} - -type NavigationStrategyImpl struct { - followAlias func(nodeContext NodeContext) bool - autoCreateMap func(nodeContext NodeContext) bool - visit func(nodeContext NodeContext) error - shouldVisitExtraFn func(nodeContext NodeContext) bool - shouldDeeplyTraverse func(nodeContext NodeContext) bool - shouldOnlyDeeplyVisitLeaves func(nodeContext NodeContext) bool - visitedNodes []*NodeContext - pathParser PathParser -} - -func (ns *NavigationStrategyImpl) GetPathParser() PathParser { - return ns.pathParser -} - -func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext { - return ns.visitedNodes -} - -func (ns *NavigationStrategyImpl) FollowAlias(nodeContext NodeContext) bool { - if ns.followAlias != nil { - return ns.followAlias(nodeContext) - } - return true -} - -func (ns *NavigationStrategyImpl) AutoCreateMap(nodeContext NodeContext) bool { - if ns.autoCreateMap != nil { - return ns.autoCreateMap(nodeContext) - } - return false -} - -func (ns *NavigationStrategyImpl) ShouldDeeplyTraverse(nodeContext NodeContext) bool { - if ns.shouldDeeplyTraverse != nil { - return ns.shouldDeeplyTraverse(nodeContext) - } - return true -} - -func (ns *NavigationStrategyImpl) ShouldOnlyDeeplyVisitLeaves(nodeContext NodeContext) bool { - if ns.shouldOnlyDeeplyVisitLeaves != nil { - return ns.shouldOnlyDeeplyVisitLeaves(nodeContext) - } - return true - -} - -func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKey string) bool { - // we should traverse aliases (if enabled), but not visit them :/ - if len(nodeContext.PathStack) == 0 { - return true - } - - if ns.alreadyVisited(nodeContext.PathStack) { - return false - } - - return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" && - ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey)) -} - -func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { - pathStack := nodeContext.PathStack - if len(pathStack) == 0 { - return true - } - log.Debug("tail len %v", len(nodeContext.Tail)) - - if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 { - return false - } - - nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) - log.Debug("nodeKey: %v, nodeContext.Head: %v", nodeKey, nodeContext.Head) - - // only visit aliases if its an exact match - return ((nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" && - ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext)) -} - -func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error { - log.Debug("Visit?, %v, %v", nodeContext.Head, pathStackToString(nodeContext.PathStack)) - DebugNode(nodeContext.Node) - if ns.shouldVisit(nodeContext) { - log.Debug("yep, visiting") - // pathStack array must be - // copied, as append() may sometimes reuse and modify the array - ns.visitedNodes = append(ns.visitedNodes, &nodeContext) - ns.DebugVisitedNodes() - return ns.visit(nodeContext) - } - log.Debug("nope, skip it") - return nil -} - -func (ns *NavigationStrategyImpl) DebugVisitedNodes() { - log.Debug("Visited Nodes:") - for _, candidate := range ns.visitedNodes { - log.Debug(" - %v", pathStackToString(candidate.PathStack)) - } -} - -func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool { - log.Debug("checking already visited pathStack: %v", pathStackToString(pathStack)) - for _, candidate := range ns.visitedNodes { - candidatePathStack := candidate.PathStack - if patchStacksMatch(candidatePathStack, pathStack) { - log.Debug("paths match, already seen it") - return true - } - - } - log.Debug("never seen it before!") - return false -} - -func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool { - log.Debug("checking against path: %v", pathStackToString(path1)) - - if len(path1) != len(path2) { - return false - } - for index, p1Value := range path1 { - - p2Value := path2[index] - if p1Value != p2Value { - return false - } - } - return true - -} diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go deleted file mode 100644 index 4e518990..00000000 --- a/pkg/yqlib/path_parser.go +++ /dev/null @@ -1,153 +0,0 @@ -package yqlib - -import ( - "fmt" - "strconv" - "strings" - - yaml "gopkg.in/yaml.v3" -) - -type PathParser interface { - ParsePath(path string) []interface{} - MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool - IsPathExpression(pathElement string) bool -} - -type pathParser struct{} - -func NewPathParser() PathParser { - return &pathParser{} -} - -func matchesString(expression string, value string) bool { - var prefixMatch = strings.TrimSuffix(expression, "*") - if prefixMatch != expression { - log.Debug("prefix match, %v", strings.HasPrefix(value, prefixMatch)) - return strings.HasPrefix(value, prefixMatch) - } - return value == expression -} - -func (p *pathParser) IsPathExpression(pathElement string) bool { - return pathElement == "*" || pathElement == "**" || strings.Contains(pathElement, "==") -} - -/** - * node: node that we may traverse/visit - * head: path element expression to match against - * tail: remaining path element expressions - * pathStack: stack of actual paths we've matched to get to node - * nodeKey: actual value of this nodes 'key' or index. - */ -func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool { - head := nodeContext.Head - if head == "**" || head == "*" { - return true - } - var headString = fmt.Sprintf("%v", head) - - if strings.Contains(headString, "==") && nodeContext.Node.Kind != yaml.ScalarNode { - log.Debug("ooh deep recursion time") - result := strings.SplitN(headString, "==", 2) - path := strings.TrimSpace(result[0]) - value := strings.TrimSpace(result[1]) - log.Debug("path %v", path) - log.Debug("value %v", value) - DebugNode(nodeContext.Node) - navigationStrategy := FilterMatchingNodesNavigationStrategy(value) - - navigator := NewDataNavigator(navigationStrategy) - err := navigator.Traverse(nodeContext.Node, p.ParsePath(path)) - if err != nil { - log.Error("Error deep recursing - ignoring") - log.Error(err.Error()) - } - log.Debug("done deep recursing, found %v matches", len(navigationStrategy.GetVisitedNodes())) - return len(navigationStrategy.GetVisitedNodes()) > 0 - } else if strings.Contains(headString, "==") && nodeContext.Node.Kind == yaml.ScalarNode { - result := strings.SplitN(headString, "==", 2) - path := strings.TrimSpace(result[0]) - value := strings.TrimSpace(result[1]) - if path == "." { - log.Debug("need to match scalar") - return matchesString(value, nodeContext.Node.Value) - } - } - - if head == "+" { - log.Debug("head is +, nodeKey is %v", nodeKey) - var _, err = strconv.ParseInt(nodeKey, 10, 64) // nolint - if err == nil { - return true - } - } - - return matchesString(headString, nodeKey) -} - -func (p *pathParser) ParsePath(path string) []interface{} { - var paths = make([]interface{}, 0) - if path == "" { - return paths - } - return p.parsePathAccum(paths, path) -} - -func (p *pathParser) parsePathAccum(paths []interface{}, remaining string) []interface{} { - head, tail := p.nextYamlPath(remaining) - if tail == "" { - return append(paths, head) - } - return p.parsePathAccum(append(paths, head), tail) -} - -func (p *pathParser) nextYamlPath(path string) (pathElement interface{}, remaining string) { - switch path[0] { - case '[': - // e.g [0].blah.cat -> we need to return "0" and "blah.cat" - var value, remainingBit = p.search(path[1:], []uint8{']'}, true) - var number, errParsingInt = strconv.ParseInt(value, 10, 64) // nolint - if errParsingInt == nil { - return number, remainingBit - } - return value, remainingBit - case '"': - // e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat" - return p.search(path[1:], []uint8{'"'}, true) - case '(': - // e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat" - return p.search(path[1:], []uint8{')'}, true) - default: - // e.g "a.blah.cat" -> return "a" and "blah.cat" - return p.search(path[0:], []uint8{'.', '[', '"', '('}, false) - } -} - -func (p *pathParser) search(path string, matchingChars []uint8, skipNext bool) (pathElement string, remaining string) { - for i := 0; i < len(path); i++ { - var char = path[i] - if p.contains(matchingChars, char) { - var remainingStart = i + 1 - if skipNext { - remainingStart = remainingStart + 1 - } else if !skipNext && char != '.' { - remainingStart = i - } - if remainingStart > len(path) { - remainingStart = len(path) - } - return path[0:i], path[remainingStart:] - } - } - return path, "" -} - -func (p *pathParser) contains(matchingChars []uint8, candidate uint8) bool { - for _, a := range matchingChars { - if a == candidate { - return true - } - } - return false -} diff --git a/pkg/yqlib/path_parser_test.go b/pkg/yqlib/path_parser_test.go deleted file mode 100644 index 7af12667..00000000 --- a/pkg/yqlib/path_parser_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package yqlib - -import ( - "testing" - - "github.com/mikefarah/yq/v3/test" -) - -var parser = NewPathParser() - -var parsePathsTests = []struct { - path string - expectedPaths []interface{} -}{ - {"a.b", append(make([]interface{}, 0), "a", "b")}, - {"a.b.**", append(make([]interface{}, 0), "a", "b", "**")}, - {"a.b.*", append(make([]interface{}, 0), "a", "b", "*")}, - {"a.b[0]", append(make([]interface{}, 0), "a", "b", int64(0))}, - {"a.b.0", append(make([]interface{}, 0), "a", "b", "0")}, - {"a.b.d[+]", append(make([]interface{}, 0), "a", "b", "d", "+")}, - {"a", append(make([]interface{}, 0), "a")}, - {"a.b.c", append(make([]interface{}, 0), "a", "b", "c")}, - {"\"a.b\".c", append(make([]interface{}, 0), "a.b", "c")}, - {"a.\"b.c\".d", append(make([]interface{}, 0), "a", "b.c", "d")}, - {"[1].a.d", append(make([]interface{}, 0), int64(1), "a", "d")}, - {"a[0].c", append(make([]interface{}, 0), "a", int64(0), "c")}, - {"[0]", append(make([]interface{}, 0), int64(0))}, -} - -func TestPathParserParsePath(t *testing.T) { - for _, tt := range parsePathsTests { - test.AssertResultComplex(t, tt.expectedPaths, parser.ParsePath(tt.path)) - } -} - -func TestPathParserMatchesNextPathElementSplat(t *testing.T) { - var node = NodeContext{Head: "*"} - test.AssertResult(t, true, parser.MatchesNextPathElement(node, "")) -} - -func TestPathParserMatchesNextPathElementDeepSplat(t *testing.T) { - var node = NodeContext{Head: "**"} - test.AssertResult(t, true, parser.MatchesNextPathElement(node, "")) -} - -func TestPathParserMatchesNextPathElementAppendArrayValid(t *testing.T) { - var node = NodeContext{Head: "+"} - test.AssertResult(t, true, parser.MatchesNextPathElement(node, "3")) -} - -func TestPathParserMatchesNextPathElementAppendArrayInvalid(t *testing.T) { - var node = NodeContext{Head: "+"} - test.AssertResult(t, false, parser.MatchesNextPathElement(node, "cat")) -} - -func TestPathParserMatchesNextPathElementPrefixMatchesWhole(t *testing.T) { - var node = NodeContext{Head: "cat*"} - test.AssertResult(t, true, parser.MatchesNextPathElement(node, "cat")) -} - -func TestPathParserMatchesNextPathElementPrefixMatchesStart(t *testing.T) { - var node = NodeContext{Head: "cat*"} - test.AssertResult(t, true, parser.MatchesNextPathElement(node, "caterpillar")) -} - -func TestPathParserMatchesNextPathElementPrefixMismatch(t *testing.T) { - var node = NodeContext{Head: "cat*"} - test.AssertResult(t, false, parser.MatchesNextPathElement(node, "dog")) -} - -func TestPathParserMatchesNextPathElementExactMatch(t *testing.T) { - var node = NodeContext{Head: "farahtek"} - test.AssertResult(t, true, parser.MatchesNextPathElement(node, "farahtek")) -} - -func TestPathParserMatchesNextPathElementExactMismatch(t *testing.T) { - var node = NodeContext{Head: "farahtek"} - test.AssertResult(t, false, parser.MatchesNextPathElement(node, "othertek")) -} diff --git a/pkg/yqlib/read_for_merge_navigation_strategy.go b/pkg/yqlib/read_for_merge_navigation_strategy.go deleted file mode 100644 index 73061338..00000000 --- a/pkg/yqlib/read_for_merge_navigation_strategy.go +++ /dev/null @@ -1,37 +0,0 @@ -package yqlib - -import "gopkg.in/yaml.v3" - -func ReadForMergeNavigationStrategy(arrayMergeStrategy ArrayMergeStrategy) NavigationStrategy { - return &NavigationStrategyImpl{ - visitedNodes: []*NodeContext{}, - pathParser: NewPathParser(), - followAlias: func(nodeContext NodeContext) bool { - return false - }, - shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool { - return false - }, - visit: func(nodeContext NodeContext) error { - return nil - }, - shouldDeeplyTraverse: func(nodeContext NodeContext) bool { - if nodeContext.Node.Kind == yaml.SequenceNode && arrayMergeStrategy == OverwriteArrayMergeStrategy { - nodeContext.IsMiddleNode = false - return false - } - - var isInArray = false - if len(nodeContext.PathStack) > 0 { - var lastElement = nodeContext.PathStack[len(nodeContext.PathStack)-1] - switch lastElement.(type) { - case int: - isInArray = true - default: - isInArray = false - } - } - return arrayMergeStrategy == UpdateArrayMergeStrategy || !isInArray - }, - } -} diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go deleted file mode 100644 index ba29e94e..00000000 --- a/pkg/yqlib/read_navigation_strategy.go +++ /dev/null @@ -1,11 +0,0 @@ -package yqlib - -func ReadNavigationStrategy() NavigationStrategy { - return &NavigationStrategyImpl{ - visitedNodes: []*NodeContext{}, - pathParser: NewPathParser(), - visit: func(nodeContext NodeContext) error { - return nil - }, - } -} diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go new file mode 100644 index 00000000..13576899 --- /dev/null +++ b/pkg/yqlib/treeops/candidate_node.go @@ -0,0 +1,62 @@ +package treeops + +import ( + "fmt" + "strconv" + "strings" + + "gopkg.in/yaml.v3" +) + +type CandidateNode struct { + Node *yaml.Node // the actual node + Path []interface{} /// the path we took to get to this node + Document uint // the document index of this node +} + +func (n *CandidateNode) GetKey() string { + return fmt.Sprintf("%v - %v", n.Document, n.Path) +} + +func (n *CandidateNode) PathStackToString() string { + return mergePathStackToString(n.Path) +} + +func mergePathStackToString(pathStack []interface{}) string { + var sb strings.Builder + for index, path := range pathStack { + switch path.(type) { + case int, int64: + // if arrayMergeStrategy == AppendArrayMergeStrategy { + // sb.WriteString("[+]") + // } else { + sb.WriteString(fmt.Sprintf("[%v]", path)) + // } + + default: + s := fmt.Sprintf("%v", path) + var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint + + hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"") + hasDoubleQuotes := strings.Contains(s, "\"") + wrappingCharacterStart := "\"" + wrappingCharacterEnd := "\"" + if hasDoubleQuotes { + wrappingCharacterStart = "(" + wrappingCharacterEnd = ")" + } + if hasSpecial || errParsingInt == nil { + sb.WriteString(wrappingCharacterStart) + } + sb.WriteString(s) + if hasSpecial || errParsingInt == nil { + sb.WriteString(wrappingCharacterEnd) + } + } + + if index < len(pathStack)-1 { + sb.WriteString(".") + } + } + return sb.String() +} diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 1a33c1e8..a5cfaaa9 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -35,7 +35,7 @@ func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode * return nil, err } for _, n := range newNodes { - matchingNodeMap.Set(n.getKey(), n) + matchingNodeMap.Set(n.GetKey(), n) } } @@ -46,7 +46,7 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat var matchingNodeMap = orderedmap.NewOrderedMap() for _, n := range matchingNodes { - matchingNodeMap.Set(n.getKey(), n) + matchingNodeMap.Set(n.GetKey(), n) } matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode) @@ -63,6 +63,10 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat } func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + if pathNode == nil { + log.Debugf("getMatchingNodes - nothing to do") + return matchingNodes, nil + } log.Debugf("Processing Path: %v", pathNode.PathElement.toString()) if pathNode.PathElement.PathElementType == SelfReference { return matchingNodes, nil diff --git a/pkg/yqlib/treeops/delete_operator.go b/pkg/yqlib/treeops/delete_operator.go index c86354a5..c80479b1 100644 --- a/pkg/yqlib/treeops/delete_operator.go +++ b/pkg/yqlib/treeops/delete_operator.go @@ -17,7 +17,7 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered for el := lhs.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) elMap := orderedmap.NewOrderedMap() - elMap.Set(candidate.getKey(), candidate) + elMap.Set(candidate.GetKey(), candidate) nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs) log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete)) if err != nil { @@ -50,9 +50,9 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa Document: candidate.Document, Path: append(candidate.Path, key.Value), } - _, shouldDelete := nodesToDelete.Get(childCandidate.getKey()) + _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) - log.Debugf("shouldDelete %v ? %v", childCandidate.getKey(), shouldDelete) + log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete) if !shouldDelete { newContents = append(newContents, key, value) @@ -76,7 +76,7 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.Ordered Path: append(candidate.Path, index), } - _, shouldDelete := nodesToDelete.Get(childCandidate.getKey()) + _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) if !shouldDelete { newContents = append(newContents, value) } diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 0ec9f272..f52319ac 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -11,16 +11,6 @@ import ( var log = logging.MustGetLogger("yq-treeops") -type CandidateNode struct { - Node *yaml.Node // the actual node - Path []interface{} /// the path we took to get to this node - Document uint // the document index of this node -} - -func (n *CandidateNode) getKey() string { - return fmt.Sprintf("%v - %v", n.Document, n.Path) -} - type PathElementType uint32 const ( @@ -76,7 +66,7 @@ func (p *PathElement) toString() string { } type YqTreeLib interface { - Get(rootNode *yaml.Node, path string) ([]*CandidateNode, error) + Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error) // GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) // Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error // New(path string) yaml.Node @@ -85,10 +75,24 @@ type YqTreeLib interface { // MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string } +func NewYqTreeLib() YqTreeLib { + return &lib{treeCreator: NewPathTreeCreator()} +} + type lib struct { treeCreator PathTreeCreator } +func (l *lib) Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error) { + nodes := []*CandidateNode{&CandidateNode{Node: documentNode.Content[0], Document: 0}} + navigator := NewDataTreeNavigator(NavigationPrefs{}) + pathNode, errPath := l.treeCreator.ParsePath(path) + if errPath != nil { + return nil, errPath + } + return navigator.GetMatchingNodes(nodes, pathNode) +} + //use for debugging only func NodesToString(collection *orderedmap.OrderedMap) string { if !log.IsEnabledFor(logging.DEBUG) { diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index b6042ce3..16eabce9 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -24,7 +24,7 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, } for el := lhs.Front(); el != nil; el = el.Next() { node := el.Value.(*CandidateNode) - log.Debugf("Assiging %v to %v", node.getKey(), pathNode.Rhs.PathElement.StringValue) + log.Debugf("Assiging %v to %v", node.GetKey(), pathNode.Rhs.PathElement.StringValue) node.Node.Value = pathNode.Rhs.PathElement.StringValue } return lhs, nil @@ -41,7 +41,7 @@ func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p } for el := rhs.Front(); el != nil; el = el.Next() { node := el.Value.(*CandidateNode) - lhs.Set(node.getKey(), node) + lhs.Set(node.GetKey(), node) } return lhs, nil } @@ -67,7 +67,7 @@ func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordere func splatNode(d *dataTreeNavigator, candidate *CandidateNode) (*orderedmap.OrderedMap, error) { elMap := orderedmap.NewOrderedMap() - elMap.Set(candidate.getKey(), candidate) + elMap.Set(candidate.GetKey(), candidate) //need to splat matching nodes, then search through them splatter := &PathTreeNode{PathElement: &PathElement{ PathElementType: PathKey, @@ -112,7 +112,7 @@ func CountOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNo length := childMatches.Len() node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"} lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} - results.Set(candidate.getKey(), lengthCand) + results.Set(candidate.GetKey(), lengthCand) } @@ -131,7 +131,7 @@ func findMatchingChildren(d *dataTreeNavigator, results *orderedmap.OrderedMap, } } else { children = orderedmap.NewOrderedMap() - children.Set(candidate.getKey(), candidate) + children.Set(candidate.GetKey(), candidate) } for childEl := children.Front(); childEl != nil; childEl = childEl.Next() { diff --git a/pkg/yqlib/treeops/path_tree.go b/pkg/yqlib/treeops/path_tree.go index fe1353fb..3ca09e32 100644 --- a/pkg/yqlib/treeops/path_tree.go +++ b/pkg/yqlib/treeops/path_tree.go @@ -39,6 +39,10 @@ func (p *pathTreeCreator) ParsePath(path string) (*PathTreeNode, error) { func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeNode, error) { var stack = make([]*PathTreeNode, 0) + if len(postFixPath) == 0 { + return nil, nil + } + for _, pathElement := range postFixPath { var newNode = PathTreeNode{PathElement: pathElement} if pathElement.PathElementType == Operation { diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go deleted file mode 100644 index b5b0e750..00000000 --- a/pkg/yqlib/update_navigation_strategy.go +++ /dev/null @@ -1,43 +0,0 @@ -package yqlib - -func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy { - return &NavigationStrategyImpl{ - visitedNodes: []*NodeContext{}, - pathParser: NewPathParser(), - followAlias: func(nodeContext NodeContext) bool { - return false - }, - autoCreateMap: func(nodeContext NodeContext) bool { - return autoCreate - }, - visit: func(nodeContext NodeContext) error { - node := nodeContext.Node - changesToApply := updateCommand.Value - if updateCommand.Overwrite || node.Value == "" { - log.Debug("going to update") - DebugNode(node) - log.Debug("with") - DebugNode(changesToApply) - if !updateCommand.DontUpdateNodeValue { - node.Value = changesToApply.Value - } - node.Tag = changesToApply.Tag - node.Kind = changesToApply.Kind - node.Style = changesToApply.Style - if !updateCommand.DontUpdateNodeContent { - node.Content = changesToApply.Content - } - node.Anchor = changesToApply.Anchor - node.Alias = changesToApply.Alias - if updateCommand.CommentsMergeStrategy != IgnoreCommentsMergeStrategy { - node.HeadComment = changesToApply.HeadComment - node.LineComment = changesToApply.LineComment - node.FootComment = changesToApply.FootComment - } - } else { - log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite) - } - return nil - }, - } -} From 829ca3b4248c8c7edab6d4f26c9da5676b148814 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 13 Oct 2020 13:17:18 +1100 Subject: [PATCH 032/129] read tests --- cmd/read_test.go | 10 +++++----- pkg/yqlib/treeops/leaf_traverser.go | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/cmd/read_test.go b/cmd/read_test.go index bbbb6e6f..cb9fdfda 100644 --- a/cmd/read_test.go +++ b/cmd/read_test.go @@ -320,7 +320,7 @@ dog: bark defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -l %s", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("read %s count(*)", filename)) if result.Error != nil { t.Error(result.Error) } @@ -336,7 +336,7 @@ func TestReadObjectLengthDeepCmd(t *testing.T) { defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -l %s holder", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("read %s count(holder.*)", filename)) if result.Error != nil { t.Error(result.Error) } @@ -355,7 +355,7 @@ holderB: defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -l -c %s holder*", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("read %s count(holder*)", filename)) if result.Error != nil { t.Error(result.Error) } @@ -374,7 +374,7 @@ holderB: defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -l -ppv %s holder*", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("read -ppv %s holder*.(count(*))", filename)) if result.Error != nil { t.Error(result.Error) } @@ -387,7 +387,7 @@ func TestReadScalarLengthCmd(t *testing.T) { defer test.RemoveTempYamlFile(filename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -l %s", filename)) + result := test.RunCmd(cmd, fmt.Sprintf("read %s 'count(.)'", filename)) if result.Error != nil { t.Error(result.Error) } diff --git a/pkg/yqlib/treeops/leaf_traverser.go b/pkg/yqlib/treeops/leaf_traverser.go index bb855b2a..1dee1740 100644 --- a/pkg/yqlib/treeops/leaf_traverser.go +++ b/pkg/yqlib/treeops/leaf_traverser.go @@ -1,6 +1,8 @@ package treeops import ( + "fmt" + "gopkg.in/yaml.v3" ) @@ -68,13 +70,23 @@ func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElemen } index := pathNode.Value.(int64) - if int64(len(candidate.Node.Content)) < index { + indexToUse := index + contentLength := int64(len(candidate.Node.Content)) + if contentLength <= index { // handle auto append here return make([]*CandidateNode, 0), nil } + if indexToUse < 0 { + indexToUse = contentLength + indexToUse + } + + if indexToUse < 0 { + return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength) + } + return []*CandidateNode{&CandidateNode{ - Node: candidate.Node.Content[index], + Node: candidate.Node.Content[indexToUse], Document: candidate.Document, Path: append(candidate.Path, index), }}, nil From afffb2c3bae9c2d6c8298df73fd257f4b784f401 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 13 Oct 2020 14:04:21 +1100 Subject: [PATCH 033/129] collect --- pkg/yqlib/treeops/data_tree_navigator_test.go | 84 +++++++++++++++++++ pkg/yqlib/treeops/lib.go | 1 + pkg/yqlib/treeops/operators.go | 31 +++++++ pkg/yqlib/treeops/path_tokeniser.go | 1 + 4 files changed, 117 insertions(+) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index dfc8ae1f..b19f9136 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -267,6 +267,34 @@ func TestDataTreeNavigatorCountWithFilter(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorCollectWithFilter(t *testing.T) { + + nodes := readDoc(t, `f: + a: frog + b: dally + c: log`) + + path, errPath := treeCreator.ParsePath("f(collect(. == *og))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [f] + Tag: , Kind: SequenceNode, Anchor: + - frog +- log +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorCountWithFilter2(t *testing.T) { nodes := readDoc(t, `f: @@ -294,6 +322,34 @@ func TestDataTreeNavigatorCountWithFilter2(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorCollectWithFilter2(t *testing.T) { + + nodes := readDoc(t, `f: + a: frog + b: dally + c: log`) + + path, errPath := treeCreator.ParsePath("collect(f(. == *og))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [] + Tag: , Kind: SequenceNode, Anchor: + - frog +- log +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorCountMultipleMatchesInside(t *testing.T) { nodes := readDoc(t, `f: @@ -321,6 +377,34 @@ func TestDataTreeNavigatorCountMultipleMatchesInside(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorCollectMultipleMatchesInside(t *testing.T) { + + nodes := readDoc(t, `f: + a: [1,2] + b: dally + c: [3,4,5]`) + + path, errPath := treeCreator.ParsePath("f(collect(a or c))") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [f] + Tag: , Kind: SequenceNode, Anchor: + - [1, 2] +- [3, 4, 5] +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorCountMultipleMatchesInsideSplat(t *testing.T) { nodes := readDoc(t, `f: diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index f52319ac..43be04e6 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -38,6 +38,7 @@ var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 35, Handler: var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 30, Handler: DeleteChildOperator} var Count = &OperationType{Type: "COUNT", NumArgs: 1, Precedence: 35, Handler: CountOperator} +var Collect = &OperationType{Type: "COLLECT", NumArgs: 1, Precedence: 35, Handler: CollectOperator} // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} // filters matches if they have the existing path diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index 16eabce9..5405bb4f 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -119,6 +119,37 @@ func CountOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNo return results, nil } +func CollectOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- collectOperation") + + log.Debugf("-- countOperation") + var results = orderedmap.NewOrderedMap() + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + elMap := orderedmap.NewOrderedMap() + elMap.Set(el.Key, el.Value) + childMatches, errChild := d.getMatchingNodes(elMap, pathNode.Rhs) + + if errChild != nil { + return nil, errChild + } + + node := &yaml.Node{Kind: yaml.SequenceNode} + + for childEl := childMatches.Front(); childEl != nil; childEl = childEl.Next() { + childCandidate := childEl.Value.(*CandidateNode) + node.Content = append(node.Content, childCandidate.Node) + } + + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.Set(candidate.GetKey(), lengthCand) + } + + return results, nil + +} + func findMatchingChildren(d *dataTreeNavigator, results *orderedmap.OrderedMap, candidate *CandidateNode, lhs *PathTreeNode, valuePattern string) error { var children *orderedmap.OrderedMap var err error diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 3dac8652..3236ceda 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -78,6 +78,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`([Oo][Rr])`), opToken(Or, false)) lexer.Add([]byte(`([Aa][Nn][Dd])`), opToken(And, false)) lexer.Add([]byte(`([Cc][Oo][Uu][Nn][Tt])`), opToken(Count, false)) + lexer.Add([]byte(`([Cc][Oo][Ll][Ll][Ee][Cc][Tt])`), opToken(Collect, false)) lexer.Add([]byte(`\.\s*==\s*`), opToken(Equals, true)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals, false)) From 449fb8952c80fc62988e89996216408e04fd99e7 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 13 Oct 2020 14:37:01 +1100 Subject: [PATCH 034/129] adding pipe --- pkg/yqlib/treeops/data_tree_navigator_test.go | 4 ++-- pkg/yqlib/treeops/lib.go | 8 ++++---- pkg/yqlib/treeops/path_postfix_test.go | 20 +++++++++++++++++++ pkg/yqlib/treeops/path_tokeniser.go | 3 ++- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index b19f9136..cdfde2f6 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -357,7 +357,7 @@ func TestDataTreeNavigatorCountMultipleMatchesInside(t *testing.T) { b: dally c: [3,4,5]`) - path, errPath := treeCreator.ParsePath("f(count(a or c))") + path, errPath := treeCreator.ParsePath("f | count(a or c)") if errPath != nil { t.Error(errPath) } @@ -384,7 +384,7 @@ func TestDataTreeNavigatorCollectMultipleMatchesInside(t *testing.T) { b: dally c: [3,4,5]`) - path, errPath := treeCreator.ParsePath("f(collect(a or c))") + path, errPath := treeCreator.ParsePath("f | collect(a or c)") if errPath != nil { t.Error(errPath) } diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 43be04e6..d2b53791 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -30,15 +30,15 @@ type OperationType struct { } var None = &OperationType{Type: "NONE", NumArgs: 0, Precedence: 0} -var Traverse = &OperationType{Type: "TRAVERSE", NumArgs: 2, Precedence: 40, Handler: TraverseOperator} +var Traverse = &OperationType{Type: "TRAVERSE", NumArgs: 2, Precedence: 35, Handler: TraverseOperator} var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 10, Handler: UnionOperator} var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator} var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 30, Handler: EqualsOperator} -var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 35, Handler: AssignOperator} +var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator} var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 30, Handler: DeleteChildOperator} -var Count = &OperationType{Type: "COUNT", NumArgs: 1, Precedence: 35, Handler: CountOperator} -var Collect = &OperationType{Type: "COLLECT", NumArgs: 1, Precedence: 35, Handler: CollectOperator} +var Count = &OperationType{Type: "COUNT", NumArgs: 1, Precedence: 40, Handler: CountOperator} +var Collect = &OperationType{Type: "COLLECT", NumArgs: 1, Precedence: 40, Handler: CollectOperator} // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} // filters matches if they have the existing path diff --git a/pkg/yqlib/treeops/path_postfix_test.go b/pkg/yqlib/treeops/path_postfix_test.go index 7559e8e8..89f112cf 100644 --- a/pkg/yqlib/treeops/path_postfix_test.go +++ b/pkg/yqlib/treeops/path_postfix_test.go @@ -25,6 +25,26 @@ func testExpression(expression string) (string, error) { return formatted, nil } +func TestPostFixTraverseBar(t *testing.T) { + var infix = "animals | collect(.)" + var expectedOutput = `PathKey - 'animals' +-------- +SELF +-------- +Operation - COLLECT +-------- +Operation - TRAVERSE +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + func TestPostFixArrayEquals(t *testing.T) { var infix = "animals(.== cat)" var expectedOutput = `PathKey - 'animals' diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 3236ceda..ccb52012 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -94,8 +94,9 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte("( |\t|\n|\r)+"), skip) lexer.Add([]byte(`"[^ "]+"`), pathToken(true)) - lexer.Add([]byte(`[^ \.\[\(\)=]+`), pathToken(false)) + lexer.Add([]byte(`[^ \|\.\[\(\)=]+`), pathToken(false)) + lexer.Add([]byte(`\|`), opToken(Traverse, false)) lexer.Add([]byte(`\.`), opToken(Traverse, false)) err := lexer.Compile() if err != nil { From 6829d8cb78cdc3daf35bfda7cbf7be7d7853d678 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 16 Oct 2020 12:29:26 +1100 Subject: [PATCH 035/129] JQ like syntax wip --- pkg/yqlib/treeops/candidate_node.go | 9 + pkg/yqlib/treeops/data_tree_navigator.go | 2 +- pkg/yqlib/treeops/data_tree_navigator_test.go | 37 ++- pkg/yqlib/treeops/equals_operator.go | 61 +++++ pkg/yqlib/treeops/leaf_traverser.go | 2 +- pkg/yqlib/treeops/lib.go | 33 ++- pkg/yqlib/treeops/operators.go | 144 ++++------- pkg/yqlib/treeops/path_postfix.go | 21 +- pkg/yqlib/treeops/path_postfix_test.go | 230 ++++++++++-------- pkg/yqlib/treeops/path_tokeniser.go | 118 +++++---- pkg/yqlib/treeops/path_tokeniser_test.go | 81 +++--- pkg/yqlib/treeops/path_tree.go | 8 +- test/utils.go | 8 + 13 files changed, 446 insertions(+), 308 deletions(-) create mode 100644 pkg/yqlib/treeops/equals_operator.go diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index 13576899..aa46a495 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -18,6 +18,15 @@ func (n *CandidateNode) GetKey() string { return fmt.Sprintf("%v - %v", n.Document, n.Path) } +// updates this candidate from the given candidate node +func (n *CandidateNode) UpdateFrom(other *CandidateNode) { + n.Node.Content = other.Node.Content + n.Node.Value = other.Node.Value + n.Node.Kind = other.Node.Kind + n.Node.Tag = other.Node.Tag + n.Node.Style = other.Node.Style +} + func (n *CandidateNode) PathStackToString() string { return mergePathStackToString(n.Path) } diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index a5cfaaa9..be30b8c5 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -70,7 +70,7 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa log.Debugf("Processing Path: %v", pathNode.PathElement.toString()) if pathNode.PathElement.PathElementType == SelfReference { return matchingNodes, nil - } else if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { + } else if pathNode.PathElement.PathElementType == PathKey { return d.traverse(matchingNodes, pathNode.PathElement) } else { handler := pathNode.PathElement.OperationType.Handler diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index cdfde2f6..1d8f52e2 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -240,14 +240,41 @@ func TestDataTreeNavigatorDeleteViaSelf(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorCountWithFilter(t *testing.T) { +func TestDataTreeNavigatorFilterWithSplat(t *testing.T) { nodes := readDoc(t, `f: a: frog b: dally c: log`) - path, errPath := treeCreator.ParsePath("f(count(. == *og))") + path, errPath := treeCreator.ParsePath(".f | .[] == \"frog\"") + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [f] + Tag: !!int, Kind: ScalarNode, Anchor: + 2 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorCountAndCollectWithFilterCmd(t *testing.T) { + + nodes := readDoc(t, `f: + a: frog + b: dally + c: log`) + + path, errPath := treeCreator.ParsePath(".f | .[] == *og ") if errPath != nil { t.Error(errPath) } @@ -934,7 +961,7 @@ func TestDataTreeNavigatorEqualsSimple(t *testing.T) { pat: {b: banana} `) - path, errPath := treeCreator.ParsePath("a.(b == apple)") + path, errPath := treeCreator.ParsePath(".a | (.[].b == \"apple\")") if errPath != nil { t.Error(errPath) } @@ -1132,7 +1159,7 @@ func TestDataTreeNavigatorArrayEqualsDeep(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorEqualsTrickey(t *testing.T) { +func xTestDataTreeNavigatorEqualsTrickey(t *testing.T) { nodes := readDoc(t, `a: cat: {b: apso, c: {d : yes}} @@ -1141,7 +1168,7 @@ func TestDataTreeNavigatorEqualsTrickey(t *testing.T) { fat: {b: apple} `) - path, errPath := treeCreator.ParsePath("a.(b == ap* and c.d == yes)") + path, errPath := treeCreator.ParsePath(".a(.b == ap* and .c.d == yes)") if errPath != nil { t.Error(errPath) } diff --git a/pkg/yqlib/treeops/equals_operator.go b/pkg/yqlib/treeops/equals_operator.go new file mode 100644 index 00000000..4a3aee42 --- /dev/null +++ b/pkg/yqlib/treeops/equals_operator.go @@ -0,0 +1,61 @@ +package treeops + +import ( + "github.com/elliotchance/orderedmap" + "gopkg.in/yaml.v3" +) + +func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- equalsOperation") + var results = orderedmap.NewOrderedMap() + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debug("equalsOperation checking %v", candidate) + + matches, errInChild := hasMatch(d, candidate, pathNode.Lhs, pathNode.Rhs) + if errInChild != nil { + return nil, errInChild + } + + matchString := "true" + if !matches { + matchString = "false" + } + + node := &yaml.Node{Kind: yaml.ScalarNode, Value: matchString, Tag: "!!bool"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.Set(candidate.GetKey(), lengthCand) + + } + + return results, nil +} + +func hasMatch(d *dataTreeNavigator, candidate *CandidateNode, lhs *PathTreeNode, rhs *PathTreeNode) (bool, error) { + childMap := orderedmap.NewOrderedMap() + childMap.Set(candidate.GetKey(), candidate) + childMatches, errChild := d.getMatchingNodes(childMap, lhs) + log.Debug("got the LHS") + if errChild != nil { + return false, errChild + } + + // TODO = handle other RHS types + return containsMatchingValue(childMatches, rhs.PathElement.StringValue), nil +} + +func containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool { + log.Debugf("-- findMatchingValues") + + for el := matchMap.Front(); el != nil; el = el.Next() { + node := el.Value.(*CandidateNode) + log.Debugf("-- comparing %v to %v", node.Node.Value, valuePattern) + if Match(node.Node.Value, valuePattern) { + return true + } + } + log.Debugf("-- done findMatchingValues") + + return false +} diff --git a/pkg/yqlib/treeops/leaf_traverser.go b/pkg/yqlib/treeops/leaf_traverser.go index 1dee1740..679a08b2 100644 --- a/pkg/yqlib/treeops/leaf_traverser.go +++ b/pkg/yqlib/treeops/leaf_traverser.go @@ -53,7 +53,7 @@ func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { log.Debug("pathNode Value %v", pathNode.Value) - if pathNode.Value == "[*]" || pathNode.Value == "*" { + if pathNode.Value == "[]" { var contents = candidate.Node.Content var newMatches = make([]*CandidateNode, len(contents)) diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index d2b53791..46d9ba2d 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -15,11 +15,13 @@ type PathElementType uint32 const ( PathKey PathElementType = 1 << iota - ArrayIndex Operation SelfReference OpenBracket CloseBracket + OpenCollect + CloseCollect + Value // e.g. string, int ) type OperationType struct { @@ -30,16 +32,23 @@ type OperationType struct { } var None = &OperationType{Type: "NONE", NumArgs: 0, Precedence: 0} -var Traverse = &OperationType{Type: "TRAVERSE", NumArgs: 2, Precedence: 35, Handler: TraverseOperator} + var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 10, Handler: UnionOperator} var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator} -var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 30, Handler: EqualsOperator} -var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator} -var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 30, Handler: DeleteChildOperator} -var Count = &OperationType{Type: "COUNT", NumArgs: 1, Precedence: 40, Handler: CountOperator} +var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator} +var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} +var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator} + +var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} + +// not sure yet + +var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Handler: DeleteChildOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 1, Precedence: 40, Handler: CollectOperator} +// var Splat = &OperationType{Type: "SPLAT", NumArgs: 0, Precedence: 40, Handler: SplatOperator} + // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} // filters matches if they have the existing path @@ -55,13 +64,15 @@ func (p *PathElement) toString() string { var result string = `` switch p.PathElementType { case PathKey: - result = result + fmt.Sprintf("PathKey - '%v'\n", p.Value) - case ArrayIndex: - result = result + fmt.Sprintf("ArrayIndex - '%v'\n", p.Value) + result = result + fmt.Sprintf("PathKey - %v", p.Value) case SelfReference: - result = result + fmt.Sprintf("SELF\n") + result = result + fmt.Sprintf("SELF") case Operation: - result = result + fmt.Sprintf("Operation - %v\n", p.OperationType.Type) + result = result + fmt.Sprintf("Operation - %v", p.OperationType.Type) + case Value: + result = result + fmt.Sprintf("Value - %v (%T)", p.Value, p.Value) + default: + result = result + "I HAVENT GOT A STRATEGY" } return result } diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index 5405bb4f..9e5afcaf 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -9,7 +9,7 @@ import ( type OperatorHandler func(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) -func TraverseOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func PipeOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err @@ -17,15 +17,32 @@ func TraverseOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap return d.getMatchingNodes(lhs, pathNode.Rhs) } +func nodeToMap(candidate *CandidateNode) *orderedmap.OrderedMap { + elMap := orderedmap.NewOrderedMap() + elMap.Set(candidate.GetKey(), candidate) + return elMap +} + func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } for el := lhs.Front(); el != nil; el = el.Next() { - node := el.Value.(*CandidateNode) - log.Debugf("Assiging %v to %v", node.GetKey(), pathNode.Rhs.PathElement.StringValue) - node.Node.Value = pathNode.Rhs.PathElement.StringValue + candidate := el.Value.(*CandidateNode) + + rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + + if err != nil { + return nil, err + } + + // grab the first value + first := rhs.Front() + + if first != nil { + candidate.UpdateFrom(first.Value.(*CandidateNode)) + } } return lhs, nil } @@ -66,54 +83,36 @@ func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordere } func splatNode(d *dataTreeNavigator, candidate *CandidateNode) (*orderedmap.OrderedMap, error) { - elMap := orderedmap.NewOrderedMap() - elMap.Set(candidate.GetKey(), candidate) //need to splat matching nodes, then search through them splatter := &PathTreeNode{PathElement: &PathElement{ PathElementType: PathKey, Value: "*", StringValue: "*", }} - return d.getMatchingNodes(elMap, splatter) + return d.getMatchingNodes(nodeToMap(candidate), splatter) } -func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - log.Debugf("-- equalsOperation") +func LengthOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- lengthOperation") var results = orderedmap.NewOrderedMap() for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - valuePattern := pathNode.Rhs.PathElement.StringValue - log.Debug("checking %v", candidate) - - errInChild := findMatchingChildren(d, results, candidate, pathNode.Lhs, valuePattern) - if errInChild != nil { - return nil, errInChild - } - } - - return results, nil -} - -func CountOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - log.Debugf("-- countOperation") - var results = orderedmap.NewOrderedMap() - - for el := matchMap.Front(); el != nil; el = el.Next() { - candidate := el.Value.(*CandidateNode) - elMap := orderedmap.NewOrderedMap() - elMap.Set(el.Key, el.Value) - childMatches, errChild := d.getMatchingNodes(elMap, pathNode.Rhs) - - if errChild != nil { - return nil, errChild + var length int + switch candidate.Node.Kind { + case yaml.ScalarNode: + length = len(candidate.Node.Value) + case yaml.MappingNode: + length = len(candidate.Node.Content) / 2 + case yaml.SequenceNode: + length = len(candidate.Node.Content) + default: + length = 0 } - length := childMatches.Len() node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"} lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} results.Set(candidate.GetKey(), lengthCand) - } return results, nil @@ -122,76 +121,25 @@ func CountOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNo func CollectOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { log.Debugf("-- collectOperation") - log.Debugf("-- countOperation") var results = orderedmap.NewOrderedMap() + node := &yaml.Node{Kind: yaml.SequenceNode} + + var document uint = 0 + var path []interface{} + for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - elMap := orderedmap.NewOrderedMap() - elMap.Set(el.Key, el.Value) - childMatches, errChild := d.getMatchingNodes(elMap, pathNode.Rhs) - - if errChild != nil { - return nil, errChild + if path == nil && candidate.Path != nil { + path = candidate.Path + document = candidate.Document } - - node := &yaml.Node{Kind: yaml.SequenceNode} - - for childEl := childMatches.Front(); childEl != nil; childEl = childEl.Next() { - childCandidate := childEl.Value.(*CandidateNode) - node.Content = append(node.Content, childCandidate.Node) - } - - lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} - results.Set(candidate.GetKey(), lengthCand) + node.Content = append(node.Content, candidate.Node) } + collectC := &CandidateNode{Node: node, Document: document, Path: path} + results.Set(collectC.GetKey(), collectC) + return results, nil } - -func findMatchingChildren(d *dataTreeNavigator, results *orderedmap.OrderedMap, candidate *CandidateNode, lhs *PathTreeNode, valuePattern string) error { - var children *orderedmap.OrderedMap - var err error - // don't splat scalars. - if candidate.Node.Kind != yaml.ScalarNode { - children, err = splatNode(d, candidate) - log.Debugf("-- splatted matches, ") - if err != nil { - return err - } - } else { - children = orderedmap.NewOrderedMap() - children.Set(candidate.GetKey(), candidate) - } - - for childEl := children.Front(); childEl != nil; childEl = childEl.Next() { - childMap := orderedmap.NewOrderedMap() - childMap.Set(childEl.Key, childEl.Value) - childMatches, errChild := d.getMatchingNodes(childMap, lhs) - log.Debug("got the LHS") - if errChild != nil { - return errChild - } - - if containsMatchingValue(childMatches, valuePattern) { - results.Set(childEl.Key, childEl.Value) - } - } - return nil -} - -func containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool { - log.Debugf("-- findMatchingValues") - - for el := matchMap.Front(); el != nil; el = el.Next() { - node := el.Value.(*CandidateNode) - log.Debugf("-- comparing %v to %v", node.Node.Value, valuePattern) - if Match(node.Node.Value, valuePattern) { - return true - } - } - log.Debugf("-- done findMatchingValues") - - return false -} diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index 40419959..f1c46cd4 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -25,17 +25,28 @@ func popOpToResult(opStack []*Token, result []*PathElement) ([]*Token, []*PathEl func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, error) { var result []*PathElement // surround the whole thing with quotes - var opStack = []*Token{&Token{PathElementType: OpenBracket, OperationType: None}} - var tokens = append(infixTokens, &Token{PathElementType: CloseBracket, OperationType: None}) + var opStack = []*Token{&Token{PathElementType: OpenBracket, OperationType: None, Value: "("}} + var tokens = append(infixTokens, &Token{PathElementType: CloseBracket, OperationType: None, Value: ")"}) for _, token := range tokens { + log.Debugf("postfix processing token %v", token.Value) switch token.PathElementType { - case PathKey, ArrayIndex, SelfReference: + case PathKey, SelfReference, Value: var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue} result = append(result, &pathElement) - case OpenBracket: + case OpenBracket, OpenCollect: opStack = append(opStack, token) - + case CloseCollect: + for len(opStack) > 0 && opStack[len(opStack)-1].PathElementType != OpenCollect { + opStack, result = popOpToResult(opStack, result) + } + if len(opStack) == 0 { + return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket") + } + // now we should have ( as the last element on the opStack, get rid of it + opStack = opStack[0 : len(opStack)-1] + //and append a collect to the opStack + opStack = append(opStack, &Token{PathElementType: Operation, OperationType: Collect}) case CloseBracket: for len(opStack) > 0 && opStack[len(opStack)-1].PathElementType != OpenBracket { opStack, result = popOpToResult(opStack, result) diff --git a/pkg/yqlib/treeops/path_postfix_test.go b/pkg/yqlib/treeops/path_postfix_test.go index 89f112cf..0673424d 100644 --- a/pkg/yqlib/treeops/path_postfix_test.go +++ b/pkg/yqlib/treeops/path_postfix_test.go @@ -20,20 +20,20 @@ func testExpression(expression string) (string, error) { } formatted := "" for _, path := range results { - formatted = formatted + path.toString() + "--------\n" + formatted = formatted + path.toString() + "\n--------\n" } return formatted, nil } func TestPostFixTraverseBar(t *testing.T) { - var infix = "animals | collect(.)" - var expectedOutput = `PathKey - 'animals' + var infix = ".animals | [.]" + var expectedOutput = `PathKey - animals -------- SELF -------- Operation - COLLECT -------- -Operation - TRAVERSE +Operation - PIPE -------- ` @@ -45,17 +45,87 @@ Operation - TRAVERSE test.AssertResultComplex(t, expectedOutput, actual) } -func TestPostFixArrayEquals(t *testing.T) { - var infix = "animals(.== cat)" - var expectedOutput = `PathKey - 'animals' +func TestPostFixPipeEquals(t *testing.T) { + var infix = `.animals | (. == "cat") ` + var expectedOutput = `PathKey - animals -------- SELF -------- -PathKey - 'cat' +Value - cat (string) -------- Operation - EQUALS -------- -Operation - TRAVERSE +Operation - PIPE +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + +func TestPostFixCollect(t *testing.T) { + var infix = "[.a]" + var expectedOutput = `PathKey - a +-------- +Operation - COLLECT +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + +func TestPostFixSplatSearch(t *testing.T) { + var infix = `.a | (.[].b == "apple")` + var expectedOutput = `PathKey - a +-------- +PathKey - [] +-------- +PathKey - b +-------- +Operation - PIPE +-------- +Value - apple (string) +-------- +Operation - EQUALS +-------- +Operation - PIPE +-------- +` + + actual, err := testExpression(infix) + if err != nil { + t.Error(err) + } + + test.AssertResultComplex(t, expectedOutput, actual) +} + +func TestPostFixCollectWithExpression(t *testing.T) { + var infix = `[ (.a == "fred") | (.d, .f)]` + var expectedOutput = `PathKey - a +-------- +Value - fred (string) +-------- +Operation - EQUALS +-------- +PathKey - d +-------- +PathKey - f +-------- +Operation - OR +-------- +Operation - PIPE +-------- +Operation - COLLECT -------- ` @@ -68,10 +138,12 @@ Operation - TRAVERSE } func TestPostFixLength(t *testing.T) { - var infix = "len(a)" - var expectedOutput = `PathKey - 'a' + var infix = ".a | length" + var expectedOutput = `PathKey - a -------- -Operation - Length +Operation - LENGTH +-------- +Operation - PIPE -------- ` @@ -84,8 +156,8 @@ Operation - Length } func TestPostFixSimpleExample(t *testing.T) { - var infix = "a" - var expectedOutput = `PathKey - 'a' + var infix = ".a" + var expectedOutput = `PathKey - a -------- ` @@ -98,16 +170,16 @@ func TestPostFixSimpleExample(t *testing.T) { } func TestPostFixSimplePathExample(t *testing.T) { - var infix = "apples.bananas*.cat" - var expectedOutput = `PathKey - 'apples' + var infix = ".apples.bananas*.cat" + var expectedOutput = `PathKey - apples -------- -PathKey - 'bananas*' +PathKey - bananas* -------- -Operation - TRAVERSE +Operation - PIPE -------- -PathKey - 'cat' +PathKey - cat -------- -Operation - TRAVERSE +Operation - PIPE -------- ` @@ -120,14 +192,14 @@ Operation - TRAVERSE } func TestPostFixSimpleAssign(t *testing.T) { - var infix = "a.b := frog" - var expectedOutput = `PathKey - 'a' + var infix = ".a.b |= \"frog\"" + var expectedOutput = `PathKey - a -------- -PathKey - 'b' +PathKey - b -------- -Operation - TRAVERSE +Operation - PIPE -------- -PathKey - 'frog' +Value - frog (string) -------- Operation - ASSIGN -------- @@ -142,38 +214,16 @@ Operation - ASSIGN } func TestPostFixSimplePathNumbersExample(t *testing.T) { - var infix = "apples[0].cat" - var expectedOutput = `PathKey - 'apples' + var infix = ".apples[0].cat" + var expectedOutput = `PathKey - apples -------- -PathKey - '0' +PathKey - 0 -------- -Operation - TRAVERSE +Operation - PIPE -------- -PathKey - 'cat' +PathKey - cat -------- -Operation - TRAVERSE --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixSimplePathAppendArrayExample(t *testing.T) { - var infix = "apples[+].cat" - var expectedOutput = `PathKey - 'apples' --------- -PathKey - '[+]' --------- -Operation - TRAVERSE --------- -PathKey - 'cat' --------- -Operation - TRAVERSE +Operation - PIPE -------- ` @@ -186,38 +236,16 @@ Operation - TRAVERSE } func TestPostFixSimplePathSplatArrayExample(t *testing.T) { - var infix = "apples.[*]cat" - var expectedOutput = `PathKey - 'apples' + var infix = ".apples[].cat" + var expectedOutput = `PathKey - apples -------- -PathKey - '[*]' +PathKey - [] -------- -Operation - TRAVERSE +Operation - PIPE -------- -PathKey - 'cat' +PathKey - cat -------- -Operation - TRAVERSE --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixDeepMatchExample(t *testing.T) { - var infix = "apples.**.cat" - var expectedOutput = `PathKey - 'apples' --------- -PathKey - '**' --------- -Operation - TRAVERSE --------- -PathKey - 'cat' --------- -Operation - TRAVERSE +Operation - PIPE -------- ` @@ -230,10 +258,10 @@ Operation - TRAVERSE } func TestPostFixOrExample(t *testing.T) { - var infix = "a OR b" - var expectedOutput = `PathKey - 'a' + var infix = ".a, .b" + var expectedOutput = `PathKey - a -------- -PathKey - 'b' +PathKey - b -------- Operation - OR -------- @@ -248,10 +276,10 @@ Operation - OR } func TestPostFixEqualsNumberExample(t *testing.T) { - var infix = "(animal == 3)" - var expectedOutput = `PathKey - 'animal' + var infix = ".animal == 3" + var expectedOutput = `PathKey - animal -------- -PathKey - '3' +Value - 3 (int64) -------- Operation - EQUALS -------- @@ -266,16 +294,16 @@ Operation - EQUALS } func TestPostFixOrWithEqualsExample(t *testing.T) { - var infix = "a==thing OR b==thongs" - var expectedOutput = `PathKey - 'a' + var infix = ".a==\"thing\", .b==.thongs" + var expectedOutput = `PathKey - a -------- -PathKey - 'thing' +Value - thing (string) -------- Operation - EQUALS -------- -PathKey - 'b' +PathKey - b -------- -PathKey - 'thongs' +PathKey - thongs -------- Operation - EQUALS -------- @@ -292,24 +320,24 @@ Operation - OR } func TestPostFixOrWithEqualsPathExample(t *testing.T) { - var infix = "apples.monkeys==thing OR bogs.bobos==thongs" - var expectedOutput = `PathKey - 'apples' + var infix = ".apples.monkeys==\"thing\", .bogs.bobos==true" + var expectedOutput = `PathKey - apples -------- -PathKey - 'monkeys' +PathKey - monkeys -------- -Operation - TRAVERSE +Operation - PIPE -------- -PathKey - 'thing' +Value - thing (string) -------- Operation - EQUALS -------- -PathKey - 'bogs' +PathKey - bogs -------- -PathKey - 'bobos' +PathKey - bobos -------- -Operation - TRAVERSE +Operation - PIPE -------- -PathKey - 'thongs' +Value - true (bool) -------- Operation - EQUALS -------- diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index ccb52012..e9d91b8c 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -18,31 +18,30 @@ type Token struct { StringValue string PrefixSelf bool - CheckForPreTraverse bool // this token can sometimes have the traverse '.' missing in frnot of it - // e.g. a[1] should really be a.[1] - CheckForPostTraverse bool // samething but for post, e.g. [1]cat should really be [1].cat + CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat } func pathToken(wrapped bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) + value = value[1:len(value)] if wrapped { value = unwrap(value) } - return &Token{PathElementType: PathKey, OperationType: None, Value: value, StringValue: value}, nil + return &Token{PathElementType: PathKey, OperationType: None, Value: value, StringValue: value, CheckForPostTraverse: true}, nil } } -func opToken(op *OperationType, againstSelf bool) lex.Action { +func opToken(op *OperationType) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) - return &Token{PathElementType: Operation, OperationType: op, Value: op.Type, StringValue: value, PrefixSelf: againstSelf}, nil + return &Token{PathElementType: Operation, OperationType: op, Value: op.Type, StringValue: value}, nil } } -func literalToken(pType PathElementType, literal string, checkForPre bool, checkForPost bool, againstSelf bool) lex.Action { +func literalToken(pType PathElementType, literal string, checkForPost bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{PathElementType: pType, OperationType: None, Value: literal, StringValue: literal, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost, PrefixSelf: againstSelf}, nil + return &Token{PathElementType: pType, OperationType: None, Value: literal, StringValue: literal, CheckForPostTraverse: checkForPost}, nil } } @@ -50,54 +49,96 @@ func unwrap(value string) string { return value[1 : len(value)-1] } -func arrayIndextoken(wrapped bool, checkForPre bool, checkForPost bool) lex.Action { +func arrayIndextoken(precedingDot bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { var numberString = string(m.Bytes) - if wrapped { - numberString = unwrap(numberString) + startIndex := 1 + if precedingDot { + startIndex = 2 } + numberString = numberString[startIndex : len(numberString)-1] var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint if errParsingInt != nil { return nil, errParsingInt } - return &Token{PathElementType: ArrayIndex, OperationType: None, Value: number, StringValue: numberString, CheckForPreTraverse: checkForPre, CheckForPostTraverse: checkForPost}, nil + return &Token{PathElementType: PathKey, OperationType: None, Value: number, StringValue: numberString, CheckForPostTraverse: true}, nil + } +} + +func numberValue() lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + var numberString = string(m.Bytes) + var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint + if errParsingInt != nil { + return nil, errParsingInt + } + return &Token{PathElementType: Value, OperationType: None, Value: number, StringValue: numberString}, nil + } +} + +func booleanValue(val bool) lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + return &Token{PathElementType: Value, OperationType: None, Value: val, StringValue: string(m.Bytes)}, nil + } +} + +func stringValue(wrapped bool) lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + value := string(m.Bytes) + if wrapped { + value = unwrap(value) + } + return &Token{PathElementType: Value, OperationType: None, Value: value, StringValue: value}, nil + } +} + +func selfToken() lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + return &Token{PathElementType: SelfReference, OperationType: None, Value: "SELF", StringValue: "SELF"}, nil } } // Creates the lexer object and compiles the NFA. func initLexer() (*lex.Lexer, error) { lexer := lex.NewLexer() - lexer.Add([]byte(`\(`), literalToken(OpenBracket, "(", true, false, false)) - lexer.Add([]byte(`\)`), literalToken(CloseBracket, ")", false, true, false)) - lexer.Add([]byte(`\.\s*\)`), literalToken(CloseBracket, ")", false, true, true)) + lexer.Add([]byte(`\(`), literalToken(OpenBracket, "(", false)) + lexer.Add([]byte(`\)`), literalToken(CloseBracket, ")", true)) - lexer.Add([]byte(`\[\+\]`), literalToken(PathKey, "[+]", true, true, false)) - lexer.Add([]byte(`\[\*\]`), literalToken(PathKey, "[*]", true, true, false)) - lexer.Add([]byte(`\*\*`), literalToken(PathKey, "**", false, false, false)) + lexer.Add([]byte(`\.?\[\]`), literalToken(PathKey, "[]", true)) + lexer.Add([]byte(`\.\.`), literalToken(PathKey, "..", true)) - lexer.Add([]byte(`([Oo][Rr])`), opToken(Or, false)) - lexer.Add([]byte(`([Aa][Nn][Dd])`), opToken(And, false)) - lexer.Add([]byte(`([Cc][Oo][Uu][Nn][Tt])`), opToken(Count, false)) - lexer.Add([]byte(`([Cc][Oo][Ll][Ll][Ee][Cc][Tt])`), opToken(Collect, false)) + lexer.Add([]byte(`,`), opToken(Or)) + lexer.Add([]byte(`length`), opToken(Length)) + lexer.Add([]byte(`([Cc][Oo][Ll][Ll][Ee][Cc][Tt])`), opToken(Collect)) - lexer.Add([]byte(`\.\s*==\s*`), opToken(Equals, true)) - lexer.Add([]byte(`\s*==\s*`), opToken(Equals, false)) + lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) - lexer.Add([]byte(`\.\s*.-\s*`), opToken(DeleteChild, true)) - lexer.Add([]byte(`\s*.-\s*`), opToken(DeleteChild, false)) + lexer.Add([]byte(`\s*.-\s*`), opToken(DeleteChild)) - lexer.Add([]byte(`\.\s*:=\s*`), opToken(Assign, true)) - lexer.Add([]byte(`\s*:=\s*`), opToken(Assign, false)) + lexer.Add([]byte(`\s*\|=\s*`), opToken(Assign)) + + lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(false)) + lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true)) - lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(true, true, true)) - lexer.Add([]byte(`-?[0-9]+`), arrayIndextoken(false, false, false)) lexer.Add([]byte("( |\t|\n|\r)+"), skip) - lexer.Add([]byte(`"[^ "]+"`), pathToken(true)) - lexer.Add([]byte(`[^ \|\.\[\(\)=]+`), pathToken(false)) + lexer.Add([]byte(`\."[^ "]+"`), pathToken(true)) + lexer.Add([]byte(`\.[^ \[\],\|\.\[\(\)=]+`), pathToken(false)) + lexer.Add([]byte(`\.`), selfToken()) - lexer.Add([]byte(`\|`), opToken(Traverse, false)) - lexer.Add([]byte(`\.`), opToken(Traverse, false)) + lexer.Add([]byte(`\|`), opToken(Pipe)) + + lexer.Add([]byte(`-?[0-9]+`), numberValue()) + + lexer.Add([]byte(`[Tt][Rr][Uu][Ee]`), booleanValue(true)) + lexer.Add([]byte(`[Ff][Aa][Ll][Ss][Ee]`), booleanValue(false)) + + lexer.Add([]byte(`"[^ "]+"`), stringValue(true)) + + lexer.Add([]byte(`\[`), literalToken(OpenCollect, "[", false)) + lexer.Add([]byte(`\]`), literalToken(CloseCollect, "]", true)) + + // lexer.Add([]byte(`[^ \,\|\.\[\(\)=]+`), stringValue(false)) err := lexer.Compile() if err != nil { return nil, err @@ -142,19 +183,12 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { var postProcessedTokens = make([]*Token, 0) for index, token := range tokens { - if index > 0 && token.CheckForPreTraverse && - (tokens[index-1].PathElementType == PathKey || tokens[index-1].PathElementType == CloseBracket) { - postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: Operation, OperationType: Traverse, Value: "."}) - } - if token.PrefixSelf { - postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: SelfReference, Value: "SELF"}) - } postProcessedTokens = append(postProcessedTokens, token) if index != len(tokens)-1 && token.CheckForPostTraverse && tokens[index+1].PathElementType == PathKey { - postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: Operation, OperationType: Traverse, Value: "."}) + postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: Operation, OperationType: Pipe, Value: "PIPE"}) } } diff --git a/pkg/yqlib/treeops/path_tokeniser_test.go b/pkg/yqlib/treeops/path_tokeniser_test.go index 6c56a5ea..731460e3 100644 --- a/pkg/yqlib/treeops/path_tokeniser_test.go +++ b/pkg/yqlib/treeops/path_tokeniser_test.go @@ -10,50 +10,53 @@ var tokeniserTests = []struct { path string expectedTokens []interface{} }{ // TODO: Ensure ALL documented examples have tests! sheesh - {"len(.)", append(make([]interface{}, 0), "LENGTH", "(", "SELF", ")")}, - {"\"len\"(.)", append(make([]interface{}, 0), "len", ".", "(", "SELF", ")")}, - // {"a OR (b OR c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")}, + // {"len(.)", append(make([]interface{}, 0), "LENGTH", "(", "SELF", ")")}, + // {"\"len\"(.)", append(make([]interface{}, 0), "len", "TRAVERSE", "(", "SELF", ")")}, + // {".a OR (.b OR .c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")}, // {"a OR (b OR c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")}, // {"a .- (b OR c)", append(make([]interface{}, 0), "a", " .- ", "(", "b", "OR", "c", ")")}, // {"(animal==3)", append(make([]interface{}, 0), "(", "animal", "==", int64(3), ")")}, // {"(animal==f3)", append(make([]interface{}, 0), "(", "animal", "==", "f3", ")")}, - // {"apples.BANANAS", append(make([]interface{}, 0), "apples", ".", "BANANAS")}, - // {"appl*.BANA*", append(make([]interface{}, 0), "appl*", ".", "BANA*")}, - // {"a.b.**", append(make([]interface{}, 0), "a", ".", "b", ".", "**")}, - // {"a.\"=\".frog", append(make([]interface{}, 0), "a", ".", "=", ".", "frog")}, - // {"a.b.*", append(make([]interface{}, 0), "a", ".", "b", ".", "*")}, - // {"a.b.thin*", append(make([]interface{}, 0), "a", ".", "b", ".", "thin*")}, - // {"a.b[0]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, - // {"a.b.[0]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, - // {"a.b[*]", append(make([]interface{}, 0), "a", ".", "b", ".", "[*]")}, - // {"a.b.[*]", append(make([]interface{}, 0), "a", ".", "b", ".", "[*]")}, - // {"a.b[+]", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]")}, - // {"a.b.[+]", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]")}, - // {"a.b[-12]", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, - // {"a.b.0", append(make([]interface{}, 0), "a", ".", "b", ".", int64(0))}, - // // {"a.b.-12", append(make([]interface{}, 0), "a", ".", "b", ".", int64(-12))}, - // {"a", append(make([]interface{}, 0), "a")}, - // {"\"a.b\".c", append(make([]interface{}, 0), "a.b", ".", "c")}, - // {`b."foo.bar"`, append(make([]interface{}, 0), "b", ".", "foo.bar")}, - // {"animals(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "cat", ")")}, - // {"animals.(.==cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "cat", ")")}, - // {"animals(. == cat)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ". == ", "cat", ")")}, - // {"animals(.==c*)", append(make([]interface{}, 0), "animals", ".", "(", "SELF", ".==", "c*", ")")}, - // {"animals(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, - // {"animals.(a.b==c*)", append(make([]interface{}, 0), "animals", ".", "(", "a", ".", "b", "==", "c*", ")")}, - // {"(a.b==c*).animals", append(make([]interface{}, 0), "(", "a", ".", "b", "==", "c*", ")", ".", "animals")}, - // {"(a.b==c*)animals", append(make([]interface{}, 0), "(", "a", ".", "b", "==", "c*", ")", ".", "animals")}, - // {"[1].a.d", append(make([]interface{}, 0), int64(1), ".", "a", ".", "d")}, - // {"[1]a.d", append(make([]interface{}, 0), int64(1), ".", "a", ".", "d")}, - // {"a[0]c", append(make([]interface{}, 0), "a", ".", int64(0), ".", "c")}, - // {"a.[0].c", append(make([]interface{}, 0), "a", ".", int64(0), ".", "c")}, + // {"apples.BANANAS", append(make([]interface{}, 0), "apples", "TRAVERSE", "BANANAS")}, + // {"appl*.BANA*", append(make([]interface{}, 0), "appl*", "TRAVERSE", "BANA*")}, + // {"a.b.**", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "**")}, + // {"a.\"=\".frog", append(make([]interface{}, 0), "a", "TRAVERSE", "=", "TRAVERSE", "frog")}, + // {"a.b.*", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "*")}, + // {"a.b.thin*", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "thin*")}, + // {".a.b.[0]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", int64(0))}, + // {".a.b.[]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "[]")}, + // {".a.b.[+]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "[+]")}, + // {".a.b.[-12]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", int64(-12))}, + // {".a.b.0", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "0")}, + // {".a", append(make([]interface{}, 0), "a")}, + // {".\"a.b\".c", append(make([]interface{}, 0), "a.b", "TRAVERSE", "c")}, + // {`.b."foo.bar"`, append(make([]interface{}, 0), "b", "TRAVERSE", "foo.bar")}, + // {`f | . == *og | length`, append(make([]interface{}, 0), "f", "TRAVERSE", "SELF", "EQUALS", "*og", "TRAVERSE", "LENGTH")}, + // {`.a, .b`, append(make([]interface{}, 0), "a", "OR", "b")}, + // {`[.a, .b]`, append(make([]interface{}, 0), "[", "a", "OR", "b", "]")}, + // {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")}, + // {`.a[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")}, + // {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")}, + {`.a | (.[].b == "apple")`, append(make([]interface{}, 0), "a", "PIPE", "(", "[]", "PIPE", "b", "EQUALS", "apple", ")")}, + + // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, + // {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")}, + // {".animals | (.==c*)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "c*", ")")}, + // {"animals(a.b==c*)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "a", "TRAVERSE", "b", "==", "c*", ")")}, + // {"animals.(a.b==c*)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "a", "TRAVERSE", "b", "==", "c*", ")")}, + // {"(a.b==c*).animals", append(make([]interface{}, 0), "(", "a", "TRAVERSE", "b", "==", "c*", ")", "TRAVERSE", "animals")}, + // {"(a.b==c*)animals", append(make([]interface{}, 0), "(", "a", "TRAVERSE", "b", "==", "c*", ")", "TRAVERSE", "animals")}, + // {"[1].a.d", append(make([]interface{}, 0), int64(1), "TRAVERSE", "a", "TRAVERSE", "d")}, + // {"[1]a.d", append(make([]interface{}, 0), int64(1), "TRAVERSE", "a", "TRAVERSE", "d")}, + // {"a[0]c", append(make([]interface{}, 0), "a", "TRAVERSE", int64(0), "TRAVERSE", "c")}, + // {"a.[0].c", append(make([]interface{}, 0), "a", "TRAVERSE", int64(0), "TRAVERSE", "c")}, // {"[0]", append(make([]interface{}, 0), int64(0))}, // {"0", append(make([]interface{}, 0), int64(0))}, - // {"a.b[+]c", append(make([]interface{}, 0), "a", ".", "b", ".", "[+]", ".", "c")}, - // {"a.cool(s.d.f == cool)", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", " == ", "cool", ")")}, - // {"a.cool.(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "OR", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "caterpillar")}, - // {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "and", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "*")}, - // {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", ".", "cool", ".", "(", "s", ".", "d", ".", "f", "==", "cool", "and", "t", ".", "b", ".", "h", "==", "frog", ")", ".", "th*")}, + // {"a.b[+]c", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "[+]", "TRAVERSE", "c")}, + // {"a.cool(s.d.f == cool)", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", " == ", "cool", ")")}, + // {"a.cool.(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", "==", "cool", "OR", "t", "TRAVERSE", "b", "TRAVERSE", "h", "==", "frog", ")", "TRAVERSE", "caterpillar")}, + // {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", "==", "cool", "and", "t", "TRAVERSE", "b", "TRAVERSE", "h", "==", "frog", ")", "TRAVERSE", "*")}, + // {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", "==", "cool", "and", "t", "TRAVERSE", "b", "TRAVERSE", "h", "==", "frog", ")", "TRAVERSE", "th*")}, } var tokeniser = NewPathTokeniser() @@ -68,6 +71,6 @@ func TestTokeniser(t *testing.T) { for _, token := range tokens { tokenValues = append(tokenValues, token.Value) } - test.AssertResultComplex(t, tt.expectedTokens, tokenValues) + test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, tt.path) } } diff --git a/pkg/yqlib/treeops/path_tree.go b/pkg/yqlib/treeops/path_tree.go index 3ca09e32..ce7c6523 100644 --- a/pkg/yqlib/treeops/path_tree.go +++ b/pkg/yqlib/treeops/path_tree.go @@ -45,12 +45,10 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeN for _, pathElement := range postFixPath { var newNode = PathTreeNode{PathElement: pathElement} - if pathElement.PathElementType == Operation { + log.Debugf("pathTree %v ", pathElement.toString()) + if pathElement.PathElementType == Operation && pathElement.OperationType.NumArgs > 0 { numArgs := pathElement.OperationType.NumArgs - if numArgs == 0 { - remaining := stack[:len(stack)-1] - stack = remaining - } else if numArgs == 1 { + if numArgs == 1 { remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1] newNode.Rhs = rhs stack = remaining diff --git a/test/utils.go b/test/utils.go index de621519..3cd61385 100644 --- a/test/utils.go +++ b/test/utils.go @@ -54,6 +54,14 @@ func AssertResultComplex(t *testing.T, expectedValue interface{}, actualValue in } } +func AssertResultComplexWithContext(t *testing.T, expectedValue interface{}, actualValue interface{}, context interface{}) { + t.Helper() + if !reflect.DeepEqual(expectedValue, actualValue) { + t.Error(context) + t.Error("\nExpected <", expectedValue, ">\nbut got <", actualValue, ">", fmt.Sprintf("%T", actualValue)) + } +} + func AssertResultWithContext(t *testing.T, expectedValue interface{}, actualValue interface{}, context interface{}) { t.Helper() if expectedValue != actualValue { From fccd03036fff492cc4dfd44c388e1762a3e06864 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 16 Oct 2020 12:47:31 +1100 Subject: [PATCH 036/129] can assign values --- pkg/yqlib/treeops/data_tree_navigator.go | 2 + pkg/yqlib/treeops/data_tree_navigator_test.go | 78 ++++++++++++++++++- pkg/yqlib/treeops/value_node_builder.go | 20 +++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 pkg/yqlib/treeops/value_node_builder.go diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index be30b8c5..08f0edeb 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -72,6 +72,8 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa return matchingNodes, nil } else if pathNode.PathElement.PathElementType == PathKey { return d.traverse(matchingNodes, pathNode.PathElement) + } else if pathNode.PathElement.PathElementType == Value { + return nodeToMap(BuildCandidateNodeFrom(pathNode.PathElement)), nil } else { handler := pathNode.PathElement.OperationType.Handler if handler != nil { diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 1d8f52e2..e0d2c00d 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -625,12 +625,12 @@ func TestDataTreeNavigatorArraySimple(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorSimpleAssign(t *testing.T) { +func TestDataTreeNavigatorSimpleAssignCmd(t *testing.T) { nodes := readDoc(t, `a: b: apple`) - path, errPath := treeCreator.ParsePath("a.b := frog") + path, errPath := treeCreator.ParsePath(`.a.b |= "frog"`) if errPath != nil { t.Error(errPath) } @@ -650,6 +650,80 @@ func TestDataTreeNavigatorSimpleAssign(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorSimpleAssignNumberCmd(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple`) + + path, errPath := treeCreator.ParsePath(`.a.b |= 5`) + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a b] + Tag: !!int, Kind: ScalarNode, Anchor: + 5 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorSimpleAssignFloatCmd(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple`) + + path, errPath := treeCreator.ParsePath(`.a.b |= 3.142`) + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a b] + Tag: !!float, Kind: ScalarNode, Anchor: + 3.142 +` + + test.AssertResult(t, expected, resultsToString(results)) +} + +func TestDataTreeNavigatorSimpleAssignBooleanCmd(t *testing.T) { + + nodes := readDoc(t, `a: + b: apple`) + path, errPath := treeCreator.ParsePath(`.a.b |= true`) + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a b] + Tag: !!bool, Kind: ScalarNode, Anchor: + true +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorSimpleAssignSelf(t *testing.T) { nodes := readDoc(t, `a: diff --git a/pkg/yqlib/treeops/value_node_builder.go b/pkg/yqlib/treeops/value_node_builder.go new file mode 100644 index 00000000..2a7203f7 --- /dev/null +++ b/pkg/yqlib/treeops/value_node_builder.go @@ -0,0 +1,20 @@ +package treeops + +import "gopkg.in/yaml.v3" + +func BuildCandidateNodeFrom(p *PathElement) *CandidateNode { + var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode} + node.Value = p.StringValue + + switch p.Value.(type) { + case float32, float64: + node.Tag = "!!float" + case int, int64, int32: + node.Tag = "!!int" + case bool: + node.Tag = "!!bool" + case string: + node.Tag = "!!str" + } + return &CandidateNode{Node: &node} +} From 59296b7d121e74cefc77a5c944ab4aa94e6e01fd Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 16 Oct 2020 12:49:15 +1100 Subject: [PATCH 037/129] can assign children! --- pkg/yqlib/treeops/data_tree_navigator_test.go | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index e0d2c00d..eaa7eee8 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -724,6 +724,31 @@ func TestDataTreeNavigatorSimpleAssignBooleanCmd(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } +func TestDataTreeNavigatorSimpleAssignChildCmd(t *testing.T) { + + nodes := readDoc(t, `a: + b: {g: 3}`) + + path, errPath := treeCreator.ParsePath(`.a |= .b`) + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + + expected := ` +-- Node -- + Document 0, path: [a] + Tag: !!map, Kind: MappingNode, Anchor: + {g: 3} +` + + test.AssertResult(t, expected, resultsToString(results)) +} + func TestDataTreeNavigatorSimpleAssignSelf(t *testing.T) { nodes := readDoc(t, `a: From 60511f5f92a67040a42165a8cd7c090cde315168 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 17 Oct 2020 22:10:47 +1100 Subject: [PATCH 038/129] refactoring, fixing --- pkg/yqlib/treeops/candidate_node.go | 2 +- pkg/yqlib/treeops/data_tree_navigator.go | 8 + pkg/yqlib/treeops/data_tree_navigator_test.go | 248 +----------------- pkg/yqlib/treeops/leaf_traverser.go | 2 +- pkg/yqlib/treeops/lib.go | 24 +- pkg/yqlib/treeops/operator_booleans.go | 72 +++++ pkg/yqlib/treeops/operator_booleans_test.go | 35 +++ pkg/yqlib/treeops/operator_collect.go | 32 +++ pkg/yqlib/treeops/operator_collect_test.go | 45 ++++ ...{delete_operator.go => operator_delete.go} | 0 ...{equals_operator.go => operator_equals.go} | 12 +- pkg/yqlib/treeops/operator_equals_test.go | 43 +++ pkg/yqlib/treeops/operator_select.go | 37 +++ pkg/yqlib/treeops/operator_select_test.go | 45 ++++ pkg/yqlib/treeops/operator_union.go | 19 ++ pkg/yqlib/treeops/operator_union_test.go | 31 +++ pkg/yqlib/treeops/operators.go | 61 +---- pkg/yqlib/treeops/operators_test.go | 28 ++ ...h_tokeniser_test.go => path_parse_test.go} | 44 +++- pkg/yqlib/treeops/path_postfix.go | 13 +- pkg/yqlib/treeops/path_postfix_test.go | 3 +- pkg/yqlib/treeops/path_tokeniser.go | 7 +- pkg/yqlib/treeops/path_tree.go | 2 +- 23 files changed, 484 insertions(+), 329 deletions(-) create mode 100644 pkg/yqlib/treeops/operator_booleans.go create mode 100644 pkg/yqlib/treeops/operator_booleans_test.go create mode 100644 pkg/yqlib/treeops/operator_collect.go create mode 100644 pkg/yqlib/treeops/operator_collect_test.go rename pkg/yqlib/treeops/{delete_operator.go => operator_delete.go} (100%) rename pkg/yqlib/treeops/{equals_operator.go => operator_equals.go} (82%) create mode 100644 pkg/yqlib/treeops/operator_equals_test.go create mode 100644 pkg/yqlib/treeops/operator_select.go create mode 100644 pkg/yqlib/treeops/operator_select_test.go create mode 100644 pkg/yqlib/treeops/operator_union.go create mode 100644 pkg/yqlib/treeops/operator_union_test.go create mode 100644 pkg/yqlib/treeops/operators_test.go rename pkg/yqlib/treeops/{path_tokeniser_test.go => path_parse_test.go} (81%) diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index aa46a495..961e9e29 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -15,7 +15,7 @@ type CandidateNode struct { } func (n *CandidateNode) GetKey() string { - return fmt.Sprintf("%v - %v", n.Document, n.Path) + return fmt.Sprintf("%v - %v - %v", n.Document, n.Path, n.Node.Value) } // updates this candidate from the given candidate node diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 08f0edeb..4a798ead 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/elliotchance/orderedmap" + "gopkg.in/op/go-logging.v1" ) type dataTreeNavigator struct { @@ -68,6 +69,13 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa return matchingNodes, nil } log.Debugf("Processing Path: %v", pathNode.PathElement.toString()) + if log.IsEnabledFor(logging.DEBUG) { + for el := matchingNodes.Front(); el != nil; el = el.Next() { + log.Debug(NodeToString(el.Value.(*CandidateNode))) + } + } + log.Debug(">>") + if pathNode.PathElement.PathElementType == SelfReference { return matchingNodes, nil } else if pathNode.PathElement.PathElementType == PathKey { diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index eaa7eee8..91117aa7 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -12,6 +12,9 @@ var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) var treeCreator = NewPathTreeCreator() func readDoc(t *testing.T, content string) []*CandidateNode { + if content == "" { + return []*CandidateNode{} + } decoder := yaml.NewDecoder(strings.NewReader(content)) var dataBucket yaml.Node err := decoder.Decode(&dataBucket) @@ -21,10 +24,10 @@ func readDoc(t *testing.T, content string) []*CandidateNode { return []*CandidateNode{&CandidateNode{Node: dataBucket.Content[0], Document: 0}} } -func resultsToString(results []*CandidateNode) string { - var pretty string = "" +func resultsToString(results []*CandidateNode) []string { + var pretty []string = make([]string, 0) for _, n := range results { - pretty = pretty + "\n" + NodeToString(n) + pretty = append(pretty, NodeToString(n)) } return pretty } @@ -1052,242 +1055,3 @@ func TestDataTreeNavigatorAnd(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } - -func TestDataTreeNavigatorEqualsSimple(t *testing.T) { - - nodes := readDoc(t, `a: - cat: {b: apple, c: yes} - pat: {b: banana} -`) - - path, errPath := treeCreator.ParsePath(".a | (.[].b == \"apple\")") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a cat] - Tag: !!map, Kind: MappingNode, Anchor: - {b: apple, c: yes} -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorEqualsSelf(t *testing.T) { - - nodes := readDoc(t, `a: frog -b: cat -c: frog`) - - path, errPath := treeCreator.ParsePath("(a or b).(. == frog)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!str, Kind: ScalarNode, Anchor: - frog -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorEqualsNested(t *testing.T) { - - nodes := readDoc(t, `a: {t: frog} -b: {t: cat} -c: {t: frog}`) - - path, errPath := treeCreator.ParsePath("(t == frog)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!map, Kind: MappingNode, Anchor: - {t: frog} - --- Node -- - Document 0, path: [c] - Tag: !!map, Kind: MappingNode, Anchor: - {t: frog} -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorArrayEqualsSelf(t *testing.T) { - - nodes := readDoc(t, `- cat -- dog -- frog`) - - path, errPath := treeCreator.ParsePath("(. == *og)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [1] - Tag: !!str, Kind: ScalarNode, Anchor: - dog - --- Node -- - Document 0, path: [2] - Tag: !!str, Kind: ScalarNode, Anchor: - frog -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorArrayEqualsSelfSplatFirst(t *testing.T) { - - nodes := readDoc(t, `- cat -- dog -- frog`) - - path, errPath := treeCreator.ParsePath("*(. == *og)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [1] - Tag: !!str, Kind: ScalarNode, Anchor: - dog - --- Node -- - Document 0, path: [2] - Tag: !!str, Kind: ScalarNode, Anchor: - frog -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorArrayEquals(t *testing.T) { - - nodes := readDoc(t, `- { b: apple, animal: rabbit } -- { b: banana, animal: cat } -- { b: corn, animal: dog }`) - - path, errPath := treeCreator.ParsePath("(b == apple or animal == dog)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [0] - Tag: !!map, Kind: MappingNode, Anchor: - {b: apple, animal: rabbit} - --- Node -- - Document 0, path: [2] - Tag: !!map, Kind: MappingNode, Anchor: - {b: corn, animal: dog} -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorArrayEqualsDeep(t *testing.T) { - - nodes := readDoc(t, `apples: - - { b: apple, animal: {legs: 2} } - - { b: banana, animal: {legs: 4} } - - { b: corn, animal: {legs: 6} } -`) - - path, errPath := treeCreator.ParsePath("apples(animal.legs == 4)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [apples 1] - Tag: !!map, Kind: MappingNode, Anchor: - {b: banana, animal: {legs: 4}} -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func xTestDataTreeNavigatorEqualsTrickey(t *testing.T) { - - nodes := readDoc(t, `a: - cat: {b: apso, c: {d : yes}} - pat: {b: apple, c: {d : no}} - sat: {b: apsy, c: {d : yes}} - fat: {b: apple} -`) - - path, errPath := treeCreator.ParsePath(".a(.b == ap* and .c.d == yes)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a cat] - Tag: !!map, Kind: MappingNode, Anchor: - {b: apso, c: {d: yes}} - --- Node -- - Document 0, path: [a sat] - Tag: !!map, Kind: MappingNode, Anchor: - {b: apsy, c: {d: yes}} -` - - test.AssertResult(t, expected, resultsToString(results)) -} diff --git a/pkg/yqlib/treeops/leaf_traverser.go b/pkg/yqlib/treeops/leaf_traverser.go index 679a08b2..3c19dc76 100644 --- a/pkg/yqlib/treeops/leaf_traverser.go +++ b/pkg/yqlib/treeops/leaf_traverser.go @@ -19,7 +19,7 @@ func NewLeafTraverser(navigationPrefs NavigationPrefs) LeafTraverser { } func (t *traverser) keyMatches(key *yaml.Node, pathNode *PathElement) bool { - return Match(key.Value, pathNode.StringValue) + return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue) } func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 46d9ba2d..01d62a86 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -33,19 +33,24 @@ type OperationType struct { var None = &OperationType{Type: "NONE", NumArgs: 0, Precedence: 0} -var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 10, Handler: UnionOperator} -var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator} +var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator} +var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator} + +var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} +var Intersection = &OperationType{Type: "INTERSECTION", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator} var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator} var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator} var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} +var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} // not sure yet +var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator} + var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Handler: DeleteChildOperator} -var Collect = &OperationType{Type: "COLLECT", NumArgs: 1, Precedence: 40, Handler: CollectOperator} // var Splat = &OperationType{Type: "SPLAT", NumArgs: 0, Precedence: 40, Handler: SplatOperator} @@ -64,13 +69,13 @@ func (p *PathElement) toString() string { var result string = `` switch p.PathElementType { case PathKey: - result = result + fmt.Sprintf("PathKey - %v", p.Value) + result = result + fmt.Sprintf("%v", p.Value) case SelfReference: result = result + fmt.Sprintf("SELF") case Operation: - result = result + fmt.Sprintf("Operation - %v", p.OperationType.Type) + result = result + fmt.Sprintf("%v", p.OperationType.Type) case Value: - result = result + fmt.Sprintf("Value - %v (%T)", p.Value, p.Value) + result = result + fmt.Sprintf("%v (%T)", p.Value, p.Value) default: result = result + "I HAVENT GOT A STRATEGY" } @@ -124,7 +129,7 @@ func NodeToString(node *CandidateNode) string { } value := node.Node if value == nil { - return "-- node is nil --" + return "-- nil --" } buf := new(bytes.Buffer) encoder := yaml.NewEncoder(buf) @@ -133,10 +138,7 @@ func NodeToString(node *CandidateNode) string { log.Error("Error debugging node, %v", errorEncoding.Error()) } encoder.Close() - return fmt.Sprintf(`-- Node -- - Document %v, path: %v - Tag: %v, Kind: %v, Anchor: %v - %v`, node.Document, node.Path, value.Tag, KindString(value.Kind), value.Anchor, buf.String()) + return fmt.Sprintf(`D%v, P%v, (%v)::%v`, node.Document, node.Path, value.Tag, buf.String()) } func KindString(kind yaml.Kind) string { diff --git a/pkg/yqlib/treeops/operator_booleans.go b/pkg/yqlib/treeops/operator_booleans.go new file mode 100644 index 00000000..45acd163 --- /dev/null +++ b/pkg/yqlib/treeops/operator_booleans.go @@ -0,0 +1,72 @@ +package treeops + +import ( + "github.com/elliotchance/orderedmap" + "gopkg.in/yaml.v3" +) + +func isTruthy(c *CandidateNode) (bool, error) { + node := c.Node + value := true + if node.Kind == yaml.ScalarNode && node.Tag == "!!bool" { + errDecoding := node.Decode(&value) + if errDecoding != nil { + return false, errDecoding + } + + } + return value, nil +} + +type boolOp func(bool, bool) bool + +func booleanOp(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode, op boolOp) (*orderedmap.OrderedMap, error) { + var results = orderedmap.NewOrderedMap() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + lhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Lhs) + if err != nil { + return nil, err + } + rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + if err != nil { + return nil, err + } + + for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() { + lhsCandidate := lhsChild.Value.(*CandidateNode) + lhsTrue, errDecoding := isTruthy(lhsCandidate) + if errDecoding != nil { + return nil, errDecoding + } + + for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() { + rhsCandidate := rhsChild.Value.(*CandidateNode) + rhsTrue, errDecoding := isTruthy(rhsCandidate) + if errDecoding != nil { + return nil, errDecoding + } + boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue)) + + results.Set(boolResult.GetKey(), boolResult) + } + } + + } + return results, nil +} + +func OrOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- orOp") + return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool { + return b1 || b2 + }) +} + +func AndOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- AndOp") + return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool { + return b1 && b2 + }) +} diff --git a/pkg/yqlib/treeops/operator_booleans_test.go b/pkg/yqlib/treeops/operator_booleans_test.go new file mode 100644 index 00000000..59f98953 --- /dev/null +++ b/pkg/yqlib/treeops/operator_booleans_test.go @@ -0,0 +1,35 @@ +package treeops + +import ( + "testing" +) + +var booleanOperatorScenarios = []expressionScenario{ + { + document: `{}`, + expression: `true or false`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, { + document: `{}`, + expression: `false or false`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, { + document: `{a: true, b: false}`, + expression: `.[] or (false, true)`, + expected: []string{ + "D0, P[a], (!!bool)::true\n", + "D0, P[b], (!!bool)::false\n", + "D0, P[b], (!!bool)::true\n", + }, + }, +} + +func TestBooleanOperatorScenarios(t *testing.T) { + for _, tt := range booleanOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operator_collect.go b/pkg/yqlib/treeops/operator_collect.go new file mode 100644 index 00000000..a1197faf --- /dev/null +++ b/pkg/yqlib/treeops/operator_collect.go @@ -0,0 +1,32 @@ +package treeops + +import ( + "github.com/elliotchance/orderedmap" + "gopkg.in/yaml.v3" +) + +func CollectOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- collectOperation") + + var results = orderedmap.NewOrderedMap() + + node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} + + var document uint = 0 + var path []interface{} + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debugf("Collecting %v", NodeToString(candidate)) + if path == nil && candidate.Path != nil && len(candidate.Path) > 1 { + path = candidate.Path[:len(candidate.Path)-1] + document = candidate.Document + } + node.Content = append(node.Content, candidate.Node) + } + + collectC := &CandidateNode{Node: node, Document: document, Path: path} + results.Set(collectC.GetKey(), collectC) + + return results, nil +} diff --git a/pkg/yqlib/treeops/operator_collect_test.go b/pkg/yqlib/treeops/operator_collect_test.go new file mode 100644 index 00000000..3fe7535c --- /dev/null +++ b/pkg/yqlib/treeops/operator_collect_test.go @@ -0,0 +1,45 @@ +package treeops + +import ( + "testing" +) + +var collectOperatorScenarios = []expressionScenario{ + { + document: `{}`, + expression: `["cat"]`, + expected: []string{ + "D0, P[], (!!seq)::- cat\n", + }, + }, { + document: `{}`, + expression: `["cat", "dog"]`, + expected: []string{ + "D0, P[], (!!seq)::- cat\n- dog\n", + }, + }, { + document: `{}`, + expression: `1 | collect`, + expected: []string{ + "D0, P[], (!!seq)::- 1\n", + }, + }, { + document: `[1,2,3]`, + expression: `[.[]]`, + expected: []string{ + "D0, P[], (!!seq)::- 1\n- 2\n- 3\n", + }, + }, { + document: `a: {b: [1,2,3]}`, + expression: `[.a.b[]]`, + expected: []string{ + "D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n", + }, + }, +} + +func TestCollectOperatorScenarios(t *testing.T) { + for _, tt := range collectOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/delete_operator.go b/pkg/yqlib/treeops/operator_delete.go similarity index 100% rename from pkg/yqlib/treeops/delete_operator.go rename to pkg/yqlib/treeops/operator_delete.go diff --git a/pkg/yqlib/treeops/equals_operator.go b/pkg/yqlib/treeops/operator_equals.go similarity index 82% rename from pkg/yqlib/treeops/equals_operator.go rename to pkg/yqlib/treeops/operator_equals.go index 4a3aee42..18dfa1c6 100644 --- a/pkg/yqlib/treeops/equals_operator.go +++ b/pkg/yqlib/treeops/operator_equals.go @@ -2,7 +2,6 @@ package treeops import ( "github.com/elliotchance/orderedmap" - "gopkg.in/yaml.v3" ) func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { @@ -18,15 +17,8 @@ func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathN return nil, errInChild } - matchString := "true" - if !matches { - matchString = "false" - } - - node := &yaml.Node{Kind: yaml.ScalarNode, Value: matchString, Tag: "!!bool"} - lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} - results.Set(candidate.GetKey(), lengthCand) - + equalsCandidate := createBooleanCandidate(candidate, matches) + results.Set(equalsCandidate.GetKey(), equalsCandidate) } return results, nil diff --git a/pkg/yqlib/treeops/operator_equals_test.go b/pkg/yqlib/treeops/operator_equals_test.go new file mode 100644 index 00000000..e6f210a2 --- /dev/null +++ b/pkg/yqlib/treeops/operator_equals_test.go @@ -0,0 +1,43 @@ +package treeops + +import ( + "testing" +) + +var equalsOperatorScenarios = []expressionScenario{ + { + document: `[cat,goat,dog]`, + expression: `(.[] == "*at")`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, { + document: `[cat,goat,dog]`, + expression: `.[] | (. == "*at")`, + expected: []string{ + "D0, P[0], (!!bool)::true\n", + "D0, P[1], (!!bool)::true\n", + "D0, P[2], (!!bool)::false\n", + }, + }, { + document: `[3, 4, 5]`, + expression: `.[] | (. == 4)`, + expected: []string{ + "D0, P[0], (!!bool)::false\n", + "D0, P[1], (!!bool)::true\n", + "D0, P[2], (!!bool)::false\n", + }, + }, { + document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, + expression: `.a | (.[].b == "apple")`, + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, +} + +func TestEqualOperatorScenarios(t *testing.T) { + for _, tt := range equalsOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operator_select.go b/pkg/yqlib/treeops/operator_select.go new file mode 100644 index 00000000..fb92a90e --- /dev/null +++ b/pkg/yqlib/treeops/operator_select.go @@ -0,0 +1,37 @@ +package treeops + +import ( + "github.com/elliotchance/orderedmap" +) + +func SelectOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + + log.Debugf("-- selectOperation") + var results = orderedmap.NewOrderedMap() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + + rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + + if err != nil { + return nil, err + } + + // grab the first value + first := rhs.Front() + + if first != nil { + result := first.Value.(*CandidateNode) + includeResult, errDecoding := isTruthy(result) + if errDecoding != nil { + return nil, errDecoding + } + + if includeResult { + results.Set(candidate.GetKey(), candidate) + } + } + } + return results, nil +} diff --git a/pkg/yqlib/treeops/operator_select_test.go b/pkg/yqlib/treeops/operator_select_test.go new file mode 100644 index 00000000..70536768 --- /dev/null +++ b/pkg/yqlib/treeops/operator_select_test.go @@ -0,0 +1,45 @@ +package treeops + +import ( + "testing" +) + +var selectOperatorScenarios = []expressionScenario{ + { + document: `[cat,goat,dog]`, + expression: `.[] | select(. == "*at")`, + expected: []string{ + "D0, P[0], (!!str)::cat\n", + "D0, P[1], (!!str)::goat\n", + }, + }, { + document: `[hot, fot, dog]`, + expression: `.[] | select(. == "*at")`, + expected: []string{}, + }, { + document: `a: [cat,goat,dog]`, + expression: `.a[] | select(. == "*at")`, + expected: []string{ + "D0, P[a 0], (!!str)::cat\n", + "D0, P[a 1], (!!str)::goat\n"}, + }, { + document: `a: { things: cat, bob: goat, horse: dog }`, + expression: `.a[] | select(. == "*at")`, + expected: []string{ + "D0, P[a things], (!!str)::cat\n", + "D0, P[a bob], (!!str)::goat\n"}, + }, { + document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`, + expression: `.a[] | select(.include)`, + expected: []string{ + "D0, P[a things], (!!map)::{include: true}\n", + "D0, P[a andMe], (!!map)::{include: fold}\n", + }, + }, +} + +func TestSelectOperatorScenarios(t *testing.T) { + for _, tt := range selectOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operator_union.go b/pkg/yqlib/treeops/operator_union.go new file mode 100644 index 00000000..df44e9f6 --- /dev/null +++ b/pkg/yqlib/treeops/operator_union.go @@ -0,0 +1,19 @@ +package treeops + +import "github.com/elliotchance/orderedmap" + +func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + for el := rhs.Front(); el != nil; el = el.Next() { + node := el.Value.(*CandidateNode) + lhs.Set(node.GetKey(), node) + } + return lhs, nil +} diff --git a/pkg/yqlib/treeops/operator_union_test.go b/pkg/yqlib/treeops/operator_union_test.go new file mode 100644 index 00000000..ff80f1d6 --- /dev/null +++ b/pkg/yqlib/treeops/operator_union_test.go @@ -0,0 +1,31 @@ +package treeops + +import ( + "testing" +) + +var unionOperatorScenarios = []expressionScenario{ + { + document: `{}`, + expression: `"cat", "dog"`, + expected: []string{ + "D0, P[], (!!str)::cat\n", + "D0, P[], (!!str)::dog\n", + }, + }, { + document: `{a: frog}`, + expression: `1, true, "cat", .a`, + expected: []string{ + "D0, P[], (!!int)::1\n", + "D0, P[], (!!bool)::true\n", + "D0, P[], (!!str)::cat\n", + "D0, P[a], (!!str)::frog\n", + }, + }, +} + +func TestUnionOperatorScenarios(t *testing.T) { + for _, tt := range unionOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index 9e5afcaf..5b5068d0 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -17,6 +17,15 @@ func PipeOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pa return d.getMatchingNodes(lhs, pathNode.Rhs) } +func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode { + valString := "true" + if !value { + valString = "false" + } + node := &yaml.Node{Kind: yaml.ScalarNode, Value: valString, Tag: "!!bool"} + return &CandidateNode{Node: node, Document: owner.Document, Path: owner.Path} +} + func nodeToMap(candidate *CandidateNode) *orderedmap.OrderedMap { elMap := orderedmap.NewOrderedMap() elMap.Set(candidate.GetKey(), candidate) @@ -47,22 +56,6 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, return lhs, nil } -func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs) - if err != nil { - return nil, err - } - for el := rhs.Front(); el != nil; el = el.Next() { - node := el.Value.(*CandidateNode) - lhs.Set(node.GetKey(), node) - } - return lhs, nil -} - func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { @@ -82,16 +75,6 @@ func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordere return matchingNodeMap, nil } -func splatNode(d *dataTreeNavigator, candidate *CandidateNode) (*orderedmap.OrderedMap, error) { - //need to splat matching nodes, then search through them - splatter := &PathTreeNode{PathElement: &PathElement{ - PathElementType: PathKey, - Value: "*", - StringValue: "*", - }} - return d.getMatchingNodes(nodeToMap(candidate), splatter) -} - func LengthOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { log.Debugf("-- lengthOperation") var results = orderedmap.NewOrderedMap() @@ -117,29 +100,3 @@ func LengthOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathN return results, nil } - -func CollectOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - log.Debugf("-- collectOperation") - - var results = orderedmap.NewOrderedMap() - - node := &yaml.Node{Kind: yaml.SequenceNode} - - var document uint = 0 - var path []interface{} - - for el := matchMap.Front(); el != nil; el = el.Next() { - candidate := el.Value.(*CandidateNode) - if path == nil && candidate.Path != nil { - path = candidate.Path - document = candidate.Document - } - node.Content = append(node.Content, candidate.Node) - } - - collectC := &CandidateNode{Node: node, Document: document, Path: path} - results.Set(collectC.GetKey(), collectC) - - return results, nil - -} diff --git a/pkg/yqlib/treeops/operators_test.go b/pkg/yqlib/treeops/operators_test.go new file mode 100644 index 00000000..fedd6dd9 --- /dev/null +++ b/pkg/yqlib/treeops/operators_test.go @@ -0,0 +1,28 @@ +package treeops + +import ( + "testing" + + "github.com/mikefarah/yq/v3/test" +) + +type expressionScenario struct { + document string + expression string + expected []string +} + +func testScenario(t *testing.T, s *expressionScenario) { + + nodes := readDoc(t, s.document) + path, errPath := treeCreator.ParsePath(s.expression) + if errPath != nil { + t.Error(errPath) + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + } + test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), s.expression) +} diff --git a/pkg/yqlib/treeops/path_tokeniser_test.go b/pkg/yqlib/treeops/path_parse_test.go similarity index 81% rename from pkg/yqlib/treeops/path_tokeniser_test.go rename to pkg/yqlib/treeops/path_parse_test.go index 731460e3..b7d56213 100644 --- a/pkg/yqlib/treeops/path_tokeniser_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -1,14 +1,16 @@ package treeops import ( + "fmt" "testing" "github.com/mikefarah/yq/v3/test" ) -var tokeniserTests = []struct { - path string - expectedTokens []interface{} +var pathTests = []struct { + path string + expectedTokens []interface{} + expectedPostFix []interface{} }{ // TODO: Ensure ALL documented examples have tests! sheesh // {"len(.)", append(make([]interface{}, 0), "LENGTH", "(", "SELF", ")")}, // {"\"len\"(.)", append(make([]interface{}, 0), "len", "TRAVERSE", "(", "SELF", ")")}, @@ -37,7 +39,21 @@ var tokeniserTests = []struct { // {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")}, // {`.a[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")}, // {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")}, - {`.a | (.[].b == "apple")`, append(make([]interface{}, 0), "a", "PIPE", "(", "[]", "PIPE", "b", "EQUALS", "apple", ")")}, + { + `.a | (.[].b == "apple")`, + append(make([]interface{}, 0), "a", "PIPE", "(", "[]", "PIPE", "b", "EQUALS", "apple", ")"), + append(make([]interface{}, 0), "a", "[]", "b", "PIPE", "apple (string)", "EQUALS", "PIPE"), + }, + { + `.[] | select(. == "*at")`, + append(make([]interface{}, 0), "[]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at", ")"), + append(make([]interface{}, 0), "[]", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"), + }, + { + `[true]`, + append(make([]interface{}, 0), "[", true, "]"), + append(make([]interface{}, 0), "true (bool)", "COLLECT"), + }, // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, // {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")}, @@ -61,8 +77,8 @@ var tokeniserTests = []struct { var tokeniser = NewPathTokeniser() -func TestTokeniser(t *testing.T) { - for _, tt := range tokeniserTests { +func TestPathParsing(t *testing.T) { + for _, tt := range pathTests { tokens, err := tokeniser.Tokenise(tt.path) if err != nil { t.Error(tt.path, err) @@ -71,6 +87,20 @@ func TestTokeniser(t *testing.T) { for _, token := range tokens { tokenValues = append(tokenValues, token.Value) } - test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, tt.path) + test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, fmt.Sprintf("tokenise: %v", tt.path)) + + results, errorP := postFixer.ConvertToPostfix(tokens) + + var readableResults []interface{} + for _, token := range results { + readableResults = append(readableResults, token.toString()) + } + + if errorP != nil { + t.Error(tt.path, err) + } + + test.AssertResultComplexWithContext(t, tt.expectedPostFix, readableResults, fmt.Sprintf("postfix: %v", tt.path)) + } } diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index f1c46cd4..3ed3e009 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -2,6 +2,8 @@ package treeops import ( "errors" + + "gopkg.in/op/go-logging.v1" ) type PathPostFixer interface { @@ -43,9 +45,10 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, if len(opStack) == 0 { return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket") } - // now we should have ( as the last element on the opStack, get rid of it + // now we should have [] as the last element on the opStack, get rid of it opStack = opStack[0 : len(opStack)-1] //and append a collect to the opStack + opStack = append(opStack, &Token{PathElementType: Operation, OperationType: Pipe}) opStack = append(opStack, &Token{PathElementType: Operation, OperationType: Collect}) case CloseBracket: for len(opStack) > 0 && opStack[len(opStack)-1].PathElementType != OpenBracket { @@ -67,5 +70,13 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, opStack = append(opStack, token) } } + + if log.IsEnabledFor(logging.DEBUG) { + log.Debugf("PostFix Result:") + for _, token := range result { + log.Debugf("> %v", token.toString()) + } + } + return result, nil } diff --git a/pkg/yqlib/treeops/path_postfix_test.go b/pkg/yqlib/treeops/path_postfix_test.go index 0673424d..ef5382f2 100644 --- a/pkg/yqlib/treeops/path_postfix_test.go +++ b/pkg/yqlib/treeops/path_postfix_test.go @@ -20,11 +20,12 @@ func testExpression(expression string) (string, error) { } formatted := "" for _, path := range results { - formatted = formatted + path.toString() + "\n--------\n" + formatted = formatted + path.toString() + ", " } return formatted, nil } + func TestPostFixTraverseBar(t *testing.T) { var infix = ".animals | [.]" var expectedOutput = `PathKey - animals diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index e9d91b8c..f00651fc 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -107,9 +107,12 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\.?\[\]`), literalToken(PathKey, "[]", true)) lexer.Add([]byte(`\.\.`), literalToken(PathKey, "..", true)) - lexer.Add([]byte(`,`), opToken(Or)) + lexer.Add([]byte(`,`), opToken(Union)) lexer.Add([]byte(`length`), opToken(Length)) - lexer.Add([]byte(`([Cc][Oo][Ll][Ll][Ee][Cc][Tt])`), opToken(Collect)) + lexer.Add([]byte(`select`), opToken(Select)) + lexer.Add([]byte(`or`), opToken(Or)) + lexer.Add([]byte(`and`), opToken(And)) + lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) diff --git a/pkg/yqlib/treeops/path_tree.go b/pkg/yqlib/treeops/path_tree.go index ce7c6523..30c67ee9 100644 --- a/pkg/yqlib/treeops/path_tree.go +++ b/pkg/yqlib/treeops/path_tree.go @@ -52,7 +52,7 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeN remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1] newNode.Rhs = rhs stack = remaining - } else { + } else if numArgs == 2 { remaining, lhs, rhs := stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1] newNode.Lhs = lhs newNode.Rhs = rhs From 5e544a5b7ec431cf75379dafa7f30fd53aa7a145 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 17 Oct 2020 22:39:01 +1100 Subject: [PATCH 039/129] value parse test --- pkg/yqlib/treeops/data_tree_navigator_test.go | 75 ------------------- pkg/yqlib/treeops/operator_assign.go | 27 +++++++ pkg/yqlib/treeops/operator_assign_test.go | 62 +++++++++++++++ pkg/yqlib/treeops/operator_value_test.go | 75 +++++++++++++++++++ pkg/yqlib/treeops/operators.go | 24 ------ pkg/yqlib/treeops/path_tokeniser.go | 17 ++++- pkg/yqlib/value_parser.go | 46 ------------ pkg/yqlib/value_parser_test.go | 65 ---------------- 8 files changed, 179 insertions(+), 212 deletions(-) create mode 100644 pkg/yqlib/treeops/operator_assign.go create mode 100644 pkg/yqlib/treeops/operator_assign_test.go create mode 100644 pkg/yqlib/treeops/operator_value_test.go delete mode 100644 pkg/yqlib/value_parser.go delete mode 100644 pkg/yqlib/value_parser_test.go diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 91117aa7..e5349a54 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -628,81 +628,6 @@ func TestDataTreeNavigatorArraySimple(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorSimpleAssignCmd(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple`) - - path, errPath := treeCreator.ParsePath(`.a.b |= "frog"`) - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a b] - Tag: !!str, Kind: ScalarNode, Anchor: - frog -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorSimpleAssignNumberCmd(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple`) - - path, errPath := treeCreator.ParsePath(`.a.b |= 5`) - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a b] - Tag: !!int, Kind: ScalarNode, Anchor: - 5 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorSimpleAssignFloatCmd(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple`) - - path, errPath := treeCreator.ParsePath(`.a.b |= 3.142`) - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a b] - Tag: !!float, Kind: ScalarNode, Anchor: - 3.142 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - func TestDataTreeNavigatorSimpleAssignBooleanCmd(t *testing.T) { nodes := readDoc(t, `a: diff --git a/pkg/yqlib/treeops/operator_assign.go b/pkg/yqlib/treeops/operator_assign.go new file mode 100644 index 00000000..0dc60900 --- /dev/null +++ b/pkg/yqlib/treeops/operator_assign.go @@ -0,0 +1,27 @@ +package treeops + +import "github.com/elliotchance/orderedmap" + +func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + for el := lhs.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + + rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + + if err != nil { + return nil, err + } + + // grab the first value + first := rhs.Front() + + if first != nil { + candidate.UpdateFrom(first.Value.(*CandidateNode)) + } + } + return matchingNodes, nil +} diff --git a/pkg/yqlib/treeops/operator_assign_test.go b/pkg/yqlib/treeops/operator_assign_test.go new file mode 100644 index 00000000..979363a9 --- /dev/null +++ b/pkg/yqlib/treeops/operator_assign_test.go @@ -0,0 +1,62 @@ +package treeops + +import ( + "testing" +) + +var assignOperatorScenarios = []expressionScenario{ + { + document: `{a: {b: apple}}`, + expression: `.a.b |= "frog"`, + expected: []string{ + "D0, P[], (!!map)::{a: {b: frog}}\n", + }, + }, { + document: `{a: {b: apple}}`, + expression: `.a.b | (. |= "frog")`, + expected: []string{ + "D0, P[a b], (!!str)::frog\n", + }, + }, { + document: `{a: {b: apple}}`, + expression: `.a.b |= 5`, + expected: []string{ + "D0, P[], (!!map)::{a: {b: 5}}\n", + }, + }, { + document: `{a: {b: apple}}`, + expression: `.a.b |= 3.142`, + expected: []string{ + "D0, P[], (!!map)::{a: {b: 3.142}}\n", + }, + // document: `{}`, + // expression: `["cat", "dog"]`, + // expected: []string{ + // "D0, P[], (!!seq)::- cat\n- dog\n", + // }, + // }, { + // document: `{}`, + // expression: `1 | collect`, + // expected: []string{ + // "D0, P[], (!!seq)::- 1\n", + // }, + // }, { + // document: `[1,2,3]`, + // expression: `[.[]]`, + // expected: []string{ + // "D0, P[], (!!seq)::- 1\n- 2\n- 3\n", + // }, + // }, { + // document: `a: {b: [1,2,3]}`, + // expression: `[.a.b[]]`, + // expected: []string{ + // "D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n", + // }, + }, +} + +func TestAssignOperatorScenarios(t *testing.T) { + for _, tt := range assignOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operator_value_test.go b/pkg/yqlib/treeops/operator_value_test.go new file mode 100644 index 00000000..b722a4b4 --- /dev/null +++ b/pkg/yqlib/treeops/operator_value_test.go @@ -0,0 +1,75 @@ +package treeops + +import ( + "testing" +) + +var valueOperatorScenarios = []expressionScenario{ + { + document: ``, + expression: `1`, + expected: []string{ + "D0, P[], (!!int)::1\n", + }, + }, { + document: ``, + expression: `-1`, + expected: []string{ + "D0, P[], (!!int)::-1\n", + }, + }, { + document: ``, + expression: `1.2`, + expected: []string{ + "D0, P[], (!!float)::1.2\n", + }, + }, { + document: ``, + expression: `-5.2e11`, + expected: []string{ + "D0, P[], (!!float)::-5.2e11\n", + }, + }, { + document: ``, + expression: `5e-10`, + expected: []string{ + "D0, P[], (!!float)::5e-10\n", + }, + }, { + document: ``, + expression: `"cat"`, + expected: []string{ + "D0, P[], (!!str)::cat\n", + }, + }, { + document: ``, + expression: `"1.3"`, + expected: []string{ + "D0, P[], (!!str)::\"1.3\"\n", + }, + }, { + document: ``, + expression: `"true"`, + expected: []string{ + "D0, P[], (!!str)::\"true\"\n", + }, + }, { + document: ``, + expression: `true`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, { + document: ``, + expression: `false`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, +} + +func TestValueOperatorScenarios(t *testing.T) { + for _, tt := range valueOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index 5b5068d0..f09f1c5e 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -32,30 +32,6 @@ func nodeToMap(candidate *CandidateNode) *orderedmap.OrderedMap { return elMap } -func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - for el := lhs.Front(); el != nil; el = el.Next() { - candidate := el.Value.(*CandidateNode) - - rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) - - if err != nil { - return nil, err - } - - // grab the first value - first := rhs.Front() - - if first != nil { - candidate.UpdateFrom(first.Value.(*CandidateNode)) - } - } - return lhs, nil -} - func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index f00651fc..5101c192 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -76,6 +76,17 @@ func numberValue() lex.Action { } } +func floatValue() lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + var numberString = string(m.Bytes) + var number, errParsingInt = strconv.ParseFloat(numberString, 64) // nolint + if errParsingInt != nil { + return nil, errParsingInt + } + return &Token{PathElementType: Value, OperationType: None, Value: number, StringValue: numberString}, nil + } +} + func booleanValue(val bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return &Token{PathElementType: Value, OperationType: None, Value: val, StringValue: string(m.Bytes)}, nil @@ -111,7 +122,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`length`), opToken(Length)) lexer.Add([]byte(`select`), opToken(Select)) lexer.Add([]byte(`or`), opToken(Or)) - lexer.Add([]byte(`and`), opToken(And)) + // lexer.Add([]byte(`and`), opToken()) lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) @@ -131,7 +142,9 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\|`), opToken(Pipe)) - lexer.Add([]byte(`-?[0-9]+`), numberValue()) + lexer.Add([]byte(`-?\d+(\.\d+)`), floatValue()) + lexer.Add([]byte(`-?[1-9](\.\d+)?[Ee][-+]?\d+`), floatValue()) + lexer.Add([]byte(`-?\d+`), numberValue()) lexer.Add([]byte(`[Tt][Rr][Uu][Ee]`), booleanValue(true)) lexer.Add([]byte(`[Ff][Aa][Ll][Ss][Ee]`), booleanValue(false)) diff --git a/pkg/yqlib/value_parser.go b/pkg/yqlib/value_parser.go deleted file mode 100644 index 6dee7400..00000000 --- a/pkg/yqlib/value_parser.go +++ /dev/null @@ -1,46 +0,0 @@ -package yqlib - -import ( - yaml "gopkg.in/yaml.v3" -) - -type ValueParser interface { - Parse(argument string, customTag string, customStyle string, anchorName string, createAlias bool) *yaml.Node -} - -type valueParser struct { -} - -func NewValueParser() ValueParser { - return &valueParser{} -} - -func (v *valueParser) Parse(argument string, customTag string, customStyle string, anchorName string, createAlias bool) *yaml.Node { - var style yaml.Style - if customStyle == "tagged" { - style = yaml.TaggedStyle - } else if customStyle == "double" { - style = yaml.DoubleQuotedStyle - } else if customStyle == "single" { - style = yaml.SingleQuotedStyle - } else if customStyle == "literal" { - style = yaml.LiteralStyle - } else if customStyle == "folded" { - style = yaml.FoldedStyle - } else if customStyle == "flow" { - style = yaml.FlowStyle - } else if customStyle != "" { - log.Error("Unknown style %v, ignoring", customStyle) - } - if argument == "[]" { - return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode, Style: style} - } - - kind := yaml.ScalarNode - - if createAlias { - kind = yaml.AliasNode - } - - return &yaml.Node{Value: argument, Tag: customTag, Kind: kind, Style: style, Anchor: anchorName} -} diff --git a/pkg/yqlib/value_parser_test.go b/pkg/yqlib/value_parser_test.go deleted file mode 100644 index a21a6ec1..00000000 --- a/pkg/yqlib/value_parser_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package yqlib - -import ( - "testing" - - "github.com/mikefarah/yq/v3/test" - yaml "gopkg.in/yaml.v3" -) - -var parseStyleTests = []struct { - customStyle string - expectedStyle yaml.Style -}{ - {"", 0}, - {"tagged", yaml.TaggedStyle}, - {"double", yaml.DoubleQuotedStyle}, - {"single", yaml.SingleQuotedStyle}, - {"folded", yaml.FoldedStyle}, - {"flow", yaml.FlowStyle}, - {"literal", yaml.LiteralStyle}, -} - -func TestValueParserStyleTag(t *testing.T) { - for _, tt := range parseStyleTests { - actual := NewValueParser().Parse("cat", "", tt.customStyle, "", false) - test.AssertResultWithContext(t, tt.expectedStyle, actual.Style, tt.customStyle) - } -} - -var parseValueTests = []struct { - argument string - customTag string - expectedTag string - testDescription string -}{ - {"true", "!!str", "!!str", "boolean forced as string"}, - {"3", "!!int", "!!int", "int"}, - {"cat", "", "", "default"}, -} - -func TestValueParserParse(t *testing.T) { - for _, tt := range parseValueTests { - actual := NewValueParser().Parse(tt.argument, tt.customTag, "", "", false) - test.AssertResultWithContext(t, tt.argument, actual.Value, tt.testDescription) - test.AssertResultWithContext(t, tt.expectedTag, actual.Tag, tt.testDescription) - test.AssertResult(t, yaml.ScalarNode, actual.Kind) - } -} - -func TestValueParserParseEmptyArray(t *testing.T) { - actual := NewValueParser().Parse("[]", "", "", "", false) - test.AssertResult(t, "!!seq", actual.Tag) - test.AssertResult(t, yaml.SequenceNode, actual.Kind) -} - -func TestValueParserParseAlias(t *testing.T) { - actual := NewValueParser().Parse("bob", "", "", "", true) - test.AssertResult(t, "bob", actual.Value) - test.AssertResult(t, yaml.AliasNode, actual.Kind) -} - -func TestValueParserAnchorname(t *testing.T) { - actual := NewValueParser().Parse("caterpillar", "", "", "foo", false) - test.AssertResult(t, "foo", actual.Anchor) -} From b026ebf2c3176752d33fc9926da236c1ee64623c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 17 Oct 2020 22:58:18 +1100 Subject: [PATCH 040/129] more refinement --- pkg/yqlib/treeops/data_tree_navigator_test.go | 74 ------------------- pkg/yqlib/treeops/operator_assign_test.go | 18 +++++ pkg/yqlib/treeops/operators_test.go | 2 + 3 files changed, 20 insertions(+), 74 deletions(-) diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index e5349a54..01654d70 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -628,80 +628,6 @@ func TestDataTreeNavigatorArraySimple(t *testing.T) { test.AssertResult(t, expected, resultsToString(results)) } -func TestDataTreeNavigatorSimpleAssignBooleanCmd(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple`) - path, errPath := treeCreator.ParsePath(`.a.b |= true`) - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a b] - Tag: !!bool, Kind: ScalarNode, Anchor: - true -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorSimpleAssignChildCmd(t *testing.T) { - - nodes := readDoc(t, `a: - b: {g: 3}`) - - path, errPath := treeCreator.ParsePath(`.a |= .b`) - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!map, Kind: MappingNode, Anchor: - {g: 3} -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorSimpleAssignSelf(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple`) - - path, errPath := treeCreator.ParsePath("a(. == apple)(. := frog)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a b] - Tag: !!str, Kind: ScalarNode, Anchor: - frog -` - - test.AssertResult(t, expected, resultsToString(results)) -} - func TestDataTreeNavigatorSimpleAssignByFind(t *testing.T) { nodes := readDoc(t, `a: diff --git a/pkg/yqlib/treeops/operator_assign_test.go b/pkg/yqlib/treeops/operator_assign_test.go index 979363a9..e44b1864 100644 --- a/pkg/yqlib/treeops/operator_assign_test.go +++ b/pkg/yqlib/treeops/operator_assign_test.go @@ -29,6 +29,24 @@ var assignOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::{a: {b: 3.142}}\n", }, + }, { + document: `{a: {b: {g: foof}}}`, + expression: `.a |= .b`, + expected: []string{ + "D0, P[], (!!map)::{a: {g: foof}}\n", + }, + }, { + document: `{a: {b: apple, c: cactus}}`, + expression: `.a[] | select(. == "apple") |= "frog"`, + expected: []string{ + "D0, P[], (!!map)::{a: {b: frog, c: cactus}}\n", + }, + }, { + document: `[candy, apple, sandy]`, + expression: `.[] | select(. == "*andy") |= "bogs"`, + expected: []string{ + "D0, P[], (!!seq)::[bogs, apple, bogs]\n", + }, // document: `{}`, // expression: `["cat", "dog"]`, // expected: []string{ diff --git a/pkg/yqlib/treeops/operators_test.go b/pkg/yqlib/treeops/operators_test.go index fedd6dd9..434a7a13 100644 --- a/pkg/yqlib/treeops/operators_test.go +++ b/pkg/yqlib/treeops/operators_test.go @@ -18,11 +18,13 @@ func testScenario(t *testing.T, s *expressionScenario) { path, errPath := treeCreator.ParsePath(s.expression) if errPath != nil { t.Error(errPath) + return } results, errNav := treeNavigator.GetMatchingNodes(nodes, path) if errNav != nil { t.Error(errNav) + return } test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), s.expression) } From 391ab8d70c032c2e9fa0f04c1fc0669293c6a670 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 18 Oct 2020 11:31:36 +1100 Subject: [PATCH 041/129] removed docs, added recursive decent --- docs/404.html | 268 ------------------ docs/assets/fonts/font-awesome.css | 4 - docs/assets/fonts/material-icons.css | 13 - docs/assets/fonts/specimen/FontAwesome.ttf | Bin 165548 -> 0 bytes docs/assets/fonts/specimen/FontAwesome.woff | Bin 98024 -> 0 bytes docs/assets/fonts/specimen/FontAwesome.woff2 | Bin 77160 -> 0 bytes .../fonts/specimen/MaterialIcons-Regular.ttf | Bin 128180 -> 0 bytes .../fonts/specimen/MaterialIcons-Regular.woff | Bin 57620 -> 0 bytes .../specimen/MaterialIcons-Regular.woff2 | Bin 44300 -> 0 bytes docs/assets/images/favicon.png | Bin 521 -> 0 bytes .../images/icons/bitbucket.1b09e088.svg | 1 - docs/assets/images/icons/github.f0b8504a.svg | 1 - docs/assets/images/icons/gitlab.6dd19c00.svg | 1 - .../javascripts/application.808e90bb.js | 60 ---- docs/assets/javascripts/lunr/lunr.ar.js | 20 -- docs/assets/javascripts/lunr/lunr.da.js | 17 -- docs/assets/javascripts/lunr/lunr.de.js | 17 -- docs/assets/javascripts/lunr/lunr.du.js | 17 -- docs/assets/javascripts/lunr/lunr.es.js | 17 -- docs/assets/javascripts/lunr/lunr.fi.js | 17 -- docs/assets/javascripts/lunr/lunr.fr.js | 17 -- docs/assets/javascripts/lunr/lunr.hu.js | 17 -- docs/assets/javascripts/lunr/lunr.it.js | 17 -- docs/assets/javascripts/lunr/lunr.ja.js | 17 -- docs/assets/javascripts/lunr/lunr.jp.js | 1 - docs/assets/javascripts/lunr/lunr.multi.js | 1 - docs/assets/javascripts/lunr/lunr.nl.js | 17 -- docs/assets/javascripts/lunr/lunr.no.js | 17 -- docs/assets/javascripts/lunr/lunr.pt.js | 17 -- docs/assets/javascripts/lunr/lunr.ro.js | 17 -- docs/assets/javascripts/lunr/lunr.ru.js | 17 -- .../javascripts/lunr/lunr.stemmer.support.js | 9 - docs/assets/javascripts/lunr/lunr.sv.js | 17 -- docs/assets/javascripts/lunr/lunr.th.js | 17 -- docs/assets/javascripts/lunr/lunr.tr.js | 17 -- docs/assets/javascripts/lunr/lunr.vi.js | 17 -- docs/assets/javascripts/lunr/tinyseg.js | 1 - docs/assets/javascripts/lunr/wordcut.js | 1 - docs/assets/javascripts/modernizr.268332fc.js | 1 - .../application-palette.a8b3c06d.css | 1 - .../stylesheets/application.1b62728e.css | 1 - docs/index.html | 202 ------------- docs/search/search_index.json | 1 - docs/sitemap.xml | 8 - docs/sitemap.xml.gz | Bin 189 -> 0 bytes pkg/yqlib/treeops/lib.go | 1 + pkg/yqlib/treeops/operator_assign_test.go | 23 -- .../treeops/operator_recursive_descent.go | 34 +++ .../operator_recursive_descent_test.go | 55 ++++ pkg/yqlib/treeops/operators_test.go | 3 +- pkg/yqlib/treeops/path_tokeniser.go | 2 +- 51 files changed, 93 insertions(+), 926 deletions(-) delete mode 100644 docs/404.html delete mode 100644 docs/assets/fonts/font-awesome.css delete mode 100644 docs/assets/fonts/material-icons.css delete mode 100644 docs/assets/fonts/specimen/FontAwesome.ttf delete mode 100644 docs/assets/fonts/specimen/FontAwesome.woff delete mode 100644 docs/assets/fonts/specimen/FontAwesome.woff2 delete mode 100644 docs/assets/fonts/specimen/MaterialIcons-Regular.ttf delete mode 100644 docs/assets/fonts/specimen/MaterialIcons-Regular.woff delete mode 100644 docs/assets/fonts/specimen/MaterialIcons-Regular.woff2 delete mode 100644 docs/assets/images/favicon.png delete mode 100644 docs/assets/images/icons/bitbucket.1b09e088.svg delete mode 100644 docs/assets/images/icons/github.f0b8504a.svg delete mode 100644 docs/assets/images/icons/gitlab.6dd19c00.svg delete mode 100644 docs/assets/javascripts/application.808e90bb.js delete mode 100644 docs/assets/javascripts/lunr/lunr.ar.js delete mode 100644 docs/assets/javascripts/lunr/lunr.da.js delete mode 100644 docs/assets/javascripts/lunr/lunr.de.js delete mode 100644 docs/assets/javascripts/lunr/lunr.du.js delete mode 100644 docs/assets/javascripts/lunr/lunr.es.js delete mode 100644 docs/assets/javascripts/lunr/lunr.fi.js delete mode 100644 docs/assets/javascripts/lunr/lunr.fr.js delete mode 100644 docs/assets/javascripts/lunr/lunr.hu.js delete mode 100644 docs/assets/javascripts/lunr/lunr.it.js delete mode 100644 docs/assets/javascripts/lunr/lunr.ja.js delete mode 100644 docs/assets/javascripts/lunr/lunr.jp.js delete mode 100644 docs/assets/javascripts/lunr/lunr.multi.js delete mode 100644 docs/assets/javascripts/lunr/lunr.nl.js delete mode 100644 docs/assets/javascripts/lunr/lunr.no.js delete mode 100644 docs/assets/javascripts/lunr/lunr.pt.js delete mode 100644 docs/assets/javascripts/lunr/lunr.ro.js delete mode 100644 docs/assets/javascripts/lunr/lunr.ru.js delete mode 100644 docs/assets/javascripts/lunr/lunr.stemmer.support.js delete mode 100644 docs/assets/javascripts/lunr/lunr.sv.js delete mode 100644 docs/assets/javascripts/lunr/lunr.th.js delete mode 100644 docs/assets/javascripts/lunr/lunr.tr.js delete mode 100644 docs/assets/javascripts/lunr/lunr.vi.js delete mode 100644 docs/assets/javascripts/lunr/tinyseg.js delete mode 100644 docs/assets/javascripts/lunr/wordcut.js delete mode 100644 docs/assets/javascripts/modernizr.268332fc.js delete mode 100644 docs/assets/stylesheets/application-palette.a8b3c06d.css delete mode 100644 docs/assets/stylesheets/application.1b62728e.css delete mode 100644 docs/index.html delete mode 100644 docs/search/search_index.json delete mode 100644 docs/sitemap.xml delete mode 100644 docs/sitemap.xml.gz create mode 100644 pkg/yqlib/treeops/operator_recursive_descent.go create mode 100644 pkg/yqlib/treeops/operator_recursive_descent_test.go diff --git a/docs/404.html b/docs/404.html deleted file mode 100644 index a7879f36..00000000 --- a/docs/404.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
- - - - -
-
- - -
-
-
- -
-
-
- - - -
-
- -

404 - Not found

- - - - - - -
-
-
-
- - - - -
- - - - - - - - \ No newline at end of file diff --git a/docs/assets/fonts/font-awesome.css b/docs/assets/fonts/font-awesome.css deleted file mode 100644 index b476b53e..00000000 --- a/docs/assets/fonts/font-awesome.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url("specimen/FontAwesome.woff2") format("woff2"),url("specimen/FontAwesome.woff") format("woff"),url("specimen/FontAwesome.ttf") format("truetype")}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} \ No newline at end of file diff --git a/docs/assets/fonts/material-icons.css b/docs/assets/fonts/material-icons.css deleted file mode 100644 index d23d365e..00000000 --- a/docs/assets/fonts/material-icons.css +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, SOFTWARE - * DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. - * SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS AND - * LIMITATIONS UNDER THE LICENSE. - */@font-face{font-family:"Material Icons";font-style:normal;font-weight:400;src:local("Material Icons"),local("MaterialIcons-Regular"),url("specimen/MaterialIcons-Regular.woff2") format("woff2"),url("specimen/MaterialIcons-Regular.woff") format("woff"),url("specimen/MaterialIcons-Regular.ttf") format("truetype")} \ No newline at end of file diff --git a/docs/assets/fonts/specimen/FontAwesome.ttf b/docs/assets/fonts/specimen/FontAwesome.ttf deleted file mode 100644 index 35acda2fa1196aad98c2adf4378a7611dd713aa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165548 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 z 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 d0mRO}xo^G_%I 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 SdSQM28?+M|D(2r_;{O0|uQ74} diff --git a/docs/assets/fonts/specimen/FontAwesome.woff2 b/docs/assets/fonts/specimen/FontAwesome.woff2 deleted file mode 100644 index 4d13fc60404b91e398a37200c4a77b645cfd9586..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77160 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 diff --git a/docs/assets/fonts/specimen/MaterialIcons-Regular.ttf b/docs/assets/fonts/specimen/MaterialIcons-Regular.ttf deleted file mode 100644 index 7015564ad166a3e9d88c82f17829f0cc01ebe29a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128180 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 ohyj3!`#ee%L;DTjx@8!5k5~VH0QmdE^#A|> diff --git a/docs/assets/fonts/specimen/MaterialIcons-Regular.woff b/docs/assets/fonts/specimen/MaterialIcons-Regular.woff deleted file mode 100644 index b648a3eea2d16b6ce783906d6b7d5f251b9eb56c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57620 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

wF7DI|Kx|V1sBQ9FYs>m5C)C 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 BJ$(QG diff --git a/docs/assets/fonts/specimen/MaterialIcons-Regular.woff2 b/docs/assets/fonts/specimen/MaterialIcons-Regular.woff2 deleted file mode 100644 index 9fa211252080046a23b2449dbdced6abc2b0bb34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44300 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 QW8}wPUeo^@jR61v08@RD2LJ#7 diff --git a/docs/assets/images/favicon.png b/docs/assets/images/favicon.png deleted file mode 100644 index 76d17f57ad903c3ea2f1b564cafb95bf9af84ee3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 521 zcmV+k0`~ohP)kdg0005dNkl2WptjAn6@db&Pvy?U$ zv>P|<&rCZfZF0jmq0opf8)91(A<*iIVPPJJT((+JiF~>9KAA3%heFdnI;SaK+~|aU zQ~!x`%y{jX1<~SK2RxN7Db8`yWBbf6p7&07{VXfaam*cUs&eu*Zu(xaIL8rP){;a< zS~$}^Td32Rw+W1TqTd|L{#~jJet4!qwKsb5hq%YXiiUV!yH=ltu0>s|FLsT+Iy7K~ z!6*Z0a@vQ;AiZo!=s{{fqR+ct6YQPzbk+j}*qe7vtu39I7 zrOtZqU}=NnLchJxsU9iY+}3TYDl|BvPsX%E@dlyLgdV%q$UP|Y?DfcGb`}K&$;drd z+hL;zy7UTccUYU+h`ONIU|d=%`(0$=KW4%tVWXj~AE \ No newline at end of file diff --git a/docs/assets/images/icons/github.f0b8504a.svg b/docs/assets/images/icons/github.f0b8504a.svg deleted file mode 100644 index 3d13b197..00000000 --- a/docs/assets/images/icons/github.f0b8504a.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/assets/images/icons/gitlab.6dd19c00.svg b/docs/assets/images/icons/gitlab.6dd19c00.svg deleted file mode 100644 index 1d9fffa7..00000000 --- a/docs/assets/images/icons/gitlab.6dd19c00.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/assets/javascripts/application.808e90bb.js b/docs/assets/javascripts/application.808e90bb.js deleted file mode 100644 index cd28ce28..00000000 --- a/docs/assets/javascripts/application.808e90bb.js +++ /dev/null @@ -1,60 +0,0 @@ -!function(e,t){for(var n in t)e[n]=t[n]}(window,function(n){var r={};function i(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return n[e].call(t.exports,t,t.exports,i),t.l=!0,t.exports}return i.m=n,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)i.d(n,r,function(e){return t[e]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=13)}([function(e,t,n){"use strict";var r={Listener:function(){function e(e,t,n){var r=this;this.els_=Array.prototype.slice.call("string"==typeof e?document.querySelectorAll(e):[].concat(e)),this.handler_="function"==typeof n?{update:n}:n,this.events_=[].concat(t),this.update_=function(e){return r.handler_.update(e)}}var t=e.prototype;return t.listen=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.addEventListener(e,n.update_,!1)})}),"function"==typeof this.handler_.setup&&this.handler_.setup()},t.unlisten=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.removeEventListener(e,n.update_)})}),"function"==typeof this.handler_.reset&&this.handler_.reset()},e}(),MatchMedia:function(e,t){this.handler_=function(e){e.matches?t.listen():t.unlisten()};var n=window.matchMedia(e);n.addListener(this.handler_),this.handler_(n)}},i={Shadow:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement&&n.parentNode instanceof HTMLElement))throw new ReferenceError;if(this.el_=n.parentNode,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLElement))throw new ReferenceError;this.header_=n,this.height_=0,this.active_=!1}var t=e.prototype;return t.setup=function(){for(var e=this.el_;e=e.previousElementSibling;){if(!(e instanceof HTMLElement))throw new ReferenceError;this.height_+=e.offsetHeight}this.update()},t.update=function(e){if(!e||"resize"!==e.type&&"orientationchange"!==e.type){var t=window.pageYOffset>=this.height_;t!==this.active_&&(this.header_.dataset.mdState=(this.active_=t)?"shadow":"")}else this.height_=0,this.setup()},t.reset=function(){this.header_.dataset.mdState="",this.height_=0,this.active_=!1},e}(),Title:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement))throw new ReferenceError;if(this.el_=n,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLHeadingElement))throw new ReferenceError;this.header_=n,this.active_=!1}var t=e.prototype;return t.setup=function(){var t=this;Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.update=function(e){var t=this,n=window.pageYOffset>=this.header_.offsetTop;n!==this.active_&&(this.el_.dataset.mdState=(this.active_=n)?"active":""),"resize"!==e.type&&"orientationchange"!==e.type||Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.width="",this.active_=!1},e}()},o={Blur:function(){function e(e){this.els_="string"==typeof e?document.querySelectorAll(e):e,this.index_=0,this.offset_=window.pageYOffset,this.dir_=!1,this.anchors_=[].reduce.call(this.els_,function(e,t){var n=decodeURIComponent(t.hash);return e.concat(document.getElementById(n.substring(1))||[])},[])}var t=e.prototype;return t.setup=function(){this.update()},t.update=function(){var e=window.pageYOffset,t=this.offset_-e<0;if(this.dir_!==t&&(this.index_=this.index_=t?0:this.els_.length-1),0!==this.anchors_.length){if(this.offset_<=e)for(var n=this.index_+1;ne)){this.index_=r;break}0=this.offset_?"lock"!==this.el_.dataset.mdState&&(this.el_.dataset.mdState="lock"):"lock"===this.el_.dataset.mdState&&(this.el_.dataset.mdState="")},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.height="",this.height_=0},e}()},c=n(6),l=n.n(c);var u={Adapter:{GitHub:function(o){var e,t;function n(e){var t;t=o.call(this,e)||this;var n=/^.+github\.com\/([^/]+)\/?([^/]+)?.*$/.exec(t.base_);if(n&&3===n.length){var r=n[1],i=n[2];t.base_="https://api.github.com/users/"+r+"/repos",t.name_=i}return t}return t=o,(e=n).prototype=Object.create(t.prototype),(e.prototype.constructor=e).__proto__=t,n.prototype.fetch_=function(){var i=this;return function n(r){return void 0===r&&(r=0),fetch(i.base_+"?per_page=100&sort=updated&page="+r).then(function(e){return e.json()}).then(function(e){if(!(e instanceof Array))return[];if(i.name_){var t=e.find(function(e){return e.name===i.name_});return t||30!==e.length?t?[i.format_(t.stargazers_count)+" Stars",i.format_(t.forks_count)+" Forks"]:[]:n(r+1)}return[e.length+" Repositories"]})}()},n}(function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLAnchorElement))throw new ReferenceError;this.el_=t,this.base_=this.el_.href,this.salt_=this.hash_(this.base_)}var t=e.prototype;return t.fetch=function(){var n=this;return new Promise(function(t){var e=l.a.getJSON(n.salt_+".cache-source");void 0!==e?t(e):n.fetch_().then(function(e){l.a.set(n.salt_+".cache-source",e,{expires:1/96}),t(e)})})},t.fetch_=function(){throw new Error("fetch_(): Not implemented")},t.format_=function(e){return 1e4=this.el_.children[0].offsetTop+(5-this.height_);e!==this.active_&&(this.el_.dataset.mdState=(this.active_=e)?"hidden":"")},t.reset=function(){this.el_.dataset.mdState="",this.active_=!1},e}()};t.a={Event:r,Header:i,Nav:o,Search:a,Sidebar:s,Source:u,Tabs:f}},function(t,e,n){(function(e){t.exports=e.lunr=n(24)}).call(this,n(4))},function(e,d,h){"use strict";(function(t){var e=h(8),n=setTimeout;function c(e){return Boolean(e&&void 0!==e.length)}function r(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],f(e,this)}function i(n,r){for(;3===n._state;)n=n._value;0!==n._state?(n._handled=!0,o._immediateFn(function(){var e=1===n._state?r.onFulfilled:r.onRejected;if(null!==e){var t;try{t=e(n._value)}catch(e){return void s(r.promise,e)}a(r.promise,t)}else(1===n._state?a:s)(r.promise,n._value)})):n._deferreds.push(r)}function a(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var n=e.then;if(e instanceof o)return t._state=3,t._value=e,void l(t);if("function"==typeof n)return void f((r=n,i=e,function(){r.apply(i,arguments)}),t)}t._state=1,t._value=e,l(t)}catch(e){s(t,e)}var r,i}function s(e,t){e._state=2,e._value=t,l(e)}function l(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;t"+n+""};this.stack_=[],r.forEach(function(e,t){var n,r=a.docs_.get(t),i=f.createElement("li",{class:"md-search-result__item"},f.createElement("a",{href:r.location,title:r.title,class:"md-search-result__link",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article md-search-result__article--document"},f.createElement("h1",{class:"md-search-result__title"},{__html:r.title.replace(s,c)}),r.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:r.text.replace(s,c)}):{}))),o=e.map(function(t){return function(){var e=a.docs_.get(t.ref);i.appendChild(f.createElement("a",{href:e.location,title:e.title,class:"md-search-result__link","data-md-rel":"anchor",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article"},f.createElement("h1",{class:"md-search-result__title"},{__html:e.title.replace(s,c)}),e.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:function(e,t){var n=t;if(e.length>n){for(;" "!==e[n]&&0<--n;);return e.substring(0,n)+"..."}return e}(e.text.replace(s,c),400)}):{})))}});(n=a.stack_).push.apply(n,[function(){return a.list_.appendChild(i)}].concat(o))});var o=this.el_.parentNode;if(!(o instanceof HTMLElement))throw new ReferenceError;for(;this.stack_.length&&o.offsetHeight>=o.scrollHeight-16;)this.stack_.shift()();var l=this.list_.querySelectorAll("[data-md-rel=anchor]");switch(Array.prototype.forEach.call(l,function(r){["click","keydown"].forEach(function(n){r.addEventListener(n,function(e){if("keydown"!==n||13===e.keyCode){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.checked&&(t.checked=!1,t.dispatchEvent(new CustomEvent("change"))),e.preventDefault(),setTimeout(function(){document.location.href=r.href},100)}})})}),r.size){case 0:this.meta_.textContent=this.message_.none;break;case 1:this.meta_.textContent=this.message_.one;break;default:this.meta_.textContent=this.message_.other.replace("#",r.size)}}}else{var u=function(e){a.docs_=e.reduce(function(e,t){var n,r,i,o=t.location.split("#"),a=o[0],s=o[1];return t.text=(n=t.text,r=document.createTextNode(n),(i=document.createElement("p")).appendChild(r),i.innerHTML),s&&(t.parent=e.get(a),t.parent&&!t.parent.done&&(t.parent.title=t.title,t.parent.text=t.text,t.parent.done=!0)),t.text=t.text.replace(/\n/g," ").replace(/\s+/g," ").replace(/\s+([,.:;!?])/g,function(e,t){return t}),t.parent&&t.parent.title===t.title||e.set(t.location,t),e},new Map);var i=a.docs_,o=a.lang_;a.stack_=[],a.index_=d()(function(){var e,t=this,n={"search.pipeline.trimmer":d.a.trimmer,"search.pipeline.stopwords":d.a.stopWordFilter},r=Object.keys(n).reduce(function(e,t){return h(t).match(/^false$/i)||e.push(n[t]),e},[]);this.pipeline.reset(),r&&(e=this.pipeline).add.apply(e,r),1===o.length&&"en"!==o[0]&&d.a[o[0]]?this.use(d.a[o[0]]):1=t.scrollHeight-16;)a.stack_.splice(0,10).forEach(function(e){return e()})})};setTimeout(function(){return"function"==typeof a.data_?a.data_().then(u):u(a.data_)},250)}},e}()}).call(this,r(3))},function(e,n,r){"use strict";(function(t){r.d(n,"a",function(){return e});var e=function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLElement))throw new ReferenceError;this.el_=t}return e.prototype.initialize=function(e){e.length&&this.el_.children.length&&this.el_.children[this.el_.children.length-1].appendChild(t.createElement("ul",{class:"md-source__facts"},e.map(function(e){return t.createElement("li",{class:"md-source__fact"},e)}))),this.el_.dataset.mdState="done"},e}()}).call(this,r(3))},,,function(e,n,c){"use strict";c.r(n),function(o){c.d(n,"app",function(){return t});c(14),c(15),c(16),c(17),c(18),c(19),c(20);var r=c(2),e=c(5),a=c.n(e),i=c(0);window.Promise=window.Promise||r.a;var s=function(e){var t=document.getElementsByName("lang:"+e)[0];if(!(t instanceof HTMLMetaElement))throw new ReferenceError;return t.content};var t={initialize:function(t){new i.a.Event.Listener(document,"DOMContentLoaded",function(){if(!(document.body instanceof HTMLElement))throw new ReferenceError;Modernizr.addTest("ios",function(){return!!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)});var e=document.querySelectorAll("table:not([class])");if(Array.prototype.forEach.call(e,function(e){var t=o.createElement("div",{class:"md-typeset__scrollwrap"},o.createElement("div",{class:"md-typeset__table"}));e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t),t.children[0].appendChild(e)}),a.a.isSupported()){var t=document.querySelectorAll(".codehilite > pre, pre > code");Array.prototype.forEach.call(t,function(e,t){var n="__code_"+t,r=o.createElement("button",{class:"md-clipboard",title:s("clipboard.copy"),"data-clipboard-target":"#"+n+" pre, #"+n+" code"},o.createElement("span",{class:"md-clipboard__message"})),i=e.parentNode;i.id=n,i.insertBefore(r,e)}),new a.a(".md-clipboard").on("success",function(e){var t=e.trigger.querySelector(".md-clipboard__message");if(!(t instanceof HTMLElement))throw new ReferenceError;e.clearSelection(),t.dataset.mdTimer&&clearTimeout(parseInt(t.dataset.mdTimer,10)),t.classList.add("md-clipboard__message--active"),t.innerHTML=s("clipboard.copied"),t.dataset.mdTimer=setTimeout(function(){t.classList.remove("md-clipboard__message--active"),t.dataset.mdTimer=""},2e3).toString()})}if(!Modernizr.details){var n=document.querySelectorAll("details > summary");Array.prototype.forEach.call(n,function(e){e.addEventListener("click",function(e){var t=e.target.parentNode;t.hasAttribute("open")?t.removeAttribute("open"):t.setAttribute("open","")})})}var r=function(){if(document.location.hash){var e=document.getElementById(document.location.hash.substring(1));if(!e)return;for(var t=e.parentNode;t&&!(t instanceof HTMLDetailsElement);)t=t.parentNode;if(t&&!t.open){t.open=!0;var n=location.hash;location.hash=" ",location.hash=n}}};if(window.addEventListener("hashchange",r),r(),Modernizr.ios){var i=document.querySelectorAll("[data-md-scrollfix]");Array.prototype.forEach.call(i,function(t){t.addEventListener("touchstart",function(){var e=t.scrollTop;0===e?t.scrollTop=1:e+t.offsetHeight===t.scrollHeight&&(t.scrollTop=e-1)})})}}).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Shadow("[data-md-component=container]","[data-md-component=header]")).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Title("[data-md-component=title]",".md-typeset h1")).listen(),document.querySelector("[data-md-component=hero]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=hero]")).listen(),document.querySelector("[data-md-component=tabs]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=tabs]")).listen(),new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=navigation]","[data-md-component=header]"))),document.querySelector("[data-md-component=toc]")&&new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=toc]","[data-md-component=header]"))),new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,"scroll",new i.a.Nav.Blur("[data-md-component=toc] .md-nav__link")));var e=document.querySelectorAll("[data-md-component=collapsible]");Array.prototype.forEach.call(e,function(e){new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(e.previousElementSibling,"click",new i.a.Nav.Collapse(e)))}),new i.a.Event.MatchMedia("(max-width: 1219px)",new i.a.Event.Listener("[data-md-component=navigation] [data-md-toggle]","change",new i.a.Nav.Scrolling("[data-md-component=navigation] nav"))),document.querySelector("[data-md-component=search]")&&(new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-toggle=search]","change",new i.a.Search.Lock("[data-md-toggle=search]"))),new i.a.Event.Listener("[data-md-component=query]",["focus","keyup","change"],new i.a.Search.Result("[data-md-component=result]",function(){return fetch(t.url.base+"/search/search_index.json",{credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){return e.docs.map(function(e){return e.location=t.url.base+"/"+e.location,e})})})).listen(),new i.a.Event.Listener("[data-md-component=reset]","click",function(){setTimeout(function(){var e=document.querySelector("[data-md-component=query]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.focus()},10)}).listen(),new i.a.Event.Listener("[data-md-toggle=search]","change",function(e){setTimeout(function(e){if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.focus()}},400,e.target)}).listen(),new i.a.Event.Listener("[data-md-component=query]","focus",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked||(e.checked=!0,e.dispatchEvent(new CustomEvent("change")))}).listen(),new i.a.Event.Listener(window,"keydown",function(e){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;var n=document.querySelector("[data-md-component=query]");if(!(n instanceof HTMLInputElement))throw new ReferenceError;if(!(document.activeElement instanceof HTMLElement&&document.activeElement.isContentEditable||e.metaKey||e.ctrlKey))if(t.checked){if(13===e.keyCode){if(n===document.activeElement){e.preventDefault();var r=document.querySelector("[data-md-component=search] [href][data-md-state=active]");r instanceof HTMLLinkElement&&(window.location=r.getAttribute("href"),t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur())}}else if(9===e.keyCode||27===e.keyCode)t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur();else if(-1!==[8,37,39].indexOf(e.keyCode))n!==document.activeElement&&n.focus();else if(-1!==[38,40].indexOf(e.keyCode)){var i=e.keyCode,o=Array.prototype.slice.call(document.querySelectorAll("[data-md-component=query], [data-md-component=search] [href]")),a=o.find(function(e){if(!(e instanceof HTMLElement))throw new ReferenceError;return"active"===e.dataset.mdState});a&&(a.dataset.mdState="");var s=Math.max(0,(o.indexOf(a)+o.length+(38===i?-1:1))%o.length);return o[s]&&(o[s].dataset.mdState="active",o[s].focus()),e.preventDefault(),e.stopPropagation(),!1}}else if(document.activeElement&&!document.activeElement.form){if("TEXTAREA"===document.activeElement.tagName||"INPUT"===document.activeElement.tagName)return;70!==e.keyCode&&83!==e.keyCode||(n.focus(),e.preventDefault())}}).listen(),new i.a.Event.Listener(window,"keypress",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t!==document.activeElement&&t.focus()}}).listen()),new i.a.Event.Listener(document.body,"keydown",function(e){if(9===e.keyCode){var t=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[for]:not([tabindex])");Array.prototype.forEach.call(t,function(e){e.offsetHeight&&(e.tabIndex=0)})}}).listen(),new i.a.Event.Listener(document.body,"mousedown",function(){var e=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[tabindex]");Array.prototype.forEach.call(e,function(e){e.removeAttribute("tabIndex")})}).listen(),document.body.addEventListener("click",function(){"tabbing"===document.body.dataset.mdState&&(document.body.dataset.mdState="")}),new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-component=navigation] [href^='#']","click",function(){var e=document.querySelector("[data-md-toggle=drawer]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked&&(e.checked=!1,e.dispatchEvent(new CustomEvent("change")))})),function(){var e=document.querySelector("[data-md-source]");if(!e)return r.a.resolve([]);if(!(e instanceof HTMLAnchorElement))throw new ReferenceError;switch(e.dataset.mdSource){case"github":return new i.a.Source.Adapter.GitHub(e).fetch();default:return r.a.resolve([])}}().then(function(t){var e=document.querySelectorAll("[data-md-source]");Array.prototype.forEach.call(e,function(e){new i.a.Source.Repository(e).initialize(t)})});var n=function(){var e=document.querySelectorAll("details");Array.prototype.forEach.call(e,function(e){e.setAttribute("open","")})};new i.a.Event.MatchMedia("print",{listen:n,unlisten:function(){}}),window.onbeforeprint=n}}}.call(this,c(3))},function(e,t,n){"use strict";n.p},function(e,t,n){"use strict";n.p},function(e,t,n){"use strict";n.p},function(e,t,n){"use strict"},function(e,t,n){"use strict"},function(e,t){!function(){if("undefined"!=typeof window)try{var e=new window.CustomEvent("test",{cancelable:!0});if(e.preventDefault(),!0!==e.defaultPrevented)throw new Error("Could not prevent default")}catch(e){var t=function(e,t){var n,r;return(t=t||{}).bubbles=!!t.bubbles,t.cancelable=!!t.cancelable,(n=document.createEvent("CustomEvent")).initCustomEvent(e,t.bubbles,t.cancelable,t.detail),r=n.preventDefault,n.preventDefault=function(){r.call(this);try{Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})}catch(e){this.defaultPrevented=!0}},n};t.prototype=window.Event.prototype,window.CustomEvent=t}}()},function(e,t,n){window.fetch||(window.fetch=n(7).default||n(7))},function(e,i,o){(function(e){var t=void 0!==e&&e||"undefined"!=typeof self&&self||window,n=Function.prototype.apply;function r(e,t){this._id=e,this._clearFn=t}i.setTimeout=function(){return new r(n.call(setTimeout,t,arguments),clearTimeout)},i.setInterval=function(){return new r(n.call(setInterval,t,arguments),clearInterval)},i.clearTimeout=i.clearInterval=function(e){e&&e.close()},r.prototype.unref=r.prototype.ref=function(){},r.prototype.close=function(){this._clearFn.call(t,this._id)},i.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},i.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},i._unrefActive=i.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;0<=t&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},o(22),i.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,i.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,o(4))},function(e,t,n){(function(e,p){!function(n,r){"use strict";if(!n.setImmediate){var i,o,t,a,e,s=1,c={},l=!1,u=n.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(n);f=f&&f.setTimeout?f:n,i="[object process]"==={}.toString.call(n.process)?function(e){p.nextTick(function(){h(e)})}:function(){if(n.postMessage&&!n.importScripts){var e=!0,t=n.onmessage;return n.onmessage=function(){e=!1},n.postMessage("","*"),n.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",e=function(e){e.source===n&&"string"==typeof e.data&&0===e.data.indexOf(a)&&h(+e.data.slice(a.length))},n.addEventListener?n.addEventListener("message",e,!1):n.attachEvent("onmessage",e),function(e){n.postMessage(a+e,"*")}):n.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){h(e.data)},function(e){t.port2.postMessage(e)}):u&&"onreadystatechange"in u.createElement("script")?(o=u.documentElement,function(e){var t=u.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):function(e){setTimeout(h,0,e)},f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n=this.length)return D.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},D.QueryLexer.prototype.width=function(){return this.pos-this.start},D.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},D.QueryLexer.prototype.backup=function(){this.pos-=1},D.QueryLexer.prototype.acceptDigitRun=function(){for(var e,t;47<(t=(e=this.next()).charCodeAt(0))&&t<58;);e!=D.QueryLexer.EOS&&this.backup()},D.QueryLexer.prototype.more=function(){return this.pos=t&&(e=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,c.find_among_b(o,4)?(c.bra=c.cursor,c.limit_backward=e,c.cursor=c.limit-r,c.cursor>c.limit_backward&&(c.cursor--,c.bra=c.cursor,c.slice_del())):c.limit_backward=e)}this.setCurrent=function(e){c.setCurrent(e)},this.getCurrent=function(){return c.getCurrent()},this.stem=function(){var e,r=c.cursor;return function(){var e,r=c.cursor+3;if(t=c.limit,0<=r&&r<=c.limit){for(i=r;;){if(e=c.cursor,c.in_grouping(d,97,248)){c.cursor=e;break}if((c.cursor=e)>=c.limit)return;c.cursor++}for(;!c.out_grouping(d,97,248);){if(c.cursor>=c.limit)return;c.cursor++}(t=c.cursor)=t&&(r=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,e=c.find_among_b(s,32),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del();break;case 2:c.in_grouping_b(u,97,229)&&c.slice_del()}}(),c.cursor=c.limit,l(),c.cursor=c.limit,function(){var e,r,i,n=c.limit-c.cursor;if(c.ket=c.cursor,c.eq_s_b(2,"st")&&(c.bra=c.cursor,c.eq_s_b(2,"ig")&&c.slice_del()),c.cursor=c.limit-n,c.cursor>=t&&(r=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,e=c.find_among_b(a,5),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del(),i=c.limit-c.cursor,l(),c.cursor=c.limit-i;break;case 2:c.slice_from("løs")}}(),c.cursor=c.limit,c.cursor>=t&&(e=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,c.out_grouping_b(d,97,248)?(c.bra=c.cursor,n=c.slice_to(n),c.limit_backward=e,c.eq_v_b(n)&&c.slice_del()):c.limit_backward=e),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.de.js b/docs/assets/javascripts/lunr/lunr.de.js deleted file mode 100644 index 73e55eb0..00000000 --- a/docs/assets/javascripts/lunr/lunr.de.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `German` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var _,p,r;e.de=function(){this.pipeline.reset(),this.pipeline.add(e.de.trimmer,e.de.stopWordFilter,e.de.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.de.stemmer))},e.de.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.de.trimmer=e.trimmerSupport.generateTrimmer(e.de.wordCharacters),e.Pipeline.registerFunction(e.de.trimmer,"trimmer-de"),e.de.stemmer=(_=e.stemmerSupport.Among,p=e.stemmerSupport.SnowballProgram,r=new function(){var r,n,i,s=[new _("",-1,6),new _("U",0,2),new _("Y",0,1),new _("ä",0,3),new _("ö",0,4),new _("ü",0,5)],o=[new _("e",-1,2),new _("em",-1,1),new _("en",-1,2),new _("ern",-1,1),new _("er",-1,1),new _("s",-1,3),new _("es",5,2)],c=[new _("en",-1,1),new _("er",-1,1),new _("st",-1,2),new _("est",2,1)],u=[new _("ig",-1,1),new _("lich",-1,1)],a=[new _("end",-1,1),new _("ig",-1,2),new _("ung",-1,1),new _("lich",-1,3),new _("isch",-1,2),new _("ik",-1,2),new _("heit",-1,3),new _("keit",-1,4)],t=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8],d=[117,30,5],l=[117,30,4],m=new p;function h(e,r,n){return!(!m.eq_s(1,e)||(m.ket=m.cursor,!m.in_grouping(t,97,252)))&&(m.slice_from(r),m.cursor=n,!0)}function w(){for(;!m.in_grouping(t,97,252);){if(m.cursor>=m.limit)return!0;m.cursor++}for(;!m.out_grouping(t,97,252);){if(m.cursor>=m.limit)return!0;m.cursor++}return!1}function f(){return i<=m.cursor}function b(){return n<=m.cursor}this.setCurrent=function(e){m.setCurrent(e)},this.getCurrent=function(){return m.getCurrent()},this.stem=function(){var e=m.cursor;return function(){for(var e,r,n,i,s=m.cursor;;)if(e=m.cursor,m.bra=e,m.eq_s(1,"ß"))m.ket=m.cursor,m.slice_from("ss");else{if(e>=m.limit)break;m.cursor=e+1}for(m.cursor=s;;)for(r=m.cursor;;){if(n=m.cursor,m.in_grouping(t,97,252)){if(i=m.cursor,m.bra=i,h("u","U",n))break;if(m.cursor=i,h("y","Y",n))break}if(n>=m.limit)return m.cursor=r;m.cursor=n+1}}(),m.cursor=e,function(){i=m.limit,n=i;var e=m.cursor+3;0<=e&&e<=m.limit&&(r=e,w()||((i=m.cursor)=m.limit)return;m.cursor++}}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.de.stemmer,"stemmer-de"),e.de.stopWordFilter=e.generateStopWordFilter("aber alle allem allen aller alles als also am an ander andere anderem anderen anderer anderes anderm andern anderr anders auch auf aus bei bin bis bist da damit dann das dasselbe dazu daß dein deine deinem deinen deiner deines dem demselben den denn denselben der derer derselbe derselben des desselben dessen dich die dies diese dieselbe dieselben diesem diesen dieser dieses dir doch dort du durch ein eine einem einen einer eines einig einige einigem einigen einiger einiges einmal er es etwas euch euer eure eurem euren eurer eures für gegen gewesen hab habe haben hat hatte hatten hier hin hinter ich ihm ihn ihnen ihr ihre ihrem ihren ihrer ihres im in indem ins ist jede jedem jeden jeder jedes jene jenem jenen jener jenes jetzt kann kein keine keinem keinen keiner keines können könnte machen man manche manchem manchen mancher manches mein meine meinem meinen meiner meines mich mir mit muss musste nach nicht nichts noch nun nur ob oder ohne sehr sein seine seinem seinen seiner seines selbst sich sie sind so solche solchem solchen solcher solches soll sollte sondern sonst um und uns unse unsem unsen unser unses unter viel vom von vor war waren warst was weg weil weiter welche welchem welchen welcher welches wenn werde werden wie wieder will wir wird wirst wo wollen wollte während würde würden zu zum zur zwar zwischen über".split(" ")),e.Pipeline.registerFunction(e.de.stopWordFilter,"stopWordFilter-de")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.du.js b/docs/assets/javascripts/lunr/lunr.du.js deleted file mode 100644 index e9c67299..00000000 --- a/docs/assets/javascripts/lunr/lunr.du.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Dutch` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var v,q,r;console.warn('[Lunr Languages] Please use the "nl" instead of the "du". The "nl" code is the standard code for Dutch language, and "du" will be removed in the next major versions.'),e.du=function(){this.pipeline.reset(),this.pipeline.add(e.du.trimmer,e.du.stopWordFilter,e.du.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.du.stemmer))},e.du.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.du.trimmer=e.trimmerSupport.generateTrimmer(e.du.wordCharacters),e.Pipeline.registerFunction(e.du.trimmer,"trimmer-du"),e.du.stemmer=(v=e.stemmerSupport.Among,q=e.stemmerSupport.SnowballProgram,r=new function(){var r,i,u,o=[new v("",-1,6),new v("á",0,1),new v("ä",0,1),new v("é",0,2),new v("ë",0,2),new v("í",0,3),new v("ï",0,3),new v("ó",0,4),new v("ö",0,4),new v("ú",0,5),new v("ü",0,5)],n=[new v("",-1,3),new v("I",0,2),new v("Y",0,1)],t=[new v("dd",-1,-1),new v("kk",-1,-1),new v("tt",-1,-1)],c=[new v("ene",-1,2),new v("se",-1,3),new v("en",-1,2),new v("heden",2,1),new v("s",-1,3)],a=[new v("end",-1,1),new v("ig",-1,2),new v("ing",-1,1),new v("lijk",-1,3),new v("baar",-1,4),new v("bar",-1,5)],l=[new v("aa",-1,-1),new v("ee",-1,-1),new v("oo",-1,-1),new v("uu",-1,-1)],m=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],d=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],f=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],_=new q;function s(e){return(_.cursor=e)>=_.limit||(_.cursor++,!1)}function w(){for(;!_.in_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}for(;!_.out_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}return!1}function b(){return i<=_.cursor}function p(){return r<=_.cursor}function g(){var e=_.limit-_.cursor;_.find_among_b(t,3)&&(_.cursor=_.limit-e,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del()))}function h(){var e;u=!1,_.ket=_.cursor,_.eq_s_b(1,"e")&&(_.bra=_.cursor,b()&&(e=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-e,_.slice_del(),u=!0,g())))}function k(){var e;b()&&(e=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-e,_.eq_s_b(3,"gem")||(_.cursor=_.limit-e,_.slice_del(),g())))}this.setCurrent=function(e){_.setCurrent(e)},this.getCurrent=function(){return _.getCurrent()},this.stem=function(){var e=_.cursor;return function(){for(var e,r,i,n=_.cursor;;){if(_.bra=_.cursor,e=_.find_among(o,11))switch(_.ket=_.cursor,e){case 1:_.slice_from("a");continue;case 2:_.slice_from("e");continue;case 3:_.slice_from("i");continue;case 4:_.slice_from("o");continue;case 5:_.slice_from("u");continue;case 6:if(_.cursor>=_.limit)break;_.cursor++;continue}break}for(_.cursor=n,_.bra=n,_.eq_s(1,"y")?(_.ket=_.cursor,_.slice_from("Y")):_.cursor=n;;)if(r=_.cursor,_.in_grouping(m,97,232)){if(i=_.cursor,_.bra=i,_.eq_s(1,"i"))_.ket=_.cursor,_.in_grouping(m,97,232)&&(_.slice_from("I"),_.cursor=r);else if(_.cursor=i,_.eq_s(1,"y"))_.ket=_.cursor,_.slice_from("Y"),_.cursor=r;else if(s(r))break}else if(s(r))break}(),_.cursor=e,i=_.limit,r=i,w()||((i=_.cursor)<3&&(i=3),w()||(r=_.cursor)),_.limit_backward=e,_.cursor=_.limit,function(){var e,r,i,n,o,t,s=_.limit-_.cursor;if(_.ket=_.cursor,e=_.find_among_b(c,5))switch(_.bra=_.cursor,e){case 1:b()&&_.slice_from("heid");break;case 2:k();break;case 3:b()&&_.out_grouping_b(f,97,232)&&_.slice_del()}if(_.cursor=_.limit-s,h(),_.cursor=_.limit-s,_.ket=_.cursor,_.eq_s_b(4,"heid")&&(_.bra=_.cursor,p()&&(r=_.limit-_.cursor,_.eq_s_b(1,"c")||(_.cursor=_.limit-r,_.slice_del(),_.ket=_.cursor,_.eq_s_b(2,"en")&&(_.bra=_.cursor,k())))),_.cursor=_.limit-s,_.ket=_.cursor,e=_.find_among_b(a,6))switch(_.bra=_.cursor,e){case 1:if(p()){if(_.slice_del(),i=_.limit-_.cursor,_.ket=_.cursor,_.eq_s_b(2,"ig")&&(_.bra=_.cursor,p()&&(n=_.limit-_.cursor,!_.eq_s_b(1,"e")))){_.cursor=_.limit-n,_.slice_del();break}_.cursor=_.limit-i,g()}break;case 2:p()&&(o=_.limit-_.cursor,_.eq_s_b(1,"e")||(_.cursor=_.limit-o,_.slice_del()));break;case 3:p()&&(_.slice_del(),h());break;case 4:p()&&_.slice_del();break;case 5:p()&&u&&_.slice_del()}_.cursor=_.limit-s,_.out_grouping_b(d,73,232)&&(t=_.limit-_.cursor,_.find_among_b(l,4)&&_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-t,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del())))}(),_.cursor=_.limit_backward,function(){for(var e;;)if(_.bra=_.cursor,e=_.find_among(n,3))switch(_.ket=_.cursor,e){case 1:_.slice_from("y");break;case 2:_.slice_from("i");break;case 3:if(_.cursor>=_.limit)return;_.cursor++}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.du.stemmer,"stemmer-du"),e.du.stopWordFilter=e.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),e.Pipeline.registerFunction(e.du.stopWordFilter,"stopWordFilter-du")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.es.js b/docs/assets/javascripts/lunr/lunr.es.js deleted file mode 100644 index 2918bd19..00000000 --- a/docs/assets/javascripts/lunr/lunr.es.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Spanish` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,s){"function"==typeof define&&define.amd?define(s):"object"==typeof exports?module.exports=s():s()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var C,P,s;e.es=function(){this.pipeline.reset(),this.pipeline.add(e.es.trimmer,e.es.stopWordFilter,e.es.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.es.stemmer))},e.es.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.es.trimmer=e.trimmerSupport.generateTrimmer(e.es.wordCharacters),e.Pipeline.registerFunction(e.es.trimmer,"trimmer-es"),e.es.stemmer=(C=e.stemmerSupport.Among,P=e.stemmerSupport.SnowballProgram,s=new function(){var r,n,i,a=[new C("",-1,6),new C("á",0,1),new C("é",0,2),new C("í",0,3),new C("ó",0,4),new C("ú",0,5)],t=[new C("la",-1,-1),new C("sela",0,-1),new C("le",-1,-1),new C("me",-1,-1),new C("se",-1,-1),new C("lo",-1,-1),new C("selo",5,-1),new C("las",-1,-1),new C("selas",7,-1),new C("les",-1,-1),new C("los",-1,-1),new C("selos",10,-1),new C("nos",-1,-1)],o=[new C("ando",-1,6),new C("iendo",-1,6),new C("yendo",-1,7),new C("ándo",-1,2),new C("iéndo",-1,1),new C("ar",-1,6),new C("er",-1,6),new C("ir",-1,6),new C("ár",-1,3),new C("ér",-1,4),new C("ír",-1,5)],s=[new C("ic",-1,-1),new C("ad",-1,-1),new C("os",-1,-1),new C("iv",-1,1)],u=[new C("able",-1,1),new C("ible",-1,1),new C("ante",-1,1)],w=[new C("ic",-1,1),new C("abil",-1,1),new C("iv",-1,1)],c=[new C("ica",-1,1),new C("ancia",-1,2),new C("encia",-1,5),new C("adora",-1,2),new C("osa",-1,1),new C("ista",-1,1),new C("iva",-1,9),new C("anza",-1,1),new C("logía",-1,3),new C("idad",-1,8),new C("able",-1,1),new C("ible",-1,1),new C("ante",-1,2),new C("mente",-1,7),new C("amente",13,6),new C("ación",-1,2),new C("ución",-1,4),new C("ico",-1,1),new C("ismo",-1,1),new C("oso",-1,1),new C("amiento",-1,1),new C("imiento",-1,1),new C("ivo",-1,9),new C("ador",-1,2),new C("icas",-1,1),new C("ancias",-1,2),new C("encias",-1,5),new C("adoras",-1,2),new C("osas",-1,1),new C("istas",-1,1),new C("ivas",-1,9),new C("anzas",-1,1),new C("logías",-1,3),new C("idades",-1,8),new C("ables",-1,1),new C("ibles",-1,1),new C("aciones",-1,2),new C("uciones",-1,4),new C("adores",-1,2),new C("antes",-1,2),new C("icos",-1,1),new C("ismos",-1,1),new C("osos",-1,1),new C("amientos",-1,1),new C("imientos",-1,1),new C("ivos",-1,9)],m=[new C("ya",-1,1),new C("ye",-1,1),new C("yan",-1,1),new C("yen",-1,1),new C("yeron",-1,1),new C("yendo",-1,1),new C("yo",-1,1),new C("yas",-1,1),new C("yes",-1,1),new C("yais",-1,1),new C("yamos",-1,1),new C("yó",-1,1)],l=[new C("aba",-1,2),new C("ada",-1,2),new C("ida",-1,2),new C("ara",-1,2),new C("iera",-1,2),new C("ía",-1,2),new C("aría",5,2),new C("ería",5,2),new C("iría",5,2),new C("ad",-1,2),new C("ed",-1,2),new C("id",-1,2),new C("ase",-1,2),new C("iese",-1,2),new C("aste",-1,2),new C("iste",-1,2),new C("an",-1,2),new C("aban",16,2),new C("aran",16,2),new C("ieran",16,2),new C("ían",16,2),new C("arían",20,2),new C("erían",20,2),new C("irían",20,2),new C("en",-1,1),new C("asen",24,2),new C("iesen",24,2),new C("aron",-1,2),new C("ieron",-1,2),new C("arán",-1,2),new C("erán",-1,2),new C("irán",-1,2),new C("ado",-1,2),new C("ido",-1,2),new C("ando",-1,2),new C("iendo",-1,2),new C("ar",-1,2),new C("er",-1,2),new C("ir",-1,2),new C("as",-1,2),new C("abas",39,2),new C("adas",39,2),new C("idas",39,2),new C("aras",39,2),new C("ieras",39,2),new C("ías",39,2),new C("arías",45,2),new C("erías",45,2),new C("irías",45,2),new C("es",-1,1),new C("ases",49,2),new C("ieses",49,2),new C("abais",-1,2),new C("arais",-1,2),new C("ierais",-1,2),new C("íais",-1,2),new C("aríais",55,2),new C("eríais",55,2),new C("iríais",55,2),new C("aseis",-1,2),new C("ieseis",-1,2),new C("asteis",-1,2),new C("isteis",-1,2),new C("áis",-1,2),new C("éis",-1,1),new C("aréis",64,2),new C("eréis",64,2),new C("iréis",64,2),new C("ados",-1,2),new C("idos",-1,2),new C("amos",-1,2),new C("ábamos",70,2),new C("áramos",70,2),new C("iéramos",70,2),new C("íamos",70,2),new C("aríamos",74,2),new C("eríamos",74,2),new C("iríamos",74,2),new C("emos",-1,1),new C("aremos",78,2),new C("eremos",78,2),new C("iremos",78,2),new C("ásemos",78,2),new C("iésemos",78,2),new C("imos",-1,2),new C("arás",-1,2),new C("erás",-1,2),new C("irás",-1,2),new C("ís",-1,2),new C("ará",-1,2),new C("erá",-1,2),new C("irá",-1,2),new C("aré",-1,2),new C("eré",-1,2),new C("iré",-1,2),new C("ió",-1,2)],d=[new C("a",-1,1),new C("e",-1,2),new C("o",-1,1),new C("os",-1,1),new C("á",-1,1),new C("é",-1,2),new C("í",-1,1),new C("ó",-1,1)],b=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,4,10],f=new P;function _(){if(f.out_grouping(b,97,252)){for(;!f.in_grouping(b,97,252);){if(f.cursor>=f.limit)return!0;f.cursor++}return!1}return!0}function h(){var e,s=f.cursor;if(function(){if(f.in_grouping(b,97,252)){var e=f.cursor;if(_()){if(f.cursor=e,!f.in_grouping(b,97,252))return!0;for(;!f.out_grouping(b,97,252);){if(f.cursor>=f.limit)return!0;f.cursor++}}return!1}return!0}()){if(f.cursor=s,!f.out_grouping(b,97,252))return;if(e=f.cursor,_()){if(f.cursor=e,!f.in_grouping(b,97,252)||f.cursor>=f.limit)return;f.cursor++}}i=f.cursor}function v(){for(;!f.in_grouping(b,97,252);){if(f.cursor>=f.limit)return!1;f.cursor++}for(;!f.out_grouping(b,97,252);){if(f.cursor>=f.limit)return!1;f.cursor++}return!0}function p(){return i<=f.cursor}function g(){return r<=f.cursor}function k(e,s){if(!g())return!0;f.slice_del(),f.ket=f.cursor;var r=f.find_among_b(e,s);return r&&(f.bra=f.cursor,1==r&&g()&&f.slice_del()),!1}function y(e){return!g()||(f.slice_del(),f.ket=f.cursor,f.eq_s_b(2,e)&&(f.bra=f.cursor,g()&&f.slice_del()),!1)}function q(){var e;if(f.ket=f.cursor,e=f.find_among_b(c,46)){switch(f.bra=f.cursor,e){case 1:if(!g())return!1;f.slice_del();break;case 2:if(y("ic"))return!1;break;case 3:if(!g())return!1;f.slice_from("log");break;case 4:if(!g())return!1;f.slice_from("u");break;case 5:if(!g())return!1;f.slice_from("ente");break;case 6:if(!(n<=f.cursor))return!1;f.slice_del(),f.ket=f.cursor,(e=f.find_among_b(s,4))&&(f.bra=f.cursor,g()&&(f.slice_del(),1==e&&(f.ket=f.cursor,f.eq_s_b(2,"at")&&(f.bra=f.cursor,g()&&f.slice_del()))));break;case 7:if(k(u,3))return!1;break;case 8:if(k(w,3))return!1;break;case 9:if(y("at"))return!1}return!0}return!1}this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var e,s=f.cursor;return e=f.cursor,i=f.limit,r=n=i,h(),f.cursor=e,v()&&(n=f.cursor,v()&&(r=f.cursor)),f.limit_backward=s,f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,f.find_among_b(t,13)&&(f.bra=f.cursor,(e=f.find_among_b(o,11))&&p()))switch(e){case 1:f.bra=f.cursor,f.slice_from("iendo");break;case 2:f.bra=f.cursor,f.slice_from("ando");break;case 3:f.bra=f.cursor,f.slice_from("ar");break;case 4:f.bra=f.cursor,f.slice_from("er");break;case 5:f.bra=f.cursor,f.slice_from("ir");break;case 6:f.slice_del();break;case 7:f.eq_s_b(1,"u")&&f.slice_del()}}(),f.cursor=f.limit,q()||(f.cursor=f.limit,function(){var e,s;if(f.cursor>=i&&(s=f.limit_backward,f.limit_backward=i,f.ket=f.cursor,e=f.find_among_b(m,12),f.limit_backward=s,e)){if(f.bra=f.cursor,1==e){if(!f.eq_s_b(1,"u"))return!1;f.slice_del()}return!0}return!1}()||(f.cursor=f.limit,function(){var e,s,r,n;if(f.cursor>=i&&(s=f.limit_backward,f.limit_backward=i,f.ket=f.cursor,e=f.find_among_b(l,96),f.limit_backward=s,e))switch(f.bra=f.cursor,e){case 1:r=f.limit-f.cursor,f.eq_s_b(1,"u")?(n=f.limit-f.cursor,f.eq_s_b(1,"g")?f.cursor=f.limit-n:f.cursor=f.limit-r):f.cursor=f.limit-r,f.bra=f.cursor;case 2:f.slice_del()}}())),f.cursor=f.limit,function(){var e,s;if(f.ket=f.cursor,e=f.find_among_b(d,8))switch(f.bra=f.cursor,e){case 1:p()&&f.slice_del();break;case 2:p()&&(f.slice_del(),f.ket=f.cursor,f.eq_s_b(1,"u")&&(f.bra=f.cursor,s=f.limit-f.cursor,f.eq_s_b(1,"g")&&(f.cursor=f.limit-s,p()&&f.slice_del())))}}(),f.cursor=f.limit_backward,function(){for(var e;;){if(f.bra=f.cursor,e=f.find_among(a,6))switch(f.ket=f.cursor,e){case 1:f.slice_from("a");continue;case 2:f.slice_from("e");continue;case 3:f.slice_from("i");continue;case 4:f.slice_from("o");continue;case 5:f.slice_from("u");continue;case 6:if(f.cursor>=f.limit)break;f.cursor++;continue}break}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return s.setCurrent(e),s.stem(),s.getCurrent()}):(s.setCurrent(e),s.stem(),s.getCurrent())}),e.Pipeline.registerFunction(e.es.stemmer,"stemmer-es"),e.es.stopWordFilter=e.generateStopWordFilter("a al algo algunas algunos ante antes como con contra cual cuando de del desde donde durante e el ella ellas ellos en entre era erais eran eras eres es esa esas ese eso esos esta estaba estabais estaban estabas estad estada estadas estado estados estamos estando estar estaremos estará estarán estarás estaré estaréis estaría estaríais estaríamos estarían estarías estas este estemos esto estos estoy estuve estuviera estuvierais estuvieran estuvieras estuvieron estuviese estuvieseis estuviesen estuvieses estuvimos estuviste estuvisteis estuviéramos estuviésemos estuvo está estábamos estáis están estás esté estéis estén estés fue fuera fuerais fueran fueras fueron fuese fueseis fuesen fueses fui fuimos fuiste fuisteis fuéramos fuésemos ha habida habidas habido habidos habiendo habremos habrá habrán habrás habré habréis habría habríais habríamos habrían habrías habéis había habíais habíamos habían habías han has hasta hay haya hayamos hayan hayas hayáis he hemos hube hubiera hubierais hubieran hubieras hubieron hubiese hubieseis hubiesen hubieses hubimos hubiste hubisteis hubiéramos hubiésemos hubo la las le les lo los me mi mis mucho muchos muy más mí mía mías mío míos nada ni no nos nosotras nosotros nuestra nuestras nuestro nuestros o os otra otras otro otros para pero poco por porque que quien quienes qué se sea seamos sean seas seremos será serán serás seré seréis sería seríais seríamos serían serías seáis sido siendo sin sobre sois somos son soy su sus suya suyas suyo suyos sí también tanto te tendremos tendrá tendrán tendrás tendré tendréis tendría tendríais tendríamos tendrían tendrías tened tenemos tenga tengamos tengan tengas tengo tengáis tenida tenidas tenido tenidos teniendo tenéis tenía teníais teníamos tenían tenías ti tiene tienen tienes todo todos tu tus tuve tuviera tuvierais tuvieran tuvieras tuvieron tuviese tuvieseis tuviesen tuvieses tuvimos tuviste tuvisteis tuviéramos tuviésemos tuvo tuya tuyas tuyo tuyos tú un una uno unos vosotras vosotros vuestra vuestras vuestro vuestros y ya yo él éramos".split(" ")),e.Pipeline.registerFunction(e.es.stopWordFilter,"stopWordFilter-es")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.fi.js b/docs/assets/javascripts/lunr/lunr.fi.js deleted file mode 100644 index f34d10e0..00000000 --- a/docs/assets/javascripts/lunr/lunr.fi.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Finnish` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(i,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():e()(i.lunr)}(this,function(){return function(i){if(void 0===i)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===i.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var v,C,e;i.fi=function(){this.pipeline.reset(),this.pipeline.add(i.fi.trimmer,i.fi.stopWordFilter,i.fi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(i.fi.stemmer))},i.fi.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",i.fi.trimmer=i.trimmerSupport.generateTrimmer(i.fi.wordCharacters),i.Pipeline.registerFunction(i.fi.trimmer,"trimmer-fi"),i.fi.stemmer=(v=i.stemmerSupport.Among,C=i.stemmerSupport.SnowballProgram,e=new function(){var n,t,l,o,r=[new v("pa",-1,1),new v("sti",-1,2),new v("kaan",-1,1),new v("han",-1,1),new v("kin",-1,1),new v("hän",-1,1),new v("kään",-1,1),new v("ko",-1,1),new v("pä",-1,1),new v("kö",-1,1)],s=[new v("lla",-1,-1),new v("na",-1,-1),new v("ssa",-1,-1),new v("ta",-1,-1),new v("lta",3,-1),new v("sta",3,-1)],a=[new v("llä",-1,-1),new v("nä",-1,-1),new v("ssä",-1,-1),new v("tä",-1,-1),new v("ltä",3,-1),new v("stä",3,-1)],u=[new v("lle",-1,-1),new v("ine",-1,-1)],c=[new v("nsa",-1,3),new v("mme",-1,3),new v("nne",-1,3),new v("ni",-1,2),new v("si",-1,1),new v("an",-1,4),new v("en",-1,6),new v("än",-1,5),new v("nsä",-1,3)],i=[new v("aa",-1,-1),new v("ee",-1,-1),new v("ii",-1,-1),new v("oo",-1,-1),new v("uu",-1,-1),new v("ää",-1,-1),new v("öö",-1,-1)],m=[new v("a",-1,8),new v("lla",0,-1),new v("na",0,-1),new v("ssa",0,-1),new v("ta",0,-1),new v("lta",4,-1),new v("sta",4,-1),new v("tta",4,9),new v("lle",-1,-1),new v("ine",-1,-1),new v("ksi",-1,-1),new v("n",-1,7),new v("han",11,1),new v("den",11,-1,q),new v("seen",11,-1,j),new v("hen",11,2),new v("tten",11,-1,q),new v("hin",11,3),new v("siin",11,-1,q),new v("hon",11,4),new v("hän",11,5),new v("hön",11,6),new v("ä",-1,8),new v("llä",22,-1),new v("nä",22,-1),new v("ssä",22,-1),new v("tä",22,-1),new v("ltä",26,-1),new v("stä",26,-1),new v("ttä",26,9)],w=[new v("eja",-1,-1),new v("mma",-1,1),new v("imma",1,-1),new v("mpa",-1,1),new v("impa",3,-1),new v("mmi",-1,1),new v("immi",5,-1),new v("mpi",-1,1),new v("impi",7,-1),new v("ejä",-1,-1),new v("mmä",-1,1),new v("immä",10,-1),new v("mpä",-1,1),new v("impä",12,-1)],_=[new v("i",-1,-1),new v("j",-1,-1)],k=[new v("mma",-1,1),new v("imma",0,-1)],b=[17,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],e=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],f=[17,97,24,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],h=new C;function p(){for(var i;i=h.cursor,!h.in_grouping(d,97,246);){if((h.cursor=i)>=h.limit)return!0;h.cursor++}for(h.cursor=i;!h.out_grouping(d,97,246);){if(h.cursor>=h.limit)return!0;h.cursor++}return!1}function g(){var i,e;if(h.cursor>=o)if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,i=h.find_among_b(r,10)){switch(h.bra=h.cursor,h.limit_backward=e,i){case 1:if(!h.in_grouping_b(f,97,246))return;break;case 2:if(!(l<=h.cursor))return}h.slice_del()}else h.limit_backward=e}function j(){return h.find_among_b(i,7)}function q(){return h.eq_s_b(1,"i")&&h.in_grouping_b(e,97,246)}this.setCurrent=function(i){h.setCurrent(i)},this.getCurrent=function(){return h.getCurrent()},this.stem=function(){var i,e=h.cursor;return o=h.limit,l=o,p()||(o=h.cursor,p()||(l=h.cursor)),n=!1,h.limit_backward=e,h.cursor=h.limit,g(),h.cursor=h.limit,function(){var i,e,r;if(h.cursor>=o)if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,i=h.find_among_b(c,9))switch(h.bra=h.cursor,h.limit_backward=e,i){case 1:r=h.limit-h.cursor,h.eq_s_b(1,"k")||(h.cursor=h.limit-r,h.slice_del());break;case 2:h.slice_del(),h.ket=h.cursor,h.eq_s_b(3,"kse")&&(h.bra=h.cursor,h.slice_from("ksi"));break;case 3:h.slice_del();break;case 4:h.find_among_b(s,6)&&h.slice_del();break;case 5:h.find_among_b(a,6)&&h.slice_del();break;case 6:h.find_among_b(u,2)&&h.slice_del()}else h.limit_backward=e}(),h.cursor=h.limit,function(){var i,e,r;if(h.cursor>=o)if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,i=h.find_among_b(m,30)){switch(h.bra=h.cursor,h.limit_backward=e,i){case 1:if(!h.eq_s_b(1,"a"))return;break;case 2:case 9:if(!h.eq_s_b(1,"e"))return;break;case 3:if(!h.eq_s_b(1,"i"))return;break;case 4:if(!h.eq_s_b(1,"o"))return;break;case 5:if(!h.eq_s_b(1,"ä"))return;break;case 6:if(!h.eq_s_b(1,"ö"))return;break;case 7:if(r=h.limit-h.cursor,!j()&&(h.cursor=h.limit-r,!h.eq_s_b(2,"ie"))){h.cursor=h.limit-r;break}if(h.cursor=h.limit-r,h.cursor<=h.limit_backward){h.cursor=h.limit-r;break}h.cursor--,h.bra=h.cursor;break;case 8:if(!h.in_grouping_b(d,97,246)||!h.out_grouping_b(d,97,246))return}h.slice_del(),n=!0}else h.limit_backward=e}(),h.cursor=h.limit,function(){var i,e,r;if(h.cursor>=l)if(e=h.limit_backward,h.limit_backward=l,h.ket=h.cursor,i=h.find_among_b(w,14)){if(h.bra=h.cursor,h.limit_backward=e,1==i){if(r=h.limit-h.cursor,h.eq_s_b(2,"po"))return;h.cursor=h.limit-r}h.slice_del()}else h.limit_backward=e}(),h.cursor=h.limit,h.cursor=(n?h.cursor>=o&&(i=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,h.find_among_b(_,2)?(h.bra=h.cursor,h.limit_backward=i,h.slice_del()):h.limit_backward=i):(h.cursor=h.limit,function(){var i,e,r,n,t,s;if(h.cursor>=o){if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,h.eq_s_b(1,"t")&&(h.bra=h.cursor,r=h.limit-h.cursor,h.in_grouping_b(d,97,246)&&(h.cursor=h.limit-r,h.slice_del(),h.limit_backward=e,n=h.limit-h.cursor,h.cursor>=l&&(h.cursor=l,t=h.limit_backward,h.limit_backward=h.cursor,h.cursor=h.limit-n,h.ket=h.cursor,i=h.find_among_b(k,2))))){if(h.bra=h.cursor,h.limit_backward=t,1==i){if(s=h.limit-h.cursor,h.eq_s_b(2,"po"))return;h.cursor=h.limit-s}return h.slice_del()}h.limit_backward=e}}()),h.limit),function(){var i,e,r,n;if(h.cursor>=o){for(i=h.limit_backward,h.limit_backward=o,e=h.limit-h.cursor,j()&&(h.cursor=h.limit-e,h.ket=h.cursor,h.cursor>h.limit_backward&&(h.cursor--,h.bra=h.cursor,h.slice_del())),h.cursor=h.limit-e,h.ket=h.cursor,h.in_grouping_b(b,97,228)&&(h.bra=h.cursor,h.out_grouping_b(d,97,246)&&h.slice_del()),h.cursor=h.limit-e,h.ket=h.cursor,h.eq_s_b(1,"j")&&(h.bra=h.cursor,r=h.limit-h.cursor,h.eq_s_b(1,"o")?h.slice_del():(h.cursor=h.limit-r,h.eq_s_b(1,"u")&&h.slice_del())),h.cursor=h.limit-e,h.ket=h.cursor,h.eq_s_b(1,"o")&&(h.bra=h.cursor,h.eq_s_b(1,"j")&&h.slice_del()),h.cursor=h.limit-e,h.limit_backward=i;;){if(n=h.limit-h.cursor,h.out_grouping_b(d,97,246)){h.cursor=h.limit-n;break}if(h.cursor=h.limit-n,h.cursor<=h.limit_backward)return;h.cursor--}h.ket=h.cursor,h.cursor>h.limit_backward&&(h.cursor--,h.bra=h.cursor,t=h.slice_to(),h.eq_v_b(t)&&h.slice_del())}}(),!0}},function(i){return"function"==typeof i.update?i.update(function(i){return e.setCurrent(i),e.stem(),e.getCurrent()}):(e.setCurrent(i),e.stem(),e.getCurrent())}),i.Pipeline.registerFunction(i.fi.stemmer,"stemmer-fi"),i.fi.stopWordFilter=i.generateStopWordFilter("ei eivät emme en et ette että he heidän heidät heihin heille heillä heiltä heissä heistä heitä hän häneen hänelle hänellä häneltä hänen hänessä hänestä hänet häntä itse ja johon joiden joihin joiksi joilla joille joilta joina joissa joista joita joka joksi jolla jolle jolta jona jonka jos jossa josta jota jotka kanssa keiden keihin keiksi keille keillä keiltä keinä keissä keistä keitä keneen keneksi kenelle kenellä keneltä kenen kenenä kenessä kenestä kenet ketkä ketkä ketä koska kuin kuka kun me meidän meidät meihin meille meillä meiltä meissä meistä meitä mihin miksi mikä mille millä miltä minkä minkä minua minulla minulle minulta minun minussa minusta minut minuun minä minä missä mistä mitkä mitä mukaan mutta ne niiden niihin niiksi niille niillä niiltä niin niin niinä niissä niistä niitä noiden noihin noiksi noilla noille noilta noin noina noissa noista noita nuo nyt näiden näihin näiksi näille näillä näiltä näinä näissä näistä näitä nämä ole olemme olen olet olette oli olimme olin olisi olisimme olisin olisit olisitte olisivat olit olitte olivat olla olleet ollut on ovat poikki se sekä sen siihen siinä siitä siksi sille sillä sillä siltä sinua sinulla sinulle sinulta sinun sinussa sinusta sinut sinuun sinä sinä sitä tai te teidän teidät teihin teille teillä teiltä teissä teistä teitä tuo tuohon tuoksi tuolla tuolle tuolta tuon tuona tuossa tuosta tuota tähän täksi tälle tällä tältä tämä tämän tänä tässä tästä tätä vaan vai vaikka yli".split(" ")),i.Pipeline.registerFunction(i.fi.stopWordFilter,"stopWordFilter-fi")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.fr.js b/docs/assets/javascripts/lunr/lunr.fr.js deleted file mode 100644 index d043ec65..00000000 --- a/docs/assets/javascripts/lunr/lunr.fr.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `French` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,y,s;e.fr=function(){this.pipeline.reset(),this.pipeline.add(e.fr.trimmer,e.fr.stopWordFilter,e.fr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.fr.stemmer))},e.fr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.fr.trimmer=e.trimmerSupport.generateTrimmer(e.fr.wordCharacters),e.Pipeline.registerFunction(e.fr.trimmer,"trimmer-fr"),e.fr.stemmer=(r=e.stemmerSupport.Among,y=e.stemmerSupport.SnowballProgram,s=new function(){var s,i,t,n=[new r("col",-1,-1),new r("par",-1,-1),new r("tap",-1,-1)],u=[new r("",-1,4),new r("I",0,1),new r("U",0,2),new r("Y",0,3)],o=[new r("iqU",-1,3),new r("abl",-1,3),new r("Ièr",-1,4),new r("ièr",-1,4),new r("eus",-1,2),new r("iv",-1,1)],c=[new r("ic",-1,2),new r("abil",-1,1),new r("iv",-1,3)],a=[new r("iqUe",-1,1),new r("atrice",-1,2),new r("ance",-1,1),new r("ence",-1,5),new r("logie",-1,3),new r("able",-1,1),new r("isme",-1,1),new r("euse",-1,11),new r("iste",-1,1),new r("ive",-1,8),new r("if",-1,8),new r("usion",-1,4),new r("ation",-1,2),new r("ution",-1,4),new r("ateur",-1,2),new r("iqUes",-1,1),new r("atrices",-1,2),new r("ances",-1,1),new r("ences",-1,5),new r("logies",-1,3),new r("ables",-1,1),new r("ismes",-1,1),new r("euses",-1,11),new r("istes",-1,1),new r("ives",-1,8),new r("ifs",-1,8),new r("usions",-1,4),new r("ations",-1,2),new r("utions",-1,4),new r("ateurs",-1,2),new r("ments",-1,15),new r("ements",30,6),new r("issements",31,12),new r("ités",-1,7),new r("ment",-1,15),new r("ement",34,6),new r("issement",35,12),new r("amment",34,13),new r("emment",34,14),new r("aux",-1,10),new r("eaux",39,9),new r("eux",-1,1),new r("ité",-1,7)],l=[new r("ira",-1,1),new r("ie",-1,1),new r("isse",-1,1),new r("issante",-1,1),new r("i",-1,1),new r("irai",4,1),new r("ir",-1,1),new r("iras",-1,1),new r("ies",-1,1),new r("îmes",-1,1),new r("isses",-1,1),new r("issantes",-1,1),new r("îtes",-1,1),new r("is",-1,1),new r("irais",13,1),new r("issais",13,1),new r("irions",-1,1),new r("issions",-1,1),new r("irons",-1,1),new r("issons",-1,1),new r("issants",-1,1),new r("it",-1,1),new r("irait",21,1),new r("issait",21,1),new r("issant",-1,1),new r("iraIent",-1,1),new r("issaIent",-1,1),new r("irent",-1,1),new r("issent",-1,1),new r("iront",-1,1),new r("ît",-1,1),new r("iriez",-1,1),new r("issiez",-1,1),new r("irez",-1,1),new r("issez",-1,1)],w=[new r("a",-1,3),new r("era",0,2),new r("asse",-1,3),new r("ante",-1,3),new r("ée",-1,2),new r("ai",-1,3),new r("erai",5,2),new r("er",-1,2),new r("as",-1,3),new r("eras",8,2),new r("âmes",-1,3),new r("asses",-1,3),new r("antes",-1,3),new r("âtes",-1,3),new r("ées",-1,2),new r("ais",-1,3),new r("erais",15,2),new r("ions",-1,1),new r("erions",17,2),new r("assions",17,3),new r("erons",-1,2),new r("ants",-1,3),new r("és",-1,2),new r("ait",-1,3),new r("erait",23,2),new r("ant",-1,3),new r("aIent",-1,3),new r("eraIent",26,2),new r("èrent",-1,2),new r("assent",-1,3),new r("eront",-1,2),new r("ât",-1,3),new r("ez",-1,2),new r("iez",32,2),new r("eriez",33,2),new r("assiez",33,3),new r("erez",32,2),new r("é",-1,2)],f=[new r("e",-1,3),new r("Ière",0,2),new r("ière",0,2),new r("ion",-1,1),new r("Ier",-1,2),new r("ier",-1,2),new r("ë",-1,4)],m=[new r("ell",-1,-1),new r("eill",-1,-1),new r("enn",-1,-1),new r("onn",-1,-1),new r("ett",-1,-1)],_=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,128,130,103,8,5],b=[1,65,20,0,0,0,0,0,0,0,0,0,0,0,0,0,128],d=new y;function k(e,r,s){return!(!d.eq_s(1,e)||(d.ket=d.cursor,!d.in_grouping(_,97,251)))&&(d.slice_from(r),d.cursor=s,!0)}function p(e,r,s){return!!d.eq_s(1,e)&&(d.ket=d.cursor,d.slice_from(r),d.cursor=s,!0)}function g(){for(;!d.in_grouping(_,97,251);){if(d.cursor>=d.limit)return!0;d.cursor++}for(;!d.out_grouping(_,97,251);){if(d.cursor>=d.limit)return!0;d.cursor++}return!1}function q(){return t<=d.cursor}function v(){return i<=d.cursor}function h(){return s<=d.cursor}function z(){if(!function(){var e,r;if(d.ket=d.cursor,e=d.find_among_b(a,43)){switch(d.bra=d.cursor,e){case 1:if(!h())return!1;d.slice_del();break;case 2:if(!h())return!1;d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"ic")&&(d.bra=d.cursor,h()?d.slice_del():d.slice_from("iqU"));break;case 3:if(!h())return!1;d.slice_from("log");break;case 4:if(!h())return!1;d.slice_from("u");break;case 5:if(!h())return!1;d.slice_from("ent");break;case 6:if(!q())return!1;if(d.slice_del(),d.ket=d.cursor,e=d.find_among_b(o,6))switch(d.bra=d.cursor,e){case 1:h()&&(d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,h()&&d.slice_del()));break;case 2:h()?d.slice_del():v()&&d.slice_from("eux");break;case 3:h()&&d.slice_del();break;case 4:q()&&d.slice_from("i")}break;case 7:if(!h())return!1;if(d.slice_del(),d.ket=d.cursor,e=d.find_among_b(c,3))switch(d.bra=d.cursor,e){case 1:h()?d.slice_del():d.slice_from("abl");break;case 2:h()?d.slice_del():d.slice_from("iqU");break;case 3:h()&&d.slice_del()}break;case 8:if(!h())return!1;if(d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,h()&&(d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"ic")))){d.bra=d.cursor,h()?d.slice_del():d.slice_from("iqU");break}break;case 9:d.slice_from("eau");break;case 10:if(!v())return!1;d.slice_from("al");break;case 11:if(h())d.slice_del();else{if(!v())return!1;d.slice_from("eux")}break;case 12:if(!v()||!d.out_grouping_b(_,97,251))return!1;d.slice_del();break;case 13:return q()&&d.slice_from("ant"),!1;case 14:return q()&&d.slice_from("ent"),!1;case 15:return r=d.limit-d.cursor,d.in_grouping_b(_,97,251)&&q()&&(d.cursor=d.limit-r,d.slice_del()),!1}return!0}return!1}()&&(d.cursor=d.limit,!function(){var e,r;if(d.cursor=t){if(s=d.limit_backward,d.limit_backward=t,d.ket=d.cursor,e=d.find_among_b(f,7))switch(d.bra=d.cursor,e){case 1:if(h()){if(i=d.limit-d.cursor,!d.eq_s_b(1,"s")&&(d.cursor=d.limit-i,!d.eq_s_b(1,"t")))break;d.slice_del()}break;case 2:d.slice_from("i");break;case 3:d.slice_del();break;case 4:d.eq_s_b(2,"gu")&&d.slice_del()}d.limit_backward=s}}();d.cursor=d.limit,d.ket=d.cursor,d.eq_s_b(1,"Y")?(d.bra=d.cursor,d.slice_from("i")):(d.cursor=d.limit,d.eq_s_b(1,"ç")&&(d.bra=d.cursor,d.slice_from("c")))}this.setCurrent=function(e){d.setCurrent(e)},this.getCurrent=function(){return d.getCurrent()},this.stem=function(){var e,r=d.cursor;return function(){for(var e,r;;){if(e=d.cursor,d.in_grouping(_,97,251)){if(d.bra=d.cursor,r=d.cursor,k("u","U",e))continue;if(d.cursor=r,k("i","I",e))continue;if(d.cursor=r,p("y","Y",e))continue}if(d.cursor=e,!k("y","Y",d.bra=e)){if(d.cursor=e,d.eq_s(1,"q")&&(d.bra=d.cursor,p("u","U",e)))continue;if((d.cursor=e)>=d.limit)return;d.cursor++}}}(),d.cursor=r,function(){var e=d.cursor;if(t=d.limit,s=i=t,d.in_grouping(_,97,251)&&d.in_grouping(_,97,251)&&d.cursor=d.limit){d.cursor=t;break}d.cursor++}while(!d.in_grouping(_,97,251))}t=d.cursor,d.cursor=e,g()||(i=d.cursor,g()||(s=d.cursor))}(),d.limit_backward=r,d.cursor=d.limit,z(),d.cursor=d.limit,e=d.limit-d.cursor,d.find_among_b(m,5)&&(d.cursor=d.limit-e,d.ket=d.cursor,d.cursor>d.limit_backward&&(d.cursor--,d.bra=d.cursor,d.slice_del())),d.cursor=d.limit,function(){for(var e,r=1;d.out_grouping_b(_,97,251);)r--;if(r<=0){if(d.ket=d.cursor,e=d.limit-d.cursor,!d.eq_s_b(1,"é")&&(d.cursor=d.limit-e,!d.eq_s_b(1,"è")))return;d.bra=d.cursor,d.slice_from("e")}}(),d.cursor=d.limit_backward,function(){for(var e,r;r=d.cursor,d.bra=r,e=d.find_among(u,4);)switch(d.ket=d.cursor,e){case 1:d.slice_from("i");break;case 2:d.slice_from("u");break;case 3:d.slice_from("y");break;case 4:if(d.cursor>=d.limit)return;d.cursor++}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return s.setCurrent(e),s.stem(),s.getCurrent()}):(s.setCurrent(e),s.stem(),s.getCurrent())}),e.Pipeline.registerFunction(e.fr.stemmer,"stemmer-fr"),e.fr.stopWordFilter=e.generateStopWordFilter("ai aie aient aies ait as au aura aurai auraient aurais aurait auras aurez auriez aurions aurons auront aux avaient avais avait avec avez aviez avions avons ayant ayez ayons c ce ceci celà ces cet cette d dans de des du elle en es est et eu eue eues eurent eus eusse eussent eusses eussiez eussions eut eux eûmes eût eûtes furent fus fusse fussent fusses fussiez fussions fut fûmes fût fûtes ici il ils j je l la le les leur leurs lui m ma mais me mes moi mon même n ne nos notre nous on ont ou par pas pour qu que quel quelle quelles quels qui s sa sans se sera serai seraient serais serait seras serez seriez serions serons seront ses soi soient sois soit sommes son sont soyez soyons suis sur t ta te tes toi ton tu un une vos votre vous y à étaient étais était étant étiez étions été étée étées étés êtes".split(" ")),e.Pipeline.registerFunction(e.fr.stopWordFilter,"stopWordFilter-fr")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.hu.js b/docs/assets/javascripts/lunr/lunr.hu.js deleted file mode 100644 index bfc68db8..00000000 --- a/docs/assets/javascripts/lunr/lunr.hu.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Hungarian` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var p,_,n;e.hu=function(){this.pipeline.reset(),this.pipeline.add(e.hu.trimmer,e.hu.stopWordFilter,e.hu.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hu.stemmer))},e.hu.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.hu.trimmer=e.trimmerSupport.generateTrimmer(e.hu.wordCharacters),e.Pipeline.registerFunction(e.hu.trimmer,"trimmer-hu"),e.hu.stemmer=(p=e.stemmerSupport.Among,_=e.stemmerSupport.SnowballProgram,n=new function(){var r,i=[new p("cs",-1,-1),new p("dzs",-1,-1),new p("gy",-1,-1),new p("ly",-1,-1),new p("ny",-1,-1),new p("sz",-1,-1),new p("ty",-1,-1),new p("zs",-1,-1)],n=[new p("á",-1,1),new p("é",-1,2)],a=[new p("bb",-1,-1),new p("cc",-1,-1),new p("dd",-1,-1),new p("ff",-1,-1),new p("gg",-1,-1),new p("jj",-1,-1),new p("kk",-1,-1),new p("ll",-1,-1),new p("mm",-1,-1),new p("nn",-1,-1),new p("pp",-1,-1),new p("rr",-1,-1),new p("ccs",-1,-1),new p("ss",-1,-1),new p("zzs",-1,-1),new p("tt",-1,-1),new p("vv",-1,-1),new p("ggy",-1,-1),new p("lly",-1,-1),new p("nny",-1,-1),new p("tty",-1,-1),new p("ssz",-1,-1),new p("zz",-1,-1)],t=[new p("al",-1,1),new p("el",-1,2)],e=[new p("ba",-1,-1),new p("ra",-1,-1),new p("be",-1,-1),new p("re",-1,-1),new p("ig",-1,-1),new p("nak",-1,-1),new p("nek",-1,-1),new p("val",-1,-1),new p("vel",-1,-1),new p("ul",-1,-1),new p("nál",-1,-1),new p("nél",-1,-1),new p("ból",-1,-1),new p("ról",-1,-1),new p("tól",-1,-1),new p("bõl",-1,-1),new p("rõl",-1,-1),new p("tõl",-1,-1),new p("ül",-1,-1),new p("n",-1,-1),new p("an",19,-1),new p("ban",20,-1),new p("en",19,-1),new p("ben",22,-1),new p("képpen",22,-1),new p("on",19,-1),new p("ön",19,-1),new p("képp",-1,-1),new p("kor",-1,-1),new p("t",-1,-1),new p("at",29,-1),new p("et",29,-1),new p("ként",29,-1),new p("anként",32,-1),new p("enként",32,-1),new p("onként",32,-1),new p("ot",29,-1),new p("ért",29,-1),new p("öt",29,-1),new p("hez",-1,-1),new p("hoz",-1,-1),new p("höz",-1,-1),new p("vá",-1,-1),new p("vé",-1,-1)],s=[new p("án",-1,2),new p("én",-1,1),new p("ánként",-1,3)],c=[new p("stul",-1,2),new p("astul",0,1),new p("ástul",0,3),new p("stül",-1,2),new p("estül",3,1),new p("éstül",3,4)],w=[new p("á",-1,1),new p("é",-1,2)],o=[new p("k",-1,7),new p("ak",0,4),new p("ek",0,6),new p("ok",0,5),new p("ák",0,1),new p("ék",0,2),new p("ök",0,3)],l=[new p("éi",-1,7),new p("áéi",0,6),new p("ééi",0,5),new p("é",-1,9),new p("ké",3,4),new p("aké",4,1),new p("eké",4,1),new p("oké",4,1),new p("áké",4,3),new p("éké",4,2),new p("öké",4,1),new p("éé",3,8)],u=[new p("a",-1,18),new p("ja",0,17),new p("d",-1,16),new p("ad",2,13),new p("ed",2,13),new p("od",2,13),new p("ád",2,14),new p("éd",2,15),new p("öd",2,13),new p("e",-1,18),new p("je",9,17),new p("nk",-1,4),new p("unk",11,1),new p("ánk",11,2),new p("énk",11,3),new p("ünk",11,1),new p("uk",-1,8),new p("juk",16,7),new p("ájuk",17,5),new p("ük",-1,8),new p("jük",19,7),new p("éjük",20,6),new p("m",-1,12),new p("am",22,9),new p("em",22,9),new p("om",22,9),new p("ám",22,10),new p("ém",22,11),new p("o",-1,18),new p("á",-1,19),new p("é",-1,20)],m=[new p("id",-1,10),new p("aid",0,9),new p("jaid",1,6),new p("eid",0,9),new p("jeid",3,6),new p("áid",0,7),new p("éid",0,8),new p("i",-1,15),new p("ai",7,14),new p("jai",8,11),new p("ei",7,14),new p("jei",10,11),new p("ái",7,12),new p("éi",7,13),new p("itek",-1,24),new p("eitek",14,21),new p("jeitek",15,20),new p("éitek",14,23),new p("ik",-1,29),new p("aik",18,26),new p("jaik",19,25),new p("eik",18,26),new p("jeik",21,25),new p("áik",18,27),new p("éik",18,28),new p("ink",-1,20),new p("aink",25,17),new p("jaink",26,16),new p("eink",25,17),new p("jeink",28,16),new p("áink",25,18),new p("éink",25,19),new p("aitok",-1,21),new p("jaitok",32,20),new p("áitok",-1,22),new p("im",-1,5),new p("aim",35,4),new p("jaim",36,1),new p("eim",35,4),new p("jeim",38,1),new p("áim",35,2),new p("éim",35,3)],k=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,52,14],f=new _;function b(){return r<=f.cursor}function d(){var e=f.limit-f.cursor;return!!f.find_among_b(a,23)&&(f.cursor=f.limit-e,!0)}function g(){if(f.cursor>f.limit_backward){f.cursor--,f.ket=f.cursor;var e=f.cursor-1;f.limit_backward<=e&&e<=f.limit&&(f.cursor=e,f.bra=e,f.slice_del())}}function h(){f.ket=f.cursor,f.find_among_b(e,44)&&(f.bra=f.cursor,b()&&(f.slice_del(),function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(n,2))&&(f.bra=f.cursor,b()))switch(e){case 1:f.slice_from("a");break;case 2:f.slice_from("e")}}()))}this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var e=f.cursor;return function(){var e,n=f.cursor;if(r=f.limit,f.in_grouping(k,97,252))for(;;){if(e=f.cursor,f.out_grouping(k,97,252))return f.cursor=e,f.find_among(i,8)||(f.cursor=e)=f.limit)return r=e;f.cursor++}if(f.cursor=n,f.out_grouping(k,97,252)){for(;!f.in_grouping(k,97,252);){if(f.cursor>=f.limit)return;f.cursor++}r=f.cursor}}(),f.limit_backward=e,f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(t,2))&&(f.bra=f.cursor,b())){if((1==e||2==e)&&!d())return;f.slice_del(),g()}}(),f.cursor=f.limit,h(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(s,3))&&(f.bra=f.cursor,b()))switch(e){case 1:f.slice_from("e");break;case 2:case 3:f.slice_from("a")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(c,6))&&(f.bra=f.cursor,b()))switch(e){case 1:case 2:f.slice_del();break;case 3:f.slice_from("a");break;case 4:f.slice_from("e")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(w,2))&&(f.bra=f.cursor,b())){if((1==e||2==e)&&!d())return;f.slice_del(),g()}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(l,12))&&(f.bra=f.cursor,b()))switch(e){case 1:case 4:case 7:case 9:f.slice_del();break;case 2:case 5:case 8:f.slice_from("e");break;case 3:case 6:f.slice_from("a")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(u,31))&&(f.bra=f.cursor,b()))switch(e){case 1:case 4:case 7:case 8:case 9:case 12:case 13:case 16:case 17:case 18:f.slice_del();break;case 2:case 5:case 10:case 14:case 19:f.slice_from("a");break;case 3:case 6:case 11:case 15:case 20:f.slice_from("e")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(m,42))&&(f.bra=f.cursor,b()))switch(e){case 1:case 4:case 5:case 6:case 9:case 10:case 11:case 14:case 15:case 16:case 17:case 20:case 21:case 24:case 25:case 26:case 29:f.slice_del();break;case 2:case 7:case 12:case 18:case 22:case 27:f.slice_from("a");break;case 3:case 8:case 13:case 19:case 23:case 28:f.slice_from("e")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(o,7))&&(f.bra=f.cursor,b()))switch(e){case 1:f.slice_from("a");break;case 2:f.slice_from("e");break;case 3:case 4:case 5:case 6:case 7:f.slice_del()}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}),e.Pipeline.registerFunction(e.hu.stemmer,"stemmer-hu"),e.hu.stopWordFilter=e.generateStopWordFilter("a abban ahhoz ahogy ahol aki akik akkor alatt amely amelyek amelyekben amelyeket amelyet amelynek ami amikor amit amolyan amíg annak arra arról az azok azon azonban azt aztán azután azzal azért be belül benne bár cikk cikkek cikkeket csak de e ebben eddig egy egyes egyetlen egyik egyre egyéb egész ehhez ekkor el ellen elsõ elég elõ elõször elõtt emilyen ennek erre ez ezek ezen ezt ezzel ezért fel felé hanem hiszen hogy hogyan igen ill ill. illetve ilyen ilyenkor ismét ison itt jobban jó jól kell kellett keressünk keresztül ki kívül között közül legalább legyen lehet lehetett lenne lenni lesz lett maga magát majd majd meg mellett mely melyek mert mi mikor milyen minden mindenki mindent mindig mint mintha mit mivel miért most már más másik még míg nagy nagyobb nagyon ne nekem neki nem nincs néha néhány nélkül olyan ott pedig persze rá s saját sem semmi sok sokat sokkal szemben szerint szinte számára talán tehát teljes tovább továbbá több ugyanis utolsó után utána vagy vagyis vagyok valaki valami valamint való van vannak vele vissza viszont volna volt voltak voltam voltunk által általában át én éppen és így õ õk õket össze úgy új újabb újra".split(" ")),e.Pipeline.registerFunction(e.hu.stopWordFilter,"stopWordFilter-hu")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.it.js b/docs/assets/javascripts/lunr/lunr.it.js deleted file mode 100644 index 58a46fb6..00000000 --- a/docs/assets/javascripts/lunr/lunr.it.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Italian` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var z,P,r;e.it=function(){this.pipeline.reset(),this.pipeline.add(e.it.trimmer,e.it.stopWordFilter,e.it.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.it.stemmer))},e.it.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.it.trimmer=e.trimmerSupport.generateTrimmer(e.it.wordCharacters),e.Pipeline.registerFunction(e.it.trimmer,"trimmer-it"),e.it.stemmer=(z=e.stemmerSupport.Among,P=e.stemmerSupport.SnowballProgram,r=new function(){var o,t,s,a=[new z("",-1,7),new z("qu",0,6),new z("á",0,1),new z("é",0,2),new z("í",0,3),new z("ó",0,4),new z("ú",0,5)],u=[new z("",-1,3),new z("I",0,1),new z("U",0,2)],c=[new z("la",-1,-1),new z("cela",0,-1),new z("gliela",0,-1),new z("mela",0,-1),new z("tela",0,-1),new z("vela",0,-1),new z("le",-1,-1),new z("cele",6,-1),new z("gliele",6,-1),new z("mele",6,-1),new z("tele",6,-1),new z("vele",6,-1),new z("ne",-1,-1),new z("cene",12,-1),new z("gliene",12,-1),new z("mene",12,-1),new z("sene",12,-1),new z("tene",12,-1),new z("vene",12,-1),new z("ci",-1,-1),new z("li",-1,-1),new z("celi",20,-1),new z("glieli",20,-1),new z("meli",20,-1),new z("teli",20,-1),new z("veli",20,-1),new z("gli",20,-1),new z("mi",-1,-1),new z("si",-1,-1),new z("ti",-1,-1),new z("vi",-1,-1),new z("lo",-1,-1),new z("celo",31,-1),new z("glielo",31,-1),new z("melo",31,-1),new z("telo",31,-1),new z("velo",31,-1)],w=[new z("ando",-1,1),new z("endo",-1,1),new z("ar",-1,2),new z("er",-1,2),new z("ir",-1,2)],r=[new z("ic",-1,-1),new z("abil",-1,-1),new z("os",-1,-1),new z("iv",-1,1)],n=[new z("ic",-1,1),new z("abil",-1,1),new z("iv",-1,1)],i=[new z("ica",-1,1),new z("logia",-1,3),new z("osa",-1,1),new z("ista",-1,1),new z("iva",-1,9),new z("anza",-1,1),new z("enza",-1,5),new z("ice",-1,1),new z("atrice",7,1),new z("iche",-1,1),new z("logie",-1,3),new z("abile",-1,1),new z("ibile",-1,1),new z("usione",-1,4),new z("azione",-1,2),new z("uzione",-1,4),new z("atore",-1,2),new z("ose",-1,1),new z("ante",-1,1),new z("mente",-1,1),new z("amente",19,7),new z("iste",-1,1),new z("ive",-1,9),new z("anze",-1,1),new z("enze",-1,5),new z("ici",-1,1),new z("atrici",25,1),new z("ichi",-1,1),new z("abili",-1,1),new z("ibili",-1,1),new z("ismi",-1,1),new z("usioni",-1,4),new z("azioni",-1,2),new z("uzioni",-1,4),new z("atori",-1,2),new z("osi",-1,1),new z("anti",-1,1),new z("amenti",-1,6),new z("imenti",-1,6),new z("isti",-1,1),new z("ivi",-1,9),new z("ico",-1,1),new z("ismo",-1,1),new z("oso",-1,1),new z("amento",-1,6),new z("imento",-1,6),new z("ivo",-1,9),new z("ità",-1,8),new z("istà",-1,1),new z("istè",-1,1),new z("istì",-1,1)],l=[new z("isca",-1,1),new z("enda",-1,1),new z("ata",-1,1),new z("ita",-1,1),new z("uta",-1,1),new z("ava",-1,1),new z("eva",-1,1),new z("iva",-1,1),new z("erebbe",-1,1),new z("irebbe",-1,1),new z("isce",-1,1),new z("ende",-1,1),new z("are",-1,1),new z("ere",-1,1),new z("ire",-1,1),new z("asse",-1,1),new z("ate",-1,1),new z("avate",16,1),new z("evate",16,1),new z("ivate",16,1),new z("ete",-1,1),new z("erete",20,1),new z("irete",20,1),new z("ite",-1,1),new z("ereste",-1,1),new z("ireste",-1,1),new z("ute",-1,1),new z("erai",-1,1),new z("irai",-1,1),new z("isci",-1,1),new z("endi",-1,1),new z("erei",-1,1),new z("irei",-1,1),new z("assi",-1,1),new z("ati",-1,1),new z("iti",-1,1),new z("eresti",-1,1),new z("iresti",-1,1),new z("uti",-1,1),new z("avi",-1,1),new z("evi",-1,1),new z("ivi",-1,1),new z("isco",-1,1),new z("ando",-1,1),new z("endo",-1,1),new z("Yamo",-1,1),new z("iamo",-1,1),new z("avamo",-1,1),new z("evamo",-1,1),new z("ivamo",-1,1),new z("eremo",-1,1),new z("iremo",-1,1),new z("assimo",-1,1),new z("ammo",-1,1),new z("emmo",-1,1),new z("eremmo",54,1),new z("iremmo",54,1),new z("immo",-1,1),new z("ano",-1,1),new z("iscano",58,1),new z("avano",58,1),new z("evano",58,1),new z("ivano",58,1),new z("eranno",-1,1),new z("iranno",-1,1),new z("ono",-1,1),new z("iscono",65,1),new z("arono",65,1),new z("erono",65,1),new z("irono",65,1),new z("erebbero",-1,1),new z("irebbero",-1,1),new z("assero",-1,1),new z("essero",-1,1),new z("issero",-1,1),new z("ato",-1,1),new z("ito",-1,1),new z("uto",-1,1),new z("avo",-1,1),new z("evo",-1,1),new z("ivo",-1,1),new z("ar",-1,1),new z("ir",-1,1),new z("erà",-1,1),new z("irà",-1,1),new z("erò",-1,1),new z("irò",-1,1)],m=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2,1],f=[17,65,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2],v=[17],b=new P;function d(e,r,n){return!(!b.eq_s(1,e)||(b.ket=b.cursor,!b.in_grouping(m,97,249)))&&(b.slice_from(r),b.cursor=n,!0)}function _(e){if(b.cursor=e,!b.in_grouping(m,97,249))return!1;for(;!b.out_grouping(m,97,249);){if(b.cursor>=b.limit)return!1;b.cursor++}return!0}function g(){var e,r=b.cursor;if(!function(){if(b.in_grouping(m,97,249)){var e=b.cursor;if(b.out_grouping(m,97,249)){for(;!b.in_grouping(m,97,249);){if(b.cursor>=b.limit)return _(e);b.cursor++}return!0}return _(e)}return!1}()){if(b.cursor=r,!b.out_grouping(m,97,249))return;if(e=b.cursor,b.out_grouping(m,97,249)){for(;!b.in_grouping(m,97,249);){if(b.cursor>=b.limit)return b.cursor=e,void(b.in_grouping(m,97,249)&&b.cursor=b.limit)return;b.cursor++}s=b.cursor}function p(){for(;!b.in_grouping(m,97,249);){if(b.cursor>=b.limit)return!1;b.cursor++}for(;!b.out_grouping(m,97,249);){if(b.cursor>=b.limit)return!1;b.cursor++}return!0}function k(){return s<=b.cursor}function h(){return o<=b.cursor}function q(){var e;if(b.ket=b.cursor,!(e=b.find_among_b(i,51)))return!1;switch(b.bra=b.cursor,e){case 1:if(!h())return!1;b.slice_del();break;case 2:if(!h())return!1;b.slice_del(),b.ket=b.cursor,b.eq_s_b(2,"ic")&&(b.bra=b.cursor,h()&&b.slice_del());break;case 3:if(!h())return!1;b.slice_from("log");break;case 4:if(!h())return!1;b.slice_from("u");break;case 5:if(!h())return!1;b.slice_from("ente");break;case 6:if(!k())return!1;b.slice_del();break;case 7:if(!(t<=b.cursor))return!1;b.slice_del(),b.ket=b.cursor,(e=b.find_among_b(r,4))&&(b.bra=b.cursor,h()&&(b.slice_del(),1==e&&(b.ket=b.cursor,b.eq_s_b(2,"at")&&(b.bra=b.cursor,h()&&b.slice_del()))));break;case 8:if(!h())return!1;b.slice_del(),b.ket=b.cursor,(e=b.find_among_b(n,3))&&(b.bra=b.cursor,1==e&&h()&&b.slice_del());break;case 9:if(!h())return!1;b.slice_del(),b.ket=b.cursor,b.eq_s_b(2,"at")&&(b.bra=b.cursor,h()&&(b.slice_del(),b.ket=b.cursor,b.eq_s_b(2,"ic")&&(b.bra=b.cursor,h()&&b.slice_del())))}return!0}function C(){var e;e=b.limit-b.cursor,b.ket=b.cursor,b.in_grouping_b(f,97,242)&&(b.bra=b.cursor,k()&&(b.slice_del(),b.ket=b.cursor,b.eq_s_b(1,"i")&&(b.bra=b.cursor,k())))?b.slice_del():b.cursor=b.limit-e,b.ket=b.cursor,b.eq_s_b(1,"h")&&(b.bra=b.cursor,b.in_grouping_b(v,99,103)&&k()&&b.slice_del())}this.setCurrent=function(e){b.setCurrent(e)},this.getCurrent=function(){return b.getCurrent()},this.stem=function(){var e,r,n,i=b.cursor;return function(){for(var e,r,n,i,o=b.cursor;;){if(b.bra=b.cursor,e=b.find_among(a,7))switch(b.ket=b.cursor,e){case 1:b.slice_from("à");continue;case 2:b.slice_from("è");continue;case 3:b.slice_from("ì");continue;case 4:b.slice_from("ò");continue;case 5:b.slice_from("ù");continue;case 6:b.slice_from("qU");continue;case 7:if(b.cursor>=b.limit)break;b.cursor++;continue}break}for(b.cursor=o;;)for(r=b.cursor;;){if(n=b.cursor,b.in_grouping(m,97,249)){if(b.bra=b.cursor,i=b.cursor,d("u","U",n))break;if(b.cursor=i,d("i","I",n))break}if(b.cursor=n,b.cursor>=b.limit)return b.cursor=r;b.cursor++}}(),b.cursor=i,e=b.cursor,s=b.limit,o=t=s,g(),b.cursor=e,p()&&(t=b.cursor,p()&&(o=b.cursor)),b.limit_backward=i,b.cursor=b.limit,function(){var e;if(b.ket=b.cursor,b.find_among_b(c,37)&&(b.bra=b.cursor,(e=b.find_among_b(w,5))&&k()))switch(e){case 1:b.slice_del();break;case 2:b.slice_from("e")}}(),b.cursor=b.limit,q()||(b.cursor=b.limit,b.cursor>=s&&(n=b.limit_backward,b.limit_backward=s,b.ket=b.cursor,(r=b.find_among_b(l,87))&&(b.bra=b.cursor,1==r&&b.slice_del()),b.limit_backward=n)),b.cursor=b.limit,C(),b.cursor=b.limit_backward,function(){for(var e;b.bra=b.cursor,e=b.find_among(u,3);)switch(b.ket=b.cursor,e){case 1:b.slice_from("i");break;case 2:b.slice_from("u");break;case 3:if(b.cursor>=b.limit)return;b.cursor++}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.it.stemmer,"stemmer-it"),e.it.stopWordFilter=e.generateStopWordFilter("a abbia abbiamo abbiano abbiate ad agl agli ai al all alla alle allo anche avemmo avendo avesse avessero avessi avessimo aveste avesti avete aveva avevamo avevano avevate avevi avevo avrai avranno avrebbe avrebbero avrei avremmo avremo avreste avresti avrete avrà avrò avuta avute avuti avuto c che chi ci coi col come con contro cui da dagl dagli dai dal dall dalla dalle dallo degl degli dei del dell della delle dello di dov dove e ebbe ebbero ebbi ed era erano eravamo eravate eri ero essendo faccia facciamo facciano facciate faccio facemmo facendo facesse facessero facessi facessimo faceste facesti faceva facevamo facevano facevate facevi facevo fai fanno farai faranno farebbe farebbero farei faremmo faremo fareste faresti farete farà farò fece fecero feci fosse fossero fossi fossimo foste fosti fu fui fummo furono gli ha hai hanno ho i il in io l la le lei li lo loro lui ma mi mia mie miei mio ne negl negli nei nel nell nella nelle nello noi non nostra nostre nostri nostro o per perché più quale quanta quante quanti quanto quella quelle quelli quello questa queste questi questo sarai saranno sarebbe sarebbero sarei saremmo saremo sareste saresti sarete sarà sarò se sei si sia siamo siano siate siete sono sta stai stando stanno starai staranno starebbe starebbero starei staremmo staremo stareste staresti starete starà starò stava stavamo stavano stavate stavi stavo stemmo stesse stessero stessi stessimo steste stesti stette stettero stetti stia stiamo stiano stiate sto su sua sue sugl sugli sui sul sull sulla sulle sullo suo suoi ti tra tu tua tue tuo tuoi tutti tutto un una uno vi voi vostra vostre vostri vostro è".split(" ")),e.Pipeline.registerFunction(e.it.stopWordFilter,"stopWordFilter-it")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.ja.js b/docs/assets/javascripts/lunr/lunr.ja.js deleted file mode 100644 index 715b834a..00000000 --- a/docs/assets/javascripts/lunr/lunr.ja.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Japanese` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Chad Liu - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(m){if(void 0===m)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===m.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var l="2"==m.version[0];m.ja=function(){this.pipeline.reset(),this.pipeline.add(m.ja.trimmer,m.ja.stopWordFilter,m.ja.stemmer),l?this.tokenizer=m.ja.tokenizer:(m.tokenizer&&(m.tokenizer=m.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=m.ja.tokenizer))};var j=new m.TinySegmenter;m.ja.tokenizer=function(e){var r,t,i,n,o,s,p,a,u;if(!arguments.length||null==e||null==e)return[];if(Array.isArray(e))return e.map(function(e){return l?new m.Token(e.toLowerCase()):e.toLowerCase()});for(r=(t=e.toString().toLowerCase().replace(/^\s+/,"")).length-1;0<=r;r--)if(/\S/.test(t.charAt(r))){t=t.substring(0,r+1);break}for(o=[],i=t.length,p=a=0;a<=i;a++)if(s=a-p,t.charAt(a).match(/\s/)||a==i){if(0=_.limit||(_.cursor++,!1)}function w(){for(;!_.in_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}for(;!_.out_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}return!1}function b(){return i<=_.cursor}function p(){return e<=_.cursor}function g(){var r=_.limit-_.cursor;_.find_among_b(t,3)&&(_.cursor=_.limit-r,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del()))}function h(){var r;u=!1,_.ket=_.cursor,_.eq_s_b(1,"e")&&(_.bra=_.cursor,b()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.slice_del(),u=!0,g())))}function k(){var r;b()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.eq_s_b(3,"gem")||(_.cursor=_.limit-r,_.slice_del(),g())))}this.setCurrent=function(r){_.setCurrent(r)},this.getCurrent=function(){return _.getCurrent()},this.stem=function(){var r=_.cursor;return function(){for(var r,e,i,n=_.cursor;;){if(_.bra=_.cursor,r=_.find_among(o,11))switch(_.ket=_.cursor,r){case 1:_.slice_from("a");continue;case 2:_.slice_from("e");continue;case 3:_.slice_from("i");continue;case 4:_.slice_from("o");continue;case 5:_.slice_from("u");continue;case 6:if(_.cursor>=_.limit)break;_.cursor++;continue}break}for(_.cursor=n,_.bra=n,_.eq_s(1,"y")?(_.ket=_.cursor,_.slice_from("Y")):_.cursor=n;;)if(e=_.cursor,_.in_grouping(m,97,232)){if(i=_.cursor,_.bra=i,_.eq_s(1,"i"))_.ket=_.cursor,_.in_grouping(m,97,232)&&(_.slice_from("I"),_.cursor=e);else if(_.cursor=i,_.eq_s(1,"y"))_.ket=_.cursor,_.slice_from("Y"),_.cursor=e;else if(s(e))break}else if(s(e))break}(),_.cursor=r,i=_.limit,e=i,w()||((i=_.cursor)<3&&(i=3),w()||(e=_.cursor)),_.limit_backward=r,_.cursor=_.limit,function(){var r,e,i,n,o,t,s=_.limit-_.cursor;if(_.ket=_.cursor,r=_.find_among_b(c,5))switch(_.bra=_.cursor,r){case 1:b()&&_.slice_from("heid");break;case 2:k();break;case 3:b()&&_.out_grouping_b(f,97,232)&&_.slice_del()}if(_.cursor=_.limit-s,h(),_.cursor=_.limit-s,_.ket=_.cursor,_.eq_s_b(4,"heid")&&(_.bra=_.cursor,p()&&(e=_.limit-_.cursor,_.eq_s_b(1,"c")||(_.cursor=_.limit-e,_.slice_del(),_.ket=_.cursor,_.eq_s_b(2,"en")&&(_.bra=_.cursor,k())))),_.cursor=_.limit-s,_.ket=_.cursor,r=_.find_among_b(a,6))switch(_.bra=_.cursor,r){case 1:if(p()){if(_.slice_del(),i=_.limit-_.cursor,_.ket=_.cursor,_.eq_s_b(2,"ig")&&(_.bra=_.cursor,p()&&(n=_.limit-_.cursor,!_.eq_s_b(1,"e")))){_.cursor=_.limit-n,_.slice_del();break}_.cursor=_.limit-i,g()}break;case 2:p()&&(o=_.limit-_.cursor,_.eq_s_b(1,"e")||(_.cursor=_.limit-o,_.slice_del()));break;case 3:p()&&(_.slice_del(),h());break;case 4:p()&&_.slice_del();break;case 5:p()&&u&&_.slice_del()}_.cursor=_.limit-s,_.out_grouping_b(d,73,232)&&(t=_.limit-_.cursor,_.find_among_b(l,4)&&_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-t,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del())))}(),_.cursor=_.limit_backward,function(){for(var r;;)if(_.bra=_.cursor,r=_.find_among(n,3))switch(_.ket=_.cursor,r){case 1:_.slice_from("y");break;case 2:_.slice_from("i");break;case 3:if(_.cursor>=_.limit)return;_.cursor++}}(),!0}},function(r){return"function"==typeof r.update?r.update(function(r){return e.setCurrent(r),e.stem(),e.getCurrent()}):(e.setCurrent(r),e.stem(),e.getCurrent())}),r.Pipeline.registerFunction(r.nl.stemmer,"stemmer-nl"),r.nl.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.nl.stopWordFilter,"stopWordFilter-nl")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.no.js b/docs/assets/javascripts/lunr/lunr.no.js deleted file mode 100644 index 031e4b20..00000000 --- a/docs/assets/javascripts/lunr/lunr.no.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Norwegian` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,n,i;e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=(r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){var o,s,a=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],m=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],u=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],d=[119,125,149,1],c=new n;this.setCurrent=function(e){c.setCurrent(e)},this.getCurrent=function(){return c.getCurrent()},this.stem=function(){var e,r,n,i,t=c.cursor;return function(){var e,r=c.cursor+3;if(s=c.limit,0<=r||r<=c.limit){for(o=r;;){if(e=c.cursor,c.in_grouping(u,97,248)){c.cursor=e;break}if(e>=c.limit)return;c.cursor=e+1}for(;!c.out_grouping(u,97,248);){if(c.cursor>=c.limit)return;c.cursor++}(s=c.cursor)=s&&(r=c.limit_backward,c.limit_backward=s,c.ket=c.cursor,e=c.find_among_b(a,29),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del();break;case 2:n=c.limit-c.cursor,c.in_grouping_b(d,98,122)?c.slice_del():(c.cursor=c.limit-n,c.eq_s_b(1,"k")&&c.out_grouping_b(u,97,248)&&c.slice_del());break;case 3:c.slice_from("er")}}(),c.cursor=c.limit,r=c.limit-c.cursor,c.cursor>=s&&(e=c.limit_backward,c.limit_backward=s,c.ket=c.cursor,c.find_among_b(m,2)?(c.bra=c.cursor,c.limit_backward=e,c.cursor=c.limit-r,c.cursor>c.limit_backward&&(c.cursor--,c.bra=c.cursor,c.slice_del())):c.limit_backward=e),c.cursor=c.limit,c.cursor>=s&&(i=c.limit_backward,c.limit_backward=s,c.ket=c.cursor,(n=c.find_among_b(l,11))?(c.bra=c.cursor,c.limit_backward=i,1==n&&c.slice_del()):c.limit_backward=i),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.pt.js b/docs/assets/javascripts/lunr/lunr.pt.js deleted file mode 100644 index 59e766fe..00000000 --- a/docs/assets/javascripts/lunr/lunr.pt.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Portuguese` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var j,C,r;e.pt=function(){this.pipeline.reset(),this.pipeline.add(e.pt.trimmer,e.pt.stopWordFilter,e.pt.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.pt.stemmer))},e.pt.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.pt.trimmer=e.trimmerSupport.generateTrimmer(e.pt.wordCharacters),e.Pipeline.registerFunction(e.pt.trimmer,"trimmer-pt"),e.pt.stemmer=(j=e.stemmerSupport.Among,C=e.stemmerSupport.SnowballProgram,r=new function(){var s,n,i,o=[new j("",-1,3),new j("ã",0,1),new j("õ",0,2)],a=[new j("",-1,3),new j("a~",0,1),new j("o~",0,2)],r=[new j("ic",-1,-1),new j("ad",-1,-1),new j("os",-1,-1),new j("iv",-1,1)],t=[new j("ante",-1,1),new j("avel",-1,1),new j("ível",-1,1)],u=[new j("ic",-1,1),new j("abil",-1,1),new j("iv",-1,1)],w=[new j("ica",-1,1),new j("ância",-1,1),new j("ência",-1,4),new j("ira",-1,9),new j("adora",-1,1),new j("osa",-1,1),new j("ista",-1,1),new j("iva",-1,8),new j("eza",-1,1),new j("logía",-1,2),new j("idade",-1,7),new j("ante",-1,1),new j("mente",-1,6),new j("amente",12,5),new j("ável",-1,1),new j("ível",-1,1),new j("ución",-1,3),new j("ico",-1,1),new j("ismo",-1,1),new j("oso",-1,1),new j("amento",-1,1),new j("imento",-1,1),new j("ivo",-1,8),new j("aça~o",-1,1),new j("ador",-1,1),new j("icas",-1,1),new j("ências",-1,4),new j("iras",-1,9),new j("adoras",-1,1),new j("osas",-1,1),new j("istas",-1,1),new j("ivas",-1,8),new j("ezas",-1,1),new j("logías",-1,2),new j("idades",-1,7),new j("uciones",-1,3),new j("adores",-1,1),new j("antes",-1,1),new j("aço~es",-1,1),new j("icos",-1,1),new j("ismos",-1,1),new j("osos",-1,1),new j("amentos",-1,1),new j("imentos",-1,1),new j("ivos",-1,8)],m=[new j("ada",-1,1),new j("ida",-1,1),new j("ia",-1,1),new j("aria",2,1),new j("eria",2,1),new j("iria",2,1),new j("ara",-1,1),new j("era",-1,1),new j("ira",-1,1),new j("ava",-1,1),new j("asse",-1,1),new j("esse",-1,1),new j("isse",-1,1),new j("aste",-1,1),new j("este",-1,1),new j("iste",-1,1),new j("ei",-1,1),new j("arei",16,1),new j("erei",16,1),new j("irei",16,1),new j("am",-1,1),new j("iam",20,1),new j("ariam",21,1),new j("eriam",21,1),new j("iriam",21,1),new j("aram",20,1),new j("eram",20,1),new j("iram",20,1),new j("avam",20,1),new j("em",-1,1),new j("arem",29,1),new j("erem",29,1),new j("irem",29,1),new j("assem",29,1),new j("essem",29,1),new j("issem",29,1),new j("ado",-1,1),new j("ido",-1,1),new j("ando",-1,1),new j("endo",-1,1),new j("indo",-1,1),new j("ara~o",-1,1),new j("era~o",-1,1),new j("ira~o",-1,1),new j("ar",-1,1),new j("er",-1,1),new j("ir",-1,1),new j("as",-1,1),new j("adas",47,1),new j("idas",47,1),new j("ias",47,1),new j("arias",50,1),new j("erias",50,1),new j("irias",50,1),new j("aras",47,1),new j("eras",47,1),new j("iras",47,1),new j("avas",47,1),new j("es",-1,1),new j("ardes",58,1),new j("erdes",58,1),new j("irdes",58,1),new j("ares",58,1),new j("eres",58,1),new j("ires",58,1),new j("asses",58,1),new j("esses",58,1),new j("isses",58,1),new j("astes",58,1),new j("estes",58,1),new j("istes",58,1),new j("is",-1,1),new j("ais",71,1),new j("eis",71,1),new j("areis",73,1),new j("ereis",73,1),new j("ireis",73,1),new j("áreis",73,1),new j("éreis",73,1),new j("íreis",73,1),new j("ásseis",73,1),new j("ésseis",73,1),new j("ísseis",73,1),new j("áveis",73,1),new j("íeis",73,1),new j("aríeis",84,1),new j("eríeis",84,1),new j("iríeis",84,1),new j("ados",-1,1),new j("idos",-1,1),new j("amos",-1,1),new j("áramos",90,1),new j("éramos",90,1),new j("íramos",90,1),new j("ávamos",90,1),new j("íamos",90,1),new j("aríamos",95,1),new j("eríamos",95,1),new j("iríamos",95,1),new j("emos",-1,1),new j("aremos",99,1),new j("eremos",99,1),new j("iremos",99,1),new j("ássemos",99,1),new j("êssemos",99,1),new j("íssemos",99,1),new j("imos",-1,1),new j("armos",-1,1),new j("ermos",-1,1),new j("irmos",-1,1),new j("ámos",-1,1),new j("arás",-1,1),new j("erás",-1,1),new j("irás",-1,1),new j("eu",-1,1),new j("iu",-1,1),new j("ou",-1,1),new j("ará",-1,1),new j("erá",-1,1),new j("irá",-1,1)],c=[new j("a",-1,1),new j("i",-1,1),new j("o",-1,1),new j("os",-1,1),new j("á",-1,1),new j("í",-1,1),new j("ó",-1,1)],l=[new j("e",-1,1),new j("ç",-1,2),new j("é",-1,1),new j("ê",-1,1)],f=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,3,19,12,2],d=new C;function v(){if(d.out_grouping(f,97,250)){for(;!d.in_grouping(f,97,250);){if(d.cursor>=d.limit)return!0;d.cursor++}return!1}return!0}function p(){var e,r,s=d.cursor;if(d.in_grouping(f,97,250))if(e=d.cursor,v()){if(d.cursor=e,function(){if(d.in_grouping(f,97,250))for(;!d.out_grouping(f,97,250);){if(d.cursor>=d.limit)return!1;d.cursor++}return i=d.cursor,!0}())return}else i=d.cursor;if(d.cursor=s,d.out_grouping(f,97,250)){if(r=d.cursor,v()){if(d.cursor=r,!d.in_grouping(f,97,250)||d.cursor>=d.limit)return;d.cursor++}i=d.cursor}}function _(){for(;!d.in_grouping(f,97,250);){if(d.cursor>=d.limit)return!1;d.cursor++}for(;!d.out_grouping(f,97,250);){if(d.cursor>=d.limit)return!1;d.cursor++}return!0}function h(){return i<=d.cursor}function b(){return s<=d.cursor}function g(){var e;if(d.ket=d.cursor,!(e=d.find_among_b(w,45)))return!1;switch(d.bra=d.cursor,e){case 1:if(!b())return!1;d.slice_del();break;case 2:if(!b())return!1;d.slice_from("log");break;case 3:if(!b())return!1;d.slice_from("u");break;case 4:if(!b())return!1;d.slice_from("ente");break;case 5:if(!(n<=d.cursor))return!1;d.slice_del(),d.ket=d.cursor,(e=d.find_among_b(r,4))&&(d.bra=d.cursor,b()&&(d.slice_del(),1==e&&(d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,b()&&d.slice_del()))));break;case 6:if(!b())return!1;d.slice_del(),d.ket=d.cursor,(e=d.find_among_b(t,3))&&(d.bra=d.cursor,1==e&&b()&&d.slice_del());break;case 7:if(!b())return!1;d.slice_del(),d.ket=d.cursor,(e=d.find_among_b(u,3))&&(d.bra=d.cursor,1==e&&b()&&d.slice_del());break;case 8:if(!b())return!1;d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,b()&&d.slice_del());break;case 9:if(!h()||!d.eq_s_b(1,"e"))return!1;d.slice_from("ir")}return!0}function k(e,r){if(d.eq_s_b(1,e)){d.bra=d.cursor;var s=d.limit-d.cursor;if(d.eq_s_b(1,r))return d.cursor=d.limit-s,h()&&d.slice_del(),!1}return!0}function q(){if(!g()&&(d.cursor=d.limit,!function(){var e,r;if(d.cursor>=i){if(r=d.limit_backward,d.limit_backward=i,d.ket=d.cursor,e=d.find_among_b(m,120))return d.bra=d.cursor,1==e&&d.slice_del(),d.limit_backward=r,!0;d.limit_backward=r}return!1}()))return d.cursor=d.limit,d.ket=d.cursor,void((e=d.find_among_b(c,7))&&(d.bra=d.cursor,1==e&&h()&&d.slice_del()));var e;d.cursor=d.limit,d.ket=d.cursor,d.eq_s_b(1,"i")&&(d.bra=d.cursor,d.eq_s_b(1,"c")&&(d.cursor=d.limit,h()&&d.slice_del()))}this.setCurrent=function(e){d.setCurrent(e)},this.getCurrent=function(){return d.getCurrent()},this.stem=function(){var e,r=d.cursor;return function(){for(var e;;){if(d.bra=d.cursor,e=d.find_among(o,3))switch(d.ket=d.cursor,e){case 1:d.slice_from("a~");continue;case 2:d.slice_from("o~");continue;case 3:if(d.cursor>=d.limit)break;d.cursor++;continue}break}}(),d.cursor=r,e=d.cursor,i=d.limit,s=n=i,p(),d.cursor=e,_()&&(n=d.cursor,_()&&(s=d.cursor)),d.limit_backward=r,d.cursor=d.limit,q(),d.cursor=d.limit,function(){var e;if(d.ket=d.cursor,e=d.find_among_b(l,4))switch(d.bra=d.cursor,e){case 1:h()&&(d.slice_del(),d.ket=d.cursor,d.limit,d.cursor,k("u","g")&&k("i","c"));break;case 2:d.slice_from("c")}}(),d.cursor=d.limit_backward,function(){for(var e;;){if(d.bra=d.cursor,e=d.find_among(a,3))switch(d.ket=d.cursor,e){case 1:d.slice_from("ã");continue;case 2:d.slice_from("õ");continue;case 3:if(d.cursor>=d.limit)break;d.cursor++;continue}break}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.pt.stemmer,"stemmer-pt"),e.pt.stopWordFilter=e.generateStopWordFilter("a ao aos aquela aquelas aquele aqueles aquilo as até com como da das de dela delas dele deles depois do dos e ela elas ele eles em entre era eram essa essas esse esses esta estamos estas estava estavam este esteja estejam estejamos estes esteve estive estivemos estiver estivera estiveram estiverem estivermos estivesse estivessem estivéramos estivéssemos estou está estávamos estão eu foi fomos for fora foram forem formos fosse fossem fui fôramos fôssemos haja hajam hajamos havemos hei houve houvemos houver houvera houveram houverei houverem houveremos houveria houveriam houvermos houverá houverão houveríamos houvesse houvessem houvéramos houvéssemos há hão isso isto já lhe lhes mais mas me mesmo meu meus minha minhas muito na nas nem no nos nossa nossas nosso nossos num numa não nós o os ou para pela pelas pelo pelos por qual quando que quem se seja sejam sejamos sem serei seremos seria seriam será serão seríamos seu seus somos sou sua suas são só também te tem temos tenha tenham tenhamos tenho terei teremos teria teriam terá terão teríamos teu teus teve tinha tinham tive tivemos tiver tivera tiveram tiverem tivermos tivesse tivessem tivéramos tivéssemos tu tua tuas tém tínhamos um uma você vocês vos à às éramos".split(" ")),e.Pipeline.registerFunction(e.pt.stopWordFilter,"stopWordFilter-pt")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.ro.js b/docs/assets/javascripts/lunr/lunr.ro.js deleted file mode 100644 index c5ecc96c..00000000 --- a/docs/assets/javascripts/lunr/lunr.ro.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Romanian` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var h,z,i;e.ro=function(){this.pipeline.reset(),this.pipeline.add(e.ro.trimmer,e.ro.stopWordFilter,e.ro.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ro.stemmer))},e.ro.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.ro.trimmer=e.trimmerSupport.generateTrimmer(e.ro.wordCharacters),e.Pipeline.registerFunction(e.ro.trimmer,"trimmer-ro"),e.ro.stemmer=(h=e.stemmerSupport.Among,z=e.stemmerSupport.SnowballProgram,i=new function(){var r,n,t,a,o=[new h("",-1,3),new h("I",0,1),new h("U",0,2)],s=[new h("ea",-1,3),new h("aţia",-1,7),new h("aua",-1,2),new h("iua",-1,4),new h("aţie",-1,7),new h("ele",-1,3),new h("ile",-1,5),new h("iile",6,4),new h("iei",-1,4),new h("atei",-1,6),new h("ii",-1,4),new h("ului",-1,1),new h("ul",-1,1),new h("elor",-1,3),new h("ilor",-1,4),new h("iilor",14,4)],c=[new h("icala",-1,4),new h("iciva",-1,4),new h("ativa",-1,5),new h("itiva",-1,6),new h("icale",-1,4),new h("aţiune",-1,5),new h("iţiune",-1,6),new h("atoare",-1,5),new h("itoare",-1,6),new h("ătoare",-1,5),new h("icitate",-1,4),new h("abilitate",-1,1),new h("ibilitate",-1,2),new h("ivitate",-1,3),new h("icive",-1,4),new h("ative",-1,5),new h("itive",-1,6),new h("icali",-1,4),new h("atori",-1,5),new h("icatori",18,4),new h("itori",-1,6),new h("ători",-1,5),new h("icitati",-1,4),new h("abilitati",-1,1),new h("ivitati",-1,3),new h("icivi",-1,4),new h("ativi",-1,5),new h("itivi",-1,6),new h("icităi",-1,4),new h("abilităi",-1,1),new h("ivităi",-1,3),new h("icităţi",-1,4),new h("abilităţi",-1,1),new h("ivităţi",-1,3),new h("ical",-1,4),new h("ator",-1,5),new h("icator",35,4),new h("itor",-1,6),new h("ător",-1,5),new h("iciv",-1,4),new h("ativ",-1,5),new h("itiv",-1,6),new h("icală",-1,4),new h("icivă",-1,4),new h("ativă",-1,5),new h("itivă",-1,6)],u=[new h("ica",-1,1),new h("abila",-1,1),new h("ibila",-1,1),new h("oasa",-1,1),new h("ata",-1,1),new h("ita",-1,1),new h("anta",-1,1),new h("ista",-1,3),new h("uta",-1,1),new h("iva",-1,1),new h("ic",-1,1),new h("ice",-1,1),new h("abile",-1,1),new h("ibile",-1,1),new h("isme",-1,3),new h("iune",-1,2),new h("oase",-1,1),new h("ate",-1,1),new h("itate",17,1),new h("ite",-1,1),new h("ante",-1,1),new h("iste",-1,3),new h("ute",-1,1),new h("ive",-1,1),new h("ici",-1,1),new h("abili",-1,1),new h("ibili",-1,1),new h("iuni",-1,2),new h("atori",-1,1),new h("osi",-1,1),new h("ati",-1,1),new h("itati",30,1),new h("iti",-1,1),new h("anti",-1,1),new h("isti",-1,3),new h("uti",-1,1),new h("işti",-1,3),new h("ivi",-1,1),new h("ităi",-1,1),new h("oşi",-1,1),new h("ităţi",-1,1),new h("abil",-1,1),new h("ibil",-1,1),new h("ism",-1,3),new h("ator",-1,1),new h("os",-1,1),new h("at",-1,1),new h("it",-1,1),new h("ant",-1,1),new h("ist",-1,3),new h("ut",-1,1),new h("iv",-1,1),new h("ică",-1,1),new h("abilă",-1,1),new h("ibilă",-1,1),new h("oasă",-1,1),new h("ată",-1,1),new h("ită",-1,1),new h("antă",-1,1),new h("istă",-1,3),new h("ută",-1,1),new h("ivă",-1,1)],w=[new h("ea",-1,1),new h("ia",-1,1),new h("esc",-1,1),new h("ăsc",-1,1),new h("ind",-1,1),new h("ând",-1,1),new h("are",-1,1),new h("ere",-1,1),new h("ire",-1,1),new h("âre",-1,1),new h("se",-1,2),new h("ase",10,1),new h("sese",10,2),new h("ise",10,1),new h("use",10,1),new h("âse",10,1),new h("eşte",-1,1),new h("ăşte",-1,1),new h("eze",-1,1),new h("ai",-1,1),new h("eai",19,1),new h("iai",19,1),new h("sei",-1,2),new h("eşti",-1,1),new h("ăşti",-1,1),new h("ui",-1,1),new h("ezi",-1,1),new h("âi",-1,1),new h("aşi",-1,1),new h("seşi",-1,2),new h("aseşi",29,1),new h("seseşi",29,2),new h("iseşi",29,1),new h("useşi",29,1),new h("âseşi",29,1),new h("işi",-1,1),new h("uşi",-1,1),new h("âşi",-1,1),new h("aţi",-1,2),new h("eaţi",38,1),new h("iaţi",38,1),new h("eţi",-1,2),new h("iţi",-1,2),new h("âţi",-1,2),new h("arăţi",-1,1),new h("serăţi",-1,2),new h("aserăţi",45,1),new h("seserăţi",45,2),new h("iserăţi",45,1),new h("userăţi",45,1),new h("âserăţi",45,1),new h("irăţi",-1,1),new h("urăţi",-1,1),new h("ârăţi",-1,1),new h("am",-1,1),new h("eam",54,1),new h("iam",54,1),new h("em",-1,2),new h("asem",57,1),new h("sesem",57,2),new h("isem",57,1),new h("usem",57,1),new h("âsem",57,1),new h("im",-1,2),new h("âm",-1,2),new h("ăm",-1,2),new h("arăm",65,1),new h("serăm",65,2),new h("aserăm",67,1),new h("seserăm",67,2),new h("iserăm",67,1),new h("userăm",67,1),new h("âserăm",67,1),new h("irăm",65,1),new h("urăm",65,1),new h("ârăm",65,1),new h("au",-1,1),new h("eau",76,1),new h("iau",76,1),new h("indu",-1,1),new h("ându",-1,1),new h("ez",-1,1),new h("ească",-1,1),new h("ară",-1,1),new h("seră",-1,2),new h("aseră",84,1),new h("seseră",84,2),new h("iseră",84,1),new h("useră",84,1),new h("âseră",84,1),new h("iră",-1,1),new h("ură",-1,1),new h("âră",-1,1),new h("ează",-1,1)],i=[new h("a",-1,1),new h("e",-1,1),new h("ie",1,1),new h("i",-1,1),new h("ă",-1,1)],m=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,2,32,0,0,4],l=new z;function f(e,i){l.eq_s(1,e)&&(l.ket=l.cursor,l.in_grouping(m,97,259)&&l.slice_from(i))}function p(){if(l.out_grouping(m,97,259)){for(;!l.in_grouping(m,97,259);){if(l.cursor>=l.limit)return!0;l.cursor++}return!1}return!0}function d(){var e,i,r=l.cursor;if(l.in_grouping(m,97,259)){if(e=l.cursor,!p())return void(a=l.cursor);if(l.cursor=e,!function(){if(l.in_grouping(m,97,259))for(;!l.out_grouping(m,97,259);){if(l.cursor>=l.limit)return!0;l.cursor++}return!1}())return void(a=l.cursor)}l.cursor=r,l.out_grouping(m,97,259)&&(i=l.cursor,p()&&(l.cursor=i,l.in_grouping(m,97,259)&&l.cursor=l.limit)return!1;l.cursor++}for(;!l.out_grouping(m,97,259);){if(l.cursor>=l.limit)return!1;l.cursor++}return!0}function v(){return t<=l.cursor}function _(){var e,i=l.limit-l.cursor;if(l.ket=l.cursor,(e=l.find_among_b(c,46))&&(l.bra=l.cursor,v())){switch(e){case 1:l.slice_from("abil");break;case 2:l.slice_from("ibil");break;case 3:l.slice_from("iv");break;case 4:l.slice_from("ic");break;case 5:l.slice_from("at");break;case 6:l.slice_from("it")}return r=!0,l.cursor=l.limit-i,!0}return!1}function g(){var e,i;for(r=!1;;)if(i=l.limit-l.cursor,!_()){l.cursor=l.limit-i;break}if(l.ket=l.cursor,(e=l.find_among_b(u,62))&&(l.bra=l.cursor,n<=l.cursor)){switch(e){case 1:l.slice_del();break;case 2:l.eq_s_b(1,"ţ")&&(l.bra=l.cursor,l.slice_from("t"));break;case 3:l.slice_from("ist")}r=!0}}function k(){var e;l.ket=l.cursor,(e=l.find_among_b(i,5))&&(l.bra=l.cursor,a<=l.cursor&&1==e&&l.slice_del())}this.setCurrent=function(e){l.setCurrent(e)},this.getCurrent=function(){return l.getCurrent()},this.stem=function(){var e,i=l.cursor;return function(){for(var e,i;e=l.cursor,l.in_grouping(m,97,259)&&(i=l.cursor,l.bra=i,f("u","U"),l.cursor=i,f("i","I")),l.cursor=e,!(l.cursor>=l.limit);)l.cursor++}(),l.cursor=i,e=l.cursor,a=l.limit,n=t=a,d(),l.cursor=e,b()&&(t=l.cursor,b()&&(n=l.cursor)),l.limit_backward=i,l.cursor=l.limit,function(){var e,i;if(l.ket=l.cursor,(e=l.find_among_b(s,16))&&(l.bra=l.cursor,v()))switch(e){case 1:l.slice_del();break;case 2:l.slice_from("a");break;case 3:l.slice_from("e");break;case 4:l.slice_from("i");break;case 5:i=l.limit-l.cursor,l.eq_s_b(2,"ab")||(l.cursor=l.limit-i,l.slice_from("i"));break;case 6:l.slice_from("at");break;case 7:l.slice_from("aţi")}}(),l.cursor=l.limit,g(),l.cursor=l.limit,r||(l.cursor=l.limit,function(){var e,i,r;if(l.cursor>=a){if(i=l.limit_backward,l.limit_backward=a,l.ket=l.cursor,e=l.find_among_b(w,94))switch(l.bra=l.cursor,e){case 1:if(r=l.limit-l.cursor,!l.out_grouping_b(m,97,259)&&(l.cursor=l.limit-r,!l.eq_s_b(1,"u")))break;case 2:l.slice_del()}l.limit_backward=i}}(),l.cursor=l.limit),k(),l.cursor=l.limit_backward,function(){for(var e;;){if(l.bra=l.cursor,e=l.find_among(o,3))switch(l.ket=l.cursor,e){case 1:l.slice_from("i");continue;case 2:l.slice_from("u");continue;case 3:if(l.cursor>=l.limit)break;l.cursor++;continue}break}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.ro.stemmer,"stemmer-ro"),e.ro.stopWordFilter=e.generateStopWordFilter("acea aceasta această aceea acei aceia acel acela acele acelea acest acesta aceste acestea aceşti aceştia acolo acord acum ai aia aibă aici al ale alea altceva altcineva am ar are asemenea asta astea astăzi asupra au avea avem aveţi azi aş aşadar aţi bine bucur bună ca care caut ce cel ceva chiar cinci cine cineva contra cu cum cumva curând curînd când cât câte câtva câţi cînd cît cîte cîtva cîţi că căci cărei căror cărui către da dacă dar datorită dată dau de deci deja deoarece departe deşi din dinaintea dintr- dintre doi doilea două drept după dă ea ei el ele eram este eu eşti face fata fi fie fiecare fii fim fiu fiţi frumos fără graţie halbă iar ieri la le li lor lui lângă lîngă mai mea mei mele mereu meu mi mie mine mult multă mulţi mulţumesc mâine mîine mă ne nevoie nici nicăieri nimeni nimeri nimic nişte noastre noastră noi noroc nostru nouă noştri nu opt ori oricare orice oricine oricum oricând oricât oricînd oricît oriunde patra patru patrulea pe pentru peste pic poate pot prea prima primul prin puţin puţina puţină până pînă rog sa sale sau se spate spre sub sunt suntem sunteţi sută sînt sîntem sînteţi să săi său ta tale te timp tine toate toată tot totuşi toţi trei treia treilea tu tăi tău un una unde undeva unei uneia unele uneori unii unor unora unu unui unuia unul vi voastre voastră voi vostru vouă voştri vreme vreo vreun vă zece zero zi zice îi îl îmi împotriva în înainte înaintea încotro încât încît între întrucât întrucît îţi ăla ălea ăsta ăstea ăştia şapte şase şi ştiu ţi ţie".split(" ")),e.Pipeline.registerFunction(e.ro.stopWordFilter,"stopWordFilter-ro")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.ru.js b/docs/assets/javascripts/lunr/lunr.ru.js deleted file mode 100644 index 104bc6e8..00000000 --- a/docs/assets/javascripts/lunr/lunr.ru.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Russian` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var h,g,n;e.ru=function(){this.pipeline.reset(),this.pipeline.add(e.ru.trimmer,e.ru.stopWordFilter,e.ru.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ru.stemmer))},e.ru.wordCharacters="Ѐ-҄҇-ԯᴫᵸⷠ-ⷿꙀ-ꚟ︮︯",e.ru.trimmer=e.trimmerSupport.generateTrimmer(e.ru.wordCharacters),e.Pipeline.registerFunction(e.ru.trimmer,"trimmer-ru"),e.ru.stemmer=(h=e.stemmerSupport.Among,g=e.stemmerSupport.SnowballProgram,n=new function(){var n,e,r=[new h("в",-1,1),new h("ив",0,2),new h("ыв",0,2),new h("вши",-1,1),new h("ивши",3,2),new h("ывши",3,2),new h("вшись",-1,1),new h("ившись",6,2),new h("ывшись",6,2)],t=[new h("ее",-1,1),new h("ие",-1,1),new h("ое",-1,1),new h("ые",-1,1),new h("ими",-1,1),new h("ыми",-1,1),new h("ей",-1,1),new h("ий",-1,1),new h("ой",-1,1),new h("ый",-1,1),new h("ем",-1,1),new h("им",-1,1),new h("ом",-1,1),new h("ым",-1,1),new h("его",-1,1),new h("ого",-1,1),new h("ему",-1,1),new h("ому",-1,1),new h("их",-1,1),new h("ых",-1,1),new h("ею",-1,1),new h("ою",-1,1),new h("ую",-1,1),new h("юю",-1,1),new h("ая",-1,1),new h("яя",-1,1)],w=[new h("ем",-1,1),new h("нн",-1,1),new h("вш",-1,1),new h("ивш",2,2),new h("ывш",2,2),new h("щ",-1,1),new h("ющ",5,1),new h("ующ",6,2)],i=[new h("сь",-1,1),new h("ся",-1,1)],u=[new h("ла",-1,1),new h("ила",0,2),new h("ыла",0,2),new h("на",-1,1),new h("ена",3,2),new h("ете",-1,1),new h("ите",-1,2),new h("йте",-1,1),new h("ейте",7,2),new h("уйте",7,2),new h("ли",-1,1),new h("или",10,2),new h("ыли",10,2),new h("й",-1,1),new h("ей",13,2),new h("уй",13,2),new h("л",-1,1),new h("ил",16,2),new h("ыл",16,2),new h("ем",-1,1),new h("им",-1,2),new h("ым",-1,2),new h("н",-1,1),new h("ен",22,2),new h("ло",-1,1),new h("ило",24,2),new h("ыло",24,2),new h("но",-1,1),new h("ено",27,2),new h("нно",27,1),new h("ет",-1,1),new h("ует",30,2),new h("ит",-1,2),new h("ыт",-1,2),new h("ют",-1,1),new h("уют",34,2),new h("ят",-1,2),new h("ны",-1,1),new h("ены",37,2),new h("ть",-1,1),new h("ить",39,2),new h("ыть",39,2),new h("ешь",-1,1),new h("ишь",-1,2),new h("ю",-1,2),new h("ую",44,2)],s=[new h("а",-1,1),new h("ев",-1,1),new h("ов",-1,1),new h("е",-1,1),new h("ие",3,1),new h("ье",3,1),new h("и",-1,1),new h("еи",6,1),new h("ии",6,1),new h("ами",6,1),new h("ями",6,1),new h("иями",10,1),new h("й",-1,1),new h("ей",12,1),new h("ией",13,1),new h("ий",12,1),new h("ой",12,1),new h("ам",-1,1),new h("ем",-1,1),new h("ием",18,1),new h("ом",-1,1),new h("ям",-1,1),new h("иям",21,1),new h("о",-1,1),new h("у",-1,1),new h("ах",-1,1),new h("ях",-1,1),new h("иях",26,1),new h("ы",-1,1),new h("ь",-1,1),new h("ю",-1,1),new h("ию",30,1),new h("ью",30,1),new h("я",-1,1),new h("ия",33,1),new h("ья",33,1)],o=[new h("ост",-1,1),new h("ость",-1,1)],c=[new h("ейше",-1,1),new h("н",-1,2),new h("ейш",-1,1),new h("ь",-1,3)],m=[33,65,8,232],l=new g;function f(){for(;!l.in_grouping(m,1072,1103);){if(l.cursor>=l.limit)return!1;l.cursor++}return!0}function a(){for(;!l.out_grouping(m,1072,1103);){if(l.cursor>=l.limit)return!1;l.cursor++}return!0}function p(e,n){var r,t;if(l.ket=l.cursor,r=l.find_among_b(e,n)){switch(l.bra=l.cursor,r){case 1:if(t=l.limit-l.cursor,!l.eq_s_b(1,"а")&&(l.cursor=l.limit-t,!l.eq_s_b(1,"я")))return!1;case 2:l.slice_del()}return!0}return!1}function d(e,n){var r;return l.ket=l.cursor,!!(r=l.find_among_b(e,n))&&(l.bra=l.cursor,1==r&&l.slice_del(),!0)}function _(){return!!d(t,26)&&(p(w,8),!0)}function b(){var e;l.ket=l.cursor,(e=l.find_among_b(o,2))&&(l.bra=l.cursor,n<=l.cursor&&1==e&&l.slice_del())}this.setCurrent=function(e){l.setCurrent(e)},this.getCurrent=function(){return l.getCurrent()},this.stem=function(){return e=l.limit,n=e,f()&&(e=l.cursor,a()&&f()&&a()&&(n=l.cursor)),l.cursor=l.limit,!(l.cursor>3]&1<<(7&s))return this.cursor++,!0}return!1},in_grouping_b:function(r,t,i){if(this.cursor>this.limit_backward){var s=b.charCodeAt(this.cursor-1);if(s<=i&&t<=s&&r[(s-=t)>>3]&1<<(7&s))return this.cursor--,!0}return!1},out_grouping:function(r,t,i){if(this.cursor>3]&1<<(7&s)))return this.cursor++,!0}return!1},out_grouping_b:function(r,t,i){if(this.cursor>this.limit_backward){var s=b.charCodeAt(this.cursor-1);if(i>3]&1<<(7&s)))return this.cursor--,!0}return!1},eq_s:function(r,t){if(this.limit-this.cursor>1),a=0,f=u=(l=r[i]).s_size){if(this.cursor=e+l.s_size,!l.method)return l.result;var m=l.method();if(this.cursor=e+l.s_size,m)return l.result}if((i=l.substring_i)<0)return 0}},find_among_b:function(r,t){for(var i=0,s=t,e=this.cursor,n=this.limit_backward,u=0,o=0,h=!1;;){for(var c=i+(s-i>>1),a=0,f=u=(_=r[i]).s_size){if(this.cursor=e-_.s_size,!_.method)return _.result;var m=_.method();if(this.cursor=e-_.s_size,m)return _.result}if((i=_.substring_i)<0)return 0}},replace_s:function(r,t,i){var s=i.length-(t-r);return b=b.substring(0,r)+i+b.substring(t),this.limit+=s,this.cursor>=t?this.cursor+=s:this.cursor>r&&(this.cursor=r),s},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>b.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),b.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.sv.js b/docs/assets/javascripts/lunr/lunr.sv.js deleted file mode 100644 index a46a4e70..00000000 --- a/docs/assets/javascripts/lunr/lunr.sv.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Swedish` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,l,n;e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=(r=e.stemmerSupport.Among,l=e.stemmerSupport.SnowballProgram,n=new function(){var n,t,i=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],s=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],a=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],o=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],u=[119,127,149],m=new l;this.setCurrent=function(e){m.setCurrent(e)},this.getCurrent=function(){return m.getCurrent()},this.stem=function(){var e,r=m.cursor;return function(){var e,r=m.cursor+3;if(t=m.limit,0<=r||r<=m.limit){for(n=r;;){if(e=m.cursor,m.in_grouping(o,97,246)){m.cursor=e;break}if(m.cursor=e,m.cursor>=m.limit)return;m.cursor++}for(;!m.out_grouping(o,97,246);){if(m.cursor>=m.limit)return;m.cursor++}(t=m.cursor)=t&&(m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(i,37),m.limit_backward=r,e))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.in_grouping_b(u,98,121)&&m.slice_del()}}(),m.cursor=m.limit,e=m.limit_backward,m.cursor>=t&&(m.limit_backward=t,m.cursor=m.limit,m.find_among_b(s,7)&&(m.cursor=m.limit,m.ket=m.cursor,m.cursor>m.limit_backward&&(m.bra=--m.cursor,m.slice_del())),m.limit_backward=e),m.cursor=m.limit,function(){var e,r;if(m.cursor>=t){if(r=m.limit_backward,m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(a,5))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.slice_from("lös");break;case 3:m.slice_from("full")}m.limit_backward=r}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.th.js b/docs/assets/javascripts/lunr/lunr.th.js deleted file mode 100644 index 7f9887f7..00000000 --- a/docs/assets/javascripts/lunr/lunr.th.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Thai` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2017, Keerati Thiwanruk - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(t){if(void 0===t)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===t.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==t.version[0];t.th=function(){this.pipeline.reset(),this.pipeline.add(t.th.trimmer),i?this.tokenizer=t.th.tokenizer:(t.tokenizer&&(t.tokenizer=t.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=t.th.tokenizer))},t.th.wordCharacters="[฀-๿]",t.th.trimmer=t.trimmerSupport.generateTrimmer(t.th.wordCharacters),t.Pipeline.registerFunction(t.th.trimmer,"trimmer-th");var n=t.wordcut;n.init(),t.th.tokenizer=function(e){if(!arguments.length||null==e||null==e)return[];if(Array.isArray(e))return e.map(function(e){return i?new t.Token(e):e});var r=e.toString().replace(/^\s+/,"");return n.cut(r).split("|")}}}); \ No newline at end of file diff --git a/docs/assets/javascripts/lunr/lunr.tr.js b/docs/assets/javascripts/lunr/lunr.tr.js deleted file mode 100644 index 64ba95cb..00000000 --- a/docs/assets/javascripts/lunr/lunr.tr.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Lunr languages, `Turkish` language - * https://github.com/MihaiValentin/lunr-languages - * - * Copyright 2014, Mihai Valentin - * http://www.mozilla.org/MPL/ - */ -/*! - * based on - * Snowball JavaScript Library v0.3 - * http://code.google.com/p/urim/ - * http://snowball.tartarus.org/ - * - * Copyright 2010, Oleg Mazko - * http://www.mozilla.org/MPL/ - */ -!function(r,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(r.lunr)}(this,function(){return function(r){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var mr,dr,i;r.tr=function(){this.pipeline.reset(),this.pipeline.add(r.tr.trimmer,r.tr.stopWordFilter,r.tr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(r.tr.stemmer))},r.tr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",r.tr.trimmer=r.trimmerSupport.generateTrimmer(r.tr.wordCharacters),r.Pipeline.registerFunction(r.tr.trimmer,"trimmer-tr"),r.tr.stemmer=(mr=r.stemmerSupport.Among,dr=r.stemmerSupport.SnowballProgram,i=new function(){var t,r=[new mr("m",-1,-1),new mr("n",-1,-1),new mr("miz",-1,-1),new mr("niz",-1,-1),new mr("muz",-1,-1),new mr("nuz",-1,-1),new mr("müz",-1,-1),new mr("nüz",-1,-1),new mr("mız",-1,-1),new mr("nız",-1,-1)],i=[new mr("leri",-1,-1),new mr("ları",-1,-1)],e=[new mr("ni",-1,-1),new mr("nu",-1,-1),new mr("nü",-1,-1),new mr("nı",-1,-1)],n=[new mr("in",-1,-1),new mr("un",-1,-1),new mr("ün",-1,-1),new mr("ın",-1,-1)],u=[new mr("a",-1,-1),new mr("e",-1,-1)],o=[new mr("na",-1,-1),new mr("ne",-1,-1)],s=[new mr("da",-1,-1),new mr("ta",-1,-1),new mr("de",-1,-1),new mr("te",-1,-1)],c=[new mr("nda",-1,-1),new mr("nde",-1,-1)],l=[new mr("dan",-1,-1),new mr("tan",-1,-1),new mr("den",-1,-1),new mr("ten",-1,-1)],a=[new mr("ndan",-1,-1),new mr("nden",-1,-1)],m=[new mr("la",-1,-1),new mr("le",-1,-1)],d=[new mr("ca",-1,-1),new mr("ce",-1,-1)],f=[new mr("im",-1,-1),new mr("um",-1,-1),new mr("üm",-1,-1),new mr("ım",-1,-1)],b=[new mr("sin",-1,-1),new mr("sun",-1,-1),new mr("sün",-1,-1),new mr("sın",-1,-1)],w=[new mr("iz",-1,-1),new mr("uz",-1,-1),new mr("üz",-1,-1),new mr("ız",-1,-1)],_=[new mr("siniz",-1,-1),new mr("sunuz",-1,-1),new mr("sünüz",-1,-1),new mr("sınız",-1,-1)],k=[new mr("lar",-1,-1),new mr("ler",-1,-1)],p=[new mr("niz",-1,-1),new mr("nuz",-1,-1),new mr("nüz",-1,-1),new mr("nız",-1,-1)],g=[new mr("dir",-1,-1),new mr("tir",-1,-1),new mr("dur",-1,-1),new mr("tur",-1,-1),new mr("dür",-1,-1),new mr("tür",-1,-1),new mr("dır",-1,-1),new mr("tır",-1,-1)],y=[new mr("casına",-1,-1),new mr("cesine",-1,-1)],z=[new mr("di",-1,-1),new mr("ti",-1,-1),new mr("dik",-1,-1),new mr("tik",-1,-1),new mr("duk",-1,-1),new mr("tuk",-1,-1),new mr("dük",-1,-1),new mr("tük",-1,-1),new mr("dık",-1,-1),new mr("tık",-1,-1),new mr("dim",-1,-1),new mr("tim",-1,-1),new mr("dum",-1,-1),new mr("tum",-1,-1),new mr("düm",-1,-1),new mr("tüm",-1,-1),new mr("dım",-1,-1),new mr("tım",-1,-1),new mr("din",-1,-1),new mr("tin",-1,-1),new mr("dun",-1,-1),new mr("tun",-1,-1),new mr("dün",-1,-1),new mr("tün",-1,-1),new mr("dın",-1,-1),new mr("tın",-1,-1),new mr("du",-1,-1),new mr("tu",-1,-1),new mr("dü",-1,-1),new mr("tü",-1,-1),new mr("dı",-1,-1),new mr("tı",-1,-1)],h=[new mr("sa",-1,-1),new mr("se",-1,-1),new mr("sak",-1,-1),new mr("sek",-1,-1),new mr("sam",-1,-1),new mr("sem",-1,-1),new mr("san",-1,-1),new mr("sen",-1,-1)],v=[new mr("miş",-1,-1),new mr("muş",-1,-1),new mr("müş",-1,-1),new mr("mış",-1,-1)],q=[new mr("b",-1,1),new mr("c",-1,2),new mr("d",-1,3),new mr("ğ",-1,4)],C=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,8,0,0,0,0,0,0,1],P=[1,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1],F=[65],S=[65],W=[["a",[1,64,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],97,305],["e",[17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130],101,252],["ı",[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],97,305],["i",[17],101,105],["o",F,111,117],["ö",S,246,252],["u",F,111,117]],L=new dr;function x(r,i,e){for(;;){var n=L.limit-L.cursor;if(L.in_grouping_b(r,i,e)){L.cursor=L.limit-n;break}if(L.cursor=L.limit-n,L.cursor<=L.limit_backward)return!1;L.cursor--}return!0}function A(){var r,i;r=L.limit-L.cursor,x(C,97,305);for(var e=0;eL.limit_backward&&(L.cursor--,e=L.limit-L.cursor,i()))?(L.cursor=L.limit-e,!0):(L.cursor=L.limit-n,r()?(L.cursor=L.limit-n,!1):(L.cursor=L.limit-n,!(L.cursor<=L.limit_backward)&&(L.cursor--,!!i()&&(L.cursor=L.limit-n,!0))))}function j(r){return E(r,function(){return L.in_grouping_b(C,97,305)})}function T(){return j(function(){return L.eq_s_b(1,"n")})}function Z(){return j(function(){return L.eq_s_b(1,"y")})}function B(){return L.find_among_b(r,10)&&E(function(){return L.in_grouping_b(P,105,305)},function(){return L.out_grouping_b(C,97,305)})}function D(){return A()&&L.in_grouping_b(P,105,305)&&j(function(){return L.eq_s_b(1,"s")})}function G(){return L.find_among_b(i,2)}function H(){return A()&&L.find_among_b(n,4)&&T()}function I(){return A()&&L.find_among_b(s,4)}function J(){return A()&&L.find_among_b(c,2)}function K(){return A()&&L.find_among_b(f,4)&&Z()}function M(){return A()&&L.find_among_b(b,4)}function N(){return A()&&L.find_among_b(w,4)&&Z()}function O(){return L.find_among_b(_,4)}function Q(){return A()&&L.find_among_b(k,2)}function R(){return A()&&L.find_among_b(g,8)}function U(){return A()&&L.find_among_b(z,32)&&Z()}function V(){return L.find_among_b(h,8)&&Z()}function X(){return A()&&L.find_among_b(v,4)&&Z()}function Y(){var r=L.limit-L.cursor;return!(X()||(L.cursor=L.limit-r,U()||(L.cursor=L.limit-r,V()||(L.cursor=L.limit-r,L.eq_s_b(3,"ken")&&Z()))))}function $(){if(L.find_among_b(y,2)){var r=L.limit-L.cursor;if(O()||(L.cursor=L.limit-r,Q()||(L.cursor=L.limit-r,K()||(L.cursor=L.limit-r,M()||(L.cursor=L.limit-r,N()||(L.cursor=L.limit-r))))),X())return!1}return!0}function rr(){if(!A()||!L.find_among_b(p,4))return!0;var r=L.limit-L.cursor;return!U()&&(L.cursor=L.limit-r,!V())}function ir(){var r,i,e,n=L.limit-L.cursor;if(L.ket=L.cursor,t=!0,Y()&&(L.cursor=L.limit-n,$()&&(L.cursor=L.limit-n,function(){if(Q()){L.bra=L.cursor,L.slice_del();var r=L.limit-L.cursor;return L.ket=L.cursor,R()||(L.cursor=L.limit-r,U()||(L.cursor=L.limit-r,V()||(L.cursor=L.limit-r,X()||(L.cursor=L.limit-r)))),t=!1}return!0}()&&(L.cursor=L.limit-n,rr()&&(L.cursor=L.limit-n,e=L.limit-L.cursor,!(O()||(L.cursor=L.limit-e,N()||(L.cursor=L.limit-e,M()||(L.cursor=L.limit-e,K()))))||(L.bra=L.cursor,L.slice_del(),i=L.limit-L.cursor,L.ket=L.cursor,X()||(L.cursor=L.limit-i),0)))))){if(L.cursor=L.limit-n,!R())return;L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,r=L.limit-L.cursor,O()||(L.cursor=L.limit-r,Q()||(L.cursor=L.limit-r,K()||(L.cursor=L.limit-r,M()||(L.cursor=L.limit-r,N()||(L.cursor=L.limit-r))))),X()||(L.cursor=L.limit-r)}L.bra=L.cursor,L.slice_del()}function er(){var r,i,e,n;if(L.ket=L.cursor,L.eq_s_b(2,"ki")){if(r=L.limit-L.cursor,I())return L.bra=L.cursor,L.slice_del(),i=L.limit-L.cursor,L.ket=L.cursor,Q()?(L.bra=L.cursor,L.slice_del(),er()):(L.cursor=L.limit-i,B()&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er()))),!0;if(L.cursor=L.limit-r,H()){if(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,e=L.limit-L.cursor,G())L.bra=L.cursor,L.slice_del();else{if(L.cursor=L.limit-e,L.ket=L.cursor,!B()&&(L.cursor=L.limit-e,!D()&&(L.cursor=L.limit-e,!er())))return!0;L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())}return!0}if(L.cursor=L.limit-r,J()){if(n=L.limit-L.cursor,G())L.bra=L.cursor,L.slice_del();else if(L.cursor=L.limit-n,D())L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er());else if(L.cursor=L.limit-n,!er())return!1;return!0}}return!1}function nr(r){if(L.ket=L.cursor,!J()&&(L.cursor=L.limit-r,!A()||!L.find_among_b(o,2)))return!1;var i=L.limit-L.cursor;if(G())L.bra=L.cursor,L.slice_del();else if(L.cursor=L.limit-i,D())L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er());else if(L.cursor=L.limit-i,!er())return!1;return!0}function tr(r){if(L.ket=L.cursor,!(A()&&L.find_among_b(a,2)||(L.cursor=L.limit-r,A()&&L.find_among_b(e,4))))return!1;var i=L.limit-L.cursor;return!(!D()&&(L.cursor=L.limit-i,!G()))&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er()),!0)}function ur(){var r,i=L.limit-L.cursor;return L.ket=L.cursor,!!(H()||(L.cursor=L.limit-i,A()&&L.find_among_b(m,2)&&Z()))&&(L.bra=L.cursor,L.slice_del(),r=L.limit-L.cursor,L.ket=L.cursor,!(!Q()||(L.bra=L.cursor,L.slice_del(),!er()))||(L.cursor=L.limit-r,L.ket=L.cursor,(B()||(L.cursor=L.limit-r,D()||(L.cursor=L.limit-r,er())))&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())),!0))}function or(){var r,i,e=L.limit-L.cursor;if(L.ket=L.cursor,!(I()||(L.cursor=L.limit-e,A()&&L.in_grouping_b(P,105,305)&&Z()||(L.cursor=L.limit-e,A()&&L.find_among_b(u,2)&&Z()))))return!1;if(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,r=L.limit-L.cursor,B())L.bra=L.cursor,L.slice_del(),i=L.limit-L.cursor,L.ket=L.cursor,Q()||(L.cursor=L.limit-i);else if(L.cursor=L.limit-r,!Q())return!0;return L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,er(),!0}function sr(){var r,i,e=L.limit-L.cursor;if(L.ket=L.cursor,Q())return L.bra=L.cursor,L.slice_del(),void er();if(L.cursor=L.limit-e,L.ket=L.cursor,A()&&L.find_among_b(d,2)&&T())if(L.bra=L.cursor,L.slice_del(),r=L.limit-L.cursor,L.ket=L.cursor,G())L.bra=L.cursor,L.slice_del();else{if(L.cursor=L.limit-r,L.ket=L.cursor,!B()&&(L.cursor=L.limit-r,!D())){if(L.cursor=L.limit-r,L.ket=L.cursor,!Q())return;if(L.bra=L.cursor,L.slice_del(),!er())return}L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())}else if(L.cursor=L.limit-e,!nr(e)&&(L.cursor=L.limit-e,!tr(e))){if(L.cursor=L.limit-e,L.ket=L.cursor,A()&&L.find_among_b(l,4))return L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,i=L.limit-L.cursor,void(B()?(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())):(L.cursor=L.limit-i,Q()?(L.bra=L.cursor,L.slice_del()):L.cursor=L.limit-i,er()));if(L.cursor=L.limit-e,!ur()){if(L.cursor=L.limit-e,G())return L.bra=L.cursor,void L.slice_del();L.cursor=L.limit-e,er()||(L.cursor=L.limit-e,or()||(L.cursor=L.limit-e,L.ket=L.cursor,(B()||(L.cursor=L.limit-e,D()))&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er()))))}}}function cr(r,i,e){if(L.cursor=L.limit-r,function(){for(;;){var r=L.limit-L.cursor;if(L.in_grouping_b(C,97,305)){L.cursor=L.limit-r;break}if(L.cursor=L.limit-r,L.cursor<=L.limit_backward)return!1;L.cursor--}return!0}()){var n=L.limit-L.cursor;if(!L.eq_s_b(1,i)&&(L.cursor=L.limit-n,!L.eq_s_b(1,e)))return!0;L.cursor=L.limit-r;var t=L.cursor;return L.insert(L.cursor,L.cursor,e),L.cursor=t,!1}return!0}function lr(r,i,e){for(;!L.eq_s(i,e);){if(L.cursor>=L.limit)return!0;L.cursor++}return i!=L.limit||(L.cursor=r,!1)}function ar(){var r,i,e=L.cursor;return!(!lr(r=L.cursor,2,"ad")||!lr(L.cursor=r,5,"soyad"))&&(L.limit_backward=e,L.cursor=L.limit,i=L.limit-L.cursor,(L.eq_s_b(1,"d")||(L.cursor=L.limit-i,L.eq_s_b(1,"g")))&&cr(i,"a","ı")&&cr(i,"e","i")&&cr(i,"o","u")&&cr(i,"ö","ü"),L.cursor=L.limit,function(){var r;if(L.ket=L.cursor,r=L.find_among_b(q,4))switch(L.bra=L.cursor,r){case 1:L.slice_from("p");break;case 2:L.slice_from("ç");break;case 3:L.slice_from("t");break;case 4:L.slice_from("k")}}(),!0)}this.setCurrent=function(r){L.setCurrent(r)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){return!!(function(){for(var r,i=L.cursor,e=2;;){for(r=L.cursor;!L.in_grouping(C,97,305);){if(L.cursor>=L.limit)return L.cursor=r,!(0e&&(this._events[n].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[n].length),"function"==typeof console.trace&&console.trace()));return this},r.prototype.once=function(n,t){if(!a(t))throw TypeError("listener must be a function");var e=!1;function r(){this.removeListener(n,r),e||(e=!0,t.apply(this,arguments))}return r.listener=t,this.on(n,r),this},r.prototype.removeListener=function(n,t){var e,r,i,o;if(!a(t))throw TypeError("listener must be a function");if(!this._events||!this._events[n])return this;if(i=(e=this._events[n]).length,r=-1,e===t||a(e.listener)&&e.listener===t)delete this._events[n],this._events.removeListener&&this.emit("removeListener",n,t);else if(c(e)){for(o=i;0this.maxLength)return i();if(!this.stat&&p(this.cache,o)){var t=this.cache[o];if(Array.isArray(t)&&(t="DIR"),!n||"DIR"===t)return i(null,t);if(n&&"FILE"===t)return i()}var e=this.statCache[o];if(void 0!==e){if(!1===e)return i(null,e);var s=e.isDirectory()?"DIR":"FILE";return n&&"FILE"===s?i():i(null,s,e)}var a=this,c=d("stat\0"+o,function(n,e){{if(e&&e.isSymbolicLink())return u.stat(o,function(n,t){n?a._stat2(r,o,null,e,i):a._stat2(r,o,n,t,i)});a._stat2(r,o,n,e,i)}});c&&u.lstat(o,c)},b.prototype._stat2=function(n,t,e,r,i){if(e)return this.statCache[t]=!1,i();var o="/"===n.slice(-1);if(this.statCache[t]=r,"/"===t.slice(-1)&&!r.isDirectory())return i(null,!1,r);var s=r.isDirectory()?"DIR":"FILE";return this.cache[t]=this.cache[t]||s,o&&"DIR"!==s?i():i(null,s,r)}}).call(this,_("_process"))},{"./common.js":15,"./sync.js":17,_process:24,assert:9,events:14,fs:12,inflight:18,inherits:19,minimatch:20,once:21,path:22,"path-is-absolute":23,util:28}],17:[function(e,r,n){(function(i){(r.exports=n).GlobSync=h;var s=e("fs"),c=e("minimatch"),g=(c.Minimatch,e("./glob.js").Glob,e("util"),e("path")),u=e("assert"),l=e("path-is-absolute"),t=e("./common.js"),o=(t.alphasort,t.alphasorti,t.setopts),a=t.ownProp,f=t.childrenIgnored;function n(n,t){if("function"==typeof t||3===arguments.length)throw new TypeError("callback provided to sync glob\nSee: https://github.com/isaacs/node-glob/issues/167");return new h(n,t).found}function h(n,t){if(!n)throw new Error("must provide pattern");if("function"==typeof t||3===arguments.length)throw new TypeError("callback provided to sync glob\nSee: https://github.com/isaacs/node-glob/issues/167");if(!(this instanceof h))return new h(n,t);if(o(this,n,t),this.noprocess)return this;var e=this.minimatch.set.length;this.matches=new Array(e);for(var r=0;rthis.maxLength)return!1;if(!this.stat&&a(this.cache,t)){var r=this.cache[t];if(Array.isArray(r)&&(r="DIR"),!e||"DIR"===r)return r;if(e&&"FILE"===r)return!1}var i=this.statCache[t];if(!i){var o;try{o=s.lstatSync(t)}catch(n){return!1}if(o.isSymbolicLink())try{i=s.statSync(t)}catch(n){i=o}else i=o}r=(this.statCache[t]=i).isDirectory()?"DIR":"FILE";return this.cache[t]=this.cache[t]||r,(!e||"DIR"===r)&&r},h.prototype._mark=function(n){return t.mark(this,n)},h.prototype._makeAbs=function(n){return t.makeAbs(this,n)}}).call(this,e("_process"))},{"./common.js":15,"./glob.js":16,_process:24,assert:9,fs:12,minimatch:20,path:22,"path-is-absolute":23,util:28}],18:[function(t,r,n){(function(s){var n=t("wrappy"),a=Object.create(null),e=t("once");r.exports=n(function(n,t){return a[n]?(a[n].push(t),null):(a[n]=[t],o=n,e(function n(){var t=a[o],e=t.length,r=function(n){for(var t=n.length,e=[],r=0;re?(t.splice(0,e),s.nextTick(function(){n.apply(null,r)})):delete a[o]}}));var o})}).call(this,t("_process"))},{_process:24,once:21,wrappy:29}],19:[function(n,t,e){"function"==typeof Object.create?t.exports=function(n,t){n.super_=t,n.prototype=Object.create(t.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}})}:t.exports=function(n,t){n.super_=t;var e=function(){};e.prototype=t.prototype,n.prototype=new e,n.prototype.constructor=n}},{}],20:[function(n,t,e){(t.exports=s).Minimatch=i;var u={sep:"/"};try{u=n("path")}catch(n){}var M=s.GLOBSTAR=i.GLOBSTAR={},r=n("brace-expansion"),C={"!":{open:"(?:(?!(?:",close:"))[^/]*?)"},"?":{open:"(?:",close:")?"},"+":{open:"(?:",close:")+"},"*":{open:"(?:",close:")*"},"@":{open:"(?:",close:")"}},P="[^/]",z=P+"*?",B="().*{}+?[]^$\\!".split("").reduce(function(n,t){return n[t]=!0,n},{});var l=/\/+/;function o(t,e){t=t||{},e=e||{};var r={};return Object.keys(e).forEach(function(n){r[n]=e[n]}),Object.keys(t).forEach(function(n){r[n]=t[n]}),r}function s(n,t,e){if("string"!=typeof t)throw new TypeError("glob pattern string required");return e||(e={}),!(!e.nocomment&&"#"===t.charAt(0))&&(""===t.trim()?""===n:new i(t,e).match(n))}function i(n,t){if(!(this instanceof i))return new i(n,t);if("string"!=typeof n)throw new TypeError("glob pattern string required");t||(t={}),n=n.trim(),"/"!==u.sep&&(n=n.split(u.sep).join("/")),this.options=t,this.set=[],this.pattern=n,this.regexp=null,this.negate=!1,this.comment=!1,this.empty=!1,this.make()}function a(n,t){if(t||(t=this instanceof i?this.options:{}),void 0===(n=void 0===n?this.pattern:n))throw new TypeError("undefined pattern");return t.nobrace||!n.match(/\{.*\}/)?[n]:r(n)}s.filter=function(r,i){return i=i||{},function(n,t,e){return s(n,r,i)}},s.defaults=function(r){if(!r||!Object.keys(r).length)return s;var i=s,n=function(n,t,e){return i.minimatch(n,t,o(r,e))};return n.Minimatch=function(n,t){return new i.Minimatch(n,o(r,t))},n},i.defaults=function(n){return n&&Object.keys(n).length?s.defaults(n).Minimatch:i},i.prototype.debug=function(){},i.prototype.make=function(){if(this._made)return;var n=this.pattern,t=this.options;if(!t.nocomment&&"#"===n.charAt(0))return void(this.comment=!0);if(!n)return void(this.empty=!0);this.parseNegate();var e=this.globSet=this.braceExpand();t.debug&&(this.debug=console.error);this.debug(this.pattern,e),e=this.globParts=e.map(function(n){return n.split(l)}),this.debug(this.pattern,e),e=e.map(function(n,t,e){return n.map(this.parse,this)},this),this.debug(this.pattern,e),e=e.filter(function(n){return-1===n.indexOf(!1)}),this.debug(this.pattern,e),this.set=e},i.prototype.parseNegate=function(){var n=this.pattern,t=!1,e=this.options,r=0;if(e.nonegate)return;for(var i=0,o=n.length;i>> no match, partial?",n,f,t,h),f!==s))}if("string"==typeof u?(c=r.nocase?l.toLowerCase()===u.toLowerCase():l===u,this.debug("string match",u,l,c)):(c=l.match(u),this.debug("pattern match",u,l,c)),!c)return!1}if(i===s&&o===a)return!0;if(i===s)return e;if(o===a)return i===s-1&&""===n[i];throw new Error("wtf?")}},{"brace-expansion":11,path:22}],21:[function(n,t,e){var r=n("wrappy");function i(n){var t=function(){return t.called?t.value:(t.called=!0,t.value=n.apply(this,arguments))};return t.called=!1,t}function o(n){var t=function(){if(t.called)throw new Error(t.onceError);return t.called=!0,t.value=n.apply(this,arguments)},e=n.name||"Function wrapped with `once`";return t.onceError=e+" shouldn't be called more than once",t.called=!1,t}t.exports=r(i),t.exports.strict=r(o),i.proto=i(function(){Object.defineProperty(Function.prototype,"once",{value:function(){return i(this)},configurable:!0}),Object.defineProperty(Function.prototype,"onceStrict",{value:function(){return o(this)},configurable:!0})})},{wrappy:29}],22:[function(n,t,u){(function(i){function o(n,t){for(var e=0,r=n.length-1;0<=r;r--){var i=n[r];"."===i?n.splice(r,1):".."===i?(n.splice(r,1),e++):e&&(n.splice(r,1),e--)}if(t)for(;e--;e)n.unshift("..");return n}var t=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,s=function(n){return t.exec(n).slice(1)};function a(n,t){if(n.filter)return n.filter(t);for(var e=[],r=0;r":">",'"':""","'":"'","`":"`"},D=d.invert(N),F=function(t){var e=function(n){return t[n]},n="(?:"+d.keys(t).join("|")+")",r=RegExp(n),i=RegExp(n,"g");return function(n){return n=null==n?"":""+n,r.test(n)?n.replace(i,e):n}};d.escape=F(N),d.unescape=F(D),d.result=function(n,t,e){var r=null==n?void 0:n[t];return void 0===r&&(r=e),d.isFunction(r)?r.call(n):r};var M=0;d.uniqueId=function(n){var t=++M+"";return n?n+t:t},d.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var C=/(.)^/,P={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},z=/\\|'|\r|\n|\u2028|\u2029/g,B=function(n){return"\\"+P[n]};d.template=function(o,n,t){!n&&t&&(n=t),n=d.defaults({},n,d.templateSettings);var e=RegExp([(n.escape||C).source,(n.interpolate||C).source,(n.evaluate||C).source].join("|")+"|$","g"),s=0,a="__p+='";o.replace(e,function(n,t,e,r,i){return a+=o.slice(s,i).replace(z,B),s=i+n.length,t?a+="'+\n((__t=("+t+"))==null?'':_.escape(__t))+\n'":e?a+="'+\n((__t=("+e+"))==null?'':__t)+\n'":r&&(a+="';\n"+r+"\n__p+='"),n}),a+="';\n",n.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{var r=new Function(n.variable||"obj","_",a)}catch(n){throw n.source=a,n}var i=function(n){return r.call(this,n,d)},c=n.variable||"obj";return i.source="function("+c+"){\n"+a+"}",i},d.chain=function(n){var t=d(n);return t._chain=!0,t};var U=function(n,t){return n._chain?d(t).chain():t};d.mixin=function(e){d.each(d.functions(e),function(n){var t=d[n]=e[n];d.prototype[n]=function(){var n=[this._wrapped];return i.apply(n,arguments),U(this,t.apply(d,n))}})},d.mixin(d),d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(t){var e=r[t];d.prototype[t]=function(){var n=this._wrapped;return e.apply(n,arguments),"shift"!==t&&"splice"!==t||0!==n.length||delete n[0],U(this,n)}}),d.each(["concat","join","slice"],function(n){var t=r[n];d.prototype[n]=function(){return U(this,t.apply(this._wrapped,arguments))}}),d.prototype.value=function(){return this._wrapped},d.prototype.valueOf=d.prototype.toJSON=d.prototype.value,d.prototype.toString=function(){return""+this._wrapped}}).call(this)},{}],26:[function(n,t,e){arguments[4][19][0].apply(e,arguments)},{dup:19}],27:[function(n,t,e){t.exports=function(n){return n&&"object"==typeof n&&"function"==typeof n.copy&&"function"==typeof n.fill&&"function"==typeof n.readUInt8}},{}],28:[function(h,n,k){(function(r,i){var a=/%[sdj%]/g;k.format=function(n){if(!_(n)){for(var t=[],e=0;e.md-nav__link{color:inherit}button[data-md-color-primary=pink]{background-color:#e91e63}[data-md-color-primary=pink] .md-typeset a{color:#e91e63}[data-md-color-primary=pink] .md-header,[data-md-color-primary=pink] .md-hero{background-color:#e91e63}[data-md-color-primary=pink] .md-nav__link--active,[data-md-color-primary=pink] .md-nav__link:active{color:#e91e63}[data-md-color-primary=pink] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=purple]{background-color:#ab47bc}[data-md-color-primary=purple] .md-typeset a{color:#ab47bc}[data-md-color-primary=purple] .md-header,[data-md-color-primary=purple] .md-hero{background-color:#ab47bc}[data-md-color-primary=purple] .md-nav__link--active,[data-md-color-primary=purple] .md-nav__link:active{color:#ab47bc}[data-md-color-primary=purple] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=deep-purple]{background-color:#7e57c2}[data-md-color-primary=deep-purple] .md-typeset a{color:#7e57c2}[data-md-color-primary=deep-purple] .md-header,[data-md-color-primary=deep-purple] .md-hero{background-color:#7e57c2}[data-md-color-primary=deep-purple] .md-nav__link--active,[data-md-color-primary=deep-purple] .md-nav__link:active{color:#7e57c2}[data-md-color-primary=deep-purple] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=indigo]{background-color:#3f51b5}[data-md-color-primary=indigo] .md-typeset a{color:#3f51b5}[data-md-color-primary=indigo] .md-header,[data-md-color-primary=indigo] .md-hero{background-color:#3f51b5}[data-md-color-primary=indigo] .md-nav__link--active,[data-md-color-primary=indigo] .md-nav__link:active{color:#3f51b5}[data-md-color-primary=indigo] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=blue]{background-color:#2196f3}[data-md-color-primary=blue] .md-typeset a{color:#2196f3}[data-md-color-primary=blue] .md-header,[data-md-color-primary=blue] .md-hero{background-color:#2196f3}[data-md-color-primary=blue] .md-nav__link--active,[data-md-color-primary=blue] .md-nav__link:active{color:#2196f3}[data-md-color-primary=blue] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=light-blue]{background-color:#03a9f4}[data-md-color-primary=light-blue] .md-typeset a{color:#03a9f4}[data-md-color-primary=light-blue] .md-header,[data-md-color-primary=light-blue] .md-hero{background-color:#03a9f4}[data-md-color-primary=light-blue] .md-nav__link--active,[data-md-color-primary=light-blue] .md-nav__link:active{color:#03a9f4}[data-md-color-primary=light-blue] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=cyan]{background-color:#00bcd4}[data-md-color-primary=cyan] .md-typeset a{color:#00bcd4}[data-md-color-primary=cyan] .md-header,[data-md-color-primary=cyan] .md-hero{background-color:#00bcd4}[data-md-color-primary=cyan] .md-nav__link--active,[data-md-color-primary=cyan] .md-nav__link:active{color:#00bcd4}[data-md-color-primary=cyan] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=teal]{background-color:#009688}[data-md-color-primary=teal] .md-typeset a{color:#009688}[data-md-color-primary=teal] .md-header,[data-md-color-primary=teal] .md-hero{background-color:#009688}[data-md-color-primary=teal] .md-nav__link--active,[data-md-color-primary=teal] .md-nav__link:active{color:#009688}[data-md-color-primary=teal] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=green]{background-color:#4caf50}[data-md-color-primary=green] .md-typeset a{color:#4caf50}[data-md-color-primary=green] .md-header,[data-md-color-primary=green] .md-hero{background-color:#4caf50}[data-md-color-primary=green] .md-nav__link--active,[data-md-color-primary=green] .md-nav__link:active{color:#4caf50}[data-md-color-primary=green] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=light-green]{background-color:#7cb342}[data-md-color-primary=light-green] .md-typeset a{color:#7cb342}[data-md-color-primary=light-green] .md-header,[data-md-color-primary=light-green] .md-hero{background-color:#7cb342}[data-md-color-primary=light-green] .md-nav__link--active,[data-md-color-primary=light-green] .md-nav__link:active{color:#7cb342}[data-md-color-primary=light-green] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=lime]{background-color:#c0ca33}[data-md-color-primary=lime] .md-typeset a{color:#c0ca33}[data-md-color-primary=lime] .md-header,[data-md-color-primary=lime] .md-hero{background-color:#c0ca33}[data-md-color-primary=lime] .md-nav__link--active,[data-md-color-primary=lime] .md-nav__link:active{color:#c0ca33}[data-md-color-primary=lime] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=yellow]{background-color:#f9a825}[data-md-color-primary=yellow] .md-typeset a{color:#f9a825}[data-md-color-primary=yellow] .md-header,[data-md-color-primary=yellow] .md-hero{background-color:#f9a825}[data-md-color-primary=yellow] .md-nav__link--active,[data-md-color-primary=yellow] .md-nav__link:active{color:#f9a825}[data-md-color-primary=yellow] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=amber]{background-color:#ffa000}[data-md-color-primary=amber] .md-typeset a{color:#ffa000}[data-md-color-primary=amber] .md-header,[data-md-color-primary=amber] .md-hero{background-color:#ffa000}[data-md-color-primary=amber] .md-nav__link--active,[data-md-color-primary=amber] .md-nav__link:active{color:#ffa000}[data-md-color-primary=amber] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=orange]{background-color:#fb8c00}[data-md-color-primary=orange] .md-typeset a{color:#fb8c00}[data-md-color-primary=orange] .md-header,[data-md-color-primary=orange] .md-hero{background-color:#fb8c00}[data-md-color-primary=orange] .md-nav__link--active,[data-md-color-primary=orange] .md-nav__link:active{color:#fb8c00}[data-md-color-primary=orange] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=deep-orange]{background-color:#ff7043}[data-md-color-primary=deep-orange] .md-typeset a{color:#ff7043}[data-md-color-primary=deep-orange] .md-header,[data-md-color-primary=deep-orange] .md-hero{background-color:#ff7043}[data-md-color-primary=deep-orange] .md-nav__link--active,[data-md-color-primary=deep-orange] .md-nav__link:active{color:#ff7043}[data-md-color-primary=deep-orange] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=brown]{background-color:#795548}[data-md-color-primary=brown] .md-typeset a{color:#795548}[data-md-color-primary=brown] .md-header,[data-md-color-primary=brown] .md-hero{background-color:#795548}[data-md-color-primary=brown] .md-nav__link--active,[data-md-color-primary=brown] .md-nav__link:active{color:#795548}[data-md-color-primary=brown] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=grey]{background-color:#757575}[data-md-color-primary=grey] .md-typeset a{color:#757575}[data-md-color-primary=grey] .md-header,[data-md-color-primary=grey] .md-hero{background-color:#757575}[data-md-color-primary=grey] .md-nav__link--active,[data-md-color-primary=grey] .md-nav__link:active{color:#757575}[data-md-color-primary=grey] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=blue-grey]{background-color:#546e7a}[data-md-color-primary=blue-grey] .md-typeset a{color:#546e7a}[data-md-color-primary=blue-grey] .md-header,[data-md-color-primary=blue-grey] .md-hero{background-color:#546e7a}[data-md-color-primary=blue-grey] .md-nav__link--active,[data-md-color-primary=blue-grey] .md-nav__link:active{color:#546e7a}[data-md-color-primary=blue-grey] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=white]{box-shadow:inset 0 0 .05rem rgba(0,0,0,.54)}[data-md-color-primary=white] .md-header,[data-md-color-primary=white] .md-hero,button[data-md-color-primary=white]{background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-hero--expand{border-bottom:.05rem solid rgba(0,0,0,.07)}[data-md-color-primary=black] .md-header,[data-md-color-primary=black] .md-hero,button[data-md-color-primary=black]{background-color:#000}button[data-md-color-accent=red]{background-color:#ff1744}[data-md-color-accent=red] .md-typeset a:active,[data-md-color-accent=red] .md-typeset a:hover{color:#ff1744}[data-md-color-accent=red] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=red] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-nav__link:focus,[data-md-color-accent=red] .md-nav__link:hover,[data-md-color-accent=red] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=red] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=red] .md-typeset .md-clipboard:active:before,[data-md-color-accent=red] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=red] .md-typeset [id] .headerlink:focus,[data-md-color-accent=red] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=red] .md-typeset [id]:target .headerlink{color:#ff1744}[data-md-color-accent=red] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-search-result__link:hover,[data-md-color-accent=red] .md-search-result__link[data-md-state=active]{background-color:rgba(255,23,68,.1)}[data-md-color-accent=red] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-source-file:hover:before{background-color:#ff1744}button[data-md-color-accent=pink]{background-color:#f50057}[data-md-color-accent=pink] .md-typeset a:active,[data-md-color-accent=pink] .md-typeset a:hover{color:#f50057}[data-md-color-accent=pink] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=pink] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-nav__link:focus,[data-md-color-accent=pink] .md-nav__link:hover,[data-md-color-accent=pink] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=pink] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=pink] .md-typeset .md-clipboard:active:before,[data-md-color-accent=pink] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=pink] .md-typeset [id] .headerlink:focus,[data-md-color-accent=pink] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=pink] .md-typeset [id]:target .headerlink{color:#f50057}[data-md-color-accent=pink] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-search-result__link:hover,[data-md-color-accent=pink] .md-search-result__link[data-md-state=active]{background-color:rgba(245,0,87,.1)}[data-md-color-accent=pink] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-source-file:hover:before{background-color:#f50057}button[data-md-color-accent=purple]{background-color:#e040fb}[data-md-color-accent=purple] .md-typeset a:active,[data-md-color-accent=purple] .md-typeset a:hover{color:#e040fb}[data-md-color-accent=purple] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=purple] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-nav__link:focus,[data-md-color-accent=purple] .md-nav__link:hover,[data-md-color-accent=purple] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=purple] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=purple] .md-typeset .md-clipboard:active:before,[data-md-color-accent=purple] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=purple] .md-typeset [id] .headerlink:focus,[data-md-color-accent=purple] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=purple] .md-typeset [id]:target .headerlink{color:#e040fb}[data-md-color-accent=purple] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-search-result__link:hover,[data-md-color-accent=purple] .md-search-result__link[data-md-state=active]{background-color:rgba(224,64,251,.1)}[data-md-color-accent=purple] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-source-file:hover:before{background-color:#e040fb}button[data-md-color-accent=deep-purple]{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-typeset a:active,[data-md-color-accent=deep-purple] .md-typeset a:hover{color:#7c4dff}[data-md-color-accent=deep-purple] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=deep-purple] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-nav__link:focus,[data-md-color-accent=deep-purple] .md-nav__link:hover,[data-md-color-accent=deep-purple] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=deep-purple] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=deep-purple] .md-typeset .md-clipboard:active:before,[data-md-color-accent=deep-purple] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=deep-purple] .md-typeset [id] .headerlink:focus,[data-md-color-accent=deep-purple] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=deep-purple] .md-typeset [id]:target .headerlink{color:#7c4dff}[data-md-color-accent=deep-purple] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-search-result__link:hover,[data-md-color-accent=deep-purple] .md-search-result__link[data-md-state=active]{background-color:rgba(124,77,255,.1)}[data-md-color-accent=deep-purple] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-source-file:hover:before{background-color:#7c4dff}button[data-md-color-accent=indigo]{background-color:#536dfe}[data-md-color-accent=indigo] .md-typeset a:active,[data-md-color-accent=indigo] .md-typeset a:hover{color:#536dfe}[data-md-color-accent=indigo] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=indigo] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-nav__link:focus,[data-md-color-accent=indigo] .md-nav__link:hover,[data-md-color-accent=indigo] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=indigo] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=indigo] .md-typeset .md-clipboard:active:before,[data-md-color-accent=indigo] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=indigo] .md-typeset [id] .headerlink:focus,[data-md-color-accent=indigo] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=indigo] .md-typeset [id]:target .headerlink{color:#536dfe}[data-md-color-accent=indigo] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-search-result__link:hover,[data-md-color-accent=indigo] .md-search-result__link[data-md-state=active]{background-color:rgba(83,109,254,.1)}[data-md-color-accent=indigo] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-source-file:hover:before{background-color:#536dfe}button[data-md-color-accent=blue]{background-color:#448aff}[data-md-color-accent=blue] .md-typeset a:active,[data-md-color-accent=blue] .md-typeset a:hover{color:#448aff}[data-md-color-accent=blue] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=blue] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-nav__link:focus,[data-md-color-accent=blue] .md-nav__link:hover,[data-md-color-accent=blue] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=blue] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=blue] .md-typeset .md-clipboard:active:before,[data-md-color-accent=blue] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=blue] .md-typeset [id] .headerlink:focus,[data-md-color-accent=blue] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=blue] .md-typeset [id]:target .headerlink{color:#448aff}[data-md-color-accent=blue] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-search-result__link:hover,[data-md-color-accent=blue] .md-search-result__link[data-md-state=active]{background-color:rgba(68,138,255,.1)}[data-md-color-accent=blue] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-source-file:hover:before{background-color:#448aff}button[data-md-color-accent=light-blue]{background-color:#0091ea}[data-md-color-accent=light-blue] .md-typeset a:active,[data-md-color-accent=light-blue] .md-typeset a:hover{color:#0091ea}[data-md-color-accent=light-blue] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=light-blue] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-nav__link:focus,[data-md-color-accent=light-blue] .md-nav__link:hover,[data-md-color-accent=light-blue] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=light-blue] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=light-blue] .md-typeset .md-clipboard:active:before,[data-md-color-accent=light-blue] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=light-blue] .md-typeset [id] .headerlink:focus,[data-md-color-accent=light-blue] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=light-blue] .md-typeset [id]:target .headerlink{color:#0091ea}[data-md-color-accent=light-blue] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-search-result__link:hover,[data-md-color-accent=light-blue] .md-search-result__link[data-md-state=active]{background-color:rgba(0,145,234,.1)}[data-md-color-accent=light-blue] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-source-file:hover:before{background-color:#0091ea}button[data-md-color-accent=cyan]{background-color:#00b8d4}[data-md-color-accent=cyan] .md-typeset a:active,[data-md-color-accent=cyan] .md-typeset a:hover{color:#00b8d4}[data-md-color-accent=cyan] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=cyan] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-nav__link:focus,[data-md-color-accent=cyan] .md-nav__link:hover,[data-md-color-accent=cyan] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=cyan] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=cyan] .md-typeset .md-clipboard:active:before,[data-md-color-accent=cyan] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=cyan] .md-typeset [id] .headerlink:focus,[data-md-color-accent=cyan] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=cyan] .md-typeset [id]:target .headerlink{color:#00b8d4}[data-md-color-accent=cyan] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-search-result__link:hover,[data-md-color-accent=cyan] .md-search-result__link[data-md-state=active]{background-color:rgba(0,184,212,.1)}[data-md-color-accent=cyan] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-source-file:hover:before{background-color:#00b8d4}button[data-md-color-accent=teal]{background-color:#00bfa5}[data-md-color-accent=teal] .md-typeset a:active,[data-md-color-accent=teal] .md-typeset a:hover{color:#00bfa5}[data-md-color-accent=teal] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=teal] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-nav__link:focus,[data-md-color-accent=teal] .md-nav__link:hover,[data-md-color-accent=teal] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=teal] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=teal] .md-typeset .md-clipboard:active:before,[data-md-color-accent=teal] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=teal] .md-typeset [id] .headerlink:focus,[data-md-color-accent=teal] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=teal] .md-typeset [id]:target .headerlink{color:#00bfa5}[data-md-color-accent=teal] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-search-result__link:hover,[data-md-color-accent=teal] .md-search-result__link[data-md-state=active]{background-color:rgba(0,191,165,.1)}[data-md-color-accent=teal] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-source-file:hover:before{background-color:#00bfa5}button[data-md-color-accent=green]{background-color:#00c853}[data-md-color-accent=green] .md-typeset a:active,[data-md-color-accent=green] .md-typeset a:hover{color:#00c853}[data-md-color-accent=green] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=green] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-nav__link:focus,[data-md-color-accent=green] .md-nav__link:hover,[data-md-color-accent=green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=green] .md-typeset .md-clipboard:active:before,[data-md-color-accent=green] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=green] .md-typeset [id]:target .headerlink{color:#00c853}[data-md-color-accent=green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-search-result__link:hover,[data-md-color-accent=green] .md-search-result__link[data-md-state=active]{background-color:rgba(0,200,83,.1)}[data-md-color-accent=green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-source-file:hover:before{background-color:#00c853}button[data-md-color-accent=light-green]{background-color:#64dd17}[data-md-color-accent=light-green] .md-typeset a:active,[data-md-color-accent=light-green] .md-typeset a:hover{color:#64dd17}[data-md-color-accent=light-green] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=light-green] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-nav__link:focus,[data-md-color-accent=light-green] .md-nav__link:hover,[data-md-color-accent=light-green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=light-green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=light-green] .md-typeset .md-clipboard:active:before,[data-md-color-accent=light-green] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=light-green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=light-green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=light-green] .md-typeset [id]:target .headerlink{color:#64dd17}[data-md-color-accent=light-green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-search-result__link:hover,[data-md-color-accent=light-green] .md-search-result__link[data-md-state=active]{background-color:rgba(100,221,23,.1)}[data-md-color-accent=light-green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-source-file:hover:before{background-color:#64dd17}button[data-md-color-accent=lime]{background-color:#aeea00}[data-md-color-accent=lime] .md-typeset a:active,[data-md-color-accent=lime] .md-typeset a:hover{color:#aeea00}[data-md-color-accent=lime] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=lime] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-nav__link:focus,[data-md-color-accent=lime] .md-nav__link:hover,[data-md-color-accent=lime] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=lime] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=lime] .md-typeset .md-clipboard:active:before,[data-md-color-accent=lime] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=lime] .md-typeset [id] .headerlink:focus,[data-md-color-accent=lime] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=lime] .md-typeset [id]:target .headerlink{color:#aeea00}[data-md-color-accent=lime] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-search-result__link:hover,[data-md-color-accent=lime] .md-search-result__link[data-md-state=active]{background-color:rgba(174,234,0,.1)}[data-md-color-accent=lime] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-source-file:hover:before{background-color:#aeea00}button[data-md-color-accent=yellow]{background-color:#ffd600}[data-md-color-accent=yellow] .md-typeset a:active,[data-md-color-accent=yellow] .md-typeset a:hover{color:#ffd600}[data-md-color-accent=yellow] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=yellow] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-nav__link:focus,[data-md-color-accent=yellow] .md-nav__link:hover,[data-md-color-accent=yellow] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=yellow] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=yellow] .md-typeset .md-clipboard:active:before,[data-md-color-accent=yellow] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=yellow] .md-typeset [id] .headerlink:focus,[data-md-color-accent=yellow] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=yellow] .md-typeset [id]:target .headerlink{color:#ffd600}[data-md-color-accent=yellow] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-search-result__link:hover,[data-md-color-accent=yellow] .md-search-result__link[data-md-state=active]{background-color:rgba(255,214,0,.1)}[data-md-color-accent=yellow] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-source-file:hover:before{background-color:#ffd600}button[data-md-color-accent=amber]{background-color:#ffab00}[data-md-color-accent=amber] .md-typeset a:active,[data-md-color-accent=amber] .md-typeset a:hover{color:#ffab00}[data-md-color-accent=amber] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=amber] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-nav__link:focus,[data-md-color-accent=amber] .md-nav__link:hover,[data-md-color-accent=amber] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=amber] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=amber] .md-typeset .md-clipboard:active:before,[data-md-color-accent=amber] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=amber] .md-typeset [id] .headerlink:focus,[data-md-color-accent=amber] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=amber] .md-typeset [id]:target .headerlink{color:#ffab00}[data-md-color-accent=amber] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-search-result__link:hover,[data-md-color-accent=amber] .md-search-result__link[data-md-state=active]{background-color:rgba(255,171,0,.1)}[data-md-color-accent=amber] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-source-file:hover:before{background-color:#ffab00}button[data-md-color-accent=orange]{background-color:#ff9100}[data-md-color-accent=orange] .md-typeset a:active,[data-md-color-accent=orange] .md-typeset a:hover{color:#ff9100}[data-md-color-accent=orange] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=orange] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-nav__link:focus,[data-md-color-accent=orange] .md-nav__link:hover,[data-md-color-accent=orange] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=orange] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=orange] .md-typeset .md-clipboard:active:before,[data-md-color-accent=orange] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=orange] .md-typeset [id] .headerlink:focus,[data-md-color-accent=orange] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=orange] .md-typeset [id]:target .headerlink{color:#ff9100}[data-md-color-accent=orange] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-search-result__link:hover,[data-md-color-accent=orange] .md-search-result__link[data-md-state=active]{background-color:rgba(255,145,0,.1)}[data-md-color-accent=orange] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-source-file:hover:before{background-color:#ff9100}button[data-md-color-accent=deep-orange]{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-typeset a:active,[data-md-color-accent=deep-orange] .md-typeset a:hover{color:#ff6e40}[data-md-color-accent=deep-orange] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=deep-orange] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-nav__link:focus,[data-md-color-accent=deep-orange] .md-nav__link:hover,[data-md-color-accent=deep-orange] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=deep-orange] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=deep-orange] .md-typeset .md-clipboard:active:before,[data-md-color-accent=deep-orange] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=deep-orange] .md-typeset [id] .headerlink:focus,[data-md-color-accent=deep-orange] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=deep-orange] .md-typeset [id]:target .headerlink{color:#ff6e40}[data-md-color-accent=deep-orange] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-search-result__link:hover,[data-md-color-accent=deep-orange] .md-search-result__link[data-md-state=active]{background-color:rgba(255,110,64,.1)}[data-md-color-accent=deep-orange] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-source-file:hover:before{background-color:#ff6e40}@media only screen and (max-width:59.9375em){[data-md-color-primary=red] .md-nav__source{background-color:rgba(190,66,64,.9675)}[data-md-color-primary=pink] .md-nav__source{background-color:rgba(185,24,79,.9675)}[data-md-color-primary=purple] .md-nav__source{background-color:rgba(136,57,150,.9675)}[data-md-color-primary=deep-purple] .md-nav__source{background-color:rgba(100,69,154,.9675)}[data-md-color-primary=indigo] .md-nav__source{background-color:rgba(50,64,144,.9675)}[data-md-color-primary=blue] .md-nav__source{background-color:rgba(26,119,193,.9675)}[data-md-color-primary=light-blue] .md-nav__source{background-color:rgba(2,134,194,.9675)}[data-md-color-primary=cyan] .md-nav__source{background-color:rgba(0,150,169,.9675)}[data-md-color-primary=teal] .md-nav__source{background-color:rgba(0,119,108,.9675)}[data-md-color-primary=green] .md-nav__source{background-color:rgba(60,139,64,.9675)}[data-md-color-primary=light-green] .md-nav__source{background-color:rgba(99,142,53,.9675)}[data-md-color-primary=lime] .md-nav__source{background-color:rgba(153,161,41,.9675)}[data-md-color-primary=yellow] .md-nav__source{background-color:rgba(198,134,29,.9675)}[data-md-color-primary=amber] .md-nav__source{background-color:rgba(203,127,0,.9675)}[data-md-color-primary=orange] .md-nav__source{background-color:rgba(200,111,0,.9675)}[data-md-color-primary=deep-orange] .md-nav__source{background-color:rgba(203,89,53,.9675)}[data-md-color-primary=brown] .md-nav__source{background-color:rgba(96,68,57,.9675)}[data-md-color-primary=grey] .md-nav__source{background-color:rgba(93,93,93,.9675)}[data-md-color-primary=blue-grey] .md-nav__source{background-color:rgba(67,88,97,.9675)}[data-md-color-primary=white] .md-nav__source{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.87)}[data-md-color-primary=black] .md-nav__source{background-color:#404040}}@media only screen and (max-width:76.1875em){html [data-md-color-primary=red] .md-nav--primary .md-nav__title--site{background-color:#ef5350}html [data-md-color-primary=pink] .md-nav--primary .md-nav__title--site{background-color:#e91e63}html [data-md-color-primary=purple] .md-nav--primary .md-nav__title--site{background-color:#ab47bc}html [data-md-color-primary=deep-purple] .md-nav--primary .md-nav__title--site{background-color:#7e57c2}html [data-md-color-primary=indigo] .md-nav--primary .md-nav__title--site{background-color:#3f51b5}html [data-md-color-primary=blue] .md-nav--primary .md-nav__title--site{background-color:#2196f3}html [data-md-color-primary=light-blue] .md-nav--primary .md-nav__title--site{background-color:#03a9f4}html [data-md-color-primary=cyan] .md-nav--primary .md-nav__title--site{background-color:#00bcd4}html [data-md-color-primary=teal] .md-nav--primary .md-nav__title--site{background-color:#009688}html [data-md-color-primary=green] .md-nav--primary .md-nav__title--site{background-color:#4caf50}html [data-md-color-primary=light-green] .md-nav--primary .md-nav__title--site{background-color:#7cb342}html [data-md-color-primary=lime] .md-nav--primary .md-nav__title--site{background-color:#c0ca33}html [data-md-color-primary=yellow] .md-nav--primary .md-nav__title--site{background-color:#f9a825}html [data-md-color-primary=amber] .md-nav--primary .md-nav__title--site{background-color:#ffa000}html [data-md-color-primary=orange] .md-nav--primary .md-nav__title--site{background-color:#fb8c00}html [data-md-color-primary=deep-orange] .md-nav--primary .md-nav__title--site{background-color:#ff7043}html [data-md-color-primary=brown] .md-nav--primary .md-nav__title--site{background-color:#795548}html [data-md-color-primary=grey] .md-nav--primary .md-nav__title--site{background-color:#757575}html [data-md-color-primary=blue-grey] .md-nav--primary .md-nav__title--site{background-color:#546e7a}html [data-md-color-primary=white] .md-nav--primary .md-nav__title--site{background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-hero{border-bottom:.05rem solid rgba(0,0,0,.07)}html [data-md-color-primary=black] .md-nav--primary .md-nav__title--site{background-color:#000}}@media only screen and (min-width:76.25em){[data-md-color-primary=red] .md-tabs{background-color:#ef5350}[data-md-color-primary=pink] .md-tabs{background-color:#e91e63}[data-md-color-primary=purple] .md-tabs{background-color:#ab47bc}[data-md-color-primary=deep-purple] .md-tabs{background-color:#7e57c2}[data-md-color-primary=indigo] .md-tabs{background-color:#3f51b5}[data-md-color-primary=blue] .md-tabs{background-color:#2196f3}[data-md-color-primary=light-blue] .md-tabs{background-color:#03a9f4}[data-md-color-primary=cyan] .md-tabs{background-color:#00bcd4}[data-md-color-primary=teal] .md-tabs{background-color:#009688}[data-md-color-primary=green] .md-tabs{background-color:#4caf50}[data-md-color-primary=light-green] .md-tabs{background-color:#7cb342}[data-md-color-primary=lime] .md-tabs{background-color:#c0ca33}[data-md-color-primary=yellow] .md-tabs{background-color:#f9a825}[data-md-color-primary=amber] .md-tabs{background-color:#ffa000}[data-md-color-primary=orange] .md-tabs{background-color:#fb8c00}[data-md-color-primary=deep-orange] .md-tabs{background-color:#ff7043}[data-md-color-primary=brown] .md-tabs{background-color:#795548}[data-md-color-primary=grey] .md-tabs{background-color:#757575}[data-md-color-primary=blue-grey] .md-tabs{background-color:#546e7a}[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07);background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=black] .md-tabs{background-color:#000}}@media only screen and (min-width:60em){[data-md-color-primary=white] .md-search__input{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=black] .md-search__input{background-color:hsla(0,0%,100%,.3)}} \ No newline at end of file diff --git a/docs/assets/stylesheets/application.1b62728e.css b/docs/assets/stylesheets/application.1b62728e.css deleted file mode 100644 index 3840dd13..00000000 --- a/docs/assets/stylesheets/application.1b62728e.css +++ /dev/null @@ -1 +0,0 @@ -html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}body{margin:0}hr{overflow:visible;box-sizing:content-box}a{-webkit-text-decoration-skip:objects}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}small,sub,sup{font-size:80%}sub,sup{position:relative;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}table{border-collapse:separate;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{margin:0;padding:0;border:0;outline-style:none;background:transparent;font-size:inherit}input{border:0;outline:0}.md-clipboard:before,.md-icon,.md-nav__button,.md-nav__link:after,.md-nav__title:before,.md-search-result__article--document:before,.md-source-file:before,.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset .critic.comment:before,.md-typeset .footnote-backref,.md-typeset .task-list-control .task-list-indicator:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before,.md-typeset summary:after{font-family:Material Icons;font-style:normal;font-variant:normal;font-weight:400;line-height:1;text-transform:none;white-space:nowrap;speak:none;word-wrap:normal;direction:ltr}.md-content__icon,.md-footer-nav__button,.md-header-nav__button,.md-nav__button,.md-nav__title:before,.md-search-result__article--document:before{display:inline-block;margin:.2rem;padding:.4rem;font-size:1.2rem;cursor:pointer}.md-icon--arrow-back:before{content:""}.md-icon--arrow-forward:before{content:""}.md-icon--menu:before{content:""}.md-icon--search:before{content:""}[dir=rtl] .md-icon--arrow-back:before{content:""}[dir=rtl] .md-icon--arrow-forward:before{content:""}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{color:rgba(0,0,0,.87);font-feature-settings:"kern","liga";font-family:Helvetica Neue,Helvetica,Arial,sans-serif}code,kbd,pre{color:rgba(0,0,0,.87);font-feature-settings:"kern";font-family:Courier New,Courier,monospace}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact}.md-typeset blockquote,.md-typeset ol,.md-typeset p,.md-typeset ul{margin:1em 0}.md-typeset h1{margin:0 0 2rem;color:rgba(0,0,0,.54);font-size:1.5625rem;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:2rem 0 .8rem;font-size:1.25rem;line-height:1.4}.md-typeset h3{margin:1.6rem 0 .8rem;font-size:1rem;font-weight:400;letter-spacing:-.01em;line-height:1.5}.md-typeset h2+h3{margin-top:.8rem}.md-typeset h4{font-size:.8rem}.md-typeset h4,.md-typeset h5,.md-typeset h6{margin:.8rem 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset h5{text-transform:uppercase}.md-typeset hr{margin:1.5em 0;border-bottom:.05rem dotted rgba(0,0,0,.26)}.md-typeset a{color:#3f51b5;word-break:break-word}.md-typeset a,.md-typeset a:before{-webkit-transition:color .125s;transition:color .125s}.md-typeset a:active,.md-typeset a:hover{color:#536dfe}.md-typeset code,.md-typeset pre{background-color:hsla(0,0%,92.5%,.5);color:#37474f;font-size:85%;direction:ltr}.md-typeset code{margin:0 .29412em;padding:.07353em 0;border-radius:.1rem;box-shadow:.29412em 0 0 hsla(0,0%,92.5%,.5),-.29412em 0 0 hsla(0,0%,92.5%,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:0;background-color:transparent;box-shadow:none}.md-typeset a>code{margin:inherit;padding:inherit;border-radius:initial;background-color:inherit;color:inherit;box-shadow:none}.md-typeset pre{position:relative;margin:1em 0;border-radius:.1rem;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset pre>code{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;font-size:inherit;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;overflow:auto}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-typeset kbd{padding:0 .29412em;border-radius:.15rem;border:.05rem solid #c9c9c9;border-bottom-color:#bcbcbc;background-color:#fcfcfc;color:#555;font-size:85%;box-shadow:0 .05rem 0 #b0b0b0;word-break:break-word}.md-typeset mark{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;background-color:rgba(255,235,59,.5);box-shadow:.25em 0 0 rgba(255,235,59,.5),-.25em 0 0 rgba(255,235,59,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{border-bottom:.05rem dotted rgba(0,0,0,.54);text-decoration:none;cursor:help}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.07812em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.07812em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;border-left:.2rem solid rgba(0,0,0,.26);color:rgba(0,0,0,.54)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid rgba(0,0,0,.26);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset iframe,.md-typeset img,.md-typeset svg{max-width:100%}.md-typeset table:not([class]){box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);display:inline-block;max-width:100%;border-radius:.1rem;font-size:.64rem;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.6rem .8rem;background-color:rgba(0,0,0,.54);color:#fff;vertical-align:top}.md-typeset table:not([class]) td{padding:.6rem .8rem;border-top:.05rem solid rgba(0,0,0,.07);vertical-align:top}.md-typeset table:not([class]) tr{-webkit-transition:background-color .125s;transition:background-color .125s}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:inset 0 .05rem 0 #fff}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;-webkit-overflow-scrolling:touch}.md-typeset .md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}.md-typeset .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{font-size:125%;overflow-x:hidden}body,html{height:100%}body{position:relative;font-size:.5rem}hr{display:block;height:.05rem;padding:0;border:0}.md-svg{display:none}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container,.md-main{overflow:auto}.md-container{display:table;width:100%;height:100%;padding-top:2.4rem;table-layout:fixed}.md-main{display:table-row;height:100%}.md-main__inner{height:100%;padding-top:1.5rem;padding-bottom:.05rem}.md-toggle{display:none}.md-overlay{position:fixed;top:0;width:0;height:0;-webkit-transition:width 0s .25s,height 0s .25s,opacity .25s;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);opacity:0;z-index:3}.md-flex{display:table}.md-flex__cell{display:table-cell;position:relative;vertical-align:top}.md-flex__cell--shrink{width:0}.md-flex__cell--stretch{display:table;width:100%;table-layout:fixed}.md-flex__ellipsis{display:table-cell;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-skip{position:fixed;width:.05rem;height:.05rem;margin:.5rem;padding:.3rem .5rem;-webkit-transform:translateY(.4rem);transform:translateY(.4rem);border-radius:.1rem;background-color:rgba(0,0,0,.87);color:#fff;font-size:.64rem;opacity:0;overflow:hidden}.md-skip:focus{width:auto;height:auto;clip:auto;-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;z-index:10}@page{margin:25mm}.md-clipboard{position:absolute;top:.3rem;right:.3rem;width:1.4rem;height:1.4rem;border-radius:.1rem;font-size:.8rem;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-clipboard:before{-webkit-transition:color .25s,opacity .25s;transition:color .25s,opacity .25s;color:rgba(0,0,0,.07);content:"\E14D"}.codehilite:hover .md-clipboard:before,.md-typeset .highlight:hover .md-clipboard:before,pre:hover .md-clipboard:before{color:rgba(0,0,0,.54)}.md-clipboard:focus:before,.md-clipboard:hover:before{color:#536dfe}.md-clipboard__message{display:block;position:absolute;top:0;right:1.7rem;padding:.3rem .5rem;-webkit-transform:translateX(.4rem);transform:translateX(.4rem);-webkit-transition:opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);transition:opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s;transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);border-radius:.1rem;background-color:rgba(0,0,0,.54);color:#fff;font-size:.64rem;white-space:nowrap;opacity:0;pointer-events:none}.md-clipboard__message--active{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;pointer-events:auto}.md-clipboard__message:before{content:attr(aria-label)}.md-clipboard__message:after{display:block;position:absolute;top:50%;right:-.2rem;width:0;margin-top:-.2rem;border-color:transparent rgba(0,0,0,.54);border-style:solid;border-width:.2rem 0 .2rem .2rem;content:""}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__icon{position:relative;margin:.4rem 0;padding:0;float:right}.md-typeset .md-content__icon{color:rgba(0,0,0,.26)}.md-header{position:fixed;top:0;right:0;left:0;height:2.4rem;-webkit-transition:background-color .25s,color .25s;transition:background-color .25s,color .25s;background-color:#3f51b5;color:#fff;box-shadow:none;z-index:2;-webkit-backface-visibility:hidden;backface-visibility:hidden}.no-js .md-header{-webkit-transition:none;transition:none;box-shadow:none}.md-header[data-md-state=shadow]{-webkit-transition:background-color .25s,color .25s,box-shadow .25s;transition:background-color .25s,color .25s,box-shadow .25s;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.md-header-nav{padding:0 .2rem}.md-header-nav__button{position:relative;-webkit-transition:opacity .25s;transition:opacity .25s;z-index:1}.md-header-nav__button:hover{opacity:.7}.md-header-nav__button.md-logo *{display:block}.no-js .md-header-nav__button.md-icon--search{display:none}.md-header-nav__topic{display:block;position:absolute;-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem);-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem)}.no-js .md-header-nav__topic{position:static}.no-js .md-header-nav__topic+.md-header-nav__topic{display:none}.md-header-nav__title{padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem);-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem)}.md-header-nav__title[data-md-state=active] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);opacity:1;z-index:0;pointer-events:auto}.md-header-nav__source{display:none}.md-hero{-webkit-transition:background .25s;transition:background .25s;background-color:#3f51b5;color:#fff;font-size:1rem;overflow:hidden}.md-hero__inner{margin-top:1rem;padding:.8rem .8rem .4rem;-webkit-transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);-webkit-transition-delay:.1s;transition-delay:.1s}[data-md-state=hidden] .md-hero__inner{pointer-events:none;-webkit-transform:translateY(.625rem);transform:translateY(.625rem);-webkit-transition:opacity .1s 0s,-webkit-transform 0s .4s;transition:opacity .1s 0s,-webkit-transform 0s .4s;transition:transform 0s .4s,opacity .1s 0s;transition:transform 0s .4s,opacity .1s 0s,-webkit-transform 0s .4s;opacity:0}.md-hero--expand .md-hero__inner{margin-bottom:1.2rem}.md-footer-nav{background-color:rgba(0,0,0,.87);color:#fff}.md-footer-nav__inner{padding:.2rem;overflow:auto}.md-footer-nav__link{padding-top:1.4rem;padding-bottom:.4rem;-webkit-transition:opacity .25s;transition:opacity .25s}.md-footer-nav__link:hover{opacity:.7}.md-footer-nav__link--prev{width:25%;float:left}[dir=rtl] .md-footer-nav__link--prev{float:right}.md-footer-nav__link--next{width:75%;float:right;text-align:right}[dir=rtl] .md-footer-nav__link--next{float:left;text-align:left}.md-footer-nav__button{-webkit-transition:background .25s;transition:background .25s}.md-footer-nav__title{position:relative;padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer-nav__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;color:hsla(0,0%,100%,.7);font-size:.75rem}.md-footer-meta{background-color:rgba(0,0,0,.895)}.md-footer-meta__inner{padding:.2rem;overflow:auto}html .md-footer-meta.md-typeset a{color:hsla(0,0%,100%,.7)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:#fff}.md-footer-copyright{margin:0 .6rem;padding:.4rem 0;color:hsla(0,0%,100%,.3);font-size:.64rem}.md-footer-copyright__highlight{color:hsla(0,0%,100%,.7)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;font-size:.8rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden}.md-nav__title:before{display:none;content:"\E5C4"}[dir=rtl] .md-nav__title:before{content:"\E5C8"}.md-nav__title .md-nav__button{display:none}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item:last-child{padding-bottom:.6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__item .md-nav__item:last-child{padding-bottom:0}.md-nav__button img{width:100%;height:auto}.md-nav__link{display:block;margin-top:.625em;-webkit-transition:color .125s;transition:color .125s;text-overflow:ellipsis;cursor:pointer;overflow:hidden}.md-nav__item--nested>.md-nav__link:after{content:"\E313"}html .md-nav__link[for=__toc],html .md-nav__link[for=__toc]+.md-nav__link:after,html .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__link[data-md-state=blur]{color:rgba(0,0,0,.54)}.md-nav__link--active,.md-nav__link:active{color:#3f51b5}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:#536dfe}.md-nav__source,.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}.md-search__form{position:relative}.md-search__input{position:relative;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;z-index:2}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-moz-placeholder{-moz-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input:-ms-input-placeholder{-ms-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-ms-input-placeholder{-ms-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::placeholder{-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}.md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}.md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}.md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-clear{display:none}.md-search__icon{position:absolute;-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:color .25s cubic-bezier(.1,.7,.1,1),opacity .25s;font-size:1.2rem;cursor:pointer;z-index:2}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}.md-search__icon[for=__search]:before{content:"\E8B6"}.md-search__icon[type=reset]{top:.3rem;right:.5rem;-webkit-transform:scale(.125);transform:scale(.125);-webkit-transition:opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);transition:opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);opacity:0}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{-webkit-transform:scale(1);transform:scale(1);opacity:1}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;width:100%;border-radius:0 0 .1rem .1rem;overflow:hidden;z-index:1}.md-search__scrollwrap{height:100%;background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07);overflow-y:auto;-webkit-overflow-scrolling:touch}.md-search-result{color:rgba(0,0,0,.87);word-break:break-word}.md-search-result__meta{padding:0 .8rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.8rem}.md-search-result__list{margin:0;padding:0;border-top:.05rem solid rgba(0,0,0,.07);list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 rgba(0,0,0,.07)}.md-search-result__link{display:block;-webkit-transition:background .25s;transition:background .25s;outline:0;overflow:hidden}.md-search-result__link:hover,.md-search-result__link[data-md-state=active]{background-color:rgba(83,109,254,.1)}.md-search-result__link:hover .md-search-result__article:before,.md-search-result__link[data-md-state=active] .md-search-result__article:before{opacity:.7}.md-search-result__link:last-child .md-search-result__teaser{margin-bottom:.6rem}.md-search-result__article{position:relative;padding:0 .8rem;overflow:auto}.md-search-result__article--document:before{position:absolute;left:0;margin:.1rem;-webkit-transition:opacity .25s;transition:opacity .25s;color:rgba(0,0,0,.54);content:"\E880"}[dir=rtl] .md-search-result__article--document:before{right:0;left:auto}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-size:.8rem;font-weight:400;line-height:1.4}.md-search-result__title{margin:.5em 0;font-size:.64rem;font-weight:700;line-height:1.4}.md-search-result__teaser{display:-webkit-box;max-height:1.65rem;margin:.5em 0;color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.4;text-overflow:ellipsis;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2}.md-search-result em{font-style:normal;font-weight:700;text-decoration:underline}.md-sidebar{position:absolute;width:12.1rem;padding:1.2rem 0;overflow:hidden}.md-sidebar[data-md-state=lock]{position:fixed;top:2.4rem}.md-sidebar--secondary{display:none}.md-sidebar__scrollwrap{max-height:100%;margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}.md-source{display:block;padding-right:.6rem;-webkit-transition:opacity .25s;transition:opacity .25s;font-size:.65rem;line-height:1.2;white-space:nowrap}[dir=rtl] .md-source{padding-right:0;padding-left:.6rem}.md-source:hover{opacity:.7}.md-source:after,.md-source__icon{display:inline-block;height:2.4rem;content:"";vertical-align:middle}.md-source__icon{width:2.4rem}.md-source__icon svg{width:1.2rem;height:1.2rem;margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:100%;margin-left:.6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden;vertical-align:middle}.md-source__facts{margin:0;padding:0;font-size:.55rem;font-weight:700;list-style-type:none;opacity:.75;overflow:hidden}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{float:left}[dir=rtl] .md-source__fact{float:right}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{margin:0 .1rem;content:"\00B7"}.md-source__fact:first-child:before{display:none}.md-source-file{display:inline-block;margin:1em .5em 1em 0;padding-right:.25rem;border-radius:.1rem;background-color:rgba(0,0,0,.07);font-size:.64rem;list-style-type:none;cursor:pointer;overflow:hidden}.md-source-file:before{display:inline-block;margin-right:.25rem;padding:.25rem;background-color:rgba(0,0,0,.26);color:#fff;font-size:.8rem;content:"\E86F";vertical-align:middle}html .md-source-file{-webkit-transition:background .4s,color .4s,box-shadow .4s cubic-bezier(.4,0,.2,1);transition:background .4s,color .4s,box-shadow .4s cubic-bezier(.4,0,.2,1)}html .md-source-file:before{-webkit-transition:inherit;transition:inherit}html body .md-typeset .md-source-file{color:rgba(0,0,0,.54)}.md-source-file:hover{box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36)}.md-source-file:hover:before{background-color:#536dfe}.md-tabs{width:100%;-webkit-transition:background .25s;transition:background .25s;background-color:#3f51b5;color:#fff;overflow:auto}.md-tabs__list{margin:0 0 0 .2rem;padding:0;list-style:none;white-space:nowrap}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;-webkit-transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);font-size:.7rem;opacity:.7}.md-tabs__link--active,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{-webkit-transition-delay:.02s;transition-delay:.02s}.md-tabs__item:nth-child(3) .md-tabs__link{-webkit-transition-delay:.04s;transition-delay:.04s}.md-tabs__item:nth-child(4) .md-tabs__link{-webkit-transition-delay:.06s;transition-delay:.06s}.md-tabs__item:nth-child(5) .md-tabs__link{-webkit-transition-delay:.08s;transition-delay:.08s}.md-tabs__item:nth-child(6) .md-tabs__link{-webkit-transition-delay:.1s;transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{-webkit-transition-delay:.12s;transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{-webkit-transition-delay:.14s;transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{-webkit-transition-delay:.16s;transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{-webkit-transition-delay:.18s;transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{-webkit-transition-delay:.2s;transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{-webkit-transition-delay:.22s;transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{-webkit-transition-delay:.24s;transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{-webkit-transition-delay:.26s;transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{-webkit-transition-delay:.28s;transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{-webkit-transition-delay:.3s;transition-delay:.3s}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs[data-md-state=hidden] .md-tabs__link{-webkit-transform:translateY(50%);transform:translateY(50%);-webkit-transition:color .25s,opacity .1s,-webkit-transform 0s .4s;transition:color .25s,opacity .1s,-webkit-transform 0s .4s;transition:color .25s,transform 0s .4s,opacity .1s;transition:color .25s,transform 0s .4s,opacity .1s,-webkit-transform 0s .4s;opacity:0}.md-typeset .admonition,.md-typeset details{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:relative;margin:1.5625em 0;padding:0 .6rem;border-left:.2rem solid #448aff;border-radius:.1rem;font-size:.64rem;overflow:auto}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin:1em 0}.md-typeset .admonition>.admonition-title,.md-typeset .admonition>summary,.md-typeset details>.admonition-title,.md-typeset details>summary{margin:0 -.6rem;padding:.4rem .6rem .4rem 2rem;border-bottom:.05rem solid rgba(68,138,255,.1);background-color:rgba(68,138,255,.1);font-weight:700}[dir=rtl] .md-typeset .admonition>.admonition-title,[dir=rtl] .md-typeset .admonition>summary,[dir=rtl] .md-typeset details>.admonition-title,[dir=rtl] .md-typeset details>summary{padding:.4rem 2rem .4rem .6rem}.md-typeset .admonition>.admonition-title:last-child,.md-typeset .admonition>summary:last-child,.md-typeset details>.admonition-title:last-child,.md-typeset details>summary:last-child{margin-bottom:0}.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before{position:absolute;left:.6rem;color:#448aff;font-size:1rem;content:"\E3C9"}[dir=rtl] .md-typeset .admonition>.admonition-title:before,[dir=rtl] .md-typeset .admonition>summary:before,[dir=rtl] .md-typeset details>.admonition-title:before,[dir=rtl] .md-typeset details>summary:before{right:.6rem;left:auto}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-left-color:#00b0ff}[dir=rtl] .md-typeset .admonition.abstract,[dir=rtl] .md-typeset .admonition.summary,[dir=rtl] .md-typeset .admonition.tldr,[dir=rtl] .md-typeset details.abstract,[dir=rtl] .md-typeset details.summary,[dir=rtl] .md-typeset details.tldr{border-right-color:#00b0ff}.md-typeset .admonition.abstract>.admonition-title,.md-typeset .admonition.abstract>summary,.md-typeset .admonition.summary>.admonition-title,.md-typeset .admonition.summary>summary,.md-typeset .admonition.tldr>.admonition-title,.md-typeset .admonition.tldr>summary,.md-typeset details.abstract>.admonition-title,.md-typeset details.abstract>summary,.md-typeset details.summary>.admonition-title,.md-typeset details.summary>summary,.md-typeset details.tldr>.admonition-title,.md-typeset details.tldr>summary{border-bottom-color:rgba(0,176,255,.1);background-color:rgba(0,176,255,.1)}.md-typeset .admonition.abstract>.admonition-title:before,.md-typeset .admonition.abstract>summary:before,.md-typeset .admonition.summary>.admonition-title:before,.md-typeset .admonition.summary>summary:before,.md-typeset .admonition.tldr>.admonition-title:before,.md-typeset .admonition.tldr>summary:before,.md-typeset details.abstract>.admonition-title:before,.md-typeset details.abstract>summary:before,.md-typeset details.summary>.admonition-title:before,.md-typeset details.summary>summary:before,.md-typeset details.tldr>.admonition-title:before,.md-typeset details.tldr>summary:before{color:#00b0ff;content:""}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-left-color:#00b8d4}[dir=rtl] .md-typeset .admonition.info,[dir=rtl] .md-typeset .admonition.todo,[dir=rtl] .md-typeset details.info,[dir=rtl] .md-typeset details.todo{border-right-color:#00b8d4}.md-typeset .admonition.info>.admonition-title,.md-typeset .admonition.info>summary,.md-typeset .admonition.todo>.admonition-title,.md-typeset .admonition.todo>summary,.md-typeset details.info>.admonition-title,.md-typeset details.info>summary,.md-typeset details.todo>.admonition-title,.md-typeset details.todo>summary{border-bottom-color:rgba(0,184,212,.1);background-color:rgba(0,184,212,.1)}.md-typeset .admonition.info>.admonition-title:before,.md-typeset .admonition.info>summary:before,.md-typeset .admonition.todo>.admonition-title:before,.md-typeset .admonition.todo>summary:before,.md-typeset details.info>.admonition-title:before,.md-typeset details.info>summary:before,.md-typeset details.todo>.admonition-title:before,.md-typeset details.todo>summary:before{color:#00b8d4;content:""}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-left-color:#00bfa5}[dir=rtl] .md-typeset .admonition.hint,[dir=rtl] .md-typeset .admonition.important,[dir=rtl] .md-typeset .admonition.tip,[dir=rtl] .md-typeset details.hint,[dir=rtl] .md-typeset details.important,[dir=rtl] .md-typeset details.tip{border-right-color:#00bfa5}.md-typeset .admonition.hint>.admonition-title,.md-typeset .admonition.hint>summary,.md-typeset .admonition.important>.admonition-title,.md-typeset .admonition.important>summary,.md-typeset .admonition.tip>.admonition-title,.md-typeset .admonition.tip>summary,.md-typeset details.hint>.admonition-title,.md-typeset details.hint>summary,.md-typeset details.important>.admonition-title,.md-typeset details.important>summary,.md-typeset details.tip>.admonition-title,.md-typeset details.tip>summary{border-bottom-color:rgba(0,191,165,.1);background-color:rgba(0,191,165,.1)}.md-typeset .admonition.hint>.admonition-title:before,.md-typeset .admonition.hint>summary:before,.md-typeset .admonition.important>.admonition-title:before,.md-typeset .admonition.important>summary:before,.md-typeset .admonition.tip>.admonition-title:before,.md-typeset .admonition.tip>summary:before,.md-typeset details.hint>.admonition-title:before,.md-typeset details.hint>summary:before,.md-typeset details.important>.admonition-title:before,.md-typeset details.important>summary:before,.md-typeset details.tip>.admonition-title:before,.md-typeset details.tip>summary:before{color:#00bfa5;content:""}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-left-color:#00c853}[dir=rtl] .md-typeset .admonition.check,[dir=rtl] .md-typeset .admonition.done,[dir=rtl] .md-typeset .admonition.success,[dir=rtl] .md-typeset details.check,[dir=rtl] .md-typeset details.done,[dir=rtl] .md-typeset details.success{border-right-color:#00c853}.md-typeset .admonition.check>.admonition-title,.md-typeset .admonition.check>summary,.md-typeset .admonition.done>.admonition-title,.md-typeset .admonition.done>summary,.md-typeset .admonition.success>.admonition-title,.md-typeset .admonition.success>summary,.md-typeset details.check>.admonition-title,.md-typeset details.check>summary,.md-typeset details.done>.admonition-title,.md-typeset details.done>summary,.md-typeset details.success>.admonition-title,.md-typeset details.success>summary{border-bottom-color:rgba(0,200,83,.1);background-color:rgba(0,200,83,.1)}.md-typeset .admonition.check>.admonition-title:before,.md-typeset .admonition.check>summary:before,.md-typeset .admonition.done>.admonition-title:before,.md-typeset .admonition.done>summary:before,.md-typeset .admonition.success>.admonition-title:before,.md-typeset .admonition.success>summary:before,.md-typeset details.check>.admonition-title:before,.md-typeset details.check>summary:before,.md-typeset details.done>.admonition-title:before,.md-typeset details.done>summary:before,.md-typeset details.success>.admonition-title:before,.md-typeset details.success>summary:before{color:#00c853;content:""}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-left-color:#64dd17}[dir=rtl] .md-typeset .admonition.faq,[dir=rtl] .md-typeset .admonition.help,[dir=rtl] .md-typeset .admonition.question,[dir=rtl] .md-typeset details.faq,[dir=rtl] .md-typeset details.help,[dir=rtl] .md-typeset details.question{border-right-color:#64dd17}.md-typeset .admonition.faq>.admonition-title,.md-typeset .admonition.faq>summary,.md-typeset .admonition.help>.admonition-title,.md-typeset .admonition.help>summary,.md-typeset .admonition.question>.admonition-title,.md-typeset .admonition.question>summary,.md-typeset details.faq>.admonition-title,.md-typeset details.faq>summary,.md-typeset details.help>.admonition-title,.md-typeset details.help>summary,.md-typeset details.question>.admonition-title,.md-typeset details.question>summary{border-bottom-color:rgba(100,221,23,.1);background-color:rgba(100,221,23,.1)}.md-typeset .admonition.faq>.admonition-title:before,.md-typeset .admonition.faq>summary:before,.md-typeset .admonition.help>.admonition-title:before,.md-typeset .admonition.help>summary:before,.md-typeset .admonition.question>.admonition-title:before,.md-typeset .admonition.question>summary:before,.md-typeset details.faq>.admonition-title:before,.md-typeset details.faq>summary:before,.md-typeset details.help>.admonition-title:before,.md-typeset details.help>summary:before,.md-typeset details.question>.admonition-title:before,.md-typeset details.question>summary:before{color:#64dd17;content:""}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-left-color:#ff9100}[dir=rtl] .md-typeset .admonition.attention,[dir=rtl] .md-typeset .admonition.caution,[dir=rtl] .md-typeset .admonition.warning,[dir=rtl] .md-typeset details.attention,[dir=rtl] .md-typeset details.caution,[dir=rtl] .md-typeset details.warning{border-right-color:#ff9100}.md-typeset .admonition.attention>.admonition-title,.md-typeset .admonition.attention>summary,.md-typeset .admonition.caution>.admonition-title,.md-typeset .admonition.caution>summary,.md-typeset .admonition.warning>.admonition-title,.md-typeset .admonition.warning>summary,.md-typeset details.attention>.admonition-title,.md-typeset details.attention>summary,.md-typeset details.caution>.admonition-title,.md-typeset details.caution>summary,.md-typeset details.warning>.admonition-title,.md-typeset details.warning>summary{border-bottom-color:rgba(255,145,0,.1);background-color:rgba(255,145,0,.1)}.md-typeset .admonition.attention>.admonition-title:before,.md-typeset .admonition.attention>summary:before,.md-typeset .admonition.caution>.admonition-title:before,.md-typeset .admonition.caution>summary:before,.md-typeset .admonition.warning>.admonition-title:before,.md-typeset .admonition.warning>summary:before,.md-typeset details.attention>.admonition-title:before,.md-typeset details.attention>summary:before,.md-typeset details.caution>.admonition-title:before,.md-typeset details.caution>summary:before,.md-typeset details.warning>.admonition-title:before,.md-typeset details.warning>summary:before{color:#ff9100;content:""}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-left-color:#ff5252}[dir=rtl] .md-typeset .admonition.fail,[dir=rtl] .md-typeset .admonition.failure,[dir=rtl] .md-typeset .admonition.missing,[dir=rtl] .md-typeset details.fail,[dir=rtl] .md-typeset details.failure,[dir=rtl] .md-typeset details.missing{border-right-color:#ff5252}.md-typeset .admonition.fail>.admonition-title,.md-typeset .admonition.fail>summary,.md-typeset .admonition.failure>.admonition-title,.md-typeset .admonition.failure>summary,.md-typeset .admonition.missing>.admonition-title,.md-typeset .admonition.missing>summary,.md-typeset details.fail>.admonition-title,.md-typeset details.fail>summary,.md-typeset details.failure>.admonition-title,.md-typeset details.failure>summary,.md-typeset details.missing>.admonition-title,.md-typeset details.missing>summary{border-bottom-color:rgba(255,82,82,.1);background-color:rgba(255,82,82,.1)}.md-typeset .admonition.fail>.admonition-title:before,.md-typeset .admonition.fail>summary:before,.md-typeset .admonition.failure>.admonition-title:before,.md-typeset .admonition.failure>summary:before,.md-typeset .admonition.missing>.admonition-title:before,.md-typeset .admonition.missing>summary:before,.md-typeset details.fail>.admonition-title:before,.md-typeset details.fail>summary:before,.md-typeset details.failure>.admonition-title:before,.md-typeset details.failure>summary:before,.md-typeset details.missing>.admonition-title:before,.md-typeset details.missing>summary:before{color:#ff5252;content:""}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-left-color:#ff1744}[dir=rtl] .md-typeset .admonition.danger,[dir=rtl] .md-typeset .admonition.error,[dir=rtl] .md-typeset details.danger,[dir=rtl] .md-typeset details.error{border-right-color:#ff1744}.md-typeset .admonition.danger>.admonition-title,.md-typeset .admonition.danger>summary,.md-typeset .admonition.error>.admonition-title,.md-typeset .admonition.error>summary,.md-typeset details.danger>.admonition-title,.md-typeset details.danger>summary,.md-typeset details.error>.admonition-title,.md-typeset details.error>summary{border-bottom-color:rgba(255,23,68,.1);background-color:rgba(255,23,68,.1)}.md-typeset .admonition.danger>.admonition-title:before,.md-typeset .admonition.danger>summary:before,.md-typeset .admonition.error>.admonition-title:before,.md-typeset .admonition.error>summary:before,.md-typeset details.danger>.admonition-title:before,.md-typeset details.danger>summary:before,.md-typeset details.error>.admonition-title:before,.md-typeset details.error>summary:before{color:#ff1744;content:""}.md-typeset .admonition.bug,.md-typeset details.bug{border-left-color:#f50057}[dir=rtl] .md-typeset .admonition.bug,[dir=rtl] .md-typeset details.bug{border-right-color:#f50057}.md-typeset .admonition.bug>.admonition-title,.md-typeset .admonition.bug>summary,.md-typeset details.bug>.admonition-title,.md-typeset details.bug>summary{border-bottom-color:rgba(245,0,87,.1);background-color:rgba(245,0,87,.1)}.md-typeset .admonition.bug>.admonition-title:before,.md-typeset .admonition.bug>summary:before,.md-typeset details.bug>.admonition-title:before,.md-typeset details.bug>summary:before{color:#f50057;content:""}.md-typeset .admonition.example,.md-typeset details.example{border-left-color:#651fff}[dir=rtl] .md-typeset .admonition.example,[dir=rtl] .md-typeset details.example{border-right-color:#651fff}.md-typeset .admonition.example>.admonition-title,.md-typeset .admonition.example>summary,.md-typeset details.example>.admonition-title,.md-typeset details.example>summary{border-bottom-color:rgba(101,31,255,.1);background-color:rgba(101,31,255,.1)}.md-typeset .admonition.example>.admonition-title:before,.md-typeset .admonition.example>summary:before,.md-typeset details.example>.admonition-title:before,.md-typeset details.example>summary:before{color:#651fff;content:""}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-left-color:#9e9e9e}[dir=rtl] .md-typeset .admonition.cite,[dir=rtl] .md-typeset .admonition.quote,[dir=rtl] .md-typeset details.cite,[dir=rtl] .md-typeset details.quote{border-right-color:#9e9e9e}.md-typeset .admonition.cite>.admonition-title,.md-typeset .admonition.cite>summary,.md-typeset .admonition.quote>.admonition-title,.md-typeset .admonition.quote>summary,.md-typeset details.cite>.admonition-title,.md-typeset details.cite>summary,.md-typeset details.quote>.admonition-title,.md-typeset details.quote>summary{border-bottom-color:hsla(0,0%,62%,.1);background-color:hsla(0,0%,62%,.1)}.md-typeset .admonition.cite>.admonition-title:before,.md-typeset .admonition.cite>summary:before,.md-typeset .admonition.quote>.admonition-title:before,.md-typeset .admonition.quote>summary:before,.md-typeset details.cite>.admonition-title:before,.md-typeset details.cite>summary:before,.md-typeset details.quote>.admonition-title:before,.md-typeset details.quote>summary:before{color:#9e9e9e;content:""}.codehilite .o,.codehilite .ow,.md-typeset .highlight .o,.md-typeset .highlight .ow{color:inherit}.codehilite .ge,.md-typeset .highlight .ge{color:#000}.codehilite .gr,.md-typeset .highlight .gr{color:#a00}.codehilite .gh,.md-typeset .highlight .gh{color:#999}.codehilite .go,.md-typeset .highlight .go{color:#888}.codehilite .gp,.md-typeset .highlight .gp{color:#555}.codehilite .gs,.md-typeset .highlight .gs{color:inherit}.codehilite .gu,.md-typeset .highlight .gu{color:#aaa}.codehilite .gt,.md-typeset .highlight .gt{color:#a00}.codehilite .gd,.md-typeset .highlight .gd{background-color:#fdd}.codehilite .gi,.md-typeset .highlight .gi{background-color:#dfd}.codehilite .k,.md-typeset .highlight .k{color:#3b78e7}.codehilite .kc,.md-typeset .highlight .kc{color:#a71d5d}.codehilite .kd,.codehilite .kn,.md-typeset .highlight .kd,.md-typeset .highlight .kn{color:#3b78e7}.codehilite .kp,.md-typeset .highlight .kp{color:#a71d5d}.codehilite .kr,.codehilite .kt,.md-typeset .highlight .kr,.md-typeset .highlight .kt{color:#3e61a2}.codehilite .c,.codehilite .cm,.md-typeset .highlight .c,.md-typeset .highlight .cm{color:#999}.codehilite .cp,.md-typeset .highlight .cp{color:#666}.codehilite .c1,.codehilite .ch,.codehilite .cs,.md-typeset .highlight .c1,.md-typeset .highlight .ch,.md-typeset .highlight .cs{color:#999}.codehilite .na,.codehilite .nb,.md-typeset .highlight .na,.md-typeset .highlight .nb{color:#c2185b}.codehilite .bp,.md-typeset .highlight .bp{color:#3e61a2}.codehilite .nc,.md-typeset .highlight .nc{color:#c2185b}.codehilite .no,.md-typeset .highlight .no{color:#3e61a2}.codehilite .nd,.codehilite .ni,.md-typeset .highlight .nd,.md-typeset .highlight .ni{color:#666}.codehilite .ne,.codehilite .nf,.md-typeset .highlight .ne,.md-typeset .highlight .nf{color:#c2185b}.codehilite .nl,.md-typeset .highlight .nl{color:#3b5179}.codehilite .nn,.md-typeset .highlight .nn{color:#ec407a}.codehilite .nt,.md-typeset .highlight .nt{color:#3b78e7}.codehilite .nv,.codehilite .vc,.codehilite .vg,.codehilite .vi,.md-typeset .highlight .nv,.md-typeset .highlight .vc,.md-typeset .highlight .vg,.md-typeset .highlight .vi{color:#3e61a2}.codehilite .nx,.md-typeset .highlight .nx{color:#ec407a}.codehilite .il,.codehilite .m,.codehilite .mf,.codehilite .mh,.codehilite .mi,.codehilite .mo,.md-typeset .highlight .il,.md-typeset .highlight .m,.md-typeset .highlight .mf,.md-typeset .highlight .mh,.md-typeset .highlight .mi,.md-typeset .highlight .mo{color:#e74c3c}.codehilite .s,.codehilite .sb,.codehilite .sc,.md-typeset .highlight .s,.md-typeset .highlight .sb,.md-typeset .highlight .sc{color:#0d904f}.codehilite .sd,.md-typeset .highlight .sd{color:#999}.codehilite .s2,.md-typeset .highlight .s2{color:#0d904f}.codehilite .se,.codehilite .sh,.codehilite .si,.codehilite .sx,.md-typeset .highlight .se,.md-typeset .highlight .sh,.md-typeset .highlight .si,.md-typeset .highlight .sx{color:#183691}.codehilite .sr,.md-typeset .highlight .sr{color:#009926}.codehilite .s1,.codehilite .ss,.md-typeset .highlight .s1,.md-typeset .highlight .ss{color:#0d904f}.codehilite .err,.md-typeset .highlight .err{color:#a61717}.codehilite .w,.md-typeset .highlight .w{color:transparent}.codehilite .hll,.md-typeset .highlight .hll{display:block;margin:0 -.6rem;padding:0 .6rem;background-color:rgba(255,235,59,.5)}.md-typeset .codehilite,.md-typeset .highlight{position:relative;margin:1em 0;padding:0;border-radius:.1rem;background-color:hsla(0,0%,92.5%,.5);color:#37474f;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset .codehilite code,.md-typeset .codehilite pre,.md-typeset .highlight code,.md-typeset .highlight pre{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;overflow:auto;vertical-align:top}.md-typeset .codehilite code::-webkit-scrollbar,.md-typeset .codehilite pre::-webkit-scrollbar,.md-typeset .highlight code::-webkit-scrollbar,.md-typeset .highlight pre::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset .codehilite code::-webkit-scrollbar-thumb,.md-typeset .codehilite pre::-webkit-scrollbar-thumb,.md-typeset .highlight code::-webkit-scrollbar-thumb,.md-typeset .highlight pre::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset .codehilite code::-webkit-scrollbar-thumb:hover,.md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,.md-typeset .highlight code::-webkit-scrollbar-thumb:hover,.md-typeset .highlight pre::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-typeset pre.codehilite,.md-typeset pre.highlight{overflow:visible}.md-typeset pre.codehilite code,.md-typeset pre.highlight code{display:block;padding:.525rem .6rem;overflow:auto}.md-typeset .codehilitetable,.md-typeset .highlighttable{display:block;margin:1em 0;border-radius:.2em;font-size:.8rem;overflow:hidden}.md-typeset .codehilitetable tbody,.md-typeset .codehilitetable td,.md-typeset .highlighttable tbody,.md-typeset .highlighttable td{display:block;padding:0}.md-typeset .codehilitetable tr,.md-typeset .highlighttable tr{display:-webkit-box;display:flex}.md-typeset .codehilitetable .codehilite,.md-typeset .codehilitetable .highlight,.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .codehilite,.md-typeset .highlighttable .highlight,.md-typeset .highlighttable .linenodiv{margin:0;border-radius:0}.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .linenodiv{padding:.525rem .6rem}.md-typeset .codehilitetable .linenos,.md-typeset .highlighttable .linenos{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.26);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-typeset .codehilitetable .linenos pre,.md-typeset .highlighttable .linenos pre{margin:0;padding:0;background-color:transparent;color:inherit;text-align:right}.md-typeset .codehilitetable .code,.md-typeset .highlighttable .code{-webkit-box-flex:1;flex:1;overflow:hidden}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{box-shadow:none}.md-typeset [id^="fnref:"]{display:inline-block}.md-typeset [id^="fnref:"]:target{margin-top:-3.8rem;padding-top:3.8rem;pointer-events:none}.md-typeset [id^="fn:"]:before{display:none;height:0;content:""}.md-typeset [id^="fn:"]:target:before{display:block;margin-top:-3.5rem;padding-top:3.5rem;pointer-events:none}.md-typeset .footnote{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset .footnote ol{margin-left:0}.md-typeset .footnote li{-webkit-transition:color .25s;transition:color .25s}.md-typeset .footnote li:target{color:rgba(0,0,0,.87)}.md-typeset .footnote li :first-child{margin-top:0}.md-typeset .footnote li:hover .footnote-backref,.md-typeset .footnote li:target .footnote-backref{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}.md-typeset .footnote li:hover .footnote-backref:hover,.md-typeset .footnote li:target .footnote-backref{color:#536dfe}.md-typeset .footnote-ref{display:inline-block;pointer-events:auto}.md-typeset .footnote-ref:before{display:inline;margin:0 .2em;border-left:.05rem solid rgba(0,0,0,.26);font-size:1.25em;content:"";vertical-align:-.25rem}.md-typeset .footnote-backref{display:inline-block;-webkit-transform:translateX(.25rem);transform:translateX(.25rem);-webkit-transition:color .25s,opacity .125s .125s,-webkit-transform .25s .125s;transition:color .25s,opacity .125s .125s,-webkit-transform .25s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s,-webkit-transform .25s .125s;color:rgba(0,0,0,.26);font-size:0;opacity:0;vertical-align:text-bottom}[dir=rtl] .md-typeset .footnote-backref{-webkit-transform:translateX(-.25rem);transform:translateX(-.25rem)}.md-typeset .footnote-backref:before{display:inline-block;font-size:.8rem;content:"\E31B"}[dir=rtl] .md-typeset .footnote-backref:before{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;-webkit-transform:translateY(.25rem);transform:translateY(.25rem);-webkit-transition:color .25s,opacity .125s .25s,-webkit-transform .25s .25s;transition:color .25s,opacity .125s .25s,-webkit-transform .25s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s,-webkit-transform .25s .25s;opacity:0}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}html body .md-typeset .headerlink{color:rgba(0,0,0,.26)}.md-typeset h1[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h1[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink,.md-typeset h1[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink:hover,.md-typeset h1[id]:target .headerlink{color:#536dfe}.md-typeset h2[id]:before{display:block;margin-top:-8px;padding-top:8px;content:""}.md-typeset h2[id]:target:before{margin-top:-3.4rem;padding-top:3.4rem}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink,.md-typeset h2[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink:hover,.md-typeset h2[id]:target .headerlink{color:#536dfe}.md-typeset h3[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h3[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink,.md-typeset h3[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink:hover,.md-typeset h3[id]:target .headerlink{color:#536dfe}.md-typeset h4[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h4[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink,.md-typeset h4[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink:hover,.md-typeset h4[id]:target .headerlink{color:#536dfe}.md-typeset h5[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h5[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink,.md-typeset h5[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink:hover,.md-typeset h5[id]:target .headerlink{color:#536dfe}.md-typeset h6[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h6[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink,.md-typeset h6[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink:hover,.md-typeset h6[id]:target .headerlink{color:#536dfe}.md-typeset .MJXc-display{margin:.75em 0;padding:.75em 0;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset .MathJax_CHTML{outline:0}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:#fdd;box-shadow:.25em 0 0 #fdd,-.25em 0 0 #fdd}.md-typeset ins.critic{background-color:#dfd;box-shadow:.25em 0 0 #dfd,-.25em 0 0 #dfd}.md-typeset .critic.comment{background-color:hsla(0,0%,92.5%,.5);color:#37474f;box-shadow:.25em 0 0 hsla(0,0%,92.5%,.5),-.25em 0 0 hsla(0,0%,92.5%,.5)}.md-typeset .critic.comment:before{padding-right:.125em;color:rgba(0,0,0,.26);content:"\E0B7";vertical-align:-.125em}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;box-shadow:none}.md-typeset .critic.block :first-child{margin-top:.5em}.md-typeset .critic.block :last-child{margin-bottom:.5em}.md-typeset details{display:block;padding-top:0}.md-typeset details[open]>summary:after{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.md-typeset details:not([open]){padding-bottom:0}.md-typeset details:not([open])>summary{border-bottom:none}.md-typeset details summary{padding-right:2rem}[dir=rtl] .md-typeset details summary{padding-left:2rem}.no-details .md-typeset details:not([open])>*{display:none}.no-details .md-typeset details:not([open]) summary{display:block}.md-typeset summary{display:block;outline:none;cursor:pointer}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset summary:after{position:absolute;top:.4rem;right:.6rem;color:rgba(0,0,0,.26);font-size:1rem;content:"\E313"}[dir=rtl] .md-typeset summary:after{right:auto;left:.6rem}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{width:1rem;vertical-align:text-top}.md-typeset code.codehilite,.md-typeset code.highlight{margin:0 .29412em;padding:.07353em 0}.md-typeset .superfences-content{display:none;-webkit-box-ordinal-group:100;order:99;width:100%;background-color:#fff}.md-typeset .superfences-content>*{margin:0;border-radius:0}.md-typeset .superfences-tabs{display:-webkit-box;display:flex;position:relative;flex-wrap:wrap;margin:1em 0;border:.05rem solid rgba(0,0,0,.07);border-radius:.2em}.md-typeset .superfences-tabs>input{display:none}.md-typeset .superfences-tabs>input:checked+label{font-weight:700}.md-typeset .superfences-tabs>input:checked+label+.superfences-content{display:block}.md-typeset .superfences-tabs>label{width:auto;padding:.6rem;-webkit-transition:color .125s;transition:color .125s;font-size:.64rem;cursor:pointer}html .md-typeset .superfences-tabs>label:hover{color:#536dfe}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control .task-list-indicator:before{position:absolute;top:.15em;left:-1.25em;color:rgba(0,0,0,.26);font-size:1.25em;content:"\E835";vertical-align:-.25em}[dir=rtl] .md-typeset .task-list-control .task-list-indicator:before{right:-1.25em;left:auto}.md-typeset .task-list-control [type=checkbox]:checked+.task-list-indicator:before{content:"\E834"}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}@media print{.md-typeset a:after{color:rgba(0,0,0,.54);content:" [" attr(href) "]"}.md-typeset code,.md-typeset pre{white-space:pre-wrap}.md-typeset code{box-shadow:none;-webkit-box-decoration-break:initial;box-decoration-break:slice}.md-clipboard,.md-content__icon,.md-footer,.md-header,.md-sidebar,.md-tabs,.md-typeset .headerlink{display:none}}@media only screen and (max-width:44.9375em){.md-typeset pre{margin:1em -.8rem;border-radius:0}.md-typeset pre>code{padding:.525rem .8rem}.md-footer-nav__link--prev .md-footer-nav__title{display:none}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}.codehilite .hll,.md-typeset .highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.codehilite,.md-typeset>.highlight{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilite code,.md-typeset>.codehilite pre,.md-typeset>.highlight code,.md-typeset>.highlight pre{padding:.525rem .8rem}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilitetable .codehilite>code,.md-typeset>.codehilitetable .codehilite>pre,.md-typeset>.codehilitetable .highlight>code,.md-typeset>.codehilitetable .highlight>pre,.md-typeset>.codehilitetable .linenodiv,.md-typeset>.highlighttable .codehilite>code,.md-typeset>.highlighttable .codehilite>pre,.md-typeset>.highlighttable .highlight>code,.md-typeset>.highlighttable .highlight>pre,.md-typeset>.highlighttable .linenodiv{padding:.5rem .8rem}.md-typeset>p>.MJXc-display{margin:.75em -.8rem;padding:.25em .8rem}.md-typeset>.superfences-tabs{margin:1em -.8rem;border:0;border-top:.05rem solid rgba(0,0,0,.07);border-radius:0}.md-typeset>.superfences-tabs code,.md-typeset>.superfences-tabs pre{padding:.525rem .8rem}}@media only screen and (min-width:100em){html{font-size:137.5%}}@media only screen and (min-width:125em){html{font-size:150%}}@media only screen and (max-width:59.9375em){body[data-md-state=lock]{overflow:hidden}.ios body[data-md-state=lock] .md-container{display:none}html .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}html .md-nav__link[for=__toc]:after{color:inherit;content:"\E8DE"}html .md-nav__link[for=__toc]+.md-nav__link{display:none}html .md-nav__link[for=__toc]~.md-nav{display:-webkit-box;display:flex}html [dir=rtl] .md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav__source{display:block;padding:0 .2rem;background-color:rgba(50,64,144,.9675);color:#fff}.md-search__overlay{position:absolute;top:.2rem;left:.2rem;width:1.8rem;height:1.8rem;-webkit-transform-origin:center;transform-origin:center;-webkit-transition:opacity .2s .2s,-webkit-transform .3s .1s;transition:opacity .2s .2s,-webkit-transform .3s .1s;transition:transform .3s .1s,opacity .2s .2s;transition:transform .3s .1s,opacity .2s .2s,-webkit-transform .3s .1s;border-radius:1rem;background-color:#fff;overflow:hidden;pointer-events:none}[dir=rtl] .md-search__overlay{right:.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transition:opacity .1s,-webkit-transform .4s;transition:opacity .1s,-webkit-transform .4s;transition:transform .4s,opacity .1s;transition:transform .4s,opacity .1s,-webkit-transform .4s;opacity:1}.md-search__inner{position:fixed;top:0;left:100%;width:100%;height:100%;-webkit-transform:translateX(5%);transform:translateX(5%);-webkit-transition:right 0s .3s,left 0s .3s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;transition:right 0s .3s,left 0s .3s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;opacity:0;z-index:2}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:right 0s 0s,left 0s 0s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;transition:right 0s 0s,left 0s 0s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;opacity:1}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;-webkit-transform:translateX(-5%);transform:translateX(-5%)}.md-search__input{width:100%;height:2.4rem;font-size:.9rem}.md-search__icon[for=__search]{top:.6rem;left:.8rem}.md-search__icon[for=__search][for=__search]:before{content:"\E5C4"}[dir=rtl] .md-search__icon[for=__search][for=__search]:before{content:"\E5C8"}.md-search__icon[type=reset]{top:.6rem;right:.8rem}.md-search__output{top:2.4rem;bottom:0}.md-search-result__article--document:before{display:none}}@media only screen and (max-width:76.1875em){[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;-webkit-transition:width 0s,height 0s,opacity .25s;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-header-nav__button.md-icon--home,.md-header-nav__button.md-logo{display:none}.md-hero__inner{margin-top:2.4rem;margin-bottom:1.2rem}.md-nav{background-color:#fff}.md-nav--primary,.md-nav--primary .md-nav{display:-webkit-box;display:flex;position:absolute;top:0;right:0;left:0;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;height:100%;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}html .md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-weight:400;line-height:2.4rem;white-space:nowrap;cursor:pointer}html .md-nav--primary .md-nav__title:before{display:block;position:absolute;top:.2rem;left:.2rem;width:2rem;height:2rem;color:rgba(0,0,0,.54)}html .md-nav--primary .md-nav__title~.md-nav__list{background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07)}html .md-nav--primary .md-nav__title~.md-nav__list>.md-nav__item:first-child{border-top:0}html .md-nav--primary .md-nav__title--site{position:relative;background-color:#3f51b5;color:#fff}html .md-nav--primary .md-nav__title--site .md-nav__button{display:block;position:absolute;top:.2rem;left:.2rem;width:3.2rem;height:3.2rem;font-size:2.4rem}html .md-nav--primary .md-nav__title--site:before{display:none}html [dir=rtl] .md-nav--primary .md-nav__title--site .md-nav__button,html [dir=rtl] .md-nav--primary .md-nav__title:before{right:.2rem;left:auto}.md-nav--primary .md-nav__list{-webkit-box-flex:1;flex:1;overflow-y:auto}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid rgba(0,0,0,.07)}[dir=rtl] .md-nav--primary .md-nav__item{padding:0}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E315"}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E314"}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link:after{position:absolute;top:50%;right:.6rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link:after{right:auto;left:.6rem}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:transparent}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav__toggle~.md-nav{display:-webkit-box;display:flex;-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-transition:opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);transition:opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);opacity:0}[dir=rtl] .md-nav__toggle~.md-nav{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.no-csstransforms3d .md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1}.no-csstransforms3d .md-nav__toggle:checked~.md-nav{display:-webkit-box;display:flex}.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;width:12.1rem;height:100%;-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);background-color:#fff;z-index:3}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}.no-csstransforms3d .md-sidebar--primary{display:none}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);-webkit-transform:translateX(12.1rem);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{-webkit-transform:translateX(-12.1rem);transform:translateX(-12.1rem)}.no-csstransforms3d [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{display:block}.md-sidebar--primary .md-sidebar__scrollwrap{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;margin:0}.md-tabs{display:none}}@media only screen and (min-width:60em){.md-content{margin-right:12.1rem}[dir=rtl] .md-content{margin-right:0;margin-left:12.1rem}.md-header-nav__button.md-icon--search{display:none}.md-header-nav__source{display:block;width:11.7rem;max-width:11.7rem;padding-right:.6rem}[dir=rtl] .md-header-nav__source{padding-right:0;padding-left:.6rem}.md-search{padding:.2rem}.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;-webkit-transition:width 0s .25s,height 0s .25s,opacity .25s;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);cursor:pointer}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:100%;-webkit-transition:width 0s,height 0s,opacity .25s;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-search__inner{position:relative;width:11.5rem;margin-right:.8rem;padding:.1rem 0;float:right;-webkit-transition:width .25s cubic-bezier(.1,.7,.1,1);transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{margin-right:0;margin-left:.8rem;float:left}.md-search__form,.md-search__input{border-radius:.1rem}.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;-webkit-transition:background-color .25s cubic-bezier(.1,.7,.1,1),color .25s cubic-bezier(.1,.7,.1,1);transition:background-color .25s cubic-bezier(.1,.7,.1,1),color .25s cubic-bezier(.1,.7,.1,1);background-color:rgba(0,0,0,.26);color:inherit;font-size:.8rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:inherit}.md-search__input::-webkit-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::-moz-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{border-radius:.1rem .1rem 0 0;background-color:#fff;color:rgba(0,0,0,.87);text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:rgba(0,0,0,.54)}.md-search__output{top:1.9rem;-webkit-transition:opacity .4s;transition:opacity .4s;opacity:0}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}.md-search__scrollwrap{max-height:0}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}.md-sidebar--secondary{display:block;margin-left:100%;-webkit-transform:translate(-100%);transform:translate(-100%)}[dir=rtl] .md-sidebar--secondary{margin-right:100%;margin-left:0;-webkit-transform:translate(100%);transform:translate(100%)}}@media only screen and (min-width:76.25em){.md-content{margin-left:12.1rem}[dir=rtl] .md-content{margin-right:12.1rem}.md-content__inner{margin-right:1.2rem;margin-left:1.2rem}.md-header-nav__button.md-icon--menu{display:none}.md-nav[data-md-state=animate]{-webkit-transition:max-height .25s cubic-bezier(.86,0,.07,1);transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav__toggle~.md-nav{max-height:0;overflow:hidden}.no-js .md-nav__toggle~.md-nav{display:none}.md-nav[data-md-state=expand],.md-nav__toggle:checked~.md-nav{max-height:100%}.no-js .md-nav[data-md-state=expand],.no-js .md-nav__toggle:checked~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--nested>.md-nav__link:after{display:inline-block;-webkit-transform-origin:.45em .45em;transform-origin:.45em .45em;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;vertical-align:-.125em}.js .md-nav__item--nested>.md-nav__link:after{-webkit-transition:-webkit-transform .4s;transition:-webkit-transform .4s;transition:transform .4s;transition:transform .4s,-webkit-transform .4s}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link:after{-webkit-transform:rotateX(180deg);transform:rotateX(180deg)}.md-search__inner{margin-right:1.2rem}[dir=rtl] .md-search__inner{margin-left:1.2rem}.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}.md-sidebar--secondary{margin-left:61rem}[dir=rtl] .md-sidebar--secondary{margin-right:61rem;margin-left:0}.md-tabs~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary .md-nav__title{display:block;padding:0}.md-tabs--active~.md-main .md-nav--primary .md-nav__title--site{display:none}.no-js .md-tabs--active~.md-main .md-nav--primary .md-nav{display:block}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{display:none;font-size:.7rem;overflow:auto;visibility:visible}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested>.md-nav__link{display:none}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--active{display:block}.md-tabs--active~.md-main .md-nav[data-md-level="1"]{max-height:none;overflow:visible}.md-tabs--active~.md-main .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-left:0}.md-tabs--active~.md-main .md-nav[data-md-level="1"] .md-nav .md-nav__title{display:none}}@media only screen and (min-width:45em){.md-footer-nav__link{width:50%}.md-footer-copyright{max-width:75%;float:left}[dir=rtl] .md-footer-copyright{float:right}.md-footer-social{padding:.6rem 0;float:right}[dir=rtl] .md-footer-social{float:left}}@media only screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(45);transform:scale(45)}}@media only screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(60);transform:scale(60)}}@media only screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(75);transform:scale(75)}}@media only screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}} \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index e66dceed..00000000 --- a/docs/index.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yq - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Skip to content - - - -

- -
- -
- - - - -
-
- - - - - -
-
- - - -

New documentation website

- -

User docs are better than ever, and have been moved here

- - - - - - - - - - -
-
-
-
- - - - -
- - - - - - - - \ No newline at end of file diff --git a/docs/search/search_index.json b/docs/search/search_index.json deleted file mode 100644 index d033dff2..00000000 --- a/docs/search/search_index.json +++ /dev/null @@ -1 +0,0 @@ -{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"New documentation website User docs are better than ever, and have been moved here","title":"Home"},{"location":"#new-documentation-website","text":"User docs are better than ever, and have been moved here","title":"New documentation website"}]} \ No newline at end of file diff --git a/docs/sitemap.xml b/docs/sitemap.xml deleted file mode 100644 index 364990d7..00000000 --- a/docs/sitemap.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - None - 2020-01-30 - daily - - \ No newline at end of file diff --git a/docs/sitemap.xml.gz b/docs/sitemap.xml.gz deleted file mode 100644 index 005f405c62b1182f63d7a199bc4fdcdc2dce8ee8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 189 zcmV;u07CyCiwFpd4>Dc?|8r?{Wo=<_E_iKh08Nff62c%1h4-FX@36e>Zhx0n7nV>WsD@+L2$c@K`fOhg-eO-sfpF$y14M()HjIy8x7 zv&WkDDUBz9X_^??WCk@N3$X*#eI5`t2%H_M+>u=KfmIRVf|i%yjeuGGpu#kSR#v>? r1uu&|&+CnC7Tqh~nR_%3*(&Qk0{+OF0!z%&^1Jd2S4&c1-T(jqr Date: Mon, 19 Oct 2020 08:36:33 +1100 Subject: [PATCH 042/129] Multiply wip --- pkg/yqlib/treeops/lib.go | 2 ++ pkg/yqlib/treeops/operator_merge.go | 20 ++++++++++++++++++ pkg/yqlib/treeops/operator_merge_test.go | 27 ++++++++++++++++++++++++ pkg/yqlib/treeops/path_tokeniser.go | 1 + pkg/yqlib/treeops/sample.yaml | 1 + pkg/yqlib/treeops/temp.json | 6 ++++++ 6 files changed, 57 insertions(+) create mode 100644 pkg/yqlib/treeops/operator_merge.go create mode 100644 pkg/yqlib/treeops/operator_merge_test.go create mode 100644 pkg/yqlib/treeops/sample.yaml create mode 100644 pkg/yqlib/treeops/temp.json diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index e66bb281..34be9b08 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -40,6 +40,8 @@ var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: U var Intersection = &OperationType{Type: "INTERSECTION", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator} var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator} +var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} + var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator} diff --git a/pkg/yqlib/treeops/operator_merge.go b/pkg/yqlib/treeops/operator_merge.go new file mode 100644 index 00000000..86ebbc14 --- /dev/null +++ b/pkg/yqlib/treeops/operator_merge.go @@ -0,0 +1,20 @@ +package treeops + +import "github.com/elliotchance/orderedmap" + +func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + for el := lhs.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + + // TODO handle scalar mulitplication + switch candidate.Node.Kind { + case + } + + } + return matchingNodes, nil +} diff --git a/pkg/yqlib/treeops/operator_merge_test.go b/pkg/yqlib/treeops/operator_merge_test.go new file mode 100644 index 00000000..c0cc75fc --- /dev/null +++ b/pkg/yqlib/treeops/operator_merge_test.go @@ -0,0 +1,27 @@ +package treeops + +import ( + "testing" +) + +var mergeOperatorScenarios = []expressionScenario{ + { + document: `{a: frog, b: cat}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: cat, b: cat}\n", + }, + }, { + document: `{a: {things: great}, b: {also: me}}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: me, things: great}, b: {also: me}}\n", + }, + }, +} + +func TestMergeOperatorScenarios(t *testing.T) { + for _, tt := range mergeOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index ec120b26..25a77ee7 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -153,6 +153,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\[`), literalToken(OpenCollect, "[", false)) lexer.Add([]byte(`\]`), literalToken(CloseCollect, "]", true)) + lexer.Add([]byte(`\*`), opToken(Multiply)) // lexer.Add([]byte(`[^ \,\|\.\[\(\)=]+`), stringValue(false)) err := lexer.Compile() diff --git a/pkg/yqlib/treeops/sample.yaml b/pkg/yqlib/treeops/sample.yaml new file mode 100644 index 00000000..7997a4d4 --- /dev/null +++ b/pkg/yqlib/treeops/sample.yaml @@ -0,0 +1 @@ +{a: {b: apple, c: cactus}} \ No newline at end of file diff --git a/pkg/yqlib/treeops/temp.json b/pkg/yqlib/treeops/temp.json new file mode 100644 index 00000000..fd19e6d5 --- /dev/null +++ b/pkg/yqlib/treeops/temp.json @@ -0,0 +1,6 @@ +{ + "a": { + "b": "apple", + "c": "cactus" + } +} From 2ddf8dd4ed185428f16f7a9c39dba262d6104b4f Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 19 Oct 2020 16:14:29 +1100 Subject: [PATCH 043/129] autovivification, merge! --- go.mod | 2 +- pkg/yqlib/treeops/candidate_node.go | 8 ++ pkg/yqlib/treeops/data_tree_navigator.go | 2 +- pkg/yqlib/treeops/leaf_traverser.go | 34 +++++++- pkg/yqlib/treeops/lib.go | 2 + pkg/yqlib/treeops/operator_assign.go | 25 ++++++ pkg/yqlib/treeops/operator_assign_test.go | 18 +++++ pkg/yqlib/treeops/operator_merge.go | 20 ----- pkg/yqlib/treeops/operator_merge_test.go | 27 ------- pkg/yqlib/treeops/operator_multilpy.go | 90 +++++++++++++++++++++ pkg/yqlib/treeops/operator_multiply_test.go | 27 +++++++ pkg/yqlib/treeops/path_postfix.go | 6 +- pkg/yqlib/treeops/value_node_builder.go | 6 +- 13 files changed, 210 insertions(+), 57 deletions(-) delete mode 100644 pkg/yqlib/treeops/operator_merge.go delete mode 100644 pkg/yqlib/treeops/operator_merge_test.go create mode 100644 pkg/yqlib/treeops/operator_multilpy.go create mode 100644 pkg/yqlib/treeops/operator_multiply_test.go diff --git a/go.mod b/go.mod index 19831915..a77d1990 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/elliotchance/orderedmap v1.3.0 github.com/fatih/color v1.9.0 github.com/goccy/go-yaml v1.8.1 - github.com/kylelemons/godebug v1.1.0 + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.7 // indirect github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.0.0 diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index 961e9e29..4d98e8ac 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -20,11 +20,19 @@ func (n *CandidateNode) GetKey() string { // updates this candidate from the given candidate node func (n *CandidateNode) UpdateFrom(other *CandidateNode) { + n.Node.Content = other.Node.Content n.Node.Value = other.Node.Value + n.UpdateAttributesFrom(other) +} + +func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { n.Node.Kind = other.Node.Kind n.Node.Tag = other.Node.Tag n.Node.Style = other.Node.Style + n.Node.FootComment = other.Node.FootComment + n.Node.HeadComment = other.Node.HeadComment + n.Node.LineComment = other.Node.LineComment } func (n *CandidateNode) PathStackToString() string { diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 4a798ead..8076647a 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -81,7 +81,7 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa } else if pathNode.PathElement.PathElementType == PathKey { return d.traverse(matchingNodes, pathNode.PathElement) } else if pathNode.PathElement.PathElementType == Value { - return nodeToMap(BuildCandidateNodeFrom(pathNode.PathElement)), nil + return nodeToMap(pathNode.PathElement.CandidateNode), nil } else { handler := pathNode.PathElement.OperationType.Handler if handler != nil { diff --git a/pkg/yqlib/treeops/leaf_traverser.go b/pkg/yqlib/treeops/leaf_traverser.go index 3c19dc76..1b60b22b 100644 --- a/pkg/yqlib/treeops/leaf_traverser.go +++ b/pkg/yqlib/treeops/leaf_traverser.go @@ -48,6 +48,18 @@ func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) }) } } + if len(newMatches) == 0 { + //no matches, create one automagically + valueNode := &yaml.Node{} + node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode) + newMatches = append(newMatches, &CandidateNode{ + Node: valueNode, + Path: append(candidate.Path, pathNode.StringValue), + Document: candidate.Document, + }) + + } + return newMatches, nil } @@ -72,9 +84,9 @@ func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElemen index := pathNode.Value.(int64) indexToUse := index contentLength := int64(len(candidate.Node.Content)) - if contentLength <= index { - // handle auto append here - return make([]*CandidateNode, 0), nil + for contentLength <= index { + candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{}) + contentLength = int64(len(candidate.Node.Content)) } if indexToUse < 0 { @@ -94,8 +106,22 @@ func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElemen } func (t *traverser) Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { - log.Debug(NodeToString(matchingNode)) + log.Debug("Traversing %v", NodeToString(matchingNode)) value := matchingNode.Node + + if value.Kind == 0 { + log.Debugf("Guessing kind") + // we must ahve added this automatically, lets guess what it should be now + switch pathNode.Value.(type) { + case int, int64: + log.Debugf("probably an array") + value.Kind = yaml.SequenceNode + default: + log.Debugf("probabel a map") + value.Kind = yaml.MappingNode + } + } + switch value.Kind { case yaml.MappingNode: log.Debug("its a map with %v entries", len(value.Content)/2) diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 34be9b08..fca412fb 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -40,6 +40,7 @@ var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: U var Intersection = &OperationType{Type: "INTERSECTION", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator} var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator} +var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} @@ -65,6 +66,7 @@ type PathElement struct { OperationType *OperationType Value interface{} StringValue string + CandidateNode *CandidateNode // used for Value Path elements } // debugging purposes only diff --git a/pkg/yqlib/treeops/operator_assign.go b/pkg/yqlib/treeops/operator_assign.go index 0dc60900..60ee08ea 100644 --- a/pkg/yqlib/treeops/operator_assign.go +++ b/pkg/yqlib/treeops/operator_assign.go @@ -25,3 +25,28 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, } return matchingNodes, nil } + +// does not update content or values +func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + for el := lhs.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + + rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + + if err != nil { + return nil, err + } + + // grab the first value + first := rhs.Front() + + if first != nil { + candidate.UpdateAttributesFrom(first.Value.(*CandidateNode)) + } + } + return matchingNodes, nil +} diff --git a/pkg/yqlib/treeops/operator_assign_test.go b/pkg/yqlib/treeops/operator_assign_test.go index 87a01a64..4ce1f0de 100644 --- a/pkg/yqlib/treeops/operator_assign_test.go +++ b/pkg/yqlib/treeops/operator_assign_test.go @@ -47,6 +47,24 @@ var assignOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!seq)::[bogs, apple, bogs]\n", }, + }, { + document: `{}`, + expression: `.a.b |= "bogs"`, + expected: []string{ + "D0, P[], (!!map)::{a: {b: bogs}}\n", + }, + }, { + document: `{}`, + expression: `.a.b[0] |= "bogs"`, + expected: []string{ + "D0, P[], (!!map)::{a: {b: [bogs]}}\n", + }, + }, { + document: `{}`, + expression: `.a.b[1].c |= "bogs"`, + expected: []string{ + "D0, P[], (!!map)::{a: {b: [null, {c: bogs}]}}\n", + }, }, } diff --git a/pkg/yqlib/treeops/operator_merge.go b/pkg/yqlib/treeops/operator_merge.go deleted file mode 100644 index 86ebbc14..00000000 --- a/pkg/yqlib/treeops/operator_merge.go +++ /dev/null @@ -1,20 +0,0 @@ -package treeops - -import "github.com/elliotchance/orderedmap" - -func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - for el := lhs.Front(); el != nil; el = el.Next() { - candidate := el.Value.(*CandidateNode) - - // TODO handle scalar mulitplication - switch candidate.Node.Kind { - case - } - - } - return matchingNodes, nil -} diff --git a/pkg/yqlib/treeops/operator_merge_test.go b/pkg/yqlib/treeops/operator_merge_test.go deleted file mode 100644 index c0cc75fc..00000000 --- a/pkg/yqlib/treeops/operator_merge_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package treeops - -import ( - "testing" -) - -var mergeOperatorScenarios = []expressionScenario{ - { - document: `{a: frog, b: cat}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: cat, b: cat}\n", - }, - }, { - document: `{a: {things: great}, b: {also: me}}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: {also: me, things: great}, b: {also: me}}\n", - }, - }, -} - -func TestMergeOperatorScenarios(t *testing.T) { - for _, tt := range mergeOperatorScenarios { - testScenario(t, &tt) - } -} diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go new file mode 100644 index 00000000..fa919cb5 --- /dev/null +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -0,0 +1,90 @@ +package treeops + +import ( + "fmt" + + "github.com/elliotchance/orderedmap" + "gopkg.in/yaml.v3" +) + +func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + + rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs) + + if err != nil { + return nil, err + } + + var results = orderedmap.NewOrderedMap() + + for el := lhs.Front(); el != nil; el = el.Next() { + lhsCandidate := el.Value.(*CandidateNode) + + for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() { + rhsCandidate := rightEl.Value.(*CandidateNode) + resultCandidate, err := multiply(d, lhsCandidate, rhsCandidate) + if err != nil { + return nil, err + } + results.Set(resultCandidate.GetKey(), resultCandidate) + } + + } + return matchingNodes, nil +} + +func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode { + var results = orderedmap.NewOrderedMap() + recursiveDecent(d, results, nodeToMap(rhs)) + + var pathIndexToStartFrom int = 0 + if results.Front() != nil { + pathIndexToStartFrom = len(results.Front().Value.(*CandidateNode).Path) + } + + for el := results.Front(); el != nil; el = el.Next() { + err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode)) + if err != nil { + return nil, err + } + } + return lhs, nil + } + return nil, fmt.Errorf("Cannot multiply %v with %v", NodeToString(lhs), NodeToString(rhs)) +} + +func createTraversalTree(path []interface{}) *PathTreeNode { + if len(path) == 0 { + return &PathTreeNode{PathElement: &PathElement{PathElementType: SelfReference}} + } else if len(path) == 1 { + return &PathTreeNode{PathElement: &PathElement{PathElementType: PathKey, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} + } + return &PathTreeNode{ + PathElement: &PathElement{PathElementType: Operation, OperationType: Pipe}, + Lhs: createTraversalTree(path[0:1]), + Rhs: createTraversalTree(path[1:])} + +} + +func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode) error { + log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) + + lhsPath := rhs.Path[pathIndexToStartFrom:] + + assignmentOp := &PathElement{PathElementType: Operation, OperationType: AssignAttributes} + if rhs.Node.Kind == yaml.ScalarNode { + assignmentOp.OperationType = Assign + } + rhsOp := &PathElement{PathElementType: Value, CandidateNode: rhs} + + assignmentOpNode := &PathTreeNode{PathElement: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{PathElement: rhsOp}} + + _, err := d.getMatchingNodes(nodeToMap(lhs), assignmentOpNode) + + return err +} diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go new file mode 100644 index 00000000..14101621 --- /dev/null +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -0,0 +1,27 @@ +package treeops + +import ( + "testing" +) + +var multiplyOperatorScenarios = []expressionScenario{ + { + // document: `{a: frog, b: cat}`, + // expression: `.a * .b`, + // expected: []string{ + // "D0, P[], (!!map)::{a: cat, b: cat}\n", + // }, + // }, { + document: `{a: {things: great}, b: {also: me}}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n", + }, + }, +} + +func TestMultiplyOperatorScenarios(t *testing.T) { + for _, tt := range multiplyOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index 3ed3e009..3688dc9f 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -33,7 +33,11 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, for _, token := range tokens { log.Debugf("postfix processing token %v", token.Value) switch token.PathElementType { - case PathKey, SelfReference, Value: + case Value: + var candidateNode = BuildCandidateNodeFrom(token) + var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue, CandidateNode: candidateNode} + result = append(result, &pathElement) + case PathKey, SelfReference: var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue} result = append(result, &pathElement) case OpenBracket, OpenCollect: diff --git a/pkg/yqlib/treeops/value_node_builder.go b/pkg/yqlib/treeops/value_node_builder.go index 2a7203f7..7c3e7d21 100644 --- a/pkg/yqlib/treeops/value_node_builder.go +++ b/pkg/yqlib/treeops/value_node_builder.go @@ -2,11 +2,11 @@ package treeops import "gopkg.in/yaml.v3" -func BuildCandidateNodeFrom(p *PathElement) *CandidateNode { +func BuildCandidateNodeFrom(token *Token) *CandidateNode { var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode} - node.Value = p.StringValue + node.Value = token.StringValue - switch p.Value.(type) { + switch token.Value.(type) { case float32, float64: node.Tag = "!!float" case int, int64, int32: From 1910563bfe1e66088ef113f85b990010fd097792 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 19 Oct 2020 16:36:46 +1100 Subject: [PATCH 044/129] merge --- pkg/yqlib/treeops/candidate_node.go | 9 ++- pkg/yqlib/treeops/data_tree_navigator_test.go | 1 + pkg/yqlib/treeops/leaf_traverser.go | 4 +- pkg/yqlib/treeops/operator_multiply_test.go | 56 +++++++++++++++++-- 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index 4d98e8ac..e714fc16 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -20,13 +20,18 @@ func (n *CandidateNode) GetKey() string { // updates this candidate from the given candidate node func (n *CandidateNode) UpdateFrom(other *CandidateNode) { - + n.UpdateAttributesFrom(other) n.Node.Content = other.Node.Content n.Node.Value = other.Node.Value - n.UpdateAttributesFrom(other) } func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { + 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 n.Node.Tag = other.Node.Tag n.Node.Style = other.Node.Style diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 01654d70..234d6296 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -19,6 +19,7 @@ func readDoc(t *testing.T, content string) []*CandidateNode { var dataBucket yaml.Node err := decoder.Decode(&dataBucket) if err != nil { + t.Error(content) t.Error(err) } return []*CandidateNode{&CandidateNode{Node: dataBucket.Content[0], Document: 0}} diff --git a/pkg/yqlib/treeops/leaf_traverser.go b/pkg/yqlib/treeops/leaf_traverser.go index 1b60b22b..21539a6a 100644 --- a/pkg/yqlib/treeops/leaf_traverser.go +++ b/pkg/yqlib/treeops/leaf_traverser.go @@ -69,8 +69,8 @@ func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElemen var contents = candidate.Node.Content var newMatches = make([]*CandidateNode, len(contents)) - - for index := 0; index < len(contents); index = index + 1 { + var index int64 + for index = 0; index < int64(len(contents)); index = index + 1 { newMatches[index] = &CandidateNode{ Document: candidate.Document, Path: append(candidate.Path, index), diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index 14101621..a4c89459 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -6,17 +6,61 @@ import ( var multiplyOperatorScenarios = []expressionScenario{ { - // document: `{a: frog, b: cat}`, - // expression: `.a * .b`, - // expected: []string{ - // "D0, P[], (!!map)::{a: cat, b: cat}\n", - // }, - // }, { + document: `{a: {also: [1]}, b: {also: me}}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", + }, + }, { + document: `{a: {also: me}, b: {also: [1]}}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", + }, + }, { + document: `{a: {also: me}, b: {also: {g: wizz}}}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", + }, + }, { + document: `{a: {also: {g: wizz}}, b: {also: me}}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", + }, + }, { + document: `{a: {also: {g: wizz}}, b: {also: [1]}}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", + }, + }, { + document: `{a: {also: [1]}, b: {also: {g: wizz}}}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", + }, + }, { document: `{a: {things: great}, b: {also: me}}`, expression: `.a * .b`, expected: []string{ "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n", }, + }, { + document: `a: {things: great} +b: + also: "me" +`, + expression: `.a * .b`, + expected: []string{ + `D0, P[], (!!map)::a: + things: great + also: "me" +b: + also: "me" +`, + }, }, } From 4bc98776a6b90d9fbabe440bc17498568d431000 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 19 Oct 2020 20:05:38 +1100 Subject: [PATCH 045/129] wip --- pkg/yqlib/treeops/lib.go | 3 +++ pkg/yqlib/treeops/operator_collect_test.go | 6 ++++++ pkg/yqlib/treeops/operator_multilpy.go | 3 ++- pkg/yqlib/treeops/operator_multiply_test.go | 6 ++++++ pkg/yqlib/treeops/path_parse_test.go | 7 ++++++- pkg/yqlib/treeops/path_postfix.go | 2 +- pkg/yqlib/treeops/path_tokeniser.go | 14 ++++++++++++++ pkg/yqlib/treeops/temp.json | 8 +++----- 8 files changed, 41 insertions(+), 8 deletions(-) diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index fca412fb..f30c87dc 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -15,6 +15,7 @@ type PathElementType uint32 const ( PathKey PathElementType = 1 << iota + DocumentKey Operation SelfReference OpenBracket @@ -75,6 +76,8 @@ func (p *PathElement) toString() string { switch p.PathElementType { case PathKey: result = result + fmt.Sprintf("%v", p.Value) + case DocumentKey: + result = result + fmt.Sprintf("D%v", p.Value) case SelfReference: result = result + fmt.Sprintf("SELF") case Operation: diff --git a/pkg/yqlib/treeops/operator_collect_test.go b/pkg/yqlib/treeops/operator_collect_test.go index 3fe7535c..f2de5a05 100644 --- a/pkg/yqlib/treeops/operator_collect_test.go +++ b/pkg/yqlib/treeops/operator_collect_test.go @@ -11,6 +11,12 @@ var collectOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!seq)::- cat\n", }, + }, { + document: `{}`, + expression: `[true]`, + expected: []string{ + "D0, P[], (!!seq)::- true\n", + }, }, { document: `{}`, expression: `["cat", "dog"]`, diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index fa919cb5..cb5c3ff0 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -38,7 +38,8 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap } func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { - if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode { + if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || + (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { var results = orderedmap.NewOrderedMap() recursiveDecent(d, results, nodeToMap(rhs)) diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index a4c89459..f9e8adb7 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -61,6 +61,12 @@ b: also: "me" `, }, + }, { + document: `{a: [1,2,3], b: [3,4,5]}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", + }, }, } diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/treeops/path_parse_test.go index b7d56213..ec8f015c 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -39,6 +39,11 @@ var pathTests = []struct { // {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")}, // {`.a[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")}, // {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")}, + { + `d0.a`, + append(make([]interface{}, 0), int64(0), "PIPE", "a"), + append(make([]interface{}, 0), "D0", "a", "PIPE"), + }, { `.a | (.[].b == "apple")`, append(make([]interface{}, 0), "a", "PIPE", "(", "[]", "PIPE", "b", "EQUALS", "apple", ")"), @@ -52,7 +57,7 @@ var pathTests = []struct { { `[true]`, append(make([]interface{}, 0), "[", true, "]"), - append(make([]interface{}, 0), "true (bool)", "COLLECT"), + append(make([]interface{}, 0), "true (bool)", "COLLECT", "PIPE"), }, // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index 3688dc9f..646ecadf 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -37,7 +37,7 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, var candidateNode = BuildCandidateNodeFrom(token) var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue, CandidateNode: candidateNode} result = append(result, &pathElement) - case PathKey, SelfReference: + case PathKey, SelfReference, DocumentKey: var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue} result = append(result, &pathElement) case OpenBracket, OpenCollect: diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 25a77ee7..6cc94dac 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -32,6 +32,18 @@ func pathToken(wrapped bool) lex.Action { } } +func documentToken() lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + var numberString = string(m.Bytes) + numberString = numberString[1:len(numberString)] + var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint + if errParsingInt != nil { + return nil, errParsingInt + } + return &Token{PathElementType: DocumentKey, OperationType: None, Value: number, StringValue: numberString, CheckForPostTraverse: true}, nil + } +} + func opToken(op *OperationType) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) @@ -136,6 +148,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte("( |\t|\n|\r)+"), skip) + lexer.Add([]byte(`d[0-9]+`), documentToken()) // $0 + lexer.Add([]byte(`\."[^ "]+"`), pathToken(true)) lexer.Add([]byte(`\.[^ \[\],\|\.\[\(\)=]+`), pathToken(false)) lexer.Add([]byte(`\.`), selfToken()) diff --git a/pkg/yqlib/treeops/temp.json b/pkg/yqlib/treeops/temp.json index fd19e6d5..7d768a0b 100644 --- a/pkg/yqlib/treeops/temp.json +++ b/pkg/yqlib/treeops/temp.json @@ -1,6 +1,4 @@ { - "a": { - "b": "apple", - "c": "cactus" - } -} + "a": [1,2], + "b": [3,4] +} \ No newline at end of file From 73cf6224f2e951ee1992a04e444d40501e646143 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 20 Oct 2020 13:53:26 +1100 Subject: [PATCH 046/129] wip --- pkg/yqlib/treeops/data_tree_navigator.go | 41 +- pkg/yqlib/treeops/lib.go | 56 +-- pkg/yqlib/treeops/operator_multilpy.go | 10 +- .../treeops/operator_recursive_descent.go | 5 +- ...traverser.go => operator_traverse_path.go} | 136 +++---- .../treeops/operator_traverse_path_test.go | 21 ++ pkg/yqlib/treeops/path_parse_test.go | 1 + pkg/yqlib/treeops/path_postfix.go | 33 +- pkg/yqlib/treeops/path_postfix_test.go | 355 ------------------ pkg/yqlib/treeops/path_tokeniser.go | 53 ++- pkg/yqlib/treeops/path_tree.go | 2 +- release_instructions.txt | 1 - 12 files changed, 180 insertions(+), 534 deletions(-) rename pkg/yqlib/treeops/{leaf_traverser.go => operator_traverse_path.go} (76%) create mode 100644 pkg/yqlib/treeops/operator_traverse_path_test.go delete mode 100644 pkg/yqlib/treeops/path_postfix_test.go diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 8076647a..7306300c 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -8,7 +8,7 @@ import ( ) type dataTreeNavigator struct { - leafTraverser LeafTraverser + navigationPrefs NavigationPrefs } type NavigationPrefs struct { @@ -20,27 +20,7 @@ type DataTreeNavigator interface { } func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { - leafTraverser := NewLeafTraverser(navigationPrefs) - return &dataTreeNavigator{leafTraverser} -} - -func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *PathElement) (*orderedmap.OrderedMap, error) { - log.Debugf("-- Traversing") - var matchingNodeMap = orderedmap.NewOrderedMap() - var newNodes []*CandidateNode - var err error - - for el := matchMap.Front(); el != nil; el = el.Next() { - newNodes, err = d.leafTraverser.Traverse(el.Value.(*CandidateNode), pathNode) - if err != nil { - return nil, err - } - for _, n := range newNodes { - matchingNodeMap.Set(n.GetKey(), n) - } - } - - return matchingNodeMap, nil + return &dataTreeNavigator{navigationPrefs} } func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { @@ -75,19 +55,10 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa } } log.Debug(">>") - - if pathNode.PathElement.PathElementType == SelfReference { - return matchingNodes, nil - } else if pathNode.PathElement.PathElementType == PathKey { - return d.traverse(matchingNodes, pathNode.PathElement) - } else if pathNode.PathElement.PathElementType == Value { - return nodeToMap(pathNode.PathElement.CandidateNode), nil - } else { - handler := pathNode.PathElement.OperationType.Handler - if handler != nil { - return handler(d, matchingNodes, pathNode) - } - return nil, fmt.Errorf("Unknown operator %v", pathNode.PathElement.OperationType) + handler := pathNode.PathElement.OperationType.Handler + if handler != nil { + return handler(d, matchingNodes, pathNode) } + return nil, fmt.Errorf("Unknown operator %v", pathNode.PathElement.OperationType) } diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index f30c87dc..28c2f459 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -11,20 +11,6 @@ import ( var log = logging.MustGetLogger("yq-treeops") -type PathElementType uint32 - -const ( - PathKey PathElementType = 1 << iota - DocumentKey - Operation - SelfReference - OpenBracket - CloseBracket - OpenCollect - CloseCollect - Value // e.g. string, int -) - type OperationType struct { Type string NumArgs uint // number of arguments to the op @@ -32,8 +18,6 @@ type OperationType struct { Handler OperatorHandler } -var None = &OperationType{Type: "NONE", NumArgs: 0, Precedence: 0} - var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator} var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator} @@ -49,6 +33,12 @@ var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: Pip var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} +var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} + +var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} +var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} +var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} + var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator} // not sure yet @@ -63,31 +53,25 @@ var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Han // filters matches if they have the existing path type PathElement struct { - PathElementType PathElementType - OperationType *OperationType - Value interface{} - StringValue string - CandidateNode *CandidateNode // used for Value Path elements + OperationType *OperationType + Value interface{} + StringValue string + CandidateNode *CandidateNode // used for Value Path elements } // debugging purposes only func (p *PathElement) toString() string { - var result string = `` - switch p.PathElementType { - case PathKey: - result = result + fmt.Sprintf("%v", p.Value) - case DocumentKey: - result = result + fmt.Sprintf("D%v", p.Value) - case SelfReference: - result = result + fmt.Sprintf("SELF") - case Operation: - result = result + fmt.Sprintf("%v", p.OperationType.Type) - case Value: - result = result + fmt.Sprintf("%v (%T)", p.Value, p.Value) - default: - result = result + "I HAVENT GOT A STRATEGY" + if p.OperationType == TraversePath { + return fmt.Sprintf("%v", p.Value) + } else if p.OperationType == DocumentFilter { + return fmt.Sprintf("d%v", p.Value) + } else if p.OperationType == SelfReference { + return "SELF" + } else if p.OperationType == ValueOp { + return fmt.Sprintf("%v (%T)", p.Value, p.Value) + } else { + return fmt.Sprintf("%v", p.OperationType.Type) } - return result } type YqTreeLib interface { diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index cb5c3ff0..57b73299 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -61,12 +61,12 @@ func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*Ca func createTraversalTree(path []interface{}) *PathTreeNode { if len(path) == 0 { - return &PathTreeNode{PathElement: &PathElement{PathElementType: SelfReference}} + return &PathTreeNode{PathElement: &PathElement{OperationType: SelfReference}} } else if len(path) == 1 { - return &PathTreeNode{PathElement: &PathElement{PathElementType: PathKey, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} + return &PathTreeNode{PathElement: &PathElement{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} } return &PathTreeNode{ - PathElement: &PathElement{PathElementType: Operation, OperationType: Pipe}, + PathElement: &PathElement{OperationType: Pipe}, Lhs: createTraversalTree(path[0:1]), Rhs: createTraversalTree(path[1:])} @@ -77,11 +77,11 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid lhsPath := rhs.Path[pathIndexToStartFrom:] - assignmentOp := &PathElement{PathElementType: Operation, OperationType: AssignAttributes} + assignmentOp := &PathElement{OperationType: AssignAttributes} if rhs.Node.Kind == yaml.ScalarNode { assignmentOp.OperationType = Assign } - rhsOp := &PathElement{PathElementType: Value, CandidateNode: rhs} + rhsOp := &PathElement{OperationType: ValueOp, CandidateNode: rhs} assignmentOpNode := &PathTreeNode{PathElement: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{PathElement: rhsOp}} diff --git a/pkg/yqlib/treeops/operator_recursive_descent.go b/pkg/yqlib/treeops/operator_recursive_descent.go index f87c5716..583eb9f9 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent.go +++ b/pkg/yqlib/treeops/operator_recursive_descent.go @@ -16,11 +16,14 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *orderedmap.Ordered } func recursiveDecent(d *dataTreeNavigator, results *orderedmap.OrderedMap, matchMap *orderedmap.OrderedMap) error { + splatPathElement := &PathElement{OperationType: TraversePath, Value: "[]"} + splatTreeNode := &PathTreeNode{PathElement: splatPathElement} + for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) results.Set(candidate.GetKey(), candidate) - children, err := d.traverse(nodeToMap(candidate), &PathElement{PathElementType: PathKey, Value: "[]"}) + children, err := TraversePathOperator(d, nodeToMap(candidate), splatTreeNode) if err != nil { return err diff --git a/pkg/yqlib/treeops/leaf_traverser.go b/pkg/yqlib/treeops/operator_traverse_path.go similarity index 76% rename from pkg/yqlib/treeops/leaf_traverser.go rename to pkg/yqlib/treeops/operator_traverse_path.go index 21539a6a..14803d72 100644 --- a/pkg/yqlib/treeops/leaf_traverser.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -3,26 +3,86 @@ package treeops import ( "fmt" + "github.com/elliotchance/orderedmap" "gopkg.in/yaml.v3" ) -type traverser struct { - prefs NavigationPrefs +func TraversePathOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- Traversing") + var matchingNodeMap = orderedmap.NewOrderedMap() + var newNodes []*CandidateNode + var err error + + for el := matchMap.Front(); el != nil; el = el.Next() { + newNodes, err = traverse(d, el.Value.(*CandidateNode), pathNode.PathElement) + if err != nil { + return nil, err + } + for _, n := range newNodes { + matchingNodeMap.Set(n.GetKey(), n) + } + } + + return matchingNodeMap, nil } -type LeafTraverser interface { - Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) +func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { + log.Debug("Traversing %v", NodeToString(matchingNode)) + value := matchingNode.Node + + if value.Kind == 0 { + log.Debugf("Guessing kind") + // we must ahve added this automatically, lets guess what it should be now + switch pathNode.Value.(type) { + case int, int64: + log.Debugf("probably an array") + value.Kind = yaml.SequenceNode + default: + log.Debugf("probabel a map") + value.Kind = yaml.MappingNode + } + } + + switch value.Kind { + case yaml.MappingNode: + log.Debug("its a map with %v entries", len(value.Content)/2) + return traverseMap(matchingNode, pathNode) + + case yaml.SequenceNode: + log.Debug("its a sequence of %v things!", len(value.Content)) + return traverseArray(matchingNode, pathNode) + // default: + + // if head == "+" { + // return n.appendArray(value, head, tail, pathStack) + // } else if len(value.Content) == 0 && head == "**" { + // return n.navigationStrategy.Visit(nodeContext) + // } + // return n.splatArray(value, head, tail, pathStack) + // } + // case yaml.AliasNode: + // log.Debug("its an alias!") + // DebugNode(value.Alias) + // if n.navigationStrategy.FollowAlias(nodeContext) { + // log.Debug("following the alias") + // return n.recurse(value.Alias, head, tail, pathStack) + // } + // return nil + case yaml.DocumentNode: + log.Debug("digging into doc node") + return traverse(d, &CandidateNode{ + Node: matchingNode.Node.Content[0], + Document: matchingNode.Document}, pathNode) + default: + return nil, nil + } } -func NewLeafTraverser(navigationPrefs NavigationPrefs) LeafTraverser { - return &traverser{navigationPrefs} -} - -func (t *traverser) keyMatches(key *yaml.Node, pathNode *PathElement) bool { +func keyMatches(key *yaml.Node, pathNode *PathElement) bool { return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue) } -func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { +func traverseMap(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. // merge aliases are defined first, but we only want to traverse them @@ -39,7 +99,7 @@ func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) value := contents[index+1] log.Debug("checking %v (%v)", key.Value, key.Tag) - if t.keyMatches(key, pathNode) { + if keyMatches(key, pathNode) { log.Debug("MATCHED") newMatches = append(newMatches, &CandidateNode{ Node: value, @@ -63,7 +123,7 @@ func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement) return newMatches, nil } -func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { +func traverseArray(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { log.Debug("pathNode Value %v", pathNode.Value) if pathNode.Value == "[]" { @@ -104,55 +164,3 @@ func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElemen }}, nil } - -func (t *traverser) Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { - log.Debug("Traversing %v", NodeToString(matchingNode)) - value := matchingNode.Node - - if value.Kind == 0 { - log.Debugf("Guessing kind") - // we must ahve added this automatically, lets guess what it should be now - switch pathNode.Value.(type) { - case int, int64: - log.Debugf("probably an array") - value.Kind = yaml.SequenceNode - default: - log.Debugf("probabel a map") - value.Kind = yaml.MappingNode - } - } - - switch value.Kind { - case yaml.MappingNode: - log.Debug("its a map with %v entries", len(value.Content)/2) - return t.traverseMap(matchingNode, pathNode) - - case yaml.SequenceNode: - log.Debug("its a sequence of %v things!", len(value.Content)) - return t.traverseArray(matchingNode, pathNode) - // default: - - // if head == "+" { - // return n.appendArray(value, head, tail, pathStack) - // } else if len(value.Content) == 0 && head == "**" { - // return n.navigationStrategy.Visit(nodeContext) - // } - // return n.splatArray(value, head, tail, pathStack) - // } - // case yaml.AliasNode: - // log.Debug("its an alias!") - // DebugNode(value.Alias) - // if n.navigationStrategy.FollowAlias(nodeContext) { - // log.Debug("following the alias") - // return n.recurse(value.Alias, head, tail, pathStack) - // } - // return nil - case yaml.DocumentNode: - log.Debug("digging into doc node") - return t.Traverse(&CandidateNode{ - Node: matchingNode.Node.Content[0], - Document: matchingNode.Document}, pathNode) - default: - return nil, nil - } -} diff --git a/pkg/yqlib/treeops/operator_traverse_path_test.go b/pkg/yqlib/treeops/operator_traverse_path_test.go new file mode 100644 index 00000000..14217970 --- /dev/null +++ b/pkg/yqlib/treeops/operator_traverse_path_test.go @@ -0,0 +1,21 @@ +package treeops + +import ( + "testing" +) + +var traversePathOperatorScenarios = []expressionScenario{ + { + document: `{a: {b: apple}}`, + expression: `.a`, + expected: []string{ + "D0, P[a], (!!map)::{b: apple}\n", + }, + }, +} + +func TestTraversePathOperatorScenarios(t *testing.T) { + for _, tt := range traversePathOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/treeops/path_parse_test.go index ec8f015c..7e2fe345 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -81,6 +81,7 @@ var pathTests = []struct { } var tokeniser = NewPathTokeniser() +var postFixer = NewPathPostFixer() func TestPathParsing(t *testing.T) { for _, tt := range pathTests { diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index 646ecadf..934f22bb 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -18,32 +18,31 @@ func NewPathPostFixer() PathPostFixer { } func popOpToResult(opStack []*Token, result []*PathElement) ([]*Token, []*PathElement) { - var operatorToPushToPostFix *Token - opStack, operatorToPushToPostFix = opStack[0:len(opStack)-1], opStack[len(opStack)-1] - var pathElement = PathElement{PathElementType: Operation, OperationType: operatorToPushToPostFix.OperationType} + var newOp *Token + opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1] + var pathElement = PathElement{OperationType: newOp.OperationType, Value: newOp.Value, StringValue: newOp.StringValue} + + if newOp.OperationType == ValueOp { + var candidateNode = BuildCandidateNodeFrom(newOp) + pathElement.CandidateNode = candidateNode + } + return opStack, append(result, &pathElement) } func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, error) { var result []*PathElement // surround the whole thing with quotes - var opStack = []*Token{&Token{PathElementType: OpenBracket, OperationType: None, Value: "("}} - var tokens = append(infixTokens, &Token{PathElementType: CloseBracket, OperationType: None, Value: ")"}) + var opStack = []*Token{&Token{TokenType: OpenBracket, Value: "("}} + var tokens = append(infixTokens, &Token{TokenType: CloseBracket, Value: ")"}) for _, token := range tokens { log.Debugf("postfix processing token %v", token.Value) - switch token.PathElementType { - case Value: - var candidateNode = BuildCandidateNodeFrom(token) - var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue, CandidateNode: candidateNode} - result = append(result, &pathElement) - case PathKey, SelfReference, DocumentKey: - var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue} - result = append(result, &pathElement) + switch token.TokenType { case OpenBracket, OpenCollect: opStack = append(opStack, token) case CloseCollect: - for len(opStack) > 0 && opStack[len(opStack)-1].PathElementType != OpenCollect { + for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != OpenCollect { opStack, result = popOpToResult(opStack, result) } if len(opStack) == 0 { @@ -52,10 +51,10 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, // now we should have [] as the last element on the opStack, get rid of it opStack = opStack[0 : len(opStack)-1] //and append a collect to the opStack - opStack = append(opStack, &Token{PathElementType: Operation, OperationType: Pipe}) - opStack = append(opStack, &Token{PathElementType: Operation, OperationType: Collect}) + opStack = append(opStack, &Token{TokenType: Operation, OperationType: Pipe}) + opStack = append(opStack, &Token{TokenType: Operation, OperationType: Collect}) case CloseBracket: - for len(opStack) > 0 && opStack[len(opStack)-1].PathElementType != OpenBracket { + for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != OpenBracket { opStack, result = popOpToResult(opStack, result) } if len(opStack) == 0 { diff --git a/pkg/yqlib/treeops/path_postfix_test.go b/pkg/yqlib/treeops/path_postfix_test.go deleted file mode 100644 index ef5382f2..00000000 --- a/pkg/yqlib/treeops/path_postfix_test.go +++ /dev/null @@ -1,355 +0,0 @@ -package treeops - -import ( - "testing" - - "github.com/mikefarah/yq/v3/test" -) - -// var tokeniser = NewPathTokeniser() -var postFixer = NewPathPostFixer() - -func testExpression(expression string) (string, error) { - tokens, err := tokeniser.Tokenise(expression) - if err != nil { - return "", err - } - results, errorP := postFixer.ConvertToPostfix(tokens) - if errorP != nil { - return "", errorP - } - formatted := "" - for _, path := range results { - formatted = formatted + path.toString() + ", " - } - return formatted, nil -} - - -func TestPostFixTraverseBar(t *testing.T) { - var infix = ".animals | [.]" - var expectedOutput = `PathKey - animals --------- -SELF --------- -Operation - COLLECT --------- -Operation - PIPE --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixPipeEquals(t *testing.T) { - var infix = `.animals | (. == "cat") ` - var expectedOutput = `PathKey - animals --------- -SELF --------- -Value - cat (string) --------- -Operation - EQUALS --------- -Operation - PIPE --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixCollect(t *testing.T) { - var infix = "[.a]" - var expectedOutput = `PathKey - a --------- -Operation - COLLECT --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixSplatSearch(t *testing.T) { - var infix = `.a | (.[].b == "apple")` - var expectedOutput = `PathKey - a --------- -PathKey - [] --------- -PathKey - b --------- -Operation - PIPE --------- -Value - apple (string) --------- -Operation - EQUALS --------- -Operation - PIPE --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixCollectWithExpression(t *testing.T) { - var infix = `[ (.a == "fred") | (.d, .f)]` - var expectedOutput = `PathKey - a --------- -Value - fred (string) --------- -Operation - EQUALS --------- -PathKey - d --------- -PathKey - f --------- -Operation - OR --------- -Operation - PIPE --------- -Operation - COLLECT --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixLength(t *testing.T) { - var infix = ".a | length" - var expectedOutput = `PathKey - a --------- -Operation - LENGTH --------- -Operation - PIPE --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixSimpleExample(t *testing.T) { - var infix = ".a" - var expectedOutput = `PathKey - a --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixSimplePathExample(t *testing.T) { - var infix = ".apples.bananas*.cat" - var expectedOutput = `PathKey - apples --------- -PathKey - bananas* --------- -Operation - PIPE --------- -PathKey - cat --------- -Operation - PIPE --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixSimpleAssign(t *testing.T) { - var infix = ".a.b |= \"frog\"" - var expectedOutput = `PathKey - a --------- -PathKey - b --------- -Operation - PIPE --------- -Value - frog (string) --------- -Operation - ASSIGN --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixSimplePathNumbersExample(t *testing.T) { - var infix = ".apples[0].cat" - var expectedOutput = `PathKey - apples --------- -PathKey - 0 --------- -Operation - PIPE --------- -PathKey - cat --------- -Operation - PIPE --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixSimplePathSplatArrayExample(t *testing.T) { - var infix = ".apples[].cat" - var expectedOutput = `PathKey - apples --------- -PathKey - [] --------- -Operation - PIPE --------- -PathKey - cat --------- -Operation - PIPE --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixOrExample(t *testing.T) { - var infix = ".a, .b" - var expectedOutput = `PathKey - a --------- -PathKey - b --------- -Operation - OR --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixEqualsNumberExample(t *testing.T) { - var infix = ".animal == 3" - var expectedOutput = `PathKey - animal --------- -Value - 3 (int64) --------- -Operation - EQUALS --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixOrWithEqualsExample(t *testing.T) { - var infix = ".a==\"thing\", .b==.thongs" - var expectedOutput = `PathKey - a --------- -Value - thing (string) --------- -Operation - EQUALS --------- -PathKey - b --------- -PathKey - thongs --------- -Operation - EQUALS --------- -Operation - OR --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} - -func TestPostFixOrWithEqualsPathExample(t *testing.T) { - var infix = ".apples.monkeys==\"thing\", .bogs.bobos==true" - var expectedOutput = `PathKey - apples --------- -PathKey - monkeys --------- -Operation - PIPE --------- -Value - thing (string) --------- -Operation - EQUALS --------- -PathKey - bogs --------- -PathKey - bobos --------- -Operation - PIPE --------- -Value - true (bool) --------- -Operation - EQUALS --------- -Operation - OR --------- -` - - actual, err := testExpression(infix) - if err != nil { - t.Error(err) - } - - test.AssertResultComplex(t, expectedOutput, actual) -} diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 6cc94dac..88384dfe 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -11,12 +11,21 @@ func skip(*lex.Scanner, *machines.Match) (interface{}, error) { return nil, nil } +type TokenType uint32 + +const ( + Operation = 1 << iota + OpenBracket + CloseBracket + OpenCollect + CloseCollect +) + type Token struct { - PathElementType PathElementType - OperationType *OperationType - Value interface{} - StringValue string - PrefixSelf bool + TokenType TokenType + OperationType *OperationType + Value interface{} + StringValue string CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat } @@ -28,7 +37,13 @@ func pathToken(wrapped bool) lex.Action { if wrapped { value = unwrap(value) } - return &Token{PathElementType: PathKey, OperationType: None, Value: value, StringValue: value, CheckForPostTraverse: true}, nil + return &Token{TokenType: Operation, OperationType: TraversePath, Value: value, StringValue: value, CheckForPostTraverse: true}, nil + } +} + +func literalPathToken(value string) lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + return &Token{TokenType: Operation, OperationType: TraversePath, Value: value, StringValue: value, CheckForPostTraverse: true}, nil } } @@ -40,20 +55,20 @@ func documentToken() lex.Action { if errParsingInt != nil { return nil, errParsingInt } - return &Token{PathElementType: DocumentKey, OperationType: None, Value: number, StringValue: numberString, CheckForPostTraverse: true}, nil + return &Token{TokenType: Operation, OperationType: DocumentFilter, Value: number, StringValue: numberString, CheckForPostTraverse: true}, nil } } func opToken(op *OperationType) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) - return &Token{PathElementType: Operation, OperationType: op, Value: op.Type, StringValue: value}, nil + return &Token{TokenType: Operation, OperationType: op, Value: op.Type, StringValue: value}, nil } } -func literalToken(pType PathElementType, literal string, checkForPost bool) lex.Action { +func literalToken(pType TokenType, literal string, checkForPost bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{PathElementType: pType, OperationType: None, Value: literal, StringValue: literal, CheckForPostTraverse: checkForPost}, nil + return &Token{TokenType: Operation, OperationType: ValueOp, Value: literal, StringValue: literal, CheckForPostTraverse: checkForPost}, nil } } @@ -73,7 +88,7 @@ func arrayIndextoken(precedingDot bool) lex.Action { if errParsingInt != nil { return nil, errParsingInt } - return &Token{PathElementType: PathKey, OperationType: None, Value: number, StringValue: numberString, CheckForPostTraverse: true}, nil + return &Token{TokenType: Operation, OperationType: TraversePath, Value: number, StringValue: numberString, CheckForPostTraverse: true}, nil } } @@ -84,7 +99,7 @@ func numberValue() lex.Action { if errParsingInt != nil { return nil, errParsingInt } - return &Token{PathElementType: Value, OperationType: None, Value: number, StringValue: numberString}, nil + return &Token{TokenType: Operation, OperationType: ValueOp, Value: number, StringValue: numberString}, nil } } @@ -95,13 +110,13 @@ func floatValue() lex.Action { if errParsingInt != nil { return nil, errParsingInt } - return &Token{PathElementType: Value, OperationType: None, Value: number, StringValue: numberString}, nil + return &Token{TokenType: Operation, OperationType: ValueOp, Value: number, StringValue: numberString}, nil } } func booleanValue(val bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{PathElementType: Value, OperationType: None, Value: val, StringValue: string(m.Bytes)}, nil + return &Token{TokenType: Operation, OperationType: ValueOp, Value: val, StringValue: string(m.Bytes)}, nil } } @@ -111,13 +126,13 @@ func stringValue(wrapped bool) lex.Action { if wrapped { value = unwrap(value) } - return &Token{PathElementType: Value, OperationType: None, Value: value, StringValue: value}, nil + return &Token{TokenType: Operation, OperationType: ValueOp, Value: value, StringValue: value}, nil } } func selfToken() lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{PathElementType: SelfReference, OperationType: None, Value: "SELF", StringValue: "SELF"}, nil + return &Token{TokenType: Operation, OperationType: SelfReference}, nil } } @@ -127,7 +142,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\(`), literalToken(OpenBracket, "(", false)) lexer.Add([]byte(`\)`), literalToken(CloseBracket, ")", true)) - lexer.Add([]byte(`\.?\[\]`), literalToken(PathKey, "[]", true)) + lexer.Add([]byte(`\.?\[\]`), literalPathToken("[]")) lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent)) lexer.Add([]byte(`,`), opToken(Union)) @@ -218,8 +233,8 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { postProcessedTokens = append(postProcessedTokens, token) if index != len(tokens)-1 && token.CheckForPostTraverse && - tokens[index+1].PathElementType == PathKey { - postProcessedTokens = append(postProcessedTokens, &Token{PathElementType: Operation, OperationType: Pipe, Value: "PIPE"}) + tokens[index+1].OperationType == TraversePath { + postProcessedTokens = append(postProcessedTokens, &Token{TokenType: Operation, OperationType: Pipe, Value: "PIPE"}) } } diff --git a/pkg/yqlib/treeops/path_tree.go b/pkg/yqlib/treeops/path_tree.go index 30c67ee9..e07cfa7c 100644 --- a/pkg/yqlib/treeops/path_tree.go +++ b/pkg/yqlib/treeops/path_tree.go @@ -46,7 +46,7 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeN for _, pathElement := range postFixPath { var newNode = PathTreeNode{PathElement: pathElement} log.Debugf("pathTree %v ", pathElement.toString()) - if pathElement.PathElementType == Operation && pathElement.OperationType.NumArgs > 0 { + if pathElement.OperationType.NumArgs > 0 { numArgs := pathElement.OperationType.NumArgs if numArgs == 1 { remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1] diff --git a/release_instructions.txt b/release_instructions.txt index 8e3727ad..8756ca9b 100644 --- a/release_instructions.txt +++ b/release_instructions.txt @@ -12,7 +12,6 @@ - snapcraft - will auto create a candidate, test it works then promote - - see https://build.snapcraft.io/user/mikefarah/yq sudo snap remove yq sudo snap install --edge yq From 4f574efdc4b527d85b850d41d9700cbf61d7835a Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 20 Oct 2020 15:33:20 +1100 Subject: [PATCH 047/129] simplified, refactored --- pkg/yqlib/treeops/data_tree_navigator.go | 6 +- pkg/yqlib/treeops/data_tree_navigator_test.go | 1524 +++++++---------- pkg/yqlib/treeops/lib.go | 31 +- pkg/yqlib/treeops/operator_equals.go | 2 +- pkg/yqlib/treeops/operator_multilpy.go | 12 +- .../treeops/operator_recursive_descent.go | 4 +- pkg/yqlib/treeops/operator_self.go | 7 + pkg/yqlib/treeops/operator_traverse_path.go | 10 +- .../treeops/operator_traverse_path_test.go | 54 + pkg/yqlib/treeops/operator_value.go | 7 + pkg/yqlib/treeops/operator_value_test.go | 3 +- pkg/yqlib/treeops/path_parse_test.go | 12 +- pkg/yqlib/treeops/path_postfix.go | 33 +- pkg/yqlib/treeops/path_tokeniser.go | 72 +- pkg/yqlib/treeops/path_tree.go | 22 +- pkg/yqlib/treeops/sample.yaml | 2 +- pkg/yqlib/treeops/temp.json | 8 +- pkg/yqlib/treeops/value_node_builder.go | 20 - 18 files changed, 848 insertions(+), 981 deletions(-) create mode 100644 pkg/yqlib/treeops/operator_self.go create mode 100644 pkg/yqlib/treeops/operator_value.go delete mode 100644 pkg/yqlib/treeops/value_node_builder.go diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 7306300c..d502047e 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -48,17 +48,17 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa log.Debugf("getMatchingNodes - nothing to do") return matchingNodes, nil } - log.Debugf("Processing Path: %v", pathNode.PathElement.toString()) + log.Debugf("Processing Op: %v", pathNode.Operation.toString()) if log.IsEnabledFor(logging.DEBUG) { for el := matchingNodes.Front(); el != nil; el = el.Next() { log.Debug(NodeToString(el.Value.(*CandidateNode))) } } log.Debug(">>") - handler := pathNode.PathElement.OperationType.Handler + handler := pathNode.Operation.OperationType.Handler if handler != nil { return handler(d, matchingNodes, pathNode) } - return nil, fmt.Errorf("Unknown operator %v", pathNode.PathElement.OperationType) + return nil, fmt.Errorf("Unknown operator %v", pathNode.Operation.OperationType) } diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 234d6296..750bf000 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -4,7 +4,6 @@ import ( "strings" "testing" - "github.com/mikefarah/yq/v3/test" yaml "gopkg.in/yaml.v3" ) @@ -33,877 +32,652 @@ func resultsToString(results []*CandidateNode) []string { return pretty } -func TestDataTreeNavigatorSimple(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple`) - - path, errPath := treeCreator.ParsePath("a") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!map, Kind: MappingNode, Anchor: - b: apple -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorDeleteSimple(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple - c: camel`) - - path, errPath := treeCreator.ParsePath("a .- b") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!map, Kind: MappingNode, Anchor: - c: camel -` - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorDeleteTwice(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple - c: camel - d: dingo`) - - path, errPath := treeCreator.ParsePath("a .- b OR a .- c") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!map, Kind: MappingNode, Anchor: - d: dingo -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorDeleteWithUnion(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple - c: camel - d: dingo`) - - path, errPath := treeCreator.ParsePath("a .- (b OR c)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!map, Kind: MappingNode, Anchor: - d: dingo -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorDeleteByIndex(t *testing.T) { - - nodes := readDoc(t, `a: - - b: apple - - b: sdfsd - - b: apple`) - - path, errPath := treeCreator.ParsePath("(a .- (0 or 1))") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!seq, Kind: SequenceNode, Anchor: - - b: apple -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorDeleteByFind(t *testing.T) { - - nodes := readDoc(t, `a: - - b: apple - - b: sdfsd - - b: apple`) - - path, errPath := treeCreator.ParsePath("(a .- (* == apple))") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!seq, Kind: SequenceNode, Anchor: - - b: sdfsd -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorDeleteArrayByFind(t *testing.T) { - - nodes := readDoc(t, `a: - - apple - - sdfsd - - apple`) - - path, errPath := treeCreator.ParsePath("(a .- (. == apple))") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!seq, Kind: SequenceNode, Anchor: - - sdfsd -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorDeleteViaSelf(t *testing.T) { - - nodes := readDoc(t, `- apple -- sdfsd -- apple`) - - path, errPath := treeCreator.ParsePath(". .- (. == apple)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [] - Tag: !!seq, Kind: SequenceNode, Anchor: - - sdfsd -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorFilterWithSplat(t *testing.T) { - - nodes := readDoc(t, `f: - a: frog - b: dally - c: log`) - - path, errPath := treeCreator.ParsePath(".f | .[] == \"frog\"") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [f] - Tag: !!int, Kind: ScalarNode, Anchor: - 2 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCountAndCollectWithFilterCmd(t *testing.T) { - - nodes := readDoc(t, `f: - a: frog - b: dally - c: log`) - - path, errPath := treeCreator.ParsePath(".f | .[] == *og ") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [f] - Tag: !!int, Kind: ScalarNode, Anchor: - 2 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCollectWithFilter(t *testing.T) { - - nodes := readDoc(t, `f: - a: frog - b: dally - c: log`) - - path, errPath := treeCreator.ParsePath("f(collect(. == *og))") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [f] - Tag: , Kind: SequenceNode, Anchor: - - frog -- log -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCountWithFilter2(t *testing.T) { - - nodes := readDoc(t, `f: - a: frog - b: dally - c: log`) - - path, errPath := treeCreator.ParsePath("count(f(. == *og))") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [] - Tag: !!int, Kind: ScalarNode, Anchor: - 2 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCollectWithFilter2(t *testing.T) { - - nodes := readDoc(t, `f: - a: frog - b: dally - c: log`) - - path, errPath := treeCreator.ParsePath("collect(f(. == *og))") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [] - Tag: , Kind: SequenceNode, Anchor: - - frog -- log -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCountMultipleMatchesInside(t *testing.T) { - - nodes := readDoc(t, `f: - a: [1,2] - b: dally - c: [3,4,5]`) - - path, errPath := treeCreator.ParsePath("f | count(a or c)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [f] - Tag: !!int, Kind: ScalarNode, Anchor: - 2 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCollectMultipleMatchesInside(t *testing.T) { - - nodes := readDoc(t, `f: - a: [1,2] - b: dally - c: [3,4,5]`) - - path, errPath := treeCreator.ParsePath("f | collect(a or c)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [f] - Tag: , Kind: SequenceNode, Anchor: - - [1, 2] -- [3, 4, 5] -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCountMultipleMatchesInsideSplat(t *testing.T) { - - nodes := readDoc(t, `f: - a: [1,2,3] - b: [1,2,3,4] - c: [1,2,3,4,5]`) - - path, errPath := treeCreator.ParsePath("f(count( (a or c)*))") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [f] - Tag: !!int, Kind: ScalarNode, Anchor: - 8 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCountMultipleMatchesOutside(t *testing.T) { - - nodes := readDoc(t, `f: - a: [1,2,3] - b: [1,2,3,4] - c: [1,2,3,4,5]`) - - path, errPath := treeCreator.ParsePath("f(a or c)(count(*))") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [f a] - Tag: !!int, Kind: ScalarNode, Anchor: - 3 --- Node -- - Document 0, path: [f c] - Tag: !!int, Kind: ScalarNode, Anchor: - 5 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCountOfResults(t *testing.T) { - - nodes := readDoc(t, `- apple -- sdfsd -- apple`) - - path, errPath := treeCreator.ParsePath("count(*)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [] - Tag: !!int, Kind: ScalarNode, Anchor: - 3 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorCountNoMatches(t *testing.T) { - - nodes := readDoc(t, `- apple -- sdfsd -- apple`) - - path, errPath := treeCreator.ParsePath("count(5)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [] - Tag: !!int, Kind: ScalarNode, Anchor: - 0 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorDeleteAndWrite(t *testing.T) { - - nodes := readDoc(t, `a: - - b: apple - - b: sdfsd - - { b: apple, c: cat }`) - - path, errPath := treeCreator.ParsePath("(a .- (0 or 1)) or (a[0].b := frog)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!seq, Kind: SequenceNode, Anchor: - - {b: frog, c: cat} - --- Node -- - Document 0, path: [a 0 b] - Tag: !!str, Kind: ScalarNode, Anchor: - frog -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorDeleteArray(t *testing.T) { - - nodes := readDoc(t, `a: - - b: apple - - b: sdfsd - - b: apple`) - - path, errPath := treeCreator.ParsePath("a .- (b == a*)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a] - Tag: !!seq, Kind: SequenceNode, Anchor: - - b: sdfsd -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorArraySimple(t *testing.T) { - - nodes := readDoc(t, `- b: apple`) - - path, errPath := treeCreator.ParsePath("[0]") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [0] - Tag: !!map, Kind: MappingNode, Anchor: - b: apple -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorSimpleAssignByFind(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple`) - - path, errPath := treeCreator.ParsePath("a(. == apple) := frog") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a b] - Tag: !!str, Kind: ScalarNode, Anchor: - frog -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorArraySplat(t *testing.T) { - - nodes := readDoc(t, `- b: apple -- c: banana`) - - path, errPath := treeCreator.ParsePath("[*]") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [0] - Tag: !!map, Kind: MappingNode, Anchor: - b: apple - --- Node -- - Document 0, path: [1] - Tag: !!map, Kind: MappingNode, Anchor: - c: banana -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorSimpleDeep(t *testing.T) { - - nodes := readDoc(t, `a: - b: apple`) - - path, errPath := treeCreator.ParsePath("a.b") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a b] - Tag: !!str, Kind: ScalarNode, Anchor: - apple -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorSimpleMismatch(t *testing.T) { - - nodes := readDoc(t, `a: - c: apple`) - - path, errPath := treeCreator.ParsePath("a.b") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := `` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorWild(t *testing.T) { - - nodes := readDoc(t, `a: - cat: apple - mad: things`) - - path, errPath := treeCreator.ParsePath("a.*a*") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a cat] - Tag: !!str, Kind: ScalarNode, Anchor: - apple - --- Node -- - Document 0, path: [a mad] - Tag: !!str, Kind: ScalarNode, Anchor: - things -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorWildDeepish(t *testing.T) { - - nodes := readDoc(t, `a: - cat: {b: 3} - mad: {b: 4} - fad: {c: t}`) - - path, errPath := treeCreator.ParsePath("a.*a*.b") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a cat b] - Tag: !!int, Kind: ScalarNode, Anchor: - 3 - --- Node -- - Document 0, path: [a mad b] - Tag: !!int, Kind: ScalarNode, Anchor: - 4 -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorOrSimple(t *testing.T) { - - nodes := readDoc(t, `a: - cat: apple - mad: things`) - - path, errPath := treeCreator.ParsePath("a.(cat or mad)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a cat] - Tag: !!str, Kind: ScalarNode, Anchor: - apple - --- Node -- - Document 0, path: [a mad] - Tag: !!str, Kind: ScalarNode, Anchor: - things -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorOrSimpleWithDepth(t *testing.T) { - - nodes := readDoc(t, `a: - cat: {b: 3} - mad: {b: 4} - fad: {c: t}`) - - path, errPath := treeCreator.ParsePath("a.(cat.* or fad.*)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a cat b] - Tag: !!int, Kind: ScalarNode, Anchor: - 3 - --- Node -- - Document 0, path: [a fad c] - Tag: !!str, Kind: ScalarNode, Anchor: - t -` - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorOrDeDupes(t *testing.T) { - - nodes := readDoc(t, `a: - cat: apple - mad: things`) - - path, errPath := treeCreator.ParsePath("a.(cat or cat)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a cat] - Tag: !!str, Kind: ScalarNode, Anchor: - apple -` - - test.AssertResult(t, expected, resultsToString(results)) -} - -func TestDataTreeNavigatorAnd(t *testing.T) { - - nodes := readDoc(t, `a: - cat: apple - pat: apple - cow: apple - mad: things`) - - path, errPath := treeCreator.ParsePath("a.(*t and c*)") - if errPath != nil { - t.Error(errPath) - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - } - - expected := ` --- Node -- - Document 0, path: [a cat] - Tag: !!str, Kind: ScalarNode, Anchor: - apple -` - - test.AssertResult(t, expected, resultsToString(results)) -} +// func TestDataTreeNavigatorDeleteSimple(t *testing.T) { + +// nodes := readDoc(t, `a: +// b: apple +// c: camel`) + +// path, errPath := treeCreator.ParsePath("a .- b") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a] +// Tag: !!map, Kind: MappingNode, Anchor: +// c: camel +// ` +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorDeleteTwice(t *testing.T) { + +// nodes := readDoc(t, `a: +// b: apple +// c: camel +// d: dingo`) + +// path, errPath := treeCreator.ParsePath("a .- b OR a .- c") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a] +// Tag: !!map, Kind: MappingNode, Anchor: +// d: dingo +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorDeleteWithUnion(t *testing.T) { + +// nodes := readDoc(t, `a: +// b: apple +// c: camel +// d: dingo`) + +// path, errPath := treeCreator.ParsePath("a .- (b OR c)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a] +// Tag: !!map, Kind: MappingNode, Anchor: +// d: dingo +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorDeleteByIndex(t *testing.T) { + +// nodes := readDoc(t, `a: +// - b: apple +// - b: sdfsd +// - b: apple`) + +// path, errPath := treeCreator.ParsePath("(a .- (0 or 1))") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a] +// Tag: !!seq, Kind: SequenceNode, Anchor: +// - b: apple +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorDeleteByFind(t *testing.T) { + +// nodes := readDoc(t, `a: +// - b: apple +// - b: sdfsd +// - b: apple`) + +// path, errPath := treeCreator.ParsePath("(a .- (* == apple))") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a] +// Tag: !!seq, Kind: SequenceNode, Anchor: +// - b: sdfsd +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorDeleteArrayByFind(t *testing.T) { + +// nodes := readDoc(t, `a: +// - apple +// - sdfsd +// - apple`) + +// path, errPath := treeCreator.ParsePath("(a .- (. == apple))") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a] +// Tag: !!seq, Kind: SequenceNode, Anchor: +// - sdfsd +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorDeleteViaSelf(t *testing.T) { + +// nodes := readDoc(t, `- apple +// - sdfsd +// - apple`) + +// path, errPath := treeCreator.ParsePath(". .- (. == apple)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [] +// Tag: !!seq, Kind: SequenceNode, Anchor: +// - sdfsd +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorFilterWithSplat(t *testing.T) { + +// nodes := readDoc(t, `f: +// a: frog +// b: dally +// c: log`) + +// path, errPath := treeCreator.ParsePath(".f | .[] == \"frog\"") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [f] +// Tag: !!int, Kind: ScalarNode, Anchor: +// 2 +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCountAndCollectWithFilterCmd(t *testing.T) { + +// nodes := readDoc(t, `f: +// a: frog +// b: dally +// c: log`) + +// path, errPath := treeCreator.ParsePath(".f | .[] == *og ") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [f] +// Tag: !!int, Kind: ScalarNode, Anchor: +// 2 +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCollectWithFilter(t *testing.T) { + +// nodes := readDoc(t, `f: +// a: frog +// b: dally +// c: log`) + +// path, errPath := treeCreator.ParsePath("f(collect(. == *og))") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [f] +// Tag: , Kind: SequenceNode, Anchor: +// - frog +// - log +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCountWithFilter2(t *testing.T) { + +// nodes := readDoc(t, `f: +// a: frog +// b: dally +// c: log`) + +// path, errPath := treeCreator.ParsePath("count(f(. == *og))") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [] +// Tag: !!int, Kind: ScalarNode, Anchor: +// 2 +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCollectWithFilter2(t *testing.T) { + +// nodes := readDoc(t, `f: +// a: frog +// b: dally +// c: log`) + +// path, errPath := treeCreator.ParsePath("collect(f(. == *og))") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [] +// Tag: , Kind: SequenceNode, Anchor: +// - frog +// - log +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCountMultipleMatchesInside(t *testing.T) { + +// nodes := readDoc(t, `f: +// a: [1,2] +// b: dally +// c: [3,4,5]`) + +// path, errPath := treeCreator.ParsePath("f | count(a or c)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [f] +// Tag: !!int, Kind: ScalarNode, Anchor: +// 2 +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCollectMultipleMatchesInside(t *testing.T) { + +// nodes := readDoc(t, `f: +// a: [1,2] +// b: dally +// c: [3,4,5]`) + +// path, errPath := treeCreator.ParsePath("f | collect(a or c)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [f] +// Tag: , Kind: SequenceNode, Anchor: +// - [1, 2] +// - [3, 4, 5] +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCountMultipleMatchesInsideSplat(t *testing.T) { + +// nodes := readDoc(t, `f: +// a: [1,2,3] +// b: [1,2,3,4] +// c: [1,2,3,4,5]`) + +// path, errPath := treeCreator.ParsePath("f(count( (a or c)*))") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [f] +// Tag: !!int, Kind: ScalarNode, Anchor: +// 8 +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCountMultipleMatchesOutside(t *testing.T) { + +// nodes := readDoc(t, `f: +// a: [1,2,3] +// b: [1,2,3,4] +// c: [1,2,3,4,5]`) + +// path, errPath := treeCreator.ParsePath("f(a or c)(count(*))") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [f a] +// Tag: !!int, Kind: ScalarNode, Anchor: +// 3 +// -- Node -- +// Document 0, path: [f c] +// Tag: !!int, Kind: ScalarNode, Anchor: +// 5 +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCountOfResults(t *testing.T) { + +// nodes := readDoc(t, `- apple +// - sdfsd +// - apple`) + +// path, errPath := treeCreator.ParsePath("count(*)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [] +// Tag: !!int, Kind: ScalarNode, Anchor: +// 3 +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorCountNoMatches(t *testing.T) { + +// nodes := readDoc(t, `- apple +// - sdfsd +// - apple`) + +// path, errPath := treeCreator.ParsePath("count(5)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [] +// Tag: !!int, Kind: ScalarNode, Anchor: +// 0 +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorDeleteAndWrite(t *testing.T) { + +// nodes := readDoc(t, `a: +// - b: apple +// - b: sdfsd +// - { b: apple, c: cat }`) + +// path, errPath := treeCreator.ParsePath("(a .- (0 or 1)) or (a[0].b := frog)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a] +// Tag: !!seq, Kind: SequenceNode, Anchor: +// - {b: frog, c: cat} + +// -- Node -- +// Document 0, path: [a 0 b] +// Tag: !!str, Kind: ScalarNode, Anchor: +// frog +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorDeleteArray(t *testing.T) { + +// nodes := readDoc(t, `a: +// - b: apple +// - b: sdfsd +// - b: apple`) + +// path, errPath := treeCreator.ParsePath("a .- (b == a*)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a] +// Tag: !!seq, Kind: SequenceNode, Anchor: +// - b: sdfsd +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorArraySimple(t *testing.T) { + +// nodes := readDoc(t, `- b: apple`) + +// path, errPath := treeCreator.ParsePath("[0]") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [0] +// Tag: !!map, Kind: MappingNode, Anchor: +// b: apple +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorSimpleAssignByFind(t *testing.T) { + +// nodes := readDoc(t, `a: +// b: apple`) + +// path, errPath := treeCreator.ParsePath("a(. == apple) := frog") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a b] +// Tag: !!str, Kind: ScalarNode, Anchor: +// frog +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorOrDeDupes(t *testing.T) { + +// nodes := readDoc(t, `a: +// cat: apple +// mad: things`) + +// path, errPath := treeCreator.ParsePath("a.(cat or cat)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a cat] +// Tag: !!str, Kind: ScalarNode, Anchor: +// apple +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } + +// func TestDataTreeNavigatorAnd(t *testing.T) { + +// nodes := readDoc(t, `a: +// cat: apple +// pat: apple +// cow: apple +// mad: things`) + +// path, errPath := treeCreator.ParsePath("a.(*t and c*)") +// if errPath != nil { +// t.Error(errPath) +// } +// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + +// if errNav != nil { +// t.Error(errNav) +// } + +// expected := ` +// -- Node -- +// Document 0, path: [a cat] +// Tag: !!str, Kind: ScalarNode, Anchor: +// apple +// ` + +// test.AssertResult(t, expected, resultsToString(results)) +// } diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 28c2f459..b60d055e 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -36,8 +36,8 @@ var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handle var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} -var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} -var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} +var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator} +var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator} var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator} @@ -52,15 +52,38 @@ var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Han // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} // filters matches if they have the existing path -type PathElement struct { +type Operation struct { OperationType *OperationType Value interface{} StringValue string CandidateNode *CandidateNode // used for Value Path elements } +func CreateValueOperation(value interface{}, stringValue string) *Operation { + var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode} + node.Value = stringValue + + switch value.(type) { + case float32, float64: + node.Tag = "!!float" + case int, int64, int32: + node.Tag = "!!int" + case bool: + node.Tag = "!!bool" + case string: + node.Tag = "!!str" + } + + return &Operation{ + OperationType: ValueOp, + Value: value, + StringValue: stringValue, + CandidateNode: &CandidateNode{Node: &node}, + } +} + // debugging purposes only -func (p *PathElement) toString() string { +func (p *Operation) toString() string { if p.OperationType == TraversePath { return fmt.Sprintf("%v", p.Value) } else if p.OperationType == DocumentFilter { diff --git a/pkg/yqlib/treeops/operator_equals.go b/pkg/yqlib/treeops/operator_equals.go index 18dfa1c6..a2a4aa2d 100644 --- a/pkg/yqlib/treeops/operator_equals.go +++ b/pkg/yqlib/treeops/operator_equals.go @@ -34,7 +34,7 @@ func hasMatch(d *dataTreeNavigator, candidate *CandidateNode, lhs *PathTreeNode, } // TODO = handle other RHS types - return containsMatchingValue(childMatches, rhs.PathElement.StringValue), nil + return containsMatchingValue(childMatches, rhs.Operation.StringValue), nil } func containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool { diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index 57b73299..e0efff67 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -61,12 +61,12 @@ func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*Ca func createTraversalTree(path []interface{}) *PathTreeNode { if len(path) == 0 { - return &PathTreeNode{PathElement: &PathElement{OperationType: SelfReference}} + return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}} } else if len(path) == 1 { - return &PathTreeNode{PathElement: &PathElement{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} + return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} } return &PathTreeNode{ - PathElement: &PathElement{OperationType: Pipe}, + Operation: &Operation{OperationType: Pipe}, Lhs: createTraversalTree(path[0:1]), Rhs: createTraversalTree(path[1:])} @@ -77,13 +77,13 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid lhsPath := rhs.Path[pathIndexToStartFrom:] - assignmentOp := &PathElement{OperationType: AssignAttributes} + assignmentOp := &Operation{OperationType: AssignAttributes} if rhs.Node.Kind == yaml.ScalarNode { assignmentOp.OperationType = Assign } - rhsOp := &PathElement{OperationType: ValueOp, CandidateNode: rhs} + rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} - assignmentOpNode := &PathTreeNode{PathElement: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{PathElement: rhsOp}} + assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{Operation: rhsOp}} _, err := d.getMatchingNodes(nodeToMap(lhs), assignmentOpNode) diff --git a/pkg/yqlib/treeops/operator_recursive_descent.go b/pkg/yqlib/treeops/operator_recursive_descent.go index 583eb9f9..38513b7a 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent.go +++ b/pkg/yqlib/treeops/operator_recursive_descent.go @@ -16,8 +16,8 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *orderedmap.Ordered } func recursiveDecent(d *dataTreeNavigator, results *orderedmap.OrderedMap, matchMap *orderedmap.OrderedMap) error { - splatPathElement := &PathElement{OperationType: TraversePath, Value: "[]"} - splatTreeNode := &PathTreeNode{PathElement: splatPathElement} + splatOperation := &Operation{OperationType: TraversePath, Value: "[]"} + splatTreeNode := &PathTreeNode{Operation: splatOperation} for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) diff --git a/pkg/yqlib/treeops/operator_self.go b/pkg/yqlib/treeops/operator_self.go new file mode 100644 index 00000000..170ef2c6 --- /dev/null +++ b/pkg/yqlib/treeops/operator_self.go @@ -0,0 +1,7 @@ +package treeops + +import "github.com/elliotchance/orderedmap" + +func SelfOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + return matchMap, nil +} diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index 14803d72..47e396df 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -14,7 +14,7 @@ func TraversePathOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, var err error for el := matchMap.Front(); el != nil; el = el.Next() { - newNodes, err = traverse(d, el.Value.(*CandidateNode), pathNode.PathElement) + newNodes, err = traverse(d, el.Value.(*CandidateNode), pathNode.Operation) if err != nil { return nil, err } @@ -26,7 +26,7 @@ func TraversePathOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, return matchingNodeMap, nil } -func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { +func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Operation) ([]*CandidateNode, error) { log.Debug("Traversing %v", NodeToString(matchingNode)) value := matchingNode.Node @@ -78,11 +78,11 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathE } } -func keyMatches(key *yaml.Node, pathNode *PathElement) bool { +func keyMatches(key *yaml.Node, pathNode *Operation) bool { return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue) } -func traverseMap(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { +func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNode, error) { // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. // merge aliases are defined first, but we only want to traverse them @@ -123,7 +123,7 @@ func traverseMap(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateN return newMatches, nil } -func traverseArray(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { +func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNode, error) { log.Debug("pathNode Value %v", pathNode.Value) if pathNode.Value == "[]" { diff --git a/pkg/yqlib/treeops/operator_traverse_path_test.go b/pkg/yqlib/treeops/operator_traverse_path_test.go index 14217970..4199d0a2 100644 --- a/pkg/yqlib/treeops/operator_traverse_path_test.go +++ b/pkg/yqlib/treeops/operator_traverse_path_test.go @@ -12,6 +12,60 @@ var traversePathOperatorScenarios = []expressionScenario{ "D0, P[a], (!!map)::{b: apple}\n", }, }, + { + document: `[{b: apple}, {c: banana}]`, + expression: `.[]`, + expected: []string{ + "D0, P[0], (!!map)::{b: apple}\n", + "D0, P[1], (!!map)::{c: banana}\n", + }, + }, + { + document: `{}`, + expression: `.a.b`, + expected: []string{ + "D0, P[a b], ()::null\n", + }, + }, + { + document: `{}`, + expression: `.[1].a`, + expected: []string{ + "D0, P[1 a], ()::null\n", + }, + }, + { + document: `{}`, + expression: `.a.[1]`, + expected: []string{ + "D0, P[a 1], ()::null\n", + }, + }, + { + document: `{a: {cat: apple, mad: things}}`, + expression: `.a."*a*"`, + expected: []string{ + "D0, P[a cat], (!!str)::apple\n", + "D0, P[a mad], (!!str)::things\n", + }, + }, + { + document: `{a: {cat: {b: 3}, mad: {b: 4}, fad: {c: t}}}`, + expression: `.a."*a*".b`, + expected: []string{ + "D0, P[a cat b], (!!int)::3\n", + "D0, P[a mad b], (!!int)::4\n", + "D0, P[a fad b], ()::null\n", + }, + }, + { + document: `{a: {cat: apple, mad: things}}`, + expression: `.a | (.cat, .mad)`, + expected: []string{ + "D0, P[a cat], (!!str)::apple\n", + "D0, P[a mad], (!!str)::things\n", + }, + }, } func TestTraversePathOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_value.go b/pkg/yqlib/treeops/operator_value.go new file mode 100644 index 00000000..caabd354 --- /dev/null +++ b/pkg/yqlib/treeops/operator_value.go @@ -0,0 +1,7 @@ +package treeops + +import "github.com/elliotchance/orderedmap" + +func ValueOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + return nodeToMap(pathNode.Operation.CandidateNode), nil +} diff --git a/pkg/yqlib/treeops/operator_value_test.go b/pkg/yqlib/treeops/operator_value_test.go index b722a4b4..c7512f61 100644 --- a/pkg/yqlib/treeops/operator_value_test.go +++ b/pkg/yqlib/treeops/operator_value_test.go @@ -11,7 +11,8 @@ var valueOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!int)::1\n", }, - }, { + }, + { document: ``, expression: `-1`, expected: []string{ diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/treeops/path_parse_test.go index 7e2fe345..16bcf245 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -41,22 +41,22 @@ var pathTests = []struct { // {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")}, { `d0.a`, - append(make([]interface{}, 0), int64(0), "PIPE", "a"), - append(make([]interface{}, 0), "D0", "a", "PIPE"), + append(make([]interface{}, 0), "d0", "PIPE", "a"), + append(make([]interface{}, 0), "d0", "a", "PIPE"), }, { `.a | (.[].b == "apple")`, - append(make([]interface{}, 0), "a", "PIPE", "(", "[]", "PIPE", "b", "EQUALS", "apple", ")"), + append(make([]interface{}, 0), "a", "PIPE", "(", "[]", "PIPE", "b", "EQUALS", "apple (string)", ")"), append(make([]interface{}, 0), "a", "[]", "b", "PIPE", "apple (string)", "EQUALS", "PIPE"), }, { `.[] | select(. == "*at")`, - append(make([]interface{}, 0), "[]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at", ")"), + append(make([]interface{}, 0), "[]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"), append(make([]interface{}, 0), "[]", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"), }, { `[true]`, - append(make([]interface{}, 0), "[", true, "]"), + append(make([]interface{}, 0), "[", "true (bool)", "]"), append(make([]interface{}, 0), "true (bool)", "COLLECT", "PIPE"), }, @@ -91,7 +91,7 @@ func TestPathParsing(t *testing.T) { } var tokenValues []interface{} for _, token := range tokens { - tokenValues = append(tokenValues, token.Value) + tokenValues = append(tokenValues, token.toString()) } test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, fmt.Sprintf("tokenise: %v", tt.path)) diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index 934f22bb..850307d4 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -7,7 +7,7 @@ import ( ) type PathPostFixer interface { - ConvertToPostfix([]*Token) ([]*PathElement, error) + ConvertToPostfix([]*Token) ([]*Operation, error) } type pathPostFixer struct { @@ -17,27 +17,20 @@ func NewPathPostFixer() PathPostFixer { return &pathPostFixer{} } -func popOpToResult(opStack []*Token, result []*PathElement) ([]*Token, []*PathElement) { +func popOpToResult(opStack []*Token, result []*Operation) ([]*Token, []*Operation) { var newOp *Token opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1] - var pathElement = PathElement{OperationType: newOp.OperationType, Value: newOp.Value, StringValue: newOp.StringValue} - - if newOp.OperationType == ValueOp { - var candidateNode = BuildCandidateNodeFrom(newOp) - pathElement.CandidateNode = candidateNode - } - - return opStack, append(result, &pathElement) + return opStack, append(result, newOp.Operation) } -func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, error) { - var result []*PathElement +func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, error) { + var result []*Operation // surround the whole thing with quotes - var opStack = []*Token{&Token{TokenType: OpenBracket, Value: "("}} - var tokens = append(infixTokens, &Token{TokenType: CloseBracket, Value: ")"}) + var opStack = []*Token{&Token{TokenType: OpenBracket}} + var tokens = append(infixTokens, &Token{TokenType: CloseBracket}) for _, token := range tokens { - log.Debugf("postfix processing token %v", token.Value) + log.Debugf("postfix processing token %v, %v", token.toString(), token.Operation) switch token.TokenType { case OpenBracket, OpenCollect: opStack = append(opStack, token) @@ -51,8 +44,8 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, // now we should have [] as the last element on the opStack, get rid of it opStack = opStack[0 : len(opStack)-1] //and append a collect to the opStack - opStack = append(opStack, &Token{TokenType: Operation, OperationType: Pipe}) - opStack = append(opStack, &Token{TokenType: Operation, OperationType: Collect}) + opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: Pipe}}) + opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: Collect}}) case CloseBracket: for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != OpenBracket { opStack, result = popOpToResult(opStack, result) @@ -64,9 +57,11 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement, opStack = opStack[0 : len(opStack)-1] default: - var currentPrecedence = token.OperationType.Precedence + var currentPrecedence = token.Operation.OperationType.Precedence // pop off higher precedent operators onto the result - for len(opStack) > 0 && opStack[len(opStack)-1].OperationType.Precedence >= currentPrecedence { + for len(opStack) > 0 && + opStack[len(opStack)-1].TokenType == OperationToken && + opStack[len(opStack)-1].Operation.OperationType.Precedence >= currentPrecedence { opStack, result = popOpToResult(opStack, result) } // add this operator to the opStack diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 88384dfe..ca302f78 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -1,6 +1,7 @@ package treeops import ( + "fmt" "strconv" lex "github.com/timtadh/lexmachine" @@ -14,7 +15,7 @@ func skip(*lex.Scanner, *machines.Match) (interface{}, error) { type TokenType uint32 const ( - Operation = 1 << iota + OperationToken = 1 << iota OpenBracket CloseBracket OpenCollect @@ -22,14 +23,28 @@ const ( ) type Token struct { - TokenType TokenType - OperationType *OperationType - Value interface{} - StringValue string + TokenType TokenType + Operation *Operation CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat } +func (t *Token) toString() string { + if t.TokenType == OperationToken { + return t.Operation.toString() + } else if t.TokenType == OpenBracket { + return "(" + } else if t.TokenType == CloseBracket { + return ")" + } else if t.TokenType == OpenCollect { + return "[" + } else if t.TokenType == CloseCollect { + return "]" + } else { + return fmt.Sprintf("NFI") + } +} + func pathToken(wrapped bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) @@ -37,13 +52,15 @@ func pathToken(wrapped bool) lex.Action { if wrapped { value = unwrap(value) } - return &Token{TokenType: Operation, OperationType: TraversePath, Value: value, StringValue: value, CheckForPostTraverse: true}, nil + op := &Operation{OperationType: TraversePath, Value: value, StringValue: value} + return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil } } func literalPathToken(value string) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{TokenType: Operation, OperationType: TraversePath, Value: value, StringValue: value, CheckForPostTraverse: true}, nil + op := &Operation{OperationType: TraversePath, Value: value, StringValue: value} + return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil } } @@ -55,20 +72,22 @@ func documentToken() lex.Action { if errParsingInt != nil { return nil, errParsingInt } - return &Token{TokenType: Operation, OperationType: DocumentFilter, Value: number, StringValue: numberString, CheckForPostTraverse: true}, nil + op := &Operation{OperationType: DocumentFilter, Value: number, StringValue: numberString} + return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil } } func opToken(op *OperationType) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) - return &Token{TokenType: Operation, OperationType: op, Value: op.Type, StringValue: value}, nil + op := &Operation{OperationType: op, Value: op.Type, StringValue: value} + return &Token{TokenType: OperationToken, Operation: op}, nil } } -func literalToken(pType TokenType, literal string, checkForPost bool) lex.Action { +func literalToken(pType TokenType, checkForPost bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{TokenType: Operation, OperationType: ValueOp, Value: literal, StringValue: literal, CheckForPostTraverse: checkForPost}, nil + return &Token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil } } @@ -88,7 +107,8 @@ func arrayIndextoken(precedingDot bool) lex.Action { if errParsingInt != nil { return nil, errParsingInt } - return &Token{TokenType: Operation, OperationType: TraversePath, Value: number, StringValue: numberString, CheckForPostTraverse: true}, nil + op := &Operation{OperationType: TraversePath, Value: number, StringValue: numberString} + return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil } } @@ -99,7 +119,8 @@ func numberValue() lex.Action { if errParsingInt != nil { return nil, errParsingInt } - return &Token{TokenType: Operation, OperationType: ValueOp, Value: number, StringValue: numberString}, nil + + return &Token{TokenType: OperationToken, Operation: CreateValueOperation(number, numberString)}, nil } } @@ -110,13 +131,13 @@ func floatValue() lex.Action { if errParsingInt != nil { return nil, errParsingInt } - return &Token{TokenType: Operation, OperationType: ValueOp, Value: number, StringValue: numberString}, nil + return &Token{TokenType: OperationToken, Operation: CreateValueOperation(number, numberString)}, nil } } func booleanValue(val bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{TokenType: Operation, OperationType: ValueOp, Value: val, StringValue: string(m.Bytes)}, nil + return &Token{TokenType: OperationToken, Operation: CreateValueOperation(val, string(m.Bytes))}, nil } } @@ -126,21 +147,22 @@ func stringValue(wrapped bool) lex.Action { if wrapped { value = unwrap(value) } - return &Token{TokenType: Operation, OperationType: ValueOp, Value: value, StringValue: value}, nil + return &Token{TokenType: OperationToken, Operation: CreateValueOperation(value, value)}, nil } } func selfToken() lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { - return &Token{TokenType: Operation, OperationType: SelfReference}, nil + op := &Operation{OperationType: SelfReference} + return &Token{TokenType: OperationToken, Operation: op}, nil } } // Creates the lexer object and compiles the NFA. func initLexer() (*lex.Lexer, error) { lexer := lex.NewLexer() - lexer.Add([]byte(`\(`), literalToken(OpenBracket, "(", false)) - lexer.Add([]byte(`\)`), literalToken(CloseBracket, ")", true)) + lexer.Add([]byte(`\(`), literalToken(OpenBracket, false)) + lexer.Add([]byte(`\)`), literalToken(CloseBracket, true)) lexer.Add([]byte(`\.?\[\]`), literalPathToken("[]")) lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent)) @@ -180,8 +202,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`"[^ "]+"`), stringValue(true)) - lexer.Add([]byte(`\[`), literalToken(OpenCollect, "[", false)) - lexer.Add([]byte(`\]`), literalToken(CloseCollect, "]", true)) + lexer.Add([]byte(`\[`), literalToken(OpenCollect, false)) + lexer.Add([]byte(`\]`), literalToken(CloseCollect, true)) lexer.Add([]byte(`\*`), opToken(Multiply)) // lexer.Add([]byte(`[^ \,\|\.\[\(\)=]+`), stringValue(false)) @@ -219,7 +241,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { if tok != nil { token := tok.(*Token) - log.Debugf("Tokenising %v - %v", token.Value, token.OperationType.Type) + log.Debugf("Tokenising %v", token.toString()) tokens = append(tokens, token) } if err != nil { @@ -233,8 +255,10 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { postProcessedTokens = append(postProcessedTokens, token) if index != len(tokens)-1 && token.CheckForPostTraverse && - tokens[index+1].OperationType == TraversePath { - postProcessedTokens = append(postProcessedTokens, &Token{TokenType: Operation, OperationType: Pipe, Value: "PIPE"}) + tokens[index+1].TokenType == OperationToken && + tokens[index+1].Operation.OperationType == TraversePath { + op := &Operation{OperationType: Pipe, Value: "PIPE"} + postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op}) } } diff --git a/pkg/yqlib/treeops/path_tree.go b/pkg/yqlib/treeops/path_tree.go index e07cfa7c..bee15ff8 100644 --- a/pkg/yqlib/treeops/path_tree.go +++ b/pkg/yqlib/treeops/path_tree.go @@ -6,14 +6,14 @@ var myPathTokeniser = NewPathTokeniser() var myPathPostfixer = NewPathPostFixer() type PathTreeNode struct { - PathElement *PathElement + Operation *Operation Lhs *PathTreeNode Rhs *PathTreeNode } type PathTreeCreator interface { ParsePath(path string) (*PathTreeNode, error) - CreatePathTree(postFixPath []*PathElement) (*PathTreeNode, error) + CreatePathTree(postFixPath []*Operation) (*PathTreeNode, error) } type pathTreeCreator struct { @@ -28,26 +28,26 @@ func (p *pathTreeCreator) ParsePath(path string) (*PathTreeNode, error) { if err != nil { return nil, err } - var pathElements []*PathElement - pathElements, err = myPathPostfixer.ConvertToPostfix(tokens) + var Operations []*Operation + Operations, err = myPathPostfixer.ConvertToPostfix(tokens) if err != nil { return nil, err } - return p.CreatePathTree(pathElements) + return p.CreatePathTree(Operations) } -func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeNode, error) { +func (p *pathTreeCreator) CreatePathTree(postFixPath []*Operation) (*PathTreeNode, error) { var stack = make([]*PathTreeNode, 0) if len(postFixPath) == 0 { return nil, nil } - for _, pathElement := range postFixPath { - var newNode = PathTreeNode{PathElement: pathElement} - log.Debugf("pathTree %v ", pathElement.toString()) - if pathElement.OperationType.NumArgs > 0 { - numArgs := pathElement.OperationType.NumArgs + for _, Operation := range postFixPath { + var newNode = PathTreeNode{Operation: Operation} + log.Debugf("pathTree %v ", Operation.toString()) + if Operation.OperationType.NumArgs > 0 { + numArgs := Operation.OperationType.NumArgs if numArgs == 1 { remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1] newNode.Rhs = rhs diff --git a/pkg/yqlib/treeops/sample.yaml b/pkg/yqlib/treeops/sample.yaml index 7997a4d4..dcee3caf 100644 --- a/pkg/yqlib/treeops/sample.yaml +++ b/pkg/yqlib/treeops/sample.yaml @@ -1 +1 @@ -{a: {b: apple, c: cactus}} \ No newline at end of file +{a: {cat: apple, mad: things}} \ No newline at end of file diff --git a/pkg/yqlib/treeops/temp.json b/pkg/yqlib/treeops/temp.json index 7d768a0b..3ce4e779 100644 --- a/pkg/yqlib/treeops/temp.json +++ b/pkg/yqlib/treeops/temp.json @@ -1,4 +1,6 @@ { - "a": [1,2], - "b": [3,4] -} \ No newline at end of file + "a": { + "cat": "apple", + "mad": "things" + } +} diff --git a/pkg/yqlib/treeops/value_node_builder.go b/pkg/yqlib/treeops/value_node_builder.go deleted file mode 100644 index 7c3e7d21..00000000 --- a/pkg/yqlib/treeops/value_node_builder.go +++ /dev/null @@ -1,20 +0,0 @@ -package treeops - -import "gopkg.in/yaml.v3" - -func BuildCandidateNodeFrom(token *Token) *CandidateNode { - var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode} - node.Value = token.StringValue - - switch token.Value.(type) { - case float32, float64: - node.Tag = "!!float" - case int, int64, int32: - node.Tag = "!!int" - case bool: - node.Tag = "!!bool" - case string: - node.Tag = "!!str" - } - return &CandidateNode{Node: &node} -} From 49615f55816f97b04517a16ee223115bb8fcffa8 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 20 Oct 2020 15:40:11 +1100 Subject: [PATCH 048/129] Added null --- pkg/yqlib/treeops/lib.go | 2 ++ pkg/yqlib/treeops/operator_select_test.go | 6 ++++++ pkg/yqlib/treeops/operator_value_test.go | 14 ++++++++++++++ pkg/yqlib/treeops/path_tokeniser.go | 9 +++++++++ 4 files changed, 31 insertions(+) diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index b60d055e..df0ba912 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -72,6 +72,8 @@ func CreateValueOperation(value interface{}, stringValue string) *Operation { node.Tag = "!!bool" case string: node.Tag = "!!str" + case nil: + node.Tag = "!!null" } return &Operation{ diff --git a/pkg/yqlib/treeops/operator_select_test.go b/pkg/yqlib/treeops/operator_select_test.go index 70536768..3a2dae1e 100644 --- a/pkg/yqlib/treeops/operator_select_test.go +++ b/pkg/yqlib/treeops/operator_select_test.go @@ -35,6 +35,12 @@ var selectOperatorScenarios = []expressionScenario{ "D0, P[a things], (!!map)::{include: true}\n", "D0, P[a andMe], (!!map)::{include: fold}\n", }, + }, { + document: `[cat,~,dog]`, + expression: `.[] | select(. == ~)`, + expected: []string{ + "D0, P[1], (!!null)::~\n", + }, }, } diff --git a/pkg/yqlib/treeops/operator_value_test.go b/pkg/yqlib/treeops/operator_value_test.go index c7512f61..43408f76 100644 --- a/pkg/yqlib/treeops/operator_value_test.go +++ b/pkg/yqlib/treeops/operator_value_test.go @@ -67,6 +67,20 @@ var valueOperatorScenarios = []expressionScenario{ "D0, P[], (!!bool)::false\n", }, }, + { + document: ``, + expression: `Null`, + expected: []string{ + "D0, P[], (!!null)::Null\n", + }, + }, + { + document: ``, + expression: `~`, + expected: []string{ + "D0, P[], (!!null)::~\n", + }, + }, } func TestValueOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index ca302f78..23c575c3 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -151,6 +151,12 @@ func stringValue(wrapped bool) lex.Action { } } +func nullValue() lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + return &Token{TokenType: OperationToken, Operation: CreateValueOperation(nil, string(m.Bytes))}, nil + } +} + func selfToken() lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { op := &Operation{OperationType: SelfReference} @@ -200,6 +206,9 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`[Tt][Rr][Uu][Ee]`), booleanValue(true)) lexer.Add([]byte(`[Ff][Aa][Ll][Ss][Ee]`), booleanValue(false)) + lexer.Add([]byte(`[Nn][Uu][Ll][Ll]`), nullValue()) + lexer.Add([]byte(`~`), nullValue()) + lexer.Add([]byte(`"[^ "]+"`), stringValue(true)) lexer.Add([]byte(`\[`), literalToken(OpenCollect, false)) From 6a698332dd5ae6b581ef8615299f0e8ac6923c34 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 20 Oct 2020 16:27:30 +1100 Subject: [PATCH 049/129] more --- pkg/yqlib/treeops/lib.go | 1 + pkg/yqlib/treeops/operator_booleans.go | 3 + pkg/yqlib/treeops/operator_equals.go | 53 ++------ pkg/yqlib/treeops/operator_equals_test.go | 62 +++++---- pkg/yqlib/treeops/operator_multilpy.go | 17 ++- pkg/yqlib/treeops/operator_multiply_test.go | 122 +++++++++--------- pkg/yqlib/treeops/operator_not.go | 20 +++ pkg/yqlib/treeops/operator_not_test.go | 56 ++++++++ pkg/yqlib/treeops/operator_traverse_path.go | 4 +- .../treeops/operator_traverse_path_test.go | 17 +++ pkg/yqlib/treeops/operator_value.go | 1 + pkg/yqlib/treeops/path_tokeniser.go | 1 + pkg/yqlib/treeops/sample.yaml | 2 +- pkg/yqlib/treeops/temp.json | 8 +- 14 files changed, 230 insertions(+), 137 deletions(-) create mode 100644 pkg/yqlib/treeops/operator_not.go create mode 100644 pkg/yqlib/treeops/operator_not_test.go diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index df0ba912..02a955ce 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -38,6 +38,7 @@ var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator} var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator} +var Not = &OperationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: NotOperator} var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator} diff --git a/pkg/yqlib/treeops/operator_booleans.go b/pkg/yqlib/treeops/operator_booleans.go index 45acd163..4e6135d2 100644 --- a/pkg/yqlib/treeops/operator_booleans.go +++ b/pkg/yqlib/treeops/operator_booleans.go @@ -8,6 +8,9 @@ import ( func isTruthy(c *CandidateNode) (bool, error) { node := c.Node value := true + if node.Tag == "!!null" { + return false, nil + } if node.Kind == yaml.ScalarNode && node.Tag == "!!bool" { errDecoding := node.Decode(&value) if errDecoding != nil { diff --git a/pkg/yqlib/treeops/operator_equals.go b/pkg/yqlib/treeops/operator_equals.go index a2a4aa2d..cee24ffa 100644 --- a/pkg/yqlib/treeops/operator_equals.go +++ b/pkg/yqlib/treeops/operator_equals.go @@ -4,50 +4,19 @@ import ( "github.com/elliotchance/orderedmap" ) -func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func EqualsOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { log.Debugf("-- equalsOperation") - var results = orderedmap.NewOrderedMap() - - for el := matchMap.Front(); el != nil; el = el.Next() { - candidate := el.Value.(*CandidateNode) - log.Debug("equalsOperation checking %v", candidate) - - matches, errInChild := hasMatch(d, candidate, pathNode.Lhs, pathNode.Rhs) - if errInChild != nil { - return nil, errInChild - } - - equalsCandidate := createBooleanCandidate(candidate, matches) - results.Set(equalsCandidate.GetKey(), equalsCandidate) - } - - return results, nil + return crossFunction(d, matchingNodes, pathNode, isEquals) } -func hasMatch(d *dataTreeNavigator, candidate *CandidateNode, lhs *PathTreeNode, rhs *PathTreeNode) (bool, error) { - childMap := orderedmap.NewOrderedMap() - childMap.Set(candidate.GetKey(), candidate) - childMatches, errChild := d.getMatchingNodes(childMap, lhs) - log.Debug("got the LHS") - if errChild != nil { - return false, errChild +func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + value := false + + if lhs.Node.Tag == "!!null" { + value = (rhs.Node.Tag == "!!null") + } else { + value = Match(lhs.Node.Value, rhs.Node.Value) } - - // TODO = handle other RHS types - return containsMatchingValue(childMatches, rhs.Operation.StringValue), nil -} - -func containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool { - log.Debugf("-- findMatchingValues") - - for el := matchMap.Front(); el != nil; el = el.Next() { - node := el.Value.(*CandidateNode) - log.Debugf("-- comparing %v to %v", node.Node.Value, valuePattern) - if Match(node.Node.Value, valuePattern) { - return true - } - } - log.Debugf("-- done findMatchingValues") - - return false + log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value) + return createBooleanCandidate(lhs, value), nil } diff --git a/pkg/yqlib/treeops/operator_equals_test.go b/pkg/yqlib/treeops/operator_equals_test.go index e6f210a2..dca372fd 100644 --- a/pkg/yqlib/treeops/operator_equals_test.go +++ b/pkg/yqlib/treeops/operator_equals_test.go @@ -5,35 +5,49 @@ import ( ) var equalsOperatorScenarios = []expressionScenario{ + // { + // document: `[cat,goat,dog]`, + // expression: `(.[] == "*at")`, + // expected: []string{ + // "D0, P[], (!!bool)::true\n", + // }, + // }, { + // document: `[cat,goat,dog]`, + // expression: `.[] | (. == "*at")`, + // expected: []string{ + // "D0, P[0], (!!bool)::true\n", + // "D0, P[1], (!!bool)::true\n", + // "D0, P[2], (!!bool)::false\n", + // }, + // }, { + // document: `[3, 4, 5]`, + // expression: `.[] | (. == 4)`, + // expected: []string{ + // "D0, P[0], (!!bool)::false\n", + // "D0, P[1], (!!bool)::true\n", + // "D0, P[2], (!!bool)::false\n", + // }, + // }, { + // document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, + // expression: `.a | (.[].b == "apple")`, + // expected: []string{ + // "D0, P[a], (!!bool)::true\n", + // }, + // }, { - document: `[cat,goat,dog]`, - expression: `(.[] == "*at")`, + document: ``, + expression: `null == null`, expected: []string{ "D0, P[], (!!bool)::true\n", }, - }, { - document: `[cat,goat,dog]`, - expression: `.[] | (. == "*at")`, - expected: []string{ - "D0, P[0], (!!bool)::true\n", - "D0, P[1], (!!bool)::true\n", - "D0, P[2], (!!bool)::false\n", - }, - }, { - document: `[3, 4, 5]`, - expression: `.[] | (. == 4)`, - expected: []string{ - "D0, P[0], (!!bool)::false\n", - "D0, P[1], (!!bool)::true\n", - "D0, P[2], (!!bool)::false\n", - }, - }, { - document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, - expression: `.a | (.[].b == "apple")`, - expected: []string{ - "D0, P[a], (!!bool)::true\n", - }, }, + // { + // document: ``, + // expression: `null == ~`, + // expected: []string{ + // "D0, P[], (!!bool)::true\n", + // }, + // }, } func TestEqualOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index e0efff67..e2539601 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -7,7 +7,9 @@ import ( "gopkg.in/yaml.v3" ) -func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) + +func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*orderedmap.OrderedMap, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err @@ -26,7 +28,7 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() { rhsCandidate := rightEl.Value.(*CandidateNode) - resultCandidate, err := multiply(d, lhsCandidate, rhsCandidate) + resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate) if err != nil { return nil, err } @@ -34,7 +36,12 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap } } - return matchingNodes, nil + return results, nil +} + +func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- MultiplyOperator") + return crossFunction(d, matchingNodes, pathNode, multiply) } func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { @@ -67,8 +74,8 @@ func createTraversalTree(path []interface{}) *PathTreeNode { } return &PathTreeNode{ Operation: &Operation{OperationType: Pipe}, - Lhs: createTraversalTree(path[0:1]), - Rhs: createTraversalTree(path[1:])} + Lhs: createTraversalTree(path[0:1]), + Rhs: createTraversalTree(path[1:])} } diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index f9e8adb7..525ddd0a 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -6,67 +6,67 @@ import ( var multiplyOperatorScenarios = []expressionScenario{ { - document: `{a: {also: [1]}, b: {also: me}}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", - }, - }, { - document: `{a: {also: me}, b: {also: [1]}}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", - }, - }, { - document: `{a: {also: me}, b: {also: {g: wizz}}}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", - }, - }, { - document: `{a: {also: {g: wizz}}, b: {also: me}}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", - }, - }, { - document: `{a: {also: {g: wizz}}, b: {also: [1]}}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", - }, - }, { - document: `{a: {also: [1]}, b: {also: {g: wizz}}}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", - }, - }, { - document: `{a: {things: great}, b: {also: me}}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n", - }, - }, { - document: `a: {things: great} -b: - also: "me" -`, - expression: `.a * .b`, - expected: []string{ - `D0, P[], (!!map)::a: - things: great - also: "me" -b: - also: "me" -`, - }, - }, { - document: `{a: [1,2,3], b: [3,4,5]}`, - expression: `.a * .b`, - expected: []string{ - "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", - }, + // document: `{a: {also: [1]}, b: {also: me}}`, + // expression: `.a * .b`, + // expected: []string{ + // "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", + // }, + // }, { + // document: `{a: {also: me}, b: {also: [1]}}`, + // expression: `.a * .b`, + // expected: []string{ + // "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", + // }, + // }, { + // document: `{a: {also: me}, b: {also: {g: wizz}}}`, + // expression: `.a * .b`, + // expected: []string{ + // "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", + // }, + // }, { + // document: `{a: {also: {g: wizz}}, b: {also: me}}`, + // expression: `.a * .b`, + // expected: []string{ + // "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", + // }, + // }, { + // document: `{a: {also: {g: wizz}}, b: {also: [1]}}`, + // expression: `.a * .b`, + // expected: []string{ + // "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", + // }, + // }, { + // document: `{a: {also: [1]}, b: {also: {g: wizz}}}`, + // expression: `.a * .b`, + // expected: []string{ + // "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", + // }, + // }, { + // document: `{a: {things: great}, b: {also: me}}`, + // expression: `.a * .b`, + // expected: []string{ + // "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n", + // }, + // }, { + // document: `a: {things: great} + // b: + // also: "me" + // `, + // expression: `(.a * .b)`, + // expected: []string{ + // `D0, P[], (!!map)::a: + // things: great + // also: "me" + // b: + // also: "me" + // `, + // }, + // }, { + // document: `{a: [1,2,3], b: [3,4,5]}`, + // expression: `.a * .b`, + // expected: []string{ + // "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", + // }, }, } diff --git a/pkg/yqlib/treeops/operator_not.go b/pkg/yqlib/treeops/operator_not.go new file mode 100644 index 00000000..4610b0e3 --- /dev/null +++ b/pkg/yqlib/treeops/operator_not.go @@ -0,0 +1,20 @@ +package treeops + +import "github.com/elliotchance/orderedmap" + +func NotOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debugf("-- notOperation") + var results = orderedmap.NewOrderedMap() + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debug("notOperation checking %v", candidate) + truthy, errDecoding := isTruthy(candidate) + if errDecoding != nil { + return nil, errDecoding + } + result := createBooleanCandidate(candidate, !truthy) + results.Set(result.GetKey(), result) + } + return results, nil +} diff --git a/pkg/yqlib/treeops/operator_not_test.go b/pkg/yqlib/treeops/operator_not_test.go new file mode 100644 index 00000000..a91ffa32 --- /dev/null +++ b/pkg/yqlib/treeops/operator_not_test.go @@ -0,0 +1,56 @@ +package treeops + +import ( + "testing" +) + +var notOperatorScenarios = []expressionScenario{ + { + document: `cat`, + expression: `. | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + document: `1`, + expression: `. | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + document: `0`, + expression: `. | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + document: `~`, + expression: `. | not`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, + { + document: `false`, + expression: `. | not`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, + { + document: `true`, + expression: `. | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, +} + +func TestNotOperatorScenarios(t *testing.T) { + for _, tt := range notOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index 47e396df..b279719f 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -110,7 +110,7 @@ func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNod } if len(newMatches) == 0 { //no matches, create one automagically - valueNode := &yaml.Node{} + valueNode := &yaml.Node{Tag: "!!null"} node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode) newMatches = append(newMatches, &CandidateNode{ Node: valueNode, @@ -145,7 +145,7 @@ func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateN indexToUse := index contentLength := int64(len(candidate.Node.Content)) for contentLength <= index { - candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{}) + candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null"}) contentLength = int64(len(candidate.Node.Content)) } diff --git a/pkg/yqlib/treeops/operator_traverse_path_test.go b/pkg/yqlib/treeops/operator_traverse_path_test.go index 4199d0a2..8f7fadf9 100644 --- a/pkg/yqlib/treeops/operator_traverse_path_test.go +++ b/pkg/yqlib/treeops/operator_traverse_path_test.go @@ -66,6 +66,23 @@ var traversePathOperatorScenarios = []expressionScenario{ "D0, P[a mad], (!!str)::things\n", }, }, + { + document: `{a: {cat: apple, mad: things}}`, + expression: `.a | (.cat, .mad, .fad)`, + expected: []string{ + "D0, P[a cat], (!!str)::apple\n", + "D0, P[a mad], (!!str)::things\n", + "D0, P[a fad], ()::null\n", + }, + }, + { + document: `{a: {cat: apple, mad: things}}`, + expression: `.a | (.cat, .mad, .fad) | select( (. == null) | not)`, + expected: []string{ + "D0, P[a cat], (!!str)::apple\n", + "D0, P[a mad], (!!str)::things\n", + }, + }, } func TestTraversePathOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_value.go b/pkg/yqlib/treeops/operator_value.go index caabd354..a1bdb5dd 100644 --- a/pkg/yqlib/treeops/operator_value.go +++ b/pkg/yqlib/treeops/operator_value.go @@ -3,5 +3,6 @@ package treeops import "github.com/elliotchance/orderedmap" func ValueOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { + log.Debug("value = %v", pathNode.Operation.CandidateNode.Node.Value) return nodeToMap(pathNode.Operation.CandidateNode), nil } diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 23c575c3..c207d403 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -177,6 +177,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`length`), opToken(Length)) lexer.Add([]byte(`select`), opToken(Select)) lexer.Add([]byte(`or`), opToken(Or)) + lexer.Add([]byte(`not`), opToken(Not)) // lexer.Add([]byte(`and`), opToken()) lexer.Add([]byte(`collect`), opToken(Collect)) diff --git a/pkg/yqlib/treeops/sample.yaml b/pkg/yqlib/treeops/sample.yaml index dcee3caf..fcd6274e 100644 --- a/pkg/yqlib/treeops/sample.yaml +++ b/pkg/yqlib/treeops/sample.yaml @@ -1 +1 @@ -{a: {cat: apple, mad: things}} \ No newline at end of file +{a: {also: [1]}, b: {also: me}} \ No newline at end of file diff --git a/pkg/yqlib/treeops/temp.json b/pkg/yqlib/treeops/temp.json index 3ce4e779..9ca35670 100644 --- a/pkg/yqlib/treeops/temp.json +++ b/pkg/yqlib/treeops/temp.json @@ -1,6 +1,10 @@ { "a": { - "cat": "apple", - "mad": "things" + "also": [ + 1 + ] + }, + "b": { + "also": "me" } } From 65e6e492cd17f0a8478a71ba5422411499a3b364 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 21 Oct 2020 12:54:58 +1100 Subject: [PATCH 050/129] wip --- go.mod | 2 +- pkg/yqlib/treeops/data_tree_navigator.go | 9 +-- pkg/yqlib/treeops/lib.go | 7 +- .../operation_collection_object_test.go | 2 + pkg/yqlib/treeops/operator_assign.go | 6 +- pkg/yqlib/treeops/operator_booleans.go | 13 ++-- pkg/yqlib/treeops/operator_booleans_test.go | 1 + pkg/yqlib/treeops/operator_collect.go | 9 +-- pkg/yqlib/treeops/operator_collect_object.go | 8 +++ pkg/yqlib/treeops/operator_create_map.go | 43 ++++++++++++ pkg/yqlib/treeops/operator_create_map_test.go | 28 ++++++++ pkg/yqlib/treeops/operator_delete.go | 29 ++++---- pkg/yqlib/treeops/operator_equals.go | 4 +- pkg/yqlib/treeops/operator_equals_test.go | 67 +++++++++---------- pkg/yqlib/treeops/operator_multilpy.go | 13 ++-- pkg/yqlib/treeops/operator_not.go | 8 +-- .../treeops/operator_recursive_descent.go | 10 +-- pkg/yqlib/treeops/operator_select.go | 8 +-- pkg/yqlib/treeops/operator_self.go | 4 +- pkg/yqlib/treeops/operator_traverse_path.go | 16 +++-- .../treeops/operator_traverse_path_test.go | 10 +-- pkg/yqlib/treeops/operator_union.go | 6 +- pkg/yqlib/treeops/operator_value.go | 4 +- pkg/yqlib/treeops/operators.go | 37 +++------- pkg/yqlib/treeops/path_parse_test.go | 10 +++ pkg/yqlib/treeops/path_tokeniser.go | 11 ++- pkg/yqlib/treeops/sample.yaml | 2 +- pkg/yqlib/treeops/temp.json | 13 ++-- 28 files changed, 232 insertions(+), 148 deletions(-) create mode 100644 pkg/yqlib/treeops/operation_collection_object_test.go create mode 100644 pkg/yqlib/treeops/operator_collect_object.go create mode 100644 pkg/yqlib/treeops/operator_create_map.go create mode 100644 pkg/yqlib/treeops/operator_create_map_test.go diff --git a/go.mod b/go.mod index a77d1990..64cafbfe 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/mikefarah/yq/v3 require ( - github.com/elliotchance/orderedmap v1.3.0 + github.com/elliotchance/orderedmap v1.3.0 // indirect github.com/fatih/color v1.9.0 github.com/goccy/go-yaml v1.8.1 github.com/kylelemons/godebug v1.1.0 // indirect diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index d502047e..1b7dbc69 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -3,7 +3,8 @@ package treeops import ( "fmt" - "github.com/elliotchance/orderedmap" + "container/list" + "gopkg.in/op/go-logging.v1" ) @@ -24,10 +25,10 @@ func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { } func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { - var matchingNodeMap = orderedmap.NewOrderedMap() + var matchingNodeMap = list.New() for _, n := range matchingNodes { - matchingNodeMap.Set(n.GetKey(), n) + matchingNodeMap.PushBack(n) } matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode) @@ -43,7 +44,7 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat return values, nil } -func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { if pathNode == nil { log.Debugf("getMatchingNodes - nothing to do") return matchingNodes, nil diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 02a955ce..50deca23 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -2,9 +2,9 @@ package treeops import ( "bytes" + "container/list" "fmt" - "github.com/elliotchance/orderedmap" "gopkg.in/op/go-logging.v1" "gopkg.in/yaml.v3" ) @@ -22,17 +22,18 @@ var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOpera var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator} var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} -var Intersection = &OperationType{Type: "INTERSECTION", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator} var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} +var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator} var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator} var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} +var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator} var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} @@ -129,7 +130,7 @@ func (l *lib) Get(document int, documentNode *yaml.Node, path string) ([]*Candid } //use for debugging only -func NodesToString(collection *orderedmap.OrderedMap) string { +func NodesToString(collection *list.List) string { if !log.IsEnabledFor(logging.DEBUG) { return "" } diff --git a/pkg/yqlib/treeops/operation_collection_object_test.go b/pkg/yqlib/treeops/operation_collection_object_test.go new file mode 100644 index 00000000..4a522dff --- /dev/null +++ b/pkg/yqlib/treeops/operation_collection_object_test.go @@ -0,0 +1,2 @@ +package treeops + diff --git a/pkg/yqlib/treeops/operator_assign.go b/pkg/yqlib/treeops/operator_assign.go index 60ee08ea..4c8222b5 100644 --- a/pkg/yqlib/treeops/operator_assign.go +++ b/pkg/yqlib/treeops/operator_assign.go @@ -1,8 +1,8 @@ package treeops -import "github.com/elliotchance/orderedmap" +import "container/list" -func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func AssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err @@ -27,7 +27,7 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, } // does not update content or values -func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err diff --git a/pkg/yqlib/treeops/operator_booleans.go b/pkg/yqlib/treeops/operator_booleans.go index 4e6135d2..ceee7131 100644 --- a/pkg/yqlib/treeops/operator_booleans.go +++ b/pkg/yqlib/treeops/operator_booleans.go @@ -1,7 +1,8 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" + "gopkg.in/yaml.v3" ) @@ -23,8 +24,8 @@ func isTruthy(c *CandidateNode) (bool, error) { type boolOp func(bool, bool) bool -func booleanOp(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode, op boolOp) (*orderedmap.OrderedMap, error) { - var results = orderedmap.NewOrderedMap() +func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) { + var results = list.New() for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -52,7 +53,7 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathN } boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue)) - results.Set(boolResult.GetKey(), boolResult) + results.PushBack(boolResult) } } @@ -60,14 +61,14 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathN return results, nil } -func OrOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func OrOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- orOp") return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool { return b1 || b2 }) } -func AndOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- AndOp") return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool { return b1 && b2 diff --git a/pkg/yqlib/treeops/operator_booleans_test.go b/pkg/yqlib/treeops/operator_booleans_test.go index 59f98953..f3013fbd 100644 --- a/pkg/yqlib/treeops/operator_booleans_test.go +++ b/pkg/yqlib/treeops/operator_booleans_test.go @@ -21,6 +21,7 @@ var booleanOperatorScenarios = []expressionScenario{ document: `{a: true, b: false}`, expression: `.[] or (false, true)`, expected: []string{ + "D0, P[a], (!!bool)::true\n", "D0, P[a], (!!bool)::true\n", "D0, P[b], (!!bool)::false\n", "D0, P[b], (!!bool)::true\n", diff --git a/pkg/yqlib/treeops/operator_collect.go b/pkg/yqlib/treeops/operator_collect.go index a1197faf..c4e1f7c0 100644 --- a/pkg/yqlib/treeops/operator_collect.go +++ b/pkg/yqlib/treeops/operator_collect.go @@ -1,14 +1,15 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" + "gopkg.in/yaml.v3" ) -func CollectOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- collectOperation") - var results = orderedmap.NewOrderedMap() + var results = list.New() node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} @@ -26,7 +27,7 @@ func CollectOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, path } collectC := &CandidateNode{Node: node, Document: document, Path: path} - results.Set(collectC.GetKey(), collectC) + results.PushBack(collectC) return results, nil } diff --git a/pkg/yqlib/treeops/operator_collect_object.go b/pkg/yqlib/treeops/operator_collect_object.go new file mode 100644 index 00000000..74ca176c --- /dev/null +++ b/pkg/yqlib/treeops/operator_collect_object.go @@ -0,0 +1,8 @@ +package treeops + +import "container/list" + +func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("-- collectObjectOperation") + return nil, nil +} diff --git a/pkg/yqlib/treeops/operator_create_map.go b/pkg/yqlib/treeops/operator_create_map.go new file mode 100644 index 00000000..eaea6725 --- /dev/null +++ b/pkg/yqlib/treeops/operator_create_map.go @@ -0,0 +1,43 @@ +package treeops + +import ( + "container/list" + + "gopkg.in/yaml.v3" +) + +func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("-- createMapOperation") + var path []interface{} = nil + var document uint = 0 + if matchingNodes.Front() != nil { + sample := matchingNodes.Front().Value.(*CandidateNode) + path = sample.Path + document = sample.Document + } + + mapPairs, err := crossFunction(d, matchingNodes, pathNode, + func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} + log.Debugf("LHS:", lhs.Node.Value) + log.Debugf("RHS:", rhs.Node.Value) + node.Content = []*yaml.Node{ + lhs.Node, + rhs.Node, + } + + return &CandidateNode{Node: &node, Document: document, Path: path}, nil + }) + + if err != nil { + return nil, err + } + //wrap up all the pairs into an array + node := yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} + for mapPair := mapPairs.Front(); mapPair != nil; mapPair = mapPair.Next() { + mapPairCandidate := mapPair.Value.(*CandidateNode) + log.Debugf("Collecting %v into sequence", NodeToString(mapPairCandidate)) + node.Content = append(node.Content, mapPairCandidate.Node) + } + return nodeToMap(&CandidateNode{Node: &node, Document: document, Path: path}), nil +} diff --git a/pkg/yqlib/treeops/operator_create_map_test.go b/pkg/yqlib/treeops/operator_create_map_test.go new file mode 100644 index 00000000..39a2e6e2 --- /dev/null +++ b/pkg/yqlib/treeops/operator_create_map_test.go @@ -0,0 +1,28 @@ +package treeops + +import ( + "testing" +) + +var createMapOperatorScenarios = []expressionScenario{ + { + document: `{name: Mike, age: 32}`, + expression: `.name: .age`, + expected: []string{ + "D0, P[], (!!seq)::- Mike: 32\n", + }, + }, + { + document: `{name: Mike, pets: [cat, dog]}`, + expression: `.name: .pets[]`, + expected: []string{ + "D0, P[], (!!seq)::- Mike: cat\n- Mike: dog\n", + }, + }, +} + +func TestCreateMapOperatorScenarios(t *testing.T) { + for _, tt := range createMapOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operator_delete.go b/pkg/yqlib/treeops/operator_delete.go index c80479b1..23adac20 100644 --- a/pkg/yqlib/treeops/operator_delete.go +++ b/pkg/yqlib/treeops/operator_delete.go @@ -1,11 +1,12 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" + "gopkg.in/yaml.v3" ) -func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err @@ -16,8 +17,8 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered for el := lhs.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - elMap := orderedmap.NewOrderedMap() - elMap.Set(candidate.GetKey(), candidate) + elMap := list.New() + elMap.PushBack(candidate) nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs) log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete)) if err != nil { @@ -35,7 +36,7 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered return lhs, nil } -func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMap) { +func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { log.Debug("deleteFromMap") node := candidate.Node contents := node.Content @@ -50,7 +51,8 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa Document: candidate.Document, Path: append(candidate.Path, key.Value), } - _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) + // _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) + shouldDelete := true log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete) @@ -61,7 +63,7 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa node.Content = newContents } -func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMap) { +func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) { log.Debug("deleteFromArray") node := candidate.Node contents := node.Content @@ -70,13 +72,14 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.Ordered for index := 0; index < len(contents); index = index + 1 { value := contents[index] - childCandidate := &CandidateNode{ - Node: value, - Document: candidate.Document, - Path: append(candidate.Path, index), - } + // childCandidate := &CandidateNode{ + // Node: value, + // Document: candidate.Document, + // Path: append(candidate.Path, index), + // } - _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) + // _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) + shouldDelete := true if !shouldDelete { newContents = append(newContents, value) } diff --git a/pkg/yqlib/treeops/operator_equals.go b/pkg/yqlib/treeops/operator_equals.go index cee24ffa..63bf6d71 100644 --- a/pkg/yqlib/treeops/operator_equals.go +++ b/pkg/yqlib/treeops/operator_equals.go @@ -1,10 +1,10 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" ) -func EqualsOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func EqualsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- equalsOperation") return crossFunction(d, matchingNodes, pathNode, isEquals) } diff --git a/pkg/yqlib/treeops/operator_equals_test.go b/pkg/yqlib/treeops/operator_equals_test.go index dca372fd..02e4479d 100644 --- a/pkg/yqlib/treeops/operator_equals_test.go +++ b/pkg/yqlib/treeops/operator_equals_test.go @@ -5,35 +5,30 @@ import ( ) var equalsOperatorScenarios = []expressionScenario{ - // { - // document: `[cat,goat,dog]`, - // expression: `(.[] == "*at")`, - // expected: []string{ - // "D0, P[], (!!bool)::true\n", - // }, - // }, { - // document: `[cat,goat,dog]`, - // expression: `.[] | (. == "*at")`, - // expected: []string{ - // "D0, P[0], (!!bool)::true\n", - // "D0, P[1], (!!bool)::true\n", - // "D0, P[2], (!!bool)::false\n", - // }, - // }, { - // document: `[3, 4, 5]`, - // expression: `.[] | (. == 4)`, - // expected: []string{ - // "D0, P[0], (!!bool)::false\n", - // "D0, P[1], (!!bool)::true\n", - // "D0, P[2], (!!bool)::false\n", - // }, - // }, { - // document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, - // expression: `.a | (.[].b == "apple")`, - // expected: []string{ - // "D0, P[a], (!!bool)::true\n", - // }, - // }, + { + document: `[cat,goat,dog]`, + expression: `.[] | (. == "*at")`, + expected: []string{ + "D0, P[0], (!!bool)::true\n", + "D0, P[1], (!!bool)::true\n", + "D0, P[2], (!!bool)::false\n", + }, + }, { + document: `[3, 4, 5]`, + expression: `.[] | (. == 4)`, + expected: []string{ + "D0, P[0], (!!bool)::false\n", + "D0, P[1], (!!bool)::true\n", + "D0, P[2], (!!bool)::false\n", + }, + }, { + document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, + expression: `.a | (.[].b == "apple")`, + expected: []string{ + "D0, P[a cat b], (!!bool)::true\n", + "D0, P[a pat b], (!!bool)::false\n", + }, + }, { document: ``, expression: `null == null`, @@ -41,13 +36,13 @@ var equalsOperatorScenarios = []expressionScenario{ "D0, P[], (!!bool)::true\n", }, }, - // { - // document: ``, - // expression: `null == ~`, - // expected: []string{ - // "D0, P[], (!!bool)::true\n", - // }, - // }, + { + document: ``, + expression: `null == ~`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, } func TestEqualOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index e2539601..a83ed052 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -3,13 +3,14 @@ package treeops import ( "fmt" - "github.com/elliotchance/orderedmap" + "container/list" + "gopkg.in/yaml.v3" ) type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) -func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*orderedmap.OrderedMap, error) { +func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*list.List, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err @@ -21,7 +22,7 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p return nil, err } - var results = orderedmap.NewOrderedMap() + var results = list.New() for el := lhs.Front(); el != nil; el = el.Next() { lhsCandidate := el.Value.(*CandidateNode) @@ -32,14 +33,14 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p if err != nil { return nil, err } - results.Set(resultCandidate.GetKey(), resultCandidate) + results.PushBack(resultCandidate) } } return results, nil } -func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- MultiplyOperator") return crossFunction(d, matchingNodes, pathNode, multiply) } @@ -47,7 +48,7 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { - var results = orderedmap.NewOrderedMap() + var results = list.New() recursiveDecent(d, results, nodeToMap(rhs)) var pathIndexToStartFrom int = 0 diff --git a/pkg/yqlib/treeops/operator_not.go b/pkg/yqlib/treeops/operator_not.go index 4610b0e3..0497bac3 100644 --- a/pkg/yqlib/treeops/operator_not.go +++ b/pkg/yqlib/treeops/operator_not.go @@ -1,10 +1,10 @@ package treeops -import "github.com/elliotchance/orderedmap" +import "container/list" -func NotOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- notOperation") - var results = orderedmap.NewOrderedMap() + var results = list.New() for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -14,7 +14,7 @@ func NotOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode return nil, errDecoding } result := createBooleanCandidate(candidate, !truthy) - results.Set(result.GetKey(), result) + results.PushBack(result) } return results, nil } diff --git a/pkg/yqlib/treeops/operator_recursive_descent.go b/pkg/yqlib/treeops/operator_recursive_descent.go index 38513b7a..36098462 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent.go +++ b/pkg/yqlib/treeops/operator_recursive_descent.go @@ -1,11 +1,11 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" ) -func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - var results = orderedmap.NewOrderedMap() +func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { + var results = list.New() err := recursiveDecent(d, results, matchMap) if err != nil { @@ -15,13 +15,13 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *orderedmap.Ordered return results, nil } -func recursiveDecent(d *dataTreeNavigator, results *orderedmap.OrderedMap, matchMap *orderedmap.OrderedMap) error { +func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error { splatOperation := &Operation{OperationType: TraversePath, Value: "[]"} splatTreeNode := &PathTreeNode{Operation: splatOperation} for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - results.Set(candidate.GetKey(), candidate) + results.PushBack(candidate) children, err := TraversePathOperator(d, nodeToMap(candidate), splatTreeNode) diff --git a/pkg/yqlib/treeops/operator_select.go b/pkg/yqlib/treeops/operator_select.go index fb92a90e..704987fc 100644 --- a/pkg/yqlib/treeops/operator_select.go +++ b/pkg/yqlib/treeops/operator_select.go @@ -1,13 +1,13 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" ) -func SelectOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func SelectOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- selectOperation") - var results = orderedmap.NewOrderedMap() + var results = list.New() for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -29,7 +29,7 @@ func SelectOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, } if includeResult { - results.Set(candidate.GetKey(), candidate) + results.PushBack(candidate) } } } diff --git a/pkg/yqlib/treeops/operator_self.go b/pkg/yqlib/treeops/operator_self.go index 170ef2c6..bd98d32d 100644 --- a/pkg/yqlib/treeops/operator_self.go +++ b/pkg/yqlib/treeops/operator_self.go @@ -1,7 +1,7 @@ package treeops -import "github.com/elliotchance/orderedmap" +import "container/list" -func SelfOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func SelfOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { return matchMap, nil } diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index b279719f..dc05c34f 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -3,13 +3,14 @@ package treeops import ( "fmt" - "github.com/elliotchance/orderedmap" + "container/list" + "gopkg.in/yaml.v3" ) -func TraversePathOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- Traversing") - var matchingNodeMap = orderedmap.NewOrderedMap() + var matchingNodeMap = list.New() var newNodes []*CandidateNode var err error @@ -19,7 +20,7 @@ func TraversePathOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, return nil, err } for _, n := range newNodes { - matchingNodeMap.Set(n.GetKey(), n) + matchingNodeMap.PushBack(n) } } @@ -30,7 +31,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Opera log.Debug("Traversing %v", NodeToString(matchingNode)) value := matchingNode.Node - if value.Kind == 0 { + if value.Tag == "!!null" { log.Debugf("Guessing kind") // we must ahve added this automatically, lets guess what it should be now switch pathNode.Value.(type) { @@ -41,6 +42,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Opera log.Debugf("probabel a map") value.Kind = yaml.MappingNode } + value.Tag = "" } switch value.Kind { @@ -110,7 +112,7 @@ func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNod } if len(newMatches) == 0 { //no matches, create one automagically - valueNode := &yaml.Node{Tag: "!!null"} + valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"} node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode) newMatches = append(newMatches, &CandidateNode{ Node: valueNode, @@ -145,7 +147,7 @@ func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateN indexToUse := index contentLength := int64(len(candidate.Node.Content)) for contentLength <= index { - candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null"}) + candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}) contentLength = int64(len(candidate.Node.Content)) } diff --git a/pkg/yqlib/treeops/operator_traverse_path_test.go b/pkg/yqlib/treeops/operator_traverse_path_test.go index 8f7fadf9..d5ecbc20 100644 --- a/pkg/yqlib/treeops/operator_traverse_path_test.go +++ b/pkg/yqlib/treeops/operator_traverse_path_test.go @@ -24,21 +24,21 @@ var traversePathOperatorScenarios = []expressionScenario{ document: `{}`, expression: `.a.b`, expected: []string{ - "D0, P[a b], ()::null\n", + "D0, P[a b], (!!null)::null\n", }, }, { document: `{}`, expression: `.[1].a`, expected: []string{ - "D0, P[1 a], ()::null\n", + "D0, P[1 a], (!!null)::null\n", }, }, { document: `{}`, expression: `.a.[1]`, expected: []string{ - "D0, P[a 1], ()::null\n", + "D0, P[a 1], (!!null)::null\n", }, }, { @@ -55,7 +55,7 @@ var traversePathOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[a cat b], (!!int)::3\n", "D0, P[a mad b], (!!int)::4\n", - "D0, P[a fad b], ()::null\n", + "D0, P[a fad b], (!!null)::null\n", }, }, { @@ -72,7 +72,7 @@ var traversePathOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[a cat], (!!str)::apple\n", "D0, P[a mad], (!!str)::things\n", - "D0, P[a fad], ()::null\n", + "D0, P[a fad], (!!null)::null\n", }, }, { diff --git a/pkg/yqlib/treeops/operator_union.go b/pkg/yqlib/treeops/operator_union.go index df44e9f6..8c100ba2 100644 --- a/pkg/yqlib/treeops/operator_union.go +++ b/pkg/yqlib/treeops/operator_union.go @@ -1,8 +1,8 @@ package treeops -import "github.com/elliotchance/orderedmap" +import "container/list" -func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func UnionOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err @@ -13,7 +13,7 @@ func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p } for el := rhs.Front(); el != nil; el = el.Next() { node := el.Value.(*CandidateNode) - lhs.Set(node.GetKey(), node) + lhs.PushBack(node) } return lhs, nil } diff --git a/pkg/yqlib/treeops/operator_value.go b/pkg/yqlib/treeops/operator_value.go index a1bdb5dd..0769a212 100644 --- a/pkg/yqlib/treeops/operator_value.go +++ b/pkg/yqlib/treeops/operator_value.go @@ -1,8 +1,8 @@ package treeops -import "github.com/elliotchance/orderedmap" +import "container/list" -func ValueOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func ValueOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debug("value = %v", pathNode.Operation.CandidateNode.Node.Value) return nodeToMap(pathNode.Operation.CandidateNode), nil } diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index f09f1c5e..c584f9d2 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -1,15 +1,15 @@ package treeops import ( + "container/list" "fmt" - "github.com/elliotchance/orderedmap" "gopkg.in/yaml.v3" ) -type OperatorHandler func(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) +type OperatorHandler func(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) -func PipeOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err @@ -26,34 +26,15 @@ func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode { return &CandidateNode{Node: node, Document: owner.Document, Path: owner.Path} } -func nodeToMap(candidate *CandidateNode) *orderedmap.OrderedMap { - elMap := orderedmap.NewOrderedMap() - elMap.Set(candidate.GetKey(), candidate) +func nodeToMap(candidate *CandidateNode) *list.List { + elMap := list.New() + elMap.PushBack(candidate) return elMap } -func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs) - if err != nil { - return nil, err - } - var matchingNodeMap = orderedmap.NewOrderedMap() - for el := lhs.Front(); el != nil; el = el.Next() { - _, exists := rhs.Get(el.Key) - if exists { - matchingNodeMap.Set(el.Key, el.Value) - } - } - return matchingNodeMap, nil -} - -func LengthOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { +func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- lengthOperation") - var results = orderedmap.NewOrderedMap() + var results = list.New() for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -71,7 +52,7 @@ func LengthOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathN node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"} lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} - results.Set(candidate.GetKey(), lengthCand) + results.PushBack(lengthCand) } return results, nil diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/treeops/path_parse_test.go index 16bcf245..825b42a1 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -59,6 +59,16 @@ var pathTests = []struct { append(make([]interface{}, 0), "[", "true (bool)", "]"), append(make([]interface{}, 0), "true (bool)", "COLLECT", "PIPE"), }, + { + `"mike": .a`, + append(make([]interface{}, 0), "mike (string)", "CREATE_MAP", "a"), + append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP"), + }, + { + `.a: "mike"`, + append(make([]interface{}, 0), "a", "CREATE_MAP", "mike (string)"), + append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP"), + }, // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, // {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")}, diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index c207d403..59953f85 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -20,6 +20,8 @@ const ( CloseBracket OpenCollect CloseCollect + OpenCollectObject + CloseCollectObject ) type Token struct { @@ -40,6 +42,10 @@ func (t *Token) toString() string { return "[" } else if t.TokenType == CloseCollect { return "]" + } else if t.TokenType == OpenCollectObject { + return "{" + } else if t.TokenType == CloseCollectObject { + return "}" } else { return fmt.Sprintf("NFI") } @@ -174,6 +180,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent)) lexer.Add([]byte(`,`), opToken(Union)) + lexer.Add([]byte(`:\s*`), opToken(CreateMap)) lexer.Add([]byte(`length`), opToken(Length)) lexer.Add([]byte(`select`), opToken(Select)) lexer.Add([]byte(`or`), opToken(Or)) @@ -195,7 +202,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`d[0-9]+`), documentToken()) // $0 lexer.Add([]byte(`\."[^ "]+"`), pathToken(true)) - lexer.Add([]byte(`\.[^ \[\],\|\.\[\(\)=]+`), pathToken(false)) + lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false)) lexer.Add([]byte(`\.`), selfToken()) lexer.Add([]byte(`\|`), opToken(Pipe)) @@ -214,6 +221,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\[`), literalToken(OpenCollect, false)) lexer.Add([]byte(`\]`), literalToken(CloseCollect, true)) + lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false)) + lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true)) lexer.Add([]byte(`\*`), opToken(Multiply)) // lexer.Add([]byte(`[^ \,\|\.\[\(\)=]+`), stringValue(false)) diff --git a/pkg/yqlib/treeops/sample.yaml b/pkg/yqlib/treeops/sample.yaml index fcd6274e..f151d6f8 100644 --- a/pkg/yqlib/treeops/sample.yaml +++ b/pkg/yqlib/treeops/sample.yaml @@ -1 +1 @@ -{a: {also: [1]}, b: {also: me}} \ No newline at end of file +{name: Mike, pets: [cat, dog]} \ No newline at end of file diff --git a/pkg/yqlib/treeops/temp.json b/pkg/yqlib/treeops/temp.json index 9ca35670..f5cf07d9 100644 --- a/pkg/yqlib/treeops/temp.json +++ b/pkg/yqlib/treeops/temp.json @@ -1,10 +1,7 @@ { - "a": { - "also": [ - 1 - ] - }, - "b": { - "also": "me" - } + "name": "Mike", + "pets": [ + "cat", + "dog" + ] } From badd47673046075684ce4e38075f84719da629cf Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 21 Oct 2020 13:54:51 +1100 Subject: [PATCH 051/129] collect object operator! --- go.mod | 1 + go.sum | 2 + pkg/yqlib/treeops/candidate_node.go | 12 +- .../operation_collection_object_test.go | 37 ++++++ pkg/yqlib/treeops/operator_collect_object.go | 53 +++++++- pkg/yqlib/treeops/operator_create_map_test.go | 8 ++ pkg/yqlib/treeops/operator_multiply_test.go | 120 +++++++++--------- .../treeops/operator_recursive_descent.go | 5 +- pkg/yqlib/treeops/operator_traverse_path.go | 6 + pkg/yqlib/treeops/path_parse_test.go | 20 +++ pkg/yqlib/treeops/path_postfix.go | 14 +- 11 files changed, 206 insertions(+), 72 deletions(-) diff --git a/go.mod b/go.mod index 64cafbfe..4152391c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( github.com/elliotchance/orderedmap v1.3.0 // indirect github.com/fatih/color v1.9.0 github.com/goccy/go-yaml v1.8.1 + github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.7 // indirect github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index c12b43cc..b36faa9e 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= +github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index e714fc16..7c3b3f9c 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/jinzhu/copier" "gopkg.in/yaml.v3" ) @@ -18,6 +19,12 @@ func (n *CandidateNode) GetKey() string { return fmt.Sprintf("%v - %v - %v", n.Document, n.Path, n.Node.Value) } +func (n *CandidateNode) Copy() *CandidateNode { + clone := &CandidateNode{} + copier.Copy(clone, n) + return clone +} + // updates this candidate from the given candidate node func (n *CandidateNode) UpdateFrom(other *CandidateNode) { n.UpdateAttributesFrom(other) @@ -34,7 +41,10 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { } n.Node.Kind = other.Node.Kind n.Node.Tag = other.Node.Tag - n.Node.Style = other.Node.Style + // not sure if this ever should happen here... + // if other.Node.Style != 0 { + // n.Node.Style = other.Node.Style + // } n.Node.FootComment = other.Node.FootComment n.Node.HeadComment = other.Node.HeadComment n.Node.LineComment = other.Node.LineComment diff --git a/pkg/yqlib/treeops/operation_collection_object_test.go b/pkg/yqlib/treeops/operation_collection_object_test.go index 4a522dff..62333ee7 100644 --- a/pkg/yqlib/treeops/operation_collection_object_test.go +++ b/pkg/yqlib/treeops/operation_collection_object_test.go @@ -1,2 +1,39 @@ package treeops +import ( + "testing" +) + +var collectObjectOperatorScenarios = []expressionScenario{ + { + document: `{name: Mike, age: 32}`, + expression: `{.name: .age}`, + expected: []string{ + "D0, P[0], (!!map)::Mike: 32\n", + }, + }, + { + document: `{name: Mike, pets: [cat, dog]}`, + expression: `{.name: .pets[]}`, + expected: []string{ + "D0, P[0], (!!map)::Mike: cat\n", + "D0, P[1], (!!map)::Mike: dog\n", + }, + }, + { + document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`, + expression: `{.name: .pets[], "f":.food[]}`, + expected: []string{ + "D0, P[], (!!map)::Mike: cat\nf: hotdog\n", + "D0, P[], (!!map)::Mike: cat\nf: burger\n", + "D0, P[], (!!map)::Mike: dog\nf: hotdog\n", + "D0, P[], (!!map)::Mike: dog\nf: burger\n", + }, + }, +} + +func TestCollectObjectOperatorScenarios(t *testing.T) { + for _, tt := range collectObjectOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operator_collect_object.go b/pkg/yqlib/treeops/operator_collect_object.go index 74ca176c..4cfee960 100644 --- a/pkg/yqlib/treeops/operator_collect_object.go +++ b/pkg/yqlib/treeops/operator_collect_object.go @@ -1,8 +1,57 @@ package treeops -import "container/list" +import ( + "container/list" +) + +/* +[Mike: cat, Bob: dog] +[Thing: rabbit, peter: sam] + +==> cross multiply + +{Mike: cat, Thing: rabbit} +{Mike: cat, peter: sam} +... +*/ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- collectObjectOperation") - return nil, nil + return collect(d, list.New(), matchMap) + +} + +func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.List) (*list.List, error) { + if remainingMatches.Len() == 0 { + return aggregate, nil + } + + candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode) + splatted, err := Splat(d, nodeToMap(candidate)) + if err != nil { + return nil, err + } + + if aggregate.Len() == 0 { + return collect(d, splatted, remainingMatches) + } + + newAgg := list.New() + + for el := aggregate.Front(); el != nil; el = el.Next() { + aggCandidate := el.Value.(*CandidateNode) + for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() { + splatCandidate := splatEl.Value.(*CandidateNode) + newCandidate := aggCandidate.Copy() + newCandidate.Path = nil + + newCandidate, err := multiply(d, newCandidate, splatCandidate) + if err != nil { + return nil, err + } + newAgg.PushBack(newCandidate) + } + } + return collect(d, newAgg, remainingMatches) + } diff --git a/pkg/yqlib/treeops/operator_create_map_test.go b/pkg/yqlib/treeops/operator_create_map_test.go index 39a2e6e2..af0bd223 100644 --- a/pkg/yqlib/treeops/operator_create_map_test.go +++ b/pkg/yqlib/treeops/operator_create_map_test.go @@ -19,6 +19,14 @@ var createMapOperatorScenarios = []expressionScenario{ "D0, P[], (!!seq)::- Mike: cat\n- Mike: dog\n", }, }, + { + document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`, + expression: `.name: .pets[], "f":.food[]`, + expected: []string{ + "D0, P[], (!!seq)::- Mike: cat\n- Mike: dog\n", + "D0, P[], (!!seq)::- f: hotdog\n- f: burger\n", + }, + }, } func TestCreateMapOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index 525ddd0a..943d9fde 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -6,67 +6,65 @@ import ( var multiplyOperatorScenarios = []expressionScenario{ { - // document: `{a: {also: [1]}, b: {also: me}}`, - // expression: `.a * .b`, - // expected: []string{ - // "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", - // }, - // }, { - // document: `{a: {also: me}, b: {also: [1]}}`, - // expression: `.a * .b`, - // expected: []string{ - // "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", - // }, - // }, { - // document: `{a: {also: me}, b: {also: {g: wizz}}}`, - // expression: `.a * .b`, - // expected: []string{ - // "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", - // }, - // }, { - // document: `{a: {also: {g: wizz}}, b: {also: me}}`, - // expression: `.a * .b`, - // expected: []string{ - // "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", - // }, - // }, { - // document: `{a: {also: {g: wizz}}, b: {also: [1]}}`, - // expression: `.a * .b`, - // expected: []string{ - // "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", - // }, - // }, { - // document: `{a: {also: [1]}, b: {also: {g: wizz}}}`, - // expression: `.a * .b`, - // expected: []string{ - // "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", - // }, - // }, { - // document: `{a: {things: great}, b: {also: me}}`, - // expression: `.a * .b`, - // expected: []string{ - // "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n", - // }, - // }, { - // document: `a: {things: great} - // b: - // also: "me" - // `, - // expression: `(.a * .b)`, - // expected: []string{ - // `D0, P[], (!!map)::a: - // things: great - // also: "me" - // b: - // also: "me" - // `, - // }, - // }, { - // document: `{a: [1,2,3], b: [3,4,5]}`, - // expression: `.a * .b`, - // expected: []string{ - // "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", - // }, + document: `{a: {also: [1]}, b: {also: me}}`, + expression: `. * {"a" : .b}`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", + }, + }, { + document: `{a: {also: me}, b: {also: [1]}}`, + expression: `. * {"a":.b}`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", + }, + }, { + document: `{a: {also: me}, b: {also: {g: wizz}}}`, + expression: `. * {"a":.b}`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", + }, + }, { + document: `{a: {also: {g: wizz}}, b: {also: me}}`, + expression: `. * {"a":.b}`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", + }, + }, { + document: `{a: {also: {g: wizz}}, b: {also: [1]}}`, + expression: `. * {"a":.b}`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", + }, + }, { + document: `{a: {also: [1]}, b: {also: {g: wizz}}}`, + expression: `. * {"a":.b}`, + expected: []string{ + "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", + }, + }, { + document: `{a: {things: great}, b: {also: me}}`, + expression: `. * {"a":.b}`, + expected: []string{ + "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n", + }, + }, { + document: `a: {things: great} +b: + also: "me" +`, + expression: `. * {"a":.b}`, + expected: []string{ + `D0, P[], (!!map)::a: {things: great, also: me} +b: + also: "me" +`, + }, + }, { + document: `{a: [1,2,3], b: [3,4,5]}`, + expression: `. * {"a":.b}`, + expected: []string{ + "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", + }, }, } diff --git a/pkg/yqlib/treeops/operator_recursive_descent.go b/pkg/yqlib/treeops/operator_recursive_descent.go index 36098462..7d7511b3 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent.go +++ b/pkg/yqlib/treeops/operator_recursive_descent.go @@ -16,14 +16,11 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod } func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error { - splatOperation := &Operation{OperationType: TraversePath, Value: "[]"} - splatTreeNode := &PathTreeNode{Operation: splatOperation} - for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) results.PushBack(candidate) - children, err := TraversePathOperator(d, nodeToMap(candidate), splatTreeNode) + children, err := Splat(d, nodeToMap(candidate)) if err != nil { return err diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index dc05c34f..2dba41dd 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -8,6 +8,12 @@ import ( "gopkg.in/yaml.v3" ) +func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) { + splatOperation := &Operation{OperationType: TraversePath, Value: "[]"} + splatTreeNode := &PathTreeNode{Operation: splatOperation} + return TraversePathOperator(d, matches, splatTreeNode) +} + func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- Traversing") var matchingNodeMap = list.New() diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/treeops/path_parse_test.go index 825b42a1..d5598c8d 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -59,6 +59,11 @@ var pathTests = []struct { append(make([]interface{}, 0), "[", "true (bool)", "]"), append(make([]interface{}, 0), "true (bool)", "COLLECT", "PIPE"), }, + { + `[true, false]`, + append(make([]interface{}, 0), "[", "true (bool)", "UNION", "false (bool)", "]"), + append(make([]interface{}, 0), "true (bool)", "false (bool)", "UNION", "COLLECT", "PIPE"), + }, { `"mike": .a`, append(make([]interface{}, 0), "mike (string)", "CREATE_MAP", "a"), @@ -69,6 +74,21 @@ var pathTests = []struct { append(make([]interface{}, 0), "a", "CREATE_MAP", "mike (string)"), append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP"), }, + { + `{"mike": .a}`, + append(make([]interface{}, 0), "{", "mike (string)", "CREATE_MAP", "a", "}"), + append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"), + }, + { + `{.a: "mike"}`, + append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "mike (string)", "}"), + append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"), + }, + { + `{.a: .c, .b[]: .f.g[]}`, + append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"), + append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"), + }, // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, // {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")}, diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/treeops/path_postfix.go index 850307d4..147e74d1 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/treeops/path_postfix.go @@ -32,10 +32,16 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, er for _, token := range tokens { log.Debugf("postfix processing token %v, %v", token.toString(), token.Operation) switch token.TokenType { - case OpenBracket, OpenCollect: + case OpenBracket, OpenCollect, OpenCollectObject: opStack = append(opStack, token) - case CloseCollect: - for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != OpenCollect { + case CloseCollect, CloseCollectObject: + var opener TokenType = OpenCollect + var collectOperator *OperationType = Collect + if token.TokenType == CloseCollectObject { + opener = OpenCollectObject + collectOperator = CollectObject + } + for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener { opStack, result = popOpToResult(opStack, result) } if len(opStack) == 0 { @@ -45,7 +51,7 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, er opStack = opStack[0 : len(opStack)-1] //and append a collect to the opStack opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: Pipe}}) - opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: Collect}}) + opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: collectOperator}}) case CloseBracket: for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != OpenBracket { opStack, result = popOpToResult(opStack, result) From 85d059340bb278a12acc2eda08099280c2317461 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 27 Oct 2020 16:45:16 +1100 Subject: [PATCH 052/129] first cli --- cmd/commands_test.go | 138 +- cmd/compare.go | 89 - cmd/compare_test.go | 115 -- cmd/constant.go | 9 +- cmd/delete.go | 41 - cmd/delete_test.go | 246 --- cmd/merge.go | 124 -- cmd/merge_test.go | 551 ------- cmd/new.go | 55 - cmd/new_test.go | 120 -- cmd/prefix.go | 50 - cmd/prefix_test.go | 189 --- cmd/read.go | 65 - cmd/read_test.go | 1463 ----------------- cmd/root.go | 70 +- cmd/shell_completion.go | 57 - cmd/utils.go | 441 +---- cmd/validate.go | 37 - cmd/validate_test.go | 31 - cmd/version.go | 2 +- examples/sample.yaml | 2 + go.mod | 2 +- go.sum | 4 + pkg/yqlib/treeops/candidate_node.go | 1 + pkg/yqlib/treeops/data_tree_navigator.go | 27 +- pkg/yqlib/treeops/data_tree_navigator_test.go | 19 +- pkg/yqlib/treeops/lib.go | 34 +- pkg/yqlib/treeops/operator_assign.go | 8 +- pkg/yqlib/treeops/operator_assign_test.go | 45 +- pkg/yqlib/treeops/operator_booleans.go | 7 +- pkg/yqlib/treeops/operator_delete.go | 4 +- pkg/yqlib/treeops/operator_multilpy.go | 9 +- pkg/yqlib/treeops/operator_multiply_test.go | 24 +- pkg/yqlib/treeops/operator_not_test.go | 70 +- .../treeops/operator_recursive_descent.go | 4 + .../operator_recursive_descent_test.go | 12 +- pkg/yqlib/treeops/operator_select.go | 2 +- pkg/yqlib/treeops/operator_union.go | 4 +- pkg/yqlib/treeops/operators.go | 11 +- pkg/yqlib/treeops/operators_test.go | 2 +- pkg/yqlib/treeops/path_parse_test.go | 2 +- yq.go | 2 +- 42 files changed, 362 insertions(+), 3826 deletions(-) delete mode 100644 cmd/compare.go delete mode 100644 cmd/compare_test.go delete mode 100644 cmd/delete.go delete mode 100644 cmd/delete_test.go delete mode 100644 cmd/merge.go delete mode 100644 cmd/merge_test.go delete mode 100644 cmd/new.go delete mode 100644 cmd/new_test.go delete mode 100644 cmd/prefix.go delete mode 100644 cmd/prefix_test.go delete mode 100644 cmd/read.go delete mode 100644 cmd/read_test.go delete mode 100644 cmd/shell_completion.go delete mode 100644 cmd/validate.go delete mode 100644 cmd/validate_test.go diff --git a/cmd/commands_test.go b/cmd/commands_test.go index a0f0a741..f6d79256 100644 --- a/cmd/commands_test.go +++ b/cmd/commands_test.go @@ -1,83 +1,83 @@ package cmd -import ( - "strings" - "testing" +// import ( +// "strings" +// "testing" - "github.com/mikefarah/yq/v3/test" - "github.com/spf13/cobra" -) +// "github.com/mikefarah/yq/v3/test" +// "github.com/spf13/cobra" +// ) -func getRootCommand() *cobra.Command { - return New() -} +// func getRootCommand() *cobra.Command { +// return New() +// } -func TestRootCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "") - if result.Error != nil { - t.Error(result.Error) - } +// func TestRootCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "") +// if result.Error != nil { +// t.Error(result.Error) +// } - if !strings.Contains(result.Output, "Usage:") { - t.Error("Expected usage message to be printed out, but the usage message was not found.") - } -} +// if !strings.Contains(result.Output, "Usage:") { +// t.Error("Expected usage message to be printed out, but the usage message was not found.") +// } +// } -func TestRootCmd_Help(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "--help") - if result.Error != nil { - t.Error(result.Error) - } +// func TestRootCmd_Help(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "--help") +// if result.Error != nil { +// t.Error(result.Error) +// } - if !strings.Contains(result.Output, "yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.") { - t.Error("Expected usage message to be printed out, but the usage message was not found.") - } -} +// if !strings.Contains(result.Output, "yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.") { +// t.Error("Expected usage message to be printed out, but the usage message was not found.") +// } +// } -func TestRootCmd_VerboseLong(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "--verbose") - if result.Error != nil { - t.Error(result.Error) - } +// func TestRootCmd_VerboseLong(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "--verbose") +// if result.Error != nil { +// t.Error(result.Error) +// } - if !verbose { - t.Error("Expected verbose to be true") - } -} +// if !verbose { +// t.Error("Expected verbose to be true") +// } +// } -func TestRootCmd_VerboseShort(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "-v") - if result.Error != nil { - t.Error(result.Error) - } +// func TestRootCmd_VerboseShort(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "-v") +// if result.Error != nil { +// t.Error(result.Error) +// } - if !verbose { - t.Error("Expected verbose to be true") - } -} +// if !verbose { +// t.Error("Expected verbose to be true") +// } +// } -func TestRootCmd_VersionShort(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "-V") - if result.Error != nil { - t.Error(result.Error) - } - if !strings.Contains(result.Output, "yq version") { - t.Error("expected version message to be printed out, but the message was not found.") - } -} +// func TestRootCmd_VersionShort(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "-V") +// if result.Error != nil { +// t.Error(result.Error) +// } +// if !strings.Contains(result.Output, "yq version") { +// t.Error("expected version message to be printed out, but the message was not found.") +// } +// } -func TestRootCmd_VersionLong(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "--version") - if result.Error != nil { - t.Error(result.Error) - } - if !strings.Contains(result.Output, "yq version") { - t.Error("expected version message to be printed out, but the message was not found.") - } -} +// func TestRootCmd_VersionLong(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "--version") +// if result.Error != nil { +// t.Error(result.Error) +// } +// if !strings.Contains(result.Output, "yq version") { +// t.Error("expected version message to be printed out, but the message was not found.") +// } +// } diff --git a/cmd/compare.go b/cmd/compare.go deleted file mode 100644 index 50fa6a9f..00000000 --- a/cmd/compare.go +++ /dev/null @@ -1,89 +0,0 @@ -package cmd - -// import ( -// "bufio" -// "bytes" -// "os" -// "strings" - -// "github.com/kylelemons/godebug/diff" -// "github.com/mikefarah/yq/v3/pkg/yqlib" -// errors "github.com/pkg/errors" -// "github.com/spf13/cobra" -// ) - -// // turn off for unit tests :( -// var forceOsExit = true - -// func createCompareCmd() *cobra.Command { -// var cmdCompare = &cobra.Command{ -// Use: "compare [yaml_file_a] [yaml_file_b]", -// Aliases: []string{"x"}, -// Short: "yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'", -// Example: ` -// yq x - data2.yml # reads from stdin -// yq x -pp dataA.yaml dataB.yaml '**' # compare paths -// yq x -d1 dataA.yaml dataB.yaml 'a.b.c' -// `, -// Long: "Deeply compares two yaml files, prints the difference. Use with prettyPrint flag to ignore formatting differences.", -// RunE: compareDocuments, -// } -// cmdCompare.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") -// cmdCompare.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") -// cmdCompare.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results") -// cmdCompare.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "strip comments out before comparing") -// cmdCompare.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors") -// return cmdCompare -// } - -// func compareDocuments(cmd *cobra.Command, args []string) error { -// var path = "" - -// if len(args) < 2 { -// return errors.New("Must provide at 2 yaml files") -// } else if len(args) > 2 { -// path = args[2] -// } - -// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() -// if errorParsingDocIndex != nil { -// return errorParsingDocIndex -// } - -// var matchingNodesA []*yqlib.NodeContext -// var matchingNodesB []*yqlib.NodeContext -// var errorDoingThings error - -// matchingNodesA, errorDoingThings = readYamlFile(args[0], path, updateAll, docIndexInt) - -// if errorDoingThings != nil { -// return errorDoingThings -// } - -// matchingNodesB, errorDoingThings = readYamlFile(args[1], path, updateAll, docIndexInt) -// if errorDoingThings != nil { -// return errorDoingThings -// } - -// var dataBufferA bytes.Buffer -// var dataBufferB bytes.Buffer -// errorDoingThings = printResults(matchingNodesA, bufio.NewWriter(&dataBufferA)) -// if errorDoingThings != nil { -// return errorDoingThings -// } -// errorDoingThings = printResults(matchingNodesB, bufio.NewWriter(&dataBufferB)) -// if errorDoingThings != nil { -// return errorDoingThings -// } - -// diffString := diff.Diff(strings.TrimSuffix(dataBufferA.String(), "\n"), strings.TrimSuffix(dataBufferB.String(), "\n")) - -// if len(diffString) > 1 { -// cmd.Print(diffString) -// cmd.Print("\n") -// if forceOsExit { -// os.Exit(1) -// } -// } -// return nil -// } diff --git a/cmd/compare_test.go b/cmd/compare_test.go deleted file mode 100644 index 670b0bab..00000000 --- a/cmd/compare_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package cmd - -// import ( -// "testing" - -// "github.com/mikefarah/yq/v3/test" -// ) - -// func TestCompareSameCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestCompareIgnoreCommentsCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "compare --stripComments ../examples/data1.yaml ../examples/data1-no-comments.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestCompareDontIgnoreCommentsCmd(t *testing.T) { -// forceOsExit = false -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1-no-comments.yaml") - -// expectedOutput := `-a: simple # just the best -// +a: simple -// b: [1, 2] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestCompareExplodeAnchorsCommentsCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "compare --explodeAnchors ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestCompareDontExplodeAnchorsCmd(t *testing.T) { -// forceOsExit = false -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "compare ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml") - -// expectedOutput := `-foo: &foo -// +foo: -// a: 1 -// foobar: -// - !!merge <<: *foo -// + a: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestCompareDifferentCmd(t *testing.T) { -// forceOsExit = false -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data3.yaml") - -// expectedOutput := `-a: simple # just the best -// -b: [1, 2] -// +a: "simple" # just the best -// +b: [1, 3] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestComparePrettyCmd(t *testing.T) { -// forceOsExit = false -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "compare -P ../examples/data1.yaml ../examples/data3.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := ` a: simple # just the best -// b: -// - 1 -// - - 2 -// + - 3 -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestComparePathsCmd(t *testing.T) { -// forceOsExit = false -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "compare -P -ppv ../examples/data1.yaml ../examples/data3.yaml **") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := ` a: simple # just the best -// b.[0]: 1 -// -b.[1]: 2 -// +b.[1]: 3 -// c.test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } diff --git a/cmd/constant.go b/cmd/constant.go index 9de00524..f9bad317 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -1,8 +1,6 @@ package cmd import ( - "github.com/mikefarah/yq/v3/pkg/yqlib" - "github.com/mikefarah/yq/v3/pkg/yqlib/treeops" logging "gopkg.in/op/go-logging.v1" ) @@ -14,7 +12,6 @@ var customStyle = "" var anchorName = "" var makeAlias = false var stripComments = false -var collectIntoArray = false var writeInplace = false var writeScript = "" var sourceYamlFile = "" @@ -22,6 +19,8 @@ var outputToJSON = false var exitStatus = false var prettyPrint = false var explodeAnchors = false +var forceColor = false +var forceNoColor = false var colorsEnabled = false var defaultValue = "" var indent = 2 @@ -31,7 +30,5 @@ var arrayMergeStrategyFlag = "update" var commentsMergeStrategyFlag = "setWhenBlank" var verbose = false var version = false -var docIndex = "0" +var shellCompletion = "" var log = logging.MustGetLogger("yq") -var lib = treeops.NewYqTreeLib() -var valueParser = yqlib.NewValueParser() diff --git a/cmd/delete.go b/cmd/delete.go deleted file mode 100644 index 96531e87..00000000 --- a/cmd/delete.go +++ /dev/null @@ -1,41 +0,0 @@ -package cmd - -// import ( -// "github.com/mikefarah/yq/v3/pkg/yqlib" -// errors "github.com/pkg/errors" -// "github.com/spf13/cobra" -// ) - -// func createDeleteCmd() *cobra.Command { -// var cmdDelete = &cobra.Command{ -// Use: "delete [yaml_file] [path_expression]", -// Aliases: []string{"d"}, -// Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'", -// Example: ` -// yq delete things.yaml 'a.b.c' -// yq delete things.yaml 'a.*.c' -// yq delete things.yaml 'a.(child.subchild==co*).c' -// yq delete things.yaml 'a.**' -// yq delete --inplace things.yaml 'a.b.c' -// yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags -// yq d -i things.yaml 'a.b.c' -// `, -// Long: `Deletes the nodes matching the given path expression from the YAML file. -// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -// `, -// RunE: deleteProperty, -// } -// cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") -// cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") -// return cmdDelete -// } - -// func deleteProperty(cmd *cobra.Command, args []string) error { -// if len(args) < 2 { -// return errors.New("Must provide ") -// } -// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) -// updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]} - -// return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -// } diff --git a/cmd/delete_test.go b/cmd/delete_test.go deleted file mode 100644 index b7715e61..00000000 --- a/cmd/delete_test.go +++ /dev/null @@ -1,246 +0,0 @@ -package cmd - -// import ( -// "fmt" -// "strings" -// "testing" - -// "github.com/mikefarah/yq/v3/test" -// ) - -// func TestDeleteYamlCmd(t *testing.T) { -// content := `a: 2 -// b: -// c: things -// d: something else -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: 2 -// b: -// d: something else -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestDeleteDeepDoesNotExistCmd(t *testing.T) { -// content := `a: 2` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: 2 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestDeleteSplatYaml(t *testing.T) { -// content := `a: other -// b: [3, 4] -// c: -// toast: leave -// test: 1 -// tell: 1 -// tasty.taco: cool -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: other -// b: [3, 4] -// c: -// toast: leave -// tasty.taco: cool -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestDeleteSplatArrayYaml(t *testing.T) { -// content := `a: 2 -// b: -// hi: -// - thing: item1 -// name: fred -// - thing: item2 -// name: sam -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: 2 -// b: -// hi: -// - name: fred -// - name: sam -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestDeleteDeepSplatArrayYaml(t *testing.T) { -// content := `thing: 123 -// b: -// hi: -// - thing: item1 -// name: fred -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `b: -// hi: -// - name: fred -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestDeleteSplatPrefixYaml(t *testing.T) { -// content := `a: 2 -// b: -// hi: -// c: things -// d: something else -// there: -// c: more things -// d: more something else -// there2: -// c: more things also -// d: more something else also -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: 2 -// b: -// hi: -// c: things -// d: something else -// there: -// d: more something else -// there2: -// d: more something else also -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestDeleteYamlArrayCmd(t *testing.T) { -// content := `- 1 -// - 2 -// - 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete %s [1]", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `- 1 -// - 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestDeleteYamlArrayExpressionCmd(t *testing.T) { -// content := `- name: fred -// - name: cat -// - name: thing -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete %s (name==cat)", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `- name: fred -// - name: thing -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestDeleteYamlMulti(t *testing.T) { -// content := `apples: great -// --- -// - 1 -// - 2 -// - 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete -d 1 %s [1]", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `apples: great -// --- -// - 1 -// - 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestDeleteYamlMultiAllCmd(t *testing.T) { -// content := `b: -// c: 3 -// apples: great -// --- -// apples: great -// something: else -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("delete %s -d * apples", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// --- -// something: else` -// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) -// } diff --git a/cmd/merge.go b/cmd/merge.go deleted file mode 100644 index 29a6aabd..00000000 --- a/cmd/merge.go +++ /dev/null @@ -1,124 +0,0 @@ -package cmd - -// import ( -// "github.com/mikefarah/yq/v3/pkg/yqlib" -// errors "github.com/pkg/errors" -// "github.com/spf13/cobra" -// yaml "gopkg.in/yaml.v3" -// ) - -// func createMergeCmd() *cobra.Command { -// var cmdMerge = &cobra.Command{ -// Use: "merge [initial_yaml_file] [additional_yaml_file]...", -// Aliases: []string{"m"}, -// Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--arrayMerge/-a strategy] sample.yaml sample2.yaml", -// Example: ` -// yq merge things.yaml other.yaml -// yq merge --inplace things.yaml other.yaml -// yq m -i things.yaml other.yaml -// yq m --overwrite things.yaml other.yaml -// yq m -i -x things.yaml other.yaml -// yq m -i -a=append things.yaml other.yaml -// yq m -i --autocreate=false things.yaml other.yaml -// `, -// Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). -// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. - -// If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. -// If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. -// `, -// RunE: mergeProperties, -// } -// cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") -// cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") -// cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") -// cmdMerge.PersistentFlags().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", `array merge strategy (update/append/overwrite) -// update: recursively update arrays by their index -// append: concatenate arrays together -// overwrite: replace arrays -// `) -// cmdMerge.PersistentFlags().StringVarP(&commentsMergeStrategyFlag, "comments", "", "setWhenBlank", `comments merge strategy (setWhenBlank/ignore/append/overwrite) -// setWhenBlank: set comment if the original document has no comment at that node -// ignore: leave comments as-is in the original -// append: append comments together -// overwrite: overwrite comments completely -// `) -// cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") -// return cmdMerge -// } - -// /* -// * We don't deeply traverse arrays when appending a merge, instead we want to -// * append the entire array element. -// */ -// func createReadFunctionForMerge(arrayMergeStrategy yqlib.ArrayMergeStrategy) func(*yaml.Node) ([]*yqlib.NodeContext, error) { -// return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) { -// return lib.GetForMerge(dataBucket, "**", arrayMergeStrategy) -// } -// } - -// func mergeProperties(cmd *cobra.Command, args []string) error { -// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) - -// if len(args) < 1 { -// return errors.New("Must provide at least 1 yaml file") -// } -// var arrayMergeStrategy yqlib.ArrayMergeStrategy - -// switch arrayMergeStrategyFlag { -// case "update": -// arrayMergeStrategy = yqlib.UpdateArrayMergeStrategy -// case "append": -// arrayMergeStrategy = yqlib.AppendArrayMergeStrategy -// case "overwrite": -// arrayMergeStrategy = yqlib.OverwriteArrayMergeStrategy -// default: -// return errors.New("Array merge strategy must be one of: update/append/overwrite") -// } - -// var commentsMergeStrategy yqlib.CommentsMergeStrategy - -// switch commentsMergeStrategyFlag { -// case "setWhenBlank": -// commentsMergeStrategy = yqlib.SetWhenBlankCommentsMergeStrategy -// case "ignore": -// commentsMergeStrategy = yqlib.IgnoreCommentsMergeStrategy -// case "append": -// commentsMergeStrategy = yqlib.AppendCommentsMergeStrategy -// case "overwrite": -// commentsMergeStrategy = yqlib.OverwriteCommentsMergeStrategy -// default: -// return errors.New("Comments merge strategy must be one of: setWhenBlank/ignore/append/overwrite") -// } - -// if len(args) > 1 { -// // first generate update commands from the file -// var filesToMerge = args[1:] - -// for _, fileToMerge := range filesToMerge { -// matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0) -// if errorProcessingFile != nil { -// return errorProcessingFile -// } -// log.Debugf("finished reading for merge!") -// for _, matchingNode := range matchingNodes { -// log.Debugf("matched node %v", lib.PathStackToString(matchingNode.PathStack)) -// yqlib.DebugNode(matchingNode.Node) -// } -// for _, matchingNode := range matchingNodes { -// mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy) -// updateCommands = append(updateCommands, yqlib.UpdateCommand{ -// Command: "merge", -// Path: mergePath, -// Value: matchingNode.Node, -// Overwrite: overwriteFlag, -// CommentsMergeStrategy: commentsMergeStrategy, -// // dont update the content for nodes midway, only leaf nodes -// DontUpdateNodeContent: matchingNode.IsMiddleNode && (arrayMergeStrategy != yqlib.OverwriteArrayMergeStrategy || matchingNode.Node.Kind != yaml.SequenceNode), -// }) -// } -// } -// } - -// return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -// } diff --git a/cmd/merge_test.go b/cmd/merge_test.go deleted file mode 100644 index b69060e3..00000000 --- a/cmd/merge_test.go +++ /dev/null @@ -1,551 +0,0 @@ -package cmd - -// import ( -// "fmt" -// "os" -// "runtime" -// "testing" - -// "github.com/mikefarah/yq/v3/test" -// ) - -// func TestMergeCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/data2.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: simple # just the best -// b: [1, 2] -// c: -// test: 1 -// toast: leave -// tell: 1 -// tasty.taco: cool -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeOneFileCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge ../examples/data1.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: simple # just the best -// b: [1, 2] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeNoAutoCreateCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge -c=false ../examples/data1.yaml ../examples/data2.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: simple # just the best -// b: [1, 2] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeOverwriteCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge -c=false --overwrite ../examples/data1.yaml ../examples/data2.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: other # just the best -// b: [3, 4] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeOverwriteDeepExampleCmd(t *testing.T) { -// content := `c: -// test: 1 -// thing: whatever -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// mergeContent := `c: -// test: 5 -// ` -// mergeFilename := test.WriteTempYamlFile(mergeContent) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --overwrite %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `c: -// test: 5 -// thing: whatever -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeAppendCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge --autocreate=false --arrays=append ../examples/data1.yaml ../examples/data2.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: simple # just the best -// b: [1, 2, 3, 4] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeAppendArraysCmd(t *testing.T) { -// content := `people: -// - name: Barry -// age: 21` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// mergeContent := `people: -// - name: Roger -// age: 44` -// mergeFilename := test.WriteTempYamlFile(mergeContent) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge --arrays=append -d* %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `people: -// - name: Barry -// age: 21 -// - name: Roger -// age: 44 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeAliasArraysCmd(t *testing.T) { -// content := ` -// vars: -// variable1: &var1 cat - -// usage: -// value1: *var1 -// valueAnother: *var1 -// valuePlain: thing -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// mergeContent := ` -// vars: -// variable2: &var2 puppy - -// usage: -// value2: *var2 -// valueAnother: *var2 -// valuePlain: *var2 -// ` - -// mergeFilename := test.WriteTempYamlFile(mergeContent) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge -x %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `vars: -// variable1: &var1 cat -// variable2: &var2 puppy -// usage: -// value1: *var1 -// valueAnother: *var2 -// valuePlain: *var2 -// value2: *var2 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeOverwriteAndAppendCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge --autocreate=false --arrays=append --overwrite ../examples/data1.yaml ../examples/data2.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: other # just the best -// b: [1, 2, 3, 4] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// var commentContentA = ` -// a: valueA1 # commentA1 -// b: valueB1 -// ` - -// var commentContentB = ` -// a: valueA2 # commentA2 -// b: valueB2 # commentB2 -// c: valueC2 # commentC2 -// ` - -// func TestMergeCommentsSetWhenBlankCmd(t *testing.T) { -// filename := test.WriteTempYamlFile(commentContentA) -// defer test.RemoveTempYamlFile(filename) - -// mergeFilename := test.WriteTempYamlFile(commentContentB) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=setWhenBlank %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: valueA1 # commentA1 -// b: valueB1 # commentB2 -// c: valueC2 # commentC2 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeCommentsIgnoreCmd(t *testing.T) { -// filename := test.WriteTempYamlFile(commentContentA) -// defer test.RemoveTempYamlFile(filename) - -// mergeFilename := test.WriteTempYamlFile(commentContentB) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=ignore %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: valueA1 # commentA1 -// b: valueB1 -// c: valueC2 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeCommentsAppendCmd(t *testing.T) { -// filename := test.WriteTempYamlFile(commentContentA) -// defer test.RemoveTempYamlFile(filename) - -// mergeFilename := test.WriteTempYamlFile(commentContentB) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=append %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: valueA1 # commentA1 # commentA2 -// b: valueB1 # commentB2 -// c: valueC2 # commentC2 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeCommentsOverwriteCmd(t *testing.T) { -// filename := test.WriteTempYamlFile(commentContentA) -// defer test.RemoveTempYamlFile(filename) - -// mergeFilename := test.WriteTempYamlFile(commentContentB) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=overwrite %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: valueA1 # commentA2 -// b: valueB1 # commentB2 -// c: valueC2 # commentC2 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeOverwriteArraysTooCmd(t *testing.T) { -// content := `a: simple # just the best -// b: [1, 2] -// c: -// test: 1 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// mergeContent := `a: things -// b: [6]` -// mergeFilename := test.WriteTempYamlFile(mergeContent) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --arrays=overwrite --overwrite %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } - -// expectedOutput := `a: things # just the best -// b: [6] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeRootArraysCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge --arrays=append ../examples/sample_array.yaml ../examples/sample_array_2.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `- 1 -// - 2 -// - 3 -// - 4 -// - 5 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeOverwriteArraysCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge --arrays=overwrite ../examples/sample_array.yaml ../examples/sample_array_2.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `- 4 -// - 5 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeUpdateArraysCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge -x --arrays=update ../examples/sample_array.yaml ../examples/sample_array_2.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `- 4 -// - 5 -// - 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeCmd_Multi(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge -d1 ../examples/multiple_docs_small.yaml ../examples/data1.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: Easy! as one two three -// --- -// another: -// document: here -// a: simple # just the best -// b: [1, 2] -// c: -// test: 1 -// --- -// - 1 -// - 2 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeYamlMultiAllCmd(t *testing.T) { -// content := `b: -// c: 3 -// apples: green -// --- -// something: else` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// mergeContent := `apples: red -// something: good` -// mergeFilename := test.WriteTempYamlFile(mergeContent) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge -d* %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// apples: green -// something: good -// --- -// something: else -// apples: red -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeSpecialCharacterKeysCmd(t *testing.T) { -// content := `` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// mergeContent := `key[bracket]: value -// key.bracket: value -// key"value": value -// key'value': value -// ` -// mergeFilename := test.WriteTempYamlFile(mergeContent) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// test.AssertResult(t, mergeContent, result.Output) -// } - -// func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) { -// content := `b: -// c: 3 -// apples: green -// --- -// something: else` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// mergeContent := `apples: red -// something: good` -// mergeFilename := test.WriteTempYamlFile(mergeContent) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge --overwrite -d* %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// apples: red -// something: good -// --- -// something: good -// apples: red -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeYamlNullMapCmd(t *testing.T) { -// content := `b:` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// mergeContent := `b: -// thing: a frog -// ` -// mergeFilename := test.WriteTempYamlFile(mergeContent) -// defer test.RemoveTempYamlFile(mergeFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge %s %s", filename, mergeFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// test.AssertResult(t, mergeContent, result.Output) -// } - -// func TestMergeCmd_Error(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge") -// if result.Error == nil { -// t.Error("Expected command to fail due to missing arg") -// } -// expectedOutput := `Must provide at least 1 yaml file` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestMergeCmd_ErrorUnreadableFile(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge ../examples/data1.yaml fake-unknown") -// if result.Error == nil { -// t.Error("Expected command to fail due to unknown file") -// } -// var expectedOutput string -// if runtime.GOOS == "windows" { -// expectedOutput = `open fake-unknown: The system cannot find the file specified.` -// } else { -// expectedOutput = `open fake-unknown: no such file or directory` -// } -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestMergeCmd_Inplace(t *testing.T) { -// filename := test.WriteTempYamlFile(test.ReadTempYamlFile("../examples/data1.yaml")) -// err := os.Chmod(filename, os.FileMode(int(0666))) -// if err != nil { -// t.Error(err) -// } -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("merge -i %s ../examples/data2.yaml", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// info, _ := os.Stat(filename) -// gotOutput := test.ReadTempYamlFile(filename) -// expectedOutput := `a: simple # just the best -// b: [1, 2] -// c: -// test: 1 -// toast: leave -// tell: 1 -// tasty.taco: cool -// ` -// test.AssertResult(t, expectedOutput, gotOutput) -// test.AssertResult(t, os.FileMode(int(0666)), info.Mode()) -// } - -// func TestMergeAllowEmptyTargetCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge ../examples/empty.yaml ../examples/data1.yaml") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: simple # just the best -// b: [1, 2] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestMergeAllowEmptyMergeCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/empty.yaml") -// expectedOutput := `a: simple # just the best -// b: [1, 2] -// c: -// test: 1 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } diff --git a/cmd/new.go b/cmd/new.go deleted file mode 100644 index 46bc9bd0..00000000 --- a/cmd/new.go +++ /dev/null @@ -1,55 +0,0 @@ -package cmd - -// import ( -// "github.com/mikefarah/yq/v3/pkg/yqlib" -// "github.com/spf13/cobra" -// ) - -// func createNewCmd() *cobra.Command { -// var cmdNew = &cobra.Command{ -// Use: "new [path] [value]", -// Aliases: []string{"n"}, -// Short: "yq n [--script/-s script_file] a.b.c newValue", -// Example: ` -// yq new 'a.b.c' cat -// yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool -// yq n 'a.b[+]' cat -// yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags -// yq n --script create_script.yaml -// `, -// Long: `Creates a new yaml w.r.t the given path and value. -// Outputs to STDOUT - -// Create Scripts: -// Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script. -// `, -// RunE: newProperty, -// } -// cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml") -// cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") -// cmdNew.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged") -// cmdNew.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name") -// cmdNew.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name") -// return cmdNew -// } - -// func newProperty(cmd *cobra.Command, args []string) error { -// var badArgsMessage = "Must provide " -// var updateCommands, updateCommandsError = readUpdateCommands(args, 2, badArgsMessage, false) -// if updateCommandsError != nil { -// return updateCommandsError -// } -// newNode := lib.New(updateCommands[0].Path) - -// for _, updateCommand := range updateCommands { - -// errorUpdating := lib.Update(&newNode, updateCommand, true) - -// if errorUpdating != nil { -// return errorUpdating -// } -// } - -// var encoder = yqlib.NewYamlEncoder(cmd.OutOrStdout(), indent, colorsEnabled) -// return encoder.Encode(&newNode) -// } diff --git a/cmd/new_test.go b/cmd/new_test.go deleted file mode 100644 index fc887de3..00000000 --- a/cmd/new_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package cmd - -// import ( -// "fmt" -// "testing" - -// "github.com/mikefarah/yq/v3/test" -// ) - -// func TestNewCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "new b.c 3") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestNewCmdScript(t *testing.T) { -// updateScript := `- command: update -// path: b.c -// value: 7` -// scriptFilename := test.WriteTempYamlFile(updateScript) -// defer test.RemoveTempYamlFile(scriptFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("new --script %s", scriptFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestNewAnchorCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "new b.c 3 --anchorName=fred") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: &fred 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestNewAliasCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "new b.c foo --makeAlias") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: *foo -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestNewArrayCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "new b[0] 3") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// - 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestNewCmd_Error(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "new b.c") -// if result.Error == nil { -// t.Error("Expected command to fail due to missing arg") -// } -// expectedOutput := `Must provide ` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestNewWithTaggedStyleCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "new b.c cat --tag=!!str --style=tagged") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: !!str cat -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestNewWithDoubleQuotedStyleCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "new b.c cat --style=double") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: "cat" -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestNewWithSingleQuotedStyleCmd(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "new b.c cat --style=single") -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 'cat' -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } diff --git a/cmd/prefix.go b/cmd/prefix.go deleted file mode 100644 index 0fcde77c..00000000 --- a/cmd/prefix.go +++ /dev/null @@ -1,50 +0,0 @@ -package cmd - -// import ( -// "github.com/mikefarah/yq/v3/pkg/yqlib" -// errors "github.com/pkg/errors" -// "github.com/spf13/cobra" -// yaml "gopkg.in/yaml.v3" -// ) - -// func createPrefixCmd() *cobra.Command { -// var cmdPrefix = &cobra.Command{ -// Use: "prefix [yaml_file] [path]", -// Aliases: []string{"p"}, -// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", -// Example: ` -// yq prefix things.yaml 'a.b.c' -// yq prefix --inplace things.yaml 'a.b.c' -// yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags -// yq p -i things.yaml 'a.b.c' -// yq p --doc 2 things.yaml 'a.b.d' -// yq p -d2 things.yaml 'a.b.d' -// `, -// Long: `Prefixes w.r.t to the yaml file at the given path. -// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -// `, -// RunE: prefixProperty, -// } -// cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") -// cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") -// return cmdPrefix -// } - -// func prefixProperty(cmd *cobra.Command, args []string) error { - -// if len(args) < 2 { -// return errors.New("Must provide ") -// } -// updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]} -// log.Debugf("args %v", args) - -// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() -// if errorParsingDocIndex != nil { -// return errorParsingDocIndex -// } - -// var updateData = func(dataBucket *yaml.Node, currentIndex int) error { -// return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand) -// } -// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) -// } diff --git a/cmd/prefix_test.go b/cmd/prefix_test.go deleted file mode 100644 index b23437c4..00000000 --- a/cmd/prefix_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package cmd - -// import ( -// "fmt" -// "runtime" -// "strings" -// "testing" - -// "github.com/mikefarah/yq/v3/test" -// ) - -// func TestPrefixCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `d: -// b: -// c: 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestPrefixCmdArray(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s [+].d.[+]", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `- d: -// - b: -// c: 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestPrefixCmd_MultiLayer(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d.e.f", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `d: -// e: -// f: -// b: -// c: 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestPrefixMultiCmd(t *testing.T) { -// content := `b: -// c: 3 -// --- -// apples: great -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// --- -// d: -// apples: great -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } -// func TestPrefixInvalidDocumentIndexCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -df d", filename)) -// if result.Error == nil { -// t.Error("Expected command to fail due to invalid path") -// } -// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestPrefixBadDocumentIndexCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename)) -// if result.Error == nil { -// t.Error("Expected command to fail due to invalid path") -// } -// expectedOutput := `asked to process document index 1 but there are only 1 document(s)` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } -// func TestPrefixMultiAllCmd(t *testing.T) { -// content := `b: -// c: 3 -// --- -// apples: great -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d * d", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `d: -// b: -// c: 3 -// --- -// d: -// apples: great` -// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) -// } - -// func TestPrefixCmd_Error(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "prefix") -// if result.Error == nil { -// t.Error("Expected command to fail due to missing arg") -// } -// expectedOutput := `Must provide ` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestPrefixCmd_ErrorUnreadableFile(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "prefix fake-unknown a.b") -// if result.Error == nil { -// t.Error("Expected command to fail due to unknown file") -// } -// var expectedOutput string -// if runtime.GOOS == "windows" { -// expectedOutput = `open fake-unknown: The system cannot find the file specified.` -// } else { -// expectedOutput = `open fake-unknown: no such file or directory` -// } -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestPrefixCmd_Inplace(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("prefix -i %s d", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// gotOutput := test.ReadTempYamlFile(filename) -// expectedOutput := `d: -// b: -// c: 3` -// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) -// } diff --git a/cmd/read.go b/cmd/read.go deleted file mode 100644 index 24530464..00000000 --- a/cmd/read.go +++ /dev/null @@ -1,65 +0,0 @@ -package cmd - -import ( - errors "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -func createReadCmd() *cobra.Command { - var cmdRead = &cobra.Command{ - Use: "read [yaml_file] [path_expression]", - Aliases: []string{"r"}, - Short: "yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value'", - Example: ` -yq read things.yaml 'a.b.c' -yq r - 'a.b.c' # reads from stdin -yq r things.yaml 'a.*.c' -yq r things.yaml 'a.**.c' # deep splat -yq r things.yaml 'a.(child.subchild==co*).c' -yq r -d1 things.yaml 'a.array[0].blah' -yq r things.yaml 'a.array[*].blah' -yq r -- things.yaml '--key-starting-with-dashes.blah' - `, - Long: "Outputs the value of the given path in the yaml file to STDOUT", - RunE: readProperty, - } - cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") - cmdRead.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results") - cmdRead.PersistentFlags().BoolVarP(&collectIntoArray, "collect", "c", false, "collect results into array") - cmdRead.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments") - cmdRead.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "print yaml without any comments") - cmdRead.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors") - cmdRead.PersistentFlags().BoolVarP(&exitStatus, "exitStatus", "e", false, "set exit status if no matches are found") - return cmdRead -} - -func readProperty(cmd *cobra.Command, args []string) error { - var path = "" - - if len(args) < 1 { - return errors.New("Must provide filename") - } else if len(args) > 1 { - path = args[1] - } - - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt) - - if exitStatus && len(matchingNodes) == 0 { - cmd.SilenceUsage = true - return errors.New("No matches found") - } - - if errorReadingStream != nil { - cmd.SilenceUsage = true - return errorReadingStream - } - out := cmd.OutOrStdout() - - return printResults(matchingNodes, out) -} diff --git a/cmd/read_test.go b/cmd/read_test.go deleted file mode 100644 index cb9fdfda..00000000 --- a/cmd/read_test.go +++ /dev/null @@ -1,1463 +0,0 @@ -package cmd - -import ( - "fmt" - "runtime" - "testing" - - "github.com/mikefarah/yq/v3/test" -) - -func TestReadCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml b.c") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} - -func TestReadCmdWithExitStatus(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml b.c -e") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} - -func TestReadCmdWithExitStatusNotExist(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml caterpillar -e") - test.AssertResult(t, "No matches found", result.Error.Error()) -} - -func TestReadCmdNotExist(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml caterpillar") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "", result.Output) -} - -func TestReadUnwrapCmd(t *testing.T) { - - content := `b: 'frog' # my favourite` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s b --unwrapScalar=false", filename)) - - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "'frog' # my favourite\n", result.Output) -} - -func TestReadStripCommentsCmd(t *testing.T) { - - content := `# this is really cool -b: # my favourite - c: 5 # cats -# blah -` - - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s --stripComments", filename)) - - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b: - c: 5 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadUnwrapJsonByDefaultCmd(t *testing.T) { - - content := `b: 'frog' # my favourite` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s b -j", filename)) - - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "\"frog\"\n", result.Output) -} - -func TestReadOutputJsonNonStringKeysCmd(t *testing.T) { - - content := ` -true: true -5: - null: - 0.1: deeply - false: things` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s -j", filename)) - - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `{"true":true,"5":{"null":{"0.1":"deeply","false":"things"}}} -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadWithAdvancedFilterCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml b.e(name==sam).value") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "4\n", result.Output) -} - -func TestReadWithAdvancedFilterMapCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml b.e(name==fr*)") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `name: fred -value: 3 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadWithKeyAndValueCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.c") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "b.c: 2\n", result.Output) -} - -func TestReadArrayCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.e[1].name") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "b.e.[1].name: sam\n", result.Output) -} - -func TestReadArrayBackwardsCmd(t *testing.T) { - content := `- one -- two -- three` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -p pv %s [-1]", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "'[-1]': three\n", result.Output) -} - -func TestReadArrayBackwardsNegative0Cmd(t *testing.T) { - content := `- one -- two -- three` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -p pv %s [-0]", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "'[0]': one\n", result.Output) -} - -func TestReadArrayBackwardsPastLimitCmd(t *testing.T) { - content := `- one -- two -- three` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -p pv %s [-4]", filename)) - expectedOutput := "Error reading path in document index 0: Index [-4] out of range, array size is 3" - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestReadArrayLengthCmd(t *testing.T) { - content := `- things -- whatever -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -l %s", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} - -func TestReadArrayLengthDeepCmd(t *testing.T) { - content := `holder: -- things -- whatever -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -l %s holder", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} - -func TestReadArrayLengthDeepMultipleCmd(t *testing.T) { - content := `holderA: -- things -- whatever -skipMe: -- yep -holderB: -- other things -- cool -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -l -c %s holder*", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} - -func TestReadCollectCmd(t *testing.T) { - content := `holderA: yep -skipMe: not me -holderB: me too -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -c %s holder*", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `- yep -- me too -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCollectArrayCmd(t *testing.T) { - content := `- name: fred - value: 32 -- name: sam - value: 67 -- name: fernie - value: 103 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -c %s (name==f*)", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `- name: fred - value: 32 -- name: fernie - value: 103 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadArrayLengthDeepMultipleWithPathCmd(t *testing.T) { - content := `holderA: -- things -- whatever -holderB: -- other things -- cool -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -l %s -ppv holder*", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "holderA: 2\nholderB: 2\n", result.Output) -} - -func TestReadObjectLengthCmd(t *testing.T) { - content := `cat: meow -dog: bark -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s count(*)", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} - -func TestReadObjectLengthDeepCmd(t *testing.T) { - content := `holder: - cat: meow - dog: bark -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s count(holder.*)", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} - -func TestReadObjectLengthDeepMultipleCmd(t *testing.T) { - content := `holderA: - cat: meow - dog: bark -holderB: - elephant: meow - zebra: bark -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s count(holder*)", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "2\n", result.Output) -} - -func TestReadObjectLengthDeepMultipleWithPathsCmd(t *testing.T) { - content := `holderA: - cat: meow - dog: bark -holderB: - elephant: meow - zebra: bark -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -ppv %s holder*.(count(*))", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "holderA: 2\nholderB: 2\n", result.Output) -} - -func TestReadScalarLengthCmd(t *testing.T) { - content := `meow` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s 'count(.)'", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "4\n", result.Output) -} - -func TestReadDoubleQuotedStringCmd(t *testing.T) { - content := `name: "meow face"` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s name", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "meow face\n", result.Output) -} - -func TestReadSingleQuotedStringCmd(t *testing.T) { - content := `name: 'meow face'` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s name", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "meow face\n", result.Output) -} - -func TestReadQuotedMultinlineStringCmd(t *testing.T) { - content := `test: | - abcdefg - hijklmno -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s test", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `abcdefg -hijklmno - -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadQuotedMultinlineNoNewLineStringCmd(t *testing.T) { - content := `test: |- - abcdefg - hijklmno -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s test", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `abcdefg -hijklmno -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadBooleanCmd(t *testing.T) { - content := `name: true` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s name", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "true\n", result.Output) -} - -func TestReadNumberCmd(t *testing.T) { - content := `name: 32.13` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s name", filename)) - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "32.13\n", result.Output) -} - -func TestReadDeepSplatCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.**") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b.c: 2 -b.d.[0]: 3 -b.d.[1]: 4 -b.d.[2]: 5 -b.e.[0].name: fred -b.e.[0].value: 3 -b.e.[1].name: sam -b.e.[1].value: 4 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadDeepSplatWithSuffixCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv ../examples/sample.yaml b.**.name") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `b.e.[0].name: fred -b.e.[1].name: sam -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadWithKeyCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p p ../examples/sample.yaml b.c") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "b.c\n", result.Output) -} - -func TestReadAnchorsCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/simple-anchor.yaml foobar.a") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "1\n", result.Output) -} - -func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv ../examples/simple-anchor.yaml foobar.a") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "foobar.a: 1\n", result.Output) -} - -func TestReadAllAnchorsWithKeyAndValueCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv ../examples/merge-anchor.yaml **") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `foo.a: original -foo.thing: coolasdf -foo.thirsty: yep -bar.b: 2 -bar.thing: coconut -bar.c: oldbar -foobarList.c: newbar -foobarList.b: 2 -foobarList.thing: coconut -foobarList.a: original -foobarList.thirsty: yep -foobar.thirty: well beyond -foobar.thing: ice -foobar.c: 3 -foobar.a: original -foobar.thirsty: yep -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsOriginalCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobar.a") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "original\n", result.Output) -} - -func TestReadMergeAnchorsExplodeJsonCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -j ../examples/merge-anchor.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `{"foo":{"a":"original","thing":"coolasdf","thirsty":"yep"},"bar":{"b":2,"thing":"coconut","c":"oldbar"},"foobarList":{"c":"newbar","b":2,"thing":"coconut","a":"original","thirsty":"yep"},"foobar":{"thirty":"well beyond","thing":"ice","c":3,"a":"original","thirsty":"yep"}} -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsExplodeSimpleCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -X ../examples/simple-anchor.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `foo: - a: 1 -foobar: - a: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsExplodeSimpleValueCmd(t *testing.T) { - content := `value: &value-pointer the value -pointer: *value-pointer` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -X %s pointer", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := "the value\n" - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsExplodeMissingCmd(t *testing.T) { - content := `a: - <<: &anchor - c: d - e: f -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -X %s", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: - c: d - e: f -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsExplodeKeyCmd(t *testing.T) { - content := `name: &nameField Mike -*nameField: Great Guy` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -X %s", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `name: Mike -Mike: Great Guy -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsExplodeSimpleArrayCmd(t *testing.T) { - content := `- things` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -X %s", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `- things -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadNumberKeyJsonCmd(t *testing.T) { - content := `data: {"40433437326": 10.833332}` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -j %s", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `{"data":{"40433437326":10.833332}} -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsExplodeSimpleArrayJsonCmd(t *testing.T) { - content := `- things` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -j %s", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `["things"] -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsExplodeSimpleValueForValueCmd(t *testing.T) { - content := `value: &value-pointer the value -pointer: *value-pointer` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -X %s value", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := "the value\n" - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsExplodeCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -X ../examples/merge-anchor.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `foo: - a: original - thing: coolasdf - thirsty: yep -bar: - b: 2 - thing: coconut - c: oldbar -foobarList: - c: newbar - b: 2 - thing: coconut - a: original - thirsty: yep -foobar: - thirty: well beyond - thing: ice - c: 3 - a: original - thirsty: yep -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsExplodeDeepCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -X ../examples/merge-anchor.yaml foobar") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `thirty: well beyond -thing: ice -c: 3 -a: original -thirsty: yep -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsOverrideCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobar.thing") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "ice\n", result.Output) -} - -func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "r -p pv ../examples/merge-anchor.yaml foobar.th*") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `foobar.thirty: well beyond -foobar.thing: ice -foobar.thirsty: yep -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobarList.a") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "original\n", result.Output) -} - -func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobarList.thing") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "coconut\n", result.Output) -} - -func TestReadMergeAnchorsListOverrideCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/merge-anchor.yaml foobarList.c") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "newbar\n", result.Output) -} - -func TestReadInvalidDocumentIndexCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -df ../examples/sample.yaml b.c") - if result.Error == nil { - t.Error("Expected command to fail due to invalid path") - } - expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestReadBadDocumentIndexCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -d1 ../examples/sample.yaml b.c") - if result.Error == nil { - t.Error("Expected command to fail due to invalid path") - } - expectedOutput := `Could not process document index 1 as there are only 1 document(s)` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestReadOrderCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/order.yaml") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, - `version: 3 -application: MyApp -`, - result.Output) -} - -func TestReadMultiCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -d 1 ../examples/multiple_docs.yaml another.document") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "here\n", result.Output) -} - -func TestReadMultiWithKeyAndValueCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p vp -d 1 ../examples/multiple_docs.yaml another.document") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "another.document: here\n", result.Output) -} - -func TestReadMultiAllCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -d* ../examples/multiple_docs.yaml commonKey") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, - `first document -second document -third document -`, result.Output) -} - -func TestReadMultiAllWithKeyAndValueCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv -d* ../examples/multiple_docs.yaml commonKey") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, - `commonKey: first document -commonKey: second document -commonKey: third document -`, result.Output) -} - -func TestReadCmd_ArrayYaml(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/array.yaml [0].gather_facts") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "false\n", result.Output) -} - -func TestReadEmptyContentCmd(t *testing.T) { - content := `` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadEmptyNodesPrintPathCmd(t *testing.T) { - content := `map: - that: {} -array: - great: [] -null: - indeed: ~` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s -ppv **", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `map.that: {} -array.great: [] -null.indeed: ~ -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadEmptyContentWithDefaultValueCmd(t *testing.T) { - content := `` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read --defaultValue things %s", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `things` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadPrettyPrintCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -P ../examples/sample.json") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: Easy! as one two three -b: - c: 2 - d: - - 3 - - 4 - e: - - name: fred - value: 3 - - name: sam - value: 4 -ab: must appear last -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadNotFoundWithExitStatus(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml adsf -e") - if result.Error == nil { - t.Error("Expected command to fail") - } - expectedOutput := `No matches found` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestReadNotFoundWithoutExitStatus(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/sample.yaml adsf") - if result.Error != nil { - t.Error("Expected command to succeed!") - } -} - -func TestReadPrettyPrintWithIndentCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -P -I4 ../examples/sample.json") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `a: Easy! as one two three -b: - c: 2 - d: - - 3 - - 4 - e: - - name: fred - value: 3 - - name: sam - value: 4 -ab: must appear last -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCmd_ArrayYaml_NoPath(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/array.yaml") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `- become: true - gather_facts: false - hosts: lalaland - name: "Apply smth" - roles: - - lala - - land - serial: 1 -- become: false - gather_facts: true -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCmd_ArrayYaml_OneElement(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/array.yaml [0]") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `become: true -gather_facts: false -hosts: lalaland -name: "Apply smth" -roles: - - lala - - land -serial: 1 -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCmd_ArrayYaml_SplatCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/array.yaml [*]") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `become: true -gather_facts: false -hosts: lalaland -name: "Apply smth" -roles: - - lala - - land -serial: 1 -become: false -gather_facts: true -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p pv ../examples/array.yaml [*]") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `'[0]': - become: true - gather_facts: false - hosts: lalaland - name: "Apply smth" - roles: - - lala - - land - serial: 1 -'[1]': - become: false - gather_facts: true -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -p p ../examples/array.yaml [*]") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `[0] -[1] -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/array.yaml [*].gather_facts") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `false -true -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/array.yaml [x].gather_facts") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ../examples/array.yaml [*].roles[x]") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadCmd_Error(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read") - if result.Error == nil { - t.Error("Expected command to fail due to missing arg") - } - expectedOutput := `Must provide filename` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestReadCmd_ErrorEmptyFilename(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read ") - if result.Error == nil { - t.Error("Expected command to fail due to missing arg") - } - expectedOutput := `Must provide filename` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestReadCmd_ErrorUnreadableFile(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read fake-unknown") - if result.Error == nil { - t.Error("Expected command to fail due to unknown file") - } - var expectedOutput string - if runtime.GOOS == "windows" { - expectedOutput = `open fake-unknown: The system cannot find the file specified.` - } else { - expectedOutput = `open fake-unknown: no such file or directory` - } - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestReadCmd_ErrorBadPath(t *testing.T) { - content := `b: - d: - e: - - 3 - - 4 - f: - - 1 - - 2 -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadToJsonCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -j ../examples/sample.yaml b") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `{"c":2,"d":[3,4,5],"e":[{"name":"fred","value":3},{"name":"sam","value":4}]} -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadToJsonPrettyCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -j -P ../examples/sample.yaml b") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `{ - "c": 2, - "d": [ - 3, - 4, - 5 - ], - "e": [ - { - "name": "fred", - "value": 3 - }, - { - "name": "sam", - "value": 4 - } - ] -} -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadToJsonPrettyIndentCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "read -j -I4 -P ../examples/sample.yaml b") - if result.Error != nil { - t.Error(result.Error) - } - expectedOutput := `{ - "c": 2, - "d": [ - 3, - 4, - 5 - ], - "e": [ - { - "name": "fred", - "value": 3 - }, - { - "name": "sam", - "value": 4 - } - ] -} -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadBadDataCmd(t *testing.T) { - content := `[!Whatever]` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s", filename)) - if result.Error == nil { - t.Error("Expected command to fail") - } - expectedOutput := `yaml: line 1: did not find expected ',' or ']'` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} - -func TestReadDeepFromRootCmd(t *testing.T) { - content := `state: - country: - city: foo -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s (**.city==foo)", filename)) - if result.Error != nil { - t.Error(result.Error) - } - - expectedOutput := `country: - city: foo -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadSplatPrefixCmd(t *testing.T) { - content := `a: 2 -b: - hi: - c: things - d: something else - there: - c: more things - d: more something else - there2: - c: more things also - d: more something else also -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read %s b.there*.c", filename)) - if result.Error != nil { - t.Error(result.Error) - } - - expectedOutput := `more things -more things also -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadSplatPrefixWithKeyAndValueCmd(t *testing.T) { - content := `a: 2 -b: - hi: - c: things - d: something else - there: - c: more things - d: more something else - there2: - c: more things also - d: more something else also -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -p pv %s b.there*.c", filename)) - if result.Error != nil { - t.Error(result.Error) - } - - expectedOutput := `b.there.c: more things -b.there2.c: more things also -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadSplatPrefixWithKeyCmd(t *testing.T) { - content := `a: 2 -b: - hi: - c: things - d: something else - there: - c: more things - d: more something else - there2: - c: more things also - d: more something else also -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("read -p p %s b.there*.c", filename)) - if result.Error != nil { - t.Error(result.Error) - } - - expectedOutput := `b.there.c -b.there2.c -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadExpression(t *testing.T) { - content := `name: value` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("r %s (x==f)", filename)) - if result.Error != nil { - t.Error(result.Error) - } - - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadFindValueArrayCmd(t *testing.T) { - content := `- cat -- dog -- rat -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("r %s (.==dog)", filename)) - if result.Error != nil { - t.Error(result.Error) - } - - expectedOutput := `dog -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadFindValueDeepArrayCmd(t *testing.T) { - content := `animals: - - cat - - dog - - rat -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("r %s animals(.==dog)", filename)) - if result.Error != nil { - t.Error(result.Error) - } - - expectedOutput := `dog -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadFindValueDeepObjectCmd(t *testing.T) { - content := `animals: - great: yes - small: sometimes -` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("r %s animals(.==yes) -ppv", filename)) - if result.Error != nil { - t.Error(result.Error) - } - - expectedOutput := `animals.great: yes -` - test.AssertResult(t, expectedOutput, result.Output) -} - -func TestReadKeepsKeyOrderInJson(t *testing.T) { - const content = `{ - "z": "One", - "a": 1, - "w": ["a", "r"], - "u": {"d": "o", "0": 11.5}, -}` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("r -j %s", filename)) - if result.Error != nil { - t.Error(result.Error) - } - - expectedOutput := `{"z":"One","a":1,"w":["a","r"],"u":{"d":"o","0":11.5}} -` - test.AssertResult(t, expectedOutput, result.Output) -} diff --git a/cmd/root.go b/cmd/root.go index b2e3ff47..e3b3bdbf 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,8 +1,11 @@ package cmd import ( + "errors" + "fmt" "os" + "github.com/mikefarah/yq/v4/pkg/yqlib/treeops" "github.com/spf13/cobra" logging "gopkg.in/op/go-logging.v1" ) @@ -17,9 +20,51 @@ func New() *cobra.Command { cmd.Print(GetVersionDisplay()) return nil } - cmd.Println(cmd.UsageString()) + if shellCompletion != "" { + switch shellCompletion { + case "bash", "": + return cmd.GenBashCompletion(os.Stdout) + case "zsh": + return cmd.GenZshCompletion(os.Stdout) + case "fish": + return cmd.GenFishCompletion(os.Stdout, true) + case "powershell": + return cmd.GenPowerShellCompletion(os.Stdout) + default: + return fmt.Errorf("Unknown variant %v", shellCompletion) + } + } + // if len(args) == 0 { + // cmd.Println(cmd.UsageString()) + // return nil + // } + cmd.SilenceUsage = true - return nil + var treeCreator = treeops.NewPathTreeCreator() + + expression := "" + if len(args) > 0 { + expression = args[0] + } + + pathNode, err := treeCreator.ParsePath(expression) + if err != nil { + return err + } + + matchingNodes, err := evaluate("-", pathNode) + if err != nil { + return err + } + + if exitStatus && matchingNodes.Len() == 0 { + cmd.SilenceUsage = true + return errors.New("No matches found") + } + + out := cmd.OutOrStdout() + + return printResults(matchingNodes, out) }, PersistentPreRun: func(cmd *cobra.Command, args []string) { cmd.SetOut(cmd.OutOrStdout()) @@ -44,18 +89,15 @@ func New() *cobra.Command { rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print") rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") - rootCmd.PersistentFlags().BoolVarP(&colorsEnabled, "colors", "C", false, "print with colors") - rootCmd.AddCommand( - createReadCmd(), - // createCompareCmd(), - createValidateCmd(), - // createWriteCmd(), - // createPrefixCmd(), - // createDeleteCmd(), - // createNewCmd(), - // createMergeCmd(), - createBashCompletionCmd(rootCmd), - ) + rootCmd.Flags().StringVarP(&shellCompletion, "shellCompletion", "", "", "[bash/zsh/powershell/fish] prints shell completion script") + + rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors") + rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors") + + // rootCmd.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + rootCmd.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") + rootCmd.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results") + return rootCmd } diff --git a/cmd/shell_completion.go b/cmd/shell_completion.go deleted file mode 100644 index d1d287b3..00000000 --- a/cmd/shell_completion.go +++ /dev/null @@ -1,57 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -var shellVariant = "bash" - -func createBashCompletionCmd(rootCmd *cobra.Command) *cobra.Command { - var completionCmd = &cobra.Command{ - Use: "shell-completion", - Short: "Generates shell completion scripts", - Long: `To load completion for: -bash: - Run - . <(yq shell-completion) - - To configure your bash shell to load completions for each session add to - your bashrc - - # ~/.bashrc or ~/.profile - . <(yq shell-completion) - -zsh: - The generated completion script should be put somewhere in your $fpath named _yq - -powershell: - Users need PowerShell version 5.0 or above, which comes with Windows 10 and - can be downloaded separately for Windows 7 or 8.1. They can then write the - completions to a file and source this file from their PowerShell profile, - which is referenced by the $Profile environment variable. - -fish: - Save the output to a fish file and add it to your completions directory. - - `, - RunE: func(cmd *cobra.Command, args []string) error { - switch shellVariant { - case "bash", "": - return rootCmd.GenBashCompletion(os.Stdout) - case "zsh": - return rootCmd.GenZshCompletion(os.Stdout) - case "fish": - return rootCmd.GenFishCompletion(os.Stdout, true) - case "powershell": - return rootCmd.GenPowerShellCompletion(os.Stdout) - default: - return fmt.Errorf("Unknown variant %v", shellVariant) - } - }, - } - completionCmd.PersistentFlags().StringVarP(&shellVariant, "variation", "V", "", "shell variation: bash (default), zsh, fish, powershell") - return completionCmd -} diff --git a/cmd/utils.go b/cmd/utils.go index 9d3e5a7a..920adbad 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -2,103 +2,73 @@ package cmd import ( "bufio" - "fmt" + "container/list" + "errors" "io" "os" - "strconv" - "github.com/mikefarah/yq/v3/pkg/yqlib" - "github.com/mikefarah/yq/v3/pkg/yqlib/treeops" - errors "github.com/pkg/errors" + "github.com/mikefarah/yq/v4/pkg/yqlib" + "github.com/mikefarah/yq/v4/pkg/yqlib/treeops" yaml "gopkg.in/yaml.v3" ) -type readDataFn func(document int, dataBucket *yaml.Node) ([]*treeops.CandidateNode, error) - -func createReadFunction(path string) func(int, *yaml.Node) ([]*treeops.CandidateNode, error) { - return func(document int, dataBucket *yaml.Node) ([]*treeops.CandidateNode, error) { - return lib.Get(document, dataBucket, path) +func readStream(filename string) (*yaml.Decoder, error) { + if filename == "" { + return nil, errors.New("Must provide filename") } -} -func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*treeops.CandidateNode, error) { - return doReadYamlFile(filename, createReadFunction(path), updateAll, docIndexInt) -} - -func doReadYamlFile(filename string, readFn readDataFn, updateAll bool, docIndexInt int) ([]*treeops.CandidateNode, error) { - var matchingNodes []*treeops.CandidateNode - - var currentIndex = 0 - var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error { - for { - var dataBucket yaml.Node - errorReading := decoder.Decode(&dataBucket) - - if errorReading == io.EOF { - return handleEOF(updateAll, docIndexInt, currentIndex) - } else if errorReading != nil { - return errorReading - } - - var errorParsing error - matchingNodes, errorParsing = appendDocument(matchingNodes, dataBucket, readFn, updateAll, docIndexInt, currentIndex) - if errorParsing != nil { - return errorParsing - } - if !updateAll && currentIndex == docIndexInt { - log.Debug("all done") - return nil - } - currentIndex = currentIndex + 1 + var stream io.Reader + if filename == "-" { + stream = bufio.NewReader(os.Stdin) + } else { + file, err := os.Open(filename) // nolint gosec + if err != nil { + return nil, err } - }) - return matchingNodes, errorReadingStream + defer safelyCloseFile(file) + stream = file + } + return yaml.NewDecoder(stream), nil } -func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { - log.Debugf("done %v / %v", currentIndex, docIndexInt) - if !updateAll && currentIndex <= docIndexInt && docIndexInt != 0 { - return fmt.Errorf("Could not process document index %v as there are only %v document(s)", docIndex, currentIndex) - } - return nil -} +func evaluate(filename string, node *treeops.PathTreeNode) (*list.List, error) { -func appendDocument(originalMatchingNodes []*treeops.CandidateNode, dataBucket yaml.Node, readFn readDataFn, updateAll bool, docIndexInt int, currentIndex int) ([]*treeops.CandidateNode, error) { - log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) - // yqlib.DebugNode(&dataBucket) - if !updateAll && currentIndex != docIndexInt { - return originalMatchingNodes, nil - } - log.Debugf("reading in document %v", currentIndex) - matchingNodes, errorParsing := readFn(currentIndex, &dataBucket) - if errorParsing != nil { - return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) - } - return append(originalMatchingNodes, matchingNodes...), nil -} + var treeNavigator = treeops.NewDataTreeNavigator(treeops.NavigationPrefs{}) -func lengthOf(node *yaml.Node) int { - kindToCheck := node.Kind - if node.Kind == yaml.DocumentNode && len(node.Content) == 1 { - log.Debugf("length of document node, calculating length of child") - kindToCheck = node.Content[0].Kind - } - switch kindToCheck { - case yaml.ScalarNode: - return len(node.Value) - case yaml.MappingNode: - return len(node.Content) / 2 - default: - return len(node.Content) - } -} + var matchingNodes = list.New() -// transforms node before printing, if required -func transformNode(node *yaml.Node) *yaml.Node { - if printLength { - return &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", lengthOf(node))} + var currentIndex uint = 0 + var decoder, err = readStream(filename) + if err != nil { + return nil, err } - return node + + for { + var dataBucket yaml.Node + errorReading := decoder.Decode(&dataBucket) + + if errorReading == io.EOF { + return matchingNodes, nil + } else if errorReading != nil { + return nil, errorReading + } + candidateNode := &treeops.CandidateNode{ + Document: currentIndex, + Filename: filename, + Node: &dataBucket, + } + inputList := list.New() + inputList.PushBack(candidateNode) + + newMatches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node) + if errorParsing != nil { + return nil, errorParsing + } + matchingNodes.PushBackList(newMatches) + currentIndex = currentIndex + 1 + } + + return matchingNodes, nil } func printNode(node *yaml.Node, writer io.Writer) error { @@ -114,9 +84,10 @@ func printNode(node *yaml.Node, writer io.Writer) error { return encoder.Encode(node) } -func removeComments(matchingNodes []*treeops.CandidateNode) { - for _, nodeContext := range matchingNodes { - removeCommentOfNode(nodeContext.Node) +func removeComments(matchingNodes *list.List) { + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*treeops.CandidateNode) + removeCommentOfNode(candidate.Node) } } @@ -130,9 +101,10 @@ func removeCommentOfNode(node *yaml.Node) { } } -func setStyle(matchingNodes []*treeops.CandidateNode, style yaml.Style) { - for _, nodeContext := range matchingNodes { - updateStyleOfNode(nodeContext.Node, style) +func setStyle(matchingNodes *list.List, style yaml.Style) { + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*treeops.CandidateNode) + updateStyleOfNode(candidate.Node, style) } } @@ -234,9 +206,10 @@ func explodeNode(node *yaml.Node) error { } } -func explode(matchingNodes []*treeops.CandidateNode) error { +func explode(matchingNodes *list.List) error { log.Debug("exploding nodes") - for _, nodeContext := range matchingNodes { + for el := matchingNodes.Front(); el != nil; el = el.Next() { + nodeContext := el.Value.(*treeops.CandidateNode) log.Debugf("exploding %v", nodeContext.GetKey()) errorExplodingNode := explodeNode(nodeContext.Node) if errorExplodingNode != nil { @@ -246,7 +219,7 @@ func explode(matchingNodes []*treeops.CandidateNode) error { return nil } -func printResults(matchingNodes []*treeops.CandidateNode, writer io.Writer) error { +func printResults(matchingNodes *list.List, writer io.Writer) error { if prettyPrint { setStyle(matchingNodes, 0) } @@ -255,6 +228,12 @@ func printResults(matchingNodes []*treeops.CandidateNode, writer io.Writer) erro removeComments(matchingNodes) } + fileInfo, _ := os.Stdout.Stat() + + if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { + colorsEnabled = true + } + //always explode anchors when printing json if explodeAnchors || outputToJSON { errorExploding := explode(matchingNodes) @@ -266,7 +245,7 @@ func printResults(matchingNodes []*treeops.CandidateNode, writer io.Writer) erro bufferedWriter := bufio.NewWriter(writer) defer safelyFlush(bufferedWriter) - if len(matchingNodes) == 0 { + if matchingNodes.Len() == 0 { log.Debug("no matching results, nothing to print") if defaultValue != "" { return writeString(bufferedWriter, defaultValue) @@ -275,9 +254,9 @@ func printResults(matchingNodes []*treeops.CandidateNode, writer io.Writer) erro } var errorWriting error - var arrayCollection = yaml.Node{Kind: yaml.SequenceNode} + for el := matchingNodes.Front(); el != nil; el = el.Next() { + mappedDoc := el.Value.(*treeops.CandidateNode) - for _, mappedDoc := range matchingNodes { switch printMode { case "p": errorWriting = writeString(bufferedWriter, mappedDoc.PathStackToString()+"\n") @@ -289,251 +268,24 @@ func printResults(matchingNodes []*treeops.CandidateNode, writer io.Writer) erro var parentNode = yaml.Node{Kind: yaml.MappingNode} parentNode.Content = make([]*yaml.Node, 2) parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: mappedDoc.PathStackToString()} - parentNode.Content[1] = transformNode(mappedDoc.Node) - if collectIntoArray { - arrayCollection.Content = append(arrayCollection.Content, &parentNode) - } else if err := printNode(&parentNode, bufferedWriter); err != nil { + if mappedDoc.Node.Kind == yaml.DocumentNode { + parentNode.Content[1] = mappedDoc.Node.Content[0] + } else { + parentNode.Content[1] = mappedDoc.Node + } + if err := printNode(&parentNode, bufferedWriter); err != nil { return err } default: - if collectIntoArray { - arrayCollection.Content = append(arrayCollection.Content, mappedDoc.Node) - } else if err := printNode(transformNode(mappedDoc.Node), bufferedWriter); err != nil { + if err := printNode(mappedDoc.Node, bufferedWriter); err != nil { return err } } } - if collectIntoArray { - if err := printNode(transformNode(&arrayCollection), bufferedWriter); err != nil { - return err - } - } - return nil } -func parseDocumentIndex() (bool, int, error) { - if docIndex == "*" { - return true, -1, nil - } - docIndexInt64, err := strconv.ParseInt(docIndex, 10, 32) - if err != nil { - return false, -1, errors.Wrapf(err, "Document index %v is not a integer or *", docIndex) - } - return false, int(docIndexInt64), nil -} - -type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error - -func isNullDocument(dataBucket *yaml.Node) bool { - return dataBucket.Kind == yaml.DocumentNode && (len(dataBucket.Content) == 0 || - dataBucket.Content[0].Kind == yaml.ScalarNode && dataBucket.Content[0].Tag == "!!null") -} - -func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn { - return func(decoder *yaml.Decoder) error { - var dataBucket yaml.Node - var errorReading error - var errorWriting error - var errorUpdating error - var currentIndex = 0 - - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - for { - log.Debugf("Read doc %v", currentIndex) - errorReading = decoder.Decode(&dataBucket) - - if errorReading == io.EOF && docIndexInt == 0 && currentIndex == 0 { - //empty document, lets just make one - dataBucket = yaml.Node{Kind: yaml.DocumentNode, Content: make([]*yaml.Node, 1)} - child := yaml.Node{Kind: yaml.MappingNode} - dataBucket.Content[0] = &child - } else if isNullDocument(&dataBucket) && (updateAll || docIndexInt == currentIndex) { - child := yaml.Node{Kind: yaml.MappingNode} - dataBucket.Content[0] = &child - } else if errorReading == io.EOF { - if !updateAll && currentIndex <= docIndexInt { - return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) - } - return nil - } else if errorReading != nil { - return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading) - } - errorUpdating = updateData(&dataBucket, currentIndex) - if errorUpdating != nil { - return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex) - } - - if prettyPrint { - updateStyleOfNode(&dataBucket, 0) - } - - errorWriting = encoder.Encode(&dataBucket) - - if errorWriting != nil { - return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting) - } - currentIndex = currentIndex + 1 - } - } -} - -// func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { -// if updateAll || currentIndex == docIndexInt { -// log.Debugf("Prefixing document %v", currentIndex) -// // yqlib.DebugNode(dataBucket) -// updateCommand.Value = dataBucket.Content[0] -// dataBucket.Content = make([]*yaml.Node, 1) - -// newNode := lib.New(updateCommand.Path) -// dataBucket.Content[0] = &newNode - -// errorUpdating := lib.Update(dataBucket, updateCommand, true) -// if errorUpdating != nil { -// return errorUpdating -// } -// } -// return nil -// } - -// func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error { -// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() -// if errorParsingDocIndex != nil { -// return errorParsingDocIndex -// } - -// var updateData = func(dataBucket *yaml.Node, currentIndex int) error { -// if updateAll || currentIndex == docIndexInt { -// log.Debugf("Updating doc %v", currentIndex) -// for _, updateCommand := range updateCommands { -// log.Debugf("Processing update to Path %v", updateCommand.Path) -// errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag) -// if errorUpdating != nil { -// return errorUpdating -// } -// } -// } -// return nil -// } -// return readAndUpdate(writer, inputFile, updateData) -// } - -// func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { -// var destination io.Writer -// var destinationName string -// var completedSuccessfully = false -// if writeInplace { -// info, err := os.Stat(inputFile) -// if err != nil { -// return err -// } -// // mkdir temp dir as some docker images does not have temp dir -// _, err = os.Stat(os.TempDir()) -// if os.IsNotExist(err) { -// err = os.Mkdir(os.TempDir(), 0700) -// if err != nil { -// return err -// } -// } else if err != nil { -// return err -// } -// tempFile, err := ioutil.TempFile("", "temp") -// if err != nil { -// return err -// } -// destinationName = tempFile.Name() -// err = os.Chmod(destinationName, info.Mode()) -// if err != nil { -// return err -// } -// destination = tempFile -// defer func() { -// safelyCloseFile(tempFile) -// if completedSuccessfully { -// safelyRenameFile(tempFile.Name(), inputFile) -// } -// }() -// } else { -// destination = stdOut -// destinationName = "Stdout" -// } - -// log.Debugf("Writing to %v from %v", destinationName, inputFile) - -// bufferedWriter := bufio.NewWriter(destination) -// defer safelyFlush(bufferedWriter) - -// var encoder yqlib.Encoder -// if outputToJSON { -// encoder = yqlib.NewJsonEncoder(bufferedWriter, prettyPrint, indent) -// } else { -// encoder = yqlib.NewYamlEncoder(bufferedWriter, indent, colorsEnabled) -// } - -// var errorProcessing = readStream(inputFile, mapYamlDecoder(updateData, encoder)) -// completedSuccessfully = errorProcessing == nil -// return errorProcessing -// } - -type updateCommandParsed struct { - Command string - Path string - Value yaml.Node -} - -// func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string, allowNoValue bool) ([]yqlib.UpdateCommand, error) { -// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0) -// if writeScript != "" { -// var parsedCommands = make([]updateCommandParsed, 0) - -// err := readData(writeScript, 0, &parsedCommands) - -// if err != nil && err != io.EOF { -// return nil, err -// } - -// log.Debugf("Read write commands file '%v'", parsedCommands) -// for index := range parsedCommands { -// parsedCommand := parsedCommands[index] -// updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true} -// updateCommands = append(updateCommands, updateCommand) -// } - -// log.Debugf("Read write commands file '%v'", updateCommands) -// } else if sourceYamlFile != "" && len(args) == expectedArgs-1 { -// log.Debugf("Reading value from %v", sourceYamlFile) -// var value yaml.Node -// err := readData(sourceYamlFile, 0, &value) -// if err != nil && err != io.EOF { -// return nil, err -// } -// log.Debug("args %v", args[expectedArgs-2]) -// updateCommands = make([]yqlib.UpdateCommand, 1) -// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value.Content[0], Overwrite: true} -// } else if len(args) == expectedArgs { -// updateCommands = make([]yqlib.UpdateCommand, 1) -// log.Debug("args %v", args) -// log.Debug("path %v", args[expectedArgs-2]) -// log.Debug("Value %v", args[expectedArgs-1]) -// value := valueParser.Parse(args[expectedArgs-1], customTag, customStyle, anchorName, makeAlias) -// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value, Overwrite: true, CommentsMergeStrategy: yqlib.IgnoreCommentsMergeStrategy} -// } else if len(args) == expectedArgs-1 && allowNoValue { -// // don't update the value -// updateCommands = make([]yqlib.UpdateCommand, 1) -// log.Debug("args %v", args) -// log.Debug("path %v", args[expectedArgs-2]) -// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse("", customTag, customStyle, anchorName, makeAlias), Overwrite: true, DontUpdateNodeValue: true} -// } else { -// return nil, errors.New(badArgsMessage) -// } -// return updateCommands, nil -// } - func safelyRenameFile(from string, to string) { if renameError := os.Rename(from, to); renameError != nil { log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) @@ -584,36 +336,3 @@ func safelyCloseFile(file *os.File) { log.Error(err.Error()) } } - -type yamlDecoderFn func(*yaml.Decoder) error - -func readStream(filename string, yamlDecoder yamlDecoderFn) error { - if filename == "" { - return errors.New("Must provide filename") - } - - var stream io.Reader - if filename == "-" { - stream = bufio.NewReader(os.Stdin) - } else { - file, err := os.Open(filename) // nolint gosec - if err != nil { - return err - } - defer safelyCloseFile(file) - stream = file - } - return yamlDecoder(yaml.NewDecoder(stream)) -} - -func readData(filename string, indexToRead int, parsedData interface{}) error { - return readStream(filename, func(decoder *yaml.Decoder) error { - for currentIndex := 0; currentIndex < indexToRead; currentIndex++ { - errorSkipping := decoder.Decode(parsedData) - if errorSkipping != nil { - return errors.Wrapf(errorSkipping, "Error processing document at index %v, %v", currentIndex, errorSkipping) - } - } - return decoder.Decode(parsedData) - }) -} diff --git a/cmd/validate.go b/cmd/validate.go deleted file mode 100644 index 5e3a9301..00000000 --- a/cmd/validate.go +++ /dev/null @@ -1,37 +0,0 @@ -package cmd - -import ( - errors "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -func createValidateCmd() *cobra.Command { - var cmdRead = &cobra.Command{ - Use: "validate [yaml_file]", - Aliases: []string{"v"}, - Short: "yq v sample.yaml", - Example: ` -yq v - # reads from stdin -`, - RunE: validateProperty, - SilenceUsage: true, - SilenceErrors: false, - } - cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - return cmdRead -} - -func validateProperty(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("Must provide filename") - } - - var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() - if errorParsingDocIndex != nil { - return errorParsingDocIndex - } - - _, errorReadingStream := readYamlFile(args[0], "", updateAll, docIndexInt) - - return errorReadingStream -} diff --git a/cmd/validate_test.go b/cmd/validate_test.go deleted file mode 100644 index 378f0b1f..00000000 --- a/cmd/validate_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package cmd - -import ( - "fmt" - "testing" - - "github.com/mikefarah/yq/v3/test" -) - -func TestValidateCmd(t *testing.T) { - cmd := getRootCommand() - result := test.RunCmd(cmd, "validate ../examples/sample.yaml b.c") - if result.Error != nil { - t.Error(result.Error) - } - test.AssertResult(t, "", result.Output) -} - -func TestValidateBadDataCmd(t *testing.T) { - content := `[!Whatever]` - filename := test.WriteTempYamlFile(content) - defer test.RemoveTempYamlFile(filename) - - cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("validate %s", filename)) - if result.Error == nil { - t.Error("Expected command to fail") - } - expectedOutput := `yaml: line 1: did not find expected ',' or ']'` - test.AssertResult(t, expectedOutput, result.Error.Error()) -} diff --git a/cmd/version.go b/cmd/version.go index c68e1d4b..394dc638 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -11,7 +11,7 @@ var ( GitDescribe string // Version is main version number that is being run at the moment. - Version = "3.4.0" + Version = "4.0.0-beta" // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/examples/sample.yaml b/examples/sample.yaml index 1fb93352..9b925d75 100644 --- a/examples/sample.yaml +++ b/examples/sample.yaml @@ -1,3 +1,5 @@ +# Some doc + a: true b: c: 2 diff --git a/go.mod b/go.mod index 4152391c..11dab62f 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/mikefarah/yq/v3 +module github.com/mikefarah/yq/v4 require ( github.com/elliotchance/orderedmap v1.3.0 // indirect diff --git a/go.sum b/go.sum index b36faa9e..524e4955 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,10 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mikefarah/yq v1.15.0 h1:ViMYNRG5UB7hzm8olxMFqPtkpMXXKO4g32/v9JUa62o= +github.com/mikefarah/yq v2.4.0+incompatible h1:oBxbWy8R9hI3BIUUxEf0CzikWa2AgnGrGhvGQt5jgjk= +github.com/mikefarah/yq/v3 v3.0.0-20201020025845-ccb718cd0f59 h1:6nvF+EEFIVD4KT64CgvAzWaMVC283Huno59khWs9r1A= +github.com/mikefarah/yq/v3 v3.0.0-20201020025845-ccb718cd0f59/go.mod h1:7eVjFf5bgozMuHk+oKpyxR2zCN3iEN1tF0/bM5jvtKo= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index 7c3b3f9c..f1ed656d 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -13,6 +13,7 @@ type CandidateNode struct { Node *yaml.Node // the actual node Path []interface{} /// the path we took to get to this node Document uint // the document index of this node + Filename string } func (n *CandidateNode) GetKey() string { diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index 1b7dbc69..8d541253 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -17,34 +17,17 @@ type NavigationPrefs struct { } type DataTreeNavigator interface { - GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) + // given a list of CandidateEntities and a pathNode, + // this will process the list against the given pathNode and return + // a new list of matching candidates + GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) } func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { return &dataTreeNavigator{navigationPrefs} } -func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { - var matchingNodeMap = list.New() - - for _, n := range matchingNodes { - matchingNodeMap.PushBack(n) - } - - matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode) - if err != nil { - return nil, err - } - - values := make([]*CandidateNode, 0, matchedNodes.Len()) - - for el := matchedNodes.Front(); el != nil; el = el.Next() { - values = append(values, el.Value.(*CandidateNode)) - } - return values, nil -} - -func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { +func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { if pathNode == nil { log.Debugf("getMatchingNodes - nothing to do") return matchingNodes, nil diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/treeops/data_tree_navigator_test.go index 750bf000..e82eac70 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/treeops/data_tree_navigator_test.go @@ -1,6 +1,7 @@ package treeops import ( + "container/list" "strings" "testing" @@ -10,9 +11,10 @@ import ( var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) var treeCreator = NewPathTreeCreator() -func readDoc(t *testing.T, content string) []*CandidateNode { +func readDoc(t *testing.T, content string) *list.List { + inputList := list.New() if content == "" { - return []*CandidateNode{} + return inputList } decoder := yaml.NewDecoder(strings.NewReader(content)) var dataBucket yaml.Node @@ -21,12 +23,19 @@ func readDoc(t *testing.T, content string) []*CandidateNode { t.Error(content) t.Error(err) } - return []*CandidateNode{&CandidateNode{Node: dataBucket.Content[0], Document: 0}} + + inputList.PushBack(&CandidateNode{ + Document: 0, + Filename: "test.yml", + Node: &dataBucket, + }) + return inputList } -func resultsToString(results []*CandidateNode) []string { +func resultsToString(results *list.List) []string { var pretty []string = make([]string, 0) - for _, n := range results { + for el := results.Front(); el != nil; el = el.Next() { + n := el.Value.(*CandidateNode) pretty = append(pretty, NodeToString(n)) } return pretty diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 50deca23..5efdaf4f 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -101,34 +101,6 @@ func (p *Operation) toString() string { } } -type YqTreeLib interface { - Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error) - // GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) - // Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error - // New(path string) yaml.Node - - // PathStackToString(pathStack []interface{}) string - // MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string -} - -func NewYqTreeLib() YqTreeLib { - return &lib{treeCreator: NewPathTreeCreator()} -} - -type lib struct { - treeCreator PathTreeCreator -} - -func (l *lib) Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error) { - nodes := []*CandidateNode{&CandidateNode{Node: documentNode.Content[0], Document: 0}} - navigator := NewDataTreeNavigator(NavigationPrefs{}) - pathNode, errPath := l.treeCreator.ParsePath(path) - if errPath != nil { - return nil, errPath - } - return navigator.GetMatchingNodes(nodes, pathNode) -} - //use for debugging only func NodesToString(collection *list.List) string { if !log.IsEnabledFor(logging.DEBUG) { @@ -157,7 +129,11 @@ func NodeToString(node *CandidateNode) string { log.Error("Error debugging node, %v", errorEncoding.Error()) } encoder.Close() - return fmt.Sprintf(`D%v, P%v, (%v)::%v`, node.Document, node.Path, value.Tag, buf.String()) + tag := value.Tag + if value.Kind == yaml.DocumentNode { + tag = "doc" + } + return fmt.Sprintf(`D%v, P%v, (%v)::%v`, node.Document, node.Path, tag, buf.String()) } func KindString(kind yaml.Kind) string { diff --git a/pkg/yqlib/treeops/operator_assign.go b/pkg/yqlib/treeops/operator_assign.go index 4c8222b5..4e37c916 100644 --- a/pkg/yqlib/treeops/operator_assign.go +++ b/pkg/yqlib/treeops/operator_assign.go @@ -3,14 +3,14 @@ package treeops import "container/list" func AssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } for el := lhs.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) if err != nil { return nil, err @@ -28,14 +28,14 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pa // does not update content or values func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } for el := lhs.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) if err != nil { return nil, err diff --git a/pkg/yqlib/treeops/operator_assign_test.go b/pkg/yqlib/treeops/operator_assign_test.go index 4ce1f0de..bbd8e59b 100644 --- a/pkg/yqlib/treeops/operator_assign_test.go +++ b/pkg/yqlib/treeops/operator_assign_test.go @@ -9,61 +9,70 @@ var assignOperatorScenarios = []expressionScenario{ document: `{a: {b: apple}}`, expression: `.a.b |= "frog"`, expected: []string{ - "D0, P[], (!!map)::{a: {b: frog}}\n", + "D0, P[], (doc)::{a: {b: frog}}\n", }, - }, { + }, + { document: `{a: {b: apple}}`, expression: `.a.b | (. |= "frog")`, expected: []string{ "D0, P[a b], (!!str)::frog\n", }, - }, { + }, + { document: `{a: {b: apple}}`, expression: `.a.b |= 5`, expected: []string{ - "D0, P[], (!!map)::{a: {b: 5}}\n", + "D0, P[], (doc)::{a: {b: 5}}\n", }, - }, { + }, + { document: `{a: {b: apple}}`, expression: `.a.b |= 3.142`, expected: []string{ - "D0, P[], (!!map)::{a: {b: 3.142}}\n", + "D0, P[], (doc)::{a: {b: 3.142}}\n", }, - }, { + }, + { document: `{a: {b: {g: foof}}}`, expression: `.a |= .b`, expected: []string{ - "D0, P[], (!!map)::{a: {g: foof}}\n", + "D0, P[], (doc)::{a: {g: foof}}\n", }, - }, { + }, + { document: `{a: {b: apple, c: cactus}}`, expression: `.a[] | select(. == "apple") |= "frog"`, expected: []string{ - "D0, P[], (!!map)::{a: {b: frog, c: cactus}}\n", + "D0, P[], (doc)::{a: {b: frog, c: cactus}}\n", }, - }, { + }, + { document: `[candy, apple, sandy]`, expression: `.[] | select(. == "*andy") |= "bogs"`, expected: []string{ - "D0, P[], (!!seq)::[bogs, apple, bogs]\n", + "D0, P[], (doc)::[bogs, apple, bogs]\n", }, - }, { + }, + { document: `{}`, expression: `.a.b |= "bogs"`, expected: []string{ - "D0, P[], (!!map)::{a: {b: bogs}}\n", + "D0, P[], (doc)::{a: {b: bogs}}\n", }, - }, { + }, + { document: `{}`, expression: `.a.b[0] |= "bogs"`, expected: []string{ - "D0, P[], (!!map)::{a: {b: [bogs]}}\n", + "D0, P[], (doc)::{a: {b: [bogs]}}\n", }, - }, { + }, + { document: `{}`, expression: `.a.b[1].c |= "bogs"`, expected: []string{ - "D0, P[], (!!map)::{a: {b: [null, {c: bogs}]}}\n", + "D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n", }, }, } diff --git a/pkg/yqlib/treeops/operator_booleans.go b/pkg/yqlib/treeops/operator_booleans.go index ceee7131..26b7b46b 100644 --- a/pkg/yqlib/treeops/operator_booleans.go +++ b/pkg/yqlib/treeops/operator_booleans.go @@ -7,8 +7,9 @@ import ( ) func isTruthy(c *CandidateNode) (bool, error) { - node := c.Node + node := UnwrapDoc(c.Node) value := true + if node.Tag == "!!null" { return false, nil } @@ -29,11 +30,11 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTre for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - lhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Lhs) + lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs) if err != nil { return nil, err } - rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) if err != nil { return nil, err } diff --git a/pkg/yqlib/treeops/operator_delete.go b/pkg/yqlib/treeops/operator_delete.go index 23adac20..c45a46b3 100644 --- a/pkg/yqlib/treeops/operator_delete.go +++ b/pkg/yqlib/treeops/operator_delete.go @@ -7,7 +7,7 @@ import ( ) func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } @@ -19,7 +19,7 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod candidate := el.Value.(*CandidateNode) elMap := list.New() elMap.PushBack(candidate) - nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs) + nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs) log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete)) if err != nil { return nil, err diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index a83ed052..18537004 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -11,12 +11,12 @@ import ( type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*list.List, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } - rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs) + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) if err != nil { return nil, err @@ -46,6 +46,9 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode * } func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + lhs.Node = UnwrapDoc(lhs.Node) + rhs.Node = UnwrapDoc(rhs.Node) + if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { var results = list.New() @@ -93,7 +96,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{Operation: rhsOp}} - _, err := d.getMatchingNodes(nodeToMap(lhs), assignmentOpNode) + _, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode) return err } diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index 943d9fde..319a35c2 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -11,43 +11,50 @@ var multiplyOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", }, - }, { + }, + { document: `{a: {also: me}, b: {also: [1]}}`, expression: `. * {"a":.b}`, expected: []string{ "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", }, - }, { + }, + { document: `{a: {also: me}, b: {also: {g: wizz}}}`, expression: `. * {"a":.b}`, expected: []string{ "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", }, - }, { + }, + { document: `{a: {also: {g: wizz}}, b: {also: me}}`, expression: `. * {"a":.b}`, expected: []string{ "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", }, - }, { + }, + { document: `{a: {also: {g: wizz}}, b: {also: [1]}}`, expression: `. * {"a":.b}`, expected: []string{ "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", }, - }, { + }, + { document: `{a: {also: [1]}, b: {also: {g: wizz}}}`, expression: `. * {"a":.b}`, expected: []string{ "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", }, - }, { + }, + { document: `{a: {things: great}, b: {also: me}}`, expression: `. * {"a":.b}`, expected: []string{ "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n", }, - }, { + }, + { document: `a: {things: great} b: also: "me" @@ -59,7 +66,8 @@ b: also: "me" `, }, - }, { + }, + { document: `{a: [1,2,3], b: [3,4,5]}`, expression: `. * {"a":.b}`, expected: []string{ diff --git a/pkg/yqlib/treeops/operator_not_test.go b/pkg/yqlib/treeops/operator_not_test.go index a91ffa32..f1e22535 100644 --- a/pkg/yqlib/treeops/operator_not_test.go +++ b/pkg/yqlib/treeops/operator_not_test.go @@ -5,27 +5,27 @@ import ( ) var notOperatorScenarios = []expressionScenario{ - { - document: `cat`, - expression: `. | not`, - expected: []string{ - "D0, P[], (!!bool)::false\n", - }, - }, - { - document: `1`, - expression: `. | not`, - expected: []string{ - "D0, P[], (!!bool)::false\n", - }, - }, - { - document: `0`, - expression: `. | not`, - expected: []string{ - "D0, P[], (!!bool)::false\n", - }, - }, + // { + // document: `cat`, + // expression: `. | not`, + // expected: []string{ + // "D0, P[], (!!bool)::false\n", + // }, + // }, + // { + // document: `1`, + // expression: `. | not`, + // expected: []string{ + // "D0, P[], (!!bool)::false\n", + // }, + // }, + // { + // document: `0`, + // expression: `. | not`, + // expected: []string{ + // "D0, P[], (!!bool)::false\n", + // }, + // }, { document: `~`, expression: `. | not`, @@ -33,20 +33,20 @@ var notOperatorScenarios = []expressionScenario{ "D0, P[], (!!bool)::true\n", }, }, - { - document: `false`, - expression: `. | not`, - expected: []string{ - "D0, P[], (!!bool)::true\n", - }, - }, - { - document: `true`, - expression: `. | not`, - expected: []string{ - "D0, P[], (!!bool)::false\n", - }, - }, + // { + // document: `false`, + // expression: `. | not`, + // expected: []string{ + // "D0, P[], (!!bool)::true\n", + // }, + // }, + // { + // document: `true`, + // expression: `. | not`, + // expected: []string{ + // "D0, P[], (!!bool)::false\n", + // }, + // }, } func TestNotOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_recursive_descent.go b/pkg/yqlib/treeops/operator_recursive_descent.go index 7d7511b3..68673fa5 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent.go +++ b/pkg/yqlib/treeops/operator_recursive_descent.go @@ -18,6 +18,10 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error { for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) + + candidate.Node = UnwrapDoc(candidate.Node) + + log.Debugf("Recursive Decent, added %v", NodeToString(candidate)) results.PushBack(candidate) children, err := Splat(d, nodeToMap(candidate)) diff --git a/pkg/yqlib/treeops/operator_recursive_descent_test.go b/pkg/yqlib/treeops/operator_recursive_descent_test.go index 0f9e595e..d0a654d0 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent_test.go +++ b/pkg/yqlib/treeops/operator_recursive_descent_test.go @@ -11,14 +11,16 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!str)::cat\n", }, - }, { + }, + { document: `{a: frog}`, expression: `..`, expected: []string{ "D0, P[], (!!map)::{a: frog}\n", "D0, P[a], (!!str)::frog\n", }, - }, { + }, + { document: `{a: {b: apple}}`, expression: `..`, expected: []string{ @@ -26,7 +28,8 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ "D0, P[a], (!!map)::{b: apple}\n", "D0, P[a b], (!!str)::apple\n", }, - }, { + }, + { document: `[1,2,3]`, expression: `..`, expected: []string{ @@ -35,7 +38,8 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ "D0, P[1], (!!int)::2\n", "D0, P[2], (!!int)::3\n", }, - }, { + }, + { document: `[{a: cat},2,true]`, expression: `..`, expected: []string{ diff --git a/pkg/yqlib/treeops/operator_select.go b/pkg/yqlib/treeops/operator_select.go index 704987fc..f0e57f2b 100644 --- a/pkg/yqlib/treeops/operator_select.go +++ b/pkg/yqlib/treeops/operator_select.go @@ -12,7 +12,7 @@ func SelectOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pa for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) if err != nil { return nil, err diff --git a/pkg/yqlib/treeops/operator_union.go b/pkg/yqlib/treeops/operator_union.go index 8c100ba2..9c50d434 100644 --- a/pkg/yqlib/treeops/operator_union.go +++ b/pkg/yqlib/treeops/operator_union.go @@ -3,11 +3,11 @@ package treeops import "container/list" func UnionOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } - rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs) + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) if err != nil { return nil, err } diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index c584f9d2..b035b1f6 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -9,12 +9,19 @@ import ( type OperatorHandler func(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) +func UnwrapDoc(node *yaml.Node) *yaml.Node { + if node.Kind == yaml.DocumentNode { + return node.Content[0] + } + return node +} + func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } - return d.getMatchingNodes(lhs, pathNode.Rhs) + return d.GetMatchingNodes(lhs, pathNode.Rhs) } func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode { diff --git a/pkg/yqlib/treeops/operators_test.go b/pkg/yqlib/treeops/operators_test.go index 6118f774..57a4c4b5 100644 --- a/pkg/yqlib/treeops/operators_test.go +++ b/pkg/yqlib/treeops/operators_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/mikefarah/yq/v3/test" + "github.com/mikefarah/yq/v4/test" ) type expressionScenario struct { diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/treeops/path_parse_test.go index d5598c8d..ed12524b 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/mikefarah/yq/v3/test" + "github.com/mikefarah/yq/v4/test" ) var pathTests = []struct { diff --git a/yq.go b/yq.go index b409fab5..f95d4dea 100644 --- a/yq.go +++ b/yq.go @@ -3,7 +3,7 @@ package main import ( "os" - command "github.com/mikefarah/yq/v3/cmd" + command "github.com/mikefarah/yq/v4/cmd" ) func main() { From 41c08891d32c070fb7ad33eae4fec6a99f555fce Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 28 Oct 2020 11:34:01 +1100 Subject: [PATCH 053/129] create object fixes --- pkg/yqlib/treeops/candidate_node.go | 10 ++++++---- pkg/yqlib/treeops/operator_collect_object.go | 4 +++- ..._object_test.go => operator_collect_object_test.go} | 9 +++++++++ pkg/yqlib/treeops/operator_create_map_test.go | 8 ++++++++ pkg/yqlib/treeops/operator_multilpy.go | 2 ++ pkg/yqlib/treeops/operator_multiply_test.go | 2 +- pkg/yqlib/treeops/operator_traverse_path.go | 4 ++-- 7 files changed, 31 insertions(+), 8 deletions(-) rename pkg/yqlib/treeops/{operation_collection_object_test.go => operator_collect_object_test.go} (83%) diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index f1ed656d..39336b8c 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -42,10 +42,12 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { } n.Node.Kind = other.Node.Kind n.Node.Tag = other.Node.Tag - // not sure if this ever should happen here... - // if other.Node.Style != 0 { - // n.Node.Style = other.Node.Style - // } + + // merge will pickup the style of the new thing + // when autocreating nodes + if n.Node.Style == 0 { + n.Node.Style = other.Node.Style + } n.Node.FootComment = other.Node.FootComment n.Node.HeadComment = other.Node.HeadComment n.Node.LineComment = other.Node.LineComment diff --git a/pkg/yqlib/treeops/operator_collect_object.go b/pkg/yqlib/treeops/operator_collect_object.go index 4cfee960..e46971c2 100644 --- a/pkg/yqlib/treeops/operator_collect_object.go +++ b/pkg/yqlib/treeops/operator_collect_object.go @@ -44,8 +44,10 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list. splatCandidate := splatEl.Value.(*CandidateNode) newCandidate := aggCandidate.Copy() newCandidate.Path = nil + splatCandidateClone := splatCandidate.Copy() + splatCandidateClone.Path = nil - newCandidate, err := multiply(d, newCandidate, splatCandidate) + newCandidate, err := multiply(d, newCandidate, splatCandidateClone) if err != nil { return nil, err } diff --git a/pkg/yqlib/treeops/operation_collection_object_test.go b/pkg/yqlib/treeops/operator_collect_object_test.go similarity index 83% rename from pkg/yqlib/treeops/operation_collection_object_test.go rename to pkg/yqlib/treeops/operator_collect_object_test.go index 62333ee7..bd90ac11 100644 --- a/pkg/yqlib/treeops/operation_collection_object_test.go +++ b/pkg/yqlib/treeops/operator_collect_object_test.go @@ -30,6 +30,15 @@ var collectObjectOperatorScenarios = []expressionScenario{ "D0, P[], (!!map)::Mike: dog\nf: burger\n", }, }, + { + document: `{name: Mike, pets: {cows: [apl, bba]}}`, + expression: `{"a":.name, "b":.pets}`, + expected: []string{ + `D0, P[], (!!map)::a: Mike +b: {cows: [apl, bba]} +`, + }, + }, } func TestCollectObjectOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_create_map_test.go b/pkg/yqlib/treeops/operator_create_map_test.go index af0bd223..65839b59 100644 --- a/pkg/yqlib/treeops/operator_create_map_test.go +++ b/pkg/yqlib/treeops/operator_create_map_test.go @@ -27,6 +27,14 @@ var createMapOperatorScenarios = []expressionScenario{ "D0, P[], (!!seq)::- f: hotdog\n- f: burger\n", }, }, + { + document: `{name: Mike, pets: {cows: [apl, bba]}}`, + expression: `"a":.name, "b":.pets`, + expected: []string{ + "D0, P[], (!!seq)::- a: Mike\n", + "D0, P[], (!!seq)::- b: {cows: [apl, bba]}\n", + }, + }, } func TestCreateMapOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index 18537004..6224e7c0 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -48,6 +48,8 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode * func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { lhs.Node = UnwrapDoc(lhs.Node) rhs.Node = UnwrapDoc(rhs.Node) + log.Debugf("Multipling LHS: %v", NodeToString(lhs)) + log.Debugf("- RHS: %v", NodeToString(rhs)) if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index 319a35c2..b2cc2b40 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -61,7 +61,7 @@ b: `, expression: `. * {"a":.b}`, expected: []string{ - `D0, P[], (!!map)::a: {things: great, also: me} + `D0, P[], (!!map)::a: {things: great, also: "me"} b: also: "me" `, diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index 2dba41dd..583fab43 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -37,7 +37,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Opera log.Debug("Traversing %v", NodeToString(matchingNode)) value := matchingNode.Node - if value.Tag == "!!null" { + if value.Tag == "!!null" && pathNode.Value != "[]" { log.Debugf("Guessing kind") // we must ahve added this automatically, lets guess what it should be now switch pathNode.Value.(type) { @@ -45,7 +45,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Opera log.Debugf("probably an array") value.Kind = yaml.SequenceNode default: - log.Debugf("probabel a map") + log.Debugf("probably a map") value.Kind = yaml.MappingNode } value.Tag = "" From 4edb3e9021c6c81be5b80fccc2b7a8a3f5749070 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 28 Oct 2020 13:00:26 +1100 Subject: [PATCH 054/129] create object fixes --- pkg/yqlib/treeops/operator_collect_object.go | 9 +++-- .../treeops/operator_collect_object_test.go | 29 ++++++++++++-- pkg/yqlib/treeops/operator_create_map.go | 8 ++-- pkg/yqlib/treeops/operator_create_map_test.go | 7 ++++ pkg/yqlib/treeops/operator_multilpy.go | 40 +++++++++++++------ pkg/yqlib/treeops/operator_multiply_test.go | 7 ++++ 6 files changed, 78 insertions(+), 22 deletions(-) diff --git a/pkg/yqlib/treeops/operator_collect_object.go b/pkg/yqlib/treeops/operator_collect_object.go index e46971c2..eea0db8b 100644 --- a/pkg/yqlib/treeops/operator_collect_object.go +++ b/pkg/yqlib/treeops/operator_collect_object.go @@ -28,6 +28,11 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list. candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode) splatted, err := Splat(d, nodeToMap(candidate)) + + for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() { + splatEl.Value.(*CandidateNode).Path = nil + } + if err != nil { return nil, err } @@ -44,10 +49,8 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list. splatCandidate := splatEl.Value.(*CandidateNode) newCandidate := aggCandidate.Copy() newCandidate.Path = nil - splatCandidateClone := splatCandidate.Copy() - splatCandidateClone.Path = nil - newCandidate, err := multiply(d, newCandidate, splatCandidateClone) + newCandidate, err := multiply(d, newCandidate, splatCandidate) if err != nil { return nil, err } diff --git a/pkg/yqlib/treeops/operator_collect_object_test.go b/pkg/yqlib/treeops/operator_collect_object_test.go index bd90ac11..a573a05f 100644 --- a/pkg/yqlib/treeops/operator_collect_object_test.go +++ b/pkg/yqlib/treeops/operator_collect_object_test.go @@ -9,15 +9,15 @@ var collectObjectOperatorScenarios = []expressionScenario{ document: `{name: Mike, age: 32}`, expression: `{.name: .age}`, expected: []string{ - "D0, P[0], (!!map)::Mike: 32\n", + "D0, P[], (!!map)::Mike: 32\n", }, }, { document: `{name: Mike, pets: [cat, dog]}`, expression: `{.name: .pets[]}`, expected: []string{ - "D0, P[0], (!!map)::Mike: cat\n", - "D0, P[1], (!!map)::Mike: dog\n", + "D0, P[], (!!map)::Mike: cat\n", + "D0, P[], (!!map)::Mike: dog\n", }, }, { @@ -36,6 +36,29 @@ var collectObjectOperatorScenarios = []expressionScenario{ expected: []string{ `D0, P[], (!!map)::a: Mike b: {cows: [apl, bba]} +`, + }, + }, + { + document: ``, + expression: `{"wrap": "frog"}`, + expected: []string{ + "D0, P[], (!!map)::wrap: frog\n", + }, + }, + { + document: `{name: Mike}`, + expression: `{"wrap": .}`, + expected: []string{ + "D0, P[], (!!map)::wrap: {name: Mike}\n", + }, + }, + { + document: `{name: Mike}`, + expression: `{"wrap": {"further": .}}`, + expected: []string{ + `D0, P[], (!!map)::wrap: + further: {name: Mike} `, }, }, diff --git a/pkg/yqlib/treeops/operator_create_map.go b/pkg/yqlib/treeops/operator_create_map.go index eaea6725..5abfdbd8 100644 --- a/pkg/yqlib/treeops/operator_create_map.go +++ b/pkg/yqlib/treeops/operator_create_map.go @@ -19,11 +19,11 @@ func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode mapPairs, err := crossFunction(d, matchingNodes, pathNode, func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} - log.Debugf("LHS:", lhs.Node.Value) - log.Debugf("RHS:", rhs.Node.Value) + log.Debugf("LHS:", NodeToString(lhs)) + log.Debugf("RHS:", NodeToString(rhs)) node.Content = []*yaml.Node{ - lhs.Node, - rhs.Node, + UnwrapDoc(lhs.Node), + UnwrapDoc(rhs.Node), } return &CandidateNode{Node: &node, Document: document, Path: path}, nil diff --git a/pkg/yqlib/treeops/operator_create_map_test.go b/pkg/yqlib/treeops/operator_create_map_test.go index 65839b59..dee6e360 100644 --- a/pkg/yqlib/treeops/operator_create_map_test.go +++ b/pkg/yqlib/treeops/operator_create_map_test.go @@ -35,6 +35,13 @@ var createMapOperatorScenarios = []expressionScenario{ "D0, P[], (!!seq)::- b: {cows: [apl, bba]}\n", }, }, + { + document: `{name: Mike}`, + expression: `"wrap": .`, + expected: []string{ + "D0, P[], (!!seq)::- wrap: {name: Mike}\n", + }, + }, } func TestCreateMapOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index 6224e7c0..1dd42173 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -53,25 +53,41 @@ func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*Ca if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { - var results = list.New() - recursiveDecent(d, results, nodeToMap(rhs)) - var pathIndexToStartFrom int = 0 - if results.Front() != nil { - pathIndexToStartFrom = len(results.Front().Value.(*CandidateNode).Path) + var newBlank = &CandidateNode{ + Path: lhs.Path, + Document: lhs.Document, + Filename: lhs.Filename, + Node: &yaml.Node{}, } + var newThing, err = mergeObjects(d, newBlank, lhs) + if err != nil { + return nil, err + } + return mergeObjects(d, newThing, rhs) - for el := results.Front(); el != nil; el = el.Next() { - err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode)) - if err != nil { - return nil, err - } - } - return lhs, nil } return nil, fmt.Errorf("Cannot multiply %v with %v", NodeToString(lhs), NodeToString(rhs)) } +func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + var results = list.New() + recursiveDecent(d, results, nodeToMap(rhs)) + + var pathIndexToStartFrom int = 0 + if results.Front() != nil { + pathIndexToStartFrom = len(results.Front().Value.(*CandidateNode).Path) + } + + for el := results.Front(); el != nil; el = el.Next() { + err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode)) + if err != nil { + return nil, err + } + } + return lhs, nil +} + func createTraversalTree(path []interface{}) *PathTreeNode { if len(path) == 0 { return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}} diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index b2cc2b40..dfad3b80 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -74,6 +74,13 @@ b: "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", }, }, + { + document: `{a: cat}`, + expression: `. * {"a": {"c": .a}}`, + expected: []string{ + "D0, P[], (!!map)::{a: {c: cat}}\n", + }, + }, } func TestMultiplyOperatorScenarios(t *testing.T) { From 643f2467eeb8736bb7e4df5bcb9416be07ab337a Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 30 Oct 2020 10:56:45 +1100 Subject: [PATCH 055/129] simple anchors --- pkg/yqlib/treeops/candidate_node.go | 1 + pkg/yqlib/treeops/lib.go | 5 +- pkg/yqlib/treeops/operator_multilpy.go | 2 +- pkg/yqlib/treeops/operator_multiply_test.go | 14 ++++++ .../treeops/operator_recursive_descent.go | 19 +++++--- .../operator_recursive_descent_test.go | 10 ++++ pkg/yqlib/treeops/operator_traverse_path.go | 48 ++++++++++--------- .../treeops/operator_traverse_path_test.go | 21 ++++++++ 8 files changed, 87 insertions(+), 33 deletions(-) diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index 39336b8c..c8d40eea 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -31,6 +31,7 @@ func (n *CandidateNode) UpdateFrom(other *CandidateNode) { n.UpdateAttributesFrom(other) n.Node.Content = other.Node.Content n.Node.Value = other.Node.Value + n.Node.Alias = other.Node.Alias } func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 5efdaf4f..35fbdbc1 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -49,8 +49,6 @@ var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Handler: DeleteChildOperator} -// var Splat = &OperationType{Type: "SPLAT", NumArgs: 0, Precedence: 40, Handler: SplatOperator} - // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} // filters matches if they have the existing path @@ -59,6 +57,7 @@ type Operation struct { Value interface{} StringValue string CandidateNode *CandidateNode // used for Value Path elements + Preferences interface{} } func CreateValueOperation(value interface{}, stringValue string) *Operation { @@ -132,6 +131,8 @@ func NodeToString(node *CandidateNode) string { tag := value.Tag if value.Kind == yaml.DocumentNode { tag = "doc" + } else if value.Kind == yaml.AliasNode { + tag = "alias" } return fmt.Sprintf(`D%v, P%v, (%v)::%v`, node.Document, node.Path, tag, buf.String()) } diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index 1dd42173..3aa2feb1 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -107,7 +107,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid lhsPath := rhs.Path[pathIndexToStartFrom:] assignmentOp := &Operation{OperationType: AssignAttributes} - if rhs.Node.Kind == yaml.ScalarNode { + if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { assignmentOp.OperationType = Assign } rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index dfad3b80..65c5af22 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -81,6 +81,20 @@ b: "D0, P[], (!!map)::{a: {c: cat}}\n", }, }, + { + document: `{a: &cat {c: frog}, b: {f: *cat}, c: {g: thongs}}`, + expression: `.c * .b`, + expected: []string{ + "D0, P[c], (!!map)::{g: thongs, f: *cat}\n", + }, + }, + { + document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`, + expression: `.c * .a`, + expected: []string{ + "D0, P[c], (!!map)::{g: thongs, c: frog}\n", + }, + }, } func TestMultiplyOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_recursive_descent.go b/pkg/yqlib/treeops/operator_recursive_descent.go index 68673fa5..5a364db6 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent.go +++ b/pkg/yqlib/treeops/operator_recursive_descent.go @@ -2,6 +2,8 @@ package treeops import ( "container/list" + + "gopkg.in/yaml.v3" ) func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { @@ -24,14 +26,17 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li log.Debugf("Recursive Decent, added %v", NodeToString(candidate)) results.PushBack(candidate) - children, err := Splat(d, nodeToMap(candidate)) + if candidate.Node.Kind != yaml.AliasNode { - if err != nil { - return err - } - err = recursiveDecent(d, results, children) - if err != nil { - return err + children, err := Splat(d, nodeToMap(candidate)) + + if err != nil { + return err + } + err = recursiveDecent(d, results, children) + if err != nil { + return err + } } } return nil diff --git a/pkg/yqlib/treeops/operator_recursive_descent_test.go b/pkg/yqlib/treeops/operator_recursive_descent_test.go index d0a654d0..0079efea 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent_test.go +++ b/pkg/yqlib/treeops/operator_recursive_descent_test.go @@ -50,6 +50,16 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ "D0, P[2], (!!bool)::true\n", }, }, + { + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `..`, + expected: []string{ + "D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n", + "D0, P[a], (!!map)::&cat {c: frog}\n", + "D0, P[a c], (!!str)::frog\n", + "D0, P[b], (alias)::*cat\n", + }, + }, } func TestRecursiveDescentOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index 583fab43..1bfa14a0 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -8,8 +8,13 @@ import ( "gopkg.in/yaml.v3" ) +type TraversePreferences struct { + DontFollowAlias bool +} + func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) { - splatOperation := &Operation{OperationType: TraversePath, Value: "[]"} + preferences := &TraversePreferences{DontFollowAlias: true} + splatOperation := &Operation{OperationType: TraversePath, Value: "[]", Preferences: preferences} splatTreeNode := &PathTreeNode{Operation: splatOperation} return TraversePathOperator(d, matches, splatTreeNode) } @@ -33,14 +38,20 @@ func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *P return matchingNodeMap, nil } -func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Operation) ([]*CandidateNode, error) { +func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) ([]*CandidateNode, error) { log.Debug("Traversing %v", NodeToString(matchingNode)) value := matchingNode.Node - if value.Tag == "!!null" && pathNode.Value != "[]" { + followAlias := true + + // if operation.Preferences != nil { + // followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias + // } + + if value.Tag == "!!null" && operation.Value != "[]" { log.Debugf("Guessing kind") // we must ahve added this automatically, lets guess what it should be now - switch pathNode.Value.(type) { + switch operation.Value.(type) { case int, int64: log.Debugf("probably an array") value.Kind = yaml.SequenceNode @@ -54,33 +65,24 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Opera switch value.Kind { case yaml.MappingNode: log.Debug("its a map with %v entries", len(value.Content)/2) - return traverseMap(matchingNode, pathNode) + return traverseMap(matchingNode, operation) case yaml.SequenceNode: log.Debug("its a sequence of %v things!", len(value.Content)) - return traverseArray(matchingNode, pathNode) - // default: + return traverseArray(matchingNode, operation) - // if head == "+" { - // return n.appendArray(value, head, tail, pathStack) - // } else if len(value.Content) == 0 && head == "**" { - // return n.navigationStrategy.Visit(nodeContext) - // } - // return n.splatArray(value, head, tail, pathStack) - // } - // case yaml.AliasNode: - // log.Debug("its an alias!") - // DebugNode(value.Alias) - // if n.navigationStrategy.FollowAlias(nodeContext) { - // log.Debug("following the alias") - // return n.recurse(value.Alias, head, tail, pathStack) - // } - // return nil + case yaml.AliasNode: + log.Debug("its an alias!") + if followAlias { + matchingNode.Node = matchingNode.Node.Alias + return traverse(d, matchingNode, operation) + } + return []*CandidateNode{matchingNode}, nil case yaml.DocumentNode: log.Debug("digging into doc node") return traverse(d, &CandidateNode{ Node: matchingNode.Node.Content[0], - Document: matchingNode.Document}, pathNode) + Document: matchingNode.Document}, operation) default: return nil, nil } diff --git a/pkg/yqlib/treeops/operator_traverse_path_test.go b/pkg/yqlib/treeops/operator_traverse_path_test.go index d5ecbc20..8b58573a 100644 --- a/pkg/yqlib/treeops/operator_traverse_path_test.go +++ b/pkg/yqlib/treeops/operator_traverse_path_test.go @@ -83,6 +83,27 @@ var traversePathOperatorScenarios = []expressionScenario{ "D0, P[a mad], (!!str)::things\n", }, }, + { + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `.b`, + expected: []string{ + "D0, P[b], (alias)::*cat\n", + }, + }, + { + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `.b.[]`, + expected: []string{ + "D0, P[b c], (!!str)::frog\n", + }, + }, + { + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `.b.c`, + expected: []string{ + "D0, P[b c], (!!str)::frog\n", + }, + }, } func TestTraversePathOperatorScenarios(t *testing.T) { From 461c3e719c299fe98550886f47e8b9c0b8a34967 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 30 Oct 2020 12:00:48 +1100 Subject: [PATCH 056/129] merge anchors! --- examples/merge-anchor.yaml | 20 ++-- go.mod | 4 +- pkg/yqlib/treeops/candidate_node.go | 2 +- pkg/yqlib/treeops/operator_traverse_path.go | 84 ++++++++++---- .../treeops/operator_traverse_path_test.go | 104 ++++++++++++++++++ 5 files changed, 178 insertions(+), 36 deletions(-) diff --git a/examples/merge-anchor.yaml b/examples/merge-anchor.yaml index 6d3f426d..61041bd5 100644 --- a/examples/merge-anchor.yaml +++ b/examples/merge-anchor.yaml @@ -1,19 +1,19 @@ foo: &foo - a: original - thing: coolasdf - thirsty: yep + a: foo_a + thing: foo_thing + c: foo_c bar: &bar - b: 2 - thing: coconut - c: oldbar + b: bar_b + thing: bar_thing + c: bar_c foobarList: + b: foobarList_b <<: [*foo,*bar] - c: newbar + c: foobarList_c foobar: + c: foobar_c <<: *foo - thirty: well beyond - thing: ice - c: 3 + thing: foobar_thing \ No newline at end of file diff --git a/go.mod b/go.mod index 11dab62f..d0f3f311 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/mikefarah/yq/v4 require ( - github.com/elliotchance/orderedmap v1.3.0 // indirect + github.com/elliotchance/orderedmap v1.3.0 github.com/fatih/color v1.9.0 github.com/goccy/go-yaml v1.8.1 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.7 // indirect - github.com/pkg/errors v0.9.1 + github.com/pkg/errors v0.9.1 // indirect github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 // indirect github.com/timtadh/data-structures v0.5.3 // indirect diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index c8d40eea..74096f58 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -17,7 +17,7 @@ type CandidateNode struct { } func (n *CandidateNode) GetKey() string { - return fmt.Sprintf("%v - %v - %v", n.Document, n.Path, n.Node.Value) + return fmt.Sprintf("%v - %v", n.Document, n.Path) } func (n *CandidateNode) Copy() *CandidateNode { diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index 1bfa14a0..a2bcc1de 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -5,6 +5,8 @@ import ( "container/list" + "github.com/elliotchance/orderedmap" + "gopkg.in/yaml.v3" ) @@ -65,7 +67,34 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper switch value.Kind { case yaml.MappingNode: log.Debug("its a map with %v entries", len(value.Content)/2) - return traverseMap(matchingNode, operation) + var newMatches = orderedmap.NewOrderedMap() + err := traverseMap(newMatches, matchingNode, operation) + + if err != nil { + return nil, err + } + + if newMatches.Len() == 0 { + //no matches, create one automagically + valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"} + node := matchingNode.Node + node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: operation.StringValue}, valueNode) + candidateNode := &CandidateNode{ + Node: valueNode, + Path: append(matchingNode.Path, operation.StringValue), + Document: matchingNode.Document, + } + newMatches.Set(candidateNode.GetKey(), candidateNode) + + } + + arrayMatches := make([]*CandidateNode, newMatches.Len()) + i := 0 + for el := newMatches.Front(); el != nil; el = el.Next() { + arrayMatches[i] = el.Value.(*CandidateNode) + i++ + } + return arrayMatches, nil case yaml.SequenceNode: log.Debug("its a sequence of %v things!", len(value.Content)) @@ -92,15 +121,13 @@ func keyMatches(key *yaml.Node, pathNode *Operation) bool { return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue) } -func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNode, error) { +func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, operation *Operation) error { // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. // merge aliases are defined first, but we only want to traverse them // if we don't find a match directly on this node first. //TODO ALIASES, auto creation? - var newMatches = make([]*CandidateNode, 0) - node := candidate.Node var contents = node.Content @@ -109,33 +136,44 @@ func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNod value := contents[index+1] log.Debug("checking %v (%v)", key.Value, key.Tag) - if keyMatches(key, pathNode) { + //skip the 'merge' tag, find a direct match first + if key.Tag == "!!merge" { + log.Debug("Merge anchor") + traverseMergeAnchor(newMatches, candidate, value, operation) + } else if keyMatches(key, operation) { log.Debug("MATCHED") - newMatches = append(newMatches, &CandidateNode{ + candidateNode := &CandidateNode{ Node: value, Path: append(candidate.Path, key.Value), Document: candidate.Document, - }) + } + newMatches.Set(candidateNode.GetKey(), candidateNode) } } - if len(newMatches) == 0 { - //no matches, create one automagically - valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"} - node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode) - newMatches = append(newMatches, &CandidateNode{ - Node: valueNode, - Path: append(candidate.Path, pathNode.StringValue), - Document: candidate.Document, - }) - } - - return newMatches, nil + return nil } -func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNode, error) { - log.Debug("pathNode Value %v", pathNode.Value) - if pathNode.Value == "[]" { +func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) { + switch value.Kind { + case yaml.AliasNode: + candidateNode := &CandidateNode{ + Node: value.Alias, + Path: originalCandidate.Path, + Document: originalCandidate.Document, + } + traverseMap(newMatches, candidateNode, operation) + case yaml.SequenceNode: + for _, childValue := range value.Content { + traverseMergeAnchor(newMatches, originalCandidate, childValue, operation) + } + } + return +} + +func traverseArray(candidate *CandidateNode, operation *Operation) ([]*CandidateNode, error) { + log.Debug("operation Value %v", operation.Value) + if operation.Value == "[]" { var contents = candidate.Node.Content var newMatches = make([]*CandidateNode, len(contents)) @@ -151,7 +189,7 @@ func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateN } - index := pathNode.Value.(int64) + index := operation.Value.(int64) indexToUse := index contentLength := int64(len(candidate.Node.Content)) for contentLength <= index { diff --git a/pkg/yqlib/treeops/operator_traverse_path_test.go b/pkg/yqlib/treeops/operator_traverse_path_test.go index 8b58573a..c5bbd94d 100644 --- a/pkg/yqlib/treeops/operator_traverse_path_test.go +++ b/pkg/yqlib/treeops/operator_traverse_path_test.go @@ -4,6 +4,28 @@ import ( "testing" ) +var mergeDocSample = ` +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 + <<: [*foo,*bar] + c: foobarList_c + +foobar: + c: foobar_c + <<: *foo + thing: foobar_thing +` + var traversePathOperatorScenarios = []expressionScenario{ { document: `{a: {b: apple}}`, @@ -104,6 +126,88 @@ var traversePathOperatorScenarios = []expressionScenario{ "D0, P[b c], (!!str)::frog\n", }, }, + { + document: mergeDocSample, + expression: `.foobar`, + expected: []string{ + "D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobar.a`, + expected: []string{ + "D0, P[foobar a], (!!str)::foo_a\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobar.c`, + expected: []string{ + "D0, P[foobar c], (!!str)::foo_c\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobar.thing`, + expected: []string{ + "D0, P[foobar thing], (!!str)::foobar_thing\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobar.[]`, + expected: []string{ + "D0, P[foobar c], (!!str)::foo_c\n", + "D0, P[foobar a], (!!str)::foo_a\n", + "D0, P[foobar thing], (!!str)::foobar_thing\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobarList`, + expected: []string{ + "D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobarList.a`, + expected: []string{ + "D0, P[foobarList a], (!!str)::foo_a\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobarList.thing`, + expected: []string{ + "D0, P[foobarList thing], (!!str)::bar_thing\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobarList.c`, + expected: []string{ + "D0, P[foobarList c], (!!str)::foobarList_c\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobarList.b`, + expected: []string{ + "D0, P[foobarList b], (!!str)::bar_b\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobarList.[]`, + expected: []string{ + "D0, P[foobarList b], (!!str)::bar_b\n", + "D0, P[foobarList a], (!!str)::foo_a\n", + "D0, P[foobarList thing], (!!str)::bar_thing\n", + "D0, P[foobarList c], (!!str)::foobarList_c\n", + }, + }, } func TestTraversePathOperatorScenarios(t *testing.T) { From b63b9644aa88338bf401fed56eb957cff0a7abb5 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 30 Oct 2020 12:40:44 +1100 Subject: [PATCH 057/129] multiply merge anchors --- pkg/yqlib/treeops/candidate_node.go | 6 ++--- pkg/yqlib/treeops/lib.go | 11 ++++++++++ pkg/yqlib/treeops/operator_multiply_test.go | 7 ++++++ .../operator_recursive_descent_test.go | 22 +++++++++++++++++++ pkg/yqlib/treeops/operator_traverse_path.go | 21 ++++++++---------- 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index 74096f58..61843a99 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -49,9 +49,9 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { if n.Node.Style == 0 { n.Node.Style = other.Node.Style } - n.Node.FootComment = other.Node.FootComment - n.Node.HeadComment = other.Node.HeadComment - n.Node.LineComment = other.Node.LineComment + n.Node.FootComment = n.Node.FootComment + other.Node.FootComment + n.Node.HeadComment = n.Node.HeadComment + other.Node.HeadComment + n.Node.LineComment = n.Node.LineComment + other.Node.LineComment } func (n *CandidateNode) PathStackToString() string { diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 35fbdbc1..4f9e684f 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -18,6 +18,17 @@ type OperationType struct { Handler OperatorHandler } +// operators TODO: +// - stripComments (recursive) +// - mergeAppend (merges and appends arrays) +// - mergeIfEmpty (sets only if the document is empty, do I do that now?) +// - updateStyle +// - updateTag +// - explodeAnchors +// - compare ?? +// - validate ?? +// - exists ?? + var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator} var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator} diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index 65c5af22..fbcd7f90 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -95,6 +95,13 @@ b: "D0, P[c], (!!map)::{g: thongs, c: frog}\n", }, }, + { + document: mergeDocSample, + expression: `.foobar * .foobarList`, + expected: []string{ + "D0, P[foobar], (!!map)::c: foobarList_c\n<<: [*foo, *bar]\nthing: foobar_thing\nb: foobarList_b\n", + }, + }, } func TestMultiplyOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_recursive_descent_test.go b/pkg/yqlib/treeops/operator_recursive_descent_test.go index 0079efea..7ced6738 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent_test.go +++ b/pkg/yqlib/treeops/operator_recursive_descent_test.go @@ -60,6 +60,28 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ "D0, P[b], (alias)::*cat\n", }, }, + { + document: mergeDocSample, + expression: `.foobar | ..`, + expected: []string{ + "D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n", + "D0, P[foobar c], (!!str)::foobar_c\n", + "D0, P[foobar <<], (alias)::*foo\n", + "D0, P[foobar thing], (!!str)::foobar_thing\n", + }, + }, + { + document: mergeDocSample, + expression: `.foobarList | ..`, + expected: []string{ + "D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n", + "D0, P[foobarList b], (!!str)::foobarList_b\n", + "D0, P[foobarList <<], (!!seq)::[*foo, *bar]\n", + "D0, P[foobarList << 0], (alias)::*foo\n", + "D0, P[foobarList << 1], (alias)::*bar\n", + "D0, P[foobarList c], (!!str)::foobarList_c\n", + }, + }, } func TestRecursiveDescentOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index a2bcc1de..3b12e9d3 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -44,12 +44,6 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper log.Debug("Traversing %v", NodeToString(matchingNode)) value := matchingNode.Node - followAlias := true - - // if operation.Preferences != nil { - // followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias - // } - if value.Tag == "!!null" && operation.Value != "[]" { log.Debugf("Guessing kind") // we must ahve added this automatically, lets guess what it should be now @@ -102,11 +96,8 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper case yaml.AliasNode: log.Debug("its an alias!") - if followAlias { - matchingNode.Node = matchingNode.Node.Alias - return traverse(d, matchingNode, operation) - } - return []*CandidateNode{matchingNode}, nil + matchingNode.Node = matchingNode.Node.Alias + return traverse(d, matchingNode, operation) case yaml.DocumentNode: log.Debug("digging into doc node") return traverse(d, &CandidateNode{ @@ -130,6 +121,12 @@ func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, op node := candidate.Node + followAlias := true + + if operation.Preferences != nil { + followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias + } + var contents = node.Content for index := 0; index < len(contents); index = index + 2 { key := contents[index] @@ -137,7 +134,7 @@ func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, op log.Debug("checking %v (%v)", key.Value, key.Tag) //skip the 'merge' tag, find a direct match first - if key.Tag == "!!merge" { + if key.Tag == "!!merge" && followAlias { log.Debug("Merge anchor") traverseMergeAnchor(newMatches, candidate, value, operation) } else if keyMatches(key, operation) { From e515b8c2db6988c024942faaeb8d3368e0605452 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 2 Nov 2020 11:20:38 +1100 Subject: [PATCH 058/129] got style --- cmd/utils.go | 98 --------------- pkg/yqlib/treeops/lib.go | 20 +++- ...or_assign.go => operator_assign_update.go} | 2 +- ...test.go => operator_assign_update_test.go} | 0 pkg/yqlib/treeops/operator_explode.go | 112 ++++++++++++++++++ pkg/yqlib/treeops/operator_explode_test.go | 35 ++++++ pkg/yqlib/treeops/operatory_style.go | 77 ++++++++++++ pkg/yqlib/treeops/operatory_style_test.go | 45 +++++++ pkg/yqlib/treeops/path_parse_test.go | 20 ++++ pkg/yqlib/treeops/path_tokeniser.go | 7 +- 10 files changed, 310 insertions(+), 106 deletions(-) rename pkg/yqlib/treeops/{operator_assign.go => operator_assign_update.go} (90%) rename pkg/yqlib/treeops/{operator_assign_test.go => operator_assign_update_test.go} (100%) create mode 100644 pkg/yqlib/treeops/operator_explode.go create mode 100644 pkg/yqlib/treeops/operator_explode_test.go create mode 100644 pkg/yqlib/treeops/operatory_style.go create mode 100644 pkg/yqlib/treeops/operatory_style_test.go diff --git a/cmd/utils.go b/cmd/utils.go index 920adbad..2b24c231 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -121,104 +121,6 @@ func writeString(writer io.Writer, txt string) error { return errorWriting } -func setIfNotThere(node *yaml.Node, key string, value *yaml.Node) { - for index := 0; index < len(node.Content); index = index + 2 { - keyNode := node.Content[index] - if keyNode.Value == key { - return - } - } - // need to add it to the map - mapEntryKey := yaml.Node{Value: key, Kind: yaml.ScalarNode} - node.Content = append(node.Content, &mapEntryKey) - node.Content = append(node.Content, value) -} - -func applyAlias(node *yaml.Node, alias *yaml.Node) { - if alias == nil { - return - } - for index := 0; index < len(alias.Content); index = index + 2 { - keyNode := alias.Content[index] - log.Debugf("applying alias key %v", keyNode.Value) - valueNode := alias.Content[index+1] - setIfNotThere(node, keyNode.Value, valueNode) - } -} - -func explodeNode(node *yaml.Node) error { - node.Anchor = "" - switch node.Kind { - case yaml.SequenceNode, yaml.DocumentNode: - for index, contentNode := range node.Content { - log.Debugf("exploding index %v", index) - errorInContent := explodeNode(contentNode) - if errorInContent != nil { - return errorInContent - } - } - return nil - case yaml.AliasNode: - log.Debugf("its an alias!") - if node.Alias != nil { - node.Kind = node.Alias.Kind - node.Style = node.Alias.Style - node.Tag = node.Alias.Tag - node.Content = node.Alias.Content - node.Value = node.Alias.Value - node.Alias = nil - } - return nil - case yaml.MappingNode: - for index := 0; index < len(node.Content); index = index + 2 { - keyNode := node.Content[index] - valueNode := node.Content[index+1] - log.Debugf("traversing %v", keyNode.Value) - if keyNode.Value != "<<" { - errorInContent := explodeNode(valueNode) - if errorInContent != nil { - return errorInContent - } - errorInContent = explodeNode(keyNode) - if errorInContent != nil { - return errorInContent - } - } else { - if valueNode.Kind == yaml.SequenceNode { - log.Debugf("an alias merge list!") - for index := len(valueNode.Content) - 1; index >= 0; index = index - 1 { - aliasNode := valueNode.Content[index] - applyAlias(node, aliasNode.Alias) - } - } else { - log.Debugf("an alias merge!") - applyAlias(node, valueNode.Alias) - } - node.Content = append(node.Content[:index], node.Content[index+2:]...) - //replay that index, since the array is shorter now. - index = index - 2 - } - } - - return nil - default: - return nil - } -} - -func explode(matchingNodes *list.List) error { - log.Debug("exploding nodes") - for el := matchingNodes.Front(); el != nil; el = el.Next() { - nodeContext := el.Value.(*treeops.CandidateNode) - log.Debugf("exploding %v", nodeContext.GetKey()) - errorExplodingNode := explodeNode(nodeContext.Node) - if errorExplodingNode != nil { - return errorExplodingNode - } - } - return nil -} - func printResults(matchingNodes *list.List, writer io.Writer) error { if prettyPrint { setStyle(matchingNodes, 0) diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 4f9e684f..51d3500b 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -19,23 +19,27 @@ type OperationType struct { } // operators TODO: -// - stripComments (recursive) +// - generator doc from operator tests +// - stripComments not recursive +// - documentIndex - retrieves document index, can be used with select // - mergeAppend (merges and appends arrays) -// - mergeIfEmpty (sets only if the document is empty, do I do that now?) -// - updateStyle -// - updateTag +// - mergeEmpty (sets only if the document is empty, do I do that now?) +// - updateStyle - not recursive +// - updateTag - not recursive // - explodeAnchors // - compare ?? // - validate ?? -// - exists ?? +// - exists var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator} var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator} var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} -var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator} +var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} +var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} + var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} @@ -44,6 +48,10 @@ var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: Pip var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} +var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator} + +var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator} + var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator} var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} diff --git a/pkg/yqlib/treeops/operator_assign.go b/pkg/yqlib/treeops/operator_assign_update.go similarity index 90% rename from pkg/yqlib/treeops/operator_assign.go rename to pkg/yqlib/treeops/operator_assign_update.go index 4e37c916..e124b7be 100644 --- a/pkg/yqlib/treeops/operator_assign.go +++ b/pkg/yqlib/treeops/operator_assign_update.go @@ -2,7 +2,7 @@ package treeops import "container/list" -func AssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { +func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err diff --git a/pkg/yqlib/treeops/operator_assign_test.go b/pkg/yqlib/treeops/operator_assign_update_test.go similarity index 100% rename from pkg/yqlib/treeops/operator_assign_test.go rename to pkg/yqlib/treeops/operator_assign_update_test.go diff --git a/pkg/yqlib/treeops/operator_explode.go b/pkg/yqlib/treeops/operator_explode.go new file mode 100644 index 00000000..77ad39d6 --- /dev/null +++ b/pkg/yqlib/treeops/operator_explode.go @@ -0,0 +1,112 @@ +package treeops + +import ( + "container/list" + + "gopkg.in/yaml.v3" +) + +func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("-- ExplodeOperation") + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + + rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + + if err != nil { + return nil, err + } + for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() { + explodeNode(childEl.Value.(*CandidateNode).Node) + } + + } + + return matchMap, nil +} + +func explodeNode(node *yaml.Node) error { + node.Anchor = "" + switch node.Kind { + case yaml.SequenceNode, yaml.DocumentNode: + for index, contentNode := range node.Content { + log.Debugf("exploding index %v", index) + errorInContent := explodeNode(contentNode) + if errorInContent != nil { + return errorInContent + } + } + return nil + case yaml.AliasNode: + log.Debugf("its an alias!") + if node.Alias != nil { + node.Kind = node.Alias.Kind + node.Style = node.Alias.Style + node.Tag = node.Alias.Tag + node.Content = node.Alias.Content + node.Value = node.Alias.Value + node.Alias = nil + } + return nil + case yaml.MappingNode: + for index := 0; index < len(node.Content); index = index + 2 { + keyNode := node.Content[index] + valueNode := node.Content[index+1] + log.Debugf("traversing %v", keyNode.Value) + if keyNode.Value != "<<" { + errorInContent := explodeNode(valueNode) + if errorInContent != nil { + return errorInContent + } + errorInContent = explodeNode(keyNode) + if errorInContent != nil { + return errorInContent + } + } else { + if valueNode.Kind == yaml.SequenceNode { + log.Debugf("an alias merge list!") + for index := len(valueNode.Content) - 1; index >= 0; index = index - 1 { + aliasNode := valueNode.Content[index] + applyAlias(node, aliasNode.Alias) + } + } else { + log.Debugf("an alias merge!") + applyAlias(node, valueNode.Alias) + } + node.Content = append(node.Content[:index], node.Content[index+2:]...) + //replay that index, since the array is shorter now. + index = index - 2 + } + } + + return nil + default: + return nil + } +} + +func applyAlias(node *yaml.Node, alias *yaml.Node) { + if alias == nil { + return + } + for index := 0; index < len(alias.Content); index = index + 2 { + keyNode := alias.Content[index] + log.Debugf("applying alias key %v", keyNode.Value) + valueNode := alias.Content[index+1] + setIfNotThere(node, keyNode.Value, valueNode) + } +} + +func setIfNotThere(node *yaml.Node, key string, value *yaml.Node) { + for index := 0; index < len(node.Content); index = index + 2 { + keyNode := node.Content[index] + if keyNode.Value == key { + return + } + } + // need to add it to the map + mapEntryKey := yaml.Node{Value: key, Kind: yaml.ScalarNode} + node.Content = append(node.Content, &mapEntryKey) + node.Content = append(node.Content, value) +} diff --git a/pkg/yqlib/treeops/operator_explode_test.go b/pkg/yqlib/treeops/operator_explode_test.go new file mode 100644 index 00000000..ed283636 --- /dev/null +++ b/pkg/yqlib/treeops/operator_explode_test.go @@ -0,0 +1,35 @@ +package treeops + +import ( + "testing" +) + +var explodeTest = []expressionScenario{ + { + document: `{a: mike}`, + expression: `explode(.a)`, + expected: []string{ + "D0, P[], (doc)::{a: mike}\n", + }, + }, + { + document: `{f : {a: &a cat, b: *a}}`, + expression: `explode(.f)`, + expected: []string{ + "D0, P[], (doc)::{f: {a: cat, b: cat}}\n", + }, + }, + { + document: mergeDocSample, + expression: `.foo* | explode(.)`, + expected: []string{ + "D0, P[], (doc)::{f: {a: cat, b: cat}}\n", + }, + }, +} + +func TestExplodeOperatorScenarios(t *testing.T) { + for _, tt := range explodeTest { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/operatory_style.go b/pkg/yqlib/treeops/operatory_style.go new file mode 100644 index 00000000..68209463 --- /dev/null +++ b/pkg/yqlib/treeops/operatory_style.go @@ -0,0 +1,77 @@ +package treeops + +import ( + "container/list" + "fmt" + + "gopkg.in/yaml.v3" +) + +func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + customStyle := pathNode.Rhs.Operation.StringValue + log.Debugf("AssignStyleOperator: %v", customStyle) + + var style yaml.Style + if customStyle == "tagged" { + style = yaml.TaggedStyle + } else if customStyle == "double" { + style = yaml.DoubleQuotedStyle + } else if customStyle == "single" { + style = yaml.SingleQuotedStyle + } else if customStyle == "literal" { + style = yaml.LiteralStyle + } else if customStyle == "folded" { + style = yaml.FoldedStyle + } else if customStyle == "flow" { + style = yaml.FlowStyle + } else if customStyle != "" { + return nil, fmt.Errorf("Unknown style %v", customStyle) + } + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + + if err != nil { + return nil, err + } + + for el := lhs.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debugf("Setting style of : %v", candidate.GetKey()) + candidate.Node.Style = style + } + + return matchingNodes, nil +} + +func GetStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("GetStyleOperator") + + var results = list.New() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + var style = "" + switch candidate.Node.Style { + case yaml.TaggedStyle: + style = "tagged" + case yaml.DoubleQuotedStyle: + style = "double" + case yaml.SingleQuotedStyle: + style = "single" + case yaml.LiteralStyle: + style = "literal" + case yaml.FoldedStyle: + style = "folded" + case yaml.FlowStyle: + style = "flow" + case 0: + style = "" + default: + style = "" + } + node := &yaml.Node{Kind: yaml.ScalarNode, Value: style, Tag: "!!str"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(lengthCand) + } + + return results, nil +} diff --git a/pkg/yqlib/treeops/operatory_style_test.go b/pkg/yqlib/treeops/operatory_style_test.go new file mode 100644 index 00000000..89e26498 --- /dev/null +++ b/pkg/yqlib/treeops/operatory_style_test.go @@ -0,0 +1,45 @@ +package treeops + +import ( + "testing" +) + +var styleOperatorScenarios = []expressionScenario{ + { + document: `{a: cat}`, + expression: `.a style="single"`, + expected: []string{ + "D0, P[], (doc)::{a: 'cat'}\n", + }, + }, + { + document: `{a: "cat", b: 'dog'}`, + expression: `.. style=""`, + expected: []string{ + "D0, P[], (!!map)::a: cat\nb: dog\n", + }, + }, + { + document: `{a: "cat", b: 'thing'}`, + expression: `.. | style`, + expected: []string{ + "D0, P[], (!!str)::flow\n", + "D0, P[a], (!!str)::double\n", + "D0, P[b], (!!str)::single\n", + }, + }, + { + document: `a: cat`, + expression: `.. | style`, + expected: []string{ + "D0, P[], (!!str)::\"\"\n", + "D0, P[a], (!!str)::\"\"\n", + }, + }, +} + +func TestStyleOperatorScenarios(t *testing.T) { + for _, tt := range styleOperatorScenarios { + testScenario(t, &tt) + } +} diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/treeops/path_parse_test.go index ed12524b..3bd3ee68 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -89,6 +89,26 @@ var pathTests = []struct { append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"), append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"), }, + { + `explode(.a.b)`, + append(make([]interface{}, 0), "EXPLODE", "(", "a", "PIPE", "b", ")"), + append(make([]interface{}, 0), "a", "b", "PIPE", "EXPLODE"), + }, + { + `.a.b style="folded"`, + append(make([]interface{}, 0), "a", "PIPE", "b", "ASSIGN_STYLE", "folded (string)"), + append(make([]interface{}, 0), "a", "b", "PIPE", "folded (string)", "ASSIGN_STYLE"), + }, + // { + // `.a.b tag="!!str"`, + // append(make([]interface{}, 0), "EXPLODE", "(", "a", "PIPE", "b", ")"), + // append(make([]interface{}, 0), "a", "b", "PIPE", "EXPLODE"), + // }, + { + `""`, + append(make([]interface{}, 0), " (string)"), + append(make([]interface{}, 0), " (string)"), + }, // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, // {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")}, diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index 59953f85..189b50f9 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -183,8 +183,13 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`:\s*`), opToken(CreateMap)) lexer.Add([]byte(`length`), opToken(Length)) lexer.Add([]byte(`select`), opToken(Select)) + lexer.Add([]byte(`explode`), opToken(Explode)) lexer.Add([]byte(`or`), opToken(Or)) lexer.Add([]byte(`not`), opToken(Not)) + + lexer.Add([]byte(`style\s*=`), opToken(AssignStyle)) + lexer.Add([]byte(`style`), opToken(GetStyle)) + // lexer.Add([]byte(`and`), opToken()) lexer.Add([]byte(`collect`), opToken(Collect)) @@ -217,7 +222,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`[Nn][Uu][Ll][Ll]`), nullValue()) lexer.Add([]byte(`~`), nullValue()) - lexer.Add([]byte(`"[^ "]+"`), stringValue(true)) + lexer.Add([]byte(`"[^ "]*"`), stringValue(true)) lexer.Add([]byte(`\[`), literalToken(OpenCollect, false)) lexer.Add([]byte(`\]`), literalToken(CloseCollect, true)) From d6ff198d63d801f01d181724a2c4cadf4e249557 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 2 Nov 2020 13:43:45 +1100 Subject: [PATCH 059/129] explode! --- pkg/yqlib/treeops/operator_explode.go | 86 +++++++++++++++------- pkg/yqlib/treeops/operator_explode_test.go | 24 +++++- pkg/yqlib/treeops/path_parse_test.go | 5 ++ pkg/yqlib/treeops/path_tree_test.go | 1 - 4 files changed, 84 insertions(+), 32 deletions(-) delete mode 100644 pkg/yqlib/treeops/path_tree_test.go diff --git a/pkg/yqlib/treeops/operator_explode.go b/pkg/yqlib/treeops/operator_explode.go index 77ad39d6..de3a73bd 100644 --- a/pkg/yqlib/treeops/operator_explode.go +++ b/pkg/yqlib/treeops/operator_explode.go @@ -50,35 +50,35 @@ func explodeNode(node *yaml.Node) error { } return nil case yaml.MappingNode: + var newContent = list.New() for index := 0; index < len(node.Content); index = index + 2 { keyNode := node.Content[index] valueNode := node.Content[index+1] log.Debugf("traversing %v", keyNode.Value) if keyNode.Value != "<<" { - errorInContent := explodeNode(valueNode) - if errorInContent != nil { - return errorInContent - } - errorInContent = explodeNode(keyNode) - if errorInContent != nil { - return errorInContent + err := overrideEntry(node, keyNode, valueNode, index, newContent) + if err != nil { + return err } } else { if valueNode.Kind == yaml.SequenceNode { log.Debugf("an alias merge list!") - for index := len(valueNode.Content) - 1; index >= 0; index = index - 1 { + for index := 0; index < len(valueNode.Content); index = index + 1 { aliasNode := valueNode.Content[index] - applyAlias(node, aliasNode.Alias) + applyAlias(node, aliasNode.Alias, index, newContent) } } else { log.Debugf("an alias merge!") - applyAlias(node, valueNode.Alias) + applyAlias(node, valueNode.Alias, index, newContent) } - node.Content = append(node.Content[:index], node.Content[index+2:]...) - //replay that index, since the array is shorter now. - index = index - 2 } } + node.Content = make([]*yaml.Node, newContent.Len()) + index := 0 + for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() { + node.Content[index] = newEl.Value.(*yaml.Node) + index++ + } return nil default: @@ -86,27 +86,57 @@ func explodeNode(node *yaml.Node) error { } } -func applyAlias(node *yaml.Node, alias *yaml.Node) { +func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent *list.List) error { if alias == nil { - return + return nil } for index := 0; index < len(alias.Content); index = index + 2 { keyNode := alias.Content[index] log.Debugf("applying alias key %v", keyNode.Value) valueNode := alias.Content[index+1] - setIfNotThere(node, keyNode.Value, valueNode) - } -} - -func setIfNotThere(node *yaml.Node, key string, value *yaml.Node) { - for index := 0; index < len(node.Content); index = index + 2 { - keyNode := node.Content[index] - if keyNode.Value == key { - return + err := overrideEntry(node, keyNode, valueNode, aliasIndex, newContent) + if err != nil { + return err } } - // need to add it to the map - mapEntryKey := yaml.Node{Value: key, Kind: yaml.ScalarNode} - node.Content = append(node.Content, &mapEntryKey) - node.Content = append(node.Content, value) + return nil +} + +func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent *list.List) error { + + err := explodeNode(value) + + if err != nil { + return err + } + + for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() { + valueEl := newEl.Next() // move forward twice + keyNode := newEl.Value.(*yaml.Node) + log.Debugf("checking new content %v:%v", keyNode.Value, valueEl.Value.(*yaml.Node).Value) + if keyNode.Value == key.Value && keyNode.Alias == nil && key.Alias == nil { + log.Debugf("overridign new content") + valueEl.Value = value + return nil + } + newEl = valueEl // move forward twice + } + + for index := startIndex + 2; index < len(node.Content); index = index + 2 { + keyNode := node.Content[index] + + if keyNode.Value == key.Value && keyNode.Alias == nil { + log.Debugf("content will be overridden at index %v", index) + return nil + } + } + + err = explodeNode(key) + if err != nil { + return err + } + log.Debugf("adding %v:%v", key.Value, value.Value) + newContent.PushBack(key) + newContent.PushBack(value) + return nil } diff --git a/pkg/yqlib/treeops/operator_explode_test.go b/pkg/yqlib/treeops/operator_explode_test.go index ed283636..b9f79cc5 100644 --- a/pkg/yqlib/treeops/operator_explode_test.go +++ b/pkg/yqlib/treeops/operator_explode_test.go @@ -4,6 +4,8 @@ import ( "testing" ) +//nested alias + var explodeTest = []expressionScenario{ { document: `{a: mike}`, @@ -20,10 +22,26 @@ var explodeTest = []expressionScenario{ }, }, { - document: mergeDocSample, - expression: `.foo* | explode(.)`, + document: `{f : {a: &a cat, *a: b}}`, + expression: `explode(.f)`, expected: []string{ - "D0, P[], (doc)::{f: {a: cat, b: cat}}\n", + "D0, P[], (doc)::{f: {a: cat, cat: b}}\n", + }, + }, + { + document: mergeDocSample, + expression: `.foo* | explode(.) | (. style="flow")`, + expected: []string{ + "D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n", + "D0, P[foobarList], (!!map)::{b: bar_b, a: foo_a, thing: bar_thing, c: foobarList_c}\n", + "D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n", + }, + }, + { + document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`, + expression: `explode(.f)`, + expected: []string{ + "D0, P[], (doc)::{f: {a: cat, b: {f: cat}, cat: {f: cat}}}\n", }, }, } diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/treeops/path_parse_test.go index 3bd3ee68..1142f4bf 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -109,6 +109,11 @@ var pathTests = []struct { append(make([]interface{}, 0), " (string)"), append(make([]interface{}, 0), " (string)"), }, + { + `.foo* | (. style="flow")`, + append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"), + append(make([]interface{}, 0), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"), + }, // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, // {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")}, diff --git a/pkg/yqlib/treeops/path_tree_test.go b/pkg/yqlib/treeops/path_tree_test.go deleted file mode 100644 index 005dee7e..00000000 --- a/pkg/yqlib/treeops/path_tree_test.go +++ /dev/null @@ -1 +0,0 @@ -package treeops From 0cb2ff5b2e1d45cd72723caf07590d5a7d5b5b36 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 2 Nov 2020 13:55:03 +1100 Subject: [PATCH 060/129] explode when outputting to json --- cmd/root.go | 7 +++++++ cmd/utils.go | 8 -------- pkg/yqlib/treeops/lib.go | 2 -- pkg/yqlib/treeops/operator_explode_test.go | 11 +++++++++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index e3b3bdbf..9ab13b0c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -52,6 +52,13 @@ func New() *cobra.Command { return err } + if outputToJSON { + explodeOp := treeops.Operation{OperationType: treeops.Explode} + explodeNode := treeops.PathTreeNode{Operation: &explodeOp} + pipeOp := treeops.Operation{OperationType: treeops.Pipe} + pathNode = &treeops.PathTreeNode{Operation: &pipeOp, Lhs: pathNode, Rhs: &explodeNode} + } + matchingNodes, err := evaluate("-", pathNode) if err != nil { return err diff --git a/cmd/utils.go b/cmd/utils.go index 2b24c231..debd25fa 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -136,14 +136,6 @@ func printResults(matchingNodes *list.List, writer io.Writer) error { colorsEnabled = true } - //always explode anchors when printing json - if explodeAnchors || outputToJSON { - errorExploding := explode(matchingNodes) - if errorExploding != nil { - return errorExploding - } - } - bufferedWriter := bufio.NewWriter(writer) defer safelyFlush(bufferedWriter) diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 51d3500b..c9ba0b0c 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -24,9 +24,7 @@ type OperationType struct { // - documentIndex - retrieves document index, can be used with select // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) -// - updateStyle - not recursive // - updateTag - not recursive -// - explodeAnchors // - compare ?? // - validate ?? // - exists diff --git a/pkg/yqlib/treeops/operator_explode_test.go b/pkg/yqlib/treeops/operator_explode_test.go index b9f79cc5..8117e142 100644 --- a/pkg/yqlib/treeops/operator_explode_test.go +++ b/pkg/yqlib/treeops/operator_explode_test.go @@ -4,8 +4,6 @@ import ( "testing" ) -//nested alias - var explodeTest = []expressionScenario{ { document: `{a: mike}`, @@ -37,6 +35,15 @@ var explodeTest = []expressionScenario{ "D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n", }, }, + { + document: mergeDocSample, + expression: `.foo* | explode(explode(.)) | (. style="flow")`, + expected: []string{ + "D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n", + "D0, P[foobarList], (!!map)::{b: bar_b, a: foo_a, thing: bar_thing, c: foobarList_c}\n", + "D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n", + }, + }, { document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`, expression: `explode(.f)`, From b1f139c96529e93bf947d15213c7707362b311f5 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 4 Nov 2020 10:48:43 +1100 Subject: [PATCH 061/129] refactored --- cmd/constant.go | 3 +- cmd/root.go | 26 +- cmd/utils.go | 232 ------------------ pkg/yqlib/{treeops => }/candidate_node.go | 2 +- .../{treeops => }/data_tree_navigator.go | 2 +- .../{treeops => }/data_tree_navigator_test.go | 2 +- pkg/yqlib/doc/Equal Operator.md | 64 +++++ pkg/yqlib/encoder.go | 6 +- pkg/yqlib/{treeops => }/lib.go | 7 +- pkg/yqlib/{treeops => }/matchKeyString.go | 2 +- .../{treeops => }/operator_assign_update.go | 2 +- .../operator_assign_update_test.go | 2 +- pkg/yqlib/{treeops => }/operator_booleans.go | 2 +- .../{treeops => }/operator_booleans_test.go | 2 +- pkg/yqlib/{treeops => }/operator_collect.go | 2 +- .../{treeops => }/operator_collect_object.go | 2 +- .../operator_collect_object_test.go | 2 +- .../{treeops => }/operator_collect_test.go | 2 +- .../{treeops => }/operator_create_map.go | 2 +- .../{treeops => }/operator_create_map_test.go | 2 +- pkg/yqlib/{treeops => }/operator_delete.go | 2 +- pkg/yqlib/{treeops => }/operator_equals.go | 2 +- .../{treeops => }/operator_equals_test.go | 3 +- pkg/yqlib/{treeops => }/operator_explode.go | 2 +- .../{treeops => }/operator_explode_test.go | 2 +- pkg/yqlib/{treeops => }/operator_multilpy.go | 2 +- .../{treeops => }/operator_multiply_test.go | 2 +- pkg/yqlib/{treeops => }/operator_not.go | 2 +- pkg/yqlib/{treeops => }/operator_not_test.go | 2 +- .../operator_recursive_descent.go | 2 +- .../operator_recursive_descent_test.go | 2 +- pkg/yqlib/{treeops => }/operator_select.go | 2 +- .../{treeops => }/operator_select_test.go | 2 +- pkg/yqlib/{treeops => }/operator_self.go | 2 +- .../{treeops => }/operator_traverse_path.go | 44 ++-- .../operator_traverse_path_test.go | 22 +- pkg/yqlib/{treeops => }/operator_union.go | 2 +- .../{treeops => }/operator_union_test.go | 2 +- pkg/yqlib/{treeops => }/operator_value.go | 2 +- .../{treeops => }/operator_value_test.go | 2 +- pkg/yqlib/{treeops => }/operators.go | 2 +- pkg/yqlib/operators_test.go | 85 +++++++ pkg/yqlib/{treeops => }/operatory_style.go | 2 +- .../{treeops => }/operatory_style_test.go | 2 +- pkg/yqlib/{treeops => }/path_parse_test.go | 2 +- pkg/yqlib/{treeops => }/path_postfix.go | 2 +- pkg/yqlib/{treeops => }/path_tokeniser.go | 2 +- pkg/yqlib/{treeops => }/path_tree.go | 2 +- pkg/yqlib/printer.go | 72 ++++++ pkg/yqlib/treeops/operators_test.go | 31 --- pkg/yqlib/treeops/sample.yaml | 1 - pkg/yqlib/treeops/temp.json | 7 - pkg/yqlib/utils.go | 122 +++++++++ 53 files changed, 452 insertions(+), 349 deletions(-) delete mode 100644 cmd/utils.go rename pkg/yqlib/{treeops => }/candidate_node.go (99%) rename pkg/yqlib/{treeops => }/data_tree_navigator.go (98%) rename pkg/yqlib/{treeops => }/data_tree_navigator_test.go (99%) create mode 100644 pkg/yqlib/doc/Equal Operator.md rename pkg/yqlib/{treeops => }/lib.go (98%) rename pkg/yqlib/{treeops => }/matchKeyString.go (97%) rename pkg/yqlib/{treeops => }/operator_assign_update.go (98%) rename pkg/yqlib/{treeops => }/operator_assign_update_test.go (99%) rename pkg/yqlib/{treeops => }/operator_booleans.go (99%) rename pkg/yqlib/{treeops => }/operator_booleans_test.go (97%) rename pkg/yqlib/{treeops => }/operator_collect.go (98%) rename pkg/yqlib/{treeops => }/operator_collect_object.go (98%) rename pkg/yqlib/{treeops => }/operator_collect_object_test.go (98%) rename pkg/yqlib/{treeops => }/operator_collect_test.go (98%) rename pkg/yqlib/{treeops => }/operator_create_map.go (98%) rename pkg/yqlib/{treeops => }/operator_create_map_test.go (98%) rename pkg/yqlib/{treeops => }/operator_delete.go (99%) rename pkg/yqlib/{treeops => }/operator_equals.go (97%) rename pkg/yqlib/{treeops => }/operator_equals_test.go (92%) rename pkg/yqlib/{treeops => }/operator_explode.go (99%) rename pkg/yqlib/{treeops => }/operator_explode_test.go (98%) rename pkg/yqlib/{treeops => }/operator_multilpy.go (99%) rename pkg/yqlib/{treeops => }/operator_multiply_test.go (99%) rename pkg/yqlib/{treeops => }/operator_not.go (97%) rename pkg/yqlib/{treeops => }/operator_not_test.go (98%) rename pkg/yqlib/{treeops => }/operator_recursive_descent.go (98%) rename pkg/yqlib/{treeops => }/operator_recursive_descent_test.go (99%) rename pkg/yqlib/{treeops => }/operator_select.go (97%) rename pkg/yqlib/{treeops => }/operator_select_test.go (98%) rename pkg/yqlib/{treeops => }/operator_self.go (90%) rename pkg/yqlib/{treeops => }/operator_traverse_path.go (86%) rename pkg/yqlib/{treeops => }/operator_traverse_path_test.go (91%) rename pkg/yqlib/{treeops => }/operator_union.go (96%) rename pkg/yqlib/{treeops => }/operator_union_test.go (97%) rename pkg/yqlib/{treeops => }/operator_value.go (94%) rename pkg/yqlib/{treeops => }/operator_value_test.go (98%) rename pkg/yqlib/{treeops => }/operators.go (99%) create mode 100644 pkg/yqlib/operators_test.go rename pkg/yqlib/{treeops => }/operatory_style.go (99%) rename pkg/yqlib/{treeops => }/operatory_style_test.go (98%) rename pkg/yqlib/{treeops => }/path_parse_test.go (99%) rename pkg/yqlib/{treeops => }/path_postfix.go (99%) rename pkg/yqlib/{treeops => }/path_tokeniser.go (99%) rename pkg/yqlib/{treeops => }/path_tree.go (99%) create mode 100644 pkg/yqlib/printer.go delete mode 100644 pkg/yqlib/treeops/operators_test.go delete mode 100644 pkg/yqlib/treeops/sample.yaml delete mode 100644 pkg/yqlib/treeops/temp.json create mode 100644 pkg/yqlib/utils.go diff --git a/cmd/constant.go b/cmd/constant.go index f9bad317..45400f76 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -11,19 +11,18 @@ var unwrapScalar = true var customStyle = "" var anchorName = "" var makeAlias = false -var stripComments = false var writeInplace = false var writeScript = "" var sourceYamlFile = "" var outputToJSON = false var exitStatus = false -var prettyPrint = false var explodeAnchors = false var forceColor = false var forceNoColor = false var colorsEnabled = false var defaultValue = "" var indent = 2 +var printDocSeparators = true var overwriteFlag = false var autoCreateFlag = true var arrayMergeStrategyFlag = "update" diff --git a/cmd/root.go b/cmd/root.go index 9ab13b0c..17602f65 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/mikefarah/yq/v4/pkg/yqlib/treeops" + "github.com/mikefarah/yq/v4/pkg/yqlib" "github.com/spf13/cobra" logging "gopkg.in/op/go-logging.v1" ) @@ -40,7 +40,7 @@ func New() *cobra.Command { // } cmd.SilenceUsage = true - var treeCreator = treeops.NewPathTreeCreator() + var treeCreator = yqlib.NewPathTreeCreator() expression := "" if len(args) > 0 { @@ -53,13 +53,13 @@ func New() *cobra.Command { } if outputToJSON { - explodeOp := treeops.Operation{OperationType: treeops.Explode} - explodeNode := treeops.PathTreeNode{Operation: &explodeOp} - pipeOp := treeops.Operation{OperationType: treeops.Pipe} - pathNode = &treeops.PathTreeNode{Operation: &pipeOp, Lhs: pathNode, Rhs: &explodeNode} + explodeOp := yqlib.Operation{OperationType: yqlib.Explode} + explodeNode := yqlib.PathTreeNode{Operation: &explodeOp} + pipeOp := yqlib.Operation{OperationType: yqlib.Pipe} + pathNode = &yqlib.PathTreeNode{Operation: &pipeOp, Lhs: pathNode, Rhs: &explodeNode} } - matchingNodes, err := evaluate("-", pathNode) + matchingNodes, err := yqlib.Evaluate("-", pathNode) if err != nil { return err } @@ -71,7 +71,14 @@ func New() *cobra.Command { out := cmd.OutOrStdout() - return printResults(matchingNodes, out) + fileInfo, _ := os.Stdout.Stat() + + if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { + colorsEnabled = true + } + printer := yqlib.NewPrinter(outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators) + + return printer.PrintResults(matchingNodes, out) }, PersistentPreRun: func(cmd *cobra.Command, args []string) { cmd.SetOut(cmd.OutOrStdout()) @@ -92,8 +99,7 @@ func New() *cobra.Command { } rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") - rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json. By default it prints a json document in one line, use the prettyPrint flag to print a formatted doc.") - rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print") + rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json. Set indent to 0 to print json in one line.") rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") diff --git a/cmd/utils.go b/cmd/utils.go deleted file mode 100644 index debd25fa..00000000 --- a/cmd/utils.go +++ /dev/null @@ -1,232 +0,0 @@ -package cmd - -import ( - "bufio" - "container/list" - "errors" - "io" - "os" - - "github.com/mikefarah/yq/v4/pkg/yqlib" - "github.com/mikefarah/yq/v4/pkg/yqlib/treeops" - yaml "gopkg.in/yaml.v3" -) - -func readStream(filename string) (*yaml.Decoder, error) { - if filename == "" { - return nil, errors.New("Must provide filename") - } - - var stream io.Reader - if filename == "-" { - stream = bufio.NewReader(os.Stdin) - } else { - file, err := os.Open(filename) // nolint gosec - if err != nil { - return nil, err - } - defer safelyCloseFile(file) - stream = file - } - return yaml.NewDecoder(stream), nil -} - -func evaluate(filename string, node *treeops.PathTreeNode) (*list.List, error) { - - var treeNavigator = treeops.NewDataTreeNavigator(treeops.NavigationPrefs{}) - - var matchingNodes = list.New() - - var currentIndex uint = 0 - var decoder, err = readStream(filename) - if err != nil { - return nil, err - } - - for { - var dataBucket yaml.Node - errorReading := decoder.Decode(&dataBucket) - - if errorReading == io.EOF { - return matchingNodes, nil - } else if errorReading != nil { - return nil, errorReading - } - candidateNode := &treeops.CandidateNode{ - Document: currentIndex, - Filename: filename, - Node: &dataBucket, - } - inputList := list.New() - inputList.PushBack(candidateNode) - - newMatches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node) - if errorParsing != nil { - return nil, errorParsing - } - matchingNodes.PushBackList(newMatches) - currentIndex = currentIndex + 1 - } - - return matchingNodes, nil -} - -func printNode(node *yaml.Node, writer io.Writer) error { - var encoder yqlib.Encoder - if node.Kind == yaml.ScalarNode && unwrapScalar && !outputToJSON { - return writeString(writer, node.Value+"\n") - } - if outputToJSON { - encoder = yqlib.NewJsonEncoder(writer, prettyPrint, indent) - } else { - encoder = yqlib.NewYamlEncoder(writer, indent, colorsEnabled) - } - return encoder.Encode(node) -} - -func removeComments(matchingNodes *list.List) { - for el := matchingNodes.Front(); el != nil; el = el.Next() { - candidate := el.Value.(*treeops.CandidateNode) - removeCommentOfNode(candidate.Node) - } -} - -func removeCommentOfNode(node *yaml.Node) { - node.HeadComment = "" - node.LineComment = "" - node.FootComment = "" - - for _, child := range node.Content { - removeCommentOfNode(child) - } -} - -func setStyle(matchingNodes *list.List, style yaml.Style) { - for el := matchingNodes.Front(); el != nil; el = el.Next() { - candidate := el.Value.(*treeops.CandidateNode) - updateStyleOfNode(candidate.Node, style) - } -} - -func updateStyleOfNode(node *yaml.Node, style yaml.Style) { - node.Style = style - - for _, child := range node.Content { - updateStyleOfNode(child, style) - } -} - -func writeString(writer io.Writer, txt string) error { - _, errorWriting := writer.Write([]byte(txt)) - return errorWriting -} - -func printResults(matchingNodes *list.List, writer io.Writer) error { - if prettyPrint { - setStyle(matchingNodes, 0) - } - - if stripComments { - removeComments(matchingNodes) - } - - fileInfo, _ := os.Stdout.Stat() - - if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { - colorsEnabled = true - } - - bufferedWriter := bufio.NewWriter(writer) - defer safelyFlush(bufferedWriter) - - if matchingNodes.Len() == 0 { - log.Debug("no matching results, nothing to print") - if defaultValue != "" { - return writeString(bufferedWriter, defaultValue) - } - return nil - } - var errorWriting error - - for el := matchingNodes.Front(); el != nil; el = el.Next() { - mappedDoc := el.Value.(*treeops.CandidateNode) - - switch printMode { - case "p": - errorWriting = writeString(bufferedWriter, mappedDoc.PathStackToString()+"\n") - if errorWriting != nil { - return errorWriting - } - case "pv", "vp": - // put it into a node and print that. - var parentNode = yaml.Node{Kind: yaml.MappingNode} - parentNode.Content = make([]*yaml.Node, 2) - parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: mappedDoc.PathStackToString()} - if mappedDoc.Node.Kind == yaml.DocumentNode { - parentNode.Content[1] = mappedDoc.Node.Content[0] - } else { - parentNode.Content[1] = mappedDoc.Node - } - if err := printNode(&parentNode, bufferedWriter); err != nil { - return err - } - default: - if err := printNode(mappedDoc.Node, bufferedWriter); err != nil { - return err - } - } - } - - return nil -} - -func safelyRenameFile(from string, to string) { - if renameError := os.Rename(from, to); renameError != nil { - log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) - log.Debug(renameError.Error()) - // can't do this rename when running in docker to a file targeted in a mounted volume, - // so gracefully degrade to copying the entire contents. - if copyError := copyFileContents(from, to); copyError != nil { - log.Errorf("Failed copying from %v to %v", from, to) - log.Error(copyError.Error()) - } else { - removeErr := os.Remove(from) - if removeErr != nil { - log.Errorf("failed removing original file: %s", from) - } - } - } -} - -// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang -func copyFileContents(src, dst string) (err error) { - in, err := os.Open(src) // nolint gosec - if err != nil { - return err - } - defer safelyCloseFile(in) - out, err := os.Create(dst) - if err != nil { - return err - } - defer safelyCloseFile(out) - if _, err = io.Copy(out, in); err != nil { - return err - } - return out.Sync() -} - -func safelyFlush(writer *bufio.Writer) { - if err := writer.Flush(); err != nil { - log.Error("Error flushing writer!") - log.Error(err.Error()) - } - -} -func safelyCloseFile(file *os.File) { - err := file.Close() - if err != nil { - log.Error("Error closing file!") - log.Error(err.Error()) - } -} diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/candidate_node.go similarity index 99% rename from pkg/yqlib/treeops/candidate_node.go rename to pkg/yqlib/candidate_node.go index 61843a99..1f9daad4 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/candidate_node.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "fmt" diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/data_tree_navigator.go similarity index 98% rename from pkg/yqlib/treeops/data_tree_navigator.go rename to pkg/yqlib/data_tree_navigator.go index 8d541253..78ff4410 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/data_tree_navigator.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "fmt" diff --git a/pkg/yqlib/treeops/data_tree_navigator_test.go b/pkg/yqlib/data_tree_navigator_test.go similarity index 99% rename from pkg/yqlib/treeops/data_tree_navigator_test.go rename to pkg/yqlib/data_tree_navigator_test.go index e82eac70..571a93e1 100644 --- a/pkg/yqlib/treeops/data_tree_navigator_test.go +++ b/pkg/yqlib/data_tree_navigator_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/doc/Equal Operator.md b/pkg/yqlib/doc/Equal Operator.md new file mode 100644 index 00000000..c8c94687 --- /dev/null +++ b/pkg/yqlib/doc/Equal Operator.md @@ -0,0 +1,64 @@ +# Equal Operator +## Examples +### Example 0 +sample.yml: +```yaml +[cat,goat,dog] +``` +Expression +```bash +yq '.[] | (. == "*at")' < sample.yml +``` +Result +```yaml +true +true +false +``` +### Example 1 +sample.yml: +```yaml +[3, 4, 5] +``` +Expression +```bash +yq '.[] | (. == 4)' < sample.yml +``` +Result +```yaml +false +true +false +``` +### Example 2 +sample.yml: +```yaml +a: { cat: {b: apple, c: whatever}, pat: {b: banana} } +``` +Expression +```bash +yq '.a | (.[].b == "apple")' < sample.yml +``` +Result +```yaml +true +false +``` +### Example 3 +Expression +```bash +yq 'null == null' < sample.yml +``` +Result +```yaml +true +``` +### Example 4 +Expression +```bash +yq 'null == ~' < sample.yml +``` +Result +```yaml +true +``` diff --git a/pkg/yqlib/encoder.go b/pkg/yqlib/encoder.go index e9ab9587..11bad1e8 100644 --- a/pkg/yqlib/encoder.go +++ b/pkg/yqlib/encoder.go @@ -74,16 +74,14 @@ func mapKeysToStrings(node *yaml.Node) { } } -func NewJsonEncoder(destination io.Writer, prettyPrint bool, indent int) Encoder { +func NewJsonEncoder(destination io.Writer, indent int) Encoder { var encoder = json.NewEncoder(destination) var indentString = "" for index := 0; index < indent; index++ { indentString = indentString + " " } - if prettyPrint { - encoder.SetIndent("", indentString) - } + encoder.SetIndent("", indentString) return &jsonEncoder{encoder} } diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/lib.go similarity index 98% rename from pkg/yqlib/treeops/lib.go rename to pkg/yqlib/lib.go index c9ba0b0c..f229c136 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/lib.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "bytes" @@ -9,8 +9,6 @@ import ( "gopkg.in/yaml.v3" ) -var log = logging.MustGetLogger("yq-treeops") - type OperationType struct { Type string NumArgs uint // number of arguments to the op @@ -25,6 +23,9 @@ type OperationType struct { // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) // - updateTag - not recursive +// - select by tag (tag==) +// - get tag (tag) +// - select by style (style==) // - compare ?? // - validate ?? // - exists diff --git a/pkg/yqlib/treeops/matchKeyString.go b/pkg/yqlib/matchKeyString.go similarity index 97% rename from pkg/yqlib/treeops/matchKeyString.go rename to pkg/yqlib/matchKeyString.go index 34714fd5..0ccb4f91 100644 --- a/pkg/yqlib/treeops/matchKeyString.go +++ b/pkg/yqlib/matchKeyString.go @@ -1,4 +1,4 @@ -package treeops +package yqlib func Match(name string, pattern string) (matched bool) { if pattern == "" { diff --git a/pkg/yqlib/treeops/operator_assign_update.go b/pkg/yqlib/operator_assign_update.go similarity index 98% rename from pkg/yqlib/treeops/operator_assign_update.go rename to pkg/yqlib/operator_assign_update.go index e124b7be..8381b479 100644 --- a/pkg/yqlib/treeops/operator_assign_update.go +++ b/pkg/yqlib/operator_assign_update.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import "container/list" diff --git a/pkg/yqlib/treeops/operator_assign_update_test.go b/pkg/yqlib/operator_assign_update_test.go similarity index 99% rename from pkg/yqlib/treeops/operator_assign_update_test.go rename to pkg/yqlib/operator_assign_update_test.go index bbd8e59b..e1b5870a 100644 --- a/pkg/yqlib/treeops/operator_assign_update_test.go +++ b/pkg/yqlib/operator_assign_update_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_booleans.go b/pkg/yqlib/operator_booleans.go similarity index 99% rename from pkg/yqlib/treeops/operator_booleans.go rename to pkg/yqlib/operator_booleans.go index 26b7b46b..28e2fbac 100644 --- a/pkg/yqlib/treeops/operator_booleans.go +++ b/pkg/yqlib/operator_booleans.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operator_booleans_test.go b/pkg/yqlib/operator_booleans_test.go similarity index 97% rename from pkg/yqlib/treeops/operator_booleans_test.go rename to pkg/yqlib/operator_booleans_test.go index f3013fbd..61437926 100644 --- a/pkg/yqlib/treeops/operator_booleans_test.go +++ b/pkg/yqlib/operator_booleans_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_collect.go b/pkg/yqlib/operator_collect.go similarity index 98% rename from pkg/yqlib/treeops/operator_collect.go rename to pkg/yqlib/operator_collect.go index c4e1f7c0..9eda72ec 100644 --- a/pkg/yqlib/treeops/operator_collect.go +++ b/pkg/yqlib/operator_collect.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operator_collect_object.go b/pkg/yqlib/operator_collect_object.go similarity index 98% rename from pkg/yqlib/treeops/operator_collect_object.go rename to pkg/yqlib/operator_collect_object.go index eea0db8b..4845aee7 100644 --- a/pkg/yqlib/treeops/operator_collect_object.go +++ b/pkg/yqlib/operator_collect_object.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operator_collect_object_test.go b/pkg/yqlib/operator_collect_object_test.go similarity index 98% rename from pkg/yqlib/treeops/operator_collect_object_test.go rename to pkg/yqlib/operator_collect_object_test.go index a573a05f..22c99577 100644 --- a/pkg/yqlib/treeops/operator_collect_object_test.go +++ b/pkg/yqlib/operator_collect_object_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_collect_test.go b/pkg/yqlib/operator_collect_test.go similarity index 98% rename from pkg/yqlib/treeops/operator_collect_test.go rename to pkg/yqlib/operator_collect_test.go index f2de5a05..a6018183 100644 --- a/pkg/yqlib/treeops/operator_collect_test.go +++ b/pkg/yqlib/operator_collect_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_create_map.go b/pkg/yqlib/operator_create_map.go similarity index 98% rename from pkg/yqlib/treeops/operator_create_map.go rename to pkg/yqlib/operator_create_map.go index 5abfdbd8..6c4d9949 100644 --- a/pkg/yqlib/treeops/operator_create_map.go +++ b/pkg/yqlib/operator_create_map.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operator_create_map_test.go b/pkg/yqlib/operator_create_map_test.go similarity index 98% rename from pkg/yqlib/treeops/operator_create_map_test.go rename to pkg/yqlib/operator_create_map_test.go index dee6e360..24d25ad6 100644 --- a/pkg/yqlib/treeops/operator_create_map_test.go +++ b/pkg/yqlib/operator_create_map_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_delete.go b/pkg/yqlib/operator_delete.go similarity index 99% rename from pkg/yqlib/treeops/operator_delete.go rename to pkg/yqlib/operator_delete.go index c45a46b3..362207d2 100644 --- a/pkg/yqlib/treeops/operator_delete.go +++ b/pkg/yqlib/operator_delete.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operator_equals.go b/pkg/yqlib/operator_equals.go similarity index 97% rename from pkg/yqlib/treeops/operator_equals.go rename to pkg/yqlib/operator_equals.go index 63bf6d71..8f663f37 100644 --- a/pkg/yqlib/treeops/operator_equals.go +++ b/pkg/yqlib/operator_equals.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operator_equals_test.go b/pkg/yqlib/operator_equals_test.go similarity index 92% rename from pkg/yqlib/treeops/operator_equals_test.go rename to pkg/yqlib/operator_equals_test.go index 02e4479d..6db4199d 100644 --- a/pkg/yqlib/treeops/operator_equals_test.go +++ b/pkg/yqlib/operator_equals_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" @@ -49,4 +49,5 @@ func TestEqualOperatorScenarios(t *testing.T) { for _, tt := range equalsOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Equal Operator", equalsOperatorScenarios) } diff --git a/pkg/yqlib/treeops/operator_explode.go b/pkg/yqlib/operator_explode.go similarity index 99% rename from pkg/yqlib/treeops/operator_explode.go rename to pkg/yqlib/operator_explode.go index de3a73bd..1d67c484 100644 --- a/pkg/yqlib/treeops/operator_explode.go +++ b/pkg/yqlib/operator_explode.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operator_explode_test.go b/pkg/yqlib/operator_explode_test.go similarity index 98% rename from pkg/yqlib/treeops/operator_explode_test.go rename to pkg/yqlib/operator_explode_test.go index 8117e142..7318b9a7 100644 --- a/pkg/yqlib/treeops/operator_explode_test.go +++ b/pkg/yqlib/operator_explode_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/operator_multilpy.go similarity index 99% rename from pkg/yqlib/treeops/operator_multilpy.go rename to pkg/yqlib/operator_multilpy.go index 3aa2feb1..b97a4364 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/operator_multilpy.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "fmt" diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/operator_multiply_test.go similarity index 99% rename from pkg/yqlib/treeops/operator_multiply_test.go rename to pkg/yqlib/operator_multiply_test.go index fbcd7f90..4b8dc283 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/operator_multiply_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_not.go b/pkg/yqlib/operator_not.go similarity index 97% rename from pkg/yqlib/treeops/operator_not.go rename to pkg/yqlib/operator_not.go index 0497bac3..3ae7e970 100644 --- a/pkg/yqlib/treeops/operator_not.go +++ b/pkg/yqlib/operator_not.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import "container/list" diff --git a/pkg/yqlib/treeops/operator_not_test.go b/pkg/yqlib/operator_not_test.go similarity index 98% rename from pkg/yqlib/treeops/operator_not_test.go rename to pkg/yqlib/operator_not_test.go index f1e22535..1c441644 100644 --- a/pkg/yqlib/treeops/operator_not_test.go +++ b/pkg/yqlib/operator_not_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_recursive_descent.go b/pkg/yqlib/operator_recursive_descent.go similarity index 98% rename from pkg/yqlib/treeops/operator_recursive_descent.go rename to pkg/yqlib/operator_recursive_descent.go index 5a364db6..78dd67cc 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent.go +++ b/pkg/yqlib/operator_recursive_descent.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operator_recursive_descent_test.go b/pkg/yqlib/operator_recursive_descent_test.go similarity index 99% rename from pkg/yqlib/treeops/operator_recursive_descent_test.go rename to pkg/yqlib/operator_recursive_descent_test.go index 7ced6738..e1a41e7d 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent_test.go +++ b/pkg/yqlib/operator_recursive_descent_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_select.go b/pkg/yqlib/operator_select.go similarity index 97% rename from pkg/yqlib/treeops/operator_select.go rename to pkg/yqlib/operator_select.go index f0e57f2b..72ba06a1 100644 --- a/pkg/yqlib/treeops/operator_select.go +++ b/pkg/yqlib/operator_select.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operator_select_test.go b/pkg/yqlib/operator_select_test.go similarity index 98% rename from pkg/yqlib/treeops/operator_select_test.go rename to pkg/yqlib/operator_select_test.go index 3a2dae1e..2af3c989 100644 --- a/pkg/yqlib/treeops/operator_select_test.go +++ b/pkg/yqlib/operator_select_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_self.go b/pkg/yqlib/operator_self.go similarity index 90% rename from pkg/yqlib/treeops/operator_self.go rename to pkg/yqlib/operator_self.go index bd98d32d..58bf2aa1 100644 --- a/pkg/yqlib/treeops/operator_self.go +++ b/pkg/yqlib/operator_self.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import "container/list" diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/operator_traverse_path.go similarity index 86% rename from pkg/yqlib/treeops/operator_traverse_path.go rename to pkg/yqlib/operator_traverse_path.go index 3b12e9d3..08626f1b 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/operator_traverse_path.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "fmt" @@ -186,26 +186,32 @@ func traverseArray(candidate *CandidateNode, operation *Operation) ([]*Candidate } - index := operation.Value.(int64) - indexToUse := index - contentLength := int64(len(candidate.Node.Content)) - for contentLength <= index { - candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}) - contentLength = int64(len(candidate.Node.Content)) - } + switch operation.Value.(type) { + case int64: + index := operation.Value.(int64) + indexToUse := index + contentLength := int64(len(candidate.Node.Content)) + for contentLength <= index { + candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}) + contentLength = int64(len(candidate.Node.Content)) + } - if indexToUse < 0 { - indexToUse = contentLength + indexToUse - } + if indexToUse < 0 { + indexToUse = contentLength + indexToUse + } - if indexToUse < 0 { - return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength) - } + if indexToUse < 0 { + return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength) + } - return []*CandidateNode{&CandidateNode{ - Node: candidate.Node.Content[indexToUse], - Document: candidate.Document, - Path: append(candidate.Path, index), - }}, nil + return []*CandidateNode{&CandidateNode{ + Node: candidate.Node.Content[indexToUse], + Document: candidate.Document, + Path: append(candidate.Path, index), + }}, nil + default: + log.Debug("argument not an int (%v), no array matches", operation.Value) + return nil, nil + } } diff --git a/pkg/yqlib/treeops/operator_traverse_path_test.go b/pkg/yqlib/operator_traverse_path_test.go similarity index 91% rename from pkg/yqlib/treeops/operator_traverse_path_test.go rename to pkg/yqlib/operator_traverse_path_test.go index c5bbd94d..9006ea4c 100644 --- a/pkg/yqlib/treeops/operator_traverse_path_test.go +++ b/pkg/yqlib/operator_traverse_path_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" @@ -126,6 +126,26 @@ var traversePathOperatorScenarios = []expressionScenario{ "D0, P[b c], (!!str)::frog\n", }, }, + { + document: `[1,2,3]`, + expression: `.b`, + expected: []string{}, + }, + { + document: `[1,2,3]`, + expression: `[0]`, + expected: []string{ + "D0, P[0], (!!int)::1\n", + }, + }, + { + description: `Maps can have numbers as keys, so this default to a non-exisiting key behaviour.`, + document: `{a: b}`, + expression: `[0]`, + expected: []string{ + "D0, P[0], (!!null)::null\n", + }, + }, { document: mergeDocSample, expression: `.foobar`, diff --git a/pkg/yqlib/treeops/operator_union.go b/pkg/yqlib/operator_union.go similarity index 96% rename from pkg/yqlib/treeops/operator_union.go rename to pkg/yqlib/operator_union.go index 9c50d434..f436fa0e 100644 --- a/pkg/yqlib/treeops/operator_union.go +++ b/pkg/yqlib/operator_union.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import "container/list" diff --git a/pkg/yqlib/treeops/operator_union_test.go b/pkg/yqlib/operator_union_test.go similarity index 97% rename from pkg/yqlib/treeops/operator_union_test.go rename to pkg/yqlib/operator_union_test.go index ff80f1d6..d28e4a98 100644 --- a/pkg/yqlib/treeops/operator_union_test.go +++ b/pkg/yqlib/operator_union_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operator_value.go b/pkg/yqlib/operator_value.go similarity index 94% rename from pkg/yqlib/treeops/operator_value.go rename to pkg/yqlib/operator_value.go index 0769a212..94650050 100644 --- a/pkg/yqlib/treeops/operator_value.go +++ b/pkg/yqlib/operator_value.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import "container/list" diff --git a/pkg/yqlib/treeops/operator_value_test.go b/pkg/yqlib/operator_value_test.go similarity index 98% rename from pkg/yqlib/treeops/operator_value_test.go rename to pkg/yqlib/operator_value_test.go index 43408f76..0642600a 100644 --- a/pkg/yqlib/treeops/operator_value_test.go +++ b/pkg/yqlib/operator_value_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/operators.go similarity index 99% rename from pkg/yqlib/treeops/operators.go rename to pkg/yqlib/operators.go index b035b1f6..b1dfc1b7 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/operators.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go new file mode 100644 index 00000000..c9745b16 --- /dev/null +++ b/pkg/yqlib/operators_test.go @@ -0,0 +1,85 @@ +package yqlib + +import ( + "bufio" + "bytes" + "fmt" + "os" + "testing" + + "github.com/mikefarah/yq/v4/test" +) + +type expressionScenario struct { + description string + document string + expression string + expected []string +} + +func testScenario(t *testing.T, s *expressionScenario) { + + nodes := readDoc(t, s.document) + path, errPath := treeCreator.ParsePath(s.expression) + if errPath != nil { + t.Error(errPath) + return + } + results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + + if errNav != nil { + t.Error(errNav) + return + } + test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document)) +} + +func documentScenarios(t *testing.T, title string, scenarios []expressionScenario) { + f, err := os.Create(fmt.Sprintf("doc/%v.md", title)) + + if err != nil { + panic(err) + } + defer f.Close() + w := bufio.NewWriter(f) + w.WriteString(fmt.Sprintf("# %v\n", title)) + w.WriteString(fmt.Sprintf("## Examples\n")) + + printer := NewPrinter(false, true, false, 2, true) + + for index, s := range scenarios { + if s.description != "" { + w.WriteString(fmt.Sprintf("### %v\n", s.description)) + } else { + w.WriteString(fmt.Sprintf("### Example %v\n", index)) + } + if s.document != "" { + w.WriteString(fmt.Sprintf("sample.yml:\n")) + w.WriteString(fmt.Sprintf("```yaml\n%v\n```\n", s.document)) + } + if s.expression != "" { + w.WriteString(fmt.Sprintf("Expression\n")) + w.WriteString(fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression)) + } + + w.WriteString(fmt.Sprintf("Result\n")) + + nodes := readDoc(t, s.document) + path, errPath := treeCreator.ParsePath(s.expression) + if errPath != nil { + t.Error(errPath) + return + } + var output bytes.Buffer + results, err := treeNavigator.GetMatchingNodes(nodes, path) + printer.PrintResults(results, bufio.NewWriter(&output)) + + w.WriteString(fmt.Sprintf("```yaml\n%v```\n", output.String())) + + if err != nil { + panic(err) + } + + } + w.Flush() +} diff --git a/pkg/yqlib/treeops/operatory_style.go b/pkg/yqlib/operatory_style.go similarity index 99% rename from pkg/yqlib/treeops/operatory_style.go rename to pkg/yqlib/operatory_style.go index 68209463..a0d4aab0 100644 --- a/pkg/yqlib/treeops/operatory_style.go +++ b/pkg/yqlib/operatory_style.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "container/list" diff --git a/pkg/yqlib/treeops/operatory_style_test.go b/pkg/yqlib/operatory_style_test.go similarity index 98% rename from pkg/yqlib/treeops/operatory_style_test.go rename to pkg/yqlib/operatory_style_test.go index 89e26498..b2aa40bc 100644 --- a/pkg/yqlib/treeops/operatory_style_test.go +++ b/pkg/yqlib/operatory_style_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "testing" diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/path_parse_test.go similarity index 99% rename from pkg/yqlib/treeops/path_parse_test.go rename to pkg/yqlib/path_parse_test.go index 1142f4bf..cc2e2e3b 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/path_parse_test.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "fmt" diff --git a/pkg/yqlib/treeops/path_postfix.go b/pkg/yqlib/path_postfix.go similarity index 99% rename from pkg/yqlib/treeops/path_postfix.go rename to pkg/yqlib/path_postfix.go index 147e74d1..992a48ee 100644 --- a/pkg/yqlib/treeops/path_postfix.go +++ b/pkg/yqlib/path_postfix.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "errors" diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go similarity index 99% rename from pkg/yqlib/treeops/path_tokeniser.go rename to pkg/yqlib/path_tokeniser.go index 189b50f9..4c5b44a0 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import ( "fmt" diff --git a/pkg/yqlib/treeops/path_tree.go b/pkg/yqlib/path_tree.go similarity index 99% rename from pkg/yqlib/treeops/path_tree.go rename to pkg/yqlib/path_tree.go index bee15ff8..aca5b386 100644 --- a/pkg/yqlib/treeops/path_tree.go +++ b/pkg/yqlib/path_tree.go @@ -1,4 +1,4 @@ -package treeops +package yqlib import "fmt" diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go new file mode 100644 index 00000000..55dcec6c --- /dev/null +++ b/pkg/yqlib/printer.go @@ -0,0 +1,72 @@ +package yqlib + +import ( + "bufio" + "container/list" + "io" + + "gopkg.in/yaml.v3" +) + +type Printer interface { + PrintResults(matchingNodes *list.List, writer io.Writer) error +} + +type resultsPrinter struct { + outputToJSON bool + unwrapScalar bool + colorsEnabled bool + indent int + printDocSeparators bool +} + +func NewPrinter(outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer { + return &resultsPrinter{outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators} +} + +func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error { + var encoder Encoder + if node.Kind == yaml.ScalarNode && p.unwrapScalar && !p.outputToJSON { + return p.writeString(writer, node.Value+"\n") + } + if p.outputToJSON { + encoder = NewJsonEncoder(writer, p.indent) + } else { + encoder = NewYamlEncoder(writer, p.indent, p.colorsEnabled) + } + return encoder.Encode(node) +} + +func (p *resultsPrinter) writeString(writer io.Writer, txt string) error { + _, errorWriting := writer.Write([]byte(txt)) + return errorWriting +} + +func (p *resultsPrinter) PrintResults(matchingNodes *list.List, writer io.Writer) error { + + bufferedWriter := bufio.NewWriter(writer) + defer safelyFlush(bufferedWriter) + + if matchingNodes.Len() == 0 { + log.Debug("no matching results, nothing to print") + return nil + } + + var previousDocIndex uint = 0 + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + mappedDoc := el.Value.(*CandidateNode) + + if previousDocIndex != mappedDoc.Document && p.printDocSeparators { + p.writeString(bufferedWriter, "---\n") + } + + if err := p.printNode(mappedDoc.Node, bufferedWriter); err != nil { + return err + } + + previousDocIndex = mappedDoc.Document + } + + return nil +} diff --git a/pkg/yqlib/treeops/operators_test.go b/pkg/yqlib/treeops/operators_test.go deleted file mode 100644 index 57a4c4b5..00000000 --- a/pkg/yqlib/treeops/operators_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package treeops - -import ( - "fmt" - "testing" - - "github.com/mikefarah/yq/v4/test" -) - -type expressionScenario struct { - document string - expression string - expected []string -} - -func testScenario(t *testing.T, s *expressionScenario) { - - nodes := readDoc(t, s.document) - path, errPath := treeCreator.ParsePath(s.expression) - if errPath != nil { - t.Error(errPath) - return - } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - - if errNav != nil { - t.Error(errNav) - return - } - test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document)) -} diff --git a/pkg/yqlib/treeops/sample.yaml b/pkg/yqlib/treeops/sample.yaml deleted file mode 100644 index f151d6f8..00000000 --- a/pkg/yqlib/treeops/sample.yaml +++ /dev/null @@ -1 +0,0 @@ -{name: Mike, pets: [cat, dog]} \ No newline at end of file diff --git a/pkg/yqlib/treeops/temp.json b/pkg/yqlib/treeops/temp.json deleted file mode 100644 index f5cf07d9..00000000 --- a/pkg/yqlib/treeops/temp.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Mike", - "pets": [ - "cat", - "dog" - ] -} diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go new file mode 100644 index 00000000..73408d44 --- /dev/null +++ b/pkg/yqlib/utils.go @@ -0,0 +1,122 @@ +package yqlib + +import ( + "bufio" + "container/list" + "errors" + "io" + "os" + + yaml "gopkg.in/yaml.v3" +) + +func readStream(filename string) (*yaml.Decoder, error) { + if filename == "" { + return nil, errors.New("Must provide filename") + } + + var stream io.Reader + if filename == "-" { + stream = bufio.NewReader(os.Stdin) + } else { + file, err := os.Open(filename) // nolint gosec + if err != nil { + return nil, err + } + defer safelyCloseFile(file) + stream = file + } + return yaml.NewDecoder(stream), nil +} + +// put this in lib +func Evaluate(filename string, node *PathTreeNode) (*list.List, error) { + + var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) + + var matchingNodes = list.New() + + var currentIndex uint = 0 + var decoder, err = readStream(filename) + if err != nil { + return nil, err + } + + for { + var dataBucket yaml.Node + errorReading := decoder.Decode(&dataBucket) + + if errorReading == io.EOF { + return matchingNodes, nil + } else if errorReading != nil { + return nil, errorReading + } + candidateNode := &CandidateNode{ + Document: currentIndex, + Filename: filename, + Node: &dataBucket, + } + inputList := list.New() + inputList.PushBack(candidateNode) + + newMatches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node) + if errorParsing != nil { + return nil, errorParsing + } + matchingNodes.PushBackList(newMatches) + currentIndex = currentIndex + 1 + } + + return matchingNodes, nil +} + +func safelyRenameFile(from string, to string) { + if renameError := os.Rename(from, to); renameError != nil { + log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) + log.Debug(renameError.Error()) + // can't do this rename when running in docker to a file targeted in a mounted volume, + // so gracefully degrade to copying the entire contents. + if copyError := copyFileContents(from, to); copyError != nil { + log.Errorf("Failed copying from %v to %v", from, to) + log.Error(copyError.Error()) + } else { + removeErr := os.Remove(from) + if removeErr != nil { + log.Errorf("failed removing original file: %s", from) + } + } + } +} + +// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang +func copyFileContents(src, dst string) (err error) { + in, err := os.Open(src) // nolint gosec + if err != nil { + return err + } + defer safelyCloseFile(in) + out, err := os.Create(dst) + if err != nil { + return err + } + defer safelyCloseFile(out) + if _, err = io.Copy(out, in); err != nil { + return err + } + return out.Sync() +} + +func safelyFlush(writer *bufio.Writer) { + if err := writer.Flush(); err != nil { + log.Error("Error flushing writer!") + log.Error(err.Error()) + } + +} +func safelyCloseFile(file *os.File) { + err := file.Close() + if err != nil { + log.Error("Error closing file!") + log.Error(err.Error()) + } +} From 5ab584afac4c973b752ce6eeadbad7e4fec39454 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 6 Nov 2020 11:23:26 +1100 Subject: [PATCH 062/129] comment ops! --- pkg/yqlib/doc/.gitignore | 1 + pkg/yqlib/doc/Equal Operator.md | 64 ----------------------------- pkg/yqlib/lib.go | 5 +-- pkg/yqlib/operator_comments.go | 47 +++++++++++++++++++++ pkg/yqlib/operator_comments_test.go | 55 +++++++++++++++++++++++++ pkg/yqlib/operator_equals_test.go | 2 +- pkg/yqlib/operatory_style.go | 14 ++++++- pkg/yqlib/operatory_style_test.go | 9 ++++ pkg/yqlib/path_tokeniser.go | 12 +++++- 9 files changed, 138 insertions(+), 71 deletions(-) create mode 100644 pkg/yqlib/doc/.gitignore delete mode 100644 pkg/yqlib/doc/Equal Operator.md create mode 100644 pkg/yqlib/operator_comments.go create mode 100644 pkg/yqlib/operator_comments_test.go diff --git a/pkg/yqlib/doc/.gitignore b/pkg/yqlib/doc/.gitignore new file mode 100644 index 00000000..dd449725 --- /dev/null +++ b/pkg/yqlib/doc/.gitignore @@ -0,0 +1 @@ +*.md diff --git a/pkg/yqlib/doc/Equal Operator.md b/pkg/yqlib/doc/Equal Operator.md deleted file mode 100644 index c8c94687..00000000 --- a/pkg/yqlib/doc/Equal Operator.md +++ /dev/null @@ -1,64 +0,0 @@ -# Equal Operator -## Examples -### Example 0 -sample.yml: -```yaml -[cat,goat,dog] -``` -Expression -```bash -yq '.[] | (. == "*at")' < sample.yml -``` -Result -```yaml -true -true -false -``` -### Example 1 -sample.yml: -```yaml -[3, 4, 5] -``` -Expression -```bash -yq '.[] | (. == 4)' < sample.yml -``` -Result -```yaml -false -true -false -``` -### Example 2 -sample.yml: -```yaml -a: { cat: {b: apple, c: whatever}, pat: {b: banana} } -``` -Expression -```bash -yq '.a | (.[].b == "apple")' < sample.yml -``` -Result -```yaml -true -false -``` -### Example 3 -Expression -```bash -yq 'null == null' < sample.yml -``` -Result -```yaml -true -``` -### Example 4 -Expression -```bash -yq 'null == ~' < sample.yml -``` -Result -```yaml -true -``` diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index f229c136..6ae95f84 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -18,14 +18,12 @@ type OperationType struct { // operators TODO: // - generator doc from operator tests -// - stripComments not recursive +// - set comments not recursive // - documentIndex - retrieves document index, can be used with select // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) // - updateTag - not recursive -// - select by tag (tag==) // - get tag (tag) -// - select by style (style==) // - compare ?? // - validate ?? // - exists @@ -38,6 +36,7 @@ var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: U var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} +var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} diff --git a/pkg/yqlib/operator_comments.go b/pkg/yqlib/operator_comments.go new file mode 100644 index 00000000..e3aeedb1 --- /dev/null +++ b/pkg/yqlib/operator_comments.go @@ -0,0 +1,47 @@ +package yqlib + +import "container/list" + +type AssignCommentPreferences struct { + LineComment bool + HeadComment bool + FootComment bool +} + +func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + + log.Debugf("AssignComments operator!") + + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + comment := "" + if rhs.Front() != nil { + comment = rhs.Front().Value.(*CandidateNode).Node.Value + } + + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + + if err != nil { + return nil, err + } + + preferences := pathNode.Operation.Preferences.(*AssignCommentPreferences) + + for el := lhs.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debugf("Setting comment of : %v", candidate.GetKey()) + if preferences.LineComment { + candidate.Node.LineComment = comment + } + if preferences.HeadComment { + candidate.Node.HeadComment = comment + } + if preferences.FootComment { + candidate.Node.FootComment = comment + } + + } + return matchingNodes, nil +} diff --git a/pkg/yqlib/operator_comments_test.go b/pkg/yqlib/operator_comments_test.go new file mode 100644 index 00000000..221a227b --- /dev/null +++ b/pkg/yqlib/operator_comments_test.go @@ -0,0 +1,55 @@ +package yqlib + +import ( + "testing" +) + +var commentOperatorScenarios = []expressionScenario{ + { + description: "Add line comment", + document: `a: cat`, + expression: `.a lineComment="single"`, + expected: []string{ + "D0, P[], (doc)::a: cat # single\n", + }, + }, + { + description: "Add head comment", + document: `a: cat`, + expression: `. headComment="single"`, + expected: []string{ + "D0, P[], (doc)::# single\n\na: cat\n", + }, + }, + { + description: "Add foot comment, using an expression", + document: `a: cat`, + expression: `. footComment=.a`, + expected: []string{ + "D0, P[], (doc)::a: cat\n\n# cat\n", + }, + }, + { + description: "Remove comment", + document: "a: cat # comment\nb: dog # leave this", + expression: `.a lineComment=""`, + expected: []string{ + "D0, P[], (doc)::a: cat\nb: dog # leave this\n", + }, + }, + { + description: "Remove all comments", + document: "# hi\n\na: cat # comment\n\n# great\n", + expression: `.. comments=""`, + expected: []string{ + "D0, P[], (!!map)::a: cat\n", + }, + }, +} + +func TestCommentOperatorScenarios(t *testing.T) { + for _, tt := range commentOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Comments Operator", commentOperatorScenarios) +} diff --git a/pkg/yqlib/operator_equals_test.go b/pkg/yqlib/operator_equals_test.go index 6db4199d..39e05456 100644 --- a/pkg/yqlib/operator_equals_test.go +++ b/pkg/yqlib/operator_equals_test.go @@ -49,5 +49,5 @@ func TestEqualOperatorScenarios(t *testing.T) { for _, tt := range equalsOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Equal Operator", equalsOperatorScenarios) + documentScenarios(t, "Equals Operator", equalsOperatorScenarios) } diff --git a/pkg/yqlib/operatory_style.go b/pkg/yqlib/operatory_style.go index a0d4aab0..872b0ada 100644 --- a/pkg/yqlib/operatory_style.go +++ b/pkg/yqlib/operatory_style.go @@ -8,8 +8,18 @@ import ( ) func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - customStyle := pathNode.Rhs.Operation.StringValue - log.Debugf("AssignStyleOperator: %v", customStyle) + + log.Debugf("AssignStyleOperator: %v") + + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + customStyle := "" + + if rhs.Front() != nil { + customStyle = rhs.Front().Value.(*CandidateNode).Node.Value + } var style yaml.Style if customStyle == "tagged" { diff --git a/pkg/yqlib/operatory_style_test.go b/pkg/yqlib/operatory_style_test.go index b2aa40bc..28909899 100644 --- a/pkg/yqlib/operatory_style_test.go +++ b/pkg/yqlib/operatory_style_test.go @@ -12,6 +12,14 @@ var styleOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::{a: 'cat'}\n", }, }, + { + description: "Set style using a path", + document: `{a: cat, b: double}`, + expression: `.a style=.b`, + expected: []string{ + "D0, P[], (doc)::{a: \"cat\", b: double}\n", + }, + }, { document: `{a: "cat", b: 'dog'}`, expression: `.. style=""`, @@ -42,4 +50,5 @@ func TestStyleOperatorScenarios(t *testing.T) { for _, tt := range styleOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Style Operator", styleOperatorScenarios) } diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 4c5b44a0..53575cdd 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -84,9 +84,13 @@ func documentToken() lex.Action { } func opToken(op *OperationType) lex.Action { + return opTokenWithPrefs(op, nil) +} + +func opTokenWithPrefs(op *OperationType, preferences interface{}) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) - op := &Operation{OperationType: op, Value: op.Type, StringValue: value} + op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences} return &Token{TokenType: OperationToken, Operation: op}, nil } } @@ -190,6 +194,12 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`style\s*=`), opToken(AssignStyle)) lexer.Add([]byte(`style`), opToken(GetStyle)) + lexer.Add([]byte(`lineComment\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{LineComment: true})) + lexer.Add([]byte(`headComment\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{HeadComment: true})) + lexer.Add([]byte(`footComment\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{FootComment: true})) + lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{LineComment: true, HeadComment: true, FootComment: true})) + // lexer.Add([]byte(`style`), opToken(GetStyle)) + // lexer.Add([]byte(`and`), opToken()) lexer.Add([]byte(`collect`), opToken(Collect)) From 05520c21687f0fb501c135a148786e7a9c0416a0 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 6 Nov 2020 11:45:18 +1100 Subject: [PATCH 063/129] more tests --- pkg/yqlib/lib.go | 1 + pkg/yqlib/operator_comments.go | 35 ++++++++++++++++-- pkg/yqlib/operator_comments_test.go | 30 ++++++++++++++-- pkg/yqlib/operator_multiply_test.go | 40 ++++++++++++++------- pkg/yqlib/operators_test.go | 56 +++++++++++++++-------------- pkg/yqlib/path_tokeniser.go | 14 +++++--- 6 files changed, 127 insertions(+), 49 deletions(-) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 6ae95f84..fadaf502 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -47,6 +47,7 @@ var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: Pip var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator} +var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator} var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator} diff --git a/pkg/yqlib/operator_comments.go b/pkg/yqlib/operator_comments.go index e3aeedb1..58cc17ac 100644 --- a/pkg/yqlib/operator_comments.go +++ b/pkg/yqlib/operator_comments.go @@ -1,8 +1,13 @@ package yqlib -import "container/list" +import ( + "container/list" + "strings" -type AssignCommentPreferences struct { + "gopkg.in/yaml.v3" +) + +type CommentOpPreferences struct { LineComment bool HeadComment bool FootComment bool @@ -27,7 +32,7 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path return nil, err } - preferences := pathNode.Operation.Preferences.(*AssignCommentPreferences) + preferences := pathNode.Operation.Preferences.(*CommentOpPreferences) for el := lhs.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -45,3 +50,27 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path } return matchingNodes, nil } + +func GetCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + preferences := pathNode.Operation.Preferences.(*CommentOpPreferences) + log.Debugf("GetComments operator!") + var results = list.New() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + comment := "" + if preferences.LineComment { + comment = candidate.Node.LineComment + } else if preferences.HeadComment { + comment = candidate.Node.HeadComment + } else if preferences.FootComment { + comment = candidate.Node.FootComment + } + comment = strings.Replace(comment, "# ", "", 1) + + node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(lengthCand) + } + return results, nil +} diff --git a/pkg/yqlib/operator_comments_test.go b/pkg/yqlib/operator_comments_test.go index 221a227b..46a16952 100644 --- a/pkg/yqlib/operator_comments_test.go +++ b/pkg/yqlib/operator_comments_test.go @@ -6,7 +6,7 @@ import ( var commentOperatorScenarios = []expressionScenario{ { - description: "Add line comment", + description: "Set line comment", document: `a: cat`, expression: `.a lineComment="single"`, expected: []string{ @@ -14,7 +14,7 @@ var commentOperatorScenarios = []expressionScenario{ }, }, { - description: "Add head comment", + description: "Set head comment", document: `a: cat`, expression: `. headComment="single"`, expected: []string{ @@ -22,7 +22,7 @@ var commentOperatorScenarios = []expressionScenario{ }, }, { - description: "Add foot comment, using an expression", + description: "Set foot comment, using an expression", document: `a: cat`, expression: `. footComment=.a`, expected: []string{ @@ -45,6 +45,30 @@ var commentOperatorScenarios = []expressionScenario{ "D0, P[], (!!map)::a: cat\n", }, }, + { + description: "Get line comment", + document: "# welcome!\n\na: cat # meow\n\n# have a great day", + expression: `.a | lineComment`, + expected: []string{ + "D0, P[a], (!!str)::meow\n", + }, + }, + { + description: "Get head comment", + document: "# welcome!\n\na: cat # meow\n\n# have a great day", + expression: `. | headComment`, + expected: []string{ + "D0, P[], (!!str)::welcome!\n", + }, + }, + { + description: "Get foot comment", + document: "# welcome!\n\na: cat # meow\n\n# have a great day", + expression: `. | footComment`, + expected: []string{ + "D0, P[], (!!str)::have a great day\n", + }, + }, } func TestCommentOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/operator_multiply_test.go b/pkg/yqlib/operator_multiply_test.go index 4b8dc283..3da2858f 100644 --- a/pkg/yqlib/operator_multiply_test.go +++ b/pkg/yqlib/operator_multiply_test.go @@ -6,6 +6,7 @@ import ( var multiplyOperatorScenarios = []expressionScenario{ { + skipDoc: true, document: `{a: {also: [1]}, b: {also: me}}`, expression: `. * {"a" : .b}`, expected: []string{ @@ -13,6 +14,7 @@ var multiplyOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {also: me}, b: {also: [1]}}`, expression: `. * {"a":.b}`, expected: []string{ @@ -20,13 +22,15 @@ var multiplyOperatorScenarios = []expressionScenario{ }, }, { - document: `{a: {also: me}, b: {also: {g: wizz}}}`, - expression: `. * {"a":.b}`, + description: "Merge objects together", + document: `{a: {also: me}, b: {also: {g: wizz}}}`, + expression: `. * {"a":.b}`, expected: []string{ "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", }, }, { + skipDoc: true, document: `{a: {also: {g: wizz}}, b: {also: me}}`, expression: `. * {"a":.b}`, expected: []string{ @@ -34,6 +38,7 @@ var multiplyOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {also: {g: wizz}}, b: {also: [1]}}`, expression: `. * {"a":.b}`, expected: []string{ @@ -41,6 +46,7 @@ var multiplyOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {also: [1]}, b: {also: {g: wizz}}}`, expression: `. * {"a":.b}`, expected: []string{ @@ -48,6 +54,7 @@ var multiplyOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {things: great}, b: {also: me}}`, expression: `. * {"a":.b}`, expected: []string{ @@ -55,6 +62,7 @@ var multiplyOperatorScenarios = []expressionScenario{ }, }, { + description: "Merge keeps style of LHS", document: `a: {things: great} b: also: "me" @@ -68,36 +76,41 @@ b: }, }, { - document: `{a: [1,2,3], b: [3,4,5]}`, - expression: `. * {"a":.b}`, + description: "Merge arrays", + document: `{a: [1,2,3], b: [3,4,5]}`, + expression: `. * {"a":.b}`, expected: []string{ "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", }, }, { - document: `{a: cat}`, - expression: `. * {"a": {"c": .a}}`, + description: "Merge to prefix an element", + document: `{a: cat, b: dog}`, + expression: `. * {"a": {"c": .a}}`, expected: []string{ - "D0, P[], (!!map)::{a: {c: cat}}\n", + "D0, P[], (!!map)::{a: {c: cat}, b: dog}\n", }, }, { - document: `{a: &cat {c: frog}, b: {f: *cat}, c: {g: thongs}}`, - expression: `.c * .b`, + description: "Merge with simple aliases", + document: `{a: &cat {c: frog}, b: {f: *cat}, c: {g: thongs}}`, + expression: `.c * .b`, expected: []string{ "D0, P[c], (!!map)::{g: thongs, f: *cat}\n", }, }, { - document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`, - expression: `.c * .a`, + description: "Merge does not copy anchor names", + document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`, + expression: `.c * .a`, expected: []string{ "D0, P[c], (!!map)::{g: thongs, c: frog}\n", }, }, { - document: mergeDocSample, - expression: `.foobar * .foobarList`, + description: "Merge with merge anchors", + document: mergeDocSample, + expression: `.foobar * .foobarList`, expected: []string{ "D0, P[foobar], (!!map)::c: foobarList_c\n<<: [*foo, *bar]\nthing: foobar_thing\nb: foobarList_b\n", }, @@ -108,4 +121,5 @@ func TestMultiplyOperatorScenarios(t *testing.T) { for _, tt := range multiplyOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Mulitply Operator", multiplyOperatorScenarios) } diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index c9745b16..03a32597 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -15,6 +15,7 @@ type expressionScenario struct { document string expression string expected []string + skipDoc bool } func testScenario(t *testing.T, s *expressionScenario) { @@ -48,36 +49,39 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari printer := NewPrinter(false, true, false, 2, true) for index, s := range scenarios { - if s.description != "" { - w.WriteString(fmt.Sprintf("### %v\n", s.description)) - } else { - w.WriteString(fmt.Sprintf("### Example %v\n", index)) - } - if s.document != "" { - w.WriteString(fmt.Sprintf("sample.yml:\n")) - w.WriteString(fmt.Sprintf("```yaml\n%v\n```\n", s.document)) - } - if s.expression != "" { - w.WriteString(fmt.Sprintf("Expression\n")) - w.WriteString(fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression)) - } + if !s.skipDoc { - w.WriteString(fmt.Sprintf("Result\n")) + if s.description != "" { + w.WriteString(fmt.Sprintf("### %v\n", s.description)) + } else { + w.WriteString(fmt.Sprintf("### Example %v\n", index)) + } + if s.document != "" { + w.WriteString(fmt.Sprintf("sample.yml:\n")) + w.WriteString(fmt.Sprintf("```yaml\n%v\n```\n", s.document)) + } + if s.expression != "" { + w.WriteString(fmt.Sprintf("Expression\n")) + w.WriteString(fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression)) + } - nodes := readDoc(t, s.document) - path, errPath := treeCreator.ParsePath(s.expression) - if errPath != nil { - t.Error(errPath) - return - } - var output bytes.Buffer - results, err := treeNavigator.GetMatchingNodes(nodes, path) - printer.PrintResults(results, bufio.NewWriter(&output)) + w.WriteString(fmt.Sprintf("Result\n")) - w.WriteString(fmt.Sprintf("```yaml\n%v```\n", output.String())) + nodes := readDoc(t, s.document) + path, errPath := treeCreator.ParsePath(s.expression) + if errPath != nil { + t.Error(errPath) + return + } + var output bytes.Buffer + results, err := treeNavigator.GetMatchingNodes(nodes, path) + printer.PrintResults(results, bufio.NewWriter(&output)) - if err != nil { - panic(err) + w.WriteString(fmt.Sprintf("```yaml\n%v```\n", output.String())) + + if err != nil { + panic(err) + } } } diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 53575cdd..4fa41c0e 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -194,10 +194,16 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`style\s*=`), opToken(AssignStyle)) lexer.Add([]byte(`style`), opToken(GetStyle)) - lexer.Add([]byte(`lineComment\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{LineComment: true})) - lexer.Add([]byte(`headComment\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{HeadComment: true})) - lexer.Add([]byte(`footComment\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{FootComment: true})) - lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{LineComment: true, HeadComment: true, FootComment: true})) + lexer.Add([]byte(`lineComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{LineComment: true})) + lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{LineComment: true})) + + lexer.Add([]byte(`headComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{HeadComment: true})) + lexer.Add([]byte(`headComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{HeadComment: true})) + + lexer.Add([]byte(`footComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{FootComment: true})) + lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{FootComment: true})) + + lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true})) // lexer.Add([]byte(`style`), opToken(GetStyle)) // lexer.Add([]byte(`and`), opToken()) From b290a65602426a68e4708cddd8189d97de70d881 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 6 Nov 2020 12:11:38 +1100 Subject: [PATCH 064/129] document index --- pkg/yqlib/data_tree_navigator_test.go | 26 ------------------ pkg/yqlib/document_index_operator.go | 20 ++++++++++++++ pkg/yqlib/document_index_operator_test.go | 32 +++++++++++++++++++++++ pkg/yqlib/lib.go | 2 +- pkg/yqlib/operators_test.go | 12 ++++----- pkg/yqlib/path_tokeniser.go | 2 ++ pkg/yqlib/printer.go | 2 +- pkg/yqlib/utils.go | 27 ++++++++++--------- 8 files changed, 76 insertions(+), 47 deletions(-) create mode 100644 pkg/yqlib/document_index_operator.go create mode 100644 pkg/yqlib/document_index_operator_test.go diff --git a/pkg/yqlib/data_tree_navigator_test.go b/pkg/yqlib/data_tree_navigator_test.go index 571a93e1..d4b18b32 100644 --- a/pkg/yqlib/data_tree_navigator_test.go +++ b/pkg/yqlib/data_tree_navigator_test.go @@ -2,36 +2,10 @@ package yqlib import ( "container/list" - "strings" - "testing" - - yaml "gopkg.in/yaml.v3" ) -var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) var treeCreator = NewPathTreeCreator() -func readDoc(t *testing.T, content string) *list.List { - inputList := list.New() - if content == "" { - return inputList - } - decoder := yaml.NewDecoder(strings.NewReader(content)) - var dataBucket yaml.Node - err := decoder.Decode(&dataBucket) - if err != nil { - t.Error(content) - t.Error(err) - } - - inputList.PushBack(&CandidateNode{ - Document: 0, - Filename: "test.yml", - Node: &dataBucket, - }) - return inputList -} - func resultsToString(results *list.List) []string { var pretty []string = make([]string, 0) for el := results.Front(); el != nil; el = el.Next() { diff --git a/pkg/yqlib/document_index_operator.go b/pkg/yqlib/document_index_operator.go new file mode 100644 index 00000000..52ec7a52 --- /dev/null +++ b/pkg/yqlib/document_index_operator.go @@ -0,0 +1,20 @@ +package yqlib + +import ( + "container/list" + "fmt" + + "gopkg.in/yaml.v3" +) + +func GetDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + var results = list.New() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"} + scalar := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(scalar) + } + return results, nil +} diff --git a/pkg/yqlib/document_index_operator_test.go b/pkg/yqlib/document_index_operator_test.go new file mode 100644 index 00000000..1fbea1be --- /dev/null +++ b/pkg/yqlib/document_index_operator_test.go @@ -0,0 +1,32 @@ +package yqlib + +import ( + "testing" +) + +var documentIndexScenarios = []expressionScenario{ + { + description: "Retrieve a document index", + document: "a: cat\n---\na: frog\n", + expression: `.a | documentIndex`, + expected: []string{ + "D0, P[a], (!!int)::0\n", + "D1, P[a], (!!int)::1\n", + }, + }, + { + description: "Filter by document index", + document: "a: cat\n---\na: frog\n", + expression: `select(. | documentIndex == 1)`, + expected: []string{ + "D1, P[], (doc)::a: frog\n", + }, + }, +} + +func TestDocumentIndexScenarios(t *testing.T) { + for _, tt := range documentIndexScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Document Index Operator", documentIndexScenarios) +} diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index fadaf502..6982af1a 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -18,7 +18,6 @@ type OperationType struct { // operators TODO: // - generator doc from operator tests -// - set comments not recursive // - documentIndex - retrieves document index, can be used with select // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) @@ -48,6 +47,7 @@ var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator} var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator} +var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator} var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator} diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 03a32597..692d4ea8 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "os" + "strings" "testing" "github.com/mikefarah/yq/v4/test" @@ -19,14 +20,12 @@ type expressionScenario struct { } func testScenario(t *testing.T, s *expressionScenario) { - - nodes := readDoc(t, s.document) - path, errPath := treeCreator.ParsePath(s.expression) + node, errPath := treeCreator.ParsePath(s.expression) if errPath != nil { t.Error(errPath) return } - results, errNav := treeNavigator.GetMatchingNodes(nodes, path) + results, errNav := EvaluateStream("sample.yaml", strings.NewReader(s.document), node) if errNav != nil { t.Error(errNav) @@ -67,14 +66,13 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari w.WriteString(fmt.Sprintf("Result\n")) - nodes := readDoc(t, s.document) - path, errPath := treeCreator.ParsePath(s.expression) + node, errPath := treeCreator.ParsePath(s.expression) if errPath != nil { t.Error(errPath) return } var output bytes.Buffer - results, err := treeNavigator.GetMatchingNodes(nodes, path) + results, err := EvaluateStream("sample.yaml", strings.NewReader(s.document), node) printer.PrintResults(results, bufio.NewWriter(&output)) w.WriteString(fmt.Sprintf("```yaml\n%v```\n", output.String())) diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 4fa41c0e..a952ff68 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -191,6 +191,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`or`), opToken(Or)) lexer.Add([]byte(`not`), opToken(Not)) + lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex)) + lexer.Add([]byte(`style\s*=`), opToken(AssignStyle)) lexer.Add([]byte(`style`), opToken(GetStyle)) diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index 55dcec6c..9aabd7b3 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -52,7 +52,7 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List, writer io.Writer return nil } - var previousDocIndex uint = 0 + var previousDocIndex uint = matchingNodes.Front().Value.(*CandidateNode).Document for el := matchingNodes.Front(); el != nil; el = el.Next() { mappedDoc := el.Value.(*CandidateNode) diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index 73408d44..459a918a 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -10,7 +10,9 @@ import ( yaml "gopkg.in/yaml.v3" ) -func readStream(filename string) (*yaml.Decoder, error) { +var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) + +func readStream(filename string) (io.Reader, error) { if filename == "" { return nil, errors.New("Must provide filename") } @@ -26,22 +28,15 @@ func readStream(filename string) (*yaml.Decoder, error) { defer safelyCloseFile(file) stream = file } - return yaml.NewDecoder(stream), nil + return stream, nil } -// put this in lib -func Evaluate(filename string, node *PathTreeNode) (*list.List, error) { - - var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) - +func EvaluateStream(filename string, reader io.Reader, node *PathTreeNode) (*list.List, error) { var matchingNodes = list.New() var currentIndex uint = 0 - var decoder, err = readStream(filename) - if err != nil { - return nil, err - } + decoder := yaml.NewDecoder(reader) for { var dataBucket yaml.Node errorReading := decoder.Decode(&dataBucket) @@ -66,8 +61,16 @@ func Evaluate(filename string, node *PathTreeNode) (*list.List, error) { matchingNodes.PushBackList(newMatches) currentIndex = currentIndex + 1 } +} + +func Evaluate(filename string, node *PathTreeNode) (*list.List, error) { + + var reader, err = readStream(filename) + if err != nil { + return nil, err + } + return EvaluateStream(filename, reader, node) - return matchingNodes, nil } func safelyRenameFile(from string, to string) { From 2edf64182bc65cb65831f2180959a9826cbb2398 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 6 Nov 2020 14:37:01 +1100 Subject: [PATCH 065/129] refining --- cmd/constant.go | 15 +----- cmd/evalute_sequence_command.go | 66 +++++++++++++++++++++++ cmd/root.go | 56 ++----------------- pkg/yqlib/data_tree_navigator_test.go | 2 - pkg/yqlib/document_index_operator_test.go | 9 ++++ pkg/yqlib/lib.go | 6 ++- pkg/yqlib/operators_test.go | 30 ++++++----- pkg/yqlib/printer.go | 9 ++++ pkg/yqlib/utils.go | 25 ++++++--- 9 files changed, 129 insertions(+), 89 deletions(-) create mode 100644 cmd/evalute_sequence_command.go diff --git a/cmd/constant.go b/cmd/constant.go index 45400f76..4a17d53e 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -4,29 +4,16 @@ import ( logging "gopkg.in/op/go-logging.v1" ) -var customTag = "" -var printMode = "v" -var printLength = false var unwrapScalar = true -var customStyle = "" -var anchorName = "" -var makeAlias = false var writeInplace = false -var writeScript = "" -var sourceYamlFile = "" var outputToJSON = false var exitStatus = false -var explodeAnchors = false var forceColor = false var forceNoColor = false var colorsEnabled = false -var defaultValue = "" var indent = 2 var printDocSeparators = true -var overwriteFlag = false -var autoCreateFlag = true -var arrayMergeStrategyFlag = "update" -var commentsMergeStrategyFlag = "setWhenBlank" +var nullInput = false var verbose = false var version = false var shellCompletion = "" diff --git a/cmd/evalute_sequence_command.go b/cmd/evalute_sequence_command.go new file mode 100644 index 00000000..fd41bf79 --- /dev/null +++ b/cmd/evalute_sequence_command.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "container/list" + "os" + + "github.com/mikefarah/yq/v4/pkg/yqlib" + "github.com/spf13/cobra" +) + +func createEvaluateSequenceCommand() *cobra.Command { + var cmdEvalSequence = &cobra.Command{ + Use: "eval-seq [expression] [yaml_file1]...", + Aliases: []string{"es"}, + Short: "Apply expression to each document in each yaml file given in sequence", + Example: ` +yq es '.a.b | length' file1.yml file2.yml +yq es < sample.yaml +yq es -n '{"a": "b"}' +`, + Long: "Evaluate Sequence:\nIterate over each yaml document, apply the expression and print the results, in sequence.", + RunE: evaluateSequence, + } + return cmdEvalSequence +} +func evaluateSequence(cmd *cobra.Command, args []string) error { + // 0 args, read std in + // 1 arg, null input, process expression + // 1 arg, read file in sequence + // 2+ args, [0] = expression, file the rest + + var matchingNodes *list.List + var err error + stat, _ := os.Stdin.Stat() + pipingStdIn := (stat.Mode() & os.ModeCharDevice) == 0 + + switch len(args) { + case 0: + if pipingStdIn { + matchingNodes, err = yqlib.Evaluate("-", "") + } else { + cmd.Println(cmd.UsageString()) + return nil + } + case 1: + if nullInput { + matchingNodes, err = yqlib.EvaluateExpression(args[0]) + } else { + matchingNodes, err = yqlib.Evaluate(args[0], "") + } + } + cmd.SilenceUsage = true + if err != nil { + return err + } + out := cmd.OutOrStdout() + + fileInfo, _ := os.Stdout.Stat() + + if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { + colorsEnabled = true + } + printer := yqlib.NewPrinter(outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators) + + return printer.PrintResults(matchingNodes, out) +} diff --git a/cmd/root.go b/cmd/root.go index 17602f65..cae90a58 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,11 +1,9 @@ package cmd import ( - "errors" "fmt" "os" - "github.com/mikefarah/yq/v4/pkg/yqlib" "github.com/spf13/cobra" logging "gopkg.in/op/go-logging.v1" ) @@ -34,51 +32,9 @@ func New() *cobra.Command { return fmt.Errorf("Unknown variant %v", shellCompletion) } } - // if len(args) == 0 { - // cmd.Println(cmd.UsageString()) - // return nil - // } - cmd.SilenceUsage = true + cmd.Println(cmd.UsageString()) + return nil - var treeCreator = yqlib.NewPathTreeCreator() - - expression := "" - if len(args) > 0 { - expression = args[0] - } - - pathNode, err := treeCreator.ParsePath(expression) - if err != nil { - return err - } - - if outputToJSON { - explodeOp := yqlib.Operation{OperationType: yqlib.Explode} - explodeNode := yqlib.PathTreeNode{Operation: &explodeOp} - pipeOp := yqlib.Operation{OperationType: yqlib.Pipe} - pathNode = &yqlib.PathTreeNode{Operation: &pipeOp, Lhs: pathNode, Rhs: &explodeNode} - } - - matchingNodes, err := yqlib.Evaluate("-", pathNode) - if err != nil { - return err - } - - if exitStatus && matchingNodes.Len() == 0 { - cmd.SilenceUsage = true - return errors.New("No matches found") - } - - out := cmd.OutOrStdout() - - fileInfo, _ := os.Stdout.Stat() - - if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { - colorsEnabled = true - } - printer := yqlib.NewPrinter(outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators) - - return printer.PrintResults(matchingNodes, out) }, PersistentPreRun: func(cmd *cobra.Command, args []string) { cmd.SetOut(cmd.OutOrStdout()) @@ -100,6 +56,8 @@ func New() *cobra.Command { rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json. Set indent to 0 to print json in one line.") + rootCmd.PersistentFlags().BoolVarP(&nullInput, "null-input", "n", false, "Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.") + rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") @@ -107,10 +65,6 @@ func New() *cobra.Command { rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors") rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors") - - // rootCmd.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") - rootCmd.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)") - rootCmd.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results") - + rootCmd.AddCommand(createEvaluateSequenceCommand()) return rootCmd } diff --git a/pkg/yqlib/data_tree_navigator_test.go b/pkg/yqlib/data_tree_navigator_test.go index d4b18b32..1e2d7b91 100644 --- a/pkg/yqlib/data_tree_navigator_test.go +++ b/pkg/yqlib/data_tree_navigator_test.go @@ -4,8 +4,6 @@ import ( "container/list" ) -var treeCreator = NewPathTreeCreator() - func resultsToString(results *list.List) []string { var pretty []string = make([]string, 0) for el := results.Front(); el != nil; el = el.Next() { diff --git a/pkg/yqlib/document_index_operator_test.go b/pkg/yqlib/document_index_operator_test.go index 1fbea1be..2a4a18bb 100644 --- a/pkg/yqlib/document_index_operator_test.go +++ b/pkg/yqlib/document_index_operator_test.go @@ -22,6 +22,15 @@ var documentIndexScenarios = []expressionScenario{ "D1, P[], (doc)::a: frog\n", }, }, + { + description: "Print Document Index with matches", + document: "a: cat\n---\na: frog\n", + expression: `.a | {"match": ., "doc": (. | documentIndex)}`, + expected: []string{ + "D0, P[], (!!map)::match: cat\ndoc: 0\n", + "D1, P[], (!!map)::match: frog\ndoc: 1\n", + }, + }, } func TestDocumentIndexScenarios(t *testing.T) { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 6982af1a..044e5648 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -18,7 +18,11 @@ type OperationType struct { // operators TODO: // - generator doc from operator tests -// - documentIndex - retrieves document index, can be used with select +// - slurp - stdin, read in sequence, vs read all +// - write in place +// - get path operator (like doc index) +// - get file index op (like doc index) +// - get file name op (like doc index) // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) // - updateTag - not recursive diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 692d4ea8..7c271ad6 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -3,6 +3,7 @@ package yqlib import ( "bufio" "bytes" + "container/list" "fmt" "os" "strings" @@ -20,15 +21,16 @@ type expressionScenario struct { } func testScenario(t *testing.T, s *expressionScenario) { - node, errPath := treeCreator.ParsePath(s.expression) - if errPath != nil { - t.Error(errPath) - return + var results *list.List + var err error + if s.document != "" { + results, err = EvaluateStream("sample.yaml", strings.NewReader(s.document), s.expression) + } else { + results, err = EvaluateExpression(s.expression) } - results, errNav := EvaluateStream("sample.yaml", strings.NewReader(s.document), node) - if errNav != nil { - t.Error(errNav) + if err != nil { + t.Error(err) return } test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document)) @@ -66,13 +68,15 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari w.WriteString(fmt.Sprintf("Result\n")) - node, errPath := treeCreator.ParsePath(s.expression) - if errPath != nil { - t.Error(errPath) - return - } var output bytes.Buffer - results, err := EvaluateStream("sample.yaml", strings.NewReader(s.document), node) + var results *list.List + var err error + if s.document != "" { + results, err = EvaluateStream("sample.yaml", strings.NewReader(s.document), s.expression) + } else { + results, err = EvaluateExpression(s.expression) + } + printer.PrintResults(results, bufio.NewWriter(&output)) w.WriteString(fmt.Sprintf("```yaml\n%v```\n", output.String())) diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index 9aabd7b3..280669b4 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -43,6 +43,15 @@ func (p *resultsPrinter) writeString(writer io.Writer, txt string) error { } func (p *resultsPrinter) PrintResults(matchingNodes *list.List, writer io.Writer) error { + var err error + if p.outputToJSON { + explodeOp := Operation{OperationType: Explode} + explodeNode := PathTreeNode{Operation: &explodeOp} + matchingNodes, err = treeNavigator.GetMatchingNodes(matchingNodes, &explodeNode) + if err != nil { + return err + } + } bufferedWriter := bufio.NewWriter(writer) defer safelyFlush(bufferedWriter) diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index 459a918a..af104efa 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -3,7 +3,6 @@ package yqlib import ( "bufio" "container/list" - "errors" "io" "os" @@ -11,12 +10,9 @@ import ( ) var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) +var treeCreator = NewPathTreeCreator() func readStream(filename string) (io.Reader, error) { - if filename == "" { - return nil, errors.New("Must provide filename") - } - var stream io.Reader if filename == "-" { stream = bufio.NewReader(os.Stdin) @@ -31,7 +27,20 @@ func readStream(filename string) (io.Reader, error) { return stream, nil } -func EvaluateStream(filename string, reader io.Reader, node *PathTreeNode) (*list.List, error) { +func EvaluateExpression(expression string) (*list.List, error) { + node, err := treeCreator.ParsePath(expression) + if err != nil { + return nil, err + } + return treeNavigator.GetMatchingNodes(list.New(), node) +} + +func EvaluateStream(filename string, reader io.Reader, expression string) (*list.List, error) { + node, err := treeCreator.ParsePath(expression) + if err != nil { + return nil, err + } + var matchingNodes = list.New() var currentIndex uint = 0 @@ -63,13 +72,13 @@ func EvaluateStream(filename string, reader io.Reader, node *PathTreeNode) (*lis } } -func Evaluate(filename string, node *PathTreeNode) (*list.List, error) { +func Evaluate(filename string, expression string) (*list.List, error) { var reader, err = readStream(filename) if err != nil { return nil, err } - return EvaluateStream(filename, reader, node) + return EvaluateStream(filename, reader, expression) } From 708ff02e8d1ea2e372d8c832a9924edc01879d53 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 13 Nov 2020 13:19:54 +1100 Subject: [PATCH 066/129] Fixed collect object for multi doc --- cmd/evaluate_all_command.go | 64 ++++++++++++ cmd/evalute_sequence_command.go | 44 ++++---- cmd/root.go | 2 +- pkg/yqlib/document_index_operator_test.go | 4 +- pkg/yqlib/lib.go | 1 + pkg/yqlib/operator_collect_object.go | 38 ++++++- pkg/yqlib/operator_collect_object_test.go | 37 ++++++- pkg/yqlib/operator_collect_test.go | 14 ++- pkg/yqlib/operator_create_map.go | 70 ++++++++++--- pkg/yqlib/operator_create_map_test.go | 44 ++++++-- pkg/yqlib/operators.go | 4 + pkg/yqlib/operators_test.go | 43 +++++--- pkg/yqlib/path_parse_test.go | 5 + pkg/yqlib/path_postfix.go | 6 ++ pkg/yqlib/path_tokeniser.go | 7 +- pkg/yqlib/printer.go | 25 +++-- pkg/yqlib/utils.go | 116 +++++++++++++++------- 17 files changed, 406 insertions(+), 118 deletions(-) create mode 100644 cmd/evaluate_all_command.go diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go new file mode 100644 index 00000000..e9a717ae --- /dev/null +++ b/cmd/evaluate_all_command.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "os" + + "github.com/mikefarah/yq/v4/pkg/yqlib" + "github.com/spf13/cobra" +) + +func createEvaluateAllCommand() *cobra.Command { + var cmdEvalAll = &cobra.Command{ + Use: "eval-all [expression] [yaml_file1]...", + Aliases: []string{"ea"}, + Short: "Loads all yaml documents of all yaml files and runs expression once", + Example: ` +yq es '.a.b | length' file1.yml file2.yml +yq es < sample.yaml +yq es -n '{"a": "b"}' +`, + Long: "Evaluate All:\nUseful when you need to run an expression across several yaml documents or files. Consumes more memory than eval-seq", + RunE: evaluateAll, + } + return cmdEvalAll +} +func evaluateAll(cmd *cobra.Command, args []string) error { + // 0 args, read std in + // 1 arg, null input, process expression + // 1 arg, read file in sequence + // 2+ args, [0] = expression, file the rest + + var err error + stat, _ := os.Stdin.Stat() + pipingStdIn := (stat.Mode() & os.ModeCharDevice) == 0 + + out := cmd.OutOrStdout() + + fileInfo, _ := os.Stdout.Stat() + + if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { + colorsEnabled = true + } + printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators) + + switch len(args) { + case 0: + if pipingStdIn { + err = yqlib.EvaluateAllFileStreams("", []string{"-"}, printer) + } else { + cmd.Println(cmd.UsageString()) + return nil + } + case 1: + if nullInput { + err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer) + } else { + err = yqlib.EvaluateAllFileStreams("", []string{args[0]}, printer) + } + default: + err = yqlib.EvaluateAllFileStreams(args[0], args[1:len(args)], printer) + } + + cmd.SilenceUsage = true + return err +} diff --git a/cmd/evalute_sequence_command.go b/cmd/evalute_sequence_command.go index fd41bf79..63077186 100644 --- a/cmd/evalute_sequence_command.go +++ b/cmd/evalute_sequence_command.go @@ -1,7 +1,6 @@ package cmd import ( - "container/list" "os" "github.com/mikefarah/yq/v4/pkg/yqlib" @@ -29,30 +28,10 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { // 1 arg, read file in sequence // 2+ args, [0] = expression, file the rest - var matchingNodes *list.List var err error stat, _ := os.Stdin.Stat() pipingStdIn := (stat.Mode() & os.ModeCharDevice) == 0 - switch len(args) { - case 0: - if pipingStdIn { - matchingNodes, err = yqlib.Evaluate("-", "") - } else { - cmd.Println(cmd.UsageString()) - return nil - } - case 1: - if nullInput { - matchingNodes, err = yqlib.EvaluateExpression(args[0]) - } else { - matchingNodes, err = yqlib.Evaluate(args[0], "") - } - } - cmd.SilenceUsage = true - if err != nil { - return err - } out := cmd.OutOrStdout() fileInfo, _ := os.Stdout.Stat() @@ -60,7 +39,26 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { colorsEnabled = true } - printer := yqlib.NewPrinter(outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators) + printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators) - return printer.PrintResults(matchingNodes, out) + switch len(args) { + case 0: + if pipingStdIn { + err = yqlib.EvaluateFileStreamsSequence("", []string{"-"}, printer) + } else { + cmd.Println(cmd.UsageString()) + return nil + } + case 1: + if nullInput { + err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer) + } else { + err = yqlib.EvaluateFileStreamsSequence("", []string{args[0]}, printer) + } + default: + err = yqlib.EvaluateFileStreamsSequence(args[0], args[1:len(args)], printer) + } + + cmd.SilenceUsage = true + return err } diff --git a/cmd/root.go b/cmd/root.go index cae90a58..cdda014c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -65,6 +65,6 @@ func New() *cobra.Command { rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors") rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors") - rootCmd.AddCommand(createEvaluateSequenceCommand()) + rootCmd.AddCommand(createEvaluateSequenceCommand(), createEvaluateAllCommand()) return rootCmd } diff --git a/pkg/yqlib/document_index_operator_test.go b/pkg/yqlib/document_index_operator_test.go index 2a4a18bb..9dd61dd2 100644 --- a/pkg/yqlib/document_index_operator_test.go +++ b/pkg/yqlib/document_index_operator_test.go @@ -25,10 +25,10 @@ var documentIndexScenarios = []expressionScenario{ { description: "Print Document Index with matches", document: "a: cat\n---\na: frog\n", - expression: `.a | {"match": ., "doc": (. | documentIndex)}`, + expression: `.a | ({"match": ., "doc": (. | documentIndex)})`, expected: []string{ "D0, P[], (!!map)::match: cat\ndoc: 0\n", - "D1, P[], (!!map)::match: frog\ndoc: 1\n", + "D0, P[], (!!map)::match: frog\ndoc: 1\n", }, }, } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 044e5648..56030da0 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -62,6 +62,7 @@ var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precede var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator} var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator} var Not = &OperationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: NotOperator} +var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator} var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator} diff --git a/pkg/yqlib/operator_collect_object.go b/pkg/yqlib/operator_collect_object.go index 4845aee7..8cd48e8a 100644 --- a/pkg/yqlib/operator_collect_object.go +++ b/pkg/yqlib/operator_collect_object.go @@ -17,8 +17,44 @@ import ( func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- collectObjectOperation") - return collect(d, list.New(), matchMap) + if matchMap.Len() == 0 { + return list.New(), nil + } + first := matchMap.Front().Value.(*CandidateNode) + var rotated []*list.List = make([]*list.List, len(first.Node.Content)) + + for i := 0; i < len(first.Node.Content); i++ { + rotated[i] = list.New() + } + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidateNode := el.Value.(*CandidateNode) + for i := 0; i < len(first.Node.Content); i++ { + rotated[i].PushBack(createChildCandidate(candidateNode, i)) + } + } + + newObject := list.New() + for i := 0; i < len(first.Node.Content); i++ { + additions, err := collect(d, list.New(), rotated[i]) + if err != nil { + return nil, err + } + newObject.PushBackList(additions) + } + + return newObject, nil + +} + +func createChildCandidate(candidate *CandidateNode, index int) *CandidateNode { + return &CandidateNode{ + Document: candidate.Document, + Path: append(candidate.Path, index), + Filename: candidate.Filename, + Node: candidate.Node.Content[index], + } } func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.List) (*list.List, error) { diff --git a/pkg/yqlib/operator_collect_object_test.go b/pkg/yqlib/operator_collect_object_test.go index 22c99577..58167dc3 100644 --- a/pkg/yqlib/operator_collect_object_test.go +++ b/pkg/yqlib/operator_collect_object_test.go @@ -5,6 +5,26 @@ import ( ) var collectObjectOperatorScenarios = []expressionScenario{ + { + document: ``, + expression: `{}`, + expected: []string{}, + }, + { + document: "{name: Mike}\n", + expression: `{"wrap": .}`, + expected: []string{ + "D0, P[], (!!map)::wrap: {name: Mike}\n", + }, + }, + { + document: "{name: Mike}\n---\n{name: Bob}", + expression: `{"wrap": .}`, + expected: []string{ + "D0, P[], (!!map)::wrap: {name: Mike}\n", + "D0, P[], (!!map)::wrap: {name: Bob}\n", + }, + }, { document: `{name: Mike, age: 32}`, expression: `{.name: .age}`, @@ -20,6 +40,16 @@ var collectObjectOperatorScenarios = []expressionScenario{ "D0, P[], (!!map)::Mike: dog\n", }, }, + { + document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}", + expression: `{.name: .pets[]}`, + expected: []string{ + "D0, P[], (!!map)::Mike: cat\n", + "D0, P[], (!!map)::Mike: dog\n", + "D0, P[], (!!map)::Rosey: monkey\n", + "D0, P[], (!!map)::Rosey: sheep\n", + }, + }, { document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`, expression: `{.name: .pets[], "f":.food[]}`, @@ -55,11 +85,9 @@ b: {cows: [apl, bba]} }, { document: `{name: Mike}`, - expression: `{"wrap": {"further": .}}`, + expression: `{"wrap": {"further": .}} | (.. style= "flow")`, expected: []string{ - `D0, P[], (!!map)::wrap: - further: {name: Mike} -`, + "D0, P[], (!!map)::{wrap: {further: {name: Mike}}}\n", }, }, } @@ -68,4 +96,5 @@ func TestCollectObjectOperatorScenarios(t *testing.T) { for _, tt := range collectObjectOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Collect into Object", collectObjectOperatorScenarios) } diff --git a/pkg/yqlib/operator_collect_test.go b/pkg/yqlib/operator_collect_test.go index a6018183..0f2501fc 100644 --- a/pkg/yqlib/operator_collect_test.go +++ b/pkg/yqlib/operator_collect_test.go @@ -6,25 +6,30 @@ import ( var collectOperatorScenarios = []expressionScenario{ { - document: `{}`, + document: ``, + expression: `[]`, + expected: []string{}, + }, + { + document: ``, expression: `["cat"]`, expected: []string{ "D0, P[], (!!seq)::- cat\n", }, }, { - document: `{}`, + document: ``, expression: `[true]`, expected: []string{ "D0, P[], (!!seq)::- true\n", }, }, { - document: `{}`, + document: ``, expression: `["cat", "dog"]`, expected: []string{ "D0, P[], (!!seq)::- cat\n- dog\n", }, }, { - document: `{}`, + document: ``, expression: `1 | collect`, expected: []string{ "D0, P[], (!!seq)::- 1\n", @@ -48,4 +53,5 @@ func TestCollectOperatorScenarios(t *testing.T) { for _, tt := range collectOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Collect into Array", collectOperatorScenarios) } diff --git a/pkg/yqlib/operator_create_map.go b/pkg/yqlib/operator_create_map.go index 6c4d9949..28cbad9d 100644 --- a/pkg/yqlib/operator_create_map.go +++ b/pkg/yqlib/operator_create_map.go @@ -8,15 +8,50 @@ import ( func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- createMapOperation") - var path []interface{} = nil + + //each matchingNodes entry should turn into a sequence of keys to create. + //then collect object should do a cross function of the same index sequence for all matches. + + var path []interface{} + var document uint = 0 - if matchingNodes.Front() != nil { - sample := matchingNodes.Front().Value.(*CandidateNode) - path = sample.Path - document = sample.Document + + sequences := list.New() + + if matchingNodes.Len() > 0 { + + for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() { + matchingNode := matchingNodeEl.Value.(*CandidateNode) + sequenceNode, err := sequenceFor(d, matchingNode, pathNode) + if err != nil { + return nil, err + } + sequences.PushBack(sequenceNode) + } + } else { + sequenceNode, err := sequenceFor(d, nil, pathNode) + if err != nil { + return nil, err + } + sequences.PushBack(sequenceNode) } - mapPairs, err := crossFunction(d, matchingNodes, pathNode, + return nodeToMap(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil + +} + +func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathTreeNode) (*CandidateNode, error) { + var path []interface{} + var document uint = 0 + var matches = list.New() + + if matchingNode != nil { + path = matchingNode.Path + document = matchingNode.Document + matches = nodeToMap(matchingNode) + } + + mapPairs, err := crossFunction(d, matches, pathNode, func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} log.Debugf("LHS:", NodeToString(lhs)) @@ -32,12 +67,19 @@ func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode if err != nil { return nil, err } - //wrap up all the pairs into an array - node := yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} - for mapPair := mapPairs.Front(); mapPair != nil; mapPair = mapPair.Next() { - mapPairCandidate := mapPair.Value.(*CandidateNode) - log.Debugf("Collecting %v into sequence", NodeToString(mapPairCandidate)) - node.Content = append(node.Content, mapPairCandidate.Node) - } - return nodeToMap(&CandidateNode{Node: &node, Document: document, Path: path}), nil + innerList := listToNodeSeq(mapPairs) + innerList.Style = yaml.FlowStyle + return &CandidateNode{Node: innerList, Document: document, Path: path}, nil +} + +//NOTE: here the document index gets dropped so we +// no longer know where the node originates from. +func listToNodeSeq(list *list.List) *yaml.Node { + node := yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} + for entry := list.Front(); entry != nil; entry = entry.Next() { + entryCandidate := entry.Value.(*CandidateNode) + log.Debugf("Collecting %v into sequence", NodeToString(entryCandidate)) + node.Content = append(node.Content, entryCandidate.Node) + } + return &node } diff --git a/pkg/yqlib/operator_create_map_test.go b/pkg/yqlib/operator_create_map_test.go index 24d25ad6..291abd0d 100644 --- a/pkg/yqlib/operator_create_map_test.go +++ b/pkg/yqlib/operator_create_map_test.go @@ -5,41 +5,71 @@ import ( ) var createMapOperatorScenarios = []expressionScenario{ + { + document: ``, + expression: `"frog": "jumps"`, + expected: []string{ + "D0, P[], (!!seq)::- [{frog: jumps}]\n", + }, + }, { document: `{name: Mike, age: 32}`, expression: `.name: .age`, expected: []string{ - "D0, P[], (!!seq)::- Mike: 32\n", + "D0, P[], (!!seq)::- [{Mike: 32}]\n", }, }, { document: `{name: Mike, pets: [cat, dog]}`, expression: `.name: .pets[]`, expected: []string{ - "D0, P[], (!!seq)::- Mike: cat\n- Mike: dog\n", + "D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n", }, }, { document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`, expression: `.name: .pets[], "f":.food[]`, expected: []string{ - "D0, P[], (!!seq)::- Mike: cat\n- Mike: dog\n", - "D0, P[], (!!seq)::- f: hotdog\n- f: burger\n", + "D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n", + "D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n", + }, + }, + { + document: "{name: Mike, pets: [cat, dog], food: [hotdog, burger]}\n---\n{name: Fred, pets: [mouse], food: [pizza, onion, apple]}", + expression: `.name: .pets[], "f":.food[]`, + expected: []string{ + "D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n- [{Fred: mouse}]\n", + "D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n- [{f: pizza}, {f: onion}, {f: apple}]\n", }, }, { document: `{name: Mike, pets: {cows: [apl, bba]}}`, expression: `"a":.name, "b":.pets`, expected: []string{ - "D0, P[], (!!seq)::- a: Mike\n", - "D0, P[], (!!seq)::- b: {cows: [apl, bba]}\n", + "D0, P[], (!!seq)::- [{a: Mike}]\n", + "D0, P[], (!!seq)::- [{b: {cows: [apl, bba]}}]\n", }, }, { document: `{name: Mike}`, expression: `"wrap": .`, expected: []string{ - "D0, P[], (!!seq)::- wrap: {name: Mike}\n", + "D0, P[], (!!seq)::- [{wrap: {name: Mike}}]\n", + }, + }, + { + document: "{name: Mike}\n---\n{name: Bob}", + expression: `"wrap": .`, + expected: []string{ + "D0, P[], (!!seq)::- [{wrap: {name: Mike}}]\n- [{wrap: {name: Bob}}]\n", + }, + }, + { + document: "{name: Mike}\n---\n{name: Bob}", + expression: `"wrap": ., .name: "great"`, + expected: []string{ + "D0, P[], (!!seq)::- [{wrap: {name: Mike}}]\n- [{wrap: {name: Bob}}]\n", + "D0, P[], (!!seq)::- [{Mike: great}]\n- [{Bob: great}]\n", }, }, } diff --git a/pkg/yqlib/operators.go b/pkg/yqlib/operators.go index b1dfc1b7..f99bca11 100644 --- a/pkg/yqlib/operators.go +++ b/pkg/yqlib/operators.go @@ -16,6 +16,10 @@ func UnwrapDoc(node *yaml.Node) *yaml.Node { return node } +func EmptyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + return list.New(), nil +} + func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 7c271ad6..350a3b61 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -23,11 +23,23 @@ type expressionScenario struct { func testScenario(t *testing.T, s *expressionScenario) { var results *list.List var err error - if s.document != "" { - results, err = EvaluateStream("sample.yaml", strings.NewReader(s.document), s.expression) - } else { - results, err = EvaluateExpression(s.expression) + + node, err := treeCreator.ParsePath(s.expression) + if err != nil { + t.Error(err) + return } + inputs := list.New() + + if s.document != "" { + inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml") + if err != nil { + t.Error(err) + return + } + } + + results, err = treeNavigator.GetMatchingNodes(inputs, node) if err != nil { t.Error(err) @@ -40,15 +52,13 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari f, err := os.Create(fmt.Sprintf("doc/%v.md", title)) if err != nil { - panic(err) + t.Error(err) } defer f.Close() w := bufio.NewWriter(f) w.WriteString(fmt.Sprintf("# %v\n", title)) w.WriteString(fmt.Sprintf("## Examples\n")) - printer := NewPrinter(false, true, false, 2, true) - for index, s := range scenarios { if !s.skipDoc { @@ -69,20 +79,23 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari w.WriteString(fmt.Sprintf("Result\n")) var output bytes.Buffer - var results *list.List var err error - if s.document != "" { - results, err = EvaluateStream("sample.yaml", strings.NewReader(s.document), s.expression) - } else { - results, err = EvaluateExpression(s.expression) - } + printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true) - printer.PrintResults(results, bufio.NewWriter(&output)) + if s.document != "" { + node, err := treeCreator.ParsePath(s.expression) + if err != nil { + t.Error(err) + } + err = EvaluateStream("sample.yaml", strings.NewReader(s.document), node, printer) + } else { + err = EvaluateAllFileStreams(s.expression, []string{}, printer) + } w.WriteString(fmt.Sprintf("```yaml\n%v```\n", output.String())) if err != nil { - panic(err) + t.Error(err) } } diff --git a/pkg/yqlib/path_parse_test.go b/pkg/yqlib/path_parse_test.go index cc2e2e3b..45037f35 100644 --- a/pkg/yqlib/path_parse_test.go +++ b/pkg/yqlib/path_parse_test.go @@ -114,6 +114,11 @@ var pathTests = []struct { append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"), append(make([]interface{}, 0), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"), }, + { + `{}`, + append(make([]interface{}, 0), "{", "}"), + append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "PIPE"), + }, // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, // {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")}, diff --git a/pkg/yqlib/path_postfix.go b/pkg/yqlib/path_postfix.go index 992a48ee..1f360e4d 100644 --- a/pkg/yqlib/path_postfix.go +++ b/pkg/yqlib/path_postfix.go @@ -41,8 +41,14 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, er opener = OpenCollectObject collectOperator = CollectObject } + itemsInMiddle := false for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener { opStack, result = popOpToResult(opStack, result) + itemsInMiddle = true + } + if !itemsInMiddle { + // must be an empty collection, add the empty object as a LHS parameter + result = append(result, &Operation{OperationType: Empty}) } if len(opStack) == 0 { return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket") diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index a952ff68..e179ab73 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -174,7 +174,6 @@ func selfToken() lex.Action { } } -// Creates the lexer object and compiles the NFA. func initLexer() (*lex.Lexer, error) { lexer := lex.NewLexer() lexer.Add([]byte(`\(`), literalToken(OpenBracket, false)) @@ -206,9 +205,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{FootComment: true})) lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true})) - // lexer.Add([]byte(`style`), opToken(GetStyle)) - // lexer.Add([]byte(`and`), opToken()) lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) @@ -222,8 +219,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte("( |\t|\n|\r)+"), skip) - lexer.Add([]byte(`d[0-9]+`), documentToken()) // $0 - + lexer.Add([]byte(`d[0-9]+`), documentToken()) lexer.Add([]byte(`\."[^ "]+"`), pathToken(true)) lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false)) lexer.Add([]byte(`\.`), selfToken()) @@ -248,7 +244,6 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true)) lexer.Add([]byte(`\*`), opToken(Multiply)) - // lexer.Add([]byte(`[^ \,\|\.\[\(\)=]+`), stringValue(false)) err := lexer.Compile() if err != nil { return nil, err diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index 280669b4..eb5d8a84 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -9,7 +9,7 @@ import ( ) type Printer interface { - PrintResults(matchingNodes *list.List, writer io.Writer) error + PrintResults(matchingNodes *list.List) error } type resultsPrinter struct { @@ -18,10 +18,20 @@ type resultsPrinter struct { colorsEnabled bool indent int printDocSeparators bool + writer io.Writer + firstTimePrinting bool } -func NewPrinter(outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer { - return &resultsPrinter{outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators} +func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer { + return &resultsPrinter{ + writer: writer, + outputToJSON: outputToJSON, + unwrapScalar: unwrapScalar, + colorsEnabled: colorsEnabled, + indent: indent, + printDocSeparators: printDocSeparators, + firstTimePrinting: true, + } } func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error { @@ -42,7 +52,7 @@ func (p *resultsPrinter) writeString(writer io.Writer, txt string) error { return errorWriting } -func (p *resultsPrinter) PrintResults(matchingNodes *list.List, writer io.Writer) error { +func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { var err error if p.outputToJSON { explodeOp := Operation{OperationType: Explode} @@ -53,7 +63,7 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List, writer io.Writer } } - bufferedWriter := bufio.NewWriter(writer) + bufferedWriter := bufio.NewWriter(p.writer) defer safelyFlush(bufferedWriter) if matchingNodes.Len() == 0 { @@ -61,12 +71,12 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List, writer io.Writer return nil } - var previousDocIndex uint = matchingNodes.Front().Value.(*CandidateNode).Document + previousDocIndex := matchingNodes.Front().Value.(*CandidateNode).Document for el := matchingNodes.Front(); el != nil; el = el.Next() { mappedDoc := el.Value.(*CandidateNode) - if previousDocIndex != mappedDoc.Document && p.printDocSeparators { + if (!p.firstTimePrinting || (previousDocIndex != mappedDoc.Document)) && p.printDocSeparators { p.writeString(bufferedWriter, "---\n") } @@ -76,6 +86,7 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List, writer io.Writer previousDocIndex = mappedDoc.Document } + p.firstTimePrinting = false return nil } diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index af104efa..83dcf82f 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -13,35 +13,14 @@ var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) var treeCreator = NewPathTreeCreator() func readStream(filename string) (io.Reader, error) { - var stream io.Reader if filename == "-" { - stream = bufio.NewReader(os.Stdin) + return bufio.NewReader(os.Stdin), nil } else { - file, err := os.Open(filename) // nolint gosec - if err != nil { - return nil, err - } - defer safelyCloseFile(file) - stream = file + return os.Open(filename) // nolint gosec } - return stream, nil } -func EvaluateExpression(expression string) (*list.List, error) { - node, err := treeCreator.ParsePath(expression) - if err != nil { - return nil, err - } - return treeNavigator.GetMatchingNodes(list.New(), node) -} - -func EvaluateStream(filename string, reader io.Reader, expression string) (*list.List, error) { - node, err := treeCreator.ParsePath(expression) - if err != nil { - return nil, err - } - - var matchingNodes = list.New() +func EvaluateStream(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error { var currentIndex uint = 0 @@ -51,9 +30,9 @@ func EvaluateStream(filename string, reader io.Reader, expression string) (*list errorReading := decoder.Decode(&dataBucket) if errorReading == io.EOF { - return matchingNodes, nil + return nil } else if errorReading != nil { - return nil, errorReading + return errorReading } candidateNode := &CandidateNode{ Document: currentIndex, @@ -63,23 +42,92 @@ func EvaluateStream(filename string, reader io.Reader, expression string) (*list inputList := list.New() inputList.PushBack(candidateNode) - newMatches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node) + matches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node) if errorParsing != nil { - return nil, errorParsing + return errorParsing } - matchingNodes.PushBackList(newMatches) + printer.PrintResults(matches) currentIndex = currentIndex + 1 } } -func Evaluate(filename string, expression string) (*list.List, error) { +func readDocuments(reader io.Reader, filename string) (*list.List, error) { + decoder := yaml.NewDecoder(reader) + inputList := list.New() + var currentIndex uint = 0 - var reader, err = readStream(filename) - if err != nil { - return nil, err + for { + var dataBucket yaml.Node + errorReading := decoder.Decode(&dataBucket) + + if errorReading == io.EOF { + switch reader.(type) { + case *os.File: + safelyCloseFile(reader.(*os.File)) + } + return inputList, nil + } else if errorReading != nil { + return nil, errorReading + } + candidateNode := &CandidateNode{ + Document: currentIndex, + Filename: filename, + Node: &dataBucket, + } + + inputList.PushBack(candidateNode) + + currentIndex = currentIndex + 1 } - return EvaluateStream(filename, reader, expression) +} +func EvaluateAllFileStreams(expression string, filenames []string, printer Printer) error { + node, err := treeCreator.ParsePath(expression) + if err != nil { + return err + } + var allDocuments *list.List = list.New() + for _, filename := range filenames { + reader, err := readStream(filename) + if err != nil { + return err + } + fileDocuments, err := readDocuments(reader, filename) + if err != nil { + return err + } + allDocuments.PushBackList(fileDocuments) + } + matches, err := treeNavigator.GetMatchingNodes(allDocuments, node) + if err != nil { + return err + } + return printer.PrintResults(matches) +} + +func EvaluateFileStreamsSequence(expression string, filenames []string, printer Printer) error { + + node, err := treeCreator.ParsePath(expression) + if err != nil { + return err + } + + for _, filename := range filenames { + reader, err := readStream(filename) + if err != nil { + return err + } + err = EvaluateStream(filename, reader, node, printer) + if err != nil { + return err + } + + switch reader.(type) { + case *os.File: + safelyCloseFile(reader.(*os.File)) + } + } + return nil } func safelyRenameFile(from string, to string) { From f8a700e4005dbf47528eaee3060e90ae5d41b5e4 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 13 Nov 2020 13:35:59 +1100 Subject: [PATCH 067/129] Alpha1 of v4! --- cmd/constant.go | 2 +- cmd/evaluate_all_command.go | 2 +- cmd/evalute_sequence_command.go | 2 +- cmd/root.go | 1 + cmd/version.go | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/constant.go b/cmd/constant.go index 4a17d53e..d2774573 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -12,7 +12,7 @@ var forceColor = false var forceNoColor = false var colorsEnabled = false var indent = 2 -var printDocSeparators = true +var noDocSeparators = false var nullInput = false var verbose = false var version = false diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index e9a717ae..246ddf27 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -39,7 +39,7 @@ func evaluateAll(cmd *cobra.Command, args []string) error { if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { colorsEnabled = true } - printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators) + printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators) switch len(args) { case 0: diff --git a/cmd/evalute_sequence_command.go b/cmd/evalute_sequence_command.go index 63077186..ab9480e9 100644 --- a/cmd/evalute_sequence_command.go +++ b/cmd/evalute_sequence_command.go @@ -39,7 +39,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { colorsEnabled = true } - printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, printDocSeparators) + printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators) switch len(args) { case 0: diff --git a/cmd/root.go b/cmd/root.go index cdda014c..4a225ef4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -57,6 +57,7 @@ func New() *cobra.Command { rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json. Set indent to 0 to print json in one line.") rootCmd.PersistentFlags().BoolVarP(&nullInput, "null-input", "n", false, "Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.") + rootCmd.PersistentFlags().BoolVarP(&noDocSeparators, "no-doc", "N", false, "Don't print document separators (---)") rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") diff --git a/cmd/version.go b/cmd/version.go index 394dc638..85d92382 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -11,7 +11,7 @@ var ( GitDescribe string // Version is main version number that is being run at the moment. - Version = "4.0.0-beta" + Version = "4.0.0-alpha1" // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release From af39fc737dc49317dafe3adf99fb24749f759482 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 13 Nov 2020 14:07:11 +1100 Subject: [PATCH 068/129] Fixed linting --- Dockerfile | 2 + cmd/constant.go | 13 +++-- cmd/evaluate_all_command.go | 6 +-- cmd/evalute_sequence_command.go | 6 +-- go.mod | 4 +- go.sum | 15 ++---- pkg/yqlib/candidate_node.go | 9 ++-- pkg/yqlib/operator_collect_object.go | 8 ++- pkg/yqlib/operator_explode.go | 15 ++++-- pkg/yqlib/operator_multilpy.go | 5 +- pkg/yqlib/operator_traverse_path.go | 16 ++++-- pkg/yqlib/operators_test.go | 36 ++++++++----- pkg/yqlib/operatory_style.go | 2 +- pkg/yqlib/path_tokeniser.go | 7 ++- pkg/yqlib/path_tree.go | 4 +- pkg/yqlib/printer.go | 5 +- pkg/yqlib/utils.go | 81 ++++++++++++++-------------- scripts/acceptance.sh | 2 +- 18 files changed, 135 insertions(+), 101 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1858eea9..43861724 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,3 +21,5 @@ ARG VERSION=none LABEL version=${VERSION} WORKDIR /workdir + +ENTRYPOINT [/usr/bin/yq] \ No newline at end of file diff --git a/cmd/constant.go b/cmd/constant.go index d2774573..9e48f52d 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -1,13 +1,11 @@ package cmd -import ( - logging "gopkg.in/op/go-logging.v1" -) - var unwrapScalar = true -var writeInplace = false + +// var writeInplace = false var outputToJSON = false -var exitStatus = false + +// var exitStatus = false var forceColor = false var forceNoColor = false var colorsEnabled = false @@ -17,4 +15,5 @@ var nullInput = false var verbose = false var version = false var shellCompletion = "" -var log = logging.MustGetLogger("yq") + +// var log = logging.MustGetLogger("yq") diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index 246ddf27..312688e4 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -11,13 +11,13 @@ func createEvaluateAllCommand() *cobra.Command { var cmdEvalAll = &cobra.Command{ Use: "eval-all [expression] [yaml_file1]...", Aliases: []string{"ea"}, - Short: "Loads all yaml documents of all yaml files and runs expression once", + Short: "Loads _all_ yaml documents of _all_ yaml files and runs expression once", Example: ` yq es '.a.b | length' file1.yml file2.yml yq es < sample.yaml yq es -n '{"a": "b"}' `, - Long: "Evaluate All:\nUseful when you need to run an expression across several yaml documents or files. Consumes more memory than eval-seq", + Long: "Evaluate All:\nUseful when you need to run an expression across several yaml documents or files. Consumes more memory than eval", RunE: evaluateAll, } return cmdEvalAll @@ -56,7 +56,7 @@ func evaluateAll(cmd *cobra.Command, args []string) error { err = yqlib.EvaluateAllFileStreams("", []string{args[0]}, printer) } default: - err = yqlib.EvaluateAllFileStreams(args[0], args[1:len(args)], printer) + err = yqlib.EvaluateAllFileStreams(args[0], args[1:], printer) } cmd.SilenceUsage = true diff --git a/cmd/evalute_sequence_command.go b/cmd/evalute_sequence_command.go index ab9480e9..191293b0 100644 --- a/cmd/evalute_sequence_command.go +++ b/cmd/evalute_sequence_command.go @@ -9,8 +9,8 @@ import ( func createEvaluateSequenceCommand() *cobra.Command { var cmdEvalSequence = &cobra.Command{ - Use: "eval-seq [expression] [yaml_file1]...", - Aliases: []string{"es"}, + Use: "eval [expression] [yaml_file1]...", + Aliases: []string{"e"}, Short: "Apply expression to each document in each yaml file given in sequence", Example: ` yq es '.a.b | length' file1.yml file2.yml @@ -56,7 +56,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { err = yqlib.EvaluateFileStreamsSequence("", []string{args[0]}, printer) } default: - err = yqlib.EvaluateFileStreamsSequence(args[0], args[1:len(args)], printer) + err = yqlib.EvaluateFileStreamsSequence(args[0], args[1:], printer) } cmd.SilenceUsage = true diff --git a/go.mod b/go.mod index d0f3f311..f3b8cb39 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,12 @@ require ( github.com/fatih/color v1.9.0 github.com/goccy/go-yaml v1.8.1 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a - github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.7 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 // indirect github.com/timtadh/data-structures v0.5.3 // indirect github.com/timtadh/lexmachine v0.2.2 - golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect + golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 diff --git a/go.sum b/go.sum index 524e4955..fe09b6de 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -62,8 +63,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= @@ -77,18 +76,13 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mikefarah/yq v1.15.0 h1:ViMYNRG5UB7hzm8olxMFqPtkpMXXKO4g32/v9JUa62o= -github.com/mikefarah/yq v2.4.0+incompatible h1:oBxbWy8R9hI3BIUUxEf0CzikWa2AgnGrGhvGQt5jgjk= -github.com/mikefarah/yq/v3 v3.0.0-20201020025845-ccb718cd0f59 h1:6nvF+EEFIVD4KT64CgvAzWaMVC283Huno59khWs9r1A= -github.com/mikefarah/yq/v3 v3.0.0-20201020025845-ccb718cd0f59/go.mod h1:7eVjFf5bgozMuHk+oKpyxR2zCN3iEN1tF0/bM5jvtKo= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -118,6 +112,7 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ= github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU= @@ -156,8 +151,8 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/pkg/yqlib/candidate_node.go b/pkg/yqlib/candidate_node.go index 1f9daad4..49a3df94 100644 --- a/pkg/yqlib/candidate_node.go +++ b/pkg/yqlib/candidate_node.go @@ -20,10 +20,13 @@ func (n *CandidateNode) GetKey() string { return fmt.Sprintf("%v - %v", n.Document, n.Path) } -func (n *CandidateNode) Copy() *CandidateNode { +func (n *CandidateNode) Copy() (*CandidateNode, error) { clone := &CandidateNode{} - copier.Copy(clone, n) - return clone + err := copier.Copy(clone, n) + if err != nil { + return nil, err + } + return clone, nil } // updates this candidate from the given candidate node diff --git a/pkg/yqlib/operator_collect_object.go b/pkg/yqlib/operator_collect_object.go index 8cd48e8a..54eda111 100644 --- a/pkg/yqlib/operator_collect_object.go +++ b/pkg/yqlib/operator_collect_object.go @@ -83,10 +83,14 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list. aggCandidate := el.Value.(*CandidateNode) for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() { splatCandidate := splatEl.Value.(*CandidateNode) - newCandidate := aggCandidate.Copy() + newCandidate, err := aggCandidate.Copy() + if err != nil { + return nil, err + } + newCandidate.Path = nil - newCandidate, err := multiply(d, newCandidate, splatCandidate) + newCandidate, err = multiply(d, newCandidate, splatCandidate) if err != nil { return nil, err } diff --git a/pkg/yqlib/operator_explode.go b/pkg/yqlib/operator_explode.go index 1d67c484..b678f61b 100644 --- a/pkg/yqlib/operator_explode.go +++ b/pkg/yqlib/operator_explode.go @@ -18,7 +18,10 @@ func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTr return nil, err } for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() { - explodeNode(childEl.Value.(*CandidateNode).Node) + err = explodeNode(childEl.Value.(*CandidateNode).Node) + if err != nil { + return nil, err + } } } @@ -65,11 +68,17 @@ func explodeNode(node *yaml.Node) error { log.Debugf("an alias merge list!") for index := 0; index < len(valueNode.Content); index = index + 1 { aliasNode := valueNode.Content[index] - applyAlias(node, aliasNode.Alias, index, newContent) + err := applyAlias(node, aliasNode.Alias, index, newContent) + if err != nil { + return err + } } } else { log.Debugf("an alias merge!") - applyAlias(node, valueNode.Alias, index, newContent) + err := applyAlias(node, valueNode.Alias, index, newContent) + if err != nil { + return err + } } } } diff --git a/pkg/yqlib/operator_multilpy.go b/pkg/yqlib/operator_multilpy.go index b97a4364..f18ba447 100644 --- a/pkg/yqlib/operator_multilpy.go +++ b/pkg/yqlib/operator_multilpy.go @@ -72,7 +72,10 @@ func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*Ca func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { var results = list.New() - recursiveDecent(d, results, nodeToMap(rhs)) + err := recursiveDecent(d, results, nodeToMap(rhs)) + if err != nil { + return nil, err + } var pathIndexToStartFrom int = 0 if results.Front() != nil { diff --git a/pkg/yqlib/operator_traverse_path.go b/pkg/yqlib/operator_traverse_path.go index 08626f1b..574cfb36 100644 --- a/pkg/yqlib/operator_traverse_path.go +++ b/pkg/yqlib/operator_traverse_path.go @@ -136,7 +136,10 @@ func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, op //skip the 'merge' tag, find a direct match first if key.Tag == "!!merge" && followAlias { log.Debug("Merge anchor") - traverseMergeAnchor(newMatches, candidate, value, operation) + err := traverseMergeAnchor(newMatches, candidate, value, operation) + if err != nil { + return err + } } else if keyMatches(key, operation) { log.Debug("MATCHED") candidateNode := &CandidateNode{ @@ -151,7 +154,7 @@ func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, op return nil } -func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) { +func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) error { switch value.Kind { case yaml.AliasNode: candidateNode := &CandidateNode{ @@ -159,13 +162,16 @@ func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *C Path: originalCandidate.Path, Document: originalCandidate.Document, } - traverseMap(newMatches, candidateNode, operation) + return traverseMap(newMatches, candidateNode, operation) case yaml.SequenceNode: for _, childValue := range value.Content { - traverseMergeAnchor(newMatches, originalCandidate, childValue, operation) + err := traverseMergeAnchor(newMatches, originalCandidate, childValue, operation) + if err != nil { + return err + } } } - return + return nil } func traverseArray(candidate *CandidateNode, operation *Operation) ([]*CandidateNode, error) { diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 350a3b61..81e0bfd8 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -48,6 +48,13 @@ func testScenario(t *testing.T, s *expressionScenario) { test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document)) } +func writeOrPanic(w *bufio.Writer, text string) { + _, err := w.WriteString(text) + if err != nil { + panic(err) + } +} + func documentScenarios(t *testing.T, title string, scenarios []expressionScenario) { f, err := os.Create(fmt.Sprintf("doc/%v.md", title)) @@ -56,27 +63,27 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari } defer f.Close() w := bufio.NewWriter(f) - w.WriteString(fmt.Sprintf("# %v\n", title)) - w.WriteString(fmt.Sprintf("## Examples\n")) + writeOrPanic(w, fmt.Sprintf("# %v\n", title)) + writeOrPanic(w, "## Examples\n") for index, s := range scenarios { if !s.skipDoc { if s.description != "" { - w.WriteString(fmt.Sprintf("### %v\n", s.description)) + writeOrPanic(w, fmt.Sprintf("### %v\n", s.description)) } else { - w.WriteString(fmt.Sprintf("### Example %v\n", index)) + writeOrPanic(w, fmt.Sprintf("### Example %v\n", index)) } if s.document != "" { - w.WriteString(fmt.Sprintf("sample.yml:\n")) - w.WriteString(fmt.Sprintf("```yaml\n%v\n```\n", s.document)) + writeOrPanic(w, "sample.yml:\n") + writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.document)) } if s.expression != "" { - w.WriteString(fmt.Sprintf("Expression\n")) - w.WriteString(fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression)) + writeOrPanic(w, "Expression\n") + writeOrPanic(w, fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression)) } - w.WriteString(fmt.Sprintf("Result\n")) + writeOrPanic(w, "Result\n") var output bytes.Buffer var err error @@ -88,15 +95,18 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari t.Error(err) } err = EvaluateStream("sample.yaml", strings.NewReader(s.document), node, printer) + if err != nil { + t.Error(err) + } } else { err = EvaluateAllFileStreams(s.expression, []string{}, printer) + if err != nil { + t.Error(err) + } } - w.WriteString(fmt.Sprintf("```yaml\n%v```\n", output.String())) + writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", output.String())) - if err != nil { - t.Error(err) - } } } diff --git a/pkg/yqlib/operatory_style.go b/pkg/yqlib/operatory_style.go index 872b0ada..b2ace2fd 100644 --- a/pkg/yqlib/operatory_style.go +++ b/pkg/yqlib/operatory_style.go @@ -59,7 +59,7 @@ func GetStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode * for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - var style = "" + var style string switch candidate.Node.Style { case yaml.TaggedStyle: style = "tagged" diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index e179ab73..aa794845 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -1,7 +1,6 @@ package yqlib import ( - "fmt" "strconv" lex "github.com/timtadh/lexmachine" @@ -47,14 +46,14 @@ func (t *Token) toString() string { } else if t.TokenType == CloseCollectObject { return "}" } else { - return fmt.Sprintf("NFI") + return "NFI" } } func pathToken(wrapped bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) - value = value[1:len(value)] + value = value[1:] if wrapped { value = unwrap(value) } @@ -73,7 +72,7 @@ func literalPathToken(value string) lex.Action { func documentToken() lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { var numberString = string(m.Bytes) - numberString = numberString[1:len(numberString)] + numberString = numberString[1:] var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint if errParsingInt != nil { return nil, errParsingInt diff --git a/pkg/yqlib/path_tree.go b/pkg/yqlib/path_tree.go index aca5b386..3cb5ada8 100644 --- a/pkg/yqlib/path_tree.go +++ b/pkg/yqlib/path_tree.go @@ -7,8 +7,8 @@ var myPathPostfixer = NewPathPostFixer() type PathTreeNode struct { Operation *Operation - Lhs *PathTreeNode - Rhs *PathTreeNode + Lhs *PathTreeNode + Rhs *PathTreeNode } type PathTreeCreator interface { diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index eb5d8a84..89c473dd 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -77,7 +77,10 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { mappedDoc := el.Value.(*CandidateNode) if (!p.firstTimePrinting || (previousDocIndex != mappedDoc.Document)) && p.printDocSeparators { - p.writeString(bufferedWriter, "---\n") + if err := p.writeString(bufferedWriter, "---\n"); err != nil { + return err + } + } if err := p.printNode(mappedDoc.Node, bufferedWriter); err != nil { diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index 83dcf82f..0d41dfe1 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -46,7 +46,10 @@ func EvaluateStream(filename string, reader io.Reader, node *PathTreeNode, print if errorParsing != nil { return errorParsing } - printer.PrintResults(matches) + err := printer.PrintResults(matches) + if err != nil { + return err + } currentIndex = currentIndex + 1 } } @@ -61,9 +64,9 @@ func readDocuments(reader io.Reader, filename string) (*list.List, error) { errorReading := decoder.Decode(&dataBucket) if errorReading == io.EOF { - switch reader.(type) { + switch reader := reader.(type) { case *os.File: - safelyCloseFile(reader.(*os.File)) + safelyCloseFile(reader) } return inputList, nil } else if errorReading != nil { @@ -122,49 +125,49 @@ func EvaluateFileStreamsSequence(expression string, filenames []string, printer return err } - switch reader.(type) { + switch reader := reader.(type) { case *os.File: - safelyCloseFile(reader.(*os.File)) + safelyCloseFile(reader) } } return nil } -func safelyRenameFile(from string, to string) { - if renameError := os.Rename(from, to); renameError != nil { - log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) - log.Debug(renameError.Error()) - // can't do this rename when running in docker to a file targeted in a mounted volume, - // so gracefully degrade to copying the entire contents. - if copyError := copyFileContents(from, to); copyError != nil { - log.Errorf("Failed copying from %v to %v", from, to) - log.Error(copyError.Error()) - } else { - removeErr := os.Remove(from) - if removeErr != nil { - log.Errorf("failed removing original file: %s", from) - } - } - } -} +// func safelyRenameFile(from string, to string) { +// if renameError := os.Rename(from, to); renameError != nil { +// log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) +// log.Debug(renameError.Error()) +// // can't do this rename when running in docker to a file targeted in a mounted volume, +// // so gracefully degrade to copying the entire contents. +// if copyError := copyFileContents(from, to); copyError != nil { +// log.Errorf("Failed copying from %v to %v", from, to) +// log.Error(copyError.Error()) +// } else { +// removeErr := os.Remove(from) +// if removeErr != nil { +// log.Errorf("failed removing original file: %s", from) +// } +// } +// } +// } -// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang -func copyFileContents(src, dst string) (err error) { - in, err := os.Open(src) // nolint gosec - if err != nil { - return err - } - defer safelyCloseFile(in) - out, err := os.Create(dst) - if err != nil { - return err - } - defer safelyCloseFile(out) - if _, err = io.Copy(out, in); err != nil { - return err - } - return out.Sync() -} +// // thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang +// func copyFileContents(src, dst string) (err error) { +// in, err := os.Open(src) // nolint gosec +// if err != nil { +// return err +// } +// defer safelyCloseFile(in) +// out, err := os.Create(dst) +// if err != nil { +// return err +// } +// defer safelyCloseFile(out) +// if _, err = io.Copy(out, in); err != nil { +// return err +// } +// return out.Sync() +// } func safelyFlush(writer *bufio.Writer) { if err := writer.Flush(); err != nil { diff --git a/scripts/acceptance.sh b/scripts/acceptance.sh index 309d588b..d6981033 100755 --- a/scripts/acceptance.sh +++ b/scripts/acceptance.sh @@ -3,7 +3,7 @@ set -e # acceptance test -X=$(./yq w ./examples/sample.yaml b.c 3 | ./yq r - b.c) +X=$(./yq e '.b.c |= 3' ./examples/sample.yaml | ./yq e '.b.c' -) if [[ $X != 3 ]]; then echo "Failed acceptance test: expected 3 but was $X" From 019acfe456a848377de2843efab1e915c0603eb9 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 13 Nov 2020 20:58:01 +1100 Subject: [PATCH 069/129] Better documentation generation --- pkg/yqlib/operator_equals_test.go | 17 ++++++++----- pkg/yqlib/operators_test.go | 41 +++++++++++++++++++++++++------ pkg/yqlib/utils.go | 34 ++++++++++++------------- 3 files changed, 62 insertions(+), 30 deletions(-) diff --git a/pkg/yqlib/operator_equals_test.go b/pkg/yqlib/operator_equals_test.go index 39e05456..a2d0ea15 100644 --- a/pkg/yqlib/operator_equals_test.go +++ b/pkg/yqlib/operator_equals_test.go @@ -6,22 +6,25 @@ import ( var equalsOperatorScenarios = []expressionScenario{ { - document: `[cat,goat,dog]`, - expression: `.[] | (. == "*at")`, + description: "Match string", + document: `[cat,goat,dog]`, + expression: `.[] | (. == "*at")`, expected: []string{ "D0, P[0], (!!bool)::true\n", "D0, P[1], (!!bool)::true\n", "D0, P[2], (!!bool)::false\n", }, }, { - document: `[3, 4, 5]`, - expression: `.[] | (. == 4)`, + description: "Match number", + document: `[3, 4, 5]`, + expression: `.[] | (. == 4)`, expected: []string{ "D0, P[0], (!!bool)::false\n", "D0, P[1], (!!bool)::true\n", "D0, P[2], (!!bool)::false\n", }, }, { + skipDoc: true, document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, expression: `.a | (.[].b == "apple")`, expected: []string{ @@ -30,6 +33,7 @@ var equalsOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: ``, expression: `null == null`, expected: []string{ @@ -37,8 +41,9 @@ var equalsOperatorScenarios = []expressionScenario{ }, }, { - document: ``, - expression: `null == ~`, + description: "Match nulls", + document: ``, + expression: `null == ~`, expected: []string{ "D0, P[], (!!bool)::true\n", }, diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 81e0bfd8..ce7e99e3 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -5,6 +5,7 @@ import ( "bytes" "container/list" "fmt" + "io" "os" "strings" "testing" @@ -55,35 +56,61 @@ func writeOrPanic(w *bufio.Writer, text string) { } } +func copyFromHeader(title string, out *os.File) (bool, error) { + source := fmt.Sprintf("doc/headers/%v.md", title) + _, err := os.Stat(source) + if os.IsNotExist(err) { + return false, nil + } + in, err := os.Open(source) // nolint gosec + if err != nil { + return false, err + } + defer safelyCloseFile(in) + _, err = io.Copy(out, in) + return true, err +} + func documentScenarios(t *testing.T, title string, scenarios []expressionScenario) { f, err := os.Create(fmt.Sprintf("doc/%v.md", title)) if err != nil { t.Error(err) + return } defer f.Close() + + hasHeader, err := copyFromHeader(title, f) + if err != nil { + t.Error(err) + return + } + w := bufio.NewWriter(f) - writeOrPanic(w, fmt.Sprintf("# %v\n", title)) - writeOrPanic(w, "## Examples\n") + + if !hasHeader { + writeOrPanic(w, fmt.Sprintf("## %v\n", title)) + } for index, s := range scenarios { if !s.skipDoc { if s.description != "" { - writeOrPanic(w, fmt.Sprintf("### %v\n", s.description)) + writeOrPanic(w, fmt.Sprintf("## %v\n", s.description)) } else { - writeOrPanic(w, fmt.Sprintf("### Example %v\n", index)) + writeOrPanic(w, fmt.Sprintf("## Example %v\n", index)) } if s.document != "" { - writeOrPanic(w, "sample.yml:\n") + //TODO: pretty here + writeOrPanic(w, "Given a sample.yml file of:\n") writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.document)) } if s.expression != "" { - writeOrPanic(w, "Expression\n") + writeOrPanic(w, "then\n") writeOrPanic(w, fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression)) } - writeOrPanic(w, "Result\n") + writeOrPanic(w, "will output\n") var output bytes.Buffer var err error diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index 0d41dfe1..ba8f684c 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -151,23 +151,23 @@ func EvaluateFileStreamsSequence(expression string, filenames []string, printer // } // } -// // thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang -// func copyFileContents(src, dst string) (err error) { -// in, err := os.Open(src) // nolint gosec -// if err != nil { -// return err -// } -// defer safelyCloseFile(in) -// out, err := os.Create(dst) -// if err != nil { -// return err -// } -// defer safelyCloseFile(out) -// if _, err = io.Copy(out, in); err != nil { -// return err -// } -// return out.Sync() -// } +// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang +func copyFileContents(src, dst string) (err error) { + in, err := os.Open(src) // nolint gosec + if err != nil { + return err + } + defer safelyCloseFile(in) + out, err := os.Create(dst) + if err != nil { + return err + } + defer safelyCloseFile(out) + if _, err = io.Copy(out, in); err != nil { + return err + } + return out.Sync() +} func safelyFlush(writer *bufio.Writer) { if err := writer.Flush(); err != nil { From d91b25840ad972bbeba36ec5a45b3090f3c4ad73 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 13 Nov 2020 21:22:05 +1100 Subject: [PATCH 070/129] Better documentation generation --- pkg/yqlib/operator_collect_test.go | 33 +++++++++++++------- pkg/yqlib/operators_test.go | 49 +++++++++++++++++++++--------- pkg/yqlib/utils.go | 32 +++++++++---------- 3 files changed, 72 insertions(+), 42 deletions(-) diff --git a/pkg/yqlib/operator_collect_test.go b/pkg/yqlib/operator_collect_test.go index 0f2501fc..26f075d7 100644 --- a/pkg/yqlib/operator_collect_test.go +++ b/pkg/yqlib/operator_collect_test.go @@ -6,43 +6,54 @@ import ( var collectOperatorScenarios = []expressionScenario{ { - document: ``, - expression: `[]`, - expected: []string{}, + description: "Collect empty", + document: ``, + expression: `[]`, + expected: []string{}, }, { - document: ``, - expression: `["cat"]`, + description: "Collect single", + document: ``, + expression: `["cat"]`, expected: []string{ "D0, P[], (!!seq)::- cat\n", }, }, { document: ``, + skipDoc: true, expression: `[true]`, expected: []string{ "D0, P[], (!!seq)::- true\n", }, - }, { - document: ``, - expression: `["cat", "dog"]`, + }, + { + description: "Collect many", + document: `{a: cat, b: dog}`, + expression: `[.a, .b]`, expected: []string{ "D0, P[], (!!seq)::- cat\n- dog\n", }, - }, { + }, + { document: ``, + skipDoc: true, expression: `1 | collect`, expected: []string{ "D0, P[], (!!seq)::- 1\n", }, - }, { + }, + { document: `[1,2,3]`, + skipDoc: true, expression: `[.[]]`, expected: []string{ "D0, P[], (!!seq)::- 1\n- 2\n- 3\n", }, - }, { + }, + { document: `a: {b: [1,2,3]}`, expression: `[.a.b[]]`, + skipDoc: true, expected: []string{ "D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n", }, diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index ce7e99e3..ff2700f8 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -56,19 +56,34 @@ func writeOrPanic(w *bufio.Writer, text string) { } } -func copyFromHeader(title string, out *os.File) (bool, error) { +func copyFromHeader(title string, out *os.File) error { source := fmt.Sprintf("doc/headers/%v.md", title) _, err := os.Stat(source) if os.IsNotExist(err) { - return false, nil + return nil } in, err := os.Open(source) // nolint gosec if err != nil { - return false, err + return err } defer safelyCloseFile(in) _, err = io.Copy(out, in) - return true, err + return err +} + +func formatYaml(yaml string) string { + var output bytes.Buffer + printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true) + + node, err := treeCreator.ParsePath(".. style= \"\"") + if err != nil { + panic(err) + } + err = EvaluateStream("sample.yaml", strings.NewReader(yaml), node, printer) + if err != nil { + panic(err) + } + return output.String() } func documentScenarios(t *testing.T, title string, scenarios []expressionScenario) { @@ -80,7 +95,7 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari } defer f.Close() - hasHeader, err := copyFromHeader(title, f) + err = copyFromHeader(title, f) if err != nil { t.Error(err) return @@ -88,26 +103,30 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari w := bufio.NewWriter(f) - if !hasHeader { - writeOrPanic(w, fmt.Sprintf("## %v\n", title)) - } + writeOrPanic(w, "## Examples\n") for index, s := range scenarios { if !s.skipDoc { if s.description != "" { - writeOrPanic(w, fmt.Sprintf("## %v\n", s.description)) + writeOrPanic(w, fmt.Sprintf("### %v\n", s.description)) } else { - writeOrPanic(w, fmt.Sprintf("## Example %v\n", index)) + writeOrPanic(w, fmt.Sprintf("### Example %v\n", index)) } if s.document != "" { //TODO: pretty here writeOrPanic(w, "Given a sample.yml file of:\n") - writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.document)) - } - if s.expression != "" { + + writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formatYaml(s.document))) writeOrPanic(w, "then\n") - writeOrPanic(w, fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression)) + if s.expression != "" { + writeOrPanic(w, fmt.Sprintf("```bash\nyq eval '%v' sample.yml\n```\n", s.expression)) + } else { + writeOrPanic(w, "```bash\nyq eval sample.yml\n```\n") + } + } else { + writeOrPanic(w, "Running\n") + writeOrPanic(w, fmt.Sprintf("```bash\nyq eval --null-input '%v'\n```\n", s.expression)) } writeOrPanic(w, "will output\n") @@ -132,7 +151,7 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari } } - writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", output.String())) + writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String())) } diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index ba8f684c..3b7360b8 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -152,22 +152,22 @@ func EvaluateFileStreamsSequence(expression string, filenames []string, printer // } // thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang -func copyFileContents(src, dst string) (err error) { - in, err := os.Open(src) // nolint gosec - if err != nil { - return err - } - defer safelyCloseFile(in) - out, err := os.Create(dst) - if err != nil { - return err - } - defer safelyCloseFile(out) - if _, err = io.Copy(out, in); err != nil { - return err - } - return out.Sync() -} +// func copyFileContents(src, dst string) (err error) { +// in, err := os.Open(src) // nolint gosec +// if err != nil { +// return err +// } +// defer safelyCloseFile(in) +// out, err := os.Create(dst) +// if err != nil { +// return err +// } +// defer safelyCloseFile(out) +// if _, err = io.Copy(out, in); err != nil { +// return err +// } +// return out.Sync() +// } func safelyFlush(writer *bufio.Writer) { if err := writer.Flush(); err != nil { From 860655b4cd71f2008289397d80151ee78e3379c7 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 13 Nov 2020 21:34:43 +1100 Subject: [PATCH 071/129] Better documentation generation --- pkg/yqlib/operator_collect_object_test.go | 38 +++++++++++++++-------- pkg/yqlib/operators_test.go | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/pkg/yqlib/operator_collect_object_test.go b/pkg/yqlib/operator_collect_object_test.go index 58167dc3..d02b0cf1 100644 --- a/pkg/yqlib/operator_collect_object_test.go +++ b/pkg/yqlib/operator_collect_object_test.go @@ -6,18 +6,21 @@ import ( var collectObjectOperatorScenarios = []expressionScenario{ { - document: ``, - expression: `{}`, - expected: []string{}, + description: `Collect empty object`, + document: ``, + expression: `{}`, + expected: []string{}, }, { - document: "{name: Mike}\n", - expression: `{"wrap": .}`, + description: `Wrap (prefix) existing object`, + document: "{name: Mike}\n", + expression: `{"wrap": .}`, expected: []string{ "D0, P[], (!!map)::wrap: {name: Mike}\n", }, }, { + skipDoc: true, document: "{name: Mike}\n---\n{name: Bob}", expression: `{"wrap": .}`, expected: []string{ @@ -26,6 +29,7 @@ var collectObjectOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{name: Mike, age: 32}`, expression: `{.name: .age}`, expected: []string{ @@ -33,24 +37,28 @@ var collectObjectOperatorScenarios = []expressionScenario{ }, }, { - document: `{name: Mike, pets: [cat, dog]}`, - expression: `{.name: .pets[]}`, + description: `Using splat to create multiple objects`, + document: `{name: Mike, pets: [cat, dog]}`, + expression: `{.name: .pets[]}`, expected: []string{ "D0, P[], (!!map)::Mike: cat\n", "D0, P[], (!!map)::Mike: dog\n", }, }, { - document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}", - expression: `{.name: .pets[]}`, + description: `Working with multiple documents`, + document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}", + expression: `{.name: .pets[]}`, expected: []string{ "D0, P[], (!!map)::Mike: cat\n", "D0, P[], (!!map)::Mike: dog\n", - "D0, P[], (!!map)::Rosey: monkey\n", - "D0, P[], (!!map)::Rosey: sheep\n", + "D1, P[], (!!map)::Rosey: monkey\n", + "D1, P[], (!!map)::Rosey: sheep\n", + "this is producing incorrect formatted yaml", }, }, { + skipDoc: true, document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`, expression: `{.name: .pets[], "f":.food[]}`, expected: []string{ @@ -61,6 +69,7 @@ var collectObjectOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{name: Mike, pets: {cows: [apl, bba]}}`, expression: `{"a":.name, "b":.pets}`, expected: []string{ @@ -70,13 +79,15 @@ b: {cows: [apl, bba]} }, }, { - document: ``, - expression: `{"wrap": "frog"}`, + description: "Creating yaml from scratch", + document: ``, + expression: `{"wrap": "frog"}`, expected: []string{ "D0, P[], (!!map)::wrap: frog\n", }, }, { + skipDoc: true, document: `{name: Mike}`, expression: `{"wrap": .}`, expected: []string{ @@ -84,6 +95,7 @@ b: {cows: [apl, bba]} }, }, { + skipDoc: true, document: `{name: Mike}`, expression: `{"wrap": {"further": .}} | (.. style= "flow")`, expected: []string{ diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index ff2700f8..b550071a 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -103,7 +103,7 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari w := bufio.NewWriter(f) - writeOrPanic(w, "## Examples\n") + writeOrPanic(w, "\n## Examples\n") for index, s := range scenarios { if !s.skipDoc { From db4762ef7cb5dab3166b4f38de22451d8b4b47e2 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 14 Nov 2020 13:38:44 +1100 Subject: [PATCH 072/129] more docs --- pkg/yqlib/lib.go | 6 +-- pkg/yqlib/operator_assign_update_test.go | 45 +++++++++++++--------- pkg/yqlib/operator_delete.go | 48 ++++++++++++++---------- pkg/yqlib/operator_delete_test.go | 39 +++++++++++++++++++ pkg/yqlib/operator_explode_test.go | 29 ++++++++------ pkg/yqlib/path_tokeniser.go | 2 +- 6 files changed, 117 insertions(+), 52 deletions(-) create mode 100644 pkg/yqlib/operator_delete_test.go diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 56030da0..c8914049 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -5,8 +5,8 @@ import ( "container/list" "fmt" - "gopkg.in/op/go-logging.v1" - "gopkg.in/yaml.v3" + logging "gopkg.in/op/go-logging.v1" + yaml "gopkg.in/yaml.v3" ) type OperationType struct { @@ -70,7 +70,7 @@ var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Pre var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator} -var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Handler: DeleteChildOperator} +var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator} // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} // filters matches if they have the existing path diff --git a/pkg/yqlib/operator_assign_update_test.go b/pkg/yqlib/operator_assign_update_test.go index e1b5870a..eb2018c5 100644 --- a/pkg/yqlib/operator_assign_update_test.go +++ b/pkg/yqlib/operator_assign_update_test.go @@ -6,13 +6,23 @@ import ( var assignOperatorScenarios = []expressionScenario{ { - document: `{a: {b: apple}}`, - expression: `.a.b |= "frog"`, + description: "Update parent to be the child value", + document: `{a: {b: {g: foof}}}`, + expression: `.a |= .b`, + expected: []string{ + "D0, P[], (doc)::{a: {g: foof}}\n", + }, + }, + { + description: "Update string value", + document: `{a: {b: apple}}`, + expression: `.a.b |= "frog"`, expected: []string{ "D0, P[], (doc)::{a: {b: frog}}\n", }, }, { + skipDoc: true, document: `{a: {b: apple}}`, expression: `.a.b | (. |= "frog")`, expected: []string{ @@ -20,6 +30,7 @@ var assignOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {b: apple}}`, expression: `.a.b |= 5`, expected: []string{ @@ -27,6 +38,7 @@ var assignOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {b: apple}}`, expression: `.a.b |= 3.142`, expected: []string{ @@ -34,41 +46,39 @@ var assignOperatorScenarios = []expressionScenario{ }, }, { - document: `{a: {b: {g: foof}}}`, - expression: `.a |= .b`, - expected: []string{ - "D0, P[], (doc)::{a: {g: foof}}\n", - }, - }, - { - document: `{a: {b: apple, c: cactus}}`, - expression: `.a[] | select(. == "apple") |= "frog"`, + description: "Update selected results", + document: `{a: {b: apple, c: cactus}}`, + expression: `.a[] | select(. == "apple") |= "frog"`, expected: []string{ "D0, P[], (doc)::{a: {b: frog, c: cactus}}\n", }, }, { - document: `[candy, apple, sandy]`, - expression: `.[] | select(. == "*andy") |= "bogs"`, + description: "Update array values", + document: `[candy, apple, sandy]`, + expression: `.[] | select(. == "*andy") |= "bogs"`, expected: []string{ "D0, P[], (doc)::[bogs, apple, bogs]\n", }, }, { - document: `{}`, - expression: `.a.b |= "bogs"`, + description: "Update empty object", + document: `{}`, + expression: `.a.b |= "bogs"`, expected: []string{ "D0, P[], (doc)::{a: {b: bogs}}\n", }, }, { - document: `{}`, - expression: `.a.b[0] |= "bogs"`, + description: "Update empty object and array", + document: `{}`, + expression: `.a.b[0] |= "bogs"`, expected: []string{ "D0, P[], (doc)::{a: {b: [bogs]}}\n", }, }, { + skipDoc: true, document: `{}`, expression: `.a.b[1].c |= "bogs"`, expected: []string{ @@ -81,4 +91,5 @@ func TestAssignOperatorScenarios(t *testing.T) { for _, tt := range assignOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Update Assign Operator", assignOperatorScenarios) } diff --git a/pkg/yqlib/operator_delete.go b/pkg/yqlib/operator_delete.go index 362207d2..e19d343c 100644 --- a/pkg/yqlib/operator_delete.go +++ b/pkg/yqlib/operator_delete.go @@ -3,19 +3,15 @@ package yqlib import ( "container/list" - "gopkg.in/yaml.v3" + yaml "gopkg.in/yaml.v3" ) func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } // for each lhs, splat the node, // the intersect it against the rhs expression // recreate the contents using only the intersection result. - for el := lhs.Front(); el != nil; el = el.Next() { + for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) elMap := list.New() elMap.PushBack(candidate) @@ -25,20 +21,22 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod return nil, err } - if candidate.Node.Kind == yaml.SequenceNode { + realNode := UnwrapDoc(candidate.Node) + + if realNode.Kind == yaml.SequenceNode { deleteFromArray(candidate, nodesToDelete) - } else if candidate.Node.Kind == yaml.MappingNode { + } else if realNode.Kind == yaml.MappingNode { deleteFromMap(candidate, nodesToDelete) } else { log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate)) } } - return lhs, nil + return matchingNodes, nil } func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { log.Debug("deleteFromMap") - node := candidate.Node + node := UnwrapDoc(candidate.Node) contents := node.Content newContents := make([]*yaml.Node, 0) @@ -51,8 +49,13 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { Document: candidate.Document, Path: append(candidate.Path, key.Value), } - // _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) - shouldDelete := true + + shouldDelete := false + for el := nodesToDelete.Front(); el != nil && shouldDelete == false; el = el.Next() { + if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() { + shouldDelete = true + } + } log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete) @@ -65,21 +68,26 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) { log.Debug("deleteFromArray") - node := candidate.Node + node := UnwrapDoc(candidate.Node) contents := node.Content newContents := make([]*yaml.Node, 0) for index := 0; index < len(contents); index = index + 1 { value := contents[index] - // childCandidate := &CandidateNode{ - // Node: value, - // Document: candidate.Document, - // Path: append(candidate.Path, index), - // } + childCandidate := &CandidateNode{ + Node: value, + Document: candidate.Document, + Path: append(candidate.Path, index), + } + + shouldDelete := false + for el := nodesToDelete.Front(); el != nil && shouldDelete == false; el = el.Next() { + if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() { + shouldDelete = true + } + } - // _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) - shouldDelete := true if !shouldDelete { newContents = append(newContents, value) } diff --git a/pkg/yqlib/operator_delete_test.go b/pkg/yqlib/operator_delete_test.go new file mode 100644 index 00000000..68476e5f --- /dev/null +++ b/pkg/yqlib/operator_delete_test.go @@ -0,0 +1,39 @@ +package yqlib + +import ( + "testing" +) + +var deleteOperatorScenarios = []expressionScenario{ + { + description: "Delete entry in map", + document: `{a: cat, b: dog}`, + expression: `del(.b)`, + expected: []string{ + "D0, P[], (doc)::{a: cat}\n", + }, + }, + { + description: "Delete entry in array", + document: `[1,2,3]`, + expression: `del(.[1])`, + expected: []string{ + "D0, P[], (doc)::[1, 3]\n", + }, + }, + { + description: "Delete no matches", + document: `{a: cat, b: dog}`, + expression: `del(.c)`, + expected: []string{ + "D0, P[], (doc)::{a: cat, b: dog}\n", + }, + }, +} + +func TestDeleteOperatorScenarios(t *testing.T) { + for _, tt := range deleteOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Delete Operator", deleteOperatorScenarios) +} diff --git a/pkg/yqlib/operator_explode_test.go b/pkg/yqlib/operator_explode_test.go index 7318b9a7..e7c609ca 100644 --- a/pkg/yqlib/operator_explode_test.go +++ b/pkg/yqlib/operator_explode_test.go @@ -6,27 +6,31 @@ import ( var explodeTest = []expressionScenario{ { - document: `{a: mike}`, - expression: `explode(.a)`, - expected: []string{ - "D0, P[], (doc)::{a: mike}\n", - }, - }, - { - document: `{f : {a: &a cat, b: *a}}`, - expression: `explode(.f)`, + description: "Explode alias and anchor", + document: `{f : {a: &a cat, b: *a}}`, + expression: `explode(.f)`, expected: []string{ "D0, P[], (doc)::{f: {a: cat, b: cat}}\n", }, }, { - document: `{f : {a: &a cat, *a: b}}`, - expression: `explode(.f)`, + description: "Explode with no aliases or anchors", + document: `{a: mike}`, + expression: `explode(.a)`, + expected: []string{ + "D0, P[], (doc)::{a: mike}\n", + }, + }, + { + description: "Explode with alias keys", + document: `{f : {a: &a cat, *a: b}}`, + expression: `explode(.f)`, expected: []string{ "D0, P[], (doc)::{f: {a: cat, cat: b}}\n", }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foo* | explode(.) | (. style="flow")`, expected: []string{ @@ -36,6 +40,7 @@ var explodeTest = []expressionScenario{ }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foo* | explode(explode(.)) | (. style="flow")`, expected: []string{ @@ -45,6 +50,7 @@ var explodeTest = []expressionScenario{ }, }, { + skipDoc: true, document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`, expression: `explode(.f)`, expected: []string{ @@ -57,4 +63,5 @@ func TestExplodeOperatorScenarios(t *testing.T) { for _, tt := range explodeTest { testScenario(t, &tt) } + documentScenarios(t, "Explode Operator", explodeTest) } diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index aa794845..aba11192 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -209,7 +209,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) - lexer.Add([]byte(`\s*.-\s*`), opToken(DeleteChild)) + lexer.Add([]byte(`del`), opToken(DeleteChild)) lexer.Add([]byte(`\s*\|=\s*`), opToken(Assign)) From af2aa9ad918aebb3410a1f2db57b4fd0ad406b4d Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 15 Nov 2020 10:50:30 +1100 Subject: [PATCH 073/129] more docs --- pkg/yqlib/operator_explode_test.go | 27 +++++++++- pkg/yqlib/operator_multiply_test.go | 19 +++++-- pkg/yqlib/operator_not_test.go | 83 ++++++++++++++++------------- pkg/yqlib/operators_test.go | 21 +++++--- 4 files changed, 99 insertions(+), 51 deletions(-) diff --git a/pkg/yqlib/operator_explode_test.go b/pkg/yqlib/operator_explode_test.go index e7c609ca..1bd9fe40 100644 --- a/pkg/yqlib/operator_explode_test.go +++ b/pkg/yqlib/operator_explode_test.go @@ -15,10 +15,10 @@ var explodeTest = []expressionScenario{ }, { description: "Explode with no aliases or anchors", - document: `{a: mike}`, + document: `a: mike`, expression: `explode(.a)`, expected: []string{ - "D0, P[], (doc)::{a: mike}\n", + "D0, P[], (doc)::a: mike\n", }, }, { @@ -29,6 +29,29 @@ var explodeTest = []expressionScenario{ "D0, P[], (doc)::{f: {a: cat, cat: b}}\n", }, }, + { + description: "Explode with merge anchors", + document: mergeDocSample, + expression: `explode(.)`, + expected: []string{`D0, P[], (doc)::foo: + a: foo_a + thing: foo_thing + c: foo_c +bar: + b: bar_b + thing: bar_thing + c: bar_c +foobarList: + b: bar_b + a: foo_a + thing: bar_thing + c: foobarList_c +foobar: + c: foo_c + a: foo_a + thing: foobar_thing +`}, + }, { skipDoc: true, document: mergeDocSample, diff --git a/pkg/yqlib/operator_multiply_test.go b/pkg/yqlib/operator_multiply_test.go index 3da2858f..a12a0220 100644 --- a/pkg/yqlib/operator_multiply_test.go +++ b/pkg/yqlib/operator_multiply_test.go @@ -22,11 +22,19 @@ var multiplyOperatorScenarios = []expressionScenario{ }, }, { - description: "Merge objects together", - document: `{a: {also: me}, b: {also: {g: wizz}}}`, + description: "Merge objects together, returning merged result only", + document: `{a: {field: me, fieldA: cat}, b: {field: {g: wizz}, fieldB: dog}}`, + expression: `.a * .b`, + expected: []string{ + "D0, P[a], (!!map)::{field: {g: wizz}, fieldA: cat, fieldB: dog}\n", + }, + }, + { + description: "Merge objects together, returning parent object", + document: `{a: {field: me, fieldA: cat}, b: {field: {g: wizz}, fieldB: dog}}`, expression: `. * {"a":.b}`, expected: []string{ - "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", + "D0, P[], (!!map)::{a: {field: {g: wizz}, fieldA: cat, fieldB: dog}, b: {field: {g: wizz}, fieldB: dog}}\n", }, }, { @@ -62,7 +70,8 @@ var multiplyOperatorScenarios = []expressionScenario{ }, }, { - description: "Merge keeps style of LHS", + description: "Merge keeps style of LHS", + dontFormatInputForDoc: true, document: `a: {things: great} b: also: "me" @@ -121,5 +130,5 @@ func TestMultiplyOperatorScenarios(t *testing.T) { for _, tt := range multiplyOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Mulitply Operator", multiplyOperatorScenarios) + documentScenarios(t, "Multiply Operator", multiplyOperatorScenarios) } diff --git a/pkg/yqlib/operator_not_test.go b/pkg/yqlib/operator_not_test.go index 1c441644..3bac2087 100644 --- a/pkg/yqlib/operator_not_test.go +++ b/pkg/yqlib/operator_not_test.go @@ -5,52 +5,61 @@ import ( ) var notOperatorScenarios = []expressionScenario{ - // { - // document: `cat`, - // expression: `. | not`, - // expected: []string{ - // "D0, P[], (!!bool)::false\n", - // }, - // }, - // { - // document: `1`, - // expression: `. | not`, - // expected: []string{ - // "D0, P[], (!!bool)::false\n", - // }, - // }, - // { - // document: `0`, - // expression: `. | not`, - // expected: []string{ - // "D0, P[], (!!bool)::false\n", - // }, - // }, { - document: `~`, - expression: `. | not`, + description: "Not true is false", + expression: `true | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + description: "Not false is true", + expression: `false | not`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, + { + description: "String values considered to be true", + expression: `"cat" | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + description: "Empty string value considered to be true", + expression: `"" | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + description: "Numbers are considered to be true", + expression: `1 | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + description: "Zero is considered to be true", + expression: `0 | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + + description: "Null is considered to be false", + expression: `~ | not`, expected: []string{ "D0, P[], (!!bool)::true\n", }, }, - // { - // document: `false`, - // expression: `. | not`, - // expected: []string{ - // "D0, P[], (!!bool)::true\n", - // }, - // }, - // { - // document: `true`, - // expression: `. | not`, - // expected: []string{ - // "D0, P[], (!!bool)::false\n", - // }, - // }, } func TestNotOperatorScenarios(t *testing.T) { for _, tt := range notOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Not Operator", notOperatorScenarios) } diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index b550071a..625997b8 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -14,11 +14,12 @@ import ( ) type expressionScenario struct { - description string - document string - expression string - expected []string - skipDoc bool + description string + document string + expression string + expected []string + skipDoc bool + dontFormatInputForDoc bool // dont format input doc for documentation generation } func testScenario(t *testing.T, s *expressionScenario) { @@ -113,11 +114,17 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari } else { writeOrPanic(w, fmt.Sprintf("### Example %v\n", index)) } + formattedDoc := "" if s.document != "" { + if s.dontFormatInputForDoc { + formattedDoc = s.document + } else { + formattedDoc = formatYaml(s.document) + } //TODO: pretty here writeOrPanic(w, "Given a sample.yml file of:\n") - writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formatYaml(s.document))) + writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc)) writeOrPanic(w, "then\n") if s.expression != "" { writeOrPanic(w, fmt.Sprintf("```bash\nyq eval '%v' sample.yml\n```\n", s.expression)) @@ -140,7 +147,7 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari if err != nil { t.Error(err) } - err = EvaluateStream("sample.yaml", strings.NewReader(s.document), node, printer) + err = EvaluateStream("sample.yaml", strings.NewReader(formattedDoc), node, printer) if err != nil { t.Error(err) } From b3efcdc202c9a4aee9cf28b72912eb8767f22086 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 15 Nov 2020 10:58:47 +1100 Subject: [PATCH 074/129] more docs --- pkg/yqlib/operator_recursive_descent_test.go | 29 +++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/pkg/yqlib/operator_recursive_descent_test.go b/pkg/yqlib/operator_recursive_descent_test.go index e1a41e7d..0de62417 100644 --- a/pkg/yqlib/operator_recursive_descent_test.go +++ b/pkg/yqlib/operator_recursive_descent_test.go @@ -6,6 +6,7 @@ import ( var recursiveDescentOperatorScenarios = []expressionScenario{ { + skipDoc: true, document: `cat`, expression: `..`, expected: []string{ @@ -13,6 +14,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: frog}`, expression: `..`, expected: []string{ @@ -21,8 +23,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { - document: `{a: {b: apple}}`, - expression: `..`, + description: "Map", + document: `{a: {b: apple}}`, + expression: `..`, expected: []string{ "D0, P[], (!!map)::{a: {b: apple}}\n", "D0, P[a], (!!map)::{b: apple}\n", @@ -30,8 +33,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { - document: `[1,2,3]`, - expression: `..`, + description: "Array", + document: `[1,2,3]`, + expression: `..`, expected: []string{ "D0, P[], (!!seq)::[1, 2, 3]\n", "D0, P[0], (!!int)::1\n", @@ -40,8 +44,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { - document: `[{a: cat},2,true]`, - expression: `..`, + description: "Array of maps", + document: `[{a: cat},2,true]`, + expression: `..`, expected: []string{ "D0, P[], (!!seq)::[{a: cat}, 2, true]\n", "D0, P[0], (!!map)::{a: cat}\n", @@ -51,8 +56,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { - document: `{a: &cat {c: frog}, b: *cat}`, - expression: `..`, + description: "Aliases are not traversed", + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `..`, expected: []string{ "D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n", "D0, P[a], (!!map)::&cat {c: frog}\n", @@ -61,8 +67,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { - document: mergeDocSample, - expression: `.foobar | ..`, + description: "Merge docs are not traversed", + document: mergeDocSample, + expression: `.foobar | ..`, expected: []string{ "D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n", "D0, P[foobar c], (!!str)::foobar_c\n", @@ -71,6 +78,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foobarList | ..`, expected: []string{ @@ -88,4 +96,5 @@ func TestRecursiveDescentOperatorScenarios(t *testing.T) { for _, tt := range recursiveDescentOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Recursive Descent Operator", recursiveDescentOperatorScenarios) } From 79867473d5e6925eaef9ad4ac5437b8e2ca14a19 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 16 Nov 2020 10:28:57 +1100 Subject: [PATCH 075/129] updating release --- compare.sh | 13 ------------- release_instructions.txt | 3 ++- scripts/release.sh | 21 +++++++++++++++++++++ scripts/{publish.sh => upload.sh} | 10 +--------- snap/snapcraft.yaml | 2 +- 5 files changed, 25 insertions(+), 24 deletions(-) delete mode 100755 compare.sh create mode 100755 scripts/release.sh rename scripts/{publish.sh => upload.sh} (82%) diff --git a/compare.sh b/compare.sh deleted file mode 100755 index fc8cd8bf..00000000 --- a/compare.sh +++ /dev/null @@ -1,13 +0,0 @@ -GREEN='\033[0;32m' -NC='\033[0m' - -echo "${GREEN}---Old---${NC}" -yq $@ > /tmp/yq-old-output -cat /tmp/yq-old-output - -echo "${GREEN}---New---${NC}" -./yq $@ > /tmp/yq-new-output -cat /tmp/yq-new-output - -echo "${GREEN}---Diff---${NC}" -colordiff /tmp/yq-old-output /tmp/yq-new-output \ No newline at end of file diff --git a/release_instructions.txt b/release_instructions.txt index 8756ca9b..14080e8f 100644 --- a/release_instructions.txt +++ b/release_instructions.txt @@ -8,7 +8,8 @@ - make local xcompile (builds binaries for all platforms) - git release - ./scripts/publish.sh + ./scripts/release.sh + ./scripts/upload.sh - snapcraft - will auto create a candidate, test it works then promote diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 00000000..c169aa2d --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -ex +GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}" + +CURRENT="$(git describe --tags --abbrev=0)" +PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)" +OWNER="mikefarah" +REPO="yq" + +release() { + github-release release \ + --user "$OWNER" \ + --draft \ + --repo "$REPO" \ + --tag "$CURRENT" +} + + + +release + diff --git a/scripts/publish.sh b/scripts/upload.sh similarity index 82% rename from scripts/publish.sh rename to scripts/upload.sh index 40b290f6..7498c2f4 100755 --- a/scripts/publish.sh +++ b/scripts/upload.sh @@ -7,14 +7,6 @@ PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)" OWNER="mikefarah" REPO="yq" -release() { - github-release release \ - --user "$OWNER" \ - --draft \ - --repo "$REPO" \ - --tag "$CURRENT" -} - upload() { mkdir -p ./build-done while IFS= read -r -d $'\0'; do @@ -32,5 +24,5 @@ upload() { done < <(find ./build -mindepth 1 -maxdepth 1 -print0) } -# release + upload diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 230d2ddc..53c3c9d3 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: yq -version: '3.4.0' +version: '4.0.0-alpha1' summary: A lightweight and portable command-line YAML processor description: | The aim of the project is to be the jq or sed of yaml files. From a57944d123da7b745c6597213cc7fec3e263e874 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 16 Nov 2020 12:09:57 +1100 Subject: [PATCH 076/129] Fixed printer --- cmd/constant.go | 1 - cmd/root.go | 23 +- cmd/shell-completion.go | 61 ++ pkg/yqlib/data_tree_navigator_test.go | 664 ---------------------- pkg/yqlib/lib.go | 5 +- pkg/yqlib/operator_collect_object_test.go | 12 +- pkg/yqlib/operator_delete.go | 4 +- pkg/yqlib/operators_test.go | 9 + pkg/yqlib/printer.go | 16 +- 9 files changed, 96 insertions(+), 699 deletions(-) create mode 100644 cmd/shell-completion.go delete mode 100644 pkg/yqlib/data_tree_navigator_test.go diff --git a/cmd/constant.go b/cmd/constant.go index 9e48f52d..dc4d01ff 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -14,6 +14,5 @@ var noDocSeparators = false var nullInput = false var verbose = false var version = false -var shellCompletion = "" // var log = logging.MustGetLogger("yq") diff --git a/cmd/root.go b/cmd/root.go index 4a225ef4..6836a864 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,7 +1,6 @@ package cmd import ( - "fmt" "os" "github.com/spf13/cobra" @@ -18,20 +17,6 @@ func New() *cobra.Command { cmd.Print(GetVersionDisplay()) return nil } - if shellCompletion != "" { - switch shellCompletion { - case "bash", "": - return cmd.GenBashCompletion(os.Stdout) - case "zsh": - return cmd.GenZshCompletion(os.Stdout) - case "fish": - return cmd.GenFishCompletion(os.Stdout, true) - case "powershell": - return cmd.GenPowerShellCompletion(os.Stdout) - default: - return fmt.Errorf("Unknown variant %v", shellCompletion) - } - } cmd.Println(cmd.UsageString()) return nil @@ -62,10 +47,12 @@ func New() *cobra.Command { rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") - rootCmd.Flags().StringVarP(&shellCompletion, "shellCompletion", "", "", "[bash/zsh/powershell/fish] prints shell completion script") - rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors") rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors") - rootCmd.AddCommand(createEvaluateSequenceCommand(), createEvaluateAllCommand()) + rootCmd.AddCommand( + createEvaluateSequenceCommand(), + createEvaluateAllCommand(), + completionCmd, + ) return rootCmd } diff --git a/cmd/shell-completion.go b/cmd/shell-completion.go new file mode 100644 index 00000000..0fadfb12 --- /dev/null +++ b/cmd/shell-completion.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +var completionCmd = &cobra.Command{ + Use: "shell-completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + Long: `To load completions: + +Bash: + +$ source <(yq shell-completion bash) + +# To load completions for each session, execute once: +Linux: + $ yq shell-completion bash > /etc/bash_completion.d/yq +MacOS: + $ yq shell-completion bash > /usr/local/etc/bash_completion.d/yq + +Zsh: + +# If shell completion is not already enabled in your environment you will need +# to enable it. You can execute the following once: + +$ echo "autoload -U compinit; compinit" >> ~/.zshrc + +# To load completions for each session, execute once: +$ yq shell-completion zsh > "${fpath[1]}/_yq" + +# You will need to start a new shell for this setup to take effect. + +Fish: + +$ yq shell-completion fish | source + +# To load completions for each session, execute once: +$ yq shell-completion fish > ~/.config/fish/completions/yq.fish +`, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.ExactValidArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + var err error = nil + switch args[0] { + case "bash": + err = cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + err = cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + err = cmd.Root().GenFishCompletion(os.Stdout, true) + case "powershell": + err = cmd.Root().GenPowerShellCompletion(os.Stdout) + } + return err + + }, +} diff --git a/pkg/yqlib/data_tree_navigator_test.go b/pkg/yqlib/data_tree_navigator_test.go deleted file mode 100644 index 1e2d7b91..00000000 --- a/pkg/yqlib/data_tree_navigator_test.go +++ /dev/null @@ -1,664 +0,0 @@ -package yqlib - -import ( - "container/list" -) - -func resultsToString(results *list.List) []string { - var pretty []string = make([]string, 0) - for el := results.Front(); el != nil; el = el.Next() { - n := el.Value.(*CandidateNode) - pretty = append(pretty, NodeToString(n)) - } - return pretty -} - -// func TestDataTreeNavigatorDeleteSimple(t *testing.T) { - -// nodes := readDoc(t, `a: -// b: apple -// c: camel`) - -// path, errPath := treeCreator.ParsePath("a .- b") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a] -// Tag: !!map, Kind: MappingNode, Anchor: -// c: camel -// ` -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorDeleteTwice(t *testing.T) { - -// nodes := readDoc(t, `a: -// b: apple -// c: camel -// d: dingo`) - -// path, errPath := treeCreator.ParsePath("a .- b OR a .- c") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a] -// Tag: !!map, Kind: MappingNode, Anchor: -// d: dingo -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorDeleteWithUnion(t *testing.T) { - -// nodes := readDoc(t, `a: -// b: apple -// c: camel -// d: dingo`) - -// path, errPath := treeCreator.ParsePath("a .- (b OR c)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a] -// Tag: !!map, Kind: MappingNode, Anchor: -// d: dingo -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorDeleteByIndex(t *testing.T) { - -// nodes := readDoc(t, `a: -// - b: apple -// - b: sdfsd -// - b: apple`) - -// path, errPath := treeCreator.ParsePath("(a .- (0 or 1))") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a] -// Tag: !!seq, Kind: SequenceNode, Anchor: -// - b: apple -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorDeleteByFind(t *testing.T) { - -// nodes := readDoc(t, `a: -// - b: apple -// - b: sdfsd -// - b: apple`) - -// path, errPath := treeCreator.ParsePath("(a .- (* == apple))") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a] -// Tag: !!seq, Kind: SequenceNode, Anchor: -// - b: sdfsd -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorDeleteArrayByFind(t *testing.T) { - -// nodes := readDoc(t, `a: -// - apple -// - sdfsd -// - apple`) - -// path, errPath := treeCreator.ParsePath("(a .- (. == apple))") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a] -// Tag: !!seq, Kind: SequenceNode, Anchor: -// - sdfsd -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorDeleteViaSelf(t *testing.T) { - -// nodes := readDoc(t, `- apple -// - sdfsd -// - apple`) - -// path, errPath := treeCreator.ParsePath(". .- (. == apple)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [] -// Tag: !!seq, Kind: SequenceNode, Anchor: -// - sdfsd -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorFilterWithSplat(t *testing.T) { - -// nodes := readDoc(t, `f: -// a: frog -// b: dally -// c: log`) - -// path, errPath := treeCreator.ParsePath(".f | .[] == \"frog\"") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [f] -// Tag: !!int, Kind: ScalarNode, Anchor: -// 2 -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCountAndCollectWithFilterCmd(t *testing.T) { - -// nodes := readDoc(t, `f: -// a: frog -// b: dally -// c: log`) - -// path, errPath := treeCreator.ParsePath(".f | .[] == *og ") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [f] -// Tag: !!int, Kind: ScalarNode, Anchor: -// 2 -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCollectWithFilter(t *testing.T) { - -// nodes := readDoc(t, `f: -// a: frog -// b: dally -// c: log`) - -// path, errPath := treeCreator.ParsePath("f(collect(. == *og))") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [f] -// Tag: , Kind: SequenceNode, Anchor: -// - frog -// - log -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCountWithFilter2(t *testing.T) { - -// nodes := readDoc(t, `f: -// a: frog -// b: dally -// c: log`) - -// path, errPath := treeCreator.ParsePath("count(f(. == *og))") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [] -// Tag: !!int, Kind: ScalarNode, Anchor: -// 2 -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCollectWithFilter2(t *testing.T) { - -// nodes := readDoc(t, `f: -// a: frog -// b: dally -// c: log`) - -// path, errPath := treeCreator.ParsePath("collect(f(. == *og))") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [] -// Tag: , Kind: SequenceNode, Anchor: -// - frog -// - log -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCountMultipleMatchesInside(t *testing.T) { - -// nodes := readDoc(t, `f: -// a: [1,2] -// b: dally -// c: [3,4,5]`) - -// path, errPath := treeCreator.ParsePath("f | count(a or c)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [f] -// Tag: !!int, Kind: ScalarNode, Anchor: -// 2 -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCollectMultipleMatchesInside(t *testing.T) { - -// nodes := readDoc(t, `f: -// a: [1,2] -// b: dally -// c: [3,4,5]`) - -// path, errPath := treeCreator.ParsePath("f | collect(a or c)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [f] -// Tag: , Kind: SequenceNode, Anchor: -// - [1, 2] -// - [3, 4, 5] -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCountMultipleMatchesInsideSplat(t *testing.T) { - -// nodes := readDoc(t, `f: -// a: [1,2,3] -// b: [1,2,3,4] -// c: [1,2,3,4,5]`) - -// path, errPath := treeCreator.ParsePath("f(count( (a or c)*))") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [f] -// Tag: !!int, Kind: ScalarNode, Anchor: -// 8 -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCountMultipleMatchesOutside(t *testing.T) { - -// nodes := readDoc(t, `f: -// a: [1,2,3] -// b: [1,2,3,4] -// c: [1,2,3,4,5]`) - -// path, errPath := treeCreator.ParsePath("f(a or c)(count(*))") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [f a] -// Tag: !!int, Kind: ScalarNode, Anchor: -// 3 -// -- Node -- -// Document 0, path: [f c] -// Tag: !!int, Kind: ScalarNode, Anchor: -// 5 -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCountOfResults(t *testing.T) { - -// nodes := readDoc(t, `- apple -// - sdfsd -// - apple`) - -// path, errPath := treeCreator.ParsePath("count(*)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [] -// Tag: !!int, Kind: ScalarNode, Anchor: -// 3 -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorCountNoMatches(t *testing.T) { - -// nodes := readDoc(t, `- apple -// - sdfsd -// - apple`) - -// path, errPath := treeCreator.ParsePath("count(5)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [] -// Tag: !!int, Kind: ScalarNode, Anchor: -// 0 -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorDeleteAndWrite(t *testing.T) { - -// nodes := readDoc(t, `a: -// - b: apple -// - b: sdfsd -// - { b: apple, c: cat }`) - -// path, errPath := treeCreator.ParsePath("(a .- (0 or 1)) or (a[0].b := frog)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a] -// Tag: !!seq, Kind: SequenceNode, Anchor: -// - {b: frog, c: cat} - -// -- Node -- -// Document 0, path: [a 0 b] -// Tag: !!str, Kind: ScalarNode, Anchor: -// frog -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorDeleteArray(t *testing.T) { - -// nodes := readDoc(t, `a: -// - b: apple -// - b: sdfsd -// - b: apple`) - -// path, errPath := treeCreator.ParsePath("a .- (b == a*)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a] -// Tag: !!seq, Kind: SequenceNode, Anchor: -// - b: sdfsd -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorArraySimple(t *testing.T) { - -// nodes := readDoc(t, `- b: apple`) - -// path, errPath := treeCreator.ParsePath("[0]") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [0] -// Tag: !!map, Kind: MappingNode, Anchor: -// b: apple -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorSimpleAssignByFind(t *testing.T) { - -// nodes := readDoc(t, `a: -// b: apple`) - -// path, errPath := treeCreator.ParsePath("a(. == apple) := frog") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a b] -// Tag: !!str, Kind: ScalarNode, Anchor: -// frog -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorOrDeDupes(t *testing.T) { - -// nodes := readDoc(t, `a: -// cat: apple -// mad: things`) - -// path, errPath := treeCreator.ParsePath("a.(cat or cat)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a cat] -// Tag: !!str, Kind: ScalarNode, Anchor: -// apple -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } - -// func TestDataTreeNavigatorAnd(t *testing.T) { - -// nodes := readDoc(t, `a: -// cat: apple -// pat: apple -// cow: apple -// mad: things`) - -// path, errPath := treeCreator.ParsePath("a.(*t and c*)") -// if errPath != nil { -// t.Error(errPath) -// } -// results, errNav := treeNavigator.GetMatchingNodes(nodes, path) - -// if errNav != nil { -// t.Error(errNav) -// } - -// expected := ` -// -- Node -- -// Document 0, path: [a cat] -// Tag: !!str, Kind: ScalarNode, Anchor: -// apple -// ` - -// test.AssertResult(t, expected, resultsToString(results)) -// } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index c8914049..de52cc78 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -17,8 +17,9 @@ type OperationType struct { } // operators TODO: -// - generator doc from operator tests -// - slurp - stdin, read in sequence, vs read all +// - add print test, particular for streams with multilpe docs +// (one that print doc1, then doc 3) in two calls +// (one that prints doc1, then doc 3) in one call // - write in place // - get path operator (like doc index) // - get file index op (like doc index) diff --git a/pkg/yqlib/operator_collect_object_test.go b/pkg/yqlib/operator_collect_object_test.go index d02b0cf1..d03ede2a 100644 --- a/pkg/yqlib/operator_collect_object_test.go +++ b/pkg/yqlib/operator_collect_object_test.go @@ -46,15 +46,15 @@ var collectObjectOperatorScenarios = []expressionScenario{ }, }, { - description: `Working with multiple documents`, - document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}", - expression: `{.name: .pets[]}`, + description: `Working with multiple documents`, + dontFormatInputForDoc: false, + document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}", + expression: `{.name: .pets[]}`, expected: []string{ "D0, P[], (!!map)::Mike: cat\n", "D0, P[], (!!map)::Mike: dog\n", - "D1, P[], (!!map)::Rosey: monkey\n", - "D1, P[], (!!map)::Rosey: sheep\n", - "this is producing incorrect formatted yaml", + "D0, P[], (!!map)::Rosey: monkey\n", + "D0, P[], (!!map)::Rosey: sheep\n", }, }, { diff --git a/pkg/yqlib/operator_delete.go b/pkg/yqlib/operator_delete.go index e19d343c..0e01af50 100644 --- a/pkg/yqlib/operator_delete.go +++ b/pkg/yqlib/operator_delete.go @@ -51,7 +51,7 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { } shouldDelete := false - for el := nodesToDelete.Front(); el != nil && shouldDelete == false; el = el.Next() { + for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() { if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() { shouldDelete = true } @@ -82,7 +82,7 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) { } shouldDelete := false - for el := nodesToDelete.Front(); el != nil && shouldDelete == false; el = el.Next() { + for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() { if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() { shouldDelete = true } diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 625997b8..7d4402b6 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -50,6 +50,15 @@ func testScenario(t *testing.T, s *expressionScenario) { test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document)) } +func resultsToString(results *list.List) []string { + var pretty []string = make([]string, 0) + for el := results.Front(); el != nil; el = el.Next() { + n := el.Value.(*CandidateNode) + pretty = append(pretty, NodeToString(n)) + } + return pretty +} + func writeOrPanic(w *bufio.Writer, text string) { _, err := w.WriteString(text) if err != nil { diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index 89c473dd..2e81c3b8 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -20,6 +20,7 @@ type resultsPrinter struct { printDocSeparators bool writer io.Writer firstTimePrinting bool + previousDocIndex uint } func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer { @@ -53,6 +54,7 @@ func (p *resultsPrinter) writeString(writer io.Writer, txt string) error { } func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { + log.Debug("PrintResults for %v matches", matchingNodes.Len()) var err error if p.outputToJSON { explodeOp := Operation{OperationType: Explode} @@ -70,13 +72,16 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { log.Debug("no matching results, nothing to print") return nil } - - previousDocIndex := matchingNodes.Front().Value.(*CandidateNode).Document + if p.firstTimePrinting { + p.previousDocIndex = matchingNodes.Front().Value.(*CandidateNode).Document + p.firstTimePrinting = false + } for el := matchingNodes.Front(); el != nil; el = el.Next() { mappedDoc := el.Value.(*CandidateNode) - - if (!p.firstTimePrinting || (previousDocIndex != mappedDoc.Document)) && p.printDocSeparators { + log.Debug("-- print sep logic: p.firstTimePrinting: %v, previousDocIndex: %v, mappedDoc.Document: %v, printDocSeparators: %v", p.firstTimePrinting, p.previousDocIndex, mappedDoc.Document, p.printDocSeparators) + if (p.previousDocIndex != mappedDoc.Document) && p.printDocSeparators { + log.Debug("-- writing doc sep") if err := p.writeString(bufferedWriter, "---\n"); err != nil { return err } @@ -87,9 +92,8 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { return err } - previousDocIndex = mappedDoc.Document + p.previousDocIndex = mappedDoc.Document } - p.firstTimePrinting = false return nil } From 83cb6421dfb3e08b584916a84fa3d1f37f8aa904 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 17 Nov 2020 16:17:38 +1100 Subject: [PATCH 077/129] added test to ensure json keys remain in order --- pkg/yqlib/encoder_test.go | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 pkg/yqlib/encoder_test.go diff --git a/pkg/yqlib/encoder_test.go b/pkg/yqlib/encoder_test.go new file mode 100644 index 00000000..f6a436a7 --- /dev/null +++ b/pkg/yqlib/encoder_test.go @@ -0,0 +1,44 @@ +package yqlib + +import ( + "bufio" + "bytes" + "strings" + "testing" + + "github.com/mikefarah/yq/v4/test" +) + +var sampleYaml = `zabbix: winner +apple: great +banana: +- {cobra: kai, angus: bob} +` + +var expectedJson = `{ + "zabbix": "winner", + "apple": "great", + "banana": [ + { + "cobra": "kai", + "angus": "bob" + } + ] +} +` + +func TestJsonEncoderPreservesObjectOrder(t *testing.T) { + var output bytes.Buffer + writer := bufio.NewWriter(&output) + + var jsonEncoder = NewJsonEncoder(writer, 2) + inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml") + if err != nil { + panic(err) + } + node := inputs.Front().Value.(*CandidateNode).Node + jsonEncoder.Encode(node) + writer.Flush() + test.AssertResult(t, expectedJson, output.String()) + +} From 088ec36acdcc15408aa4de0dce27241c0a1e5bb4 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 18 Nov 2020 09:44:16 +1100 Subject: [PATCH 078/129] include docs for tracking --- pkg/yqlib/doc/.gitignore | 1 - pkg/yqlib/doc/Collect into Array.md | 41 ++++ pkg/yqlib/doc/Collect into Object.md | 80 +++++++ pkg/yqlib/doc/Comments Operator.md | 120 +++++++++++ pkg/yqlib/doc/Delete Operator.md | 48 +++++ pkg/yqlib/doc/Document Index Operator.md | 56 +++++ pkg/yqlib/doc/Equals Operator.md | 62 ++++++ pkg/yqlib/doc/Explode Operator.md | 95 +++++++++ pkg/yqlib/doc/Multiply Operator.md | 196 ++++++++++++++++++ pkg/yqlib/doc/Not Operator.md | 72 +++++++ pkg/yqlib/doc/Recursive Descent Operator.md | 139 +++++++++++++ pkg/yqlib/doc/Style Operator.md | 79 +++++++ pkg/yqlib/doc/Update Assign Operator.md | 93 +++++++++ pkg/yqlib/doc/headers/Collect into Array.md | 4 + pkg/yqlib/doc/headers/Collect into Object.md | 1 + pkg/yqlib/doc/headers/Comments Operator.md | 1 + pkg/yqlib/doc/headers/Delete Operator.md | 1 + pkg/yqlib/doc/headers/Equals Operator.md | 14 ++ pkg/yqlib/doc/headers/Explode Operator.md | 1 + pkg/yqlib/doc/headers/Multiply Operator.md | 5 + pkg/yqlib/doc/headers/Not Operator.md | 1 + .../doc/headers/Recursive Descent Operator.md | 5 + .../doc/headers/Update Assign Operator.md | 1 + 23 files changed, 1115 insertions(+), 1 deletion(-) delete mode 100644 pkg/yqlib/doc/.gitignore create mode 100644 pkg/yqlib/doc/Collect into Array.md create mode 100644 pkg/yqlib/doc/Collect into Object.md create mode 100644 pkg/yqlib/doc/Comments Operator.md create mode 100644 pkg/yqlib/doc/Delete Operator.md create mode 100644 pkg/yqlib/doc/Document Index Operator.md create mode 100644 pkg/yqlib/doc/Equals Operator.md create mode 100644 pkg/yqlib/doc/Explode Operator.md create mode 100644 pkg/yqlib/doc/Multiply Operator.md create mode 100644 pkg/yqlib/doc/Not Operator.md create mode 100644 pkg/yqlib/doc/Recursive Descent Operator.md create mode 100644 pkg/yqlib/doc/Style Operator.md create mode 100644 pkg/yqlib/doc/Update Assign Operator.md create mode 100644 pkg/yqlib/doc/headers/Collect into Array.md create mode 100644 pkg/yqlib/doc/headers/Collect into Object.md create mode 100644 pkg/yqlib/doc/headers/Comments Operator.md create mode 100644 pkg/yqlib/doc/headers/Delete Operator.md create mode 100644 pkg/yqlib/doc/headers/Equals Operator.md create mode 100644 pkg/yqlib/doc/headers/Explode Operator.md create mode 100644 pkg/yqlib/doc/headers/Multiply Operator.md create mode 100644 pkg/yqlib/doc/headers/Not Operator.md create mode 100644 pkg/yqlib/doc/headers/Recursive Descent Operator.md create mode 100644 pkg/yqlib/doc/headers/Update Assign Operator.md diff --git a/pkg/yqlib/doc/.gitignore b/pkg/yqlib/doc/.gitignore deleted file mode 100644 index dd449725..00000000 --- a/pkg/yqlib/doc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.md diff --git a/pkg/yqlib/doc/Collect into Array.md b/pkg/yqlib/doc/Collect into Array.md new file mode 100644 index 00000000..fa8e25a8 --- /dev/null +++ b/pkg/yqlib/doc/Collect into Array.md @@ -0,0 +1,41 @@ +# Collect into Array + +This creates an array using the expression between the square brackets. + + +## Examples +### Collect empty +Running +```bash +yq eval --null-input '[]' +``` +will output +```yaml +``` + +### Collect single +Running +```bash +yq eval --null-input '["cat"]' +``` +will output +```yaml +- cat +``` + +### Collect many +Given a sample.yml file of: +```yaml +a: cat +b: dog +``` +then +```bash +yq eval '[.a, .b]' sample.yml +``` +will output +```yaml +- cat +- dog +``` + diff --git a/pkg/yqlib/doc/Collect into Object.md b/pkg/yqlib/doc/Collect into Object.md new file mode 100644 index 00000000..a54a262d --- /dev/null +++ b/pkg/yqlib/doc/Collect into Object.md @@ -0,0 +1,80 @@ +This is used to construct objects (or maps). This can be used against existing yaml, or to create fresh yaml documents. +## Examples +### Collect empty object +Running +```bash +yq eval --null-input '{}' +``` +will output +```yaml +``` + +### Wrap (prefix) existing object +Given a sample.yml file of: +```yaml +name: Mike +``` +then +```bash +yq eval '{"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 eval '{.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 eval '{.name: .pets[]}' sample.yml +``` +will output +```yaml +Mike: cat +Mike: dog +--- +Rosey: monkey +--- +Rosey: sheep +``` + +### Creating yaml from scratch +Running +```bash +yq eval --null-input '{"wrap": "frog"}' +``` +will output +```yaml +wrap: frog +``` + diff --git a/pkg/yqlib/doc/Comments Operator.md b/pkg/yqlib/doc/Comments Operator.md new file mode 100644 index 00000000..78b41b44 --- /dev/null +++ b/pkg/yqlib/doc/Comments Operator.md @@ -0,0 +1,120 @@ +Use these comment operators to set or retrieve comments. +## Examples +### Set line comment +Given a sample.yml file of: +```yaml +a: cat +``` +then +```bash +yq eval '.a lineComment="single"' sample.yml +``` +will output +```yaml +a: cat # single +``` + +### Set head comment +Given a sample.yml file of: +```yaml +a: cat +``` +then +```bash +yq eval '. headComment="single"' sample.yml +``` +will output +```yaml +# single + +a: cat +``` + +### Set foot comment, using an expression +Given a sample.yml file of: +```yaml +a: cat +``` +then +```bash +yq eval '. footComment=.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 eval '.a lineComment=""' sample.yml +``` +will output +```yaml +a: cat +b: dog # leave this +``` + +### Remove all comments +Given a sample.yml file of: +```yaml +a: cat # comment +``` +then +```bash +yq eval '.. comments=""' sample.yml +``` +will output +```yaml +a: cat +``` + +### Get line comment +Given a sample.yml file of: +```yaml +a: cat # meow +``` +then +```bash +yq eval '.a | lineComment' sample.yml +``` +will output +```yaml +meow +``` + +### Get head comment +Given a sample.yml file of: +```yaml +a: cat # meow +``` +then +```bash +yq eval '. | headComment' sample.yml +``` +will output +```yaml +welcome! +``` + +### Get foot comment +Given a sample.yml file of: +```yaml +a: cat # meow +``` +then +```bash +yq eval '. | footComment' sample.yml +``` +will output +```yaml +have a great day +``` + diff --git a/pkg/yqlib/doc/Delete Operator.md b/pkg/yqlib/doc/Delete Operator.md new file mode 100644 index 00000000..6b42520c --- /dev/null +++ b/pkg/yqlib/doc/Delete Operator.md @@ -0,0 +1,48 @@ +Deletes matching entries in maps or arrays. +## Examples +### Delete entry in map +Given a sample.yml file of: +```yaml +a: cat +b: dog +``` +then +```bash +yq eval 'del(.b)' sample.yml +``` +will output +```yaml +{a: cat} +``` + +### Delete entry in array +Given a sample.yml file of: +```yaml +- 1 +- 2 +- 3 +``` +then +```bash +yq eval 'del(.[1])' sample.yml +``` +will output +```yaml +[1, 3] +``` + +### Delete no matches +Given a sample.yml file of: +```yaml +a: cat +b: dog +``` +then +```bash +yq eval 'del(.c)' sample.yml +``` +will output +```yaml +{a: cat, b: dog} +``` + diff --git a/pkg/yqlib/doc/Document Index Operator.md b/pkg/yqlib/doc/Document Index Operator.md new file mode 100644 index 00000000..33126730 --- /dev/null +++ b/pkg/yqlib/doc/Document Index Operator.md @@ -0,0 +1,56 @@ + +## Examples +### Retrieve a document index +Given a sample.yml file of: +```yaml +a: cat +--- +a: frog +``` +then +```bash +yq eval '.a | documentIndex' 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 eval 'select(. | documentIndex == 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 eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml +``` +will output +```yaml +match: cat +doc: 0 +--- +match: frog +doc: 1 +``` + diff --git a/pkg/yqlib/doc/Equals Operator.md b/pkg/yqlib/doc/Equals Operator.md new file mode 100644 index 00000000..904b0eea --- /dev/null +++ b/pkg/yqlib/doc/Equals Operator.md @@ -0,0 +1,62 @@ +## Equals Operator + +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) +``` + + +## Examples +### Match string +Given a sample.yml file of: +```yaml +- cat +- goat +- dog +``` +then +```bash +yq eval '.[] | (. == "*at")' sample.yml +``` +will output +```yaml +true +true +false +``` + +### Match number +Given a sample.yml file of: +```yaml +- 3 +- 4 +- 5 +``` +then +```bash +yq eval '.[] | (. == 4)' sample.yml +``` +will output +```yaml +false +true +false +``` + +### Match nulls +Running +```bash +yq eval --null-input 'null == ~' +``` +will output +```yaml +true +``` + diff --git a/pkg/yqlib/doc/Explode Operator.md b/pkg/yqlib/doc/Explode Operator.md new file mode 100644 index 00000000..80e70890 --- /dev/null +++ b/pkg/yqlib/doc/Explode Operator.md @@ -0,0 +1,95 @@ +Explodes (or dereferences) aliases and anchors. +## Examples +### Explode alias and anchor +Given a sample.yml file of: +```yaml +f: + a: &a cat + b: *a +``` +then +```bash +yq eval '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 eval '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 eval '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 eval '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 + a: foo_a + thing: bar_thing + c: foobarList_c +foobar: + c: foo_c + a: foo_a + thing: foobar_thing +``` + diff --git a/pkg/yqlib/doc/Multiply Operator.md b/pkg/yqlib/doc/Multiply Operator.md new file mode 100644 index 00000000..c82265e6 --- /dev/null +++ b/pkg/yqlib/doc/Multiply Operator.md @@ -0,0 +1,196 @@ +Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS. + +Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). + +Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. +## Examples +### Merge objects together, returning merged result only +Given a sample.yml file of: +```yaml +a: + field: me + fieldA: cat +b: + field: + g: wizz + fieldB: dog +``` +then +```bash +yq eval '.a * .b' sample.yml +``` +will output +```yaml +field: + g: wizz +fieldA: cat +fieldB: dog +``` + +### Merge objects together, returning parent object +Given a sample.yml file of: +```yaml +a: + field: me + fieldA: cat +b: + field: + g: wizz + fieldB: dog +``` +then +```bash +yq eval '. * {"a":.b}' sample.yml +``` +will output +```yaml +a: + field: + g: wizz + fieldA: cat + fieldB: dog +b: + field: + g: wizz + fieldB: dog +``` + +### Merge keeps style of LHS +Given a sample.yml file of: +```yaml +a: {things: great} +b: + also: "me" +``` +then +```bash +yq eval '. * {"a":.b}' sample.yml +``` +will output +```yaml +a: {things: great, also: "me"} +b: + also: "me" +``` + +### Merge arrays +Given a sample.yml file of: +```yaml +a: + - 1 + - 2 + - 3 +b: + - 3 + - 4 + - 5 +``` +then +```bash +yq eval '. * {"a":.b}' sample.yml +``` +will output +```yaml +a: + - 3 + - 4 + - 5 +b: + - 3 + - 4 + - 5 +``` + +### Merge to prefix an element +Given a sample.yml file of: +```yaml +a: cat +b: dog +``` +then +```bash +yq eval '. * {"a": {"c": .a}}' sample.yml +``` +will output +```yaml +a: + c: cat +b: dog +``` + +### Merge with simple aliases +Given a sample.yml file of: +```yaml +a: &cat + c: frog +b: + f: *cat +c: + g: thongs +``` +then +```bash +yq eval '.c * .b' sample.yml +``` +will output +```yaml +g: thongs +f: *cat +``` + +### Merge does not copy anchor names +Given a sample.yml file of: +```yaml +a: + c: &cat frog +b: + f: *cat +c: + g: thongs +``` +then +```bash +yq eval '.c * .a' sample.yml +``` +will output +```yaml +g: thongs +c: frog +``` + +### Merge 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 eval '.foobar * .foobarList' sample.yml +``` +will output +```yaml +c: foobarList_c +<<: + - *foo + - *bar +thing: foobar_thing +b: foobarList_b +``` + diff --git a/pkg/yqlib/doc/Not Operator.md b/pkg/yqlib/doc/Not Operator.md new file mode 100644 index 00000000..49d4fd54 --- /dev/null +++ b/pkg/yqlib/doc/Not Operator.md @@ -0,0 +1,72 @@ +This is a boolean operator and will return `true` when given a `false` value (including null), and `false` otherwise. +## Examples +### Not true is false +Running +```bash +yq eval --null-input 'true | not' +``` +will output +```yaml +false +``` + +### Not false is true +Running +```bash +yq eval --null-input 'false | not' +``` +will output +```yaml +true +``` + +### String values considered to be true +Running +```bash +yq eval --null-input '"cat" | not' +``` +will output +```yaml +false +``` + +### Empty string value considered to be true +Running +```bash +yq eval --null-input '"" | not' +``` +will output +```yaml +false +``` + +### Numbers are considered to be true +Running +```bash +yq eval --null-input '1 | not' +``` +will output +```yaml +false +``` + +### Zero is considered to be true +Running +```bash +yq eval --null-input '0 | not' +``` +will output +```yaml +false +``` + +### Null is considered to be false +Running +```bash +yq eval --null-input '~ | not' +``` +will output +```yaml +true +``` + diff --git a/pkg/yqlib/doc/Recursive Descent Operator.md b/pkg/yqlib/doc/Recursive Descent Operator.md new file mode 100644 index 00000000..6d0069b0 --- /dev/null +++ b/pkg/yqlib/doc/Recursive Descent Operator.md @@ -0,0 +1,139 @@ +This operator recursively matches 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, for instance to set the `style` of all nodes in a yaml doc: + +```bash +yq eval '.. style = "flow"' file.yaml +``` +## Examples +### Matches single scalar value +Given a sample.yml file of: +```yaml +cat +``` +then +```bash +yq eval '..' sample.yml +``` +will output +```yaml +cat +``` + +### Map +Given a sample.yml file of: +```yaml +a: + b: apple +``` +then +```bash +yq eval '..' sample.yml +``` +will output +```yaml +a: + b: apple +b: apple +apple +``` + +### Array +Given a sample.yml file of: +```yaml +- 1 +- 2 +- 3 +``` +then +```bash +yq eval '..' sample.yml +``` +will output +```yaml +- 1 +- 2 +- 3 +1 +2 +3 +``` + +### Array of maps +Given a sample.yml file of: +```yaml +- a: cat +- 2 +- true +``` +then +```bash +yq eval '..' sample.yml +``` +will output +```yaml +- a: cat +- 2 +- true +a: cat +cat +2 +true +``` + +### Aliases are not traversed +Given a sample.yml file of: +```yaml +a: &cat + c: frog +b: *cat +``` +then +```bash +yq eval '..' sample.yml +``` +will output +```yaml +a: &cat + c: frog +b: *cat +&cat +c: frog +frog +*cat +``` + +### Merge docs are not traversed +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 eval '.foobar | ..' sample.yml +``` +will output +```yaml +c: foobar_c +!!merge <<: *foo +thing: foobar_thing +foobar_c +*foo +foobar_thing +``` + diff --git a/pkg/yqlib/doc/Style Operator.md b/pkg/yqlib/doc/Style Operator.md new file mode 100644 index 00000000..1d0b38fd --- /dev/null +++ b/pkg/yqlib/doc/Style Operator.md @@ -0,0 +1,79 @@ + +## Examples +### Example 0 +Given a sample.yml file of: +```yaml +a: cat +``` +then +```bash +yq eval '.a style="single"' sample.yml +``` +will output +```yaml +{a: 'cat'} +``` + +### Set style using a path +Given a sample.yml file of: +```yaml +a: cat +b: double +``` +then +```bash +yq eval '.a style=.b' sample.yml +``` +will output +```yaml +{a: "cat", b: double} +``` + +### Example 2 +Given a sample.yml file of: +```yaml +a: cat +b: dog +``` +then +```bash +yq eval '.. style=""' sample.yml +``` +will output +```yaml +a: cat +b: dog +``` + +### Example 3 +Given a sample.yml file of: +```yaml +a: cat +b: thing +``` +then +```bash +yq eval '.. | style' sample.yml +``` +will output +```yaml +flow +double +single +``` + +### Example 4 +Given a sample.yml file of: +```yaml +a: cat +``` +then +```bash +yq eval '.. | style' sample.yml +``` +will output +```yaml + + +``` + diff --git a/pkg/yqlib/doc/Update Assign Operator.md b/pkg/yqlib/doc/Update Assign Operator.md new file mode 100644 index 00000000..7b28878b --- /dev/null +++ b/pkg/yqlib/doc/Update Assign Operator.md @@ -0,0 +1,93 @@ +Updates the LHS using the expression on the RHS. Note that the RHS runs against the _original_ LHS value, so that you can evaluate a new value based on the old (e.g. increment). +## Examples +### Update parent to be the child value +Given a sample.yml file of: +```yaml +a: + b: + g: foof +``` +then +```bash +yq eval '.a |= .b' sample.yml +``` +will output +```yaml +{a: {g: foof}} +``` + +### Update string value +Given a sample.yml file of: +```yaml +a: + b: apple +``` +then +```bash +yq eval '.a.b |= "frog"' sample.yml +``` +will output +```yaml +{a: {b: frog}} +``` + +### Update selected results +Given a sample.yml file of: +```yaml +a: + b: apple + c: cactus +``` +then +```bash +yq eval '.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 eval '.[] | select(. == "*andy") |= "bogs"' sample.yml +``` +will output +```yaml +[bogs, apple, bogs] +``` + +### Update empty object +Given a sample.yml file of: +```yaml +'': null +``` +then +```bash +yq eval '.a.b |= "bogs"' sample.yml +``` +will output +```yaml +{a: {b: bogs}} +``` + +### Update empty object and array +Given a sample.yml file of: +```yaml +'': null +``` +then +```bash +yq eval '.a.b[0] |= "bogs"' sample.yml +``` +will output +```yaml +{a: {b: [bogs]}} +``` + diff --git a/pkg/yqlib/doc/headers/Collect into Array.md b/pkg/yqlib/doc/headers/Collect into Array.md new file mode 100644 index 00000000..defa7ce8 --- /dev/null +++ b/pkg/yqlib/doc/headers/Collect into Array.md @@ -0,0 +1,4 @@ +# Collect into Array + +This creates an array using the expression between the square brackets. + diff --git a/pkg/yqlib/doc/headers/Collect into Object.md b/pkg/yqlib/doc/headers/Collect into Object.md new file mode 100644 index 00000000..e0552575 --- /dev/null +++ b/pkg/yqlib/doc/headers/Collect into Object.md @@ -0,0 +1 @@ +This is used to construct objects (or maps). This can be used against existing yaml, or to create fresh yaml documents. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Comments Operator.md b/pkg/yqlib/doc/headers/Comments Operator.md new file mode 100644 index 00000000..aacd9771 --- /dev/null +++ b/pkg/yqlib/doc/headers/Comments Operator.md @@ -0,0 +1 @@ +Use these comment operators to set or retrieve comments. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Delete Operator.md b/pkg/yqlib/doc/headers/Delete Operator.md new file mode 100644 index 00000000..e3c0d1cc --- /dev/null +++ b/pkg/yqlib/doc/headers/Delete Operator.md @@ -0,0 +1 @@ +Deletes matching entries in maps or arrays. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Equals Operator.md b/pkg/yqlib/doc/headers/Equals Operator.md new file mode 100644 index 00000000..a60d8dfa --- /dev/null +++ b/pkg/yqlib/doc/headers/Equals Operator.md @@ -0,0 +1,14 @@ +## Equals Operator + +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) +``` + diff --git a/pkg/yqlib/doc/headers/Explode Operator.md b/pkg/yqlib/doc/headers/Explode Operator.md new file mode 100644 index 00000000..5fea8360 --- /dev/null +++ b/pkg/yqlib/doc/headers/Explode Operator.md @@ -0,0 +1 @@ +Explodes (or dereferences) aliases and anchors. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Multiply Operator.md b/pkg/yqlib/doc/headers/Multiply Operator.md new file mode 100644 index 00000000..3597af37 --- /dev/null +++ b/pkg/yqlib/doc/headers/Multiply Operator.md @@ -0,0 +1,5 @@ +Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS. + +Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). + +Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Not Operator.md b/pkg/yqlib/doc/headers/Not Operator.md new file mode 100644 index 00000000..87022061 --- /dev/null +++ b/pkg/yqlib/doc/headers/Not Operator.md @@ -0,0 +1 @@ +This is a boolean operator and will return `true` when given a `false` value (including null), and `false` otherwise. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Recursive Descent Operator.md b/pkg/yqlib/doc/headers/Recursive Descent Operator.md new file mode 100644 index 00000000..530e64c6 --- /dev/null +++ b/pkg/yqlib/doc/headers/Recursive Descent Operator.md @@ -0,0 +1,5 @@ +This operator recursively matches 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, for instance to set the `style` of all nodes in a yaml doc: + +```bash +yq eval '.. style= "flow"' file.yaml +``` \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Update Assign Operator.md b/pkg/yqlib/doc/headers/Update Assign Operator.md new file mode 100644 index 00000000..c555f5b5 --- /dev/null +++ b/pkg/yqlib/doc/headers/Update Assign Operator.md @@ -0,0 +1 @@ +Updates the LHS using the expression on the RHS. Note that the RHS runs against the _original_ LHS value, so that you can evaluate a new value based on the old (e.g. increment). \ No newline at end of file From 2c062bc2a55704e8ea58cc88d7648c50902dbd73 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 18 Nov 2020 09:37:42 +1100 Subject: [PATCH 079/129] Added printer test --- pkg/yqlib/lib.go | 6 +-- pkg/yqlib/printer_test.go | 82 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 pkg/yqlib/printer_test.go diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index de52cc78..542a232d 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -17,13 +17,11 @@ type OperationType struct { } // operators TODO: -// - add print test, particular for streams with multilpe docs -// (one that print doc1, then doc 3) in two calls -// (one that prints doc1, then doc 3) in one call -// - write in place +// - get Kind // - get path operator (like doc index) // - get file index op (like doc index) // - get file name op (like doc index) +// - write in place // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) // - updateTag - not recursive diff --git a/pkg/yqlib/printer_test.go b/pkg/yqlib/printer_test.go new file mode 100644 index 00000000..e60973d8 --- /dev/null +++ b/pkg/yqlib/printer_test.go @@ -0,0 +1,82 @@ +package yqlib + +import ( + "bufio" + "bytes" + "strings" + "testing" + + "github.com/mikefarah/yq/v4/test" +) + +var multiDocSample = `a: banana +--- +a: apple +--- +a: coconut +` + +func TestPrinterMultipleDocsInSequence(t *testing.T) { + var output bytes.Buffer + var writer = bufio.NewWriter(&output) + printer := NewPrinter(writer, false, true, false, 2, true) + + inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml") + if err != nil { + panic(err) + } + + el := inputs.Front() + sample1 := nodeToMap(el.Value.(*CandidateNode)) + + el = el.Next() + sample2 := nodeToMap(el.Value.(*CandidateNode)) + + el = el.Next() + sample3 := nodeToMap(el.Value.(*CandidateNode)) + + printer.PrintResults(sample1) + printer.PrintResults(sample2) + printer.PrintResults(sample3) + + writer.Flush() + test.AssertResult(t, multiDocSample, output.String()) + +} + +func TestPrinterMultipleDocsInSinglePrint(t *testing.T) { + var output bytes.Buffer + var writer = bufio.NewWriter(&output) + printer := NewPrinter(writer, false, true, false, 2, true) + + inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml") + if err != nil { + panic(err) + } + + printer.PrintResults(inputs) + + writer.Flush() + test.AssertResult(t, multiDocSample, output.String()) +} + +func TestPrinterMultipleDocsJson(t *testing.T) { + var output bytes.Buffer + var writer = bufio.NewWriter(&output) + printer := NewPrinter(writer, true, true, false, 0, false) + + inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml") + if err != nil { + panic(err) + } + + printer.PrintResults(inputs) + + expected := `{"a":"banana"} +{"a":"apple"} +{"a":"coconut"} +` + + writer.Flush() + test.AssertResult(t, expected, output.String()) +} From 3356061e1e41ac2c072ccd0d2e281fff12aa214e Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 18 Nov 2020 09:41:29 +1100 Subject: [PATCH 080/129] select doc --- pkg/yqlib/operator_select_test.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/yqlib/operator_select_test.go b/pkg/yqlib/operator_select_test.go index 2af3c989..b7ae38f9 100644 --- a/pkg/yqlib/operator_select_test.go +++ b/pkg/yqlib/operator_select_test.go @@ -6,29 +6,34 @@ import ( var selectOperatorScenarios = []expressionScenario{ { - document: `[cat,goat,dog]`, - expression: `.[] | select(. == "*at")`, + description: "Select elements from array", + document: `[cat,goat,dog]`, + expression: `.[] | select(. == "*at")`, expected: []string{ "D0, P[0], (!!str)::cat\n", "D0, P[1], (!!str)::goat\n", }, }, { + skipDoc: true, document: `[hot, fot, dog]`, expression: `.[] | select(. == "*at")`, expected: []string{}, }, { + skipDoc: true, document: `a: [cat,goat,dog]`, expression: `.a[] | select(. == "*at")`, expected: []string{ "D0, P[a 0], (!!str)::cat\n", "D0, P[a 1], (!!str)::goat\n"}, }, { - document: `a: { things: cat, bob: goat, horse: dog }`, - expression: `.a[] | select(. == "*at")`, + description: "Select matching values in map", + document: `a: { things: cat, bob: goat, horse: dog }`, + expression: `.a[] | select(. == "*at")`, expected: []string{ "D0, P[a things], (!!str)::cat\n", "D0, P[a bob], (!!str)::goat\n"}, }, { + skipDoc: true, document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`, expression: `.a[] | select(.include)`, expected: []string{ @@ -36,6 +41,7 @@ var selectOperatorScenarios = []expressionScenario{ "D0, P[a andMe], (!!map)::{include: fold}\n", }, }, { + skipDoc: true, document: `[cat,~,dog]`, expression: `.[] | select(. == ~)`, expected: []string{ @@ -48,4 +54,5 @@ func TestSelectOperatorScenarios(t *testing.T) { for _, tt := range selectOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Select Operator", selectOperatorScenarios) } From dcacad1e7e78cc53f7f895b180c8a986b6743f6e Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 18 Nov 2020 10:32:30 +1100 Subject: [PATCH 081/129] docs --- pkg/yqlib/doc/Mulitply Operator.md | 121 +++++++ pkg/yqlib/doc/Select Operator.md | 39 +++ pkg/yqlib/doc/Traverse Operator.md | 353 +++++++++++++++++++++ pkg/yqlib/doc/Union Operator.md | 49 +++ pkg/yqlib/doc/Update Assign Operator.md | 41 ++- pkg/yqlib/doc/headers/Select Operator.md | 1 + pkg/yqlib/doc/headers/Traverse Operator.md | 1 + pkg/yqlib/doc/headers/Union Operator.md | 1 + pkg/yqlib/lib.go | 1 - pkg/yqlib/operator_assign_update_test.go | 8 + pkg/yqlib/operator_select_test.go | 23 +- pkg/yqlib/operator_traverse_path_test.go | 96 ++++-- pkg/yqlib/operator_union_test.go | 22 +- pkg/yqlib/operators_test.go | 5 + 14 files changed, 706 insertions(+), 55 deletions(-) create mode 100644 pkg/yqlib/doc/Mulitply Operator.md create mode 100644 pkg/yqlib/doc/Select Operator.md create mode 100644 pkg/yqlib/doc/Traverse Operator.md create mode 100644 pkg/yqlib/doc/Union Operator.md create mode 100644 pkg/yqlib/doc/headers/Select Operator.md create mode 100644 pkg/yqlib/doc/headers/Traverse Operator.md create mode 100644 pkg/yqlib/doc/headers/Union Operator.md diff --git a/pkg/yqlib/doc/Mulitply Operator.md b/pkg/yqlib/doc/Mulitply Operator.md new file mode 100644 index 00000000..24c00aa2 --- /dev/null +++ b/pkg/yqlib/doc/Mulitply Operator.md @@ -0,0 +1,121 @@ +# Mulitply Operator +## Examples +### Merge objects together +sample.yml: +```yaml +{a: {also: me}, b: {also: {g: wizz}}} +``` +Expression +```bash +yq '. * {"a":.b}' < sample.yml +``` +Result +```yaml +{a: {also: {g: wizz}}, b: {also: {g: wizz}}} +``` +### Merge keeps style of LHS +sample.yml: +```yaml +a: {things: great} +b: + also: "me" + +``` +Expression +```bash +yq '. * {"a":.b}' < sample.yml +``` +Result +```yaml +a: {things: great, also: "me"} +b: + also: "me" +``` +### Merge arrays +sample.yml: +```yaml +{a: [1,2,3], b: [3,4,5]} +``` +Expression +```bash +yq '. * {"a":.b}' < sample.yml +``` +Result +```yaml +{a: [3, 4, 5], b: [3, 4, 5]} +``` +### Merge to prefix an element +sample.yml: +```yaml +{a: cat, b: dog} +``` +Expression +```bash +yq '. * {"a": {"c": .a}}' < sample.yml +``` +Result +```yaml +{a: {c: cat}, b: dog} +``` +### Merge with simple aliases +sample.yml: +```yaml +{a: &cat {c: frog}, b: {f: *cat}, c: {g: thongs}} +``` +Expression +```bash +yq '.c * .b' < sample.yml +``` +Result +```yaml +{g: thongs, f: *cat} +``` +### Merge does not copy anchor names +sample.yml: +```yaml +{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}} +``` +Expression +```bash +yq '.c * .a' < sample.yml +``` +Result +```yaml +{g: thongs, c: frog} +``` +### Merge with merge anchors +sample.yml: +```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 + <<: [*foo,*bar] + c: foobarList_c + +foobar: + c: foobar_c + <<: *foo + thing: foobar_thing + +``` +Expression +```bash +yq '.foobar * .foobarList' < sample.yml +``` +Result +```yaml +c: foobarList_c +<<: [*foo, *bar] +thing: foobar_thing +b: foobarList_b +``` diff --git a/pkg/yqlib/doc/Select Operator.md b/pkg/yqlib/doc/Select Operator.md new file mode 100644 index 00000000..3ddb6cc9 --- /dev/null +++ b/pkg/yqlib/doc/Select Operator.md @@ -0,0 +1,39 @@ +Select is used to filter arrays and maps by a boolean expression. +## Examples +### Select elements from array +Given a sample.yml file of: +```yaml +- cat +- goat +- dog +``` +then +```bash +yq eval '.[] | select(. == "*at")' sample.yml +``` +will output +```yaml +cat +goat +``` + +### Select and update matching values in map +Given a sample.yml file of: +```yaml +a: + things: cat + bob: goat + horse: dog +``` +then +```bash +yq eval '(.a[] | select(. == "*at")) |= "rabbit"' sample.yml +``` +will output +```yaml +a: + things: rabbit + bob: rabbit + horse: dog +``` + diff --git a/pkg/yqlib/doc/Traverse Operator.md b/pkg/yqlib/doc/Traverse Operator.md new file mode 100644 index 00000000..dace7ed4 --- /dev/null +++ b/pkg/yqlib/doc/Traverse Operator.md @@ -0,0 +1,353 @@ +This is the simples (and perhaps most used) operator, it is used to navigate deeply into yaml structurse. +## Examples +### Simple map navigation +Given a sample.yml file of: +```yaml +a: + b: apple +``` +then +```bash +yq eval '.a' sample.yml +``` +will output +```yaml +b: apple +``` + +### Splat +Often used to pipe children into other operators + +Given a sample.yml file of: +```yaml +- b: apple +- c: banana +``` +then +```bash +yq eval '.[]' sample.yml +``` +will output +```yaml +b: apple +c: banana +``` + +### Children don't exist +Nodes are added dynamically while traversing + +Given a sample.yml file of: +```yaml +c: banana +``` +then +```bash +yq eval '.a.b' sample.yml +``` +will output +```yaml +null +``` + +### Wildcard matching +Given a sample.yml file of: +```yaml +a: + cat: apple + mad: things +``` +then +```bash +yq eval '.a."*a*"' sample.yml +``` +will output +```yaml +apple +things +``` + +### Aliases +Given a sample.yml file of: +```yaml +a: &cat + c: frog +b: *cat +``` +then +```bash +yq eval '.b' sample.yml +``` +will output +```yaml +*cat +``` + +### Traversing aliases with splat +Given a sample.yml file of: +```yaml +a: &cat + c: frog +b: *cat +``` +then +```bash +yq eval '.b.[]' sample.yml +``` +will output +```yaml +frog +``` + +### Traversing aliases explicitly +Given a sample.yml file of: +```yaml +a: &cat + c: frog +b: *cat +``` +then +```bash +yq eval '.b.c' sample.yml +``` +will output +```yaml +frog +``` + +### Traversing arrays by index +Given a sample.yml file of: +```yaml +- 1 +- 2 +- 3 +``` +then +```bash +yq eval '[0]' sample.yml +``` +will output +```yaml +1 +``` + +### Maps with numeric keys +Given a sample.yml file of: +```yaml +2: cat +``` +then +```bash +yq eval '[2]' sample.yml +``` +will output +```yaml +cat +``` + +### Maps with non existing numeric keys +Given a sample.yml file of: +```yaml +a: b +``` +then +```bash +yq eval '[0]' sample.yml +``` +will output +```yaml +null +``` + +### Traversing 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 eval '.foobar.a' sample.yml +``` +will output +```yaml +foo_a +``` + +### Traversing merge anchors with override +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 eval '.foobar.c' sample.yml +``` +will output +```yaml +foo_c +``` + +### Traversing merge anchors with local override +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 eval '.foobar.thing' sample.yml +``` +will output +```yaml +foobar_thing +``` + +### Splatting 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 eval '.foobar.[]' sample.yml +``` +will output +```yaml +foo_c +foo_a +foobar_thing +``` + +### Traversing merge anchor lists +Note that the later merge anchors override previous + +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 eval '.foobarList.thing' sample.yml +``` +will output +```yaml +bar_thing +``` + +### Splatting merge anchor lists +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 eval '.foobarList.[]' sample.yml +``` +will output +```yaml +bar_b +foo_a +bar_thing +foobarList_c +``` + diff --git a/pkg/yqlib/doc/Union Operator.md b/pkg/yqlib/doc/Union Operator.md new file mode 100644 index 00000000..e1ce5fb2 --- /dev/null +++ b/pkg/yqlib/doc/Union Operator.md @@ -0,0 +1,49 @@ +This operator is used to combine different results together. +## Examples +### Combine scalars +Running +```bash +yq eval --null-input '1, true, "cat"' +``` +will output +```yaml +1 +true +cat +``` + +### Combine selected paths +Given a sample.yml file of: +```yaml +a: fieldA +b: fieldB +c: fieldC +``` +then +```bash +yq eval '.a, .c' sample.yml +``` +will output +```yaml +fieldA +fieldC +``` + +### Combine selected paths +Given a sample.yml file of: +```yaml +a: fieldA +b: fieldB +c: fieldC +``` +then +```bash +yq eval '(.a, .c) |= "potatoe"' sample.yml +``` +will output +```yaml +a: potatoe +b: fieldB +c: potatoe +``` + diff --git a/pkg/yqlib/doc/Update Assign Operator.md b/pkg/yqlib/doc/Update Assign Operator.md index 7b28878b..866d2db2 100644 --- a/pkg/yqlib/doc/Update Assign Operator.md +++ b/pkg/yqlib/doc/Update Assign Operator.md @@ -13,7 +13,26 @@ yq eval '.a |= .b' sample.yml ``` will output ```yaml -{a: {g: foof}} +a: + g: foof +``` + +### Updated multiple paths +Given a sample.yml file of: +```yaml +a: fieldA +b: fieldB +c: fieldC +``` +then +```bash +yq eval '(.a, .c) |= "potatoe"' sample.yml +``` +will output +```yaml +a: potatoe +b: fieldB +c: potatoe ``` ### Update string value @@ -28,7 +47,8 @@ yq eval '.a.b |= "frog"' sample.yml ``` will output ```yaml -{a: {b: frog}} +a: + b: frog ``` ### Update selected results @@ -44,7 +64,9 @@ yq eval '.a[] | select(. == "apple") |= "frog"' sample.yml ``` will output ```yaml -{a: {b: frog, c: cactus}} +a: + b: frog + c: cactus ``` ### Update array values @@ -60,7 +82,9 @@ yq eval '.[] | select(. == "*andy") |= "bogs"' sample.yml ``` will output ```yaml -[bogs, apple, bogs] +- bogs +- apple +- bogs ``` ### Update empty object @@ -74,7 +98,9 @@ yq eval '.a.b |= "bogs"' sample.yml ``` will output ```yaml -{a: {b: bogs}} +'': null +a: + b: bogs ``` ### Update empty object and array @@ -88,6 +114,9 @@ yq eval '.a.b[0] |= "bogs"' sample.yml ``` will output ```yaml -{a: {b: [bogs]}} +'': null +a: + b: + - bogs ``` diff --git a/pkg/yqlib/doc/headers/Select Operator.md b/pkg/yqlib/doc/headers/Select Operator.md new file mode 100644 index 00000000..3d1546f1 --- /dev/null +++ b/pkg/yqlib/doc/headers/Select Operator.md @@ -0,0 +1 @@ +Select is used to filter arrays and maps by a boolean expression. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Traverse Operator.md b/pkg/yqlib/doc/headers/Traverse Operator.md new file mode 100644 index 00000000..ff02606c --- /dev/null +++ b/pkg/yqlib/doc/headers/Traverse Operator.md @@ -0,0 +1 @@ +This is the simples (and perhaps most used) operator, it is used to navigate deeply into yaml structurse. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Union Operator.md b/pkg/yqlib/doc/headers/Union Operator.md new file mode 100644 index 00000000..787bda7f --- /dev/null +++ b/pkg/yqlib/doc/headers/Union Operator.md @@ -0,0 +1 @@ +This operator is used to combine different results together. \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 542a232d..ed1e4bf0 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -17,7 +17,6 @@ type OperationType struct { } // operators TODO: -// - get Kind // - get path operator (like doc index) // - get file index op (like doc index) // - get file name op (like doc index) diff --git a/pkg/yqlib/operator_assign_update_test.go b/pkg/yqlib/operator_assign_update_test.go index eb2018c5..1fbc5e8c 100644 --- a/pkg/yqlib/operator_assign_update_test.go +++ b/pkg/yqlib/operator_assign_update_test.go @@ -13,6 +13,14 @@ var assignOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::{a: {g: foof}}\n", }, }, + { + description: "Updated multiple paths", + document: `{a: fieldA, b: fieldB, c: fieldC}`, + expression: `(.a, .c) |= "potatoe"`, + expected: []string{ + "D0, P[], (doc)::{a: potatoe, b: fieldB, c: potatoe}\n", + }, + }, { description: "Update string value", document: `{a: {b: apple}}`, diff --git a/pkg/yqlib/operator_select_test.go b/pkg/yqlib/operator_select_test.go index b7ae38f9..2d3f0966 100644 --- a/pkg/yqlib/operator_select_test.go +++ b/pkg/yqlib/operator_select_test.go @@ -13,26 +13,30 @@ var selectOperatorScenarios = []expressionScenario{ "D0, P[0], (!!str)::cat\n", "D0, P[1], (!!str)::goat\n", }, - }, { + }, + { skipDoc: true, document: `[hot, fot, dog]`, expression: `.[] | select(. == "*at")`, expected: []string{}, - }, { + }, + { skipDoc: true, document: `a: [cat,goat,dog]`, expression: `.a[] | select(. == "*at")`, expected: []string{ "D0, P[a 0], (!!str)::cat\n", "D0, P[a 1], (!!str)::goat\n"}, - }, { - description: "Select matching values in map", + }, + { + description: "Select and update matching values in map", document: `a: { things: cat, bob: goat, horse: dog }`, - expression: `.a[] | select(. == "*at")`, + expression: `(.a[] | select(. == "*at")) |= "rabbit"`, expected: []string{ - "D0, P[a things], (!!str)::cat\n", - "D0, P[a bob], (!!str)::goat\n"}, - }, { + "D0, P[], (doc)::a: {things: rabbit, bob: rabbit, horse: dog}\n", + }, + }, + { skipDoc: true, document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`, expression: `.a[] | select(.include)`, @@ -40,7 +44,8 @@ var selectOperatorScenarios = []expressionScenario{ "D0, P[a things], (!!map)::{include: true}\n", "D0, P[a andMe], (!!map)::{include: fold}\n", }, - }, { + }, + { skipDoc: true, document: `[cat,~,dog]`, expression: `.[] | select(. == ~)`, diff --git a/pkg/yqlib/operator_traverse_path_test.go b/pkg/yqlib/operator_traverse_path_test.go index 9006ea4c..16809ea1 100644 --- a/pkg/yqlib/operator_traverse_path_test.go +++ b/pkg/yqlib/operator_traverse_path_test.go @@ -28,28 +28,34 @@ foobar: var traversePathOperatorScenarios = []expressionScenario{ { - document: `{a: {b: apple}}`, - expression: `.a`, + description: "Simple map navigation", + document: `{a: {b: apple}}`, + expression: `.a`, expected: []string{ "D0, P[a], (!!map)::{b: apple}\n", }, }, { - document: `[{b: apple}, {c: banana}]`, - expression: `.[]`, + description: "Splat", + subdescription: "Often used to pipe children into other operators", + document: `[{b: apple}, {c: banana}]`, + expression: `.[]`, expected: []string{ "D0, P[0], (!!map)::{b: apple}\n", "D0, P[1], (!!map)::{c: banana}\n", }, }, { - document: `{}`, - expression: `.a.b`, + description: "Children don't exist", + subdescription: "Nodes are added dynamically while traversing", + document: `{c: banana}`, + expression: `.a.b`, expected: []string{ "D0, P[a b], (!!null)::null\n", }, }, { + skipDoc: true, document: `{}`, expression: `.[1].a`, expected: []string{ @@ -57,6 +63,7 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{}`, expression: `.a.[1]`, expected: []string{ @@ -64,14 +71,16 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { - document: `{a: {cat: apple, mad: things}}`, - expression: `.a."*a*"`, + description: "Wildcard matching", + document: `{a: {cat: apple, mad: things}}`, + expression: `.a."*a*"`, expected: []string{ "D0, P[a cat], (!!str)::apple\n", "D0, P[a mad], (!!str)::things\n", }, }, { + skipDoc: true, document: `{a: {cat: {b: 3}, mad: {b: 4}, fad: {c: t}}}`, expression: `.a."*a*".b`, expected: []string{ @@ -81,6 +90,7 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {cat: apple, mad: things}}`, expression: `.a | (.cat, .mad)`, expected: []string{ @@ -89,6 +99,7 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {cat: apple, mad: things}}`, expression: `.a | (.cat, .mad, .fad)`, expected: []string{ @@ -98,6 +109,7 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {cat: apple, mad: things}}`, expression: `.a | (.cat, .mad, .fad) | select( (. == null) | not)`, expected: []string{ @@ -106,40 +118,53 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { - document: `{a: &cat {c: frog}, b: *cat}`, - expression: `.b`, + description: "Aliases", + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `.b`, expected: []string{ "D0, P[b], (alias)::*cat\n", }, }, { - document: `{a: &cat {c: frog}, b: *cat}`, - expression: `.b.[]`, + description: "Traversing aliases with splat", + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `.b.[]`, expected: []string{ "D0, P[b c], (!!str)::frog\n", }, }, { - document: `{a: &cat {c: frog}, b: *cat}`, - expression: `.b.c`, + description: "Traversing aliases explicitly", + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `.b.c`, expected: []string{ "D0, P[b c], (!!str)::frog\n", }, }, { + skipDoc: true, document: `[1,2,3]`, expression: `.b`, expected: []string{}, }, { - document: `[1,2,3]`, - expression: `[0]`, + description: "Traversing arrays by index", + document: `[1,2,3]`, + expression: `[0]`, expected: []string{ "D0, P[0], (!!int)::1\n", }, }, { - description: `Maps can have numbers as keys, so this default to a non-exisiting key behaviour.`, + description: "Maps with numeric keys", + document: `{2: cat}`, + expression: `[2]`, + expected: []string{ + "D0, P[2], (!!str)::cat\n", + }, + }, + { + description: "Maps with non existing numeric keys", document: `{a: b}`, expression: `[0]`, expected: []string{ @@ -147,6 +172,7 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foobar`, expected: []string{ @@ -154,29 +180,33 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { - document: mergeDocSample, - expression: `.foobar.a`, + description: "Traversing merge anchors", + document: mergeDocSample, + expression: `.foobar.a`, expected: []string{ "D0, P[foobar a], (!!str)::foo_a\n", }, }, { - document: mergeDocSample, - expression: `.foobar.c`, + description: "Traversing merge anchors with override", + document: mergeDocSample, + expression: `.foobar.c`, expected: []string{ "D0, P[foobar c], (!!str)::foo_c\n", }, }, { - document: mergeDocSample, - expression: `.foobar.thing`, + description: "Traversing merge anchors with local override", + document: mergeDocSample, + expression: `.foobar.thing`, expected: []string{ "D0, P[foobar thing], (!!str)::foobar_thing\n", }, }, { - document: mergeDocSample, - expression: `.foobar.[]`, + description: "Splatting merge anchors", + document: mergeDocSample, + expression: `.foobar.[]`, expected: []string{ "D0, P[foobar c], (!!str)::foo_c\n", "D0, P[foobar a], (!!str)::foo_a\n", @@ -184,6 +214,7 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foobarList`, expected: []string{ @@ -191,6 +222,7 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foobarList.a`, expected: []string{ @@ -198,13 +230,16 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { - document: mergeDocSample, - expression: `.foobarList.thing`, + description: "Traversing merge anchor lists", + subdescription: "Note that the later merge anchors override previous", + document: mergeDocSample, + expression: `.foobarList.thing`, expected: []string{ "D0, P[foobarList thing], (!!str)::bar_thing\n", }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foobarList.c`, expected: []string{ @@ -212,6 +247,7 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foobarList.b`, expected: []string{ @@ -219,8 +255,9 @@ var traversePathOperatorScenarios = []expressionScenario{ }, }, { - document: mergeDocSample, - expression: `.foobarList.[]`, + description: "Splatting merge anchor lists", + document: mergeDocSample, + expression: `.foobarList.[]`, expected: []string{ "D0, P[foobarList b], (!!str)::bar_b\n", "D0, P[foobarList a], (!!str)::foo_a\n", @@ -234,4 +271,5 @@ func TestTraversePathOperatorScenarios(t *testing.T) { for _, tt := range traversePathOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Traverse Operator", traversePathOperatorScenarios) } diff --git a/pkg/yqlib/operator_union_test.go b/pkg/yqlib/operator_union_test.go index d28e4a98..578dd12a 100644 --- a/pkg/yqlib/operator_union_test.go +++ b/pkg/yqlib/operator_union_test.go @@ -6,20 +6,21 @@ import ( var unionOperatorScenarios = []expressionScenario{ { - document: `{}`, - expression: `"cat", "dog"`, - expected: []string{ - "D0, P[], (!!str)::cat\n", - "D0, P[], (!!str)::dog\n", - }, - }, { - document: `{a: frog}`, - expression: `1, true, "cat", .a`, + description: "Combine scalars", + expression: `1, true, "cat"`, expected: []string{ "D0, P[], (!!int)::1\n", "D0, P[], (!!bool)::true\n", "D0, P[], (!!str)::cat\n", - "D0, P[a], (!!str)::frog\n", + }, + }, + { + description: "Combine selected paths", + document: `{a: fieldA, b: fieldB, c: fieldC}`, + expression: `.a, .c`, + expected: []string{ + "D0, P[a], (!!str)::fieldA\n", + "D0, P[c], (!!str)::fieldC\n", }, }, } @@ -28,4 +29,5 @@ func TestUnionOperatorScenarios(t *testing.T) { for _, tt := range unionOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Union Operator", unionOperatorScenarios) } diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 7d4402b6..8af48869 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -15,6 +15,7 @@ import ( type expressionScenario struct { description string + subdescription string document string expression string expected []string @@ -123,6 +124,10 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari } else { writeOrPanic(w, fmt.Sprintf("### Example %v\n", index)) } + if s.subdescription != "" { + writeOrPanic(w, s.subdescription) + writeOrPanic(w, "\n\n") + } formattedDoc := "" if s.document != "" { if s.dontFormatInputForDoc { From bb3b08e648835f78c073936f5e5500f739d4547d Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 18 Nov 2020 20:42:37 +1100 Subject: [PATCH 082/129] wip style docs and test --- pkg/yqlib/doc/Style Operator.md | 155 ++++++++++++++++-- pkg/yqlib/doc/headers/Style Operator.md | 1 + .../{operatory_style.go => operator_style.go} | 0 pkg/yqlib/operator_style_test.go | 98 +++++++++++ pkg/yqlib/operatory_style_test.go | 54 ------ 5 files changed, 243 insertions(+), 65 deletions(-) create mode 100644 pkg/yqlib/doc/headers/Style Operator.md rename pkg/yqlib/{operatory_style.go => operator_style.go} (100%) create mode 100644 pkg/yqlib/operator_style_test.go delete mode 100644 pkg/yqlib/operatory_style_test.go diff --git a/pkg/yqlib/doc/Style Operator.md b/pkg/yqlib/doc/Style Operator.md index 1d0b38fd..f0080286 100644 --- a/pkg/yqlib/doc/Style Operator.md +++ b/pkg/yqlib/doc/Style Operator.md @@ -1,17 +1,149 @@ - +The style operator can be used to get or set the style of nodes (e.g. string style, yaml style) ## Examples -### Example 0 +### Set tagged style Given a sample.yml file of: ```yaml a: cat +b: 5 +c: 3.2 +e: true ``` then ```bash -yq eval '.a style="single"' sample.yml +yq eval '.. style="tagged"' sample.yml ``` will output ```yaml -{a: 'cat'} +!!map +a: !!str cat +b: !!int 5 +c: !!float 3.2 +e: !!bool true +``` + +### Set double quote style +Given a sample.yml file of: +```yaml +a: cat +b: 5 +c: 3.2 +e: true +``` +then +```bash +yq eval '.. style="double"' sample.yml +``` +will output +```yaml +a: "cat" +b: "5" +c: "3.2" +e: "true" +``` + +### Set single quote style +Given a sample.yml file of: +```yaml +a: cat +b: 5 +c: 3.2 +e: true +``` +then +```bash +yq eval '.. style="single"' sample.yml +``` +will output +```yaml +a: 'cat' +b: '5' +c: '3.2' +e: 'true' +``` + +### Set literal quote style +Given a sample.yml file of: +```yaml +a: cat +b: 5 +c: 3.2 +e: true +``` +then +```bash +yq eval '.. style="literal"' sample.yml +``` +will output +```yaml +a: |- + cat +b: |- + 5 +c: |- + 3.2 +e: |- + true +``` + +### Set folded quote style +Given a sample.yml file of: +```yaml +a: cat +b: 5 +c: 3.2 +e: true +``` +then +```bash +yq eval '.. style="folded"' sample.yml +``` +will output +```yaml +a: >- + cat +b: >- + 5 +c: >- + 3.2 +e: >- + true +``` + +### Set flow quote style +Given a sample.yml file of: +```yaml +a: cat +b: 5 +c: 3.2 +e: true +``` +then +```bash +yq eval '.. style="flow"' sample.yml +``` +will output +```yaml +{a: cat, b: 5, c: 3.2, e: true} +``` + +### Set empty (default) quote style +Given a sample.yml file of: +```yaml +a: cat +b: 5 +c: 3.2 +e: true +``` +then +```bash +yq eval '.. style=""' sample.yml +``` +will output +```yaml +a: cat +b: 5 +c: 3.2 +e: true ``` ### Set style using a path @@ -26,10 +158,11 @@ yq eval '.a style=.b' sample.yml ``` will output ```yaml -{a: "cat", b: double} +a: "cat" +b: double ``` -### Example 2 +### Example 8 Given a sample.yml file of: ```yaml a: cat @@ -45,7 +178,7 @@ a: cat b: dog ``` -### Example 3 +### Example 9 Given a sample.yml file of: ```yaml a: cat @@ -57,12 +190,12 @@ yq eval '.. | style' sample.yml ``` will output ```yaml -flow -double -single + + + ``` -### Example 4 +### Example 10 Given a sample.yml file of: ```yaml a: cat diff --git a/pkg/yqlib/doc/headers/Style Operator.md b/pkg/yqlib/doc/headers/Style Operator.md new file mode 100644 index 00000000..08fc4d23 --- /dev/null +++ b/pkg/yqlib/doc/headers/Style Operator.md @@ -0,0 +1 @@ +The style operator can be used to get or set the style of nodes (e.g. string style, yaml style) \ No newline at end of file diff --git a/pkg/yqlib/operatory_style.go b/pkg/yqlib/operator_style.go similarity index 100% rename from pkg/yqlib/operatory_style.go rename to pkg/yqlib/operator_style.go diff --git a/pkg/yqlib/operator_style_test.go b/pkg/yqlib/operator_style_test.go new file mode 100644 index 00000000..1bdaf983 --- /dev/null +++ b/pkg/yqlib/operator_style_test.go @@ -0,0 +1,98 @@ +package yqlib + +import ( + "testing" +) + +var styleOperatorScenarios = []expressionScenario{ + { + description: "Set tagged style", + document: `{a: cat, b: 5, c: 3.2, e: true}`, + expression: `.. style="tagged"`, + expected: []string{ + "D0, P[], (doc)::{a: 'cat'}\n", + }, + }, + { + description: "Set double quote style", + document: `{a: cat, b: 5, c: 3.2, e: true}`, + expression: `.. style="double"`, + expected: []string{ + "D0, P[], (doc)::{a: 'cat'}\n", + }, + }, + { + description: "Set single quote style", + document: `{a: cat, b: 5, c: 3.2, e: true}`, + expression: `.. style="single"`, + expected: []string{ + "D0, P[], (doc)::{a: 'cat'}\n", + }, + }, + { + description: "Set literal quote style", + document: `{a: cat, b: 5, c: 3.2, e: true}`, + expression: `.. style="literal"`, + expected: []string{ + "D0, P[], (doc)::{a: 'cat'}\n", + }, + }, + { + description: "Set folded quote style", + document: `{a: cat, b: 5, c: 3.2, e: true}`, + expression: `.. style="folded"`, + expected: []string{ + "D0, P[], (doc)::{a: 'cat'}\n", + }, + }, + { + description: "Set flow quote style", + document: `{a: cat, b: 5, c: 3.2, e: true}`, + expression: `.. style="flow"`, + expected: []string{ + "D0, P[], (doc)::{a: 'cat'}\n", + }, + }, + { + description: "Set empty (default) quote style", + document: `{a: cat, b: 5, c: 3.2, e: true}`, + expression: `.. style=""`, + expected: []string{ + "D0, P[], (doc)::{a: 'cat'}\n", + }, + }, + { + skipDoc: true, + document: `{a: cat, b: double}`, + expression: `.a style=.b`, + expected: []string{ + "D0, P[], (doc)::{a: \"cat\", b: double}\n", + }, + }, + { + description: "Read style", + document: `{a: "cat", b: 'thing'}`, + expression: `.. | style`, + expected: []string{ + "D0, P[], (!!str)::flow\n", + "D0, P[a], (!!str)::double\n", + "D0, P[b], (!!str)::single\n", + }, + }, + { + skipDoc: true, + document: `a: cat`, + expression: `.. | style`, + expected: []string{ + "D0, P[], (!!str)::\"\"\n", + "D0, P[a], (!!str)::\"\"\n", + }, + }, +} + +func TestStyleOperatorScenarios(t *testing.T) { + for _, tt := range styleOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Style Operator", styleOperatorScenarios) +} diff --git a/pkg/yqlib/operatory_style_test.go b/pkg/yqlib/operatory_style_test.go deleted file mode 100644 index 28909899..00000000 --- a/pkg/yqlib/operatory_style_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package yqlib - -import ( - "testing" -) - -var styleOperatorScenarios = []expressionScenario{ - { - document: `{a: cat}`, - expression: `.a style="single"`, - expected: []string{ - "D0, P[], (doc)::{a: 'cat'}\n", - }, - }, - { - description: "Set style using a path", - document: `{a: cat, b: double}`, - expression: `.a style=.b`, - expected: []string{ - "D0, P[], (doc)::{a: \"cat\", b: double}\n", - }, - }, - { - document: `{a: "cat", b: 'dog'}`, - expression: `.. style=""`, - expected: []string{ - "D0, P[], (!!map)::a: cat\nb: dog\n", - }, - }, - { - document: `{a: "cat", b: 'thing'}`, - expression: `.. | style`, - expected: []string{ - "D0, P[], (!!str)::flow\n", - "D0, P[a], (!!str)::double\n", - "D0, P[b], (!!str)::single\n", - }, - }, - { - document: `a: cat`, - expression: `.. | style`, - expected: []string{ - "D0, P[], (!!str)::\"\"\n", - "D0, P[a], (!!str)::\"\"\n", - }, - }, -} - -func TestStyleOperatorScenarios(t *testing.T) { - for _, tt := range styleOperatorScenarios { - testScenario(t, &tt) - } - documentScenarios(t, "Style Operator", styleOperatorScenarios) -} From 9b48cf80e035ee169d4fc6cc30885b654e3bc1d1 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 18 Nov 2020 20:43:36 +1100 Subject: [PATCH 083/129] updated todo --- pkg/yqlib/lib.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index ed1e4bf0..10e76917 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -17,6 +17,7 @@ type OperationType struct { } // operators TODO: +// -normal update operator // - get path operator (like doc index) // - get file index op (like doc index) // - get file name op (like doc index) From 36084a60a949b9d89284d0d2e8e838213dbd9ba3 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 19 Nov 2020 16:45:05 +1100 Subject: [PATCH 084/129] Added tag operator --- pkg/yqlib/doc/Collect into Object.md | 5 +- pkg/yqlib/doc/Comments Operator.md | 4 +- pkg/yqlib/doc/Delete Operator.md | 8 ++- pkg/yqlib/doc/Document Index Operator.md | 1 - pkg/yqlib/doc/Explode Operator.md | 8 ++- pkg/yqlib/doc/Recursive Descent Operator.md | 16 +----- pkg/yqlib/doc/Style Operator.md | 49 +--------------- pkg/yqlib/doc/Tag Operator.md | 45 +++++++++++++++ pkg/yqlib/doc/Union Operator.md | 18 ------ pkg/yqlib/doc/headers/Tag Operator.md | 1 + pkg/yqlib/lib.go | 5 ++ pkg/yqlib/operator_style_test.go | 30 +++++++--- pkg/yqlib/operator_tag.go | 51 +++++++++++++++++ pkg/yqlib/operator_tag_test.go | 36 ++++++++++++ pkg/yqlib/path_parse_test.go | 21 +++++++ pkg/yqlib/path_tokeniser.go | 63 ++++++++++++++------- 16 files changed, 241 insertions(+), 120 deletions(-) create mode 100644 pkg/yqlib/doc/Tag Operator.md create mode 100644 pkg/yqlib/doc/headers/Tag Operator.md create mode 100644 pkg/yqlib/operator_tag.go create mode 100644 pkg/yqlib/operator_tag_test.go diff --git a/pkg/yqlib/doc/Collect into Object.md b/pkg/yqlib/doc/Collect into Object.md index a54a262d..c46f5d79 100644 --- a/pkg/yqlib/doc/Collect into Object.md +++ b/pkg/yqlib/doc/Collect into Object.md @@ -20,7 +20,8 @@ yq eval '{"wrap": .}' sample.yml ``` will output ```yaml -wrap: {name: Mike} +wrap: + name: Mike ``` ### Using splat to create multiple objects @@ -62,9 +63,7 @@ will output ```yaml Mike: cat Mike: dog ---- Rosey: monkey ---- Rosey: sheep ``` diff --git a/pkg/yqlib/doc/Comments Operator.md b/pkg/yqlib/doc/Comments Operator.md index 78b41b44..ab8b8864 100644 --- a/pkg/yqlib/doc/Comments Operator.md +++ b/pkg/yqlib/doc/Comments Operator.md @@ -101,7 +101,7 @@ yq eval '. | headComment' sample.yml ``` will output ```yaml -welcome! + ``` ### Get foot comment @@ -115,6 +115,6 @@ yq eval '. | footComment' sample.yml ``` will output ```yaml -have a great day + ``` diff --git a/pkg/yqlib/doc/Delete Operator.md b/pkg/yqlib/doc/Delete Operator.md index 6b42520c..dff1282f 100644 --- a/pkg/yqlib/doc/Delete Operator.md +++ b/pkg/yqlib/doc/Delete Operator.md @@ -12,7 +12,7 @@ yq eval 'del(.b)' sample.yml ``` will output ```yaml -{a: cat} +a: cat ``` ### Delete entry in array @@ -28,7 +28,8 @@ yq eval 'del(.[1])' sample.yml ``` will output ```yaml -[1, 3] +- 1 +- 3 ``` ### Delete no matches @@ -43,6 +44,7 @@ yq eval 'del(.c)' sample.yml ``` will output ```yaml -{a: cat, b: dog} +a: cat +b: dog ``` diff --git a/pkg/yqlib/doc/Document Index Operator.md b/pkg/yqlib/doc/Document Index Operator.md index 33126730..5dd62822 100644 --- a/pkg/yqlib/doc/Document Index Operator.md +++ b/pkg/yqlib/doc/Document Index Operator.md @@ -49,7 +49,6 @@ will output ```yaml match: cat doc: 0 ---- match: frog doc: 1 ``` diff --git a/pkg/yqlib/doc/Explode Operator.md b/pkg/yqlib/doc/Explode Operator.md index 80e70890..3ca0e208 100644 --- a/pkg/yqlib/doc/Explode Operator.md +++ b/pkg/yqlib/doc/Explode Operator.md @@ -13,7 +13,9 @@ yq eval 'explode(.f)' sample.yml ``` will output ```yaml -{f: {a: cat, b: cat}} +f: + a: cat + b: cat ``` ### Explode with no aliases or anchors @@ -43,7 +45,9 @@ yq eval 'explode(.f)' sample.yml ``` will output ```yaml -{f: {a: cat, cat: b}} +f: + a: cat + cat: b ``` ### Explode with merge anchors diff --git a/pkg/yqlib/doc/Recursive Descent Operator.md b/pkg/yqlib/doc/Recursive Descent Operator.md index 6d0069b0..9eec8436 100644 --- a/pkg/yqlib/doc/Recursive Descent Operator.md +++ b/pkg/yqlib/doc/Recursive Descent Operator.md @@ -1,23 +1,9 @@ This operator recursively matches 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, for instance to set the `style` of all nodes in a yaml doc: ```bash -yq eval '.. style = "flow"' file.yaml +yq eval '.. style= "flow"' file.yaml ``` ## Examples -### Matches single scalar value -Given a sample.yml file of: -```yaml -cat -``` -then -```bash -yq eval '..' sample.yml -``` -will output -```yaml -cat -``` - ### Map Given a sample.yml file of: ```yaml diff --git a/pkg/yqlib/doc/Style Operator.md b/pkg/yqlib/doc/Style Operator.md index f0080286..0c78cbe5 100644 --- a/pkg/yqlib/doc/Style Operator.md +++ b/pkg/yqlib/doc/Style Operator.md @@ -146,39 +146,7 @@ c: 3.2 e: true ``` -### Set style using a path -Given a sample.yml file of: -```yaml -a: cat -b: double -``` -then -```bash -yq eval '.a style=.b' sample.yml -``` -will output -```yaml -a: "cat" -b: double -``` - -### Example 8 -Given a sample.yml file of: -```yaml -a: cat -b: dog -``` -then -```bash -yq eval '.. style=""' sample.yml -``` -will output -```yaml -a: cat -b: dog -``` - -### Example 9 +### Read style Given a sample.yml file of: ```yaml a: cat @@ -193,20 +161,5 @@ will output -``` - -### Example 10 -Given a sample.yml file of: -```yaml -a: cat -``` -then -```bash -yq eval '.. | style' sample.yml -``` -will output -```yaml - - ``` diff --git a/pkg/yqlib/doc/Tag Operator.md b/pkg/yqlib/doc/Tag Operator.md new file mode 100644 index 00000000..e9e4de21 --- /dev/null +++ b/pkg/yqlib/doc/Tag Operator.md @@ -0,0 +1,45 @@ +The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`). +## Examples +### Get tag +Given a sample.yml file of: +```yaml +a: cat +b: 5 +c: 3.2 +e: true +f: [] +``` +then +```bash +yq eval '.. | tag' sample.yml +``` +will output +```yaml +!!map +!!str +!!int +!!float +!!bool +!!seq +``` + +### Convert numbers to strings +Given a sample.yml file of: +```yaml +a: cat +b: 5 +c: 3.2 +e: true +``` +then +```bash +yq eval '(.. | select(tag == "!!int")) tag = "!!str"' sample.yml +``` +will output +```yaml +a: cat +b: "5" +c: 3.2 +e: true +``` + diff --git a/pkg/yqlib/doc/Union Operator.md b/pkg/yqlib/doc/Union Operator.md index e1ce5fb2..e8975de3 100644 --- a/pkg/yqlib/doc/Union Operator.md +++ b/pkg/yqlib/doc/Union Operator.md @@ -29,21 +29,3 @@ fieldA fieldC ``` -### Combine selected paths -Given a sample.yml file of: -```yaml -a: fieldA -b: fieldB -c: fieldC -``` -then -```bash -yq eval '(.a, .c) |= "potatoe"' sample.yml -``` -will output -```yaml -a: potatoe -b: fieldB -c: potatoe -``` - diff --git a/pkg/yqlib/doc/headers/Tag Operator.md b/pkg/yqlib/doc/headers/Tag Operator.md new file mode 100644 index 00000000..1243de10 --- /dev/null +++ b/pkg/yqlib/doc/headers/Tag Operator.md @@ -0,0 +1 @@ +The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`). \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 10e76917..d690e2ef 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -36,8 +36,12 @@ var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOp var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} + +// TODO: implement this +var PlainAssign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} +var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator} var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} @@ -49,6 +53,7 @@ var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: Pip var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator} +var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator} var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator} var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator} diff --git a/pkg/yqlib/operator_style_test.go b/pkg/yqlib/operator_style_test.go index 1bdaf983..ff84df12 100644 --- a/pkg/yqlib/operator_style_test.go +++ b/pkg/yqlib/operator_style_test.go @@ -10,7 +10,7 @@ var styleOperatorScenarios = []expressionScenario{ document: `{a: cat, b: 5, c: 3.2, e: true}`, expression: `.. style="tagged"`, expected: []string{ - "D0, P[], (doc)::{a: 'cat'}\n", + "D0, P[], (!!map)::!!map\na: !!str cat\nb: !!int 5\nc: !!float 3.2\ne: !!bool true\n", }, }, { @@ -18,7 +18,7 @@ var styleOperatorScenarios = []expressionScenario{ document: `{a: cat, b: 5, c: 3.2, e: true}`, expression: `.. style="double"`, expected: []string{ - "D0, P[], (doc)::{a: 'cat'}\n", + "D0, P[], (!!map)::a: \"cat\"\nb: \"5\"\nc: \"3.2\"\ne: \"true\"\n", }, }, { @@ -26,7 +26,7 @@ var styleOperatorScenarios = []expressionScenario{ document: `{a: cat, b: 5, c: 3.2, e: true}`, expression: `.. style="single"`, expected: []string{ - "D0, P[], (doc)::{a: 'cat'}\n", + "D0, P[], (!!map)::a: 'cat'\nb: '5'\nc: '3.2'\ne: 'true'\n", }, }, { @@ -34,7 +34,15 @@ var styleOperatorScenarios = []expressionScenario{ document: `{a: cat, b: 5, c: 3.2, e: true}`, expression: `.. style="literal"`, expected: []string{ - "D0, P[], (doc)::{a: 'cat'}\n", + `D0, P[], (!!map)::a: |- + cat +b: |- + 5 +c: |- + 3.2 +e: |- + true +`, }, }, { @@ -42,7 +50,15 @@ var styleOperatorScenarios = []expressionScenario{ document: `{a: cat, b: 5, c: 3.2, e: true}`, expression: `.. style="folded"`, expected: []string{ - "D0, P[], (doc)::{a: 'cat'}\n", + `D0, P[], (!!map)::a: >- + cat +b: >- + 5 +c: >- + 3.2 +e: >- + true +`, }, }, { @@ -50,7 +66,7 @@ var styleOperatorScenarios = []expressionScenario{ document: `{a: cat, b: 5, c: 3.2, e: true}`, expression: `.. style="flow"`, expected: []string{ - "D0, P[], (doc)::{a: 'cat'}\n", + "D0, P[], (!!map)::{a: cat, b: 5, c: 3.2, e: true}\n", }, }, { @@ -58,7 +74,7 @@ var styleOperatorScenarios = []expressionScenario{ document: `{a: cat, b: 5, c: 3.2, e: true}`, expression: `.. style=""`, expected: []string{ - "D0, P[], (doc)::{a: 'cat'}\n", + "D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n", }, }, { diff --git a/pkg/yqlib/operator_tag.go b/pkg/yqlib/operator_tag.go new file mode 100644 index 00000000..8b9c515b --- /dev/null +++ b/pkg/yqlib/operator_tag.go @@ -0,0 +1,51 @@ +package yqlib + +import ( + "container/list" + + "gopkg.in/yaml.v3" +) + +func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + + log.Debugf("AssignTagOperator: %v") + + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + tag := "" + + if rhs.Front() != nil { + tag = rhs.Front().Value.(*CandidateNode).Node.Value + } + + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + + if err != nil { + return nil, err + } + + for el := lhs.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debugf("Setting tag of : %v", candidate.GetKey()) + candidate.Node.Tag = tag + } + + return matchingNodes, nil +} + +func GetTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("GetTagOperator") + + var results = list.New() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Tag, Tag: "!!str"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(lengthCand) + } + + return results, nil +} diff --git a/pkg/yqlib/operator_tag_test.go b/pkg/yqlib/operator_tag_test.go new file mode 100644 index 00000000..9d4878dc --- /dev/null +++ b/pkg/yqlib/operator_tag_test.go @@ -0,0 +1,36 @@ +package yqlib + +import ( + "testing" +) + +var tagOperatorScenarios = []expressionScenario{ + { + description: "Get tag", + document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`, + expression: `.. | tag`, + expected: []string{ + "D0, P[], (!!str)::'!!map'\n", + "D0, P[a], (!!str)::'!!str'\n", + "D0, P[b], (!!str)::'!!int'\n", + "D0, P[c], (!!str)::'!!float'\n", + "D0, P[e], (!!str)::'!!bool'\n", + "D0, P[f], (!!str)::'!!seq'\n", + }, + }, + { + description: "Convert numbers to strings", + document: `{a: cat, b: 5, c: 3.2, e: true}`, + expression: `(.. | select(tag == "!!int")) tag= "!!str"`, + expected: []string{ + "D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n", + }, + }, +} + +func TestTagOperatorScenarios(t *testing.T) { + for _, tt := range tagOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Tag Operator", tagOperatorScenarios) +} diff --git a/pkg/yqlib/path_parse_test.go b/pkg/yqlib/path_parse_test.go index 45037f35..b78a4bd7 100644 --- a/pkg/yqlib/path_parse_test.go +++ b/pkg/yqlib/path_parse_test.go @@ -99,6 +99,27 @@ var pathTests = []struct { append(make([]interface{}, 0), "a", "PIPE", "b", "ASSIGN_STYLE", "folded (string)"), append(make([]interface{}, 0), "a", "b", "PIPE", "folded (string)", "ASSIGN_STYLE"), }, + { + `tag == "str"`, + append(make([]interface{}, 0), "GET_TAG", "EQUALS", "str (string)"), + append(make([]interface{}, 0), "GET_TAG", "str (string)", "EQUALS"), + }, + { + `. tag= "str"`, + append(make([]interface{}, 0), "SELF", "ASSIGN_TAG", "str (string)"), + append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_TAG"), + }, + { + `lineComment == "str"`, + append(make([]interface{}, 0), "GET_COMMENT", "EQUALS", "str (string)"), + append(make([]interface{}, 0), "GET_COMMENT", "str (string)", "EQUALS"), + }, + { + `. lineComment= "str"`, + append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"), + append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"), + }, + // { // `.a.b tag="!!str"`, // append(make([]interface{}, 0), "EXPLODE", "(", "a", "PIPE", "b", ")"), diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index aba11192..71614c0e 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -24,10 +24,11 @@ const ( ) type Token struct { - TokenType TokenType - Operation *Operation + TokenType TokenType + Operation *Operation + AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it + CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat - CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat } func (t *Token) toString() string { @@ -83,14 +84,22 @@ func documentToken() lex.Action { } func opToken(op *OperationType) lex.Action { - return opTokenWithPrefs(op, nil) + return opTokenWithPrefs(op, nil, nil) } -func opTokenWithPrefs(op *OperationType, preferences interface{}) lex.Action { +func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.Action { + return opTokenWithPrefs(opType, assignOpType, nil) +} + +func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences} - return &Token{TokenType: OperationToken, Operation: op}, nil + var assign *Operation + if assignOpType != nil { + assign = &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, Preferences: preferences} + } + return &Token{TokenType: OperationToken, Operation: op, AssignOperation: assign}, nil } } @@ -191,23 +200,22 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex)) - lexer.Add([]byte(`style\s*=`), opToken(AssignStyle)) - lexer.Add([]byte(`style`), opToken(GetStyle)) + lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle)) - lexer.Add([]byte(`lineComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{LineComment: true})) - lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{LineComment: true})) + lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag)) - lexer.Add([]byte(`headComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{HeadComment: true})) - lexer.Add([]byte(`headComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{HeadComment: true})) + lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true})) - lexer.Add([]byte(`footComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{FootComment: true})) - lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{FootComment: true})) + lexer.Add([]byte(`headComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{HeadComment: true})) - lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true})) + lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true})) + + lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, nil, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true})) lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) + lexer.Add([]byte(`\s*=\s*`), opToken(PlainAssign)) lexer.Add([]byte(`del`), opToken(DeleteChild)) @@ -286,15 +294,28 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { } var postProcessedTokens = make([]*Token, 0) + skipNextToken := false + for index, token := range tokens { + if skipNextToken { + skipNextToken = false + } else { - postProcessedTokens = append(postProcessedTokens, token) + if index != len(tokens)-1 && token.AssignOperation != nil && + tokens[index+1].TokenType == OperationToken && + tokens[index+1].Operation.OperationType == PlainAssign { + token.Operation = token.AssignOperation + skipNextToken = true + } - if index != len(tokens)-1 && token.CheckForPostTraverse && - tokens[index+1].TokenType == OperationToken && - tokens[index+1].Operation.OperationType == TraversePath { - op := &Operation{OperationType: Pipe, Value: "PIPE"} - postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op}) + postProcessedTokens = append(postProcessedTokens, token) + + if index != len(tokens)-1 && token.CheckForPostTraverse && + tokens[index+1].TokenType == OperationToken && + tokens[index+1].Operation.OperationType == TraversePath { + op := &Operation{OperationType: Pipe, Value: "PIPE"} + postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op}) + } } } From 75044e480c7b1921d0661427b0f6b1daa5c3d51b Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 19 Nov 2020 17:08:13 +1100 Subject: [PATCH 085/129] Added plain assignment --- ... Assign Operator.md => Assign Operator.md} | 43 ++++++++++++++++++- pkg/yqlib/doc/Tag Operator.md | 2 +- pkg/yqlib/doc/headers/Assign Operator.md | 7 +++ .../doc/headers/Update Assign Operator.md | 1 - pkg/yqlib/lib.go | 2 - ...or_assign_update.go => operator_assign.go} | 15 ++++++- ...update_test.go => operator_assign_test.go} | 21 ++++++++- pkg/yqlib/operator_multilpy.go | 1 + pkg/yqlib/path_tokeniser.go | 6 +-- 9 files changed, 87 insertions(+), 11 deletions(-) rename pkg/yqlib/doc/{Update Assign Operator.md => Assign Operator.md} (61%) create mode 100644 pkg/yqlib/doc/headers/Assign Operator.md delete mode 100644 pkg/yqlib/doc/headers/Update Assign Operator.md rename pkg/yqlib/{operator_assign_update.go => operator_assign.go} (77%) rename pkg/yqlib/{operator_assign_update_test.go => operator_assign_test.go} (78%) diff --git a/pkg/yqlib/doc/Update Assign Operator.md b/pkg/yqlib/doc/Assign Operator.md similarity index 61% rename from pkg/yqlib/doc/Update Assign Operator.md rename to pkg/yqlib/doc/Assign Operator.md index 866d2db2..0dadc5bf 100644 --- a/pkg/yqlib/doc/Update Assign Operator.md +++ b/pkg/yqlib/doc/Assign Operator.md @@ -1,4 +1,10 @@ -Updates the LHS using the expression on the RHS. Note that the RHS runs against the _original_ LHS value, so that you can evaluate a new value based on the old (e.g. increment). +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. ## Examples ### Update parent to be the child value Given a sample.yml file of: @@ -17,6 +23,23 @@ a: g: foof ``` +### Update to be the sibling value +Given a sample.yml file of: +```yaml +a: + b: child +b: sibling +``` +then +```bash +yq eval '.a = .b' sample.yml +``` +will output +```yaml +a: sibling +b: sibling +``` + ### Updated multiple paths Given a sample.yml file of: ```yaml @@ -36,6 +59,24 @@ c: potatoe ``` ### Update string value +Given a sample.yml file of: +```yaml +a: + b: apple +``` +then +```bash +yq eval '.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: diff --git a/pkg/yqlib/doc/Tag Operator.md b/pkg/yqlib/doc/Tag Operator.md index e9e4de21..863a2368 100644 --- a/pkg/yqlib/doc/Tag Operator.md +++ b/pkg/yqlib/doc/Tag Operator.md @@ -33,7 +33,7 @@ e: true ``` then ```bash -yq eval '(.. | select(tag == "!!int")) tag = "!!str"' sample.yml +yq eval '(.. | select(tag == "!!int")) tag= "!!str"' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/doc/headers/Assign Operator.md b/pkg/yqlib/doc/headers/Assign Operator.md new file mode 100644 index 00000000..5cd669cf --- /dev/null +++ b/pkg/yqlib/doc/headers/Assign Operator.md @@ -0,0 +1,7 @@ +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. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Update Assign Operator.md b/pkg/yqlib/doc/headers/Update Assign Operator.md deleted file mode 100644 index c555f5b5..00000000 --- a/pkg/yqlib/doc/headers/Update Assign Operator.md +++ /dev/null @@ -1 +0,0 @@ -Updates the LHS using the expression on the RHS. Note that the RHS runs against the _original_ LHS value, so that you can evaluate a new value based on the old (e.g. increment). \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index d690e2ef..d90433a1 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -37,8 +37,6 @@ var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: U var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} -// TODO: implement this -var PlainAssign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator} diff --git a/pkg/yqlib/operator_assign_update.go b/pkg/yqlib/operator_assign.go similarity index 77% rename from pkg/yqlib/operator_assign_update.go rename to pkg/yqlib/operator_assign.go index 8381b479..5a6f3b58 100644 --- a/pkg/yqlib/operator_assign_update.go +++ b/pkg/yqlib/operator_assign.go @@ -2,15 +2,28 @@ package yqlib import "container/list" +type AssignOpPreferences struct { + UpdateAssign bool +} + func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } + preferences := pathNode.Operation.Preferences.(*AssignOpPreferences) + + var rhs *list.List + if !preferences.UpdateAssign { + rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + } + for el := lhs.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + if preferences.UpdateAssign { + rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + } if err != nil { return nil, err diff --git a/pkg/yqlib/operator_assign_update_test.go b/pkg/yqlib/operator_assign_test.go similarity index 78% rename from pkg/yqlib/operator_assign_update_test.go rename to pkg/yqlib/operator_assign_test.go index 1fbc5e8c..8c5fb38e 100644 --- a/pkg/yqlib/operator_assign_update_test.go +++ b/pkg/yqlib/operator_assign_test.go @@ -13,6 +13,14 @@ var assignOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::{a: {g: foof}}\n", }, }, + { + description: "Update to be the sibling value", + document: `{a: {b: child}, b: sibling}`, + expression: `.a = .b`, + expected: []string{ + "D0, P[], (doc)::{a: sibling, b: sibling}\n", + }, + }, { description: "Updated multiple paths", document: `{a: fieldA, b: fieldB, c: fieldC}`, @@ -24,7 +32,16 @@ var assignOperatorScenarios = []expressionScenario{ { description: "Update string value", document: `{a: {b: apple}}`, - expression: `.a.b |= "frog"`, + expression: `.a.b = "frog"`, + expected: []string{ + "D0, P[], (doc)::{a: {b: frog}}\n", + }, + }, + { + description: "Update string value via |=", + subdescription: "Note there is no difference between `=` and `|=` when the RHS is a scalar", + document: `{a: {b: apple}}`, + expression: `.a.b |= "frog"`, expected: []string{ "D0, P[], (doc)::{a: {b: frog}}\n", }, @@ -99,5 +116,5 @@ func TestAssignOperatorScenarios(t *testing.T) { for _, tt := range assignOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Update Assign Operator", assignOperatorScenarios) + documentScenarios(t, "Assign Operator", assignOperatorScenarios) } diff --git a/pkg/yqlib/operator_multilpy.go b/pkg/yqlib/operator_multilpy.go index f18ba447..9f25962e 100644 --- a/pkg/yqlib/operator_multilpy.go +++ b/pkg/yqlib/operator_multilpy.go @@ -112,6 +112,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid assignmentOp := &Operation{OperationType: AssignAttributes} if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { assignmentOp.OperationType = Assign + assignmentOp.Preferences = &AssignOpPreferences{false} } rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 71614c0e..d9c78ef5 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -215,11 +215,11 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) - lexer.Add([]byte(`\s*=\s*`), opToken(PlainAssign)) + lexer.Add([]byte(`\s*=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{false})) lexer.Add([]byte(`del`), opToken(DeleteChild)) - lexer.Add([]byte(`\s*\|=\s*`), opToken(Assign)) + lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true})) lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(false)) lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true)) @@ -303,7 +303,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { if index != len(tokens)-1 && token.AssignOperation != nil && tokens[index+1].TokenType == OperationToken && - tokens[index+1].Operation.OperationType == PlainAssign { + tokens[index+1].Operation.OperationType == Assign { token.Operation = token.AssignOperation skipNextToken = true } From 9bd9468526dfc0a5f7fe54096a62c26cf80f7b39 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 19 Nov 2020 22:11:26 +1100 Subject: [PATCH 086/129] Minor fixes --- pkg/yqlib/doc/Delete Operator.md | 16 ++++++++++++++++ pkg/yqlib/encoder_test.go | 5 ++++- pkg/yqlib/lib.go | 3 --- pkg/yqlib/operator_delete_test.go | 8 ++++++++ pkg/yqlib/printer_test.go | 27 ++++++++++++++++++++++----- 5 files changed, 50 insertions(+), 9 deletions(-) diff --git a/pkg/yqlib/doc/Delete Operator.md b/pkg/yqlib/doc/Delete Operator.md index dff1282f..8bcf3533 100644 --- a/pkg/yqlib/doc/Delete Operator.md +++ b/pkg/yqlib/doc/Delete Operator.md @@ -48,3 +48,19 @@ a: cat b: dog ``` +### Delete matching entries +Given a sample.yml file of: +```yaml +a: cat +b: dog +c: bat +``` +then +```bash +yq eval 'del( .[] | select(. == "*at") )' sample.yml +``` +will output +```yaml +b: dog +``` + diff --git a/pkg/yqlib/encoder_test.go b/pkg/yqlib/encoder_test.go index f6a436a7..867ad40f 100644 --- a/pkg/yqlib/encoder_test.go +++ b/pkg/yqlib/encoder_test.go @@ -37,7 +37,10 @@ func TestJsonEncoderPreservesObjectOrder(t *testing.T) { panic(err) } node := inputs.Front().Value.(*CandidateNode).Node - jsonEncoder.Encode(node) + err = jsonEncoder.Encode(node) + if err != nil { + panic(err) + } writer.Flush() test.AssertResult(t, expectedJson, output.String()) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index d90433a1..dee8f335 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -68,10 +68,7 @@ var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator} var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator} -// not sure yet - var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator} - var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator} // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} diff --git a/pkg/yqlib/operator_delete_test.go b/pkg/yqlib/operator_delete_test.go index 68476e5f..77e00184 100644 --- a/pkg/yqlib/operator_delete_test.go +++ b/pkg/yqlib/operator_delete_test.go @@ -29,6 +29,14 @@ var deleteOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::{a: cat, b: dog}\n", }, }, + { + description: "Delete matching entries", + document: `{a: cat, b: dog, c: bat}`, + expression: `del( .[] | select(. == "*at") )`, + expected: []string{ + "D0, P[], (doc)::{b: dog}\n", + }, + }, } func TestDeleteOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/printer_test.go b/pkg/yqlib/printer_test.go index e60973d8..699a8d05 100644 --- a/pkg/yqlib/printer_test.go +++ b/pkg/yqlib/printer_test.go @@ -35,9 +35,20 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) { el = el.Next() sample3 := nodeToMap(el.Value.(*CandidateNode)) - printer.PrintResults(sample1) - printer.PrintResults(sample2) - printer.PrintResults(sample3) + err = printer.PrintResults(sample1) + if err != nil { + panic(err) + } + + err = printer.PrintResults(sample2) + if err != nil { + panic(err) + } + + err = printer.PrintResults(sample3) + if err != nil { + panic(err) + } writer.Flush() test.AssertResult(t, multiDocSample, output.String()) @@ -54,7 +65,10 @@ func TestPrinterMultipleDocsInSinglePrint(t *testing.T) { panic(err) } - printer.PrintResults(inputs) + err = printer.PrintResults(inputs) + if err != nil { + panic(err) + } writer.Flush() test.AssertResult(t, multiDocSample, output.String()) @@ -70,7 +84,10 @@ func TestPrinterMultipleDocsJson(t *testing.T) { panic(err) } - printer.PrintResults(inputs) + err = printer.PrintResults(inputs) + if err != nil { + panic(err) + } expected := `{"a":"banana"} {"a":"apple"} From 8e1ce4ca7099c9cbfa57b4891183a09b53e45928 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 19 Nov 2020 22:12:34 +1100 Subject: [PATCH 087/129] Updated todo --- pkg/yqlib/lib.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index dee8f335..463948fb 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -17,15 +17,12 @@ type OperationType struct { } // operators TODO: -// -normal update operator // - get path operator (like doc index) // - get file index op (like doc index) // - get file name op (like doc index) // - write in place // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) -// - updateTag - not recursive -// - get tag (tag) // - compare ?? // - validate ?? // - exists From 9674acf6848a9b8a50e33e981ba3acf3e9477887 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 19 Nov 2020 22:53:05 +1100 Subject: [PATCH 088/129] Fixed docker file, fixed doco --- Dockerfile | 2 +- pkg/yqlib/operator_assign_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 43861724..6dafe03e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,4 +22,4 @@ LABEL version=${VERSION} WORKDIR /workdir -ENTRYPOINT [/usr/bin/yq] \ No newline at end of file +ENTRYPOINT /usr/bin/yq diff --git a/pkg/yqlib/operator_assign_test.go b/pkg/yqlib/operator_assign_test.go index 8c5fb38e..c3d50bb4 100644 --- a/pkg/yqlib/operator_assign_test.go +++ b/pkg/yqlib/operator_assign_test.go @@ -6,7 +6,7 @@ import ( var assignOperatorScenarios = []expressionScenario{ { - description: "Update parent to be the child value", + description: "Update node to be the child value", document: `{a: {b: {g: foof}}}`, expression: `.a |= .b`, expected: []string{ @@ -14,7 +14,7 @@ var assignOperatorScenarios = []expressionScenario{ }, }, { - description: "Update to be the sibling value", + description: "Update node to be the sibling value", document: `{a: {b: child}, b: sibling}`, expression: `.a = .b`, expected: []string{ From c08980e70f47432d6c250ab32db14924d4591abd Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 20 Nov 2020 13:53:44 +1100 Subject: [PATCH 089/129] Set entrypoint to yq --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6dafe03e..bbd6b914 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,4 +22,4 @@ LABEL version=${VERSION} WORKDIR /workdir -ENTRYPOINT /usr/bin/yq +ENTRYPOINT ["/usr/bin/yq"] From bc87aca8d7aa0afe83b7efce4b8dd9ec3965d763 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 20 Nov 2020 14:35:34 +1100 Subject: [PATCH 090/129] wip --- pkg/yqlib/doc/Boolean Operators.md | 157 +++++++++++++++++++++ pkg/yqlib/doc/headers/Boolean Operators.md | 1 + pkg/yqlib/operator_booleans_test.go | 30 +++- pkg/yqlib/path_tokeniser.go | 1 + 4 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 pkg/yqlib/doc/Boolean Operators.md create mode 100644 pkg/yqlib/doc/headers/Boolean Operators.md diff --git a/pkg/yqlib/doc/Boolean Operators.md b/pkg/yqlib/doc/Boolean Operators.md new file mode 100644 index 00000000..198ab8da --- /dev/null +++ b/pkg/yqlib/doc/Boolean Operators.md @@ -0,0 +1,157 @@ +The `or` and `and` operators take two parameters and return a boolean result. These are most commonly used with the `select` operator to filter particular nodes. +## Examples +### Update node to be the child value +Given a sample.yml file of: +```yaml +a: + b: + g: foof +``` +then +```bash +yq eval '.a |= .b' sample.yml +``` +will output +```yaml +a: + g: foof +``` + +### Update node to be the sibling value +Given a sample.yml file of: +```yaml +a: + b: child +b: sibling +``` +then +```bash +yq eval '.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 eval '(.a, .c) |= "potatoe"' sample.yml +``` +will output +```yaml +a: potatoe +b: fieldB +c: potatoe +``` + +### Update string value +Given a sample.yml file of: +```yaml +a: + b: apple +``` +then +```bash +yq eval '.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 eval '.a.b |= "frog"' sample.yml +``` +will output +```yaml +a: + b: frog +``` + +### Update selected results +Given a sample.yml file of: +```yaml +a: + b: apple + c: cactus +``` +then +```bash +yq eval '.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 eval '.[] | select(. == "*andy") |= "bogs"' sample.yml +``` +will output +```yaml +- bogs +- apple +- bogs +``` + +### Update empty object +Given a sample.yml file of: +```yaml +'': null +``` +then +```bash +yq eval '.a.b |= "bogs"' sample.yml +``` +will output +```yaml +'': null +a: + b: bogs +``` + +### Update empty object and array +Given a sample.yml file of: +```yaml +'': null +``` +then +```bash +yq eval '.a.b[0] |= "bogs"' sample.yml +``` +will output +```yaml +'': null +a: + b: + - bogs +``` + diff --git a/pkg/yqlib/doc/headers/Boolean Operators.md b/pkg/yqlib/doc/headers/Boolean Operators.md new file mode 100644 index 00000000..15be7b7f --- /dev/null +++ b/pkg/yqlib/doc/headers/Boolean Operators.md @@ -0,0 +1 @@ +The `or` and `and` operators take two parameters and return a boolean result. These are most commonly used with the `select` operator to filter particular nodes. \ No newline at end of file diff --git a/pkg/yqlib/operator_booleans_test.go b/pkg/yqlib/operator_booleans_test.go index 61437926..6c9b1dea 100644 --- a/pkg/yqlib/operator_booleans_test.go +++ b/pkg/yqlib/operator_booleans_test.go @@ -6,18 +6,37 @@ import ( var booleanOperatorScenarios = []expressionScenario{ { - document: `{}`, - expression: `true or false`, + description: "OR example", + expression: `true or false`, expected: []string{ "D0, P[], (!!bool)::true\n", }, - }, { - document: `{}`, + }, + { + description: "AND example", + expression: `true and false`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + document: "[{a: bird, b: dog}, {a: frog, b: bird}, {a: cat, b: fly}]", + description: "Matching nodes with select, equals and or", + expression: `.[] | select(.a == "cat" or .b == "dog")`, + expected: []string{ + "D0, P[], (!!map)::{a: bird, b: dog}\n", + "D0, P[], (!!map)::{a: cat, b: fly}\n", + }, + }, + { + skipDoc: true, expression: `false or false`, expected: []string{ "D0, P[], (!!bool)::false\n", }, - }, { + }, + { + skipDoc: true, document: `{a: true, b: false}`, expression: `.[] or (false, true)`, expected: []string{ @@ -33,4 +52,5 @@ func TestBooleanOperatorScenarios(t *testing.T) { for _, tt := range booleanOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Boolean Operators", assignOperatorScenarios) } diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index d9c78ef5..115b6d5b 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -196,6 +196,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`select`), opToken(Select)) lexer.Add([]byte(`explode`), opToken(Explode)) lexer.Add([]byte(`or`), opToken(Or)) + lexer.Add([]byte(`and`), opToken(And)) lexer.Add([]byte(`not`), opToken(Not)) lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex)) From f03005f86d27e25da8745a3d6526ddaef4b2514b Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 20 Nov 2020 15:29:53 +1100 Subject: [PATCH 091/129] Fixed boolean ops --- pkg/yqlib/doc/Assign Operator.md | 4 +-- pkg/yqlib/operator_booleans.go | 55 +++++++++++++++++++---------- pkg/yqlib/operator_booleans_test.go | 4 +-- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/pkg/yqlib/doc/Assign Operator.md b/pkg/yqlib/doc/Assign Operator.md index 0dadc5bf..f5b893ba 100644 --- a/pkg/yqlib/doc/Assign Operator.md +++ b/pkg/yqlib/doc/Assign Operator.md @@ -6,7 +6,7 @@ Which will assign the LHS node values to the RHS node values. The RHS expression ### 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. ## Examples -### Update parent to be the child value +### Update node to be the child value Given a sample.yml file of: ```yaml a: @@ -23,7 +23,7 @@ a: g: foof ``` -### Update to be the sibling value +### Update node to be the sibling value Given a sample.yml file of: ```yaml a: diff --git a/pkg/yqlib/operator_booleans.go b/pkg/yqlib/operator_booleans.go index 28e2fbac..ee7f6931 100644 --- a/pkg/yqlib/operator_booleans.go +++ b/pkg/yqlib/operator_booleans.go @@ -3,7 +3,7 @@ package yqlib import ( "container/list" - "gopkg.in/yaml.v3" + yaml "gopkg.in/yaml.v3" ) func isTruthy(c *CandidateNode) (bool, error) { @@ -25,9 +25,42 @@ func isTruthy(c *CandidateNode) (bool, error) { type boolOp func(bool, bool) bool +func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error { + for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() { + lhsCandidate := lhsChild.Value.(*CandidateNode) + lhsTrue, errDecoding := isTruthy(lhsCandidate) + if errDecoding != nil { + return errDecoding + } + + for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() { + rhsCandidate := rhsChild.Value.(*CandidateNode) + rhsTrue, errDecoding := isTruthy(rhsCandidate) + if errDecoding != nil { + return errDecoding + } + boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue)) + results.PushBack(boolResult) + } + } + return nil +} + func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) { var results = list.New() + if matchingNodes.Len() == 0 { + lhs, err := d.GetMatchingNodes(list.New(), pathNode.Lhs) + if err != nil { + return nil, err + } + rhs, err := d.GetMatchingNodes(list.New(), pathNode.Rhs) + if err != nil { + return nil, err + } + return results, performBoolOp(results, lhs, rhs, op) + } + for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs) @@ -39,23 +72,9 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTre return nil, err } - for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() { - lhsCandidate := lhsChild.Value.(*CandidateNode) - lhsTrue, errDecoding := isTruthy(lhsCandidate) - if errDecoding != nil { - return nil, errDecoding - } - - for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() { - rhsCandidate := rhsChild.Value.(*CandidateNode) - rhsTrue, errDecoding := isTruthy(rhsCandidate) - if errDecoding != nil { - return nil, errDecoding - } - boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue)) - - results.PushBack(boolResult) - } + err = performBoolOp(results, lhs, rhs, op) + if err != nil { + return nil, err } } diff --git a/pkg/yqlib/operator_booleans_test.go b/pkg/yqlib/operator_booleans_test.go index 6c9b1dea..12e65c51 100644 --- a/pkg/yqlib/operator_booleans_test.go +++ b/pkg/yqlib/operator_booleans_test.go @@ -24,8 +24,8 @@ var booleanOperatorScenarios = []expressionScenario{ description: "Matching nodes with select, equals and or", expression: `.[] | select(.a == "cat" or .b == "dog")`, expected: []string{ - "D0, P[], (!!map)::{a: bird, b: dog}\n", - "D0, P[], (!!map)::{a: cat, b: fly}\n", + "D0, P[0], (!!map)::{a: bird, b: dog}\n", + "D0, P[2], (!!map)::{a: cat, b: fly}\n", }, }, { From 663413cd7a82d509141f88ccad3ec0507992b814 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 20 Nov 2020 15:31:49 +1100 Subject: [PATCH 092/129] Fixed typo --- pkg/yqlib/doc/Boolean Operators.md | 153 ++++------------------------ pkg/yqlib/operator_booleans_test.go | 2 +- 2 files changed, 21 insertions(+), 134 deletions(-) diff --git a/pkg/yqlib/doc/Boolean Operators.md b/pkg/yqlib/doc/Boolean Operators.md index 198ab8da..daebec08 100644 --- a/pkg/yqlib/doc/Boolean Operators.md +++ b/pkg/yqlib/doc/Boolean Operators.md @@ -1,157 +1,44 @@ The `or` and `and` operators take two parameters and return a boolean result. These are most commonly used with the `select` operator to filter particular nodes. ## Examples -### Update node to be the child value -Given a sample.yml file of: -```yaml -a: - b: - g: foof -``` -then +### OR example +Running ```bash -yq eval '.a |= .b' sample.yml +yq eval --null-input 'true or false' ``` will output ```yaml -a: - g: foof +true ``` -### Update node to be the sibling value -Given a sample.yml file of: -```yaml -a: - b: child -b: sibling -``` -then +### AND example +Running ```bash -yq eval '.a = .b' sample.yml +yq eval --null-input 'true and false' ``` will output ```yaml -a: sibling -b: sibling +false ``` -### Updated multiple paths +### Matching nodes with select, equals and or Given a sample.yml file of: ```yaml -a: fieldA -b: fieldB -c: fieldC +- a: bird + b: dog +- a: frog + b: bird +- a: cat + b: fly ``` then ```bash -yq eval '(.a, .c) |= "potatoe"' sample.yml +yq eval '.[] | select(.a == "cat" or .b == "dog")' sample.yml ``` will output ```yaml -a: potatoe -b: fieldB -c: potatoe -``` - -### Update string value -Given a sample.yml file of: -```yaml -a: - b: apple -``` -then -```bash -yq eval '.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 eval '.a.b |= "frog"' sample.yml -``` -will output -```yaml -a: - b: frog -``` - -### Update selected results -Given a sample.yml file of: -```yaml -a: - b: apple - c: cactus -``` -then -```bash -yq eval '.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 eval '.[] | select(. == "*andy") |= "bogs"' sample.yml -``` -will output -```yaml -- bogs -- apple -- bogs -``` - -### Update empty object -Given a sample.yml file of: -```yaml -'': null -``` -then -```bash -yq eval '.a.b |= "bogs"' sample.yml -``` -will output -```yaml -'': null -a: - b: bogs -``` - -### Update empty object and array -Given a sample.yml file of: -```yaml -'': null -``` -then -```bash -yq eval '.a.b[0] |= "bogs"' sample.yml -``` -will output -```yaml -'': null -a: - b: - - bogs +a: bird +b: dog +a: cat +b: fly ``` diff --git a/pkg/yqlib/operator_booleans_test.go b/pkg/yqlib/operator_booleans_test.go index 12e65c51..29275216 100644 --- a/pkg/yqlib/operator_booleans_test.go +++ b/pkg/yqlib/operator_booleans_test.go @@ -52,5 +52,5 @@ func TestBooleanOperatorScenarios(t *testing.T) { for _, tt := range booleanOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Boolean Operators", assignOperatorScenarios) + documentScenarios(t, "Boolean Operators", booleanOperatorScenarios) } From 356aac5a1febf60c240f41356043837a295969b9 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 20 Nov 2020 15:33:21 +1100 Subject: [PATCH 093/129] fixed boolean example --- pkg/yqlib/doc/Boolean Operators.md | 10 +++++----- pkg/yqlib/operator_booleans_test.go | 5 ++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pkg/yqlib/doc/Boolean Operators.md b/pkg/yqlib/doc/Boolean Operators.md index daebec08..0b8a5e5d 100644 --- a/pkg/yqlib/doc/Boolean Operators.md +++ b/pkg/yqlib/doc/Boolean Operators.md @@ -32,13 +32,13 @@ Given a sample.yml file of: ``` then ```bash -yq eval '.[] | select(.a == "cat" or .b == "dog")' sample.yml +yq eval '[.[] | select(.a == "cat" or .b == "dog")]' sample.yml ``` will output ```yaml -a: bird -b: dog -a: cat -b: fly +- a: bird + b: dog +- a: cat + b: fly ``` diff --git a/pkg/yqlib/operator_booleans_test.go b/pkg/yqlib/operator_booleans_test.go index 29275216..b21c4050 100644 --- a/pkg/yqlib/operator_booleans_test.go +++ b/pkg/yqlib/operator_booleans_test.go @@ -22,10 +22,9 @@ var booleanOperatorScenarios = []expressionScenario{ { document: "[{a: bird, b: dog}, {a: frog, b: bird}, {a: cat, b: fly}]", description: "Matching nodes with select, equals and or", - expression: `.[] | select(.a == "cat" or .b == "dog")`, + expression: `[.[] | select(.a == "cat" or .b == "dog")]`, expected: []string{ - "D0, P[0], (!!map)::{a: bird, b: dog}\n", - "D0, P[2], (!!map)::{a: cat, b: fly}\n", + "D0, P[], (!!seq)::- {a: bird, b: dog}\n- {a: cat, b: fly}\n", }, }, { From 4e385a1b934a0a4a28cf52dfb79d21ff7528e71e Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 20 Nov 2020 15:50:15 +1100 Subject: [PATCH 094/129] get file wip --- pkg/yqlib/candidate_node.go | 11 ++++++----- pkg/yqlib/lib.go | 2 ++ pkg/yqlib/operator_file.go | 38 +++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 pkg/yqlib/operator_file.go diff --git a/pkg/yqlib/candidate_node.go b/pkg/yqlib/candidate_node.go index 49a3df94..603bd9b8 100644 --- a/pkg/yqlib/candidate_node.go +++ b/pkg/yqlib/candidate_node.go @@ -6,14 +6,15 @@ import ( "strings" "github.com/jinzhu/copier" - "gopkg.in/yaml.v3" + yaml "gopkg.in/yaml.v3" ) type CandidateNode struct { - Node *yaml.Node // the actual node - Path []interface{} /// the path we took to get to this node - Document uint // the document index of this node - Filename string + Node *yaml.Node // the actual node + Path []interface{} /// the path we took to get to this node + Document uint // the document index of this node + Filename string + FileIndex int } func (n *CandidateNode) GetKey() string { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 463948fb..0c93f086 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -51,6 +51,8 @@ var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Han var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator} var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator} var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator} +var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilename} +var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndex} var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator} diff --git a/pkg/yqlib/operator_file.go b/pkg/yqlib/operator_file.go new file mode 100644 index 00000000..ad0653b4 --- /dev/null +++ b/pkg/yqlib/operator_file.go @@ -0,0 +1,38 @@ +package yqlib + +import ( + "container/list" + "fmt" + + yaml "gopkg.in/yaml.v3" +) + +func GetFilename(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("GetFilename") + + var results = list.New() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(lengthCand) + } + + return results, nil +} + +func GetFileIndex(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("GetFileIndex") + + var results = list.New() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(lengthCand) + } + + return results, nil +} From d38caf6bc2c4fdf21318b2f920c8e9ba2b691559 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 20 Nov 2020 22:57:32 +1100 Subject: [PATCH 095/129] Added File operators --- pkg/yqlib/doc/File Operators.md | 34 +++++++++++++++++++++++++ pkg/yqlib/doc/headers/File Operators.md | 5 ++++ pkg/yqlib/encoder_test.go | 2 +- pkg/yqlib/lib.go | 4 +-- pkg/yqlib/operator_file.go | 4 +-- pkg/yqlib/operator_file_test.go | 31 ++++++++++++++++++++++ pkg/yqlib/operator_multilpy.go | 11 +++++--- pkg/yqlib/operators_test.go | 2 +- pkg/yqlib/path_tokeniser.go | 2 ++ pkg/yqlib/printer_test.go | 6 ++--- pkg/yqlib/utils.go | 25 ++++++++++++------ 11 files changed, 105 insertions(+), 21 deletions(-) create mode 100644 pkg/yqlib/doc/File Operators.md create mode 100644 pkg/yqlib/doc/headers/File Operators.md create mode 100644 pkg/yqlib/operator_file_test.go diff --git a/pkg/yqlib/doc/File Operators.md b/pkg/yqlib/doc/File Operators.md new file mode 100644 index 00000000..9ecc52fd --- /dev/null +++ b/pkg/yqlib/doc/File Operators.md @@ -0,0 +1,34 @@ +The file operator is used to filter based on filename. This is most often used with merge when needing to merge specific files together. + +```bash +yq eval 'filename == "file1.yaml" * fileIndex == 0' file1.yaml file2.yaml +``` +## Examples +### Get filename +Given a sample.yml file of: +```yaml +'': null +``` +then +```bash +yq eval 'filename' sample.yml +``` +will output +```yaml +sample.yaml +``` + +### Get file index +Given a sample.yml file of: +```yaml +'': null +``` +then +```bash +yq eval 'fileIndex' sample.yml +``` +will output +```yaml +73 +``` + diff --git a/pkg/yqlib/doc/headers/File Operators.md b/pkg/yqlib/doc/headers/File Operators.md new file mode 100644 index 00000000..6f836113 --- /dev/null +++ b/pkg/yqlib/doc/headers/File Operators.md @@ -0,0 +1,5 @@ +The file operator is used to filter based on filename. This is most often used with merge when needing to merge specific files together. + +```bash +yq eval 'filename == "file1.yaml" * fileIndex == 0' file1.yaml file2.yaml +``` \ No newline at end of file diff --git a/pkg/yqlib/encoder_test.go b/pkg/yqlib/encoder_test.go index 867ad40f..d7211734 100644 --- a/pkg/yqlib/encoder_test.go +++ b/pkg/yqlib/encoder_test.go @@ -32,7 +32,7 @@ func TestJsonEncoderPreservesObjectOrder(t *testing.T) { writer := bufio.NewWriter(&output) var jsonEncoder = NewJsonEncoder(writer, 2) - inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml") + inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0) if err != nil { panic(err) } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 0c93f086..4cd7b597 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -51,8 +51,8 @@ var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Han var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator} var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator} var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator} -var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilename} -var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndex} +var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator} +var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator} var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator} diff --git a/pkg/yqlib/operator_file.go b/pkg/yqlib/operator_file.go index ad0653b4..23331951 100644 --- a/pkg/yqlib/operator_file.go +++ b/pkg/yqlib/operator_file.go @@ -7,7 +7,7 @@ import ( yaml "gopkg.in/yaml.v3" ) -func GetFilename(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { +func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("GetFilename") var results = list.New() @@ -22,7 +22,7 @@ func GetFilename(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT return results, nil } -func GetFileIndex(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { +func GetFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("GetFileIndex") var results = list.New() diff --git a/pkg/yqlib/operator_file_test.go b/pkg/yqlib/operator_file_test.go new file mode 100644 index 00000000..7117426c --- /dev/null +++ b/pkg/yqlib/operator_file_test.go @@ -0,0 +1,31 @@ +package yqlib + +import ( + "testing" +) + +var fileOperatorScenarios = []expressionScenario{ + { + description: "Get filename", + document: `{}`, + expression: `filename`, + expected: []string{ + "D0, P[], (!!str)::sample.yml\n", + }, + }, + { + description: "Get file index", + document: `{}`, + expression: `fileIndex`, + expected: []string{ + "D0, P[], (!!int)::0\n", + }, + }, +} + +func TestFileOperatorsScenarios(t *testing.T) { + for _, tt := range fileOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "File Operators", fileOperatorScenarios) +} diff --git a/pkg/yqlib/operator_multilpy.go b/pkg/yqlib/operator_multilpy.go index 9f25962e..ac7b456e 100644 --- a/pkg/yqlib/operator_multilpy.go +++ b/pkg/yqlib/operator_multilpy.go @@ -5,7 +5,7 @@ import ( "container/list" - "gopkg.in/yaml.v3" + yaml "gopkg.in/yaml.v3" ) type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) @@ -15,12 +15,14 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat if err != nil { return nil, err } + log.Debugf("crossFunction LHS len: %v", lhs.Len()) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) if err != nil { return nil, err } + log.Debugf("crossFunction RHS len: %v", rhs.Len()) var results = list.New() @@ -28,6 +30,7 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat lhsCandidate := el.Value.(*CandidateNode) for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() { + log.Debugf("Applying calc") rhsCandidate := rightEl.Value.(*CandidateNode) resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate) if err != nil { @@ -48,8 +51,8 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode * func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { lhs.Node = UnwrapDoc(lhs.Node) rhs.Node = UnwrapDoc(rhs.Node) - log.Debugf("Multipling LHS: %v", NodeToString(lhs)) - log.Debugf("- RHS: %v", NodeToString(rhs)) + log.Debugf("Multipling LHS: %v", lhs.Node.Tag) + log.Debugf("- RHS: %v", rhs.Node.Tag) if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { @@ -67,7 +70,7 @@ func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*Ca return mergeObjects(d, newThing, rhs) } - return nil, fmt.Errorf("Cannot multiply %v with %v", NodeToString(lhs), NodeToString(rhs)) + return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag) } func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 8af48869..c9713b00 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -35,7 +35,7 @@ func testScenario(t *testing.T, s *expressionScenario) { inputs := list.New() if s.document != "" { - inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml") + inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0) if err != nil { t.Error(err) return diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 115b6d5b..1fc0ed3b 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -204,6 +204,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle)) lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag)) + lexer.Add([]byte(`filename`), opToken(GetFilename)) + lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex)) lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true})) diff --git a/pkg/yqlib/printer_test.go b/pkg/yqlib/printer_test.go index 699a8d05..f54af3c3 100644 --- a/pkg/yqlib/printer_test.go +++ b/pkg/yqlib/printer_test.go @@ -21,7 +21,7 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) { var writer = bufio.NewWriter(&output) printer := NewPrinter(writer, false, true, false, 2, true) - inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml") + inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0) if err != nil { panic(err) } @@ -60,7 +60,7 @@ func TestPrinterMultipleDocsInSinglePrint(t *testing.T) { var writer = bufio.NewWriter(&output) printer := NewPrinter(writer, false, true, false, 2, true) - inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml") + inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0) if err != nil { panic(err) } @@ -79,7 +79,7 @@ func TestPrinterMultipleDocsJson(t *testing.T) { var writer = bufio.NewWriter(&output) printer := NewPrinter(writer, true, true, false, 0, false) - inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml") + inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0) if err != nil { panic(err) } diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index 3b7360b8..4833094b 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -9,9 +9,13 @@ import ( yaml "gopkg.in/yaml.v3" ) +//TODO: convert to interface + struct + var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) var treeCreator = NewPathTreeCreator() +var fileIndex = 0 + func readStream(filename string) (io.Reader, error) { if filename == "-" { return bufio.NewReader(os.Stdin), nil @@ -30,14 +34,16 @@ func EvaluateStream(filename string, reader io.Reader, node *PathTreeNode, print errorReading := decoder.Decode(&dataBucket) if errorReading == io.EOF { + fileIndex = fileIndex + 1 return nil } else if errorReading != nil { return errorReading } candidateNode := &CandidateNode{ - Document: currentIndex, - Filename: filename, - Node: &dataBucket, + Document: currentIndex, + Filename: filename, + Node: &dataBucket, + FileIndex: fileIndex, } inputList := list.New() inputList.PushBack(candidateNode) @@ -54,7 +60,7 @@ func EvaluateStream(filename string, reader io.Reader, node *PathTreeNode, print } } -func readDocuments(reader io.Reader, filename string) (*list.List, error) { +func readDocuments(reader io.Reader, filename string, fileIndex int) (*list.List, error) { decoder := yaml.NewDecoder(reader) inputList := list.New() var currentIndex uint = 0 @@ -73,9 +79,10 @@ func readDocuments(reader io.Reader, filename string) (*list.List, error) { return nil, errorReading } candidateNode := &CandidateNode{ - Document: currentIndex, - Filename: filename, - Node: &dataBucket, + Document: currentIndex, + Filename: filename, + Node: &dataBucket, + FileIndex: fileIndex, } inputList.PushBack(candidateNode) @@ -85,6 +92,7 @@ func readDocuments(reader io.Reader, filename string) (*list.List, error) { } func EvaluateAllFileStreams(expression string, filenames []string, printer Printer) error { + fileIndex := 0 node, err := treeCreator.ParsePath(expression) if err != nil { return err @@ -95,11 +103,12 @@ func EvaluateAllFileStreams(expression string, filenames []string, printer Print if err != nil { return err } - fileDocuments, err := readDocuments(reader, filename) + fileDocuments, err := readDocuments(reader, filename, fileIndex) if err != nil { return err } allDocuments.PushBackList(fileDocuments) + fileIndex = fileIndex + 1 } matches, err := treeNavigator.GetMatchingNodes(allDocuments, node) if err != nil { From e451119014c37f6408952ada532bc0cf7f811aa7 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 20 Nov 2020 23:08:12 +1100 Subject: [PATCH 096/129] Added File operators --- pkg/yqlib/doc/File Operators.md | 8 ++++---- pkg/yqlib/doc/headers/File Operators.md | 4 ++-- pkg/yqlib/lib.go | 2 -- pkg/yqlib/operator_file_test.go | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pkg/yqlib/doc/File Operators.md b/pkg/yqlib/doc/File Operators.md index 9ecc52fd..bf122b63 100644 --- a/pkg/yqlib/doc/File Operators.md +++ b/pkg/yqlib/doc/File Operators.md @@ -1,13 +1,13 @@ -The file operator is used to filter based on filename. This is most often used with merge when needing to merge specific files together. +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). ```bash -yq eval 'filename == "file1.yaml" * fileIndex == 0' file1.yaml file2.yaml +yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml ``` ## Examples ### Get filename Given a sample.yml file of: ```yaml -'': null +a: cat ``` then ```bash @@ -21,7 +21,7 @@ sample.yaml ### Get file index Given a sample.yml file of: ```yaml -'': null +a: cat ``` then ```bash diff --git a/pkg/yqlib/doc/headers/File Operators.md b/pkg/yqlib/doc/headers/File Operators.md index 6f836113..2efc91d8 100644 --- a/pkg/yqlib/doc/headers/File Operators.md +++ b/pkg/yqlib/doc/headers/File Operators.md @@ -1,5 +1,5 @@ -The file operator is used to filter based on filename. This is most often used with merge when needing to merge specific files together. +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). ```bash -yq eval 'filename == "file1.yaml" * fileIndex == 0' file1.yaml file2.yaml +yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml ``` \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 4cd7b597..b2ea9350 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -18,8 +18,6 @@ type OperationType struct { // operators TODO: // - get path operator (like doc index) -// - get file index op (like doc index) -// - get file name op (like doc index) // - write in place // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) diff --git a/pkg/yqlib/operator_file_test.go b/pkg/yqlib/operator_file_test.go index 7117426c..a4735cef 100644 --- a/pkg/yqlib/operator_file_test.go +++ b/pkg/yqlib/operator_file_test.go @@ -7,7 +7,7 @@ import ( var fileOperatorScenarios = []expressionScenario{ { description: "Get filename", - document: `{}`, + document: `{a: cat}`, expression: `filename`, expected: []string{ "D0, P[], (!!str)::sample.yml\n", @@ -15,7 +15,7 @@ var fileOperatorScenarios = []expressionScenario{ }, { description: "Get file index", - document: `{}`, + document: `{a: cat}`, expression: `fileIndex`, expected: []string{ "D0, P[], (!!int)::0\n", From fc3af441e5d4e8760a2586afd6661e31a1b1cb93 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Nov 2020 11:56:28 +1100 Subject: [PATCH 097/129] Extracted out evaluators --- cmd/evaluate_all_command.go | 10 ++-- cmd/evalute_sequence_command.go | 11 ++-- pkg/yqlib/all_at_once_evaluator.go | 45 +++++++++++++++ pkg/yqlib/data_tree_navigator.go | 17 ++---- pkg/yqlib/doc/File Operators.md | 2 +- pkg/yqlib/operators_test.go | 9 ++- pkg/yqlib/stream_evaluator.go | 85 ++++++++++++++++++++++++++++ pkg/yqlib/utils.go | 91 +----------------------------- 8 files changed, 156 insertions(+), 114 deletions(-) create mode 100644 pkg/yqlib/all_at_once_evaluator.go create mode 100644 pkg/yqlib/stream_evaluator.go diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index 312688e4..460f0cb7 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -40,23 +40,23 @@ func evaluateAll(cmd *cobra.Command, args []string) error { colorsEnabled = true } printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators) - + allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator() switch len(args) { case 0: if pipingStdIn { - err = yqlib.EvaluateAllFileStreams("", []string{"-"}, printer) + err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer) } else { cmd.Println(cmd.UsageString()) return nil } case 1: if nullInput { - err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer) + err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer) } else { - err = yqlib.EvaluateAllFileStreams("", []string{args[0]}, printer) + err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer) } default: - err = yqlib.EvaluateAllFileStreams(args[0], args[1:], printer) + err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer) } cmd.SilenceUsage = true diff --git a/cmd/evalute_sequence_command.go b/cmd/evalute_sequence_command.go index 191293b0..56125fbb 100644 --- a/cmd/evalute_sequence_command.go +++ b/cmd/evalute_sequence_command.go @@ -41,22 +41,25 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { } printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators) + streamEvaluator := yqlib.NewStreamEvaluator() + allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator() + switch len(args) { case 0: if pipingStdIn { - err = yqlib.EvaluateFileStreamsSequence("", []string{"-"}, printer) + err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer) } else { cmd.Println(cmd.UsageString()) return nil } case 1: if nullInput { - err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer) + err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer) } else { - err = yqlib.EvaluateFileStreamsSequence("", []string{args[0]}, printer) + err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer) } default: - err = yqlib.EvaluateFileStreamsSequence(args[0], args[1:], printer) + err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer) } cmd.SilenceUsage = true diff --git a/pkg/yqlib/all_at_once_evaluator.go b/pkg/yqlib/all_at_once_evaluator.go new file mode 100644 index 00000000..4082c7e2 --- /dev/null +++ b/pkg/yqlib/all_at_once_evaluator.go @@ -0,0 +1,45 @@ +package yqlib + +import "container/list" + +/** + Loads all yaml documents of all files given into memory, then runs the given expression once. +**/ +type Evaluator interface { + EvaluateFiles(expression string, filenames []string, printer Printer) error +} + +type allAtOnceEvaluator struct { + treeNavigator DataTreeNavigator + treeCreator PathTreeCreator +} + +func NewAllAtOnceEvaluator() Evaluator { + return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()} +} + +func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error { + fileIndex := 0 + node, err := treeCreator.ParsePath(expression) + if err != nil { + return err + } + var allDocuments *list.List = list.New() + for _, filename := range filenames { + reader, err := readStream(filename) + if err != nil { + return err + } + fileDocuments, err := readDocuments(reader, filename, fileIndex) + if err != nil { + return err + } + allDocuments.PushBackList(fileDocuments) + fileIndex = fileIndex + 1 + } + matches, err := treeNavigator.GetMatchingNodes(allDocuments, node) + if err != nil { + return err + } + return printer.PrintResults(matches) +} diff --git a/pkg/yqlib/data_tree_navigator.go b/pkg/yqlib/data_tree_navigator.go index 78ff4410..f8acc73f 100644 --- a/pkg/yqlib/data_tree_navigator.go +++ b/pkg/yqlib/data_tree_navigator.go @@ -5,17 +5,9 @@ import ( "container/list" - "gopkg.in/op/go-logging.v1" + logging "gopkg.in/op/go-logging.v1" ) -type dataTreeNavigator struct { - navigationPrefs NavigationPrefs -} - -type NavigationPrefs struct { - FollowAlias bool -} - type DataTreeNavigator interface { // given a list of CandidateEntities and a pathNode, // this will process the list against the given pathNode and return @@ -23,8 +15,11 @@ type DataTreeNavigator interface { GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) } -func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { - return &dataTreeNavigator{navigationPrefs} +type dataTreeNavigator struct { +} + +func NewDataTreeNavigator() DataTreeNavigator { + return &dataTreeNavigator{} } func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { diff --git a/pkg/yqlib/doc/File Operators.md b/pkg/yqlib/doc/File Operators.md index bf122b63..a6edf7ba 100644 --- a/pkg/yqlib/doc/File Operators.md +++ b/pkg/yqlib/doc/File Operators.md @@ -29,6 +29,6 @@ yq eval 'fileIndex' sample.yml ``` will output ```yaml -73 +0 ``` diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index c9713b00..6c929f6a 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -90,7 +90,8 @@ func formatYaml(yaml string) string { if err != nil { panic(err) } - err = EvaluateStream("sample.yaml", strings.NewReader(yaml), node, printer) + streamEvaluator := NewStreamEvaluator() + err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(yaml), node, printer) if err != nil { panic(err) } @@ -161,12 +162,14 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari if err != nil { t.Error(err) } - err = EvaluateStream("sample.yaml", strings.NewReader(formattedDoc), node, printer) + streamEvaluator := NewStreamEvaluator() + err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer) if err != nil { t.Error(err) } } else { - err = EvaluateAllFileStreams(s.expression, []string{}, printer) + allAtOnceEvaluator := NewAllAtOnceEvaluator() + err = allAtOnceEvaluator.EvaluateFiles(s.expression, []string{}, printer) if err != nil { t.Error(err) } diff --git a/pkg/yqlib/stream_evaluator.go b/pkg/yqlib/stream_evaluator.go new file mode 100644 index 00000000..d4c22f19 --- /dev/null +++ b/pkg/yqlib/stream_evaluator.go @@ -0,0 +1,85 @@ +package yqlib + +import ( + "container/list" + "io" + "os" + + yaml "gopkg.in/yaml.v3" +) + +type StreamEvaluator interface { + Evaluate(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error + EvaluateFiles(expression string, filenames []string, printer Printer) error +} + +type streamEvaluator struct { + treeNavigator DataTreeNavigator + treeCreator PathTreeCreator + fileIndex int +} + +func NewStreamEvaluator() StreamEvaluator { + return &streamEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()} +} + +func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error { + + node, err := treeCreator.ParsePath(expression) + if err != nil { + return err + } + + for _, filename := range filenames { + reader, err := readStream(filename) + if err != nil { + return err + } + err = s.Evaluate(filename, reader, node, printer) + if err != nil { + return err + } + + switch reader := reader.(type) { + case *os.File: + safelyCloseFile(reader) + } + } + return nil +} + +func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error { + + var currentIndex uint + + decoder := yaml.NewDecoder(reader) + for { + var dataBucket yaml.Node + errorReading := decoder.Decode(&dataBucket) + + if errorReading == io.EOF { + s.fileIndex = s.fileIndex + 1 + return nil + } else if errorReading != nil { + return errorReading + } + candidateNode := &CandidateNode{ + Document: currentIndex, + Filename: filename, + Node: &dataBucket, + FileIndex: s.fileIndex, + } + inputList := list.New() + inputList.PushBack(candidateNode) + + matches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node) + if errorParsing != nil { + return errorParsing + } + err := printer.PrintResults(matches) + if err != nil { + return err + } + currentIndex = currentIndex + 1 + } +} diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index 4833094b..d46cdd77 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -11,11 +11,9 @@ import ( //TODO: convert to interface + struct -var treeNavigator = NewDataTreeNavigator(NavigationPrefs{}) +var treeNavigator = NewDataTreeNavigator() var treeCreator = NewPathTreeCreator() -var fileIndex = 0 - func readStream(filename string) (io.Reader, error) { if filename == "-" { return bufio.NewReader(os.Stdin), nil @@ -24,42 +22,6 @@ func readStream(filename string) (io.Reader, error) { } } -func EvaluateStream(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error { - - var currentIndex uint = 0 - - decoder := yaml.NewDecoder(reader) - for { - var dataBucket yaml.Node - errorReading := decoder.Decode(&dataBucket) - - if errorReading == io.EOF { - fileIndex = fileIndex + 1 - return nil - } else if errorReading != nil { - return errorReading - } - candidateNode := &CandidateNode{ - Document: currentIndex, - Filename: filename, - Node: &dataBucket, - FileIndex: fileIndex, - } - inputList := list.New() - inputList.PushBack(candidateNode) - - matches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node) - if errorParsing != nil { - return errorParsing - } - err := printer.PrintResults(matches) - if err != nil { - return err - } - currentIndex = currentIndex + 1 - } -} - func readDocuments(reader io.Reader, filename string, fileIndex int) (*list.List, error) { decoder := yaml.NewDecoder(reader) inputList := list.New() @@ -91,57 +53,6 @@ func readDocuments(reader io.Reader, filename string, fileIndex int) (*list.List } } -func EvaluateAllFileStreams(expression string, filenames []string, printer Printer) error { - fileIndex := 0 - node, err := treeCreator.ParsePath(expression) - if err != nil { - return err - } - var allDocuments *list.List = list.New() - for _, filename := range filenames { - reader, err := readStream(filename) - if err != nil { - return err - } - fileDocuments, err := readDocuments(reader, filename, fileIndex) - if err != nil { - return err - } - allDocuments.PushBackList(fileDocuments) - fileIndex = fileIndex + 1 - } - matches, err := treeNavigator.GetMatchingNodes(allDocuments, node) - if err != nil { - return err - } - return printer.PrintResults(matches) -} - -func EvaluateFileStreamsSequence(expression string, filenames []string, printer Printer) error { - - node, err := treeCreator.ParsePath(expression) - if err != nil { - return err - } - - for _, filename := range filenames { - reader, err := readStream(filename) - if err != nil { - return err - } - err = EvaluateStream(filename, reader, node, printer) - if err != nil { - return err - } - - switch reader := reader.(type) { - case *os.File: - safelyCloseFile(reader) - } - } - return nil -} - // func safelyRenameFile(from string, to string) { // if renameError := os.Rename(from, to); renameError != nil { // log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) From 064cff13418f470a1a42d7a1403a0b44f196421b Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Nov 2020 12:19:57 +1100 Subject: [PATCH 098/129] added path operator! --- pkg/yqlib/doc/Path Operators.md | 59 +++++++++++++++++++++++++ pkg/yqlib/doc/headers/Path Operators.md | 1 + pkg/yqlib/lib.go | 2 +- pkg/yqlib/operator_path.go | 39 ++++++++++++++++ pkg/yqlib/operator_path_test.go | 45 +++++++++++++++++++ pkg/yqlib/path_tokeniser.go | 1 + 6 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 pkg/yqlib/doc/Path Operators.md create mode 100644 pkg/yqlib/doc/headers/Path Operators.md create mode 100644 pkg/yqlib/operator_path.go create mode 100644 pkg/yqlib/operator_path_test.go diff --git a/pkg/yqlib/doc/Path Operators.md b/pkg/yqlib/doc/Path Operators.md new file mode 100644 index 00000000..27ec6988 --- /dev/null +++ b/pkg/yqlib/doc/Path Operators.md @@ -0,0 +1,59 @@ +The path operator can be used to find 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. +## Examples +### Map path +Given a sample.yml file of: +```yaml +a: + b: cat +``` +then +```bash +yq eval '.a.b | path' sample.yml +``` +will output +```yaml +- a +- b +``` + +### Array path +Given a sample.yml file of: +```yaml +a: + - cat + - dog +``` +then +```bash +yq eval '.a.[] | select(. == "dog") | path' sample.yml +``` +will output +```yaml +- a +- 1 +``` + +### Print path and value +Given a sample.yml file of: +```yaml +a: + - cat + - dog + - frog +``` +then +```bash +yq eval '.a.[] | select(. == "*og") | [{"path":path, "value":.}]' sample.yml +``` +will output +```yaml +- path: + - a + - 1 + value: dog +- path: + - a + - 2 + value: frog +``` + diff --git a/pkg/yqlib/doc/headers/Path Operators.md b/pkg/yqlib/doc/headers/Path Operators.md new file mode 100644 index 00000000..a731d40e --- /dev/null +++ b/pkg/yqlib/doc/headers/Path Operators.md @@ -0,0 +1 @@ +The path operator can be used to find 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. \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index b2ea9350..837ae4f8 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -17,7 +17,6 @@ type OperationType struct { } // operators TODO: -// - get path operator (like doc index) // - write in place // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) @@ -51,6 +50,7 @@ var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator} var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator} var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator} +var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator} var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator} diff --git a/pkg/yqlib/operator_path.go b/pkg/yqlib/operator_path.go new file mode 100644 index 00000000..2f27f48e --- /dev/null +++ b/pkg/yqlib/operator_path.go @@ -0,0 +1,39 @@ +package yqlib + +import ( + "container/list" + "fmt" + + yaml "gopkg.in/yaml.v3" +) + +func createPathNodeFor(pathElement interface{}) *yaml.Node { + switch pathElement := pathElement.(type) { + case string: + return &yaml.Node{Kind: yaml.ScalarNode, Value: pathElement, Tag: "!!str"} + default: + return &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", pathElement), Tag: "!!int"} + } +} + +func GetPathOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("GetPath") + + var results = list.New() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} + + content := make([]*yaml.Node, len(candidate.Path)) + for pathIndex := 0; pathIndex < len(candidate.Path); pathIndex++ { + path := candidate.Path[pathIndex] + content[pathIndex] = createPathNodeFor(path) + } + node.Content = content + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(lengthCand) + } + + return results, nil +} diff --git a/pkg/yqlib/operator_path_test.go b/pkg/yqlib/operator_path_test.go new file mode 100644 index 00000000..ecc59f23 --- /dev/null +++ b/pkg/yqlib/operator_path_test.go @@ -0,0 +1,45 @@ +package yqlib + +import ( + "testing" +) + +var pathOperatorScenarios = []expressionScenario{ + { + description: "Map path", + document: `{a: {b: cat}}`, + expression: `.a.b | path`, + expected: []string{ + "D0, P[a b], (!!seq)::- a\n- b\n", + }, + }, + { + description: "Array path", + document: `{a: [cat, dog]}`, + expression: `.a.[] | select(. == "dog") | path`, + expected: []string{ + "D0, P[a 1], (!!seq)::- a\n- 1\n", + }, + }, + { + description: "Print path and value", + document: `{a: [cat, dog, frog]}`, + expression: `.a.[] | select(. == "*og") | [{"path":path, "value":.}]`, + expected: []string{`D0, P[], (!!seq)::- path: + - a + - 1 + value: dog +- path: + - a + - 2 + value: frog +`}, + }, +} + +func TestPathOperatorsScenarios(t *testing.T) { + for _, tt := range pathOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Path Operators", pathOperatorScenarios) +} diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 1fc0ed3b..17af2a25 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -206,6 +206,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag)) lexer.Add([]byte(`filename`), opToken(GetFilename)) lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex)) + lexer.Add([]byte(`path`), opToken(GetPath)) lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true})) From e9fa873af885c359ac1602271cf3418f96de60f2 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Nov 2020 12:22:15 +1100 Subject: [PATCH 099/129] path operator singular --- pkg/yqlib/doc/{Path Operators.md => Path Operator.md} | 0 pkg/yqlib/doc/headers/{Path Operators.md => Path Operator.md} | 0 pkg/yqlib/operator_path_test.go | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename pkg/yqlib/doc/{Path Operators.md => Path Operator.md} (100%) rename pkg/yqlib/doc/headers/{Path Operators.md => Path Operator.md} (100%) diff --git a/pkg/yqlib/doc/Path Operators.md b/pkg/yqlib/doc/Path Operator.md similarity index 100% rename from pkg/yqlib/doc/Path Operators.md rename to pkg/yqlib/doc/Path Operator.md diff --git a/pkg/yqlib/doc/headers/Path Operators.md b/pkg/yqlib/doc/headers/Path Operator.md similarity index 100% rename from pkg/yqlib/doc/headers/Path Operators.md rename to pkg/yqlib/doc/headers/Path Operator.md diff --git a/pkg/yqlib/operator_path_test.go b/pkg/yqlib/operator_path_test.go index ecc59f23..5341f7af 100644 --- a/pkg/yqlib/operator_path_test.go +++ b/pkg/yqlib/operator_path_test.go @@ -41,5 +41,5 @@ func TestPathOperatorsScenarios(t *testing.T) { for _, tt := range pathOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Path Operators", pathOperatorScenarios) + documentScenarios(t, "Path Operator", pathOperatorScenarios) } From aed598c736858f231911dcb4ff36661e6ae50aa2 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Nov 2020 13:16:54 +1100 Subject: [PATCH 100/129] Fixing docs --- .../doc/{Assign Operator.md => Assign.md} | 32 ++--- pkg/yqlib/doc/Boolean Operators.md | 79 +++++++++++- pkg/yqlib/doc/Collect into Array.md | 7 +- pkg/yqlib/doc/Collect into Object.md | 12 +- ...ments Operator.md => Comment Operators.md} | 17 ++- .../doc/{Delete Operator.md => Delete.md} | 9 +- pkg/yqlib/doc/Document Index Operator.md | 8 +- pkg/yqlib/doc/Document Index.md | 54 ++++++++ .../doc/{Equals Operator.md => Equals.md} | 9 +- .../doc/{Explode Operator.md => Explode.md} | 9 +- pkg/yqlib/doc/File Operators.md | 7 +- pkg/yqlib/doc/Mulitply Operator.md | 121 ------------------ .../doc/{Multiply Operator.md => Multiply.md} | 26 ++-- pkg/yqlib/doc/Not Operator.md | 72 ----------- pkg/yqlib/doc/{Path Operator.md => Path.md} | 9 +- ...scent Operator.md => Recursive Descent.md} | 94 +++----------- .../doc/{Select Operator.md => Select.md} | 5 +- pkg/yqlib/doc/{Style Operator.md => Style.md} | 28 ++-- pkg/yqlib/doc/{Tag Operator.md => Tag.md} | 7 +- .../doc/{Traverse Operator.md => Traverse.md} | 51 +++++--- pkg/yqlib/doc/{Union Operator.md => Union.md} | 5 +- .../headers/{Assign Operator.md => Assign.md} | 0 pkg/yqlib/doc/headers/Boolean Operators.md | 2 +- ...ments Operator.md => Comment Operators.md} | 0 .../headers/{Delete Operator.md => Delete.md} | 0 pkg/yqlib/doc/headers/Document Index.md | 1 + .../headers/{Equals Operator.md => Equals.md} | 2 - .../{Explode Operator.md => Explode.md} | 0 pkg/yqlib/doc/headers/File Operators.md | 2 + .../{Multiply Operator.md => Multiply.md} | 9 +- pkg/yqlib/doc/headers/Not Operator.md | 1 - pkg/yqlib/doc/headers/Path Operator.md | 1 - pkg/yqlib/doc/headers/Path.md | 1 + ...scent Operator.md => Recursive Descent.md} | 0 .../headers/{Select Operator.md => Select.md} | 0 .../headers/{Style Operator.md => Style.md} | 0 pkg/yqlib/doc/headers/Tag Operator.md | 1 - pkg/yqlib/doc/headers/Tag.md | 1 + pkg/yqlib/doc/headers/Traverse Operator.md | 1 - pkg/yqlib/doc/headers/Traverse.md | 1 + .../headers/{Union Operator.md => Union.md} | 0 pkg/yqlib/operator_assign_test.go | 16 ++- pkg/yqlib/operator_booleans.go | 17 +++ pkg/yqlib/operator_booleans_test.go | 50 ++++++++ pkg/yqlib/operator_collect_object.go | 6 +- pkg/yqlib/operator_collect_object_test.go | 4 +- pkg/yqlib/operator_comments_test.go | 2 +- pkg/yqlib/operator_delete_test.go | 2 +- ...operator.go => operator_document_index.go} | 0 ...est.go => operator_document_index_test.go} | 2 +- pkg/yqlib/operator_equals_test.go | 2 +- pkg/yqlib/operator_explode_test.go | 2 +- ...rator_multilpy.go => operator_multiply.go} | 0 pkg/yqlib/operator_multiply_test.go | 2 +- pkg/yqlib/operator_not.go | 20 --- pkg/yqlib/operator_not_test.go | 65 ---------- pkg/yqlib/operator_path_test.go | 2 +- pkg/yqlib/operator_recursive_descent_test.go | 34 ++--- pkg/yqlib/operator_select_test.go | 2 +- pkg/yqlib/operator_style_test.go | 16 ++- pkg/yqlib/operator_tag_test.go | 2 +- pkg/yqlib/operator_traverse_path_test.go | 11 +- pkg/yqlib/operator_union_test.go | 2 +- pkg/yqlib/operators_test.go | 14 +- 64 files changed, 414 insertions(+), 543 deletions(-) rename pkg/yqlib/doc/{Assign Operator.md => Assign.md} (84%) rename pkg/yqlib/doc/{Comments Operator.md => Comment Operators.md} (85%) rename pkg/yqlib/doc/{Delete Operator.md => Delete.md} (84%) create mode 100644 pkg/yqlib/doc/Document Index.md rename pkg/yqlib/doc/{Equals Operator.md => Equals.md} (88%) rename pkg/yqlib/doc/{Explode Operator.md => Explode.md} (88%) delete mode 100644 pkg/yqlib/doc/Mulitply Operator.md rename pkg/yqlib/doc/{Multiply Operator.md => Multiply.md} (83%) delete mode 100644 pkg/yqlib/doc/Not Operator.md rename pkg/yqlib/doc/{Path Operator.md => Path.md} (68%) rename pkg/yqlib/doc/{Recursive Descent Operator.md => Recursive Descent.md} (50%) rename pkg/yqlib/doc/{Select Operator.md => Select.md} (83%) rename pkg/yqlib/doc/{Style Operator.md => Style.md} (85%) rename pkg/yqlib/doc/{Tag Operator.md => Tag.md} (77%) rename pkg/yqlib/doc/{Traverse Operator.md => Traverse.md} (82%) rename pkg/yqlib/doc/{Union Operator.md => Union.md} (83%) rename pkg/yqlib/doc/headers/{Assign Operator.md => Assign.md} (100%) rename pkg/yqlib/doc/headers/{Comments Operator.md => Comment Operators.md} (100%) rename pkg/yqlib/doc/headers/{Delete Operator.md => Delete.md} (100%) create mode 100644 pkg/yqlib/doc/headers/Document Index.md rename pkg/yqlib/doc/headers/{Equals Operator.md => Equals.md} (92%) rename pkg/yqlib/doc/headers/{Explode Operator.md => Explode.md} (100%) rename pkg/yqlib/doc/headers/{Multiply Operator.md => Multiply.md} (69%) delete mode 100644 pkg/yqlib/doc/headers/Not Operator.md delete mode 100644 pkg/yqlib/doc/headers/Path Operator.md create mode 100644 pkg/yqlib/doc/headers/Path.md rename pkg/yqlib/doc/headers/{Recursive Descent Operator.md => Recursive Descent.md} (100%) rename pkg/yqlib/doc/headers/{Select Operator.md => Select.md} (100%) rename pkg/yqlib/doc/headers/{Style Operator.md => Style.md} (100%) delete mode 100644 pkg/yqlib/doc/headers/Tag Operator.md create mode 100644 pkg/yqlib/doc/headers/Tag.md delete mode 100644 pkg/yqlib/doc/headers/Traverse Operator.md create mode 100644 pkg/yqlib/doc/headers/Traverse.md rename pkg/yqlib/doc/headers/{Union Operator.md => Union.md} (100%) rename pkg/yqlib/{document_index_operator.go => operator_document_index.go} (100%) rename pkg/yqlib/{document_index_operator_test.go => operator_document_index_test.go} (92%) rename pkg/yqlib/{operator_multilpy.go => operator_multiply.go} (100%) delete mode 100644 pkg/yqlib/operator_not.go delete mode 100644 pkg/yqlib/operator_not_test.go diff --git a/pkg/yqlib/doc/Assign Operator.md b/pkg/yqlib/doc/Assign.md similarity index 84% rename from pkg/yqlib/doc/Assign Operator.md rename to pkg/yqlib/doc/Assign.md index f5b893ba..a783587d 100644 --- a/pkg/yqlib/doc/Assign Operator.md +++ b/pkg/yqlib/doc/Assign.md @@ -5,8 +5,7 @@ Which will assign the LHS node values to the RHS node values. The RHS expression ### 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. -## Examples -### Update node to be the child value +## Update node to be the child value Given a sample.yml file of: ```yaml a: @@ -23,7 +22,7 @@ a: g: foof ``` -### Update node to be the sibling value +## Update node to be the sibling value Given a sample.yml file of: ```yaml a: @@ -40,7 +39,7 @@ a: sibling b: sibling ``` -### Updated multiple paths +## Updated multiple paths Given a sample.yml file of: ```yaml a: fieldA @@ -58,7 +57,7 @@ b: fieldB c: potatoe ``` -### Update string value +## Update string value Given a sample.yml file of: ```yaml a: @@ -74,7 +73,7 @@ a: b: frog ``` -### Update string value via |= +## Update string value via |= Note there is no difference between `=` and `|=` when the RHS is a scalar Given a sample.yml file of: @@ -92,7 +91,7 @@ a: b: frog ``` -### Update selected results +## Update selected results Given a sample.yml file of: ```yaml a: @@ -110,7 +109,7 @@ a: c: cactus ``` -### Update array values +## Update array values Given a sample.yml file of: ```yaml - candy @@ -128,10 +127,10 @@ will output - bogs ``` -### Update empty object +## Update empty object Given a sample.yml file of: ```yaml -'': null +{} ``` then ```bash @@ -139,15 +138,13 @@ yq eval '.a.b |= "bogs"' sample.yml ``` will output ```yaml -'': null -a: - b: bogs +{a: {b: bogs}} ``` -### Update empty object and array +## Update empty object and array Given a sample.yml file of: ```yaml -'': null +{} ``` then ```bash @@ -155,9 +152,6 @@ yq eval '.a.b[0] |= "bogs"' sample.yml ``` will output ```yaml -'': null -a: - b: - - bogs +{a: {b: [bogs]}} ``` diff --git a/pkg/yqlib/doc/Boolean Operators.md b/pkg/yqlib/doc/Boolean Operators.md index 0b8a5e5d..7955937c 100644 --- a/pkg/yqlib/doc/Boolean Operators.md +++ b/pkg/yqlib/doc/Boolean Operators.md @@ -1,6 +1,5 @@ -The `or` and `and` operators take two parameters and return a boolean result. These are most commonly used with the `select` operator to filter particular nodes. -## Examples -### OR example +The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes. +## OR example Running ```bash yq eval --null-input 'true or false' @@ -10,7 +9,7 @@ will output true ``` -### AND example +## AND example Running ```bash yq eval --null-input 'true and false' @@ -20,7 +19,7 @@ will output false ``` -### Matching nodes with select, equals and or +## Matching nodes with select, equals and or Given a sample.yml file of: ```yaml - a: bird @@ -42,3 +41,73 @@ will output b: fly ``` +## Not true is false +Running +```bash +yq eval --null-input 'true | not' +``` +will output +```yaml +false +``` + +## Not false is true +Running +```bash +yq eval --null-input 'false | not' +``` +will output +```yaml +true +``` + +## String values considered to be true +Running +```bash +yq eval --null-input '"cat" | not' +``` +will output +```yaml +false +``` + +## Empty string value considered to be true +Running +```bash +yq eval --null-input '"" | not' +``` +will output +```yaml +false +``` + +## Numbers are considered to be true +Running +```bash +yq eval --null-input '1 | not' +``` +will output +```yaml +false +``` + +## Zero is considered to be true +Running +```bash +yq eval --null-input '0 | not' +``` +will output +```yaml +false +``` + +## Null is considered to be false +Running +```bash +yq eval --null-input '~ | not' +``` +will output +```yaml +true +``` + diff --git a/pkg/yqlib/doc/Collect into Array.md b/pkg/yqlib/doc/Collect into Array.md index fa8e25a8..d06927e6 100644 --- a/pkg/yqlib/doc/Collect into Array.md +++ b/pkg/yqlib/doc/Collect into Array.md @@ -3,8 +3,7 @@ This creates an array using the expression between the square brackets. -## Examples -### Collect empty +## Collect empty Running ```bash yq eval --null-input '[]' @@ -13,7 +12,7 @@ will output ```yaml ``` -### Collect single +## Collect single Running ```bash yq eval --null-input '["cat"]' @@ -23,7 +22,7 @@ will output - cat ``` -### Collect many +## Collect many Given a sample.yml file of: ```yaml a: cat diff --git a/pkg/yqlib/doc/Collect into Object.md b/pkg/yqlib/doc/Collect into Object.md index c46f5d79..ded3288d 100644 --- a/pkg/yqlib/doc/Collect into Object.md +++ b/pkg/yqlib/doc/Collect into Object.md @@ -1,15 +1,15 @@ This is used to construct objects (or maps). This can be used against existing yaml, or to create fresh yaml documents. -## Examples -### Collect empty object +## Collect empty object Running ```bash yq eval --null-input '{}' ``` will output ```yaml +{} ``` -### Wrap (prefix) existing object +## Wrap (prefix) existing object Given a sample.yml file of: ```yaml name: Mike @@ -24,7 +24,7 @@ wrap: name: Mike ``` -### Using splat to create multiple objects +## Using splat to create multiple objects Given a sample.yml file of: ```yaml name: Mike @@ -42,7 +42,7 @@ Mike: cat Mike: dog ``` -### Working with multiple documents +## Working with multiple documents Given a sample.yml file of: ```yaml name: Mike @@ -67,7 +67,7 @@ Rosey: monkey Rosey: sheep ``` -### Creating yaml from scratch +## Creating yaml from scratch Running ```bash yq eval --null-input '{"wrap": "frog"}' diff --git a/pkg/yqlib/doc/Comments Operator.md b/pkg/yqlib/doc/Comment Operators.md similarity index 85% rename from pkg/yqlib/doc/Comments Operator.md rename to pkg/yqlib/doc/Comment Operators.md index ab8b8864..d6d133ca 100644 --- a/pkg/yqlib/doc/Comments Operator.md +++ b/pkg/yqlib/doc/Comment Operators.md @@ -1,6 +1,5 @@ Use these comment operators to set or retrieve comments. -## Examples -### Set line comment +## Set line comment Given a sample.yml file of: ```yaml a: cat @@ -14,7 +13,7 @@ will output a: cat # single ``` -### Set head comment +## Set head comment Given a sample.yml file of: ```yaml a: cat @@ -30,7 +29,7 @@ will output a: cat ``` -### Set foot comment, using an expression +## Set foot comment, using an expression Given a sample.yml file of: ```yaml a: cat @@ -46,7 +45,7 @@ a: cat # cat ``` -### Remove comment +## Remove comment Given a sample.yml file of: ```yaml a: cat # comment @@ -62,7 +61,7 @@ a: cat b: dog # leave this ``` -### Remove all comments +## Remove all comments Given a sample.yml file of: ```yaml a: cat # comment @@ -76,7 +75,7 @@ will output a: cat ``` -### Get line comment +## Get line comment Given a sample.yml file of: ```yaml a: cat # meow @@ -90,7 +89,7 @@ will output meow ``` -### Get head comment +## Get head comment Given a sample.yml file of: ```yaml a: cat # meow @@ -104,7 +103,7 @@ will output ``` -### Get foot comment +## Get foot comment Given a sample.yml file of: ```yaml a: cat # meow diff --git a/pkg/yqlib/doc/Delete Operator.md b/pkg/yqlib/doc/Delete.md similarity index 84% rename from pkg/yqlib/doc/Delete Operator.md rename to pkg/yqlib/doc/Delete.md index 8bcf3533..601bf30a 100644 --- a/pkg/yqlib/doc/Delete Operator.md +++ b/pkg/yqlib/doc/Delete.md @@ -1,6 +1,5 @@ Deletes matching entries in maps or arrays. -## Examples -### Delete entry in map +## Delete entry in map Given a sample.yml file of: ```yaml a: cat @@ -15,7 +14,7 @@ will output a: cat ``` -### Delete entry in array +## Delete entry in array Given a sample.yml file of: ```yaml - 1 @@ -32,7 +31,7 @@ will output - 3 ``` -### Delete no matches +## Delete no matches Given a sample.yml file of: ```yaml a: cat @@ -48,7 +47,7 @@ a: cat b: dog ``` -### Delete matching entries +## Delete matching entries Given a sample.yml file of: ```yaml a: cat diff --git a/pkg/yqlib/doc/Document Index Operator.md b/pkg/yqlib/doc/Document Index Operator.md index 5dd62822..30ba1ec6 100644 --- a/pkg/yqlib/doc/Document Index Operator.md +++ b/pkg/yqlib/doc/Document Index Operator.md @@ -1,6 +1,4 @@ - -## Examples -### Retrieve a document index +## Retrieve a document index Given a sample.yml file of: ```yaml a: cat @@ -18,7 +16,7 @@ will output 1 ``` -### Filter by document index +## Filter by document index Given a sample.yml file of: ```yaml a: cat @@ -34,7 +32,7 @@ will output a: frog ``` -### Print Document Index with matches +## Print Document Index with matches Given a sample.yml file of: ```yaml a: cat diff --git a/pkg/yqlib/doc/Document Index.md b/pkg/yqlib/doc/Document Index.md new file mode 100644 index 00000000..948e50e4 --- /dev/null +++ b/pkg/yqlib/doc/Document Index.md @@ -0,0 +1,54 @@ +Use the `documentIndex` operator 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 eval '.a | documentIndex' 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 eval 'select(. | documentIndex == 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 eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml +``` +will output +```yaml +match: cat +doc: 0 +match: frog +doc: 1 +``` + diff --git a/pkg/yqlib/doc/Equals Operator.md b/pkg/yqlib/doc/Equals.md similarity index 88% rename from pkg/yqlib/doc/Equals Operator.md rename to pkg/yqlib/doc/Equals.md index 904b0eea..fda10922 100644 --- a/pkg/yqlib/doc/Equals Operator.md +++ b/pkg/yqlib/doc/Equals.md @@ -1,5 +1,3 @@ -## Equals Operator - This is a boolean operator that will return ```true``` if the LHS is equal to the RHS and ``false`` otherwise. ``` @@ -13,8 +11,7 @@ select(.a == .b) ``` -## Examples -### Match string +## Match string Given a sample.yml file of: ```yaml - cat @@ -32,7 +29,7 @@ true false ``` -### Match number +## Match number Given a sample.yml file of: ```yaml - 3 @@ -50,7 +47,7 @@ true false ``` -### Match nulls +## Match nulls Running ```bash yq eval --null-input 'null == ~' diff --git a/pkg/yqlib/doc/Explode Operator.md b/pkg/yqlib/doc/Explode.md similarity index 88% rename from pkg/yqlib/doc/Explode Operator.md rename to pkg/yqlib/doc/Explode.md index 3ca0e208..379d44a0 100644 --- a/pkg/yqlib/doc/Explode Operator.md +++ b/pkg/yqlib/doc/Explode.md @@ -1,6 +1,5 @@ Explodes (or dereferences) aliases and anchors. -## Examples -### Explode alias and anchor +## Explode alias and anchor Given a sample.yml file of: ```yaml f: @@ -18,7 +17,7 @@ f: b: cat ``` -### Explode with no aliases or anchors +## Explode with no aliases or anchors Given a sample.yml file of: ```yaml a: mike @@ -32,7 +31,7 @@ will output a: mike ``` -### Explode with alias keys +## Explode with alias keys Given a sample.yml file of: ```yaml f: @@ -50,7 +49,7 @@ f: cat: b ``` -### Explode with merge anchors +## Explode with merge anchors Given a sample.yml file of: ```yaml foo: &foo diff --git a/pkg/yqlib/doc/File Operators.md b/pkg/yqlib/doc/File Operators.md index a6edf7ba..946dfee9 100644 --- a/pkg/yqlib/doc/File Operators.md +++ b/pkg/yqlib/doc/File Operators.md @@ -1,10 +1,11 @@ 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). +## Merging files +Note the use of eval-all to ensure all documents are loaded into memory. ```bash yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml ``` -## Examples -### Get filename +## Get filename Given a sample.yml file of: ```yaml a: cat @@ -18,7 +19,7 @@ will output sample.yaml ``` -### Get file index +## Get file index Given a sample.yml file of: ```yaml a: cat diff --git a/pkg/yqlib/doc/Mulitply Operator.md b/pkg/yqlib/doc/Mulitply Operator.md deleted file mode 100644 index 24c00aa2..00000000 --- a/pkg/yqlib/doc/Mulitply Operator.md +++ /dev/null @@ -1,121 +0,0 @@ -# Mulitply Operator -## Examples -### Merge objects together -sample.yml: -```yaml -{a: {also: me}, b: {also: {g: wizz}}} -``` -Expression -```bash -yq '. * {"a":.b}' < sample.yml -``` -Result -```yaml -{a: {also: {g: wizz}}, b: {also: {g: wizz}}} -``` -### Merge keeps style of LHS -sample.yml: -```yaml -a: {things: great} -b: - also: "me" - -``` -Expression -```bash -yq '. * {"a":.b}' < sample.yml -``` -Result -```yaml -a: {things: great, also: "me"} -b: - also: "me" -``` -### Merge arrays -sample.yml: -```yaml -{a: [1,2,3], b: [3,4,5]} -``` -Expression -```bash -yq '. * {"a":.b}' < sample.yml -``` -Result -```yaml -{a: [3, 4, 5], b: [3, 4, 5]} -``` -### Merge to prefix an element -sample.yml: -```yaml -{a: cat, b: dog} -``` -Expression -```bash -yq '. * {"a": {"c": .a}}' < sample.yml -``` -Result -```yaml -{a: {c: cat}, b: dog} -``` -### Merge with simple aliases -sample.yml: -```yaml -{a: &cat {c: frog}, b: {f: *cat}, c: {g: thongs}} -``` -Expression -```bash -yq '.c * .b' < sample.yml -``` -Result -```yaml -{g: thongs, f: *cat} -``` -### Merge does not copy anchor names -sample.yml: -```yaml -{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}} -``` -Expression -```bash -yq '.c * .a' < sample.yml -``` -Result -```yaml -{g: thongs, c: frog} -``` -### Merge with merge anchors -sample.yml: -```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 - <<: [*foo,*bar] - c: foobarList_c - -foobar: - c: foobar_c - <<: *foo - thing: foobar_thing - -``` -Expression -```bash -yq '.foobar * .foobarList' < sample.yml -``` -Result -```yaml -c: foobarList_c -<<: [*foo, *bar] -thing: foobar_thing -b: foobarList_b -``` diff --git a/pkg/yqlib/doc/Multiply Operator.md b/pkg/yqlib/doc/Multiply.md similarity index 83% rename from pkg/yqlib/doc/Multiply Operator.md rename to pkg/yqlib/doc/Multiply.md index c82265e6..9f09fea5 100644 --- a/pkg/yqlib/doc/Multiply Operator.md +++ b/pkg/yqlib/doc/Multiply.md @@ -3,8 +3,15 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. -## Examples -### Merge objects together, returning merged result only + +## Merging files +Note the use of eval-all to ensure all documents are loaded into memory. + +```bash +yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml +``` + +## Merge objects together, returning merged result only Given a sample.yml file of: ```yaml a: @@ -27,7 +34,7 @@ fieldA: cat fieldB: dog ``` -### Merge objects together, returning parent object +## Merge objects together, returning parent object Given a sample.yml file of: ```yaml a: @@ -55,12 +62,13 @@ b: fieldB: dog ``` -### Merge keeps style of LHS +## Merge keeps style of LHS Given a sample.yml file of: ```yaml a: {things: great} b: also: "me" + ``` then ```bash @@ -73,7 +81,7 @@ b: also: "me" ``` -### Merge arrays +## Merge arrays Given a sample.yml file of: ```yaml a: @@ -101,7 +109,7 @@ b: - 5 ``` -### Merge to prefix an element +## Merge to prefix an element Given a sample.yml file of: ```yaml a: cat @@ -118,7 +126,7 @@ a: b: dog ``` -### Merge with simple aliases +## Merge with simple aliases Given a sample.yml file of: ```yaml a: &cat @@ -138,7 +146,7 @@ g: thongs f: *cat ``` -### Merge does not copy anchor names +## Merge does not copy anchor names Given a sample.yml file of: ```yaml a: @@ -158,7 +166,7 @@ g: thongs c: frog ``` -### Merge with merge anchors +## Merge with merge anchors Given a sample.yml file of: ```yaml foo: &foo diff --git a/pkg/yqlib/doc/Not Operator.md b/pkg/yqlib/doc/Not Operator.md deleted file mode 100644 index 49d4fd54..00000000 --- a/pkg/yqlib/doc/Not Operator.md +++ /dev/null @@ -1,72 +0,0 @@ -This is a boolean operator and will return `true` when given a `false` value (including null), and `false` otherwise. -## Examples -### Not true is false -Running -```bash -yq eval --null-input 'true | not' -``` -will output -```yaml -false -``` - -### Not false is true -Running -```bash -yq eval --null-input 'false | not' -``` -will output -```yaml -true -``` - -### String values considered to be true -Running -```bash -yq eval --null-input '"cat" | not' -``` -will output -```yaml -false -``` - -### Empty string value considered to be true -Running -```bash -yq eval --null-input '"" | not' -``` -will output -```yaml -false -``` - -### Numbers are considered to be true -Running -```bash -yq eval --null-input '1 | not' -``` -will output -```yaml -false -``` - -### Zero is considered to be true -Running -```bash -yq eval --null-input '0 | not' -``` -will output -```yaml -false -``` - -### Null is considered to be false -Running -```bash -yq eval --null-input '~ | not' -``` -will output -```yaml -true -``` - diff --git a/pkg/yqlib/doc/Path Operator.md b/pkg/yqlib/doc/Path.md similarity index 68% rename from pkg/yqlib/doc/Path Operator.md rename to pkg/yqlib/doc/Path.md index 27ec6988..4acacce0 100644 --- a/pkg/yqlib/doc/Path Operator.md +++ b/pkg/yqlib/doc/Path.md @@ -1,6 +1,5 @@ -The path operator can be used to find 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. -## Examples -### Map 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. +## Map path Given a sample.yml file of: ```yaml a: @@ -16,7 +15,7 @@ will output - b ``` -### Array path +## Array path Given a sample.yml file of: ```yaml a: @@ -33,7 +32,7 @@ will output - 1 ``` -### Print path and value +## Print path and value Given a sample.yml file of: ```yaml a: diff --git a/pkg/yqlib/doc/Recursive Descent Operator.md b/pkg/yqlib/doc/Recursive Descent.md similarity index 50% rename from pkg/yqlib/doc/Recursive Descent Operator.md rename to pkg/yqlib/doc/Recursive Descent.md index 9eec8436..3a3390e3 100644 --- a/pkg/yqlib/doc/Recursive Descent Operator.md +++ b/pkg/yqlib/doc/Recursive Descent.md @@ -3,69 +3,7 @@ This operator recursively matches all children nodes given of a particular eleme ```bash yq eval '.. style= "flow"' file.yaml ``` -## Examples -### Map -Given a sample.yml file of: -```yaml -a: - b: apple -``` -then -```bash -yq eval '..' sample.yml -``` -will output -```yaml -a: - b: apple -b: apple -apple -``` - -### Array -Given a sample.yml file of: -```yaml -- 1 -- 2 -- 3 -``` -then -```bash -yq eval '..' sample.yml -``` -will output -```yaml -- 1 -- 2 -- 3 -1 -2 -3 -``` - -### Array of maps -Given a sample.yml file of: -```yaml -- a: cat -- 2 -- true -``` -then -```bash -yq eval '..' sample.yml -``` -will output -```yaml -- a: cat -- 2 -- true -a: cat -cat -2 -true -``` - -### Aliases are not traversed +## Aliases are not traversed Given a sample.yml file of: ```yaml a: &cat @@ -74,20 +12,20 @@ b: *cat ``` then ```bash -yq eval '..' sample.yml +yq eval '[..]' sample.yml ``` will output ```yaml -a: &cat +- a: &cat + c: frog + b: *cat +- &cat c: frog -b: *cat -&cat -c: frog -frog -*cat +- frog +- *cat ``` -### Merge docs are not traversed +## Merge docs are not traversed Given a sample.yml file of: ```yaml foo: &foo @@ -111,15 +49,15 @@ foobar: ``` then ```bash -yq eval '.foobar | ..' sample.yml +yq eval '.foobar | [..]' sample.yml ``` will output ```yaml -c: foobar_c -!!merge <<: *foo -thing: foobar_thing -foobar_c -*foo -foobar_thing +- c: foobar_c + !!merge <<: *foo + thing: foobar_thing +- foobar_c +- *foo +- foobar_thing ``` diff --git a/pkg/yqlib/doc/Select Operator.md b/pkg/yqlib/doc/Select.md similarity index 83% rename from pkg/yqlib/doc/Select Operator.md rename to pkg/yqlib/doc/Select.md index 3ddb6cc9..1726853b 100644 --- a/pkg/yqlib/doc/Select Operator.md +++ b/pkg/yqlib/doc/Select.md @@ -1,6 +1,5 @@ Select is used to filter arrays and maps by a boolean expression. -## Examples -### Select elements from array +## Select elements from array Given a sample.yml file of: ```yaml - cat @@ -17,7 +16,7 @@ cat goat ``` -### Select and update matching values in map +## Select and update matching values in map Given a sample.yml file of: ```yaml a: diff --git a/pkg/yqlib/doc/Style Operator.md b/pkg/yqlib/doc/Style.md similarity index 85% rename from pkg/yqlib/doc/Style Operator.md rename to pkg/yqlib/doc/Style.md index 0c78cbe5..d089aa4d 100644 --- a/pkg/yqlib/doc/Style Operator.md +++ b/pkg/yqlib/doc/Style.md @@ -1,6 +1,5 @@ The style operator can be used to get or set the style of nodes (e.g. string style, yaml style) -## Examples -### Set tagged style +## Set tagged style Given a sample.yml file of: ```yaml a: cat @@ -21,7 +20,7 @@ c: !!float 3.2 e: !!bool true ``` -### Set double quote style +## Set double quote style Given a sample.yml file of: ```yaml a: cat @@ -41,7 +40,7 @@ c: "3.2" e: "true" ``` -### Set single quote style +## Set single quote style Given a sample.yml file of: ```yaml a: cat @@ -61,7 +60,7 @@ c: '3.2' e: 'true' ``` -### Set literal quote style +## Set literal quote style Given a sample.yml file of: ```yaml a: cat @@ -85,7 +84,7 @@ e: |- true ``` -### Set folded quote style +## Set folded quote style Given a sample.yml file of: ```yaml a: cat @@ -109,7 +108,7 @@ e: >- true ``` -### Set flow quote style +## Set flow quote style Given a sample.yml file of: ```yaml a: cat @@ -126,7 +125,9 @@ will output {a: cat, b: 5, c: 3.2, e: true} ``` -### Set empty (default) quote style +## Pretty print +Set empty (default) quote style + Given a sample.yml file of: ```yaml a: cat @@ -146,11 +147,10 @@ c: 3.2 e: true ``` -### Read style +## Read style Given a sample.yml file of: ```yaml -a: cat -b: thing +{a: "cat", b: 'thing'} ``` then ```bash @@ -158,8 +158,8 @@ yq eval '.. | style' sample.yml ``` will output ```yaml - - - +flow +double +single ``` diff --git a/pkg/yqlib/doc/Tag Operator.md b/pkg/yqlib/doc/Tag.md similarity index 77% rename from pkg/yqlib/doc/Tag Operator.md rename to pkg/yqlib/doc/Tag.md index 863a2368..519142fd 100644 --- a/pkg/yqlib/doc/Tag Operator.md +++ b/pkg/yqlib/doc/Tag.md @@ -1,6 +1,5 @@ -The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`). -## Examples -### Get tag +The tag operator can be used to get or set the tag of nodes (e.g. `!!str`, `!!int`, `!!bool`). +## Get tag Given a sample.yml file of: ```yaml a: cat @@ -23,7 +22,7 @@ will output !!seq ``` -### Convert numbers to strings +## Convert numbers to strings Given a sample.yml file of: ```yaml a: cat diff --git a/pkg/yqlib/doc/Traverse Operator.md b/pkg/yqlib/doc/Traverse.md similarity index 82% rename from pkg/yqlib/doc/Traverse Operator.md rename to pkg/yqlib/doc/Traverse.md index dace7ed4..88ccdb10 100644 --- a/pkg/yqlib/doc/Traverse Operator.md +++ b/pkg/yqlib/doc/Traverse.md @@ -1,6 +1,5 @@ -This is the simples (and perhaps most used) operator, it is used to navigate deeply into yaml structurse. -## Examples -### Simple map navigation +This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse. +## Simple map navigation Given a sample.yml file of: ```yaml a: @@ -15,7 +14,7 @@ will output b: apple ``` -### Splat +## Splat Often used to pipe children into other operators Given a sample.yml file of: @@ -33,7 +32,23 @@ b: apple c: banana ``` -### Children don't exist +## Special characters +Use quotes around path elements with special characters + +Given a sample.yml file of: +```yaml +"{}": frog +``` +then +```bash +yq eval '."{}"' sample.yml +``` +will output +```yaml +frog +``` + +## Children don't exist Nodes are added dynamically while traversing Given a sample.yml file of: @@ -49,7 +64,7 @@ will output null ``` -### Wildcard matching +## Wildcard matching Given a sample.yml file of: ```yaml a: @@ -66,7 +81,7 @@ apple things ``` -### Aliases +## Aliases Given a sample.yml file of: ```yaml a: &cat @@ -82,7 +97,7 @@ will output *cat ``` -### Traversing aliases with splat +## Traversing aliases with splat Given a sample.yml file of: ```yaml a: &cat @@ -98,7 +113,7 @@ will output frog ``` -### Traversing aliases explicitly +## Traversing aliases explicitly Given a sample.yml file of: ```yaml a: &cat @@ -114,7 +129,7 @@ will output frog ``` -### Traversing arrays by index +## Traversing arrays by index Given a sample.yml file of: ```yaml - 1 @@ -130,7 +145,7 @@ will output 1 ``` -### Maps with numeric keys +## Maps with numeric keys Given a sample.yml file of: ```yaml 2: cat @@ -144,7 +159,7 @@ will output cat ``` -### Maps with non existing numeric keys +## Maps with non existing numeric keys Given a sample.yml file of: ```yaml a: b @@ -158,7 +173,7 @@ will output null ``` -### Traversing merge anchors +## Traversing merge anchors Given a sample.yml file of: ```yaml foo: &foo @@ -189,7 +204,7 @@ will output foo_a ``` -### Traversing merge anchors with override +## Traversing merge anchors with override Given a sample.yml file of: ```yaml foo: &foo @@ -220,7 +235,7 @@ will output foo_c ``` -### Traversing merge anchors with local override +## Traversing merge anchors with local override Given a sample.yml file of: ```yaml foo: &foo @@ -251,7 +266,7 @@ will output foobar_thing ``` -### Splatting merge anchors +## Splatting merge anchors Given a sample.yml file of: ```yaml foo: &foo @@ -284,7 +299,7 @@ foo_a foobar_thing ``` -### Traversing merge anchor lists +## Traversing merge anchor lists Note that the later merge anchors override previous Given a sample.yml file of: @@ -317,7 +332,7 @@ will output bar_thing ``` -### Splatting merge anchor lists +## Splatting merge anchor lists Given a sample.yml file of: ```yaml foo: &foo diff --git a/pkg/yqlib/doc/Union Operator.md b/pkg/yqlib/doc/Union.md similarity index 83% rename from pkg/yqlib/doc/Union Operator.md rename to pkg/yqlib/doc/Union.md index e8975de3..e01782fe 100644 --- a/pkg/yqlib/doc/Union Operator.md +++ b/pkg/yqlib/doc/Union.md @@ -1,6 +1,5 @@ This operator is used to combine different results together. -## Examples -### Combine scalars +## Combine scalars Running ```bash yq eval --null-input '1, true, "cat"' @@ -12,7 +11,7 @@ true cat ``` -### Combine selected paths +## Combine selected paths Given a sample.yml file of: ```yaml a: fieldA diff --git a/pkg/yqlib/doc/headers/Assign Operator.md b/pkg/yqlib/doc/headers/Assign.md similarity index 100% rename from pkg/yqlib/doc/headers/Assign Operator.md rename to pkg/yqlib/doc/headers/Assign.md diff --git a/pkg/yqlib/doc/headers/Boolean Operators.md b/pkg/yqlib/doc/headers/Boolean Operators.md index 15be7b7f..69c46bda 100644 --- a/pkg/yqlib/doc/headers/Boolean Operators.md +++ b/pkg/yqlib/doc/headers/Boolean Operators.md @@ -1 +1 @@ -The `or` and `and` operators take two parameters and return a boolean result. These are most commonly used with the `select` operator to filter particular nodes. \ No newline at end of file +The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Comments Operator.md b/pkg/yqlib/doc/headers/Comment Operators.md similarity index 100% rename from pkg/yqlib/doc/headers/Comments Operator.md rename to pkg/yqlib/doc/headers/Comment Operators.md diff --git a/pkg/yqlib/doc/headers/Delete Operator.md b/pkg/yqlib/doc/headers/Delete.md similarity index 100% rename from pkg/yqlib/doc/headers/Delete Operator.md rename to pkg/yqlib/doc/headers/Delete.md diff --git a/pkg/yqlib/doc/headers/Document Index.md b/pkg/yqlib/doc/headers/Document Index.md new file mode 100644 index 00000000..4430ab14 --- /dev/null +++ b/pkg/yqlib/doc/headers/Document Index.md @@ -0,0 +1 @@ +Use the `documentIndex` operator to select nodes of a particular document. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Equals Operator.md b/pkg/yqlib/doc/headers/Equals.md similarity index 92% rename from pkg/yqlib/doc/headers/Equals Operator.md rename to pkg/yqlib/doc/headers/Equals.md index a60d8dfa..5e68bc50 100644 --- a/pkg/yqlib/doc/headers/Equals Operator.md +++ b/pkg/yqlib/doc/headers/Equals.md @@ -1,5 +1,3 @@ -## Equals Operator - This is a boolean operator that will return ```true``` if the LHS is equal to the RHS and ``false`` otherwise. ``` diff --git a/pkg/yqlib/doc/headers/Explode Operator.md b/pkg/yqlib/doc/headers/Explode.md similarity index 100% rename from pkg/yqlib/doc/headers/Explode Operator.md rename to pkg/yqlib/doc/headers/Explode.md diff --git a/pkg/yqlib/doc/headers/File Operators.md b/pkg/yqlib/doc/headers/File Operators.md index 2efc91d8..394a0bc4 100644 --- a/pkg/yqlib/doc/headers/File Operators.md +++ b/pkg/yqlib/doc/headers/File Operators.md @@ -1,5 +1,7 @@ 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). +## Merging files +Note the use of eval-all to ensure all documents are loaded into memory. ```bash yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml ``` \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Multiply Operator.md b/pkg/yqlib/doc/headers/Multiply.md similarity index 69% rename from pkg/yqlib/doc/headers/Multiply Operator.md rename to pkg/yqlib/doc/headers/Multiply.md index 3597af37..62c73302 100644 --- a/pkg/yqlib/doc/headers/Multiply Operator.md +++ b/pkg/yqlib/doc/headers/Multiply.md @@ -2,4 +2,11 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). -Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. \ No newline at end of file +Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. + +## Merging files +Note the use of eval-all to ensure all documents are loaded into memory. + +```bash +yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml +``` diff --git a/pkg/yqlib/doc/headers/Not Operator.md b/pkg/yqlib/doc/headers/Not Operator.md deleted file mode 100644 index 87022061..00000000 --- a/pkg/yqlib/doc/headers/Not Operator.md +++ /dev/null @@ -1 +0,0 @@ -This is a boolean operator and will return `true` when given a `false` value (including null), and `false` otherwise. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Path Operator.md b/pkg/yqlib/doc/headers/Path Operator.md deleted file mode 100644 index a731d40e..00000000 --- a/pkg/yqlib/doc/headers/Path Operator.md +++ /dev/null @@ -1 +0,0 @@ -The path operator can be used to find 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. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Path.md b/pkg/yqlib/doc/headers/Path.md new file mode 100644 index 00000000..fdb4ec0b --- /dev/null +++ b/pkg/yqlib/doc/headers/Path.md @@ -0,0 +1 @@ +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. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Recursive Descent Operator.md b/pkg/yqlib/doc/headers/Recursive Descent.md similarity index 100% rename from pkg/yqlib/doc/headers/Recursive Descent Operator.md rename to pkg/yqlib/doc/headers/Recursive Descent.md diff --git a/pkg/yqlib/doc/headers/Select Operator.md b/pkg/yqlib/doc/headers/Select.md similarity index 100% rename from pkg/yqlib/doc/headers/Select Operator.md rename to pkg/yqlib/doc/headers/Select.md diff --git a/pkg/yqlib/doc/headers/Style Operator.md b/pkg/yqlib/doc/headers/Style.md similarity index 100% rename from pkg/yqlib/doc/headers/Style Operator.md rename to pkg/yqlib/doc/headers/Style.md diff --git a/pkg/yqlib/doc/headers/Tag Operator.md b/pkg/yqlib/doc/headers/Tag Operator.md deleted file mode 100644 index 1243de10..00000000 --- a/pkg/yqlib/doc/headers/Tag Operator.md +++ /dev/null @@ -1 +0,0 @@ -The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`). \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Tag.md b/pkg/yqlib/doc/headers/Tag.md new file mode 100644 index 00000000..0d329fc1 --- /dev/null +++ b/pkg/yqlib/doc/headers/Tag.md @@ -0,0 +1 @@ +The tag operator can be used to get or set the tag of nodes (e.g. `!!str`, `!!int`, `!!bool`). \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Traverse Operator.md b/pkg/yqlib/doc/headers/Traverse Operator.md deleted file mode 100644 index ff02606c..00000000 --- a/pkg/yqlib/doc/headers/Traverse Operator.md +++ /dev/null @@ -1 +0,0 @@ -This is the simples (and perhaps most used) operator, it is used to navigate deeply into yaml structurse. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Traverse.md b/pkg/yqlib/doc/headers/Traverse.md new file mode 100644 index 00000000..d69c3078 --- /dev/null +++ b/pkg/yqlib/doc/headers/Traverse.md @@ -0,0 +1 @@ +This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Union Operator.md b/pkg/yqlib/doc/headers/Union.md similarity index 100% rename from pkg/yqlib/doc/headers/Union Operator.md rename to pkg/yqlib/doc/headers/Union.md diff --git a/pkg/yqlib/operator_assign_test.go b/pkg/yqlib/operator_assign_test.go index c3d50bb4..82e928d9 100644 --- a/pkg/yqlib/operator_assign_test.go +++ b/pkg/yqlib/operator_assign_test.go @@ -87,17 +87,19 @@ var assignOperatorScenarios = []expressionScenario{ }, }, { - description: "Update empty object", - document: `{}`, - expression: `.a.b |= "bogs"`, + description: "Update empty object", + dontFormatInputForDoc: true, + document: `{}`, + expression: `.a.b |= "bogs"`, expected: []string{ "D0, P[], (doc)::{a: {b: bogs}}\n", }, }, { - description: "Update empty object and array", - document: `{}`, - expression: `.a.b[0] |= "bogs"`, + description: "Update empty object and array", + dontFormatInputForDoc: true, + document: `{}`, + expression: `.a.b[0] |= "bogs"`, expected: []string{ "D0, P[], (doc)::{a: {b: [bogs]}}\n", }, @@ -116,5 +118,5 @@ func TestAssignOperatorScenarios(t *testing.T) { for _, tt := range assignOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Assign Operator", assignOperatorScenarios) + documentScenarios(t, "Assign", assignOperatorScenarios) } diff --git a/pkg/yqlib/operator_booleans.go b/pkg/yqlib/operator_booleans.go index ee7f6931..74c32cea 100644 --- a/pkg/yqlib/operator_booleans.go +++ b/pkg/yqlib/operator_booleans.go @@ -94,3 +94,20 @@ func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT return b1 && b2 }) } + +func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("-- notOperation") + var results = list.New() + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debug("notOperation checking %v", candidate) + truthy, errDecoding := isTruthy(candidate) + if errDecoding != nil { + return nil, errDecoding + } + result := createBooleanCandidate(candidate, !truthy) + results.PushBack(result) + } + return results, nil +} diff --git a/pkg/yqlib/operator_booleans_test.go b/pkg/yqlib/operator_booleans_test.go index b21c4050..ba2b6aa2 100644 --- a/pkg/yqlib/operator_booleans_test.go +++ b/pkg/yqlib/operator_booleans_test.go @@ -45,6 +45,56 @@ var booleanOperatorScenarios = []expressionScenario{ "D0, P[b], (!!bool)::true\n", }, }, + { + description: "Not true is false", + expression: `true | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + description: "Not false is true", + expression: `false | not`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, + { + description: "String values considered to be true", + expression: `"cat" | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + description: "Empty string value considered to be true", + expression: `"" | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + description: "Numbers are considered to be true", + expression: `1 | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + description: "Zero is considered to be true", + expression: `0 | not`, + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + + description: "Null is considered to be false", + expression: `~ | not`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, } func TestBooleanOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/operator_collect_object.go b/pkg/yqlib/operator_collect_object.go index 54eda111..298880c9 100644 --- a/pkg/yqlib/operator_collect_object.go +++ b/pkg/yqlib/operator_collect_object.go @@ -2,6 +2,8 @@ package yqlib import ( "container/list" + + yaml "gopkg.in/yaml.v3" ) /* @@ -19,7 +21,9 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode * log.Debugf("-- collectObjectOperation") if matchMap.Len() == 0 { - return list.New(), nil + node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Value: "{}"} + candidate := &CandidateNode{Node: node} + return nodeToMap(candidate), nil } first := matchMap.Front().Value.(*CandidateNode) var rotated []*list.List = make([]*list.List, len(first.Node.Content)) diff --git a/pkg/yqlib/operator_collect_object_test.go b/pkg/yqlib/operator_collect_object_test.go index d03ede2a..4682789f 100644 --- a/pkg/yqlib/operator_collect_object_test.go +++ b/pkg/yqlib/operator_collect_object_test.go @@ -9,7 +9,9 @@ var collectObjectOperatorScenarios = []expressionScenario{ description: `Collect empty object`, document: ``, expression: `{}`, - expected: []string{}, + expected: []string{ + "D0, P[], (!!map)::{}\n", + }, }, { description: `Wrap (prefix) existing object`, diff --git a/pkg/yqlib/operator_comments_test.go b/pkg/yqlib/operator_comments_test.go index 46a16952..7405624f 100644 --- a/pkg/yqlib/operator_comments_test.go +++ b/pkg/yqlib/operator_comments_test.go @@ -75,5 +75,5 @@ func TestCommentOperatorScenarios(t *testing.T) { for _, tt := range commentOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Comments Operator", commentOperatorScenarios) + documentScenarios(t, "Comment Operators", commentOperatorScenarios) } diff --git a/pkg/yqlib/operator_delete_test.go b/pkg/yqlib/operator_delete_test.go index 77e00184..43e02d15 100644 --- a/pkg/yqlib/operator_delete_test.go +++ b/pkg/yqlib/operator_delete_test.go @@ -43,5 +43,5 @@ func TestDeleteOperatorScenarios(t *testing.T) { for _, tt := range deleteOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Delete Operator", deleteOperatorScenarios) + documentScenarios(t, "Delete", deleteOperatorScenarios) } diff --git a/pkg/yqlib/document_index_operator.go b/pkg/yqlib/operator_document_index.go similarity index 100% rename from pkg/yqlib/document_index_operator.go rename to pkg/yqlib/operator_document_index.go diff --git a/pkg/yqlib/document_index_operator_test.go b/pkg/yqlib/operator_document_index_test.go similarity index 92% rename from pkg/yqlib/document_index_operator_test.go rename to pkg/yqlib/operator_document_index_test.go index 9dd61dd2..0f0a3088 100644 --- a/pkg/yqlib/document_index_operator_test.go +++ b/pkg/yqlib/operator_document_index_test.go @@ -37,5 +37,5 @@ func TestDocumentIndexScenarios(t *testing.T) { for _, tt := range documentIndexScenarios { testScenario(t, &tt) } - documentScenarios(t, "Document Index Operator", documentIndexScenarios) + documentScenarios(t, "Document Index", documentIndexScenarios) } diff --git a/pkg/yqlib/operator_equals_test.go b/pkg/yqlib/operator_equals_test.go index a2d0ea15..ce172503 100644 --- a/pkg/yqlib/operator_equals_test.go +++ b/pkg/yqlib/operator_equals_test.go @@ -54,5 +54,5 @@ func TestEqualOperatorScenarios(t *testing.T) { for _, tt := range equalsOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Equals Operator", equalsOperatorScenarios) + documentScenarios(t, "Equals", equalsOperatorScenarios) } diff --git a/pkg/yqlib/operator_explode_test.go b/pkg/yqlib/operator_explode_test.go index 1bd9fe40..5f3aac99 100644 --- a/pkg/yqlib/operator_explode_test.go +++ b/pkg/yqlib/operator_explode_test.go @@ -86,5 +86,5 @@ func TestExplodeOperatorScenarios(t *testing.T) { for _, tt := range explodeTest { testScenario(t, &tt) } - documentScenarios(t, "Explode Operator", explodeTest) + documentScenarios(t, "Explode", explodeTest) } diff --git a/pkg/yqlib/operator_multilpy.go b/pkg/yqlib/operator_multiply.go similarity index 100% rename from pkg/yqlib/operator_multilpy.go rename to pkg/yqlib/operator_multiply.go diff --git a/pkg/yqlib/operator_multiply_test.go b/pkg/yqlib/operator_multiply_test.go index a12a0220..ea29cf47 100644 --- a/pkg/yqlib/operator_multiply_test.go +++ b/pkg/yqlib/operator_multiply_test.go @@ -130,5 +130,5 @@ func TestMultiplyOperatorScenarios(t *testing.T) { for _, tt := range multiplyOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Multiply Operator", multiplyOperatorScenarios) + documentScenarios(t, "Multiply", multiplyOperatorScenarios) } diff --git a/pkg/yqlib/operator_not.go b/pkg/yqlib/operator_not.go deleted file mode 100644 index 3ae7e970..00000000 --- a/pkg/yqlib/operator_not.go +++ /dev/null @@ -1,20 +0,0 @@ -package yqlib - -import "container/list" - -func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { - log.Debugf("-- notOperation") - var results = list.New() - - for el := matchMap.Front(); el != nil; el = el.Next() { - candidate := el.Value.(*CandidateNode) - log.Debug("notOperation checking %v", candidate) - truthy, errDecoding := isTruthy(candidate) - if errDecoding != nil { - return nil, errDecoding - } - result := createBooleanCandidate(candidate, !truthy) - results.PushBack(result) - } - return results, nil -} diff --git a/pkg/yqlib/operator_not_test.go b/pkg/yqlib/operator_not_test.go deleted file mode 100644 index 3bac2087..00000000 --- a/pkg/yqlib/operator_not_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package yqlib - -import ( - "testing" -) - -var notOperatorScenarios = []expressionScenario{ - { - description: "Not true is false", - expression: `true | not`, - expected: []string{ - "D0, P[], (!!bool)::false\n", - }, - }, - { - description: "Not false is true", - expression: `false | not`, - expected: []string{ - "D0, P[], (!!bool)::true\n", - }, - }, - { - description: "String values considered to be true", - expression: `"cat" | not`, - expected: []string{ - "D0, P[], (!!bool)::false\n", - }, - }, - { - description: "Empty string value considered to be true", - expression: `"" | not`, - expected: []string{ - "D0, P[], (!!bool)::false\n", - }, - }, - { - description: "Numbers are considered to be true", - expression: `1 | not`, - expected: []string{ - "D0, P[], (!!bool)::false\n", - }, - }, - { - description: "Zero is considered to be true", - expression: `0 | not`, - expected: []string{ - "D0, P[], (!!bool)::false\n", - }, - }, - { - - description: "Null is considered to be false", - expression: `~ | not`, - expected: []string{ - "D0, P[], (!!bool)::true\n", - }, - }, -} - -func TestNotOperatorScenarios(t *testing.T) { - for _, tt := range notOperatorScenarios { - testScenario(t, &tt) - } - documentScenarios(t, "Not Operator", notOperatorScenarios) -} diff --git a/pkg/yqlib/operator_path_test.go b/pkg/yqlib/operator_path_test.go index 5341f7af..8102faf3 100644 --- a/pkg/yqlib/operator_path_test.go +++ b/pkg/yqlib/operator_path_test.go @@ -41,5 +41,5 @@ func TestPathOperatorsScenarios(t *testing.T) { for _, tt := range pathOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Path Operator", pathOperatorScenarios) + documentScenarios(t, "Path", pathOperatorScenarios) } diff --git a/pkg/yqlib/operator_recursive_descent_test.go b/pkg/yqlib/operator_recursive_descent_test.go index 0de62417..e08a98ed 100644 --- a/pkg/yqlib/operator_recursive_descent_test.go +++ b/pkg/yqlib/operator_recursive_descent_test.go @@ -23,9 +23,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { - description: "Map", - document: `{a: {b: apple}}`, - expression: `..`, + skipDoc: true, + document: `{a: {b: apple}}`, + expression: `..`, expected: []string{ "D0, P[], (!!map)::{a: {b: apple}}\n", "D0, P[a], (!!map)::{b: apple}\n", @@ -33,9 +33,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { - description: "Array", - document: `[1,2,3]`, - expression: `..`, + skipDoc: true, + document: `[1,2,3]`, + expression: `..`, expected: []string{ "D0, P[], (!!seq)::[1, 2, 3]\n", "D0, P[0], (!!int)::1\n", @@ -44,9 +44,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ }, }, { - description: "Array of maps", - document: `[{a: cat},2,true]`, - expression: `..`, + skipDoc: true, + document: `[{a: cat},2,true]`, + expression: `..`, expected: []string{ "D0, P[], (!!seq)::[{a: cat}, 2, true]\n", "D0, P[0], (!!map)::{a: cat}\n", @@ -58,23 +58,17 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ { description: "Aliases are not traversed", document: `{a: &cat {c: frog}, b: *cat}`, - expression: `..`, + expression: `[..]`, expected: []string{ - "D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n", - "D0, P[a], (!!map)::&cat {c: frog}\n", - "D0, P[a c], (!!str)::frog\n", - "D0, P[b], (alias)::*cat\n", + "D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n", }, }, { description: "Merge docs are not traversed", document: mergeDocSample, - expression: `.foobar | ..`, + expression: `.foobar | [..]`, expected: []string{ - "D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n", - "D0, P[foobar c], (!!str)::foobar_c\n", - "D0, P[foobar <<], (alias)::*foo\n", - "D0, P[foobar thing], (!!str)::foobar_thing\n", + "D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n", }, }, { @@ -96,5 +90,5 @@ func TestRecursiveDescentOperatorScenarios(t *testing.T) { for _, tt := range recursiveDescentOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Recursive Descent Operator", recursiveDescentOperatorScenarios) + documentScenarios(t, "Recursive Descent", recursiveDescentOperatorScenarios) } diff --git a/pkg/yqlib/operator_select_test.go b/pkg/yqlib/operator_select_test.go index 2d3f0966..3b495ee4 100644 --- a/pkg/yqlib/operator_select_test.go +++ b/pkg/yqlib/operator_select_test.go @@ -59,5 +59,5 @@ func TestSelectOperatorScenarios(t *testing.T) { for _, tt := range selectOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Select Operator", selectOperatorScenarios) + documentScenarios(t, "Select", selectOperatorScenarios) } diff --git a/pkg/yqlib/operator_style_test.go b/pkg/yqlib/operator_style_test.go index ff84df12..d98f48a0 100644 --- a/pkg/yqlib/operator_style_test.go +++ b/pkg/yqlib/operator_style_test.go @@ -70,9 +70,10 @@ e: >- }, }, { - description: "Set empty (default) quote style", - document: `{a: cat, b: 5, c: 3.2, e: true}`, - expression: `.. style=""`, + description: "Pretty print", + subdescription: "Set empty (default) quote style", + document: `{a: cat, b: 5, c: 3.2, e: true}`, + expression: `.. style=""`, expected: []string{ "D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n", }, @@ -86,9 +87,10 @@ e: >- }, }, { - description: "Read style", - document: `{a: "cat", b: 'thing'}`, - expression: `.. | style`, + description: "Read style", + document: `{a: "cat", b: 'thing'}`, + dontFormatInputForDoc: true, + expression: `.. | style`, expected: []string{ "D0, P[], (!!str)::flow\n", "D0, P[a], (!!str)::double\n", @@ -110,5 +112,5 @@ func TestStyleOperatorScenarios(t *testing.T) { for _, tt := range styleOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Style Operator", styleOperatorScenarios) + documentScenarios(t, "Style", styleOperatorScenarios) } diff --git a/pkg/yqlib/operator_tag_test.go b/pkg/yqlib/operator_tag_test.go index 9d4878dc..e918c73f 100644 --- a/pkg/yqlib/operator_tag_test.go +++ b/pkg/yqlib/operator_tag_test.go @@ -32,5 +32,5 @@ func TestTagOperatorScenarios(t *testing.T) { for _, tt := range tagOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Tag Operator", tagOperatorScenarios) + documentScenarios(t, "Tag", tagOperatorScenarios) } diff --git a/pkg/yqlib/operator_traverse_path_test.go b/pkg/yqlib/operator_traverse_path_test.go index 16809ea1..7c98f8aa 100644 --- a/pkg/yqlib/operator_traverse_path_test.go +++ b/pkg/yqlib/operator_traverse_path_test.go @@ -45,6 +45,15 @@ var traversePathOperatorScenarios = []expressionScenario{ "D0, P[1], (!!map)::{c: banana}\n", }, }, + { + description: "Special characters", + subdescription: "Use quotes around path elements with special characters", + document: `{"{}": frog}`, + expression: `."{}"`, + expected: []string{ + "D0, P[{}], (!!str)::frog\n", + }, + }, { description: "Children don't exist", subdescription: "Nodes are added dynamically while traversing", @@ -271,5 +280,5 @@ func TestTraversePathOperatorScenarios(t *testing.T) { for _, tt := range traversePathOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Traverse Operator", traversePathOperatorScenarios) + documentScenarios(t, "Traverse", traversePathOperatorScenarios) } diff --git a/pkg/yqlib/operator_union_test.go b/pkg/yqlib/operator_union_test.go index 578dd12a..41719296 100644 --- a/pkg/yqlib/operator_union_test.go +++ b/pkg/yqlib/operator_union_test.go @@ -29,5 +29,5 @@ func TestUnionOperatorScenarios(t *testing.T) { for _, tt := range unionOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Union Operator", unionOperatorScenarios) + documentScenarios(t, "Union", unionOperatorScenarios) } diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 6c929f6a..4f38aa91 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -114,17 +114,13 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari } w := bufio.NewWriter(f) + writeOrPanic(w, "\n") - writeOrPanic(w, "\n## Examples\n") - - for index, s := range scenarios { + for _, s := range scenarios { if !s.skipDoc { - if s.description != "" { - writeOrPanic(w, fmt.Sprintf("### %v\n", s.description)) - } else { - writeOrPanic(w, fmt.Sprintf("### Example %v\n", index)) - } + writeOrPanic(w, fmt.Sprintf("## %v\n", s.description)) + if s.subdescription != "" { writeOrPanic(w, s.subdescription) writeOrPanic(w, "\n\n") @@ -132,7 +128,7 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari formattedDoc := "" if s.document != "" { if s.dontFormatInputForDoc { - formattedDoc = s.document + formattedDoc = s.document + "\n" } else { formattedDoc = formatYaml(s.document) } From 3f04a1b52ea0d312661ab6fc4d9c91c0904f4d82 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Nov 2020 13:50:32 +1100 Subject: [PATCH 101/129] Fixed empty array op --- pkg/yqlib/doc/Assign.md | 2 +- pkg/yqlib/doc/Collect into Array.md | 1 + pkg/yqlib/doc/Collect into Object.md | 4 +- pkg/yqlib/doc/Document Index Operator.md | 53 ----------------------- pkg/yqlib/doc/Select.md | 2 +- pkg/yqlib/operator_assign_test.go | 2 +- pkg/yqlib/operator_collect.go | 8 +++- pkg/yqlib/operator_collect_object_test.go | 6 +-- pkg/yqlib/operator_collect_test.go | 6 ++- pkg/yqlib/operator_create_map_test.go | 6 +-- pkg/yqlib/operator_select_test.go | 6 +-- pkg/yqlib/operators_test.go | 2 +- pkg/yqlib/path_parse_test.go | 14 +++++- pkg/yqlib/path_postfix.go | 2 +- pkg/yqlib/path_tokeniser.go | 13 +++--- 15 files changed, 45 insertions(+), 82 deletions(-) delete mode 100644 pkg/yqlib/doc/Document Index Operator.md diff --git a/pkg/yqlib/doc/Assign.md b/pkg/yqlib/doc/Assign.md index a783587d..c5f1ce61 100644 --- a/pkg/yqlib/doc/Assign.md +++ b/pkg/yqlib/doc/Assign.md @@ -100,7 +100,7 @@ a: ``` then ```bash -yq eval '.a[] | select(. == "apple") |= "frog"' sample.yml +yq eval '.a.[] | select(. == "apple") |= "frog"' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/doc/Collect into Array.md b/pkg/yqlib/doc/Collect into Array.md index d06927e6..8195ee9a 100644 --- a/pkg/yqlib/doc/Collect into Array.md +++ b/pkg/yqlib/doc/Collect into Array.md @@ -10,6 +10,7 @@ yq eval --null-input '[]' ``` will output ```yaml +[] ``` ## Collect single diff --git a/pkg/yqlib/doc/Collect into Object.md b/pkg/yqlib/doc/Collect into Object.md index ded3288d..ffbc11bf 100644 --- a/pkg/yqlib/doc/Collect into Object.md +++ b/pkg/yqlib/doc/Collect into Object.md @@ -34,7 +34,7 @@ pets: ``` then ```bash -yq eval '{.name: .pets[]}' sample.yml +yq eval '{.name: .pets.[]}' sample.yml ``` will output ```yaml @@ -57,7 +57,7 @@ pets: ``` then ```bash -yq eval '{.name: .pets[]}' sample.yml +yq eval '{.name: .pets.[]}' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/doc/Document Index Operator.md b/pkg/yqlib/doc/Document Index Operator.md deleted file mode 100644 index 30ba1ec6..00000000 --- a/pkg/yqlib/doc/Document Index Operator.md +++ /dev/null @@ -1,53 +0,0 @@ -## Retrieve a document index -Given a sample.yml file of: -```yaml -a: cat ---- -a: frog -``` -then -```bash -yq eval '.a | documentIndex' 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 eval 'select(. | documentIndex == 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 eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml -``` -will output -```yaml -match: cat -doc: 0 -match: frog -doc: 1 -``` - diff --git a/pkg/yqlib/doc/Select.md b/pkg/yqlib/doc/Select.md index 1726853b..0dfe2769 100644 --- a/pkg/yqlib/doc/Select.md +++ b/pkg/yqlib/doc/Select.md @@ -26,7 +26,7 @@ a: ``` then ```bash -yq eval '(.a[] | select(. == "*at")) |= "rabbit"' sample.yml +yq eval '(.a.[] | select(. == "*at")) |= "rabbit"' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/operator_assign_test.go b/pkg/yqlib/operator_assign_test.go index 82e928d9..06251349 100644 --- a/pkg/yqlib/operator_assign_test.go +++ b/pkg/yqlib/operator_assign_test.go @@ -73,7 +73,7 @@ var assignOperatorScenarios = []expressionScenario{ { description: "Update selected results", document: `{a: {b: apple, c: cactus}}`, - expression: `.a[] | select(. == "apple") |= "frog"`, + expression: `.a.[] | select(. == "apple") |= "frog"`, expected: []string{ "D0, P[], (doc)::{a: {b: frog, c: cactus}}\n", }, diff --git a/pkg/yqlib/operator_collect.go b/pkg/yqlib/operator_collect.go index 9eda72ec..ffdfda62 100644 --- a/pkg/yqlib/operator_collect.go +++ b/pkg/yqlib/operator_collect.go @@ -3,12 +3,18 @@ package yqlib import ( "container/list" - "gopkg.in/yaml.v3" + yaml "gopkg.in/yaml.v3" ) func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- collectOperation") + if matchMap.Len() == 0 { + node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"} + candidate := &CandidateNode{Node: node} + return nodeToMap(candidate), nil + } + var results = list.New() node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} diff --git a/pkg/yqlib/operator_collect_object_test.go b/pkg/yqlib/operator_collect_object_test.go index 4682789f..165b0c9c 100644 --- a/pkg/yqlib/operator_collect_object_test.go +++ b/pkg/yqlib/operator_collect_object_test.go @@ -41,7 +41,7 @@ var collectObjectOperatorScenarios = []expressionScenario{ { description: `Using splat to create multiple objects`, document: `{name: Mike, pets: [cat, dog]}`, - expression: `{.name: .pets[]}`, + expression: `{.name: .pets.[]}`, expected: []string{ "D0, P[], (!!map)::Mike: cat\n", "D0, P[], (!!map)::Mike: dog\n", @@ -51,7 +51,7 @@ var collectObjectOperatorScenarios = []expressionScenario{ description: `Working with multiple documents`, dontFormatInputForDoc: false, document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}", - expression: `{.name: .pets[]}`, + expression: `{.name: .pets.[]}`, expected: []string{ "D0, P[], (!!map)::Mike: cat\n", "D0, P[], (!!map)::Mike: dog\n", @@ -62,7 +62,7 @@ var collectObjectOperatorScenarios = []expressionScenario{ { skipDoc: true, document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`, - expression: `{.name: .pets[], "f":.food[]}`, + expression: `{.name: .pets.[], "f":.food.[]}`, expected: []string{ "D0, P[], (!!map)::Mike: cat\nf: hotdog\n", "D0, P[], (!!map)::Mike: cat\nf: burger\n", diff --git a/pkg/yqlib/operator_collect_test.go b/pkg/yqlib/operator_collect_test.go index 26f075d7..cadfef85 100644 --- a/pkg/yqlib/operator_collect_test.go +++ b/pkg/yqlib/operator_collect_test.go @@ -9,7 +9,9 @@ var collectOperatorScenarios = []expressionScenario{ description: "Collect empty", document: ``, expression: `[]`, - expected: []string{}, + expected: []string{ + "D0, P[], (!!seq)::[]\n", + }, }, { description: "Collect single", @@ -52,7 +54,7 @@ var collectOperatorScenarios = []expressionScenario{ }, { document: `a: {b: [1,2,3]}`, - expression: `[.a.b[]]`, + expression: `[.a.b.[]]`, skipDoc: true, expected: []string{ "D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n", diff --git a/pkg/yqlib/operator_create_map_test.go b/pkg/yqlib/operator_create_map_test.go index 291abd0d..6a972467 100644 --- a/pkg/yqlib/operator_create_map_test.go +++ b/pkg/yqlib/operator_create_map_test.go @@ -21,14 +21,14 @@ var createMapOperatorScenarios = []expressionScenario{ }, { document: `{name: Mike, pets: [cat, dog]}`, - expression: `.name: .pets[]`, + expression: `.name: .pets.[]`, expected: []string{ "D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n", }, }, { document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`, - expression: `.name: .pets[], "f":.food[]`, + expression: `.name: .pets.[], "f":.food.[]`, expected: []string{ "D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n", "D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n", @@ -36,7 +36,7 @@ var createMapOperatorScenarios = []expressionScenario{ }, { document: "{name: Mike, pets: [cat, dog], food: [hotdog, burger]}\n---\n{name: Fred, pets: [mouse], food: [pizza, onion, apple]}", - expression: `.name: .pets[], "f":.food[]`, + expression: `.name: .pets.[], "f":.food.[]`, expected: []string{ "D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n- [{Fred: mouse}]\n", "D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n- [{f: pizza}, {f: onion}, {f: apple}]\n", diff --git a/pkg/yqlib/operator_select_test.go b/pkg/yqlib/operator_select_test.go index 3b495ee4..d98805ae 100644 --- a/pkg/yqlib/operator_select_test.go +++ b/pkg/yqlib/operator_select_test.go @@ -23,7 +23,7 @@ var selectOperatorScenarios = []expressionScenario{ { skipDoc: true, document: `a: [cat,goat,dog]`, - expression: `.a[] | select(. == "*at")`, + expression: `.a.[] | select(. == "*at")`, expected: []string{ "D0, P[a 0], (!!str)::cat\n", "D0, P[a 1], (!!str)::goat\n"}, @@ -31,7 +31,7 @@ var selectOperatorScenarios = []expressionScenario{ { description: "Select and update matching values in map", document: `a: { things: cat, bob: goat, horse: dog }`, - expression: `(.a[] | select(. == "*at")) |= "rabbit"`, + expression: `(.a.[] | select(. == "*at")) |= "rabbit"`, expected: []string{ "D0, P[], (doc)::a: {things: rabbit, bob: rabbit, horse: dog}\n", }, @@ -39,7 +39,7 @@ var selectOperatorScenarios = []expressionScenario{ { skipDoc: true, document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`, - expression: `.a[] | select(.include)`, + expression: `.a.[] | select(.include)`, expected: []string{ "D0, P[a things], (!!map)::{include: true}\n", "D0, P[a andMe], (!!map)::{include: fold}\n", diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 4f38aa91..15e52d74 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -29,7 +29,7 @@ func testScenario(t *testing.T, s *expressionScenario) { node, err := treeCreator.ParsePath(s.expression) if err != nil { - t.Error(err) + t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err)) return } inputs := list.New() diff --git a/pkg/yqlib/path_parse_test.go b/pkg/yqlib/path_parse_test.go index b78a4bd7..f8c4decc 100644 --- a/pkg/yqlib/path_parse_test.go +++ b/pkg/yqlib/path_parse_test.go @@ -37,8 +37,18 @@ var pathTests = []struct { // {`.a, .b`, append(make([]interface{}, 0), "a", "OR", "b")}, // {`[.a, .b]`, append(make([]interface{}, 0), "[", "a", "OR", "b", "]")}, // {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")}, - // {`.a[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")}, + // {`.a.[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")}, // {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")}, + // { + // `["cat"]`, + // append(make([]interface{}, 0), "[", "cat (string)", "]"), + // append(make([]interface{}, 0), "cat (string)", "COLLECT", "PIPE"), + // }, + { + `[]`, + append(make([]interface{}, 0), "[", "]"), + append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"), + }, { `d0.a`, append(make([]interface{}, 0), "d0", "PIPE", "a"), @@ -85,7 +95,7 @@ var pathTests = []struct { append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"), }, { - `{.a: .c, .b[]: .f.g[]}`, + `{.a: .c, .b.[]: .f.g.[]}`, append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"), append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"), }, diff --git a/pkg/yqlib/path_postfix.go b/pkg/yqlib/path_postfix.go index 1f360e4d..bac3096e 100644 --- a/pkg/yqlib/path_postfix.go +++ b/pkg/yqlib/path_postfix.go @@ -3,7 +3,7 @@ package yqlib import ( "errors" - "gopkg.in/op/go-logging.v1" + logging "gopkg.in/op/go-logging.v1" ) type PathPostFixer interface { diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 17af2a25..7f510b41 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -33,6 +33,7 @@ type Token struct { func (t *Token) toString() string { if t.TokenType == OperationToken { + log.Debug("toString, its an op") return t.Operation.toString() } else if t.TokenType == OpenBracket { return "(" @@ -58,13 +59,7 @@ func pathToken(wrapped bool) lex.Action { if wrapped { value = unwrap(value) } - op := &Operation{OperationType: TraversePath, Value: value, StringValue: value} - return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil - } -} - -func literalPathToken(value string) lex.Action { - return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + log.Debug("PathToken %v", value) op := &Operation{OperationType: TraversePath, Value: value, StringValue: value} return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil } @@ -78,6 +73,7 @@ func documentToken() lex.Action { if errParsingInt != nil { return nil, errParsingInt } + log.Debug("documentToken %v", string(m.Bytes)) op := &Operation{OperationType: DocumentFilter, Value: number, StringValue: numberString} return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil } @@ -93,6 +89,7 @@ func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.A func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + log.Debug("opTokenWithPrefs %v", string(m.Bytes)) value := string(m.Bytes) op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences} var assign *Operation @@ -187,7 +184,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\(`), literalToken(OpenBracket, false)) lexer.Add([]byte(`\)`), literalToken(CloseBracket, true)) - lexer.Add([]byte(`\.?\[\]`), literalPathToken("[]")) + lexer.Add([]byte(`\.\[\]`), pathToken(false)) lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent)) lexer.Add([]byte(`,`), opToken(Union)) From 3d6a231722cf141b7ae73eda0cd97c3a5b440170 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 24 Nov 2020 11:38:39 +1100 Subject: [PATCH 102/129] Added has operator --- pkg/yqlib/doc/Has.md | 44 ++++++++++++++++++++++++++++ pkg/yqlib/doc/headers/Has.md | 1 + pkg/yqlib/lib.go | 1 + pkg/yqlib/operator_has.go | 53 ++++++++++++++++++++++++++++++++++ pkg/yqlib/operator_has_test.go | 48 ++++++++++++++++++++++++++++++ pkg/yqlib/path_tokeniser.go | 1 + 6 files changed, 148 insertions(+) create mode 100644 pkg/yqlib/doc/Has.md create mode 100644 pkg/yqlib/doc/headers/Has.md create mode 100644 pkg/yqlib/operator_has.go create mode 100644 pkg/yqlib/operator_has_test.go diff --git a/pkg/yqlib/doc/Has.md b/pkg/yqlib/doc/Has.md new file mode 100644 index 00000000..1f25009c --- /dev/null +++ b/pkg/yqlib/doc/Has.md @@ -0,0 +1,44 @@ +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 eval '.[] | has("a")' sample.yml +``` +will output +```yaml +true +true +true +false +``` + +## Has array index +Given a sample.yml file of: +```yaml +- [] +- [1] +- [1, 2] +- [1, null] +- [1, 2, 3] + +``` +then +```bash +yq eval '.[] | has(1)' sample.yml +``` +will output +```yaml +false +false +true +true +true +``` + diff --git a/pkg/yqlib/doc/headers/Has.md b/pkg/yqlib/doc/headers/Has.md new file mode 100644 index 00000000..df898780 --- /dev/null +++ b/pkg/yqlib/doc/headers/Has.md @@ -0,0 +1 @@ +This is operation that returns true if the key exists in a map (or index in an array), false otherwise. \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 837ae4f8..59fdace9 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -66,6 +66,7 @@ var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator} var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator} var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator} +var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator} var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator} // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} diff --git a/pkg/yqlib/operator_has.go b/pkg/yqlib/operator_has.go new file mode 100644 index 00000000..91e7a8e5 --- /dev/null +++ b/pkg/yqlib/operator_has.go @@ -0,0 +1,53 @@ +package yqlib + +import ( + "container/list" + "strconv" + + yaml "gopkg.in/yaml.v3" +) + +func HasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + + log.Debugf("-- hasOperation") + var results = list.New() + + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + wanted := rhs.Front().Value.(*CandidateNode).Node + wantedKey := wanted.Value + + if err != nil { + return nil, err + } + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + + // grab the first value + var contents = candidate.Node.Content + switch candidate.Node.Kind { + case yaml.MappingNode: + candidateHasKey := false + for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 { + key := contents[index] + if key.Value == wantedKey { + candidateHasKey = true + } + } + results.PushBack(createBooleanCandidate(candidate, candidateHasKey)) + case yaml.SequenceNode: + candidateHasKey := false + if wanted.Tag == "!!int" { + var number, errParsingInt = strconv.ParseInt(wantedKey, 10, 64) // nolint + if errParsingInt != nil { + return nil, errParsingInt + } + candidateHasKey = int64(len(contents)) > number + } + results.PushBack(createBooleanCandidate(candidate, candidateHasKey)) + default: + results.PushBack(createBooleanCandidate(candidate, false)) + } + } + return results, nil +} diff --git a/pkg/yqlib/operator_has_test.go b/pkg/yqlib/operator_has_test.go new file mode 100644 index 00000000..dc0c3d2d --- /dev/null +++ b/pkg/yqlib/operator_has_test.go @@ -0,0 +1,48 @@ +package yqlib + +import ( + "testing" +) + +var hasOperatorScenarios = []expressionScenario{ + { + description: "Has map key", + document: `- a: "yes" +- a: ~ +- a: +- b: nope +`, + expression: `.[] | has("a")`, + expected: []string{ + "D0, P[0], (!!bool)::true\n", + "D0, P[1], (!!bool)::true\n", + "D0, P[2], (!!bool)::true\n", + "D0, P[3], (!!bool)::false\n", + }, + }, + { + dontFormatInputForDoc: true, + description: "Has array index", + document: `- [] +- [1] +- [1, 2] +- [1, null] +- [1, 2, 3] +`, + expression: `.[] | has(1)`, + expected: []string{ + "D0, P[0], (!!bool)::false\n", + "D0, P[1], (!!bool)::false\n", + "D0, P[2], (!!bool)::true\n", + "D0, P[3], (!!bool)::true\n", + "D0, P[4], (!!bool)::true\n", + }, + }, +} + +func TestHasOperatorScenarios(t *testing.T) { + for _, tt := range hasOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Has", hasOperatorScenarios) +} diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 7f510b41..9d5d3b99 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -191,6 +191,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`:\s*`), opToken(CreateMap)) lexer.Add([]byte(`length`), opToken(Length)) lexer.Add([]byte(`select`), opToken(Select)) + lexer.Add([]byte(`has`), opToken(Has)) lexer.Add([]byte(`explode`), opToken(Explode)) lexer.Add([]byte(`or`), opToken(Or)) lexer.Add([]byte(`and`), opToken(And)) From 1ce30b25dc4d3c6966dbbc30b384956b8aa4c8d3 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 24 Nov 2020 13:07:19 +1100 Subject: [PATCH 103/129] Add operator! --- pkg/yqlib/doc/Add.md | 107 +++++++++++++++++++++++++++++++++ pkg/yqlib/doc/headers/Add.md | 4 ++ pkg/yqlib/lib.go | 3 +- pkg/yqlib/operator_add.go | 69 +++++++++++++++++++++ pkg/yqlib/operator_add_test.go | 55 +++++++++++++++++ pkg/yqlib/path_tokeniser.go | 1 + 6 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 pkg/yqlib/doc/Add.md create mode 100644 pkg/yqlib/doc/headers/Add.md create mode 100644 pkg/yqlib/operator_add.go create mode 100644 pkg/yqlib/operator_add_test.go diff --git a/pkg/yqlib/doc/Add.md b/pkg/yqlib/doc/Add.md new file mode 100644 index 00000000..6eee9cc0 --- /dev/null +++ b/pkg/yqlib/doc/Add.md @@ -0,0 +1,107 @@ +Add behaves differently according to the type of the LHS: +- arrays: concatenate +- number scalars: arithmetic addition (soon) +- string scalars: concatenate (soon) +## Concatenate arrays +Given a sample.yml file of: +```yaml +a: + - 1 + - 2 +b: + - 3 + - 4 +``` +then +```bash +yq eval '.a + .b' sample.yml +``` +will output +```yaml +- 1 +- 2 +- 3 +- 4 +``` + +## Concatenate null to array +Given a sample.yml file of: +```yaml +a: + - 1 + - 2 +``` +then +```bash +yq eval '.a + null' sample.yml +``` +will output +```yaml +- 1 +- 2 +``` + +## Add object to array +Given a sample.yml file of: +```yaml +a: + - 1 + - 2 +c: + cat: meow +``` +then +```bash +yq eval '.a + .c' sample.yml +``` +will output +```yaml +- 1 +- 2 +- cat: meow +``` + +## Add string to array +Given a sample.yml file of: +```yaml +a: + - 1 + - 2 +``` +then +```bash +yq eval '.a + "hello"' sample.yml +``` +will output +```yaml +- 1 +- 2 +- hello +``` + +## Update array (append) +Given a sample.yml file of: +```yaml +a: + - 1 + - 2 +b: + - 3 + - 4 +``` +then +```bash +yq eval '.a = .a + .b' sample.yml +``` +will output +```yaml +a: + - 1 + - 2 + - 3 + - 4 +b: + - 3 + - 4 +``` + diff --git a/pkg/yqlib/doc/headers/Add.md b/pkg/yqlib/doc/headers/Add.md new file mode 100644 index 00000000..dd9f2a38 --- /dev/null +++ b/pkg/yqlib/doc/headers/Add.md @@ -0,0 +1,4 @@ +Add behaves differently according to the type of the LHS: +- arrays: concatenate +- number scalars: arithmetic addition (soon) +- string scalars: concatenate (soon) \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 59fdace9..e9a393b8 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -36,7 +36,8 @@ var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 4 var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator} var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator} -var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} +var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator} +var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator} var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator} diff --git a/pkg/yqlib/operator_add.go b/pkg/yqlib/operator_add.go new file mode 100644 index 00000000..fad85ecd --- /dev/null +++ b/pkg/yqlib/operator_add.go @@ -0,0 +1,69 @@ +package yqlib + +import ( + "fmt" + + "container/list" + + yaml "gopkg.in/yaml.v3" +) + +func toNodes(candidates *list.List) []*yaml.Node { + + if candidates.Len() == 0 { + return []*yaml.Node{} + } + candidate := candidates.Front().Value.(*CandidateNode) + + if candidate.Node.Tag == "!!null" { + return []*yaml.Node{} + } + + switch candidate.Node.Kind { + case yaml.SequenceNode: + return candidate.Node.Content + default: + return []*yaml.Node{candidate.Node} + } + +} + +func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("Add operator") + var results = list.New() + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + + if err != nil { + return nil, err + } + + for el := lhs.Front(); el != nil; el = el.Next() { + lhsCandidate := el.Value.(*CandidateNode) + lhsNode := UnwrapDoc(lhsCandidate.Node) + + var newBlank = &CandidateNode{ + Path: lhsCandidate.Path, + Document: lhsCandidate.Document, + Filename: lhsCandidate.Filename, + Node: &yaml.Node{}, + } + + switch lhsNode.Kind { + case yaml.MappingNode: + return nil, fmt.Errorf("Maps not yet supported for addition") + case yaml.SequenceNode: + newBlank.Node.Kind = yaml.SequenceNode + newBlank.Node.Style = lhsNode.Style + newBlank.Node.Tag = "!!seq" + newBlank.Node.Content = append(lhsNode.Content, toNodes(rhs)...) + results.PushBack(newBlank) + case yaml.ScalarNode: + return nil, fmt.Errorf("Scalars not yet supported for addition") + } + } + return results, nil +} diff --git a/pkg/yqlib/operator_add_test.go b/pkg/yqlib/operator_add_test.go new file mode 100644 index 00000000..8f4459ed --- /dev/null +++ b/pkg/yqlib/operator_add_test.go @@ -0,0 +1,55 @@ +package yqlib + +import ( + "testing" +) + +var addOperatorScenarios = []expressionScenario{ + { + description: "Concatenate arrays", + document: `{a: [1,2], b: [3,4]}`, + expression: `.a + .b`, + expected: []string{ + "D0, P[a], (!!seq)::[1, 2, 3, 4]\n", + }, + }, + { + description: "Concatenate null to array", + document: `{a: [1,2]}`, + expression: `.a + null`, + expected: []string{ + "D0, P[a], (!!seq)::[1, 2]\n", + }, + }, + { + description: "Add object to array", + document: `{a: [1,2], c: {cat: meow}}`, + expression: `.a + .c`, + expected: []string{ + "D0, P[a], (!!seq)::[1, 2, {cat: meow}]\n", + }, + }, + { + description: "Add string to array", + document: `{a: [1,2]}`, + expression: `.a + "hello"`, + expected: []string{ + "D0, P[a], (!!seq)::[1, 2, hello]\n", + }, + }, + { + description: "Update array (append)", + document: `{a: [1,2], b: [3,4]}`, + expression: `.a = .a + .b`, + expected: []string{ + "D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n", + }, + }, +} + +func TestAddOperatorScenarios(t *testing.T) { + for _, tt := range addOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Add", addOperatorScenarios) +} diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 9d5d3b99..9d0faba6 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -252,6 +252,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false)) lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true)) lexer.Add([]byte(`\*`), opToken(Multiply)) + lexer.Add([]byte(`\+`), opToken(Add)) err := lexer.Compile() if err != nil { From 0a66bb797dfdf4f57bfbfcfa29ccad98859ac6fc Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 25 Nov 2020 13:32:32 +1100 Subject: [PATCH 104/129] 4 alpha2 --- cmd/version.go | 2 +- snap/snapcraft.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/version.go b/cmd/version.go index 85d92382..e67624c0 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -11,7 +11,7 @@ var ( GitDescribe string // Version is main version number that is being run at the moment. - Version = "4.0.0-alpha1" + Version = "4.0.0-alpha2" // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 53c3c9d3..55daab5b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: yq -version: '4.0.0-alpha1' +version: '4.0.0-alpha2' summary: A lightweight and portable command-line YAML processor description: | The aim of the project is to be the jq or sed of yaml files. From 5205f012482583a180724d60612967ff24a41a6c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 25 Nov 2020 15:01:12 +1100 Subject: [PATCH 105/129] Fixed recursive decent on empty objects/arrays --- pkg/yqlib/doc/Add.md | 1 + pkg/yqlib/doc/headers/Add.md | 2 +- pkg/yqlib/lib.go | 4 +--- pkg/yqlib/operator_multiply_test.go | 7 +++++++ pkg/yqlib/operator_recursive_descent.go | 2 +- pkg/yqlib/operator_recursive_descent_test.go | 16 ++++++++++++++++ 6 files changed, 27 insertions(+), 5 deletions(-) diff --git a/pkg/yqlib/doc/Add.md b/pkg/yqlib/doc/Add.md index 6eee9cc0..82550b40 100644 --- a/pkg/yqlib/doc/Add.md +++ b/pkg/yqlib/doc/Add.md @@ -2,6 +2,7 @@ Add behaves differently according to the type of the LHS: - arrays: concatenate - number scalars: arithmetic addition (soon) - string scalars: concatenate (soon) + ## Concatenate arrays Given a sample.yml file of: ```yaml diff --git a/pkg/yqlib/doc/headers/Add.md b/pkg/yqlib/doc/headers/Add.md index dd9f2a38..f451aa95 100644 --- a/pkg/yqlib/doc/headers/Add.md +++ b/pkg/yqlib/doc/headers/Add.md @@ -1,4 +1,4 @@ Add behaves differently according to the type of the LHS: - arrays: concatenate - number scalars: arithmetic addition (soon) -- string scalars: concatenate (soon) \ No newline at end of file +- string scalars: concatenate (soon) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index e9a393b8..8883d392 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -17,6 +17,7 @@ type OperationType struct { } // operators TODO: +// - cookbook doc for common things // - write in place // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) @@ -70,9 +71,6 @@ var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator} var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator} -// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} -// filters matches if they have the existing path - type Operation struct { OperationType *OperationType Value interface{} diff --git a/pkg/yqlib/operator_multiply_test.go b/pkg/yqlib/operator_multiply_test.go index ea29cf47..d16de6fa 100644 --- a/pkg/yqlib/operator_multiply_test.go +++ b/pkg/yqlib/operator_multiply_test.go @@ -13,6 +13,13 @@ var multiplyOperatorScenarios = []expressionScenario{ "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", }, }, + { + skipDoc: true, + expression: `{} * {"cat":"dog"}`, + expected: []string{ + "D0, P[], (!!map)::cat: dog\n", + }, + }, { skipDoc: true, document: `{a: {also: me}, b: {also: [1]}}`, diff --git a/pkg/yqlib/operator_recursive_descent.go b/pkg/yqlib/operator_recursive_descent.go index 78dd67cc..51e1439c 100644 --- a/pkg/yqlib/operator_recursive_descent.go +++ b/pkg/yqlib/operator_recursive_descent.go @@ -26,7 +26,7 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li log.Debugf("Recursive Decent, added %v", NodeToString(candidate)) results.PushBack(candidate) - if candidate.Node.Kind != yaml.AliasNode { + if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 { children, err := Splat(d, nodeToMap(candidate)) diff --git a/pkg/yqlib/operator_recursive_descent_test.go b/pkg/yqlib/operator_recursive_descent_test.go index e08a98ed..c5c7254a 100644 --- a/pkg/yqlib/operator_recursive_descent_test.go +++ b/pkg/yqlib/operator_recursive_descent_test.go @@ -5,6 +5,22 @@ import ( ) var recursiveDescentOperatorScenarios = []expressionScenario{ + { + skipDoc: true, + document: `{}`, + expression: `..`, + expected: []string{ + "D0, P[], (!!map)::{}\n", + }, + }, + { + skipDoc: true, + document: `[]`, + expression: `..`, + expected: []string{ + "D0, P[], (!!seq)::[]\n", + }, + }, { skipDoc: true, document: `cat`, From 13679e51e281e5769591dd647a851db93d16978c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 26 Nov 2020 11:20:53 +1100 Subject: [PATCH 106/129] Added get key examples --- pkg/yqlib/doc/.gitignore | 1 + pkg/yqlib/doc/Path.md | 34 +++++++++++++++++++++++++++++++++ pkg/yqlib/doc/aa.md | 0 pkg/yqlib/doc/headers/Path.md | 4 +++- pkg/yqlib/operator_path_test.go | 16 ++++++++++++++++ 5 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 pkg/yqlib/doc/.gitignore create mode 100644 pkg/yqlib/doc/aa.md diff --git a/pkg/yqlib/doc/.gitignore b/pkg/yqlib/doc/.gitignore new file mode 100644 index 00000000..c4c4ffc6 --- /dev/null +++ b/pkg/yqlib/doc/.gitignore @@ -0,0 +1 @@ +*.zip diff --git a/pkg/yqlib/doc/Path.md b/pkg/yqlib/doc/Path.md index 4acacce0..9bf1f404 100644 --- a/pkg/yqlib/doc/Path.md +++ b/pkg/yqlib/doc/Path.md @@ -1,4 +1,7 @@ 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. + ## Map path Given a sample.yml file of: ```yaml @@ -15,6 +18,21 @@ will output - b ``` +## Get map key +Given a sample.yml file of: +```yaml +a: + b: cat +``` +then +```bash +yq eval '.a.b | path | .[-1]' sample.yml +``` +will output +```yaml +b +``` + ## Array path Given a sample.yml file of: ```yaml @@ -32,6 +50,22 @@ will output - 1 ``` +## Get array index +Given a sample.yml file of: +```yaml +a: + - cat + - dog +``` +then +```bash +yq eval '.a.[] | select(. == "dog") | path | .[-1]' sample.yml +``` +will output +```yaml +1 +``` + ## Print path and value Given a sample.yml file of: ```yaml diff --git a/pkg/yqlib/doc/aa.md b/pkg/yqlib/doc/aa.md new file mode 100644 index 00000000..e69de29b diff --git a/pkg/yqlib/doc/headers/Path.md b/pkg/yqlib/doc/headers/Path.md index fdb4ec0b..3347edea 100644 --- a/pkg/yqlib/doc/headers/Path.md +++ b/pkg/yqlib/doc/headers/Path.md @@ -1 +1,3 @@ -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. \ No newline at end of file +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. diff --git a/pkg/yqlib/operator_path_test.go b/pkg/yqlib/operator_path_test.go index 8102faf3..d9170878 100644 --- a/pkg/yqlib/operator_path_test.go +++ b/pkg/yqlib/operator_path_test.go @@ -13,6 +13,14 @@ var pathOperatorScenarios = []expressionScenario{ "D0, P[a b], (!!seq)::- a\n- b\n", }, }, + { + description: "Get map key", + document: `{a: {b: cat}}`, + expression: `.a.b | path | .[-1]`, + expected: []string{ + "D0, P[a b -1], (!!str)::b\n", + }, + }, { description: "Array path", document: `{a: [cat, dog]}`, @@ -21,6 +29,14 @@ var pathOperatorScenarios = []expressionScenario{ "D0, P[a 1], (!!seq)::- a\n- 1\n", }, }, + { + description: "Get array index", + document: `{a: [cat, dog]}`, + expression: `.a.[] | select(. == "dog") | path | .[-1]`, + expected: []string{ + "D0, P[a 1 -1], (!!int)::1\n", + }, + }, { description: "Print path and value", document: `{a: [cat, dog, frog]}`, From 3cecb4e383ce0b3cb01c82db773bc094c5875866 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 28 Nov 2020 10:41:09 +1100 Subject: [PATCH 107/129] wip --- pkg/yqlib/doc/Multiply.md | 41 +++++++++++++--- pkg/yqlib/operator_add.go | 33 +++++++++---- pkg/yqlib/operator_add_test.go | 3 ++ pkg/yqlib/operator_collect_object.go | 2 +- pkg/yqlib/operator_multiply.go | 63 +++++++++++++++---------- pkg/yqlib/operator_multiply_test.go | 31 +++++++++--- pkg/yqlib/operator_recursive_descent.go | 11 +++-- pkg/yqlib/path_tokeniser.go | 4 +- 8 files changed, 135 insertions(+), 53 deletions(-) diff --git a/pkg/yqlib/doc/Multiply.md b/pkg/yqlib/doc/Multiply.md index 9f09fea5..42d11573 100644 --- a/pkg/yqlib/doc/Multiply.md +++ b/pkg/yqlib/doc/Multiply.md @@ -66,9 +66,9 @@ b: Given a sample.yml file of: ```yaml a: {things: great} -b: - also: "me" - + b: + also: "me" + ``` then ```bash @@ -76,9 +76,6 @@ yq eval '. * {"a":.b}' sample.yml ``` will output ```yaml -a: {things: great, also: "me"} -b: - also: "me" ``` ## Merge arrays @@ -109,6 +106,38 @@ b: - 5 ``` +## Merge, appending arrays +Given a sample.yml file of: +```yaml +a: + array: + - 1 + - 2 + - animal: dog + value: coconut +b: + array: + - 3 + - 4 + - animal: cat + value: banana +``` +then +```bash +yq eval '.a *+ .b' sample.yml +``` +will output +```yaml +array: + - 1 + - 2 + - animal: dog + - 3 + - 4 + - animal: cat +value: banana +``` + ## Merge to prefix an element Given a sample.yml file of: ```yaml diff --git a/pkg/yqlib/operator_add.go b/pkg/yqlib/operator_add.go index fad85ecd..9b294c53 100644 --- a/pkg/yqlib/operator_add.go +++ b/pkg/yqlib/operator_add.go @@ -8,6 +8,10 @@ import ( yaml "gopkg.in/yaml.v3" ) +type AddPreferences struct { + InPlace bool +} + func toNodes(candidates *list.List) []*yaml.Node { if candidates.Len() == 0 { @@ -41,26 +45,35 @@ func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT return nil, err } + preferences := pathNode.Operation.Preferences + inPlace := preferences != nil && preferences.(*AddPreferences).InPlace + for el := lhs.Front(); el != nil; el = el.Next() { lhsCandidate := el.Value.(*CandidateNode) lhsNode := UnwrapDoc(lhsCandidate.Node) - var newBlank = &CandidateNode{ - Path: lhsCandidate.Path, - Document: lhsCandidate.Document, - Filename: lhsCandidate.Filename, - Node: &yaml.Node{}, + var target *CandidateNode + + if inPlace { + target = lhsCandidate + } else { + target = &CandidateNode{ + Path: lhsCandidate.Path, + Document: lhsCandidate.Document, + Filename: lhsCandidate.Filename, + Node: &yaml.Node{}, + } } switch lhsNode.Kind { case yaml.MappingNode: return nil, fmt.Errorf("Maps not yet supported for addition") case yaml.SequenceNode: - newBlank.Node.Kind = yaml.SequenceNode - newBlank.Node.Style = lhsNode.Style - newBlank.Node.Tag = "!!seq" - newBlank.Node.Content = append(lhsNode.Content, toNodes(rhs)...) - results.PushBack(newBlank) + target.Node.Kind = yaml.SequenceNode + target.Node.Style = lhsNode.Style + target.Node.Tag = "!!seq" + target.Node.Content = append(lhsNode.Content, toNodes(rhs)...) + results.PushBack(target) case yaml.ScalarNode: return nil, fmt.Errorf("Scalars not yet supported for addition") } diff --git a/pkg/yqlib/operator_add_test.go b/pkg/yqlib/operator_add_test.go index 8f4459ed..14923be7 100644 --- a/pkg/yqlib/operator_add_test.go +++ b/pkg/yqlib/operator_add_test.go @@ -5,6 +5,9 @@ import ( ) var addOperatorScenarios = []expressionScenario{ + { + description: "+= test and doc", + }, { description: "Concatenate arrays", document: `{a: [1,2], b: [3,4]}`, diff --git a/pkg/yqlib/operator_collect_object.go b/pkg/yqlib/operator_collect_object.go index 298880c9..147fc633 100644 --- a/pkg/yqlib/operator_collect_object.go +++ b/pkg/yqlib/operator_collect_object.go @@ -94,7 +94,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list. newCandidate.Path = nil - newCandidate, err = multiply(d, newCandidate, splatCandidate) + newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate) if err != nil { return nil, err } diff --git a/pkg/yqlib/operator_multiply.go b/pkg/yqlib/operator_multiply.go index ac7b456e..a9657fbb 100644 --- a/pkg/yqlib/operator_multiply.go +++ b/pkg/yqlib/operator_multiply.go @@ -43,39 +43,49 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat return results, nil } +type MultiplyPreferences struct { + AppendArrays bool +} + func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- MultiplyOperator") - return crossFunction(d, matchingNodes, pathNode, multiply) + return crossFunction(d, matchingNodes, pathNode, multiply(pathNode.Operation.Preferences.(*MultiplyPreferences))) } -func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { - lhs.Node = UnwrapDoc(lhs.Node) - rhs.Node = UnwrapDoc(rhs.Node) - log.Debugf("Multipling LHS: %v", lhs.Node.Tag) - log.Debugf("- RHS: %v", rhs.Node.Tag) +func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + lhs.Node = UnwrapDoc(lhs.Node) + rhs.Node = UnwrapDoc(rhs.Node) + log.Debugf("Multipling LHS: %v", lhs.Node.Tag) + log.Debugf("- RHS: %v", rhs.Node.Tag) - if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || - (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { + shouldAppendArrays := preferences.AppendArrays + + if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || + (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { + + var newBlank = &CandidateNode{ + Path: lhs.Path, + Document: lhs.Document, + Filename: lhs.Filename, + Node: &yaml.Node{}, + } + var newThing, err = mergeObjects(d, newBlank, lhs, false) + if err != nil { + return nil, err + } + return mergeObjects(d, newThing, rhs, shouldAppendArrays) - var newBlank = &CandidateNode{ - Path: lhs.Path, - Document: lhs.Document, - Filename: lhs.Filename, - Node: &yaml.Node{}, } - var newThing, err = mergeObjects(d, newBlank, lhs) - if err != nil { - return nil, err - } - return mergeObjects(d, newThing, rhs) - + return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag) } - return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag) } -func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { +func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) { var results = list.New() - err := recursiveDecent(d, results, nodeToMap(rhs)) + + // shouldn't recurse arrays if appending + err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays) if err != nil { return nil, err } @@ -86,7 +96,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) } for el := results.Front(); el != nil; el = el.Next() { - err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode)) + err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays) if err != nil { return nil, err } @@ -107,7 +117,8 @@ func createTraversalTree(path []interface{}) *PathTreeNode { } -func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode) error { +func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error { + log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) lhsPath := rhs.Path[pathIndexToStartFrom:] @@ -116,6 +127,10 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { assignmentOp.OperationType = Assign assignmentOp.Preferences = &AssignOpPreferences{false} + } else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode { + log.Debugf("append! lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) + assignmentOp.OperationType = Add + assignmentOp.Preferences = &AddPreferences{InPlace: true} } rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} diff --git a/pkg/yqlib/operator_multiply_test.go b/pkg/yqlib/operator_multiply_test.go index d16de6fa..3007bdeb 100644 --- a/pkg/yqlib/operator_multiply_test.go +++ b/pkg/yqlib/operator_multiply_test.go @@ -5,6 +5,9 @@ import ( ) var multiplyOperatorScenarios = []expressionScenario{ + { + description: "*+ doc", + }, { skipDoc: true, document: `{a: {also: [1]}, b: {also: me}}`, @@ -80,15 +83,15 @@ var multiplyOperatorScenarios = []expressionScenario{ description: "Merge keeps style of LHS", dontFormatInputForDoc: true, document: `a: {things: great} -b: - also: "me" -`, + b: + also: "me" + `, expression: `. * {"a":.b}`, expected: []string{ `D0, P[], (!!map)::a: {things: great, also: "me"} -b: - also: "me" -`, + b: + also: "me" + `, }, }, { @@ -99,6 +102,22 @@ b: "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", }, }, + { + skipDoc: true, + document: `{a: [1], b: [2]}`, + expression: `.a *+ .b`, + expected: []string{ + "D0, P[a], (!!seq)::[1, 2]\n", + }, + }, + { + description: "Merge, appending arrays", + document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`, + expression: `.a *+ .b`, + expected: []string{ + "D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n", + }, + }, { description: "Merge to prefix an element", document: `{a: cat, b: dog}`, diff --git a/pkg/yqlib/operator_recursive_descent.go b/pkg/yqlib/operator_recursive_descent.go index 51e1439c..0cb8f898 100644 --- a/pkg/yqlib/operator_recursive_descent.go +++ b/pkg/yqlib/operator_recursive_descent.go @@ -3,13 +3,13 @@ package yqlib import ( "container/list" - "gopkg.in/yaml.v3" + yaml "gopkg.in/yaml.v3" ) func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { var results = list.New() - err := recursiveDecent(d, results, matchMap) + err := recursiveDecent(d, results, matchMap, true) if err != nil { return nil, err } @@ -17,7 +17,7 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod return results, nil } -func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error { +func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, recurseArray bool) error { for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -26,14 +26,15 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li log.Debugf("Recursive Decent, added %v", NodeToString(candidate)) results.PushBack(candidate) - if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 { + if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 && + (recurseArray || candidate.Node.Kind != yaml.SequenceNode) { children, err := Splat(d, nodeToMap(candidate)) if err != nil { return err } - err = recursiveDecent(d, results, children) + err = recursiveDecent(d, results, children, recurseArray) if err != nil { return err } diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 9d0faba6..5946156b 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -251,8 +251,10 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\]`), literalToken(CloseCollect, true)) lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false)) lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true)) - lexer.Add([]byte(`\*`), opToken(Multiply)) + lexer.Add([]byte(`\*`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: false})) + lexer.Add([]byte(`\*\+`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: true})) lexer.Add([]byte(`\+`), opToken(Add)) + lexer.Add([]byte(`\+=`), opTokenWithPrefs(Add, nil, &AddPreferences{InPlace: true})) err := lexer.Compile() if err != nil { From 3f48201a19bc45a202037109df3abc22c6edb03c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 28 Nov 2020 10:46:04 +1100 Subject: [PATCH 108/129] wip --- pkg/yqlib/operator_add_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/yqlib/operator_add_test.go b/pkg/yqlib/operator_add_test.go index 14923be7..5e16536b 100644 --- a/pkg/yqlib/operator_add_test.go +++ b/pkg/yqlib/operator_add_test.go @@ -7,6 +7,8 @@ import ( var addOperatorScenarios = []expressionScenario{ { description: "+= test and doc", + expression: ".a.b+= .e.f" + expected: []string{"add .e.g to be, return top level node"} }, { description: "Concatenate arrays", From 3a030651a3c5450614b86b7b5c56d76a0ef33783 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 28 Nov 2020 11:24:16 +1100 Subject: [PATCH 109/129] Added append equals, merge append. Fixed creating numeric arrays --- pkg/yqlib/doc/Add.md | 25 ++++++++++++++++++ pkg/yqlib/doc/Assign.md | 2 +- pkg/yqlib/doc/Multiply.md | 11 +++++--- pkg/yqlib/doc/Traverse.md | 6 ++--- pkg/yqlib/doc/headers/Add.md | 2 ++ pkg/yqlib/doc/headers/Multiply.md | 2 ++ pkg/yqlib/lib.go | 1 + pkg/yqlib/operator_add.go | 33 ++++++++++++------------ pkg/yqlib/operator_add_test.go | 9 ++++--- pkg/yqlib/operator_assign_test.go | 4 +-- pkg/yqlib/operator_collect_test.go | 8 ++++++ pkg/yqlib/operator_multiply.go | 4 +-- pkg/yqlib/operator_multiply_test.go | 15 +++++------ pkg/yqlib/operator_traverse_path_test.go | 6 ++--- pkg/yqlib/operators_test.go | 2 +- pkg/yqlib/path_parse_test.go | 5 ++++ pkg/yqlib/path_tokeniser.go | 3 +-- 17 files changed, 92 insertions(+), 46 deletions(-) diff --git a/pkg/yqlib/doc/Add.md b/pkg/yqlib/doc/Add.md index 82550b40..bb994943 100644 --- a/pkg/yqlib/doc/Add.md +++ b/pkg/yqlib/doc/Add.md @@ -3,6 +3,31 @@ Add behaves differently according to the type of the LHS: - number scalars: arithmetic addition (soon) - string scalars: concatenate (soon) +Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`. + +## Concatenate and assign arrays +Given a sample.yml file of: +```yaml +a: + val: thing + b: + - cat + - dog +``` +then +```bash +yq eval '.a.b += ["cow"]' sample.yml +``` +will output +```yaml +a: + val: thing + b: + - cat + - dog + - cow +``` + ## Concatenate arrays Given a sample.yml file of: ```yaml diff --git a/pkg/yqlib/doc/Assign.md b/pkg/yqlib/doc/Assign.md index c5f1ce61..11524986 100644 --- a/pkg/yqlib/doc/Assign.md +++ b/pkg/yqlib/doc/Assign.md @@ -148,7 +148,7 @@ Given a sample.yml file of: ``` then ```bash -yq eval '.a.b[0] |= "bogs"' sample.yml +yq eval '.a.b.[0] |= "bogs"' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/doc/Multiply.md b/pkg/yqlib/doc/Multiply.md index 42d11573..3fc98d53 100644 --- a/pkg/yqlib/doc/Multiply.md +++ b/pkg/yqlib/doc/Multiply.md @@ -2,6 +2,8 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). +To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them. + Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. ## Merging files @@ -66,9 +68,9 @@ b: Given a sample.yml file of: ```yaml a: {things: great} - b: - also: "me" - +b: + also: "me" + ``` then ```bash @@ -76,6 +78,9 @@ yq eval '. * {"a":.b}' sample.yml ``` will output ```yaml +a: {things: great, also: "me"} +b: + also: "me" ``` ## Merge arrays diff --git a/pkg/yqlib/doc/Traverse.md b/pkg/yqlib/doc/Traverse.md index 88ccdb10..20befb25 100644 --- a/pkg/yqlib/doc/Traverse.md +++ b/pkg/yqlib/doc/Traverse.md @@ -138,7 +138,7 @@ Given a sample.yml file of: ``` then ```bash -yq eval '[0]' sample.yml +yq eval '.[0]' sample.yml ``` will output ```yaml @@ -152,7 +152,7 @@ Given a sample.yml file of: ``` then ```bash -yq eval '[2]' sample.yml +yq eval '.[2]' sample.yml ``` will output ```yaml @@ -166,7 +166,7 @@ a: b ``` then ```bash -yq eval '[0]' sample.yml +yq eval '.[0]' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/doc/headers/Add.md b/pkg/yqlib/doc/headers/Add.md index f451aa95..96cf7246 100644 --- a/pkg/yqlib/doc/headers/Add.md +++ b/pkg/yqlib/doc/headers/Add.md @@ -2,3 +2,5 @@ Add behaves differently according to the type of the LHS: - arrays: concatenate - number scalars: arithmetic addition (soon) - string scalars: concatenate (soon) + +Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`. diff --git a/pkg/yqlib/doc/headers/Multiply.md b/pkg/yqlib/doc/headers/Multiply.md index 62c73302..81cf758b 100644 --- a/pkg/yqlib/doc/headers/Multiply.md +++ b/pkg/yqlib/doc/headers/Multiply.md @@ -2,6 +2,8 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). +To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them. + Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. ## Merging files diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 8883d392..abc4e1a5 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -31,6 +31,7 @@ var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOp var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} +var AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} diff --git a/pkg/yqlib/operator_add.go b/pkg/yqlib/operator_add.go index 9b294c53..4bab2f22 100644 --- a/pkg/yqlib/operator_add.go +++ b/pkg/yqlib/operator_add.go @@ -8,8 +8,18 @@ import ( yaml "gopkg.in/yaml.v3" ) -type AddPreferences struct { - InPlace bool +func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode { + return &PathTreeNode{Operation: &Operation{OperationType: Add}, + Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}, + Rhs: rhs} +} + +func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + assignmentOp := &Operation{OperationType: Assign} + assignmentOp.Preferences = &AssignOpPreferences{true} + + assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)} + return d.GetMatchingNodes(matchingNodes, assignmentOpNode) } func toNodes(candidates *list.List) []*yaml.Node { @@ -45,24 +55,15 @@ func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT return nil, err } - preferences := pathNode.Operation.Preferences - inPlace := preferences != nil && preferences.(*AddPreferences).InPlace - for el := lhs.Front(); el != nil; el = el.Next() { lhsCandidate := el.Value.(*CandidateNode) lhsNode := UnwrapDoc(lhsCandidate.Node) - var target *CandidateNode - - if inPlace { - target = lhsCandidate - } else { - target = &CandidateNode{ - Path: lhsCandidate.Path, - Document: lhsCandidate.Document, - Filename: lhsCandidate.Filename, - Node: &yaml.Node{}, - } + target := &CandidateNode{ + Path: lhsCandidate.Path, + Document: lhsCandidate.Document, + Filename: lhsCandidate.Filename, + Node: &yaml.Node{}, } switch lhsNode.Kind { diff --git a/pkg/yqlib/operator_add_test.go b/pkg/yqlib/operator_add_test.go index 5e16536b..04092635 100644 --- a/pkg/yqlib/operator_add_test.go +++ b/pkg/yqlib/operator_add_test.go @@ -6,9 +6,12 @@ import ( var addOperatorScenarios = []expressionScenario{ { - description: "+= test and doc", - expression: ".a.b+= .e.f" - expected: []string{"add .e.g to be, return top level node"} + description: "Concatenate and assign arrays", + document: `{a: {val: thing, b: [cat,dog]}}`, + expression: ".a.b += [\"cow\"]", + expected: []string{ + "D0, P[], (doc)::{a: {val: thing, b: [cat, dog, cow]}}\n", + }, }, { description: "Concatenate arrays", diff --git a/pkg/yqlib/operator_assign_test.go b/pkg/yqlib/operator_assign_test.go index 06251349..cf5fa788 100644 --- a/pkg/yqlib/operator_assign_test.go +++ b/pkg/yqlib/operator_assign_test.go @@ -99,7 +99,7 @@ var assignOperatorScenarios = []expressionScenario{ description: "Update empty object and array", dontFormatInputForDoc: true, document: `{}`, - expression: `.a.b[0] |= "bogs"`, + expression: `.a.b.[0] |= "bogs"`, expected: []string{ "D0, P[], (doc)::{a: {b: [bogs]}}\n", }, @@ -107,7 +107,7 @@ var assignOperatorScenarios = []expressionScenario{ { skipDoc: true, document: `{}`, - expression: `.a.b[1].c |= "bogs"`, + expression: `.a.b.[1].c |= "bogs"`, expected: []string{ "D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n", }, diff --git a/pkg/yqlib/operator_collect_test.go b/pkg/yqlib/operator_collect_test.go index cadfef85..9441ae10 100644 --- a/pkg/yqlib/operator_collect_test.go +++ b/pkg/yqlib/operator_collect_test.go @@ -13,6 +13,14 @@ var collectOperatorScenarios = []expressionScenario{ "D0, P[], (!!seq)::[]\n", }, }, + { + skipDoc: true, + document: ``, + expression: `[3]`, + expected: []string{ + "D0, P[], (!!seq)::- 3\n", + }, + }, { description: "Collect single", document: ``, diff --git a/pkg/yqlib/operator_multiply.go b/pkg/yqlib/operator_multiply.go index a9657fbb..ad19fcf2 100644 --- a/pkg/yqlib/operator_multiply.go +++ b/pkg/yqlib/operator_multiply.go @@ -128,9 +128,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid assignmentOp.OperationType = Assign assignmentOp.Preferences = &AssignOpPreferences{false} } else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode { - log.Debugf("append! lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) - assignmentOp.OperationType = Add - assignmentOp.Preferences = &AddPreferences{InPlace: true} + assignmentOp.OperationType = AddAssign } rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} diff --git a/pkg/yqlib/operator_multiply_test.go b/pkg/yqlib/operator_multiply_test.go index 3007bdeb..d900c000 100644 --- a/pkg/yqlib/operator_multiply_test.go +++ b/pkg/yqlib/operator_multiply_test.go @@ -5,9 +5,6 @@ import ( ) var multiplyOperatorScenarios = []expressionScenario{ - { - description: "*+ doc", - }, { skipDoc: true, document: `{a: {also: [1]}, b: {also: me}}`, @@ -83,15 +80,15 @@ var multiplyOperatorScenarios = []expressionScenario{ description: "Merge keeps style of LHS", dontFormatInputForDoc: true, document: `a: {things: great} - b: - also: "me" - `, +b: + also: "me" +`, expression: `. * {"a":.b}`, expected: []string{ `D0, P[], (!!map)::a: {things: great, also: "me"} - b: - also: "me" - `, +b: + also: "me" +`, }, }, { diff --git a/pkg/yqlib/operator_traverse_path_test.go b/pkg/yqlib/operator_traverse_path_test.go index 7c98f8aa..e39e5a62 100644 --- a/pkg/yqlib/operator_traverse_path_test.go +++ b/pkg/yqlib/operator_traverse_path_test.go @@ -159,7 +159,7 @@ var traversePathOperatorScenarios = []expressionScenario{ { description: "Traversing arrays by index", document: `[1,2,3]`, - expression: `[0]`, + expression: `.[0]`, expected: []string{ "D0, P[0], (!!int)::1\n", }, @@ -167,7 +167,7 @@ var traversePathOperatorScenarios = []expressionScenario{ { description: "Maps with numeric keys", document: `{2: cat}`, - expression: `[2]`, + expression: `.[2]`, expected: []string{ "D0, P[2], (!!str)::cat\n", }, @@ -175,7 +175,7 @@ var traversePathOperatorScenarios = []expressionScenario{ { description: "Maps with non existing numeric keys", document: `{a: b}`, - expression: `[0]`, + expression: `.[0]`, expected: []string{ "D0, P[0], (!!null)::null\n", }, diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 15e52d74..083ce930 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -37,7 +37,7 @@ func testScenario(t *testing.T, s *expressionScenario) { if s.document != "" { inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0) if err != nil { - t.Error(err) + t.Error(err, s.document) return } } diff --git a/pkg/yqlib/path_parse_test.go b/pkg/yqlib/path_parse_test.go index f8c4decc..ab8ccfb5 100644 --- a/pkg/yqlib/path_parse_test.go +++ b/pkg/yqlib/path_parse_test.go @@ -49,6 +49,11 @@ var pathTests = []struct { append(make([]interface{}, 0), "[", "]"), append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"), }, + { + `[3]`, + append(make([]interface{}, 0), "[", "3 (int64)", "]"), + append(make([]interface{}, 0), "3 (int64)", "COLLECT", "PIPE"), + }, { `d0.a`, append(make([]interface{}, 0), "d0", "PIPE", "a"), diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 5946156b..d712e191 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -223,7 +223,6 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true})) - lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(false)) lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true)) lexer.Add([]byte("( |\t|\n|\r)+"), skip) @@ -254,7 +253,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\*`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: false})) lexer.Add([]byte(`\*\+`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: true})) lexer.Add([]byte(`\+`), opToken(Add)) - lexer.Add([]byte(`\+=`), opTokenWithPrefs(Add, nil, &AddPreferences{InPlace: true})) + lexer.Add([]byte(`\+=`), opToken(AddAssign)) err := lexer.Compile() if err != nil { From 1258fa199e8533a78516e0432a6b6a11717b6a96 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 28 Nov 2020 11:25:10 +1100 Subject: [PATCH 110/129] Updated lib todo list --- pkg/yqlib/lib.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index abc4e1a5..a36e0557 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -19,11 +19,9 @@ type OperationType struct { // operators TODO: // - cookbook doc for common things // - write in place -// - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) // - compare ?? // - validate ?? -// - exists var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator} var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator} From 8de10e550d9a9792036d56aa866e5ddd8090fe88 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 29 Nov 2020 20:25:47 +1100 Subject: [PATCH 111/129] wip - write in place --- cmd/constant.go | 4 +- cmd/evaluate_all_command.go | 20 + cmd/root.go | 1 + cmd/write.go | 61 --- cmd/write_test.go | 610 ---------------------------- pkg/yqlib/file_utils.go | 50 +++ pkg/yqlib/lib.go | 1 + pkg/yqlib/printer.go | 11 +- pkg/yqlib/utils.go | 53 --- pkg/yqlib/write_in_place_handler.go | 55 +++ 10 files changed, 137 insertions(+), 729 deletions(-) delete mode 100644 cmd/write.go delete mode 100644 cmd/write_test.go create mode 100644 pkg/yqlib/file_utils.go create mode 100644 pkg/yqlib/write_in_place_handler.go diff --git a/cmd/constant.go b/cmd/constant.go index dc4d01ff..efc0d1bd 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -2,7 +2,7 @@ package cmd var unwrapScalar = true -// var writeInplace = false +var writeInplace = false var outputToJSON = false // var exitStatus = false @@ -14,5 +14,3 @@ var noDocSeparators = false var nullInput = false var verbose = false var version = false - -// var log = logging.MustGetLogger("yq") diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index 460f0cb7..00daffda 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "os" "github.com/mikefarah/yq/v4/pkg/yqlib" @@ -39,7 +40,24 @@ func evaluateAll(cmd *cobra.Command, args []string) error { if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { colorsEnabled = true } + + if writeInplace && len(args) < 2 { + return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file") + } + + completedSuccessfully := false + + if writeInplace { + writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1]) + out, err = writeInPlaceHandler.CreateTempFile() + if err != nil { + return err + } + defer writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) + } + printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators) + allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator() switch len(args) { case 0: @@ -59,6 +77,8 @@ func evaluateAll(cmd *cobra.Command, args []string) error { err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer) } + completedSuccessfully = err == nil + cmd.SilenceUsage = true return err } diff --git a/cmd/root.go b/cmd/root.go index 6836a864..05878555 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -46,6 +46,7 @@ func New() *cobra.Command { rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") + rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.") rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors") rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors") diff --git a/cmd/write.go b/cmd/write.go deleted file mode 100644 index a1c30607..00000000 --- a/cmd/write.go +++ /dev/null @@ -1,61 +0,0 @@ -package cmd - -// import ( -// "github.com/spf13/cobra" -// ) - -// func createWriteCmd() *cobra.Command { -// var cmdWrite = &cobra.Command{ -// Use: "write [yaml_file] [path_expression] [value]", -// Aliases: []string{"w"}, -// Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue", -// Example: ` -// yq write things.yaml 'a.b.c' true -// yq write things.yaml 'a.*.c' true -// yq write things.yaml 'a.**' true -// yq write things.yaml 'a.(child.subchild==co*).c' true -// yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool -// yq write things.yaml 'a.b.c' --tag '!!float' 3 -// yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags -// yq w -i things.yaml 'a.b.c' cat -// yq w -i -s update_script.yaml things.yaml -// yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array -// yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file -// `, -// Long: `Updates the yaml file w.r.t the given path and value. -// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. - -// Append value to array adds the value to the end of array. - -// Update Scripts: -// Note that you can give an update script to perform more sophisticated update. Update script -// format is list of update commands (update or delete) like so: -// --- -// - command: update -// path: b.c -// value: -// #great -// things: frog # wow! -// - command: delete -// path: b.d -// `, -// RunE: writeProperty, -// } -// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") -// cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") -// cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)") -// cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") -// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") -// cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged") -// cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name") -// cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name") -// return cmdWrite -// } - -// func writeProperty(cmd *cobra.Command, args []string) error { -// var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ", true) -// if updateCommandsError != nil { -// return updateCommandsError -// } -// return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -// } diff --git a/cmd/write_test.go b/cmd/write_test.go deleted file mode 100644 index bb1ab671..00000000 --- a/cmd/write_test.go +++ /dev/null @@ -1,610 +0,0 @@ -package cmd - -// import ( -// "fmt" -// "runtime" -// "strings" -// "testing" - -// "github.com/mikefarah/yq/v3/test" -// ) - -// func TestWriteCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteKeepCommentsCmd(t *testing.T) { -// content := `b: -// c: 3 # comment -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 7 # comment -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithTaggedStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --tag=!!str --style=tagged", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: !!str cat -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithDoubleQuotedStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=double", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: "cat" -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteUpdateStyleOnlyCmd(t *testing.T) { -// content := `b: -// c: dog -// d: things -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --style=single", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 'dog' -// d: 'things' -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteUpdateTagOnlyCmd(t *testing.T) { -// content := `b: -// c: true -// d: false -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --tag=!!str", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: "true" -// d: "false" -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithSingleQuotedStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=single", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 'cat' -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithLiteralStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=literal", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: |- -// cat -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithFoldedStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=folded", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: >- -// cat -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteEmptyMultiDocCmd(t *testing.T) { -// content := `# this is empty -// --- -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `c: 7 - -// # this is empty -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteSurroundingEmptyMultiDocCmd(t *testing.T) { -// content := `--- -// # empty -// --- -// cat: frog -// --- - -// # empty -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d1 c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := ` - -// # empty -// --- -// cat: frog -// c: 7 -// --- - -// # empty -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteFromFileCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// source := `kittens: are cute # sure are!` -// fromFilename := test.WriteTempYamlFile(source) -// defer test.RemoveTempYamlFile(fromFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c -f %s", filename, fromFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: -// kittens: are cute # sure are! -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteEmptyCmd(t *testing.T) { -// content := `` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteAutoCreateCmd(t *testing.T) { -// content := `applications: -// - name: app -// env:` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s applications[0].env.hello world", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `applications: -// - name: app -// env: -// hello: world -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmdScript(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// updateScript := `- command: update -// path: b.c -// value: 7` -// scriptFilename := test.WriteTempYamlFile(updateScript) -// defer test.RemoveTempYamlFile(scriptFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmdEmptyScript(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// updateScript := `` -// scriptFilename := test.WriteTempYamlFile(updateScript) -// defer test.RemoveTempYamlFile(scriptFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteMultiCmd(t *testing.T) { -// content := `b: -// c: 3 -// --- -// apples: great -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// --- -// apples: ok -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } -// func TestWriteInvalidDocumentIndexCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename)) -// if result.Error == nil { -// t.Error("Expected command to fail due to invalid path") -// } -// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestWriteBadDocumentIndexCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename)) -// if result.Error == nil { -// t.Error("Expected command to fail due to invalid path") -// } -// expectedOutput := `asked to process document index 1 but there are only 1 document(s)` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } -// func TestWriteMultiAllCmd(t *testing.T) { -// content := `b: -// c: 3 -// --- -// apples: great -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// apples: ok -// --- -// apples: ok` -// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) -// } - -// func TestWriteCmd_EmptyArray(t *testing.T) { -// content := `b: 3` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s a []", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: 3 -// a: [] -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_Error(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "write") -// if result.Error == nil { -// t.Error("Expected command to fail due to missing arg") -// } -// expectedOutput := `Must provide ` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestWriteCmd_ErrorUnreadableFile(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "write fake-unknown a.b 3") -// if result.Error == nil { -// t.Error("Expected command to fail due to unknown file") -// } -// var expectedOutput string -// if runtime.GOOS == "windows" { -// expectedOutput = `open fake-unknown: The system cannot find the file specified.` -// } else { -// expectedOutput = `open fake-unknown: no such file or directory` -// } -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestWriteCmd_Inplace(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// gotOutput := test.ReadTempYamlFile(filename) -// expectedOutput := `b: -// c: 7` -// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) -// } - -// func TestWriteCmd_InplaceError(t *testing.T) { -// content := `b: cat -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename)) -// if result.Error == nil { -// t.Error("Expected Error to occur!") -// } -// gotOutput := test.ReadTempYamlFile(filename) -// test.AssertResult(t, content, gotOutput) -// } - -// func TestWriteCmd_Append(t *testing.T) { -// content := `b: -// - foo -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// - foo -// - 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_AppendInline(t *testing.T) { -// content := `b: [foo]` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: [foo, 7] -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_AppendInlinePretty(t *testing.T) { -// content := `b: [foo]` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -P b[+] 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// - foo -// - 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_AppendEmptyArray(t *testing.T) { -// content := `a: 2 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: 2 -// b: -// - v -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_SplatArray(t *testing.T) { -// content := `b: -// - c: thing -// - c: another thing -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// - c: new -// - c: new -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_SplatMap(t *testing.T) { -// content := `b: -// c: thing -// d: another thing -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: new -// d: new -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_SplatMapEmpty(t *testing.T) { -// content := `b: -// c: thing -// d: another thing -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: {} -// d: another thing -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } diff --git a/pkg/yqlib/file_utils.go b/pkg/yqlib/file_utils.go new file mode 100644 index 00000000..cfe8d2c4 --- /dev/null +++ b/pkg/yqlib/file_utils.go @@ -0,0 +1,50 @@ +package yqlib + +import ( + "io" + "os" +) + +func safelyRenameFile(from string, to string) { + if renameError := os.Rename(from, to); renameError != nil { + log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) + log.Debug(renameError.Error()) + // can't do this rename when running in docker to a file targeted in a mounted volume, + // so gracefully degrade to copying the entire contents. + if copyError := copyFileContents(from, to); copyError != nil { + log.Errorf("Failed copying from %v to %v", from, to) + log.Error(copyError.Error()) + } else { + removeErr := os.Remove(from) + if removeErr != nil { + log.Errorf("failed removing original file: %s", from) + } + } + } +} + +// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang +func copyFileContents(src, dst string) (err error) { + in, err := os.Open(src) // nolint gosec + if err != nil { + return err + } + defer safelyCloseFile(in) + out, err := os.Create(dst) + if err != nil { + return err + } + defer safelyCloseFile(out) + if _, err = io.Copy(out, in); err != nil { + return err + } + return out.Sync() +} + +func safelyCloseFile(file *os.File) { + err := file.Close() + if err != nil { + log.Error("Error closing file!") + log.Error(err.Error()) + } +} diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index a36e0557..d6782658 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -18,6 +18,7 @@ type OperationType struct { // operators TODO: // - cookbook doc for common things +// - existStatus // - write in place // - mergeEmpty (sets only if the document is empty, do I do that now?) // - compare ?? diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index 2e81c3b8..6c7f41e7 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -5,7 +5,7 @@ import ( "container/list" "io" - "gopkg.in/yaml.v3" + yaml "gopkg.in/yaml.v3" ) type Printer interface { @@ -53,6 +53,13 @@ func (p *resultsPrinter) writeString(writer io.Writer, txt string) error { return errorWriting } +func (p *resultsPrinter) safelyFlush(writer *bufio.Writer) { + if err := writer.Flush(); err != nil { + log.Error("Error flushing writer!") + log.Error(err.Error()) + } +} + func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { log.Debug("PrintResults for %v matches", matchingNodes.Len()) var err error @@ -66,7 +73,7 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { } bufferedWriter := bufio.NewWriter(p.writer) - defer safelyFlush(bufferedWriter) + defer p.safelyFlush(bufferedWriter) if matchingNodes.Len() == 0 { log.Debug("no matching results, nothing to print") diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index d46cdd77..092df9d2 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -9,8 +9,6 @@ import ( yaml "gopkg.in/yaml.v3" ) -//TODO: convert to interface + struct - var treeNavigator = NewDataTreeNavigator() var treeCreator = NewPathTreeCreator() @@ -52,54 +50,3 @@ func readDocuments(reader io.Reader, filename string, fileIndex int) (*list.List currentIndex = currentIndex + 1 } } - -// func safelyRenameFile(from string, to string) { -// if renameError := os.Rename(from, to); renameError != nil { -// log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) -// log.Debug(renameError.Error()) -// // can't do this rename when running in docker to a file targeted in a mounted volume, -// // so gracefully degrade to copying the entire contents. -// if copyError := copyFileContents(from, to); copyError != nil { -// log.Errorf("Failed copying from %v to %v", from, to) -// log.Error(copyError.Error()) -// } else { -// removeErr := os.Remove(from) -// if removeErr != nil { -// log.Errorf("failed removing original file: %s", from) -// } -// } -// } -// } - -// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang -// func copyFileContents(src, dst string) (err error) { -// in, err := os.Open(src) // nolint gosec -// if err != nil { -// return err -// } -// defer safelyCloseFile(in) -// out, err := os.Create(dst) -// if err != nil { -// return err -// } -// defer safelyCloseFile(out) -// if _, err = io.Copy(out, in); err != nil { -// return err -// } -// return out.Sync() -// } - -func safelyFlush(writer *bufio.Writer) { - if err := writer.Flush(); err != nil { - log.Error("Error flushing writer!") - log.Error(err.Error()) - } - -} -func safelyCloseFile(file *os.File) { - err := file.Close() - if err != nil { - log.Error("Error closing file!") - log.Error(err.Error()) - } -} diff --git a/pkg/yqlib/write_in_place_handler.go b/pkg/yqlib/write_in_place_handler.go new file mode 100644 index 00000000..b282c044 --- /dev/null +++ b/pkg/yqlib/write_in_place_handler.go @@ -0,0 +1,55 @@ +package yqlib + +import ( + "io/ioutil" + "os" +) + +type WriteInPlaceHandler interface { + CreateTempFile() (*os.File, error) + FinishWriteInPlace(evaluatedSuccessfully bool) +} + +type writeInPlaceHandler struct { + inputFilename string + tempFile *os.File +} + +func NewWriteInPlaceHandler(inputFile string) WriteInPlaceHandler { + + return &writeInPlaceHandler{inputFile, nil} +} + +func (w *writeInPlaceHandler) CreateTempFile() (*os.File, error) { + info, err := os.Stat(w.inputFilename) + if err != nil { + return nil, err + } + _, err = os.Stat(os.TempDir()) + if os.IsNotExist(err) { + err = os.Mkdir(os.TempDir(), 0700) + if err != nil { + return nil, err + } + } else if err != nil { + return nil, err + } + + file, err := ioutil.TempFile("", "temp") + if err != nil { + return nil, err + } + + err = os.Chmod(file.Name(), info.Mode()) + w.tempFile = file + return file, err +} + +func (w *writeInPlaceHandler) FinishWriteInPlace(evaluatedSuccessfully bool) { + safelyCloseFile(w.tempFile) + if evaluatedSuccessfully { + safelyRenameFile(w.tempFile.Name(), w.inputFilename) + } else { + os.Remove(w.tempFile.Name()) + } +} From 9bc66c80b69a5fe149799e12e9e89a9893713743 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 30 Nov 2020 16:05:07 +1100 Subject: [PATCH 112/129] Added write-inlplace flag --- cmd/constant.go | 2 ++ cmd/evaluate_all_command.go | 8 +++++--- cmd/evalute_sequence_command.go | 21 ++++++++++++++++++++- pkg/yqlib/constants.go | 5 ----- pkg/yqlib/lib.go | 2 ++ pkg/yqlib/write_in_place_handler.go | 4 ++++ 6 files changed, 33 insertions(+), 9 deletions(-) delete mode 100644 pkg/yqlib/constants.go diff --git a/cmd/constant.go b/cmd/constant.go index efc0d1bd..98e20796 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -14,3 +14,5 @@ var noDocSeparators = false var nullInput = false var verbose = false var version = false + +var completedSuccessfully = false diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index 00daffda..ec4bf20a 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -45,15 +45,17 @@ func evaluateAll(cmd *cobra.Command, args []string) error { return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file") } - completedSuccessfully := false - if writeInplace { + // only use colors if its forced + colorsEnabled = forceColor writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1]) out, err = writeInPlaceHandler.CreateTempFile() if err != nil { return err } - defer writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) + // need to indirectly call the function so that completedSuccessfully is + // passed when we finish execution as opposed to now + defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }() } printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators) diff --git a/cmd/evalute_sequence_command.go b/cmd/evalute_sequence_command.go index 56125fbb..cdd70bd0 100644 --- a/cmd/evalute_sequence_command.go +++ b/cmd/evalute_sequence_command.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "os" "github.com/mikefarah/yq/v4/pkg/yqlib" @@ -39,6 +40,24 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { colorsEnabled = true } + + if writeInplace && len(args) < 2 { + return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file") + } + + if writeInplace { + // only use colors if its forced + colorsEnabled = forceColor + writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1]) + out, err = writeInPlaceHandler.CreateTempFile() + if err != nil { + return err + } + // need to indirectly call the function so that completedSuccessfully is + // passed when we finish execution as opposed to now + defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }() + } + printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators) streamEvaluator := yqlib.NewStreamEvaluator() @@ -61,7 +80,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { default: err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer) } - + completedSuccessfully = err == nil cmd.SilenceUsage = true return err } diff --git a/pkg/yqlib/constants.go b/pkg/yqlib/constants.go deleted file mode 100644 index d8c4f7d6..00000000 --- a/pkg/yqlib/constants.go +++ /dev/null @@ -1,5 +0,0 @@ -package yqlib - -import "gopkg.in/op/go-logging.v1" - -var log = logging.MustGetLogger("yq-lib") diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index d6782658..e3693f06 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -9,6 +9,8 @@ import ( yaml "gopkg.in/yaml.v3" ) +var log = logging.MustGetLogger("yq-lib") + type OperationType struct { Type string NumArgs uint // number of arguments to the op diff --git a/pkg/yqlib/write_in_place_handler.go b/pkg/yqlib/write_in_place_handler.go index b282c044..a4fbb013 100644 --- a/pkg/yqlib/write_in_place_handler.go +++ b/pkg/yqlib/write_in_place_handler.go @@ -41,15 +41,19 @@ func (w *writeInPlaceHandler) CreateTempFile() (*os.File, error) { } err = os.Chmod(file.Name(), info.Mode()) + log.Debug("writing to tempfile: %v", file.Name()) w.tempFile = file return file, err } func (w *writeInPlaceHandler) FinishWriteInPlace(evaluatedSuccessfully bool) { + log.Debug("Going to write-inplace, evaluatedSuccessfully=%v, target=%v", evaluatedSuccessfully, w.inputFilename) safelyCloseFile(w.tempFile) if evaluatedSuccessfully { + log.Debug("moved temp file to target") safelyRenameFile(w.tempFile.Name(), w.inputFilename) } else { + log.Debug("removed temp file") os.Remove(w.tempFile.Name()) } } From c9229439f74282db466673a468642c27c6d57e44 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 30 Nov 2020 16:35:21 +1100 Subject: [PATCH 113/129] added exit status --- cmd/constant.go | 2 +- cmd/evaluate_all_command.go | 7 ++++++- cmd/evalute_sequence_command.go | 8 +++++++- cmd/root.go | 1 + pkg/yqlib/printer.go | 9 +++++++++ 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/cmd/constant.go b/cmd/constant.go index 98e20796..fb8f94d4 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -5,7 +5,7 @@ var unwrapScalar = true var writeInplace = false var outputToJSON = false -// var exitStatus = false +var exitStatus = false var forceColor = false var forceNoColor = false var colorsEnabled = false diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index ec4bf20a..5bf24654 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -1,6 +1,7 @@ package cmd import ( + "errors" "fmt" "os" @@ -24,6 +25,7 @@ yq es -n '{"a": "b"}' return cmdEvalAll } func evaluateAll(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true // 0 args, read std in // 1 arg, null input, process expression // 1 arg, read file in sequence @@ -81,6 +83,9 @@ func evaluateAll(cmd *cobra.Command, args []string) error { completedSuccessfully = err == nil - cmd.SilenceUsage = true + if err == nil && exitStatus && !printer.PrintedAnything() { + return errors.New("no matches found") + } + return err } diff --git a/cmd/evalute_sequence_command.go b/cmd/evalute_sequence_command.go index cdd70bd0..45625207 100644 --- a/cmd/evalute_sequence_command.go +++ b/cmd/evalute_sequence_command.go @@ -1,6 +1,7 @@ package cmd import ( + "errors" "fmt" "os" @@ -24,6 +25,7 @@ yq es -n '{"a": "b"}' return cmdEvalSequence } func evaluateSequence(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true // 0 args, read std in // 1 arg, null input, process expression // 1 arg, read file in sequence @@ -81,6 +83,10 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer) } completedSuccessfully = err == nil - cmd.SilenceUsage = true + + if err == nil && exitStatus && !printer.PrintedAnything() { + return errors.New("no matches found") + } + return err } diff --git a/cmd/root.go b/cmd/root.go index 05878555..9dbe6521 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -47,6 +47,7 @@ func New() *cobra.Command { rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.") + rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned") rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors") rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors") diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index 6c7f41e7..1da0728a 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -10,6 +10,7 @@ import ( type Printer interface { PrintResults(matchingNodes *list.List) error + PrintedAnything() bool } type resultsPrinter struct { @@ -21,6 +22,7 @@ type resultsPrinter struct { writer io.Writer firstTimePrinting bool previousDocIndex uint + printedMatches bool } func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer { @@ -35,7 +37,14 @@ func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEn } } +func (p *resultsPrinter) PrintedAnything() bool { + return p.printedMatches +} + func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error { + p.printedMatches = p.printedMatches || (node.Tag != "!!null" && + (node.Tag != "!!bool" || node.Value != "false")) + var encoder Encoder if node.Kind == yaml.ScalarNode && p.unwrapScalar && !p.outputToJSON { return p.writeString(writer, node.Value+"\n") From 08f579f4e3d5de07fe35310a1b03b048e1beba44 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 1 Dec 2020 14:06:49 +1100 Subject: [PATCH 114/129] Fixed create yaml --- cmd/evalute_sequence_command.go | 3 +-- pkg/yqlib/doc/Assign.md | 12 ++++++++++++ pkg/yqlib/operator_assign.go | 5 +++++ pkg/yqlib/operator_assign_test.go | 7 +++++++ pkg/yqlib/operators_test.go | 16 +++++++++++++--- pkg/yqlib/stream_evaluator.go | 22 ++++++++++++++++++++++ 6 files changed, 60 insertions(+), 5 deletions(-) diff --git a/cmd/evalute_sequence_command.go b/cmd/evalute_sequence_command.go index 45625207..9ee0d294 100644 --- a/cmd/evalute_sequence_command.go +++ b/cmd/evalute_sequence_command.go @@ -63,7 +63,6 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators) streamEvaluator := yqlib.NewStreamEvaluator() - allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator() switch len(args) { case 0: @@ -75,7 +74,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) error { } case 1: if nullInput { - err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer) + err = streamEvaluator.EvaluateNew(args[0], printer) } else { err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer) } diff --git a/pkg/yqlib/doc/Assign.md b/pkg/yqlib/doc/Assign.md index 11524986..f902e029 100644 --- a/pkg/yqlib/doc/Assign.md +++ b/pkg/yqlib/doc/Assign.md @@ -5,6 +5,18 @@ Which will assign the LHS node values to the RHS node values. The RHS expression ### 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. +## Create yaml file +Running +```bash +yq eval --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 diff --git a/pkg/yqlib/operator_assign.go b/pkg/yqlib/operator_assign.go index 5a6f3b58..82c7db04 100644 --- a/pkg/yqlib/operator_assign.go +++ b/pkg/yqlib/operator_assign.go @@ -36,6 +36,11 @@ func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo candidate.UpdateFrom(first.Value.(*CandidateNode)) } } + // // if there was nothing given, perhaps we are creating a new yaml doc + // if matchingNodes.Len() == 0 { + // log.Debug("started with nothing, returning LHS, %v", lhs.Len()) + // return lhs, nil + // } return matchingNodes, nil } diff --git a/pkg/yqlib/operator_assign_test.go b/pkg/yqlib/operator_assign_test.go index cf5fa788..def448bb 100644 --- a/pkg/yqlib/operator_assign_test.go +++ b/pkg/yqlib/operator_assign_test.go @@ -5,6 +5,13 @@ import ( ) var assignOperatorScenarios = []expressionScenario{ + { + description: "Create yaml file", + expression: `(.a.b = "cat") | (.x = "frog")`, + expected: []string{ + "D0, P[], ()::a:\n b: cat\nx: frog\n", + }, + }, { description: "Update node to be the child value", document: `{a: {b: {g: foof}}}`, diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 083ce930..610c8873 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/mikefarah/yq/v4/test" + yaml "gopkg.in/yaml.v3" ) type expressionScenario struct { @@ -40,6 +41,15 @@ func testScenario(t *testing.T, s *expressionScenario) { t.Error(err, s.document) return } + } else { + candidateNode := &CandidateNode{ + Document: 0, + Filename: "", + Node: &yaml.Node{Tag: "!!null"}, + FileIndex: 0, + } + inputs.PushBack(candidateNode) + } results, err = treeNavigator.GetMatchingNodes(inputs, node) @@ -152,20 +162,20 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari var output bytes.Buffer var err error printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true) + streamEvaluator := NewStreamEvaluator() if s.document != "" { node, err := treeCreator.ParsePath(s.expression) if err != nil { t.Error(err) } - streamEvaluator := NewStreamEvaluator() err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer) + if err != nil { t.Error(err) } } else { - allAtOnceEvaluator := NewAllAtOnceEvaluator() - err = allAtOnceEvaluator.EvaluateFiles(s.expression, []string{}, printer) + err = streamEvaluator.EvaluateNew(s.expression, printer) if err != nil { t.Error(err) } diff --git a/pkg/yqlib/stream_evaluator.go b/pkg/yqlib/stream_evaluator.go index d4c22f19..b2fdb9e1 100644 --- a/pkg/yqlib/stream_evaluator.go +++ b/pkg/yqlib/stream_evaluator.go @@ -11,6 +11,7 @@ import ( type StreamEvaluator interface { Evaluate(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error EvaluateFiles(expression string, filenames []string, printer Printer) error + EvaluateNew(expression string, printer Printer) error } type streamEvaluator struct { @@ -23,6 +24,27 @@ func NewStreamEvaluator() StreamEvaluator { return &streamEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()} } +func (s *streamEvaluator) EvaluateNew(expression string, printer Printer) error { + node, err := treeCreator.ParsePath(expression) + if err != nil { + return err + } + candidateNode := &CandidateNode{ + Document: 0, + Filename: "", + Node: &yaml.Node{Tag: "!!null"}, + FileIndex: 0, + } + inputList := list.New() + inputList.PushBack(candidateNode) + + matches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node) + if errorParsing != nil { + return errorParsing + } + return printer.PrintResults(matches) +} + func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error { node, err := treeCreator.ParsePath(expression) From cf4915d78663b37b3c182628620174afb03d4bbd Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 1 Dec 2020 14:14:16 +1100 Subject: [PATCH 115/129] improved acceptance tests --- scripts/acceptance.sh | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/scripts/acceptance.sh b/scripts/acceptance.sh index d6981033..f41a288e 100755 --- a/scripts/acceptance.sh +++ b/scripts/acceptance.sh @@ -3,10 +3,47 @@ set -e # acceptance test -X=$(./yq e '.b.c |= 3' ./examples/sample.yaml | ./yq e '.b.c' -) -if [[ $X != 3 ]]; then - echo "Failed acceptance test: expected 3 but was $X" + + +random=$((1 + $RANDOM % 10)) +./yq e -n ".a = $random" > test.yml +X=$(./yq e '.a' test.yml) + +if [[ $X != $random ]]; then + echo "Failed create: expected $random but was $X" exit 1 fi + +echo "created yaml successfully" + +update=$(($random + 1)) +./yq e -i ".a = $update" test.yml + +X=$(./yq e '.a' test.yml) +if [[ $X != $update ]]; then + echo "Failed to update inplace test: expected $update but was $X" + exit 1 +fi + +echo "updated in place successfully" + +X=$(./yq e '.z' test.yml) +echo "no exit status success" + +set +e + +X=$(./yq e -e '.z' test.yml) + +if [[ $? != 1 ]]; then + echo "Expected error code 1 but was $?" + exit 1 +fi + +echo "exit status success" + +set -e + +rm test.yml + echo "acceptance tests passed" From 773b1a3517bfe3409fd98abd84fc9f8e4ed75844 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 1 Dec 2020 14:23:27 +1100 Subject: [PATCH 116/129] fixed create doc for eval-all --- cmd/evaluate_all_command.go | 2 +- scripts/acceptance.sh | 29 ++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index 5bf24654..f2c8f0a0 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -73,7 +73,7 @@ func evaluateAll(cmd *cobra.Command, args []string) error { } case 1: if nullInput { - err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer) + err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer) } else { err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer) } diff --git a/scripts/acceptance.sh b/scripts/acceptance.sh index f41a288e..b543a8b3 100755 --- a/scripts/acceptance.sh +++ b/scripts/acceptance.sh @@ -6,6 +6,7 @@ set -e +echo "test eval-sequence" random=$((1 + $RANDOM % 10)) ./yq e -n ".a = $random" > test.yml X=$(./yq e '.a' test.yml) @@ -15,7 +16,9 @@ if [[ $X != $random ]]; then exit 1 fi -echo "created yaml successfully" +echo "--success" + +echo "test update-in-place" update=$(($random + 1)) ./yq e -i ".a = $update" test.yml @@ -26,24 +29,36 @@ if [[ $X != $update ]]; then exit 1 fi -echo "updated in place successfully" +echo "--success" -X=$(./yq e '.z' test.yml) -echo "no exit status success" +echo "test eval-all" +./yq ea -n ".a = $random" > test-eval-all.yml +Y=$(./yq ea '.a' test-eval-all.yml) +if [[ $Y != $random ]]; then + echo "Failed create with eval all: expected $random but was $X" + exit 1 +fi +echo "--success" + +echo "test no exit status" +./yq e '.z' test.yml +echo "--success" + +echo "test exit status" set +e -X=$(./yq e -e '.z' test.yml) +./yq e -e '.z' test.yml if [[ $? != 1 ]]; then echo "Expected error code 1 but was $?" exit 1 fi -echo "exit status success" +echo "--success" set -e rm test.yml - +rm test-eval-all.yml echo "acceptance tests passed" From 363fe5d28311514b296084e1310215da8e04482b Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 1 Dec 2020 15:06:54 +1100 Subject: [PATCH 117/129] Added sort keys operator --- pkg/yqlib/doc/Sort Keys.md | 68 ++++++++++++++++++++++++++++ pkg/yqlib/doc/headers/Sort Keys.md | 9 ++++ pkg/yqlib/lib.go | 3 +- pkg/yqlib/operator_sort_keys.go | 53 ++++++++++++++++++++++ pkg/yqlib/operator_sort_keys_test.go | 32 +++++++++++++ pkg/yqlib/path_tokeniser.go | 1 + 6 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 pkg/yqlib/doc/Sort Keys.md create mode 100644 pkg/yqlib/doc/headers/Sort Keys.md create mode 100644 pkg/yqlib/operator_sort_keys.go create mode 100644 pkg/yqlib/operator_sort_keys_test.go diff --git a/pkg/yqlib/doc/Sort Keys.md b/pkg/yqlib/doc/Sort Keys.md new file mode 100644 index 00000000..a1a99306 --- /dev/null +++ b/pkg/yqlib/doc/Sort Keys.md @@ -0,0 +1,68 @@ +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 eval -i 'sortKeys(..)' file1.yml +yq eval -i 'sortKeys(..)' file2.yml +diff file1.yml file2.yml +``` + +## Sort keys of map +Given a sample.yml file of: +```yaml +c: frog +a: blah +b: bing +``` +then +```bash +yq eval 'sortKeys(.)' sample.yml +``` +will output +```yaml +a: blah +b: bing +c: frog +``` + +## Sort keys recursively +Note the array elements are left unsorted, but maps inside arrays are sorted + +Given a sample.yml file of: +```yaml +bParent: + c: dog + array: + - 3 + - 1 + - 2 +aParent: + z: donkey + x: + - c: yum + b: delish + - b: ew + a: apple +``` +then +```bash +yq eval 'sortKeys(..)' sample.yml +``` +will output +```yaml +aParent: + x: + - b: delish + c: yum + - a: apple + b: ew + z: donkey +bParent: + array: + - 3 + - 1 + - 2 + c: dog +``` + diff --git a/pkg/yqlib/doc/headers/Sort Keys.md b/pkg/yqlib/doc/headers/Sort Keys.md new file mode 100644 index 00000000..00eb71a9 --- /dev/null +++ b/pkg/yqlib/doc/headers/Sort Keys.md @@ -0,0 +1,9 @@ +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 eval -i 'sortKeys(..)' file1.yml +yq eval -i 'sortKeys(..)' file2.yml +diff file1.yml file2.yml +``` diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index e3693f06..73be42b2 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -20,8 +20,6 @@ type OperationType struct { // operators TODO: // - cookbook doc for common things -// - existStatus -// - write in place // - mergeEmpty (sets only if the document is empty, do I do that now?) // - compare ?? // - validate ?? @@ -57,6 +55,7 @@ var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator} var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator} +var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: SortKeysOperator} var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator} var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} diff --git a/pkg/yqlib/operator_sort_keys.go b/pkg/yqlib/operator_sort_keys.go new file mode 100644 index 00000000..e8f39381 --- /dev/null +++ b/pkg/yqlib/operator_sort_keys.go @@ -0,0 +1,53 @@ +package yqlib + +import ( + "container/list" + "sort" + + yaml "gopkg.in/yaml.v3" +) + +func SortKeysOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + if err != nil { + return nil, err + } + + for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() { + node := UnwrapDoc(childEl.Value.(*CandidateNode).Node) + if node.Kind == yaml.MappingNode { + sortKeys(node) + } + if err != nil { + return nil, err + } + } + + } + return matchingNodes, nil +} + +func sortKeys(node *yaml.Node) { + keys := make([]string, len(node.Content)/2) + keyBucket := map[string]*yaml.Node{} + valueBucket := map[string]*yaml.Node{} + var contents = node.Content + for index := 0; index < len(contents); index = index + 2 { + key := contents[index] + value := contents[index+1] + keys[index/2] = key.Value + keyBucket[key.Value] = key + valueBucket[key.Value] = value + } + sort.Strings(keys) + sortedContent := make([]*yaml.Node, len(node.Content)) + for index := 0; index < len(keys); index = index + 1 { + keyString := keys[index] + sortedContent[index*2] = keyBucket[keyString] + sortedContent[1+(index*2)] = valueBucket[keyString] + } + node.Content = sortedContent +} diff --git a/pkg/yqlib/operator_sort_keys_test.go b/pkg/yqlib/operator_sort_keys_test.go new file mode 100644 index 00000000..303b5adc --- /dev/null +++ b/pkg/yqlib/operator_sort_keys_test.go @@ -0,0 +1,32 @@ +package yqlib + +import ( + "testing" +) + +var sortKeysOperatorScenarios = []expressionScenario{ + { + description: "Sort keys of map", + document: `{c: frog, a: blah, b: bing}`, + expression: `sortKeys(.)`, + expected: []string{ + "D0, P[], (doc)::{a: blah, b: bing, c: frog}\n", + }, + }, + { + description: "Sort keys recursively", + subdescription: "Note the array elements are left unsorted, but maps inside arrays are sorted", + document: `{bParent: {c: dog, array: [3,1,2]}, aParent: {z: donkey, x: [{c: yum, b: delish}, {b: ew, a: apple}]}}`, + expression: `sortKeys(..)`, + expected: []string{ + "D0, P[], (!!map)::{aParent: {x: [{b: delish, c: yum}, {a: apple, b: ew}], z: donkey}, bParent: {array: [3, 1, 2], c: dog}}\n", + }, + }, +} + +func TestSortKeysOperatorScenarios(t *testing.T) { + for _, tt := range sortKeysOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Sort Keys", sortKeysOperatorScenarios) +} diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index d712e191..e6cc4b17 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -190,6 +190,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`,`), opToken(Union)) lexer.Add([]byte(`:\s*`), opToken(CreateMap)) lexer.Add([]byte(`length`), opToken(Length)) + lexer.Add([]byte(`sortKeys`), opToken(SortKeys)) lexer.Add([]byte(`select`), opToken(Select)) lexer.Add([]byte(`has`), opToken(Has)) lexer.Add([]byte(`explode`), opToken(Explode)) From 8cd290c00b0e42091af64fa82077bdd56625464e Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 1 Dec 2020 15:15:12 +1100 Subject: [PATCH 118/129] incrementing version --- cmd/version.go | 2 +- snap/snapcraft.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/version.go b/cmd/version.go index e67624c0..f95a019e 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -11,7 +11,7 @@ var ( GitDescribe string // Version is main version number that is being run at the moment. - Version = "4.0.0-alpha2" + Version = "4.0.0-beta1" // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 55daab5b..22c90f2c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: yq -version: '4.0.0-alpha2' +version: '4.0.0-beta1' summary: A lightweight and portable command-line YAML processor description: | The aim of the project is to be the jq or sed of yaml files. From da027f69d768194290e1893f2a109951d891270c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 1 Dec 2020 16:16:20 +1100 Subject: [PATCH 119/129] updated cobra package --- cmd/evaluate_all_command.go | 5 +- cmd/evalute_sequence_command.go | 15 ++- go.mod | 5 +- go.sum | 166 ++++++++++++++++++++++++++++++++ 4 files changed, 184 insertions(+), 7 deletions(-) diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index f2c8f0a0..fc7b4ec2 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -15,9 +15,8 @@ func createEvaluateAllCommand() *cobra.Command { Aliases: []string{"ea"}, Short: "Loads _all_ yaml documents of _all_ yaml files and runs expression once", Example: ` -yq es '.a.b | length' file1.yml file2.yml -yq es < sample.yaml -yq es -n '{"a": "b"}' +# merges f2.yml into f1.yml (inplace) +yq eval-all --inplace 'select(fileIndex == 0) * select(fileIndex == 1)' f1.yml f2.yml `, Long: "Evaluate All:\nUseful when you need to run an expression across several yaml documents or files. Consumes more memory than eval", RunE: evaluateAll, diff --git a/cmd/evalute_sequence_command.go b/cmd/evalute_sequence_command.go index 9ee0d294..c9eabeb8 100644 --- a/cmd/evalute_sequence_command.go +++ b/cmd/evalute_sequence_command.go @@ -15,9 +15,18 @@ func createEvaluateSequenceCommand() *cobra.Command { Aliases: []string{"e"}, Short: "Apply expression to each document in each yaml file given in sequence", Example: ` -yq es '.a.b | length' file1.yml file2.yml -yq es < sample.yaml -yq es -n '{"a": "b"}' +# runs the expression against each file, in series +yq e '.a.b | length' f1.yml f2.yml + +# prints out the file +yq e sample.yaml + +# prints a new yaml document +yq e -n '.a.b.c = "cat"' + + +# updates file.yaml directly +yq e '.a.b = "cool"' -i file.yaml `, Long: "Evaluate Sequence:\nIterate over each yaml document, apply the expression and print the results, in sequence.", RunE: evaluateSequence, diff --git a/go.mod b/go.mod index f3b8cb39..9599ee08 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,18 @@ module github.com/mikefarah/yq/v4 require ( + github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect github.com/elliotchance/orderedmap v1.3.0 github.com/fatih/color v1.9.0 github.com/goccy/go-yaml v1.8.1 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/mattn/go-colorable v0.1.7 // indirect - github.com/spf13/cobra v1.0.0 + github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 // indirect github.com/timtadh/data-structures v0.5.3 // indirect github.com/timtadh/lexmachine v0.2.2 + github.com/ugorji/go v1.1.4 // indirect + github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 diff --git a/go.sum b/go.sum index fe09b6de..4a0116d2 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,36 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -26,6 +46,7 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -39,20 +60,55 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -65,10 +121,13 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= @@ -76,14 +135,26 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -94,26 +165,36 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ= github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU= github.com/timtadh/lexmachine v0.2.2 h1:g55RnjdYazm5wnKv59pwFcBJHOyvTPfDEoz21s4PHmY= @@ -123,28 +204,71 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= @@ -154,28 +278,64 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE= gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -183,6 +343,12 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From c9dbf04da380ed1f7352f2c384dbc04e12cf40ef Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 1 Dec 2020 17:58:07 +1100 Subject: [PATCH 120/129] Added pipe and length docs, fix pipe precedence --- README.md | 19 ++++++----- go.mod | 4 --- go.sum | 13 +------- pkg/yqlib/doc/Assign.md | 6 ++-- pkg/yqlib/doc/Length.md | 54 +++++++++++++++++++++++++++++++ pkg/yqlib/doc/Pipe.md | 35 ++++++++++++++++++++ pkg/yqlib/doc/headers/Length.md | 1 + pkg/yqlib/doc/headers/Pipe.md | 1 + pkg/yqlib/lib.go | 5 ++- pkg/yqlib/operator_assign_test.go | 6 ++-- pkg/yqlib/operator_length.go | 35 ++++++++++++++++++++ pkg/yqlib/operator_length_test.go | 42 ++++++++++++++++++++++++ pkg/yqlib/operator_multiply.go | 2 +- pkg/yqlib/operator_pipe.go | 11 +++++++ pkg/yqlib/operator_pipe_test.go | 31 ++++++++++++++++++ pkg/yqlib/operators.go | 35 -------------------- pkg/yqlib/path_postfix.go | 2 +- pkg/yqlib/path_tokeniser.go | 2 +- 18 files changed, 233 insertions(+), 71 deletions(-) create mode 100644 pkg/yqlib/doc/Length.md create mode 100644 pkg/yqlib/doc/Pipe.md create mode 100644 pkg/yqlib/doc/headers/Length.md create mode 100644 pkg/yqlib/doc/headers/Pipe.md create mode 100644 pkg/yqlib/operator_length.go create mode 100644 pkg/yqlib/operator_length_test.go create mode 100644 pkg/yqlib/operator_pipe.go create mode 100644 pkg/yqlib/operator_pipe_test.go diff --git a/README.md b/README.md index c7282c36..393ebeaf 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,16 @@ snap install yq `yq` installs with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can: ``` -sudo cat /etc/myfile | yq r - a.path +sudo cat /etc/myfile | yq e '.a.path' - ``` And to write to a root file you can either use [sponge](https://linux.die.net/man/1/sponge): ``` -sudo cat /etc/myfile | yq w - a.path value | sudo sponge /etc/myfile +sudo cat /etc/myfile | yq e '.a.path = "value"' - | sudo sponge /etc/myfile ``` or write to a temporary file: ``` -sudo cat /etc/myfile | yq w - a.path value | sudo tee /etc/myfile.tmp +sudo cat /etc/myfile | yq e '.a.path = "value"' | sudo tee /etc/myfile.tmp sudo mv /etc/myfile.tmp /etc/myfile rm /etc/myfile.tmp ``` @@ -48,7 +48,7 @@ wget https://github.com/mikefarah/yq/releases/download/{VERSION}/{BINARY} -O /us chmod +x /usr/bin/yq ``` -For instance, VERSION=3.4.0 and BINARY=yq_linux_amd64 +For instance, VERSION=4.0.0 and BINARY=yq_linux_amd64 ### Run with Docker @@ -56,7 +56,7 @@ For instance, VERSION=3.4.0 and BINARY=yq_linux_amd64 #### Oneshot use: ```bash -docker run --rm -v "${PWD}":/workdir mikefarah/yq yq [flags] FILE... +docker run --rm -v "${PWD}":/workdir mikefarah/yq [flags] [expression ]FILE... ``` #### Run commands interactively: @@ -69,13 +69,13 @@ It can be useful to have a bash function to avoid typing the whole docker comman ```bash yq() { - docker run --rm -i -v "${PWD}":/workdir mikefarah/yq yq "$@" + docker run --rm -i -v "${PWD}":/workdir mikefarah/yq "$@" } ``` ### Go Get: ``` -GO111MODULE=on go get github.com/mikefarah/yq/v3 +GO111MODULE=on go get github.com/mikefarah/yq/v4 ``` ## Community Supported Installation methods @@ -108,9 +108,8 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq) ## Features - Written in portable go, so you can download a lovely dependency free binary -- [Colorize the output](https://mikefarah.gitbook.io/yq/usage/output-format#colorize-output) -- [Deep read a yaml file with a given path expression](https://mikefarah.gitbook.io/yq/commands/read#basic) -- [List matching paths of a given path expression](https://mikefarah.gitbook.io/yq/commands/read#path-only) +- Colorized yaml output +- [Deep read a yaml file with a given path expression](https://mikefarah.gitbook.io/yq/v/v4.x/traverse) - [Return the lengths of arrays/object/scalars](https://mikefarah.gitbook.io/yq/commands/read#printing-length-of-the-results) - Update a yaml file given a [path expression](https://mikefarah.gitbook.io/yq/commands/write-update#basic) or [script file](https://mikefarah.gitbook.io/yq/commands/write-update#basic) - Update creates any missing entries in the path on the fly diff --git a/go.mod b/go.mod index 9599ee08..ea84d979 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,14 @@ module github.com/mikefarah/yq/v4 require ( - github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect github.com/elliotchance/orderedmap v1.3.0 github.com/fatih/color v1.9.0 github.com/goccy/go-yaml v1.8.1 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/mattn/go-colorable v0.1.7 // indirect github.com/spf13/cobra v1.1.1 - github.com/spf13/pflag v1.0.5 // indirect github.com/timtadh/data-structures v0.5.3 // indirect github.com/timtadh/lexmachine v0.2.2 - github.com/ugorji/go v1.1.4 // indirect - github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 diff --git a/go.sum b/go.sum index 4a0116d2..f2cd58f3 100644 --- a/go.sum +++ b/go.sum @@ -17,7 +17,6 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -27,9 +26,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -76,7 +73,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -120,7 +116,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= @@ -177,8 +172,6 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= @@ -186,7 +179,6 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -200,9 +192,7 @@ github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctf github.com/timtadh/lexmachine v0.2.2 h1:g55RnjdYazm5wnKv59pwFcBJHOyvTPfDEoz21s4PHmY= github.com/timtadh/lexmachine v0.2.2/go.mod h1:GBJvD5OAfRn/gnp92zb9KTgHLB7akKyxmVivoYCcjQI= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -244,7 +234,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -325,7 +314,6 @@ google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBr google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= @@ -344,6 +332,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/yqlib/doc/Assign.md b/pkg/yqlib/doc/Assign.md index f902e029..ce549eb2 100644 --- a/pkg/yqlib/doc/Assign.md +++ b/pkg/yqlib/doc/Assign.md @@ -8,7 +8,7 @@ This will do a similar thing to the plain form, however, the RHS expression is r ## Create yaml file Running ```bash -yq eval --null-input '(.a.b = "cat") | (.x = "frog")' +yq eval --null-input '.a.b = "cat" | .x = "frog"' ``` will output ```yaml @@ -112,7 +112,7 @@ a: ``` then ```bash -yq eval '.a.[] | select(. == "apple") |= "frog"' sample.yml +yq eval '(.a.[] | select(. == "apple")) = "frog"' sample.yml ``` will output ```yaml @@ -130,7 +130,7 @@ Given a sample.yml file of: ``` then ```bash -yq eval '.[] | select(. == "*andy") |= "bogs"' sample.yml +yq eval '(.[] | select(. == "*andy")) = "bogs"' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/doc/Length.md b/pkg/yqlib/doc/Length.md new file mode 100644 index 00000000..dcd76562 --- /dev/null +++ b/pkg/yqlib/doc/Length.md @@ -0,0 +1,54 @@ +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 eval '.a | length' sample.yml +``` +will output +```yaml +3 +``` + +## Map length +returns number of entries + +Given a sample.yml file of: +```yaml +a: cat +c: dog +``` +then +```bash +yq eval '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 eval 'length' sample.yml +``` +will output +```yaml +4 +``` + diff --git a/pkg/yqlib/doc/Pipe.md b/pkg/yqlib/doc/Pipe.md new file mode 100644 index 00000000..b1160d29 --- /dev/null +++ b/pkg/yqlib/doc/Pipe.md @@ -0,0 +1,35 @@ +Pipe the results of an expression into another. Like the bash operator. + +## Simple Pipe +Given a sample.yml file of: +```yaml +a: + b: cat +``` +then +```bash +yq eval '.a | .b' sample.yml +``` +will output +```yaml +cat +``` + +## Multiple updates +Given a sample.yml file of: +```yaml +a: cow +b: sheep +c: same +``` +then +```bash +yq eval '.a = "cat" | .b = "dog"' sample.yml +``` +will output +```yaml +a: cat +b: dog +c: same +``` + diff --git a/pkg/yqlib/doc/headers/Length.md b/pkg/yqlib/doc/headers/Length.md new file mode 100644 index 00000000..7415c182 --- /dev/null +++ b/pkg/yqlib/doc/headers/Length.md @@ -0,0 +1 @@ +Returns the lengths of the nodes. Length is defined according to the type of the node. diff --git a/pkg/yqlib/doc/headers/Pipe.md b/pkg/yqlib/doc/headers/Pipe.md new file mode 100644 index 00000000..6b3c7e84 --- /dev/null +++ b/pkg/yqlib/doc/headers/Pipe.md @@ -0,0 +1 @@ +Pipe the results of an expression into another. Like the bash operator. diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 73be42b2..bec37c96 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -29,6 +29,8 @@ var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOp var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} +var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: PipeOperator} + var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} var AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator} @@ -42,7 +44,8 @@ var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOp var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator} -var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator} + +var ShortPipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator} var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} diff --git a/pkg/yqlib/operator_assign_test.go b/pkg/yqlib/operator_assign_test.go index def448bb..67eacc74 100644 --- a/pkg/yqlib/operator_assign_test.go +++ b/pkg/yqlib/operator_assign_test.go @@ -7,7 +7,7 @@ import ( var assignOperatorScenarios = []expressionScenario{ { description: "Create yaml file", - expression: `(.a.b = "cat") | (.x = "frog")`, + expression: `.a.b = "cat" | .x = "frog"`, expected: []string{ "D0, P[], ()::a:\n b: cat\nx: frog\n", }, @@ -80,7 +80,7 @@ var assignOperatorScenarios = []expressionScenario{ { description: "Update selected results", document: `{a: {b: apple, c: cactus}}`, - expression: `.a.[] | select(. == "apple") |= "frog"`, + expression: `(.a.[] | select(. == "apple")) = "frog"`, expected: []string{ "D0, P[], (doc)::{a: {b: frog, c: cactus}}\n", }, @@ -88,7 +88,7 @@ var assignOperatorScenarios = []expressionScenario{ { description: "Update array values", document: `[candy, apple, sandy]`, - expression: `.[] | select(. == "*andy") |= "bogs"`, + expression: `(.[] | select(. == "*andy")) = "bogs"`, expected: []string{ "D0, P[], (doc)::[bogs, apple, bogs]\n", }, diff --git a/pkg/yqlib/operator_length.go b/pkg/yqlib/operator_length.go new file mode 100644 index 00000000..38c959a9 --- /dev/null +++ b/pkg/yqlib/operator_length.go @@ -0,0 +1,35 @@ +package yqlib + +import ( + "container/list" + "fmt" + + yaml "gopkg.in/yaml.v3" +) + +func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("-- lengthOperation") + var results = list.New() + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + targetNode := UnwrapDoc(candidate.Node) + var length int + switch targetNode.Kind { + case yaml.ScalarNode: + length = len(targetNode.Value) + case yaml.MappingNode: + length = len(targetNode.Content) / 2 + case yaml.SequenceNode: + length = len(targetNode.Content) + default: + length = 0 + } + + node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(lengthCand) + } + + return results, nil +} diff --git a/pkg/yqlib/operator_length_test.go b/pkg/yqlib/operator_length_test.go new file mode 100644 index 00000000..353f2ef8 --- /dev/null +++ b/pkg/yqlib/operator_length_test.go @@ -0,0 +1,42 @@ +package yqlib + +import ( + "testing" +) + +var lengthOperatorScenarios = []expressionScenario{ + { + description: "String length", + subdescription: "returns length of string", + document: `{a: cat}`, + expression: `.a | length`, + expected: []string{ + "D0, P[a], (!!int)::3\n", + }, + }, + { + description: "Map length", + subdescription: "returns number of entries", + document: `{a: cat, c: dog}`, + expression: `length`, + expected: []string{ + "D0, P[], (!!int)::2\n", + }, + }, + { + description: "Array length", + subdescription: "returns number of elements", + document: `[2,4,6,8]`, + expression: `length`, + expected: []string{ + "D0, P[], (!!int)::4\n", + }, + }, +} + +func TestLengthOperatorScenarios(t *testing.T) { + for _, tt := range lengthOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Length", lengthOperatorScenarios) +} diff --git a/pkg/yqlib/operator_multiply.go b/pkg/yqlib/operator_multiply.go index ad19fcf2..dc1af6a3 100644 --- a/pkg/yqlib/operator_multiply.go +++ b/pkg/yqlib/operator_multiply.go @@ -111,7 +111,7 @@ func createTraversalTree(path []interface{}) *PathTreeNode { return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} } return &PathTreeNode{ - Operation: &Operation{OperationType: Pipe}, + Operation: &Operation{OperationType: ShortPipe}, Lhs: createTraversalTree(path[0:1]), Rhs: createTraversalTree(path[1:])} diff --git a/pkg/yqlib/operator_pipe.go b/pkg/yqlib/operator_pipe.go new file mode 100644 index 00000000..189bd2de --- /dev/null +++ b/pkg/yqlib/operator_pipe.go @@ -0,0 +1,11 @@ +package yqlib + +import "container/list" + +func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + if err != nil { + return nil, err + } + return d.GetMatchingNodes(lhs, pathNode.Rhs) +} diff --git a/pkg/yqlib/operator_pipe_test.go b/pkg/yqlib/operator_pipe_test.go new file mode 100644 index 00000000..c9df9e93 --- /dev/null +++ b/pkg/yqlib/operator_pipe_test.go @@ -0,0 +1,31 @@ +package yqlib + +import ( + "testing" +) + +var pipeOperatorScenarios = []expressionScenario{ + { + description: "Simple Pipe", + document: `{a: {b: cat}}`, + expression: `.a | .b`, + expected: []string{ + "D0, P[a b], (!!str)::cat\n", + }, + }, + { + description: "Multiple updates", + document: `{a: cow, b: sheep, c: same}`, + expression: `.a = "cat" | .b = "dog"`, + expected: []string{ + "D0, P[], (doc)::{a: cat, b: dog, c: same}\n", + }, + }, +} + +func TestPipeOperatorScenarios(t *testing.T) { + for _, tt := range pipeOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Pipe", pipeOperatorScenarios) +} diff --git a/pkg/yqlib/operators.go b/pkg/yqlib/operators.go index f99bca11..f9f16122 100644 --- a/pkg/yqlib/operators.go +++ b/pkg/yqlib/operators.go @@ -2,7 +2,6 @@ package yqlib import ( "container/list" - "fmt" "gopkg.in/yaml.v3" ) @@ -20,14 +19,6 @@ func EmptyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat return list.New(), nil } -func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) - if err != nil { - return nil, err - } - return d.GetMatchingNodes(lhs, pathNode.Rhs) -} - func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode { valString := "true" if !value { @@ -42,29 +33,3 @@ func nodeToMap(candidate *CandidateNode) *list.List { elMap.PushBack(candidate) return elMap } - -func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { - log.Debugf("-- lengthOperation") - var results = list.New() - - for el := matchMap.Front(); el != nil; el = el.Next() { - candidate := el.Value.(*CandidateNode) - var length int - switch candidate.Node.Kind { - case yaml.ScalarNode: - length = len(candidate.Node.Value) - case yaml.MappingNode: - length = len(candidate.Node.Content) / 2 - case yaml.SequenceNode: - length = len(candidate.Node.Content) - default: - length = 0 - } - - node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"} - lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} - results.PushBack(lengthCand) - } - - return results, nil -} diff --git a/pkg/yqlib/path_postfix.go b/pkg/yqlib/path_postfix.go index bac3096e..a26adbcc 100644 --- a/pkg/yqlib/path_postfix.go +++ b/pkg/yqlib/path_postfix.go @@ -56,7 +56,7 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, er // now we should have [] as the last element on the opStack, get rid of it opStack = opStack[0 : len(opStack)-1] //and append a collect to the opStack - opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: Pipe}}) + opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: ShortPipe}}) opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: collectOperator}}) case CloseBracket: for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != OpenBracket { diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index e6cc4b17..cea7cdda 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -318,7 +318,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { if index != len(tokens)-1 && token.CheckForPostTraverse && tokens[index+1].TokenType == OperationToken && tokens[index+1].Operation.OperationType == TraversePath { - op := &Operation{OperationType: Pipe, Value: "PIPE"} + op := &Operation{OperationType: ShortPipe, Value: "PIPE"} postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op}) } } From 2c3357702d3d59d7a223e44d9b55b84aaf51bb6c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 1 Dec 2020 18:08:41 +1100 Subject: [PATCH 121/129] clarified pipe parsing tests --- pkg/yqlib/lib.go | 4 +- pkg/yqlib/path_parse_test.go | 99 +++++++++--------------------------- 2 files changed, 25 insertions(+), 78 deletions(-) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index bec37c96..ce6367e9 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -21,8 +21,6 @@ type OperationType struct { // operators TODO: // - cookbook doc for common things // - mergeEmpty (sets only if the document is empty, do I do that now?) -// - compare ?? -// - validate ?? var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator} var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator} @@ -45,7 +43,7 @@ var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOp var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator} -var ShortPipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator} +var ShortPipe = &OperationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator} var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} diff --git a/pkg/yqlib/path_parse_test.go b/pkg/yqlib/path_parse_test.go index ab8ccfb5..db118a1f 100644 --- a/pkg/yqlib/path_parse_test.go +++ b/pkg/yqlib/path_parse_test.go @@ -11,58 +11,26 @@ var pathTests = []struct { path string expectedTokens []interface{} expectedPostFix []interface{} -}{ // TODO: Ensure ALL documented examples have tests! sheesh - // {"len(.)", append(make([]interface{}, 0), "LENGTH", "(", "SELF", ")")}, - // {"\"len\"(.)", append(make([]interface{}, 0), "len", "TRAVERSE", "(", "SELF", ")")}, - // {".a OR (.b OR .c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")}, - // {"a OR (b OR c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")}, - // {"a .- (b OR c)", append(make([]interface{}, 0), "a", " .- ", "(", "b", "OR", "c", ")")}, - // {"(animal==3)", append(make([]interface{}, 0), "(", "animal", "==", int64(3), ")")}, - // {"(animal==f3)", append(make([]interface{}, 0), "(", "animal", "==", "f3", ")")}, - // {"apples.BANANAS", append(make([]interface{}, 0), "apples", "TRAVERSE", "BANANAS")}, - // {"appl*.BANA*", append(make([]interface{}, 0), "appl*", "TRAVERSE", "BANA*")}, - // {"a.b.**", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "**")}, - // {"a.\"=\".frog", append(make([]interface{}, 0), "a", "TRAVERSE", "=", "TRAVERSE", "frog")}, - // {"a.b.*", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "*")}, - // {"a.b.thin*", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "thin*")}, - // {".a.b.[0]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", int64(0))}, - // {".a.b.[]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "[]")}, - // {".a.b.[+]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "[+]")}, - // {".a.b.[-12]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", int64(-12))}, - // {".a.b.0", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "0")}, - // {".a", append(make([]interface{}, 0), "a")}, - // {".\"a.b\".c", append(make([]interface{}, 0), "a.b", "TRAVERSE", "c")}, - // {`.b."foo.bar"`, append(make([]interface{}, 0), "b", "TRAVERSE", "foo.bar")}, - // {`f | . == *og | length`, append(make([]interface{}, 0), "f", "TRAVERSE", "SELF", "EQUALS", "*og", "TRAVERSE", "LENGTH")}, - // {`.a, .b`, append(make([]interface{}, 0), "a", "OR", "b")}, - // {`[.a, .b]`, append(make([]interface{}, 0), "[", "a", "OR", "b", "]")}, - // {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")}, - // {`.a.[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")}, - // {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")}, - // { - // `["cat"]`, - // append(make([]interface{}, 0), "[", "cat (string)", "]"), - // append(make([]interface{}, 0), "cat (string)", "COLLECT", "PIPE"), - // }, +}{ { `[]`, append(make([]interface{}, 0), "[", "]"), - append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"), + append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"), }, { `[3]`, append(make([]interface{}, 0), "[", "3 (int64)", "]"), - append(make([]interface{}, 0), "3 (int64)", "COLLECT", "PIPE"), + append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"), }, { `d0.a`, - append(make([]interface{}, 0), "d0", "PIPE", "a"), - append(make([]interface{}, 0), "d0", "a", "PIPE"), + append(make([]interface{}, 0), "d0", "SHORT_PIPE", "a"), + append(make([]interface{}, 0), "d0", "a", "SHORT_PIPE"), }, { - `.a | (.[].b == "apple")`, - append(make([]interface{}, 0), "a", "PIPE", "(", "[]", "PIPE", "b", "EQUALS", "apple (string)", ")"), - append(make([]interface{}, 0), "a", "[]", "b", "PIPE", "apple (string)", "EQUALS", "PIPE"), + `.a | .[].b == "apple"`, + append(make([]interface{}, 0), "a", "PIPE", "[]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"), + append(make([]interface{}, 0), "a", "[]", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"), }, { `.[] | select(. == "*at")`, @@ -72,12 +40,12 @@ var pathTests = []struct { { `[true]`, append(make([]interface{}, 0), "[", "true (bool)", "]"), - append(make([]interface{}, 0), "true (bool)", "COLLECT", "PIPE"), + append(make([]interface{}, 0), "true (bool)", "COLLECT", "SHORT_PIPE"), }, { `[true, false]`, append(make([]interface{}, 0), "[", "true (bool)", "UNION", "false (bool)", "]"), - append(make([]interface{}, 0), "true (bool)", "false (bool)", "UNION", "COLLECT", "PIPE"), + append(make([]interface{}, 0), "true (bool)", "false (bool)", "UNION", "COLLECT", "SHORT_PIPE"), }, { `"mike": .a`, @@ -92,27 +60,27 @@ var pathTests = []struct { { `{"mike": .a}`, append(make([]interface{}, 0), "{", "mike (string)", "CREATE_MAP", "a", "}"), - append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"), + append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"), }, { `{.a: "mike"}`, append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "mike (string)", "}"), - append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"), + append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"), }, { `{.a: .c, .b.[]: .f.g.[]}`, - append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"), - append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"), + append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "SHORT_PIPE", "[]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "SHORT_PIPE", "[]", "}"), + append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "SHORT_PIPE", "f", "g", "SHORT_PIPE", "[]", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"), }, { `explode(.a.b)`, - append(make([]interface{}, 0), "EXPLODE", "(", "a", "PIPE", "b", ")"), - append(make([]interface{}, 0), "a", "b", "PIPE", "EXPLODE"), + append(make([]interface{}, 0), "EXPLODE", "(", "a", "SHORT_PIPE", "b", ")"), + append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "EXPLODE"), }, { `.a.b style="folded"`, - append(make([]interface{}, 0), "a", "PIPE", "b", "ASSIGN_STYLE", "folded (string)"), - append(make([]interface{}, 0), "a", "b", "PIPE", "folded (string)", "ASSIGN_STYLE"), + append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_STYLE", "folded (string)"), + append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "folded (string)", "ASSIGN_STYLE"), }, { `tag == "str"`, @@ -135,11 +103,11 @@ var pathTests = []struct { append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"), }, - // { - // `.a.b tag="!!str"`, - // append(make([]interface{}, 0), "EXPLODE", "(", "a", "PIPE", "b", ")"), - // append(make([]interface{}, 0), "a", "b", "PIPE", "EXPLODE"), - // }, + { + `.a.b tag="!!str"`, + append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"), + append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "!!str (string)", "ASSIGN_TAG"), + }, { `""`, append(make([]interface{}, 0), " (string)"), @@ -153,27 +121,8 @@ var pathTests = []struct { { `{}`, append(make([]interface{}, 0), "{", "}"), - append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "PIPE"), + append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"), }, - - // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, - // {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")}, - // {".animals | (.==c*)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "c*", ")")}, - // {"animals(a.b==c*)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "a", "TRAVERSE", "b", "==", "c*", ")")}, - // {"animals.(a.b==c*)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "a", "TRAVERSE", "b", "==", "c*", ")")}, - // {"(a.b==c*).animals", append(make([]interface{}, 0), "(", "a", "TRAVERSE", "b", "==", "c*", ")", "TRAVERSE", "animals")}, - // {"(a.b==c*)animals", append(make([]interface{}, 0), "(", "a", "TRAVERSE", "b", "==", "c*", ")", "TRAVERSE", "animals")}, - // {"[1].a.d", append(make([]interface{}, 0), int64(1), "TRAVERSE", "a", "TRAVERSE", "d")}, - // {"[1]a.d", append(make([]interface{}, 0), int64(1), "TRAVERSE", "a", "TRAVERSE", "d")}, - // {"a[0]c", append(make([]interface{}, 0), "a", "TRAVERSE", int64(0), "TRAVERSE", "c")}, - // {"a.[0].c", append(make([]interface{}, 0), "a", "TRAVERSE", int64(0), "TRAVERSE", "c")}, - // {"[0]", append(make([]interface{}, 0), int64(0))}, - // {"0", append(make([]interface{}, 0), int64(0))}, - // {"a.b[+]c", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "[+]", "TRAVERSE", "c")}, - // {"a.cool(s.d.f == cool)", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", " == ", "cool", ")")}, - // {"a.cool.(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", "==", "cool", "OR", "t", "TRAVERSE", "b", "TRAVERSE", "h", "==", "frog", ")", "TRAVERSE", "caterpillar")}, - // {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", "==", "cool", "and", "t", "TRAVERSE", "b", "TRAVERSE", "h", "==", "frog", ")", "TRAVERSE", "*")}, - // {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", "==", "cool", "and", "t", "TRAVERSE", "b", "TRAVERSE", "h", "==", "frog", ")", "TRAVERSE", "th*")}, } var tokeniser = NewPathTokeniser() From a3e422ff769d4308f74cf7a1276cfc23e99bde97 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 1 Dec 2020 18:10:10 +1100 Subject: [PATCH 122/129] added another test --- pkg/yqlib/path_parse_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/yqlib/path_parse_test.go b/pkg/yqlib/path_parse_test.go index db118a1f..dad1ede7 100644 --- a/pkg/yqlib/path_parse_test.go +++ b/pkg/yqlib/path_parse_test.go @@ -32,6 +32,11 @@ var pathTests = []struct { append(make([]interface{}, 0), "a", "PIPE", "[]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"), append(make([]interface{}, 0), "a", "[]", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"), }, + { + `(.a | .[].b) == "apple"`, + append(make([]interface{}, 0), "(", "a", "PIPE", "[]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"), + append(make([]interface{}, 0), "a", "[]", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"), + }, { `.[] | select(. == "*at")`, append(make([]interface{}, 0), "[]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"), From db60746e4e38652b91ade3b7bd08da3e82c934c3 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 9 Dec 2020 12:15:14 +1100 Subject: [PATCH 123/129] Can now properly handle .a[] expressions --- pkg/yqlib/path_parse_test.go | 15 +++++++++ pkg/yqlib/path_tokeniser.go | 65 ++++++++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/pkg/yqlib/path_parse_test.go b/pkg/yqlib/path_parse_test.go index dad1ede7..17673114 100644 --- a/pkg/yqlib/path_parse_test.go +++ b/pkg/yqlib/path_parse_test.go @@ -17,6 +17,21 @@ var pathTests = []struct { append(make([]interface{}, 0), "[", "]"), append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"), }, + { + `.a[]`, + append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]"), + append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE"), + }, + { + `.a.[]`, + append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]"), + append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE"), + }, + { + `.a[].c`, + append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]", "SHORT_PIPE", "c"), + append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE", "c", "SHORT_PIPE"), + }, { `[3]`, append(make([]interface{}, 0), "[", "3 (int64)", "]"), diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index cea7cdda..a51d0261 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -21,6 +21,7 @@ const ( CloseCollect OpenCollectObject CloseCollectObject + SplatOrEmptyCollect ) type Token struct { @@ -47,6 +48,8 @@ func (t *Token) toString() string { return "{" } else if t.TokenType == CloseCollectObject { return "}" + } else if t.TokenType == SplatOrEmptyCollect { + return "[]?" } else { return "NFI" } @@ -247,6 +250,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`"[^ "]*"`), stringValue(true)) + lexer.Add([]byte(`\[\]`), literalToken(SplatOrEmptyCollect, true)) + lexer.Add([]byte(`\[`), literalToken(OpenCollect, false)) lexer.Add([]byte(`\]`), literalToken(CloseCollect, true)) lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false)) @@ -301,28 +306,54 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { skipNextToken := false - for index, token := range tokens { + for index := range tokens { if skipNextToken { skipNextToken = false } else { - - if index != len(tokens)-1 && token.AssignOperation != nil && - tokens[index+1].TokenType == OperationToken && - tokens[index+1].Operation.OperationType == Assign { - token.Operation = token.AssignOperation - skipNextToken = true - } - - postProcessedTokens = append(postProcessedTokens, token) - - if index != len(tokens)-1 && token.CheckForPostTraverse && - tokens[index+1].TokenType == OperationToken && - tokens[index+1].Operation.OperationType == TraversePath { - op := &Operation{OperationType: ShortPipe, Value: "PIPE"} - postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op}) - } + postProcessedTokens, skipNextToken = p.handleToken(tokens, index, postProcessedTokens) } } return postProcessedTokens, nil } + +func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTokens []*Token) (tokensAccum []*Token, skipNextToken bool) { + skipNextToken = false + token := tokens[index] + if token.TokenType == SplatOrEmptyCollect { + if index > 0 && tokens[index-1].TokenType == OperationToken && + tokens[index-1].Operation.OperationType == TraversePath { + // must be a splat without a preceding dot , e.g. .a[] + // lets put a pipe in front of it, and convert it to a traverse "[]" token + pipeOp := &Operation{OperationType: ShortPipe, Value: "PIPE"} + + postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: pipeOp}) + + traverseOp := &Operation{OperationType: TraversePath, Value: "[]", StringValue: "[]"} + token = &Token{TokenType: OperationToken, Operation: traverseOp, CheckForPostTraverse: true} + + } else { + // gotta be a collect empty array, we need to split this into two tokens + // one OpenCollect, the other CloseCollect + postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OpenCollect}) + token = &Token{TokenType: CloseCollect, CheckForPostTraverse: true} + } + } + + if index != len(tokens)-1 && token.AssignOperation != nil && + tokens[index+1].TokenType == OperationToken && + tokens[index+1].Operation.OperationType == Assign { + token.Operation = token.AssignOperation + skipNextToken = true + } + + postProcessedTokens = append(postProcessedTokens, token) + + if index != len(tokens)-1 && token.CheckForPostTraverse && + tokens[index+1].TokenType == OperationToken && + tokens[index+1].Operation.OperationType == TraversePath { + op := &Operation{OperationType: ShortPipe, Value: "PIPE"} + postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op}) + } + return postProcessedTokens, skipNextToken +} From 09a9e1e7f0c61fdf0933192c2873a749f05e8734 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 15 Dec 2020 14:33:50 +1100 Subject: [PATCH 124/129] handle multiple document streams --- pkg/yqlib/printer.go | 8 ++++--- pkg/yqlib/printer_test.go | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index 1da0728a..340abef6 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -22,6 +22,7 @@ type resultsPrinter struct { writer io.Writer firstTimePrinting bool previousDocIndex uint + previousFileIndex int printedMatches bool } @@ -89,19 +90,20 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { return nil } if p.firstTimePrinting { - p.previousDocIndex = matchingNodes.Front().Value.(*CandidateNode).Document + node := matchingNodes.Front().Value.(*CandidateNode) + p.previousDocIndex = node.Document + p.previousFileIndex = node.FileIndex p.firstTimePrinting = false } for el := matchingNodes.Front(); el != nil; el = el.Next() { mappedDoc := el.Value.(*CandidateNode) log.Debug("-- print sep logic: p.firstTimePrinting: %v, previousDocIndex: %v, mappedDoc.Document: %v, printDocSeparators: %v", p.firstTimePrinting, p.previousDocIndex, mappedDoc.Document, p.printDocSeparators) - if (p.previousDocIndex != mappedDoc.Document) && p.printDocSeparators { + if (p.previousDocIndex != mappedDoc.Document || p.previousFileIndex != mappedDoc.FileIndex) && p.printDocSeparators { log.Debug("-- writing doc sep") if err := p.writeString(bufferedWriter, "---\n"); err != nil { return err } - } if err := p.printNode(mappedDoc.Node, bufferedWriter); err != nil { diff --git a/pkg/yqlib/printer_test.go b/pkg/yqlib/printer_test.go index f54af3c3..aaafb448 100644 --- a/pkg/yqlib/printer_test.go +++ b/pkg/yqlib/printer_test.go @@ -52,7 +52,53 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) { writer.Flush() test.AssertResult(t, multiDocSample, output.String()) +} +func TestPrinterMultipleFilesInSequence(t *testing.T) { + var output bytes.Buffer + var writer = bufio.NewWriter(&output) + printer := NewPrinter(writer, false, true, false, 2, true) + + inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0) + if err != nil { + panic(err) + } + + el := inputs.Front() + elNode := el.Value.(*CandidateNode) + elNode.Document = 0 + elNode.FileIndex = 0 + sample1 := nodeToMap(elNode) + + el = el.Next() + elNode = el.Value.(*CandidateNode) + elNode.Document = 0 + elNode.FileIndex = 1 + sample2 := nodeToMap(elNode) + + el = el.Next() + elNode = el.Value.(*CandidateNode) + elNode.Document = 0 + elNode.FileIndex = 2 + sample3 := nodeToMap(elNode) + + err = printer.PrintResults(sample1) + if err != nil { + panic(err) + } + + err = printer.PrintResults(sample2) + if err != nil { + panic(err) + } + + err = printer.PrintResults(sample3) + if err != nil { + panic(err) + } + + writer.Flush() + test.AssertResult(t, multiDocSample, output.String()) } func TestPrinterMultipleDocsInSinglePrint(t *testing.T) { From a96b74e7792c30fbd22a3a8c4279bab2128993ea Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 17 Dec 2020 14:02:54 +1100 Subject: [PATCH 125/129] Added better error reporting --- pkg/yqlib/path_tokeniser.go | 5 +++-- pkg/yqlib/path_tree.go | 11 ++++++++++- pkg/yqlib/path_tree_test.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 pkg/yqlib/path_tree_test.go diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index a51d0261..a3d64e61 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -1,6 +1,7 @@ package yqlib import ( + "fmt" "strconv" lex "github.com/timtadh/lexmachine" @@ -288,7 +289,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { scanner, err := p.lexer.Scanner([]byte(path)) if err != nil { - return nil, err + return nil, fmt.Errorf("Parsing expression: %v", err) } var tokens []*Token for tok, err, eof := scanner.Next(); !eof; tok, err, eof = scanner.Next() { @@ -299,7 +300,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { tokens = append(tokens, token) } if err != nil { - return nil, err + return nil, fmt.Errorf("Parsing expression: %v", err) } } var postProcessedTokens = make([]*Token, 0) diff --git a/pkg/yqlib/path_tree.go b/pkg/yqlib/path_tree.go index 3cb5ada8..a9d1ea06 100644 --- a/pkg/yqlib/path_tree.go +++ b/pkg/yqlib/path_tree.go @@ -1,6 +1,9 @@ package yqlib -import "fmt" +import ( + "fmt" + "strings" +) var myPathTokeniser = NewPathTokeniser() var myPathPostfixer = NewPathPostFixer() @@ -49,10 +52,16 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*Operation) (*PathTreeNod if Operation.OperationType.NumArgs > 0 { numArgs := Operation.OperationType.NumArgs if numArgs == 1 { + if len(stack) < 1 { + return nil, fmt.Errorf("'%v' expects 1 arg but received none", strings.TrimSpace(Operation.StringValue)) + } remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1] newNode.Rhs = rhs stack = remaining } else if numArgs == 2 { + if len(stack) < 2 { + return nil, fmt.Errorf("'%v' expects 2 args but there is %v", strings.TrimSpace(Operation.StringValue), len(stack)) + } remaining, lhs, rhs := stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1] newNode.Lhs = lhs newNode.Rhs = rhs diff --git a/pkg/yqlib/path_tree_test.go b/pkg/yqlib/path_tree_test.go new file mode 100644 index 00000000..bd5d618e --- /dev/null +++ b/pkg/yqlib/path_tree_test.go @@ -0,0 +1,37 @@ +package yqlib + +import ( + "testing" + + "github.com/mikefarah/yq/v4/test" +) + +func TestPathTreeNoArgsForTwoArgOp(t *testing.T) { + _, err := treeCreator.ParsePath("=") + test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error()) +} + +func TestPathTreeOneLhsArgsForTwoArgOp(t *testing.T) { + _, err := treeCreator.ParsePath(".a =") + test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error()) +} + +func TestPathTreeOneRhsArgsForTwoArgOp(t *testing.T) { + _, err := treeCreator.ParsePath("= .a") + test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error()) +} + +func TestPathTreeTwoArgsForTwoArgOp(t *testing.T) { + _, err := treeCreator.ParsePath(".a = .b") + test.AssertResultComplex(t, nil, err) +} + +func TestPathTreeNoArgsForOneArgOp(t *testing.T) { + _, err := treeCreator.ParsePath("explode") + test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error()) +} + +func TestPathTreeOneArgForOneArgOp(t *testing.T) { + _, err := treeCreator.ParsePath("explode(.)") + test.AssertResultComplex(t, nil, err) +} From bb088f6aa2c91ade549d3df2c4f9afd1059cec9a Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 17 Dec 2020 14:19:46 +1100 Subject: [PATCH 126/129] Added better error reporting --- pkg/yqlib/path_tree.go | 2 +- pkg/yqlib/path_tree_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/yqlib/path_tree.go b/pkg/yqlib/path_tree.go index a9d1ea06..4ea6f42f 100644 --- a/pkg/yqlib/path_tree.go +++ b/pkg/yqlib/path_tree.go @@ -71,7 +71,7 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*Operation) (*PathTreeNod stack = append(stack, &newNode) } if len(stack) != 1 { - return nil, fmt.Errorf("expected stack to have 1 thing but its %v", stack) + return nil, fmt.Errorf("expected end of expression but found '%v', please check expression syntax", strings.TrimSpace(stack[1].Operation.StringValue)) } return stack[0], nil } diff --git a/pkg/yqlib/path_tree_test.go b/pkg/yqlib/path_tree_test.go index bd5d618e..6b2ab713 100644 --- a/pkg/yqlib/path_tree_test.go +++ b/pkg/yqlib/path_tree_test.go @@ -35,3 +35,8 @@ func TestPathTreeOneArgForOneArgOp(t *testing.T) { _, err := treeCreator.ParsePath("explode(.)") test.AssertResultComplex(t, nil, err) } + +func TestPathTreeExtraArgs(t *testing.T) { + _, err := treeCreator.ParsePath("sortKeys(.) explode(.)") + test.AssertResultComplex(t, "expected end of expression but found 'explode', please check expression syntax", err.Error()) +} From 1e8f755e7ca00754d8b2bb96e7e7b4040665aa68 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 20 Dec 2020 12:37:04 +1100 Subject: [PATCH 127/129] Updating readme for imminent v4 release --- README.md | 57 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 393ebeaf..91bdeeea 100644 --- a/README.md +++ b/README.md @@ -108,21 +108,17 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq) ## Features - Written in portable go, so you can download a lovely dependency free binary +- Uses similar syntax as `jq` but works with YAML and JSON files +- Fully supports multi document yaml files - Colorized yaml output -- [Deep read a yaml file with a given path expression](https://mikefarah.gitbook.io/yq/v/v4.x/traverse) -- [Return the lengths of arrays/object/scalars](https://mikefarah.gitbook.io/yq/commands/read#printing-length-of-the-results) -- Update a yaml file given a [path expression](https://mikefarah.gitbook.io/yq/commands/write-update#basic) or [script file](https://mikefarah.gitbook.io/yq/commands/write-update#basic) -- Update creates any missing entries in the path on the fly -- Deeply [compare](https://mikefarah.gitbook.io/yq/commands/compare) yaml files -- Keeps yaml formatting and comments when updating -- [Validate a yaml file](https://mikefarah.gitbook.io/yq/commands/validate) -- Create a yaml file given a [deep path and value](https://mikefarah.gitbook.io/yq/commands/create#creating-a-simple-yaml-file) or a [script file](https://mikefarah.gitbook.io/yq/commands/create#creating-using-a-create-script) -- [Prefix a path to a yaml file](https://mikefarah.gitbook.io/yq/commands/prefix) -- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/usage/convert) -- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/commands/read#from-stdin) -- [Merge](https://mikefarah.gitbook.io/yq/commands/merge) multiple yaml files with various options for [overriding](https://mikefarah.gitbook.io/yq/commands/merge#overwrite-values) and [appending](https://mikefarah.gitbook.io/yq/commands/merge#append-values-with-arrays) -- Supports multiple documents in a single yaml file for [reading](https://mikefarah.gitbook.io/yq/commands/read#multiple-documents), [writing](https://mikefarah.gitbook.io/yq/commands/write-update#multiple-documents) and [merging](https://mikefarah.gitbook.io/yq/commands/merge#multiple-documents) -- General shell completion scripts (bash/zsh/fish/powershell) (https://mikefarah.gitbook.io/yq/commands/shell-completion) +- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/v/v4.x/traverse) +- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/v/v4.x/sort-keys) +- [Update yaml inplace](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate#flags) +- [Complex expressions to select and update](https://mikefarah.gitbook.io/yq/v/v4.x/select#select-and-update-matching-values-in-map) +- Keeps yaml formatting and comments when updating (though there are issues with whitespace) +- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert) +- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate) +- General shell completion scripts (bash/zsh/fish/powershell) (https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion) ## [Usage](https://mikefarah.gitbook.io/yq/) @@ -134,32 +130,35 @@ Usage: yq [command] Available Commands: - compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value' - delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)' + eval Apply expression to each document in each yaml file given in sequence + eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once help Help about any command - merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml - new yq n [--script/-s script_file] a.b.c newValue - prefix yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c - read yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value' - shell-completion Generates shell completion scripts - validate yq v sample.yaml - write yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue + shell-completion Generate completion script Flags: - -C, --colors print with colors + -C, --colors force print with colors + -e, --exit-status set exit status if there are no matches or null or false is returned -h, --help help for yq -I, --indent int sets indent level for output (default 2) - -P, --prettyPrint pretty print - -j, --tojson output as json. By default it prints a json document in one line, use the prettyPrint flag to print a formatted doc. + -i, --inplace update the yaml file inplace of first yaml file given. + -M, --no-colors force print with no colors + -N, --no-doc Don't print document separators (---) + -n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch. + -j, --tojson output as json. Set indent to 0 to print json in one line. -v, --verbose verbose mode -V, --version Print version information and quit Use "yq [command] --help" for more information about a command. ``` -## Upgrade from V2 -If you've been using v2 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/upgrading-from-v2). +Simple Example: + +```bash +yq e '.a.b | length' f1.yml f2.yml +``` + +## Upgrade from V3 +If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/v/v4.x/upgrading-from-v3). ## 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) -- You cannot (yet) select multiple paths/keys from the yaml to be printed out (https://github.com/mikefarah/yq/issues/287) From f39c57fed989060353f950f919108a7f04b25807 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 20 Dec 2020 12:39:42 +1100 Subject: [PATCH 128/129] Updating readme for imminent v4 release --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 91bdeeea..d6e386b5 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq) - Colorized yaml output - [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/v/v4.x/traverse) - [Sort yaml by keys](https://mikefarah.gitbook.io/yq/v/v4.x/sort-keys) +- Manipulate yaml [comments](https://app.gitbook.com/@mikefarah/s/yq/v/v4.x/comment-operators), [styling](https://app.gitbook.com/@mikefarah/s/yq/v/v4.x/style), [tags](https://app.gitbook.com/@mikefarah/s/yq/v/v4.x/tag) and [anchors](https://app.gitbook.com/@mikefarah/s/yq/v/v4.x/explode). - [Update yaml inplace](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate#flags) - [Complex expressions to select and update](https://mikefarah.gitbook.io/yq/v/v4.x/select#select-and-update-matching-values-in-map) - Keeps yaml formatting and comments when updating (though there are issues with whitespace) From 2577fe5425d34642a4572147317d2ea655187187 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 20 Dec 2020 13:29:12 +1100 Subject: [PATCH 129/129] Increment version --- cmd/version.go | 2 +- snap/snapcraft.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/version.go b/cmd/version.go index f95a019e..167f356d 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -11,7 +11,7 @@ var ( GitDescribe string // Version is main version number that is being run at the moment. - Version = "4.0.0-beta1" + Version = "4.0.0" // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 22c90f2c..e8c60978 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: yq -version: '4.0.0-beta1' +version: '4.0.0' summary: A lightweight and portable command-line YAML processor description: | The aim of the project is to be the jq or sed of yaml files.