From ee900ec9975bc315dd16c037b9cec7c9821f0b31 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Mon, 2 Oct 2023 14:43:12 +1100 Subject: [PATCH] Added kind operator --- pkg/yqlib/doc/operators/kind.md | 77 +++++++++++++++++++++++++++++++++ pkg/yqlib/lexer_participle.go | 1 + pkg/yqlib/lib.go | 1 + pkg/yqlib/operator_kind.go | 39 +++++++++++++++++ pkg/yqlib/operator_kind_test.go | 49 +++++++++++++++++++++ 5 files changed, 167 insertions(+) create mode 100644 pkg/yqlib/doc/operators/kind.md create mode 100644 pkg/yqlib/operator_kind.go create mode 100644 pkg/yqlib/operator_kind_test.go diff --git a/pkg/yqlib/doc/operators/kind.md b/pkg/yqlib/doc/operators/kind.md new file mode 100644 index 00000000..1f65dfbb --- /dev/null +++ b/pkg/yqlib/doc/operators/kind.md @@ -0,0 +1,77 @@ + +## Get kind +Given a sample.yml file of: +```yaml +a: cat +b: 5 +c: 3.2 +e: true +f: [] +g: {} +h: null +``` +then +```bash +yq '.. | kind' sample.yml +``` +will output +```yaml +map +scalar +scalar +scalar +scalar +seq +map +scalar +``` + +## Get kind, ignores custom tags +Unlike tag, kind is not affected by custom tags. + +Given a sample.yml file of: +```yaml +a: !!thing cat +b: !!foo {} +c: !!bar [] +``` +then +```bash +yq '.. | kind' sample.yml +``` +will output +```yaml +map +scalar +map +seq +``` + +## Add comments only to scalars +An example of how you can use kind + +Given a sample.yml file of: +```yaml +a: + b: 5 + c: 3.2 +e: true +f: [] +g: {} +h: null +``` +then +```bash +yq '(.. | select(kind == "scalar")) line_comment = "this is a scalar"' sample.yml +``` +will output +```yaml +a: + b: 5 # this is a scalar + c: 3.2 # this is a scalar +e: true # this is a scalar +f: [] +g: {} +h: null # this is a scalar +``` + diff --git a/pkg/yqlib/lexer_participle.go b/pkg/yqlib/lexer_participle.go index 2352354a..4388752b 100644 --- a/pkg/yqlib/lexer_participle.go +++ b/pkg/yqlib/lexer_participle.go @@ -152,6 +152,7 @@ var participleYqRules = []*participleYqRule{ assignableOp("style", getStyleOpType, assignStyleOpType), assignableOp("tag|type", getTagOpType, assignTagOpType), + simpleOp("kind", getKindOpType), assignableOp("anchor", getAnchorOpType, assignAnchorOpType), assignableOp("alias", getAliasOpType, assignAliasOpType), diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 5fc503ae..756c161e 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -120,6 +120,7 @@ var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Preceden var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator} var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator} var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator} +var getKindOpType = &operationType{Type: "GET_KIND", NumArgs: 0, Precedence: 50, Handler: getKindOperator} var getKeyOpType = &operationType{Type: "GET_KEY", NumArgs: 0, Precedence: 50, Handler: getKeyOperator} var isKeyOpType = &operationType{Type: "IS_KEY", NumArgs: 0, Precedence: 50, Handler: isKeyOperator} diff --git a/pkg/yqlib/operator_kind.go b/pkg/yqlib/operator_kind.go new file mode 100644 index 00000000..da0892c7 --- /dev/null +++ b/pkg/yqlib/operator_kind.go @@ -0,0 +1,39 @@ +package yqlib + +import ( + "container/list" + + yaml "gopkg.in/yaml.v3" +) + +func kindToText(kind yaml.Kind) string { + switch kind { + case yaml.MappingNode: + return "map" + case yaml.SequenceNode: + return "seq" + case yaml.DocumentNode: + return "doc" + case yaml.ScalarNode: + return "scalar" + case yaml.AliasNode: + return "alias" + default: + return "unknown" + } +} + +func getKindOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + log.Debugf("GetKindOperator") + + var results = list.New() + + for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + node := &yaml.Node{Kind: yaml.ScalarNode, Value: kindToText(candidate.Node.Kind), Tag: "!!str"} + result := candidate.CreateReplacement(node) + results.PushBack(result) + } + + return context.ChildContext(results), nil +} diff --git a/pkg/yqlib/operator_kind_test.go b/pkg/yqlib/operator_kind_test.go new file mode 100644 index 00000000..fadf69d8 --- /dev/null +++ b/pkg/yqlib/operator_kind_test.go @@ -0,0 +1,49 @@ +package yqlib + +import ( + "testing" +) + +var kindOperatorScenarios = []expressionScenario{ + { + description: "Get kind", + document: `{a: cat, b: 5, c: 3.2, e: true, f: [], g: {}, h: null}`, + expression: `.. | kind`, + expected: []string{ + "D0, P[], (!!str)::map\n", + "D0, P[a], (!!str)::scalar\n", + "D0, P[b], (!!str)::scalar\n", + "D0, P[c], (!!str)::scalar\n", + "D0, P[e], (!!str)::scalar\n", + "D0, P[f], (!!str)::seq\n", + "D0, P[g], (!!str)::map\n", + "D0, P[h], (!!str)::scalar\n", + }, + }, + { + description: "Get kind, ignores custom tags", + subdescription: "Unlike tag, kind is not affected by custom tags.", + document: `{a: !!thing cat, b: !!foo {}, c: !!bar []}`, + expression: `.. | kind`, + expected: []string{ + "D0, P[], (!!str)::map\n", + "D0, P[a], (!!str)::scalar\n", + "D0, P[b], (!!str)::map\n", + "D0, P[c], (!!str)::seq\n", + }, + }, + { + description: "Add comments only to scalars", + subdescription: "An example of how you can use kind", + document: "a:\n b: 5\n c: 3.2\ne: true\nf: []\ng: {}\nh: null", + expression: `(.. | select(kind == "scalar")) line_comment = "this is a scalar"`, + expected: []string{"D0, P[], (!!map)::a:\n b: 5 # this is a scalar\n c: 3.2 # this is a scalar\ne: true # this is a scalar\nf: []\ng: {}\nh: null # this is a scalar\n"}, + }, +} + +func TestKindOperatorScenarios(t *testing.T) { + for _, tt := range kindOperatorScenarios { + testScenario(t, &tt) + } + documentOperatorScenarios(t, "kind", kindOperatorScenarios) +}