Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bundles/org.openhab.io.yamlcomposer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ children:
- ["doc/basics", "YAML Basics"]
- ["doc/variables", "Variables"]
- ["doc/conditionals", "Conditionals"]
- ["doc/loops", "Loops"]
- ["doc/include", "Include"]
- ["doc/templates", "Templates"]
- ["doc/packages", "Packages"]
Expand All @@ -27,6 +28,7 @@ Each feature addresses a different kind of reuse, composition, or abstraction to
|--------------------------------------------|-----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
| **Variables and Substitution (`${..}`)** | Insert dynamic values or evaluate expressions | Build labels, topics, IDs, or computed values |
| **Conditionals (`!if`)** | Conditionally include or exclude YAML blocks | Enable or disable features when using packages or template flags |
| **Loops (`!for`)** | Generate repeated YAML blocks from a list or map | Create multiple items, channels, or thing definitions from structured data |
| **Include (`!include`)** | Insert the contents of another file | Reuse YAML across files; parameterize reusable blocks |
| **Templates (`!insert`)** | Reuse YAML defined within the same file | Local parameterized blocks; reusable channel or item fragments |
| **Packages** | Bundle multiple top-level sections into one reusable unit | Define reusable device structures containing things, items, metadata; sourced from external files or templates |
Expand All @@ -37,6 +39,7 @@ Each feature has a dedicated documentation page:

- [Variables and Substitution](doc/variables.md)
- [Conditionals](doc/conditionals.md)
- [Loops](doc/loops.md)
- [Include](doc/include.md)
- [Templates](doc/templates.md)
- [Packages](doc/packages.md)
Expand Down
231 changes: 197 additions & 34 deletions bundles/org.openhab.io.yamlcomposer/doc/conditionals.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,193 @@
# Conditionals (!if)

The `!if` tag performs logical branching during the **preprocessing phase**.
Conditional tags perform logical branching during the **preprocessing phase**.

> **Note:** Conditions are evaluated **once** when the YAML file is loaded.
> These are not runtime rules.
> They do not react to live state changes in openHAB.

[[toc]]

## When to Use `!if`
## When to Use Conditional Tags

Use the `!if` tag to adapt your configuration based on the **Resolution Context** (variables defined in the file, injected via `!include` or `!insert`, or [environment globals](variables.md#env-to-access-environment-variables)).
Use conditional tags to adapt your configuration based on the **Resolution Context**.
The Resolution Context includes variables defined in the file, variables injected via `!include` or `!insert`, and [environment globals](variables.md#env-to-access-environment-variables).

Conditional tags are useful for selecting configuration blocks, enabling optional features, or merging additional properties.

- **Conditional Snippets**: choose between alternative configuration blocks or values.
- **Optional Properties**: conditionally merge in additional settings using [merge keys (<<)](merge-keys.md).
- **Multi-Branch Logic**: select one of several possible configuration branches.

## Conditional Forms

The conditional system supports three forms:

- **Key‑Level Form**: uses `!if`, `!elseif` (and aliases `!elsif`, `!elif`), and `!else ~:` as map keys.
- **Mapping Form**: uses `if:`, `then:`, and `else:` keys inside a single mapping.
- **Sequence Form**: uses `if:`, `elseif:`, and `else:` entries inside a list.

All forms evaluate expressions once during preprocessing.

### Differences Between the Three Forms

Each form serves a different purpose and behaves differently when used inside maps or lists.
The table below summarizes the key differences so you can choose the right form before diving into the detailed syntax.

| Feature / Aspect | **Key‑Level Form** | **Mapping Form** | **Sequence Form** |
|:-----------------------|:-----------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Syntax** | `!if <expr>:`<br>`!elseif <expr>:`<br>`!else ~:` | `!if`<br>&nbsp;&nbsp;`if: <expr>`<br>&nbsp;&nbsp;`then: <val>`<br>&nbsp;&nbsp;`else: <val>` | `!if`<br>&nbsp;&nbsp;`- if: <expr>`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`then: <val>`<br>&nbsp;&nbsp;`- elseif: <expr>`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`then: <val>`<br>&nbsp;&nbsp;`- else: <val>` |
| **Behavior in Maps** | Merges nested key‑value pairs directly into the parent map | Resolves to a single value | Resolves to a single value |
| **Behavior in Lists** | Splices items directly into the parent list | Returns a single list element (branch may itself be a list) | Returns a single list element (branch may itself be a list) |
| **Multi‑Branching** | Supported via sibling keys (`!elseif`, `!else ~`) | Single condition (`if` / `then` / `else`) | Multi‑branch entries (`if`, `elseif`, `else`) |
| **Unmatched Fallback** | Inactive branches are omitted entirely | Resolves to `null` | Resolves to `null` |
| **Primary Use Case** | Conditionally merging groups of properties or inserting multiple inline list items | Simple ternary scalar/container assignment | Multi‑branch ternary scalar/container assignment |

## Key‑Level Form

The key‑level form applies conditional tags directly as map keys.
The tags `!if`, `!elseif` (and its aliases `!elsif`, `!elif`), and `!else ~` allow multi‑branch logic using separate map entries.
Each tag evaluates its expression (except `!else ~`, which has no expression).
The nested content (map or list) underneath the selected tag is merged into the parent structure.

Use this form when you want to conditionally merge additional map entries or list items into the surrounding structure.

::: tip Hints

- Expressions may be quoted or unquoted.
Quote expressions when they contain characters YAML might misinterpret, such as `:` or `#`.
- If the expression is truthy, the nested content (map or list) is merged into the parent structure.
- If the expression is falsy, the nested content is ignored.
- Branches are evaluated in order from top to bottom.
Only the first truthy branch is selected.
Inactive branches are ignored.

:::

## Basic Syntax
### Simple Example

The `!if` tag supports two forms: a **Mapping Form** for simple logic and a **Sequence Form** for multiple branches.
```yaml
variables:
items_count: 20
things_count: 5

test:
!if '"bar" == "bar"':
foo: bar
!if items_count > 10:
items: a lot of items
!if things_count > 10:
things: a lot of things
other: baz
```

The `if:` and `elseif:` keys are treated as **implicit expressions**.
You do not need to wrap the expression in `${...}`.
Result:

### Mapping Form (Simple)
```yaml
test:
foo: bar
items: a lot of items
other: baz
```

Use this for simple if/else decisions.
### List Example

```yaml
variables:
add_extra: true

items:
MyItem:
tags:
- alpha
- beta
- !if add_extra:
- gamma
- delta
- epsilon
```

Result:

```yaml
items:
MyItem:
tags:
- alpha
- beta
- gamma
- delta
- epsilon
```

### Multi‑Branch Example

```yaml
mode:
!if "env == 'prod'":
value: "production"
!elseif "env == 'staging'":
value: "staging"
!elif "env == 'dev'": # !elseif, !elif, and !elsif can be used interchangeably
value: "development"
!else ~:
value: "unknown"
```

Result:

```yaml
mode:
value: "production"
```

::: tip Important — `!else` Requires `~`

A bare `!else:` is **invalid YAML** because it produces an empty mapping.

You **must** write:

```yaml
!else ~:
```

The `~` is YAML's canonical `null` literal and exists only to satisfy YAML's requirement that every key has a value.

:::

### Key Uniqueness

Each conditional tag is a map key.
YAML requires each key to be unique.
A duplicate‑key error occurs only when two conditional tags have identical key scalars.
Inline comments may be used to make identical expressions unique.

```yaml
test:
!if "true # unique 1":
foo: bar
!if "true # unique 2":
qux: quux
!if "true # unique 3":
corge: grault
```

### Returning Simple Values

The _key‑level form_ is intended for returning maps and lists.
Use inline expressions when you want to return a simple scalar value.
Inline expressions do not require conditional tags.
Inline expressions evaluate directly to a single value.

```yaml
foo: ${"High" if 5 > 2 else "Low"}
```

Alternatively the other two forms below also return a single value.

## Mapping Form

Use the mapping form for simple if/else decisions inside a single block.

```yaml
example: !if
Expand All @@ -36,14 +199,15 @@ example: !if
| Key | Description | Required |
|:-------|:------------------------------------------------------------------------------------------------------------------------|:---------|
| `if` | The expression to evaluate. | Yes |
| `then` | The value to return if **truthy** ([see rules below](#truthiness-rules)). Can be a scalar, map, list, or any valid tag. | Yes |
| `else` | The value to return if **falsy**. | No |
| `then` | The value to return if truthy. Can be a scalar, map, list, or any valid tag. | Yes |
| `else` | The value to return if falsy. | No |

### Sequence Form (Multiple Branches)
## Sequence Form

Use this for multiple ordered conditions.
Use the sequence form when you prefer to express multi‑branch logic inside a single list.
This form mirrors the behavior of the key‑level tags but keeps all branches grouped together.

Evaluates conditions in order and stops at the first **truthy** match.
Conditions are evaluated in order and stop at the first truthy match.
If no condition matches and no `else` is provided, the tag resolves to `null`.

```yaml
Expand All @@ -57,9 +221,9 @@ environment_type: !if

## Expression Evaluation

The `if:` key follows a specific order of operations.
Expressions used in key‑level `!if` and `!elseif` tags follow the same evaluation rules as expressions in `if:` keys.

### 1. Bare Expressions (Recommended)
### Bare Expressions (Recommended)

The string is evaluated directly as an expression against the available variables.

Expand All @@ -68,32 +232,26 @@ if: count > 10 and status == 'ALARM'
```

::: tip
The expression can be quoted when it contains characters that YAML would otherwise misinterpret, such as `:` or `#`.
Quote expressions when they contain characters YAML would otherwise misinterpret, such as `:` or `#`.
:::

### 2. Using substitution pattern (Advanced — Double Evaluation)
### Substitution Pattern (Advanced — Double Evaluation)

If you use a substitution pattern inside an `if:` key, the substitution engine runs **first** to resolve `${...}` patterns.
If you use a substitution pattern inside an `if:` key, the substitution engine runs first.
The resulting string is then evaluated as an expression.
This is useful for building logic strings from variables.

```yaml
variables:
operator: ">"

test: !if
if: 75 ${operator} 50
# Step 1: ${operator} resolves to ">"
# Step 2: The if expression resolves to "75 > 50"
# Step 3: expression evaluates to true
then: "High"
```

## Truthiness Rules

When a value is used in a conditional, it is first evaluated and then interpreted as either **truthy** or **falsy**.

The following values are considered **falsy**:
Falsy values:

- `false`
- `null`
Expand All @@ -102,21 +260,20 @@ The following values are considered **falsy**:
- empty lists (`[]`)
- empty maps (`{}`)

All other values are **truthy**.
This includes any non‑empty string, any non‑zero number, and any non‑empty collection.
All other values are truthy.

### Short-Circuiting (Lazy Evaluation)
### ShortCircuiting (Lazy Evaluation)

Only the active branch is processed.
Tags such as `!include` inside inactive branches are ignored.
An `!include` in an inactive branch is never loaded and will not cause errors if the file does not exist.
Inactive branches do not load files and do not cause errors.

## Advanced Integration

### Nesting and Composition

The `!if` tag is fully recursive.
You can nest `!if` tags within the `then` or `else` blocks to create complex decision trees.
Conditional tags are fully recursive.
You can nest conditional tags inside `then` or `else` blocks.

```yaml
status: !if
Expand All @@ -130,7 +287,7 @@ status: !if

### Conditional Merging (Mixins)

Use `!if` with the YAML merge key (`<<`) to conditionally mix in sets of properties.
Use conditional tags with the YAML merge key (`<<`) to conditionally mix in sets of properties.

```yaml
server_config:
Expand All @@ -157,8 +314,14 @@ network_settings: !if
## Common Pitfalls

1. **Expression vs String Literal**: `if: production` checks for a variable named `production`.
To check for the literal string, quote it: `if: env == 'production'`.
Quote string literals: `if: env == 'production'`.
1. **Incorrect `!else` syntax**:
A bare `!else:` is invalid.
Always write `!else ~:` when using the key‑level form.
1. **Omitting `else`**: If no condition matches and there is no `else`, the result is `null`.
1. **Invalid YAML**: Even inactive branches must be syntactically valid YAML.
1. **Branch Ordering**: In the key‑level form, branches are evaluated in map order.
The first truthy `!if` or `!elseif` wins.
The `!else ~` branch applies only when no earlier branch matches.

See [Expression Syntax](variables.md#expression-syntax) for more details.
Loading