From 77630ca179f94ad028711edfb5cf550f79db924c Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 9 May 2021 13:59:23 +1000 Subject: [PATCH] Added to_entries op --- pkg/yqlib/doc/Entries.md | 37 +++++++++++++++++ pkg/yqlib/expression_tokeniser.go | 1 + pkg/yqlib/lib.go | 1 + pkg/yqlib/operator_entries.go | 67 ++++++++++++++++++++++++++++++ pkg/yqlib/operator_entries_test.go | 31 ++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 pkg/yqlib/doc/Entries.md create mode 100644 pkg/yqlib/operator_entries.go create mode 100644 pkg/yqlib/operator_entries_test.go diff --git a/pkg/yqlib/doc/Entries.md b/pkg/yqlib/doc/Entries.md new file mode 100644 index 00000000..9b91eb97 --- /dev/null +++ b/pkg/yqlib/doc/Entries.md @@ -0,0 +1,37 @@ + +## to_entries Map +Given a sample.yml file of: +```yaml +a: 1 +b: 2 +``` +then +```bash +yq eval 'to_entries' sample.yml +``` +will output +```yaml +- key: a + value: 1 +- key: b + value: 2 +``` + +## to_entries Array +Given a sample.yml file of: +```yaml +- a +- b +``` +then +```bash +yq eval 'to_entries' sample.yml +``` +will output +```yaml +- key: 0 + value: a +- key: 1 + value: b +``` + diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index 1ab33611..6d5a8d85 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -278,6 +278,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`fileIndex`), opToken(getFileIndexOpType)) lexer.Add([]byte(`fi`), opToken(getFileIndexOpType)) lexer.Add([]byte(`path`), opToken(getPathOpType)) + lexer.Add([]byte(`to_entries`), opToken(toEntriesOpType)) lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{LineComment: true})) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 39fc2610..3f0b074b 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -60,6 +60,7 @@ var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator} var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator} +var toEntriesOpType = &operationType{Type: "TO_ENTRIES", NumArgs: 0, Precedence: 50, Handler: toEntriesOperator} var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator} var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator} var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator} diff --git a/pkg/yqlib/operator_entries.go b/pkg/yqlib/operator_entries.go new file mode 100644 index 00000000..0140180e --- /dev/null +++ b/pkg/yqlib/operator_entries.go @@ -0,0 +1,67 @@ +package yqlib + +import ( + "container/list" + "fmt" + yaml "gopkg.in/yaml.v3" +) + +func entrySeqFor(key *yaml.Node, value *yaml.Node) *yaml.Node { + var keyKey = &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "key"} + var valueKey = &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "value"} + + return &yaml.Node{ + Kind: yaml.MappingNode, + Tag: "!!map", + Content: []*yaml.Node{keyKey, key, valueKey, value}, + } +} + +func toEntriesFromMap(candidateNode *CandidateNode) *CandidateNode { + var sequence = &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} + var entriesNode = candidateNode.CreateChild(nil, sequence) + + var contents = unwrapDoc(candidateNode.Node).Content + for index := 0; index < len(contents); index = index + 2 { + key := contents[index] + value := contents[index+1] + + sequence.Content = append(sequence.Content, entrySeqFor(key, value)) + } + return entriesNode +} + +func toEntriesfromSeq(candidateNode *CandidateNode) *CandidateNode { + var sequence = &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} + var entriesNode = candidateNode.CreateChild(nil, sequence) + + var contents = unwrapDoc(candidateNode.Node).Content + for index := 0; index < len(contents); index = index + 1 { + key := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!int", Value: fmt.Sprintf("%v", index)} + value := contents[index] + + sequence.Content = append(sequence.Content, entrySeqFor(key, value)) + } + return entriesNode +} + +func toEntriesOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + var results = list.New() + for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + candidateNode := unwrapDoc(candidate.Node) + + switch candidateNode.Kind { + case yaml.MappingNode: + results.PushBack(toEntriesFromMap(candidate)) + + case yaml.SequenceNode: + results.PushBack(toEntriesfromSeq(candidate)) + default: + return Context{}, fmt.Errorf("%v has no keys", candidate.Node.Tag) + } + } + + return context.ChildContext(results), nil + +} \ No newline at end of file diff --git a/pkg/yqlib/operator_entries_test.go b/pkg/yqlib/operator_entries_test.go new file mode 100644 index 00000000..3aafd2e9 --- /dev/null +++ b/pkg/yqlib/operator_entries_test.go @@ -0,0 +1,31 @@ +package yqlib + +import ( + "testing" +) + +var entriesOperatorScenarios = []expressionScenario{ + { + description: "to_entries Map", + document: `{a: 1, b: 2}`, + expression: `to_entries`, + expected: []string{ + "D0, P[], (!!seq)::- key: a\n value: 1\n- key: b\n value: 2\n", + }, + }, + { + description: "to_entries Array", + document: `[a, b]`, + expression: `to_entries`, + expected: []string{ + "D0, P[], (!!seq)::- key: 0\n value: a\n- key: 1\n value: b\n", + }, + }, +} + +func TestEntriesOperatorScenarios(t *testing.T) { + for _, tt := range entriesOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Entries", entriesOperatorScenarios) +}