blueprints: Add !Enumerate, !Value and !Index tags (#4338)
				
					
				
			* Added For and Item tags * Removed Sequence node support from ForItem tag * Added ForItemIndex tag * Added support for iterating over mappings * Added support for mapping output body * Renamed tags: For to Enumerate, ForItem to Value, ForItemIndex to Index * Refactored tests * Formatting * Improved exception info * Improved error handing * Added docs * lint * Small doc improvements * Replaced deepcopy() call with call to copy() * Fix mistake in docs example * Fix missed "!" in example
This commit is contained in:
		@ -105,3 +105,130 @@ Requires at least one argument after the mode selection.
 | 
			
		||||
If only a single argument is provided, its boolean representation will be returned for all normal modes and its negated boolean representation will be returned for all negated modes.
 | 
			
		||||
 | 
			
		||||
Normally, it should be used to define complex conditions for use with an `!If` tag or for the `conditions` attribute of a blueprint entry (see [the blueprint file structure](./structure.md)). However, this is essentially just a boolean evaluator so it can be used everywhere a boolean representation is required.
 | 
			
		||||
 | 
			
		||||
#### `!Enumerate`, `!Index` and `!Value`
 | 
			
		||||
 | 
			
		||||
These tags collectively make it possible to iterate over objects which support iteration. Any iterable Python object is supported. Such objects are sequences (`[]`), mappings (`{}`) and even strings.
 | 
			
		||||
 | 
			
		||||
1. `!Enumerate` tag:
 | 
			
		||||
 | 
			
		||||
This tag takes 3 arguments:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
!Enumerate [<iterable>, <output_object_type>, <single_item_yaml>]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
-   **iterable**: Any Python iterable or custom tag that resolves to such iterable
 | 
			
		||||
-   **output_object_type**: `SEQ` or `MAP`. Controls whether the returned YAML will be a mapping or a sequence.
 | 
			
		||||
-   **single_item_yaml**: The YAML to use to create a single entry in the output object
 | 
			
		||||
 | 
			
		||||
2. `!Index` tag:
 | 
			
		||||
 | 
			
		||||
:::info
 | 
			
		||||
This tag is only valid inside an `!Enumerate` tag
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
This tag takes 1 argument:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
!Index <depth>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
-   **depth**: Must be >= 0. A depth of 0 refers to the `!Enumerate` tag this tag is located in. A depth of 1 refers to one `!Enumerate` tag above that (to be used when multiple `!Enumerate` tags are nested inside each other).
 | 
			
		||||
 | 
			
		||||
Accesses the `!Enumerate` tag's iterable and resolves to the index of the item currently being iterated (in case `!Enumerate` is iterating over a sequence), or the mapping key (in case `!Enumerate` is iterating over a mapping).
 | 
			
		||||
 | 
			
		||||
For example, given a sequence like this - `["a", "b", "c"]`, this tag will resolve to `0` on the first `!Enumerate` tag iteration, `1` on the second and so on. However, if given a mapping like this - `{"key1": "value1", "key2": "value2", "key3": "value3"}`, it will first resolve to `key1`, then to `key2` and so on.
 | 
			
		||||
 | 
			
		||||
3. `!Value` tag:
 | 
			
		||||
 | 
			
		||||
:::info
 | 
			
		||||
This tag is only valid inside an `!Enumerate` tag
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
This tag takes 1 argument:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
!Value <depth>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
-   **depth**: Must be >= 0. A depth of 0 refers to the `!Enumerate` tag this tag is located in. A depth of 1 refers to one `!Enumerate` tag above that (to be used when multiple `!Enumerate` tags are nested inside each other).
 | 
			
		||||
 | 
			
		||||
Accesses the `!Enumerate` tag's iterable and resolves to the value of the item currently being iterated.
 | 
			
		||||
 | 
			
		||||
For example, given a sequence like this - `["a", "b", "c"]`, this tag will resolve to `a` on the first `!Enumerate` tag iteration, `b` on the second and so on. If given a mapping like this - `{"key1": "value1", "key2": "value2", "key3": "value3"}`, it will first resolve to `value1`, then to `value2` and so on.
 | 
			
		||||
 | 
			
		||||
Minimal examples:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
configuration_stages: !Enumerate [
 | 
			
		||||
    !Context map_of_totp_stage_names_and_types,
 | 
			
		||||
    SEQ, # Output a sequence
 | 
			
		||||
    !Find [!Format ["authentik_stages_authenticator_%s.authenticator%sstage", !Index 0, !Index 0], [name, !Value 0]] # The value of each item in the sequence
 | 
			
		||||
]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The above example will resolve to something like this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
configuration_stages:
 | 
			
		||||
- !Find [authentik_stages_authenticator_<stage_type_1>.authenticator<stage_type_1>stage, [name, <stage_name_1>]]
 | 
			
		||||
- !Find [authentik_stages_authenticator_<stage_type_2>.authenticator<stage_type_2>stage, [name, <stage_name_2>]]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Similarly, a mapping can be generated like so:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
example: !Enumerate [
 | 
			
		||||
    !Context list_of_totp_stage_names,
 | 
			
		||||
    MAP, # Output a map
 | 
			
		||||
    [
 | 
			
		||||
        !Index 0, # The key to assign to each entry
 | 
			
		||||
        !Value 0, # The value to assign to each entry
 | 
			
		||||
    ]
 | 
			
		||||
]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The above example will resolve to something like this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
example:
 | 
			
		||||
  0: <stage_name_1>
 | 
			
		||||
  1: <stage_name_2>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Full example:
 | 
			
		||||
 | 
			
		||||
:::warning
 | 
			
		||||
Note that an `!Enumeration` tag's iterable can never be an `!Item` or `!Value` tag with a depth of `0`. Minimum depth allowed is `1`. This is because a depth of `0` refers to the `!Enumeration` tag the `!Item` or `!Value` tag is in, and an `!Enumeration` tag cannot iterate over itself.
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
example: !Enumerate [
 | 
			
		||||
    !Context sequence, # ["foo", "bar"]
 | 
			
		||||
    MAP, # Output a map
 | 
			
		||||
    [
 | 
			
		||||
        !Index 0, # Use the indexes of the items in the sequence as keys
 | 
			
		||||
        !Enumerate [ # Nested enumeration
 | 
			
		||||
            # Iterate over each item of the parent enumerate tag.
 | 
			
		||||
            # Notice depth is 1, not 0, since we are inside the nested enumeration tag!
 | 
			
		||||
            !Value 1,
 | 
			
		||||
            SEQ, # Output a sequence
 | 
			
		||||
            !Format ["%s: (index: %d, letter: %s)", !Value 1, !Index 0, !Value 0]
 | 
			
		||||
        ]
 | 
			
		||||
    ]
 | 
			
		||||
]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The above example will resolve to something like this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
'0':
 | 
			
		||||
- 'foo: (index: 0, letter: f)'
 | 
			
		||||
- 'foo: (index: 1, letter: o)'
 | 
			
		||||
- 'foo: (index: 2, letter: o)'
 | 
			
		||||
'1':
 | 
			
		||||
- 'bar: (index: 0, letter: b)'
 | 
			
		||||
- 'bar: (index: 1, letter: a)'
 | 
			
		||||
- 'bar: (index: 2, letter: r)'
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user