diff --git a/pkg/yqlib/expression_processing_test.go b/pkg/yqlib/expression_processing_test.go index 3e4b38bf..62947c15 100644 --- a/pkg/yqlib/expression_processing_test.go +++ b/pkg/yqlib/expression_processing_test.go @@ -15,6 +15,26 @@ var pathTests = []struct { expectedTokens []interface{} expectedPostFix []interface{} }{ + { + `envsubst(ne)`, + append(make([]interface{}, 0), "ENVSUBST"), + append(make([]interface{}, 0), "ENVSUBST"), + }, + { + `envsubst(nu)`, + append(make([]interface{}, 0), "ENVSUBST"), + append(make([]interface{}, 0), "ENVSUBST"), + }, + { + `envsubst(nu, ne)`, + append(make([]interface{}, 0), "ENVSUBST"), + append(make([]interface{}, 0), "ENVSUBST"), + }, + { + `envsubst(ne, nu)`, + append(make([]interface{}, 0), "ENVSUBST"), + append(make([]interface{}, 0), "ENVSUBST"), + }, { `[.a, .b]`, append(make([]interface{}, 0), "[", "a", "UNION", "b", "]"), diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index 57c0bde9..b0a256e9 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -507,6 +507,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`strenv\([^\)]+\)`), envOp(true)) lexer.Add([]byte(`env\([^\)]+\)`), envOp(false)) + lexer.Add([]byte(`envsubst\((ne|nu| |,)+\)`), opToken(envsubstOpType)) lexer.Add([]byte(`envsubst`), opToken(envsubstOpType)) lexer.Add([]byte(`\[`), literalToken(openCollect, false)) diff --git a/pkg/yqlib/operator_env.go b/pkg/yqlib/operator_env.go index d4990c34..464e9f2b 100644 --- a/pkg/yqlib/operator_env.go +++ b/pkg/yqlib/operator_env.go @@ -12,6 +12,8 @@ import ( type envOpPreferences struct { StringValue bool + NoUnset bool + NoEmpty bool } func envOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { @@ -52,6 +54,7 @@ func envOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi func envsubstOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { var results = list.New() + preferences := expressionNode.Operation.Preferences.(envOpPreferences) for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -61,7 +64,7 @@ func envsubstOperator(d *dataTreeNavigator, context Context, expressionNode *Exp return Context{}, fmt.Errorf("cannot substitute with %v, can only substitute strings. Hint: Most often you'll want to use '|=' over '=' for this operation", node.Tag) } - value, err := envsubst.String(node.Value) + value, err := envsubst.StringRestricted(node.Value, preferences.NoUnset, preferences.NoEmpty) if err != nil { return Context{}, err } diff --git a/pkg/yqlib/operator_env_test.go b/pkg/yqlib/operator_env_test.go index 000b6faf..e8dfd51a 100644 --- a/pkg/yqlib/operator_env_test.go +++ b/pkg/yqlib/operator_env_test.go @@ -88,6 +88,29 @@ var envOperatorScenarios = []expressionScenario{ "D0, P[], (!!str)::the meows\n", }, }, + { + description: "Replace strings with envsubst(nu), missing variables", + environmentVariables: map[string]string{"myenv": "cat"}, + expression: `"the ${myenvnonexisting} meows" | envsubst`, + expected: []string{ + "D0, P[], (!!str)::the meows\n", + }, + }, + { + description: "Replace strings with envsubst(ne), empty variable", + environmentVariables: map[string]string{"myenv": ""}, + expression: `"the ${myenv} meows" | envsubst`, + expected: []string{ + "D0, P[], (!!str)::the meows\n", + }, + }, + { + description: "Replace strings with envsubst(ne), missing variable", + expression: `"the ${myenv} meows" | envsubst`, + expected: []string{ + "D0, P[], (!!str)::the meows\n", + }, + }, { description: "Replace strings with envsubst, missing variables with defaults", environmentVariables: map[string]string{"myenv": "cat"},