diff --git a/core/src/template-string.ts b/core/src/template-string.ts index f37a539fb8..dfc011bd0f 100644 --- a/core/src/template-string.ts +++ b/core/src/template-string.ts @@ -374,6 +374,20 @@ function buildBinaryExpression(head: any, tail: any) { return left !== right } + if (operator === "+") { + if (isNumber(left) && isNumber(right)) { + return left + right + } else if (isArray(left) && isArray(right)) { + return left.concat(right) + } else { + const err = new TemplateStringError( + `Both terms need to be either arrays or numbers for + operator (got ${typeof left} and ${typeof right}).`, + { left, right, operator } + ) + return { _error: err } + } + } + // All other operators require numbers to make sense (we're not gonna allow random JS weirdness) if (!isNumber(left) || !isNumber(right)) { const err = new TemplateStringError( @@ -390,8 +404,6 @@ function buildBinaryExpression(head: any, tail: any) { return left / right case "%": return left % right - case "+": - return left + right case "-": return left - right case "<=": diff --git a/core/test/unit/src/template-string.ts b/core/test/unit/src/template-string.ts index e945e14c89..c70c228c19 100644 --- a/core/test/unit/src/template-string.ts +++ b/core/test/unit/src/template-string.ts @@ -460,6 +460,26 @@ describe("resolveTemplateString", async () => { ) }) + it("should concatenate two arrays", async () => { + const res = resolveTemplateString("${a + b}", new TestContext({ a: [1], b: [2, 3] })) + expect(res).to.eql([1, 2, 3]) + }) + + it("should add two numbers together", async () => { + const res = resolveTemplateString("${1 + a}", new TestContext({ a: 2 })) + expect(res).to.equal(3) + }) + + it("should throw when using + on number and array", async () => { + return expectError( + () => resolveTemplateString("${a + b}", new TestContext({ a: 123, b: ["a"] })), + (err) => + expect(stripAnsi(err.message)).to.equal( + "Invalid template string (${a + b}): Both terms need to be either arrays or numbers for + operator (got number and object)." + ) + ) + }) + it("should correctly evaluate clauses in parentheses", async () => { const res = resolveTemplateString("${(1 + 2) * (3 + 4)}", new TestContext({})) expect(res).to.equal(21) diff --git a/docs/using-garden/variables-and-templating.md b/docs/using-garden/variables-and-templating.md index fd310d8d64..5729dcf0c6 100644 --- a/docs/using-garden/variables-and-templating.md +++ b/docs/using-garden/variables-and-templating.md @@ -46,8 +46,9 @@ You can use a variety of operators in template string expressions: * Logical: `&&`, `||`, ternary (` ? : `) * Unary: `!` (negation), `typeof` (returns the type of the following value as a string, e.g. `"boolean"` or `"number"`) * Relational: `contains` (to see if an array contains a value, an object contains a key, or a string contains a substring) +* Arrays: `+` -The arithmetic and numeric comparison operators can only be used for numeric literals and keys that resolve to numbers. The equality and logical operators work with any term. +The arithmetic and numeric comparison operators can only be used for numeric literals and keys that resolve to numbers, except the `+` operator which can be used to concatenate two array references. The equality and logical operators work with any term (but be warned that arrays and complex objects aren't currently compared in-depth). Clauses are evaluated in standard precedence order, but you can also use parentheses to control evaluation order (e.g. `${(1 + 2) * (3 + 4)}` evaluates to 21). @@ -97,7 +98,7 @@ The `contains` operator can be used in several ways: * `${var.some-string contains "some"}` checks if the `var.some-string` string includes the substring `"some"`. * `${var.some-object contains "some-key"}` checks if the `var.some-object` object includes the key `"some-key"`. -And the arithmetic operators can be handy when provisioning resources: +The arithmetic operators can be handy when provisioning resources: ```yaml kind: Module @@ -108,6 +109,32 @@ services: ... ``` +```yaml +kind: Module +type: container +... +services: + replicas: ${var.default-replicas + 1} + ... +``` + +And the `+` operator can also be used to concatenate two arrays: + +```yaml +kind: Project +# ... +variables: + some-values: ["a", "b"] + other-values: ["c", "d"] +--- +kind: Module +type: helm +# ... +values: + some-array: ${var.some-values + var.other-values} + ... +``` + ### Multi-line if/else statements In addition to the conditionals described above, you can use if/else blocks. These are particularly handy when templating multi-line strings and generated files in [module templates](./module-templates.md).