diff --git a/pkg/yqlib/doc/Alternative (Default value).md b/pkg/yqlib/doc/Alternative (Default value).md new file mode 100644 index 00000000..6521c378 --- /dev/null +++ b/pkg/yqlib/doc/Alternative (Default value).md @@ -0,0 +1,73 @@ +This operator is used to provide alternative (or default) values when a particular expression is either null or false. + +## LHS is defined +Given a sample.yml file of: +```yaml +a: bridge +``` +then +```bash +yq eval '.a // "hello"' sample.yml +``` +will output +```yaml +bridge +``` + +## LHS is not defined +Given a sample.yml file of: +```yaml +{} +``` +then +```bash +yq eval '.a // "hello"' sample.yml +``` +will output +```yaml +hello +``` + +## LHS is null +Given a sample.yml file of: +```yaml +a: ~ +``` +then +```bash +yq eval '.a // "hello"' sample.yml +``` +will output +```yaml +hello +``` + +## LHS is false +Given a sample.yml file of: +```yaml +a: false +``` +then +```bash +yq eval '.a // "hello"' sample.yml +``` +will output +```yaml +hello +``` + +## RHS is an expression +Given a sample.yml file of: +```yaml +a: false +b: cat +``` +then +```bash +yq eval '.a // .b' sample.yml +``` +will output +```yaml +cat +``` + diff --git a/pkg/yqlib/doc/headers/Alternative (Default value).md b/pkg/yqlib/doc/headers/Alternative (Default value).md new file mode 100644 index 00000000..1f601398 --- /dev/null +++ b/pkg/yqlib/doc/headers/Alternative (Default value).md @@ -0,0 +1 @@ +This operator is used to provide alternative (or default) values when a particular expression is either null or false. diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index ce6367e9..c1d7dd37 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -39,6 +39,7 @@ var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedenc var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator} var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator} +var Alternative = &OperationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 45, Handler: AlternativeOperator} 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/oeprator_alternative_test.go b/pkg/yqlib/oeprator_alternative_test.go new file mode 100644 index 00000000..a2661c71 --- /dev/null +++ b/pkg/yqlib/oeprator_alternative_test.go @@ -0,0 +1,55 @@ +package yqlib + +import ( + "testing" +) + +var alternativeOperatorScenarios = []expressionScenario{ + { + description: "LHS is defined", + expression: `.a // "hello"`, + document: `{a: bridge}`, + expected: []string{ + "D0, P[a], (!!str)::bridge\n", + }, + }, + { + description: "LHS is not defined", + expression: `.a // "hello"`, + document: `{}`, + expected: []string{ + "D0, P[], (!!str)::hello\n", + }, + }, + { + description: "LHS is null", + expression: `.a // "hello"`, + document: `{a: ~}`, + expected: []string{ + "D0, P[], (!!str)::hello\n", + }, + }, + { + description: "LHS is false", + expression: `.a // "hello"`, + document: `{a: false}`, + expected: []string{ + "D0, P[], (!!str)::hello\n", + }, + }, + { + description: "RHS is an expression", + expression: `.a // .b`, + document: `{a: false, b: cat}`, + expected: []string{ + "D0, P[b], (!!str)::cat\n", + }, + }, +} + +func TestAlternativeOperatorScenarios(t *testing.T) { + for _, tt := range alternativeOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Alternative (Default value)", alternativeOperatorScenarios) +} diff --git a/pkg/yqlib/operator_alternative.go b/pkg/yqlib/operator_alternative.go new file mode 100644 index 00000000..9f8d97f5 --- /dev/null +++ b/pkg/yqlib/operator_alternative.go @@ -0,0 +1,28 @@ +package yqlib + +import ( + "container/list" +) + +// corssFunction no matches +// can boolean use crossfunction + +func AlternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("-- alternative") + return crossFunction(d, matchingNodes, pathNode, alternativeFunc) +} + +func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + lhs.Node = UnwrapDoc(lhs.Node) + rhs.Node = UnwrapDoc(rhs.Node) + log.Debugf("Alternative LHS: %v", lhs.Node.Tag) + log.Debugf("- RHS: %v", rhs.Node.Tag) + + isTrue, err := isTruthy(lhs) + if err != nil { + return nil, err + } else if isTrue { + return lhs, nil + } + return rhs, nil +} diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index a3d64e61..bcb7cc02 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -201,6 +201,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`or`), opToken(Or)) lexer.Add([]byte(`and`), opToken(And)) lexer.Add([]byte(`not`), opToken(Not)) + lexer.Add([]byte(`\/\/`), opToken(Alternative)) lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex))