# Auto-Generated "Supported Features" Tables
The `features` Sphinx directive generates the _supported features_ tables for the [](supported-platforms) document and the module/plugin descriptions.
```eval_rst
.. contents:: Table of Contents
:depth: 2
:local:
:backlinks: none
```
## Creating and Describing Feature Tables
The `features` directive is invoked with the [MyST directive syntax](https://myst-parser.readthedocs.io/en/latest/syntax/roles-and-directives.html#syntax-directives):
````md
```{features}
```
````
The `{features}` directive content is a YAML-formatted description of the features that should be included in the table, for example:
````md
```{features}
- title: BFD
protocol
enabled: bfd
caveats: bfd.caveats
- title: OSPF
with BFD
enabled: ospf and bfd
- title: IS-IS
with BFD
enabled: isis and bfd
- title: BGP
with BFD
enabled: bgp.bfd
caveats: bgp.bfd.caveats
```
````
Each column definition (feature) can have these parameters:
* **title** (required) -- the column header. Use `
` to create multi-line headers.
* **enabled** or **text** (exactly one required) -- see below
* **caveats** (optional) -- the Python expression that checks whether the device/feature entry should include a link to caveats. The value of the specified device feature could be **true**, in which case the link pointing to the caveat is `caveats-_device_`, or a documentation anchor ID.
### Feature Display Modes
The directive supports two mutually exclusive modes for displaying feature data:
* **enabled** -- the Python expression that results in a truthy value if the feature works on the device. The cell is displayed as a green checkmark or a red cross. The expression can use all device definition data (for example, `libvirt.image`) and the device features (for example, `ospf.areas`).
* **text** -- the Python expression whose result is displayed as text in the table cell. Falsy values are displayed as red crosses, lists are joined with commas, booleans are converted to checkmarks/crosses, and other values are converted to strings. Use this when you need to display non-boolean information (like deployment methods or configuration modes).
For example, to display the list of non-default configuration deployment methods:
````md
```{features}
- title: Alternate
deployment method
text: >-
[ x for x in initial.config_mode if x != group_vars.get('netlab_config_mode','') ]
```
````
### Caveats
The **caveats** parameter links a cell to an explanation of feature limitations. A truthy caveat value generates a link to an anchor with ID `caveats-_device_` (usually defined in `caveats.md`). A string value is used as the link target. A caveat value of `yes` generates a warning emoji with no link (for caveats explained below the table).
### Built-in Functions
The `enabled` and `text` expressions can use the `len()` built-in function. For example:
````md
```{features}
- title: router
enabled: >-
"router" in initial.roles and len(initial.roles) > 1
```
````
### Provider-Specific Features
The directive supports provider-specific features. When a device has provider-specific data or feature definitions (for example, `clab.features` or `libvirt.features`), the directive generates additional rows for that device/provider combination, displayed as "Device (containers)" or "Device (VMs)".
Child device deduplication also works at the provider-specific level: if a child device's provider-specific features match its parent's, the child row is removed.
### Parent/Child Device Handling
When dealing with parent/child devices (for example, Cisco IOS or Junos platforms), the `features` directive automatically:
1. Detects whether a child device supports a different set of features than the parent device
2. Removes child devices that inherit feature settings from their parents
3. Creates a "this parent device includes these other devices" footnote
Two device parameters influence this behavior:
* **docname** is a shorter device name (between device code and full device description). For example, Arista EOS has `eos` device code, "Arista vEOS VM or cEOS container" **description**, and "Arista EOS" **docname**.
* Sometimes, you don't want a device to be displayed like a parent device in the features table. For example, the `sonic` device uses `frr` as its parent, but we'd definitely not want to have "FRR includes SONiC" as a footnote. In these cases, set the **docparent** parameter of the parent device to `False`.
## Developing New Feature Tables
The best way to develop a new feature table is to study existing examples. Here are the common patterns:
### Simple Boolean Features
Check whether a device supports a feature by referencing a dotted attribute path within the `features` device dictionary.
````md
```{features}
- title: BGP
timers
enabled: bgp.timers
- title: BFD
enabled: bgp.bfd
```
````
### Complex Boolean Expressions
Use Python operators for more sophisticated checks:
````md
```{features}
- title: IPv4 DNS
client
enabled: |
services.dns is True or 'ipv4' in services.dns
- title: IPv6 DNS
client
enabled: |
services.dns is True or 'ipv6' in services.dns
```
````
### Checking List Membership
Use `in` to check whether a feature is in a list of supported values:
````md
```{features}
- title: "on"
enabled: 'on' in bgp.remove_private_as.valid
- title: all
enabled: 'all' in bgp.remove_private_as.valid
```
````
### Using .get() with Defaults
Use `.get()` when a feature attribute might not exist, and you need a default value:
````md
```{features}
- title: Transport
VRF
enabled: |
services.dns and services.get('dns.transport_vrf',True) != False
```
````
### Combining "enabled" with Caveats
Link cells to caveats when a feature has known limitations:
````md
```{features}
- title: Route
import
enabled: bgp['import']
caveats: bgp.caveats['import']
- title: Graceful
Restart
enabled: bgp.gr
caveats: bgp.gr.caveats
```
````
### Displaying Text Values
Use **text** instead of **enabled** when you need to show non-boolean data:
````md
```{features}
- title: Alternate
deployment method
text: >-
[ x for x in initial.config_mode if x != group_vars.get('netlab_config_mode','') ]
```
````
## Testing Feature Tables
To test a feature table, build the documentation and inspect the rendered output. The following text assumes you're working in the `docs` directory:
1. **Add the new *features* table** next to the original table. The new table should match the original one unless we were sloppy and forgot to update feature tables (in which case device settings/code are the final arbiter).
2. **Build the documentation** with `make html` ([more details](dev-testing-docs)). When modifying device data, `touch` the `.md` file to trigger the build process
3. **Open the relevant page** in `_build/html/` in your browser and verify the table looks correct.
4. **Iterate**: edit the feature table YAML, rebuild, and compare the output until the table matches the expected result.
5. **Remove** the original table once you're done.
### Debugging
* Set the `NETLAB_DEBUG_FEATURES` environment variable to get debug output during the build. The variable accepts comma-separated flags:
```bash
NETLAB_DEBUG_FEATURES=df_data,df_row make html
```
* `df_data` -- dumps per-device feature data and provider-specific checks
* `df_row` -- dumps the computed feature row for each device
* A useful technique when developing a new feature table is to include a **reference table** (a manually curated table showing the expected output) below the auto-generated one. Compare the two, adjust the YAML expressions or device settings, rebuild, and repeat until the output matches. Once satisfied, remove the reference table.