diff --git a/pkg/yqlib/doc/operators/keys.md b/pkg/yqlib/doc/operators/keys.md index 9be768e5..f4ef293a 100644 --- a/pkg/yqlib/doc/operators/keys.md +++ b/pkg/yqlib/doc/operators/keys.md @@ -118,3 +118,43 @@ will output comment on key ``` +## Check node is a key +Given a sample.yml file of: +```yaml +a: + b: + - cat + c: frog +``` +then +```bash +yq '[... | { "p": path | join("."), "isKey": is_key, "tag": tag }]' sample.yml +``` +will output +```yaml +- p: "" + isKey: false + tag: '!!map' +- p: a + isKey: true + tag: '!!str' +- p: a + isKey: false + tag: '!!map' +- p: a.b + isKey: true + tag: '!!str' +- p: a.b + isKey: false + tag: '!!seq' +- p: a.b.0 + isKey: false + tag: '!!str' +- p: a.c + isKey: true + tag: '!!str' +- p: a.c + isKey: false + tag: '!!str' +``` + diff --git a/pkg/yqlib/lexer_participle.go b/pkg/yqlib/lexer_participle.go index a289aa37..61454801 100644 --- a/pkg/yqlib/lexer_participle.go +++ b/pkg/yqlib/lexer_participle.go @@ -123,6 +123,7 @@ var participleYqRules = []*participleYqRule{ simpleOp("keys", keysOpType), simpleOp("key", getKeyOpType), + simpleOp("is_?key", isKeyOpType), simpleOp("file_?name|fileName", getFilenameOpType), simpleOp("file_?index|fileIndex|fi", getFileIndexOpType), diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index d4823dd2..0ff6e806 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -121,6 +121,7 @@ var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 5 var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator} var getKeyOpType = &operationType{Type: "GET_KEY", NumArgs: 0, Precedence: 50, Handler: getKeyOperator} +var isKeyOpType = &operationType{Type: "IS_KEY", NumArgs: 0, Precedence: 50, Handler: isKeyOperator} var getParentOpType = &operationType{Type: "GET_PARENT", NumArgs: 0, Precedence: 50, Handler: getParentOperator} var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator} diff --git a/pkg/yqlib/operator_keys.go b/pkg/yqlib/operator_keys.go index da0c19dd..0e77310f 100644 --- a/pkg/yqlib/operator_keys.go +++ b/pkg/yqlib/operator_keys.go @@ -7,6 +7,20 @@ import ( "gopkg.in/yaml.v3" ) +func isKeyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + log.Debugf("-- isKeyOperator") + + var results = list.New() + + for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + + results.PushBack(createBooleanCandidate(candidate, candidate.IsMapKey)) + } + + return context.ChildContext(results), nil +} + func getKeyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { log.Debugf("-- getKeyOperator") diff --git a/pkg/yqlib/operator_keys_test.go b/pkg/yqlib/operator_keys_test.go index b42306f7..3f821b51 100644 --- a/pkg/yqlib/operator_keys_test.go +++ b/pkg/yqlib/operator_keys_test.go @@ -4,6 +4,32 @@ import ( "testing" ) +var expectedIsKey = `D0, P[], (!!seq)::- p: "" + isKey: false + tag: '!!map' +- p: a + isKey: true + tag: '!!str' +- p: a + isKey: false + tag: '!!map' +- p: a.b + isKey: true + tag: '!!str' +- p: a.b + isKey: false + tag: '!!seq' +- p: a.b.0 + isKey: false + tag: '!!str' +- p: a.c + isKey: true + tag: '!!str' +- p: a.c + isKey: false + tag: '!!str' +` + var keysOperatorScenarios = []expressionScenario{ { description: "Map keys", @@ -75,6 +101,14 @@ var keysOperatorScenarios = []expressionScenario{ "D0, P[a x], (!!str)::comment on key\n", }, }, + { + description: "Check node is a key", + document: "a: \n b: [cat]\n c: frog\n", + expression: `[... | { "p": path | join("."), "isKey": is_key, "tag": tag }]`, + expected: []string{ + expectedIsKey, + }, + }, } func TestKeysOperatorScenarios(t *testing.T) {