257 lines
5.8 KiB
Markdown
257 lines
5.8 KiB
Markdown
|
|
# Conditional Logic Examples
|
||
|
|
|
||
|
|
This directory demonstrates all supported conditional operators in TypeDialog forms.
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
TypeDialog supports rich conditional logic for dynamic form behavior. Fields can be shown/hidden based on previous answers using the `when` attribute.
|
||
|
|
|
||
|
|
## Running the Examples
|
||
|
|
|
||
|
|
### CLI Backend
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cargo run --bin typedialog -- examples/13-conditional-logic/conditional-demo.toml
|
||
|
|
```
|
||
|
|
|
||
|
|
### TUI Backend
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cargo run --bin typedialog-tui -- examples/13-conditional-logic/conditional-demo.toml
|
||
|
|
```
|
||
|
|
|
||
|
|
### Web Backend
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cargo run --bin typedialog-web -- examples/13-conditional-logic/conditional-demo.toml
|
||
|
|
```
|
||
|
|
|
||
|
|
## Supported Operators
|
||
|
|
|
||
|
|
### Comparison Operators
|
||
|
|
|
||
|
|
#### Equality and Inequality
|
||
|
|
|
||
|
|
- `==`: Equal to
|
||
|
|
- `!=`: Not equal to
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[[elements]]
|
||
|
|
name = "mysql_config"
|
||
|
|
when = "database_driver == mysql"
|
||
|
|
|
||
|
|
[[elements]]
|
||
|
|
name = "server_warning"
|
||
|
|
when = "database_driver != sqlite"
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Numeric Comparisons
|
||
|
|
|
||
|
|
- `>`: Greater than
|
||
|
|
- `<`: Less than
|
||
|
|
- `>=`: Greater than or equal
|
||
|
|
- `<=`: Less than or equal
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[[elements]]
|
||
|
|
name = "privileged_port_warning"
|
||
|
|
when = "server_port < 1024"
|
||
|
|
|
||
|
|
[[elements]]
|
||
|
|
name = "high_port_warning"
|
||
|
|
when = "server_port > 10000"
|
||
|
|
```
|
||
|
|
|
||
|
|
### String Operators
|
||
|
|
|
||
|
|
- `contains`: Check if field contains substring
|
||
|
|
- `startswith`: Check if field starts with prefix
|
||
|
|
- `endswith`: Check if field ends with suffix
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[[elements]]
|
||
|
|
name = "https_notice"
|
||
|
|
when = "project_url startswith https"
|
||
|
|
|
||
|
|
[[elements]]
|
||
|
|
name = "github_specific"
|
||
|
|
when = "project_url endswith github.com"
|
||
|
|
|
||
|
|
[[elements]]
|
||
|
|
name = "gitlab_ci"
|
||
|
|
when = "project_url contains gitlab"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Array Membership
|
||
|
|
|
||
|
|
- `in`: Check if value exists in array field
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[[elements]]
|
||
|
|
name = "rust_toolchain"
|
||
|
|
when = "rust in detected_languages"
|
||
|
|
|
||
|
|
[[elements]]
|
||
|
|
name = "python_venv"
|
||
|
|
when = "python in detected_languages"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Works with**:
|
||
|
|
|
||
|
|
- MultiSelect fields (JSON array or comma-separated string)
|
||
|
|
- Array values from JSON output
|
||
|
|
|
||
|
|
### File System Conditions
|
||
|
|
|
||
|
|
- `file_exists(path)`: Check if file or directory exists
|
||
|
|
- `!file_exists(path)`: Check if file does NOT exist (negation)
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[[elements]]
|
||
|
|
name = "dockerfile_exists_notice"
|
||
|
|
when = "file_exists(Dockerfile)"
|
||
|
|
|
||
|
|
[[elements]]
|
||
|
|
name = "create_dockerfile"
|
||
|
|
when = "!file_exists(Dockerfile)"
|
||
|
|
|
||
|
|
[[elements]]
|
||
|
|
name = "env_setup"
|
||
|
|
type = "group"
|
||
|
|
includes = ["fragments/environment-setup.toml"]
|
||
|
|
when = "!file_exists(.env)"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Path resolution**:
|
||
|
|
|
||
|
|
- Absolute paths: `/etc/config.toml`
|
||
|
|
- Relative paths: `Dockerfile`, `.env`, `config/settings.toml`
|
||
|
|
- Works with both files and directories
|
||
|
|
|
||
|
|
## Form Flow Example
|
||
|
|
|
||
|
|
1. **Select database** → Shows driver-specific fields
|
||
|
|
2. **Enter port number** → Shows warnings based on port range
|
||
|
|
3. **Enter project URL** → Shows platform-specific options
|
||
|
|
4. **Select languages** → Shows language-specific tooling
|
||
|
|
5. **Check for files** → Conditionally load setup fragments
|
||
|
|
|
||
|
|
## Key Features
|
||
|
|
|
||
|
|
### Dynamic Field Visibility
|
||
|
|
|
||
|
|
Fields appear/disappear based on user input:
|
||
|
|
|
||
|
|
```toml
|
||
|
|
# Only shows if user selects MySQL
|
||
|
|
[[elements]]
|
||
|
|
name = "mysql_password"
|
||
|
|
type = "password"
|
||
|
|
when = "database_driver == mysql"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Conditional Fragment Loading
|
||
|
|
|
||
|
|
Load entire form sections conditionally:
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[[elements]]
|
||
|
|
name = "docker_setup"
|
||
|
|
type = "group"
|
||
|
|
includes = ["fragments/docker-init.toml"]
|
||
|
|
when = "!file_exists(Dockerfile)"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Multi-Condition Fields
|
||
|
|
|
||
|
|
Same field can have complex logic (future: `&&` and `||`):
|
||
|
|
|
||
|
|
```toml
|
||
|
|
# Current: Single condition per field
|
||
|
|
when = "rust in detected_languages"
|
||
|
|
|
||
|
|
# Future: Compound conditions
|
||
|
|
when = "(rust in detected_languages) && (server_port >= 1024)"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Testing Scenarios
|
||
|
|
|
||
|
|
### Scenario 1: Database Configuration
|
||
|
|
|
||
|
|
1. Select `mysql` → See MySQL-specific fields
|
||
|
|
2. Select `postgresql` → See PostgreSQL-specific fields
|
||
|
|
3. Select `sqlite` → No extra server fields
|
||
|
|
|
||
|
|
### Scenario 2: Port Validation
|
||
|
|
|
||
|
|
1. Enter `80` → See privileged port warning (`< 1024`)
|
||
|
|
2. Enter `8080` → See standard port notice (`>= 1024`)
|
||
|
|
3. Enter `15000` → See high port warning (`> 10000`)
|
||
|
|
|
||
|
|
### Scenario 3: Language Detection
|
||
|
|
|
||
|
|
1. Select `rust` + `python` → See both Rust and Python config fields
|
||
|
|
2. Select only `javascript` → See only Node.js version selector
|
||
|
|
3. Select none → No language-specific fields appear
|
||
|
|
|
||
|
|
### Scenario 4: File System Checks
|
||
|
|
|
||
|
|
1. **With Dockerfile present**:
|
||
|
|
- Shows "Dockerfile found" notice
|
||
|
|
- Skips Docker setup wizard
|
||
|
|
|
||
|
|
2. **Without Dockerfile**:
|
||
|
|
- Asks to create Dockerfile
|
||
|
|
- Shows Docker setup group
|
||
|
|
|
||
|
|
3. **With .env file**:
|
||
|
|
- Asks to use existing config
|
||
|
|
- Skips environment setup
|
||
|
|
|
||
|
|
4. **Without .env file**:
|
||
|
|
- Loads environment setup fragment
|
||
|
|
- Prompts for all env variables
|
||
|
|
|
||
|
|
## Implementation Details
|
||
|
|
|
||
|
|
### Condition Evaluation
|
||
|
|
|
||
|
|
Conditions are evaluated at runtime during form execution:
|
||
|
|
|
||
|
|
1. User answers a field
|
||
|
|
2. System checks all pending fields for `when` conditions
|
||
|
|
3. Fields with satisfied conditions become visible
|
||
|
|
4. Fields with unsatisfied conditions remain hidden
|
||
|
|
|
||
|
|
### Fragment Loading
|
||
|
|
|
||
|
|
Conditional groups with `includes` load fragments only when condition is true:
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[[elements]]
|
||
|
|
name = "advanced_config"
|
||
|
|
type = "group"
|
||
|
|
includes = ["fragments/advanced-settings.toml"]
|
||
|
|
when = "enable_advanced == true"
|
||
|
|
```
|
||
|
|
|
||
|
|
This prevents loading unnecessary form definitions until needed.
|
||
|
|
|
||
|
|
### Type Conversion
|
||
|
|
|
||
|
|
TypeDialog automatically handles type conversions in conditions:
|
||
|
|
|
||
|
|
- String `"8080"` compared to number `1024` → converted to number
|
||
|
|
- Boolean `true` compared to string `"true"` → compared as boolean
|
||
|
|
- Number compared to string number → compared numerically
|
||
|
|
|
||
|
|
## Related Documentation
|
||
|
|
|
||
|
|
- [Field Types Reference](../../docs/field_types.md) - All field types and attributes
|
||
|
|
- [Fragment System](../../docs/fragment-search-paths.md) - Dynamic form composition
|
||
|
|
- [Nickel Integration](../../docs/nickel.md) - Schema-driven forms
|
||
|
|
|
||
|
|
## Source Code
|
||
|
|
|
||
|
|
Condition evaluation logic: `crates/typedialog-core/src/form_parser/conditions.rs`
|