Skip to content

Commit

Permalink
feat(templates): allow concatenating arrays with + operator
Browse files Browse the repository at this point in the history
The example from the docs:

```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}
  ...
```
  • Loading branch information
edvald committed Feb 3, 2021
1 parent e8ef6b2 commit 4b8a5bb
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 4 deletions.
16 changes: 14 additions & 2 deletions core/src/template-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 "<=":
Expand Down
20 changes: 20 additions & 0 deletions core/test/unit/src/template-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 29 additions & 2 deletions docs/using-garden/variables-and-templating.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ You can use a variety of operators in template string expressions:
* Logical: `&&`, `||`, ternary (`<test> ? <value if true> : <value if false>`)
* 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).

Expand Down Expand Up @@ -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
Expand All @@ -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).
Expand Down

0 comments on commit 4b8a5bb

Please sign in to comment.