From 39e5c35a287ea2b52d5b7d4d0082938585c14798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Pe=CC=81rez?= Date: Sun, 28 Dec 2025 18:28:34 +0000 Subject: [PATCH] chore: add new conditionals to docs --- docs/field_types.md | 133 ++++++++- examples/13-conditional-logic/README.md | 256 ++++++++++++++++++ .../conditional-demo.toml | 198 ++++++++++++++ .../fragments/environment-setup.toml | 72 +++++ examples/README.md | 35 +++ 5 files changed, 689 insertions(+), 5 deletions(-) create mode 100644 examples/13-conditional-logic/README.md create mode 100644 examples/13-conditional-logic/conditional-demo.toml create mode 100644 examples/13-conditional-logic/fragments/environment-setup.toml diff --git a/docs/field_types.md b/docs/field_types.md index 4bbda48..111c085 100644 --- a/docs/field_types.md +++ b/docs/field_types.md @@ -453,12 +453,135 @@ when = "database_driver == mysql" required = true ``` -**Supported operators**: +### Comparison Operators -- `==`: Equality -- `!=`: Inequality -- Parentheses for grouping (future) -- Logical AND/OR (future) +**Equality and inequality**: + +- `==`: Equal to +- `!=`: Not equal to + +```toml +[[elements]] +name = "postgres_config" +type = "text" +when = "database_driver == postgresql" + +[[elements]] +name = "legacy_warning" +type = "section" +content = "⚠️ SQLite is for development only" +when = "database_driver != postgresql" +``` + +**Numeric comparisons**: + +- `>`: Greater than +- `<`: Less than +- `>=`: Greater than or equal +- `<=`: Less than or equal + +```toml +[[elements]] +name = "performance_warning" +type = "section" +content = "⚠️ High port number may require elevated privileges" +when = "port >= 1024" + +[[elements]] +name = "pool_size_warning" +type = "section" +when = "connection_pool > 100" +``` + +### String Operators + +**String matching**: + +- `contains`: Check if field contains substring +- `startswith`: Check if field starts with prefix +- `endswith`: Check if field ends with suffix + +```toml +[[elements]] +name = "rust_specific" +type = "text" +prompt = "Rust toolchain version" +when = "language contains rust" + +[[elements]] +name = "protocol_warning" +type = "section" +when = "url startswith https" + +[[elements]] +name = "yaml_parser" +type = "select" +when = "config_file endswith .yaml" +``` + +### Array Membership + +**`in` operator**: Check if value exists in array field + +```toml +[[elements]] +name = "rust_features" +type = "multiselect" +prompt = "Rust-specific features" +when = "rust in detected_languages" +options = [ + { value = "clippy", label = "Clippy linting" }, + { value = "cargo_audit", label = "Security auditing" } +] + +[[elements]] +name = "python_virtualenv" +type = "confirm" +when = "python in languages" +``` + +**Note**: The `in` operator works with: + +- Array fields (JSON array values) +- MultiSelect fields (comma-separated strings) + +### File System Conditions + +**`file_exists(path)`**: Check if file or directory exists + +```toml +[[elements]] +name = "use_existing_config" +type = "confirm" +prompt = "Existing config.toml found. Use it?" +when = "file_exists(config.toml)" + +[[elements]] +name = "create_new_config" +type = "text" +prompt = "Configuration name" +when = "!file_exists(.env)" +``` + +**Negation with `!`**: + +- `!file_exists(path)`: File does NOT exist + +```toml +[[elements]] +name = "docker_setup" +type = "group" +includes = ["fragments/docker-init.toml"] +when = "!file_exists(Dockerfile)" +``` + +### Future Support + +**Planned features**: + +- Parentheses for grouping: `(a == b) && (c == d)` +- Logical AND: `&&` +- Logical OR: `||` --- diff --git a/examples/13-conditional-logic/README.md b/examples/13-conditional-logic/README.md new file mode 100644 index 0000000..148b7b8 --- /dev/null +++ b/examples/13-conditional-logic/README.md @@ -0,0 +1,256 @@ +# 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` diff --git a/examples/13-conditional-logic/conditional-demo.toml b/examples/13-conditional-logic/conditional-demo.toml new file mode 100644 index 0000000..a3da8cb --- /dev/null +++ b/examples/13-conditional-logic/conditional-demo.toml @@ -0,0 +1,198 @@ +# Conditional Logic Demo +# Demonstrates all supported conditional operators in TypeDialog + +name = "conditional_demo" +description = "Complete demonstration of conditional field visibility" + +# ==================== +# COMPARISON OPERATORS +# ==================== + +[[elements]] +name = "database_driver" +type = "select" +prompt = "Select database driver" +required = true +options = [ + { value = "sqlite", label = "SQLite (embedded)" }, + { value = "mysql", label = "MySQL" }, + { value = "postgresql", label = "PostgreSQL" } +] + +# Equality (==) +[[elements]] +name = "mysql_config" +type = "text" +prompt = "MySQL connection string" +when = "database_driver == mysql" +placeholder = "mysql://localhost:3306/db" + +# Inequality (!=) +[[elements]] +name = "server_warning" +type = "section" +content = "⚠️ You selected a server-based database. Ensure the server is running." +when = "database_driver != sqlite" + +# ==================== +# NUMERIC COMPARISONS +# ==================== + +[[elements]] +name = "server_port" +type = "text" +prompt = "Server port" +default = "8080" +required = true + +# Greater than (>) +[[elements]] +name = "high_port_warning" +type = "section" +content = "⚠️ Port > 10000 is uncommon. Double-check your configuration." +when = "server_port > 10000" + +# Less than (<) +[[elements]] +name = "privileged_port_warning" +type = "section" +content = "⚠️ Port < 1024 requires root/admin privileges." +when = "server_port < 1024" + +# Greater than or equal (>=) +[[elements]] +name = "standard_port_notice" +type = "section" +content = "✓ Using standard user port range (>= 1024)" +when = "server_port >= 1024" + +# Less than or equal (<=) +[[elements]] +name = "low_port_range" +type = "section" +content = "Using low port range (<= 5000)" +when = "server_port <= 5000" + +# ==================== +# STRING OPERATORS +# ==================== + +[[elements]] +name = "project_url" +type = "text" +prompt = "Project repository URL" +placeholder = "https://github.com/user/repo" + +# startswith +[[elements]] +name = "https_notice" +type = "section" +content = "✓ Secure HTTPS URL detected" +when = "project_url startswith https" + +# endswith +[[elements]] +name = "github_specific" +type = "text" +prompt = "GitHub Actions enabled?" +when = "project_url endswith github.com" + +# contains +[[elements]] +name = "gitlab_ci" +type = "confirm" +prompt = "Enable GitLab CI integration?" +when = "project_url contains gitlab" + +# ==================== +# ARRAY MEMBERSHIP (in) +# ==================== + +[[elements]] +name = "detected_languages" +type = "multiselect" +prompt = "Which languages are used in your project?" +display_mode = "grid" +options = [ + { value = "rust", label = "🦀 Rust" }, + { value = "python", label = "🐍 Python" }, + { value = "javascript", label = "📜 JavaScript" }, + { value = "go", label = "🐹 Go" } +] + +# Array membership check +[[elements]] +name = "rust_toolchain" +type = "select" +prompt = "Rust toolchain version" +when = "rust in detected_languages" +options = [ + { value = "stable", label = "Stable" }, + { value = "nightly", label = "Nightly" }, + { value = "beta", label = "Beta" } +] + +[[elements]] +name = "python_venv" +type = "confirm" +prompt = "Use Python virtual environment?" +when = "python in detected_languages" +default = true + +[[elements]] +name = "nodejs_version" +type = "select" +prompt = "Node.js version" +when = "javascript in detected_languages" +options = [ + { value = "18", label = "Node.js 18 LTS" }, + { value = "20", label = "Node.js 20 LTS" }, + { value = "latest", label = "Latest" } +] + +# ==================== +# FILE SYSTEM CONDITIONS +# ==================== + +# file_exists(path) +[[elements]] +name = "dockerfile_exists_notice" +type = "section" +content = "✓ Dockerfile found in current directory" +when = "file_exists(Dockerfile)" + +# !file_exists(path) - negation +[[elements]] +name = "create_dockerfile" +type = "confirm" +prompt = "No Dockerfile found. Create one?" +when = "!file_exists(Dockerfile)" +default = true + +[[elements]] +name = "use_existing_config" +type = "confirm" +prompt = "Existing .env file found. Use existing configuration?" +when = "file_exists(.env)" + +[[elements]] +name = "env_setup" +type = "group" +includes = ["fragments/environment-setup.toml"] +when = "!file_exists(.env)" + +# ==================== +# COMBINED CONDITIONS +# ==================== + +[[elements]] +name = "rust_docker_setup" +type = "section" +content = "🦀 Rust + Docker detected. Consider using rust:alpine base image." +when = "rust in detected_languages" + +[[elements]] +name = "production_ready_check" +type = "section" +content = "✅ Production-ready configuration detected (HTTPS + standard port)" +when = "server_port >= 1024" diff --git a/examples/13-conditional-logic/fragments/environment-setup.toml b/examples/13-conditional-logic/fragments/environment-setup.toml new file mode 100644 index 0000000..220c2ca --- /dev/null +++ b/examples/13-conditional-logic/fragments/environment-setup.toml @@ -0,0 +1,72 @@ +# Environment Setup Fragment +# Loaded conditionally when .env file doesn't exist + +name = "environment_setup" +description = "Environment variables configuration" +display_mode = "complete" + +[[elements]] +name = "env_header" +type = "section_header" +title = "🔧 Environment Configuration" +border_top = true +border_bottom = true + +[[elements]] +name = "env_mode" +type = "select" +prompt = "Environment mode" +required = true +options = [ + { value = "development", label = "Development" }, + { value = "staging", label = "Staging" }, + { value = "production", label = "Production" } +] + +[[elements]] +name = "log_level" +type = "select" +prompt = "Log level" +default = "info" +options = [ + { value = "trace", label = "Trace (verbose)" }, + { value = "debug", label = "Debug" }, + { value = "info", label = "Info" }, + { value = "warn", label = "Warning" }, + { value = "error", label = "Error" } +] + +[[elements]] +name = "debug_mode" +type = "confirm" +prompt = "Enable debug mode?" +when = "env_mode == development" +default = true + +[[elements]] +name = "api_key" +type = "password" +prompt = "API Key" +required = true +help = "Your application API key" + +[[elements]] +name = "database_url" +type = "text" +prompt = "Database URL" +placeholder = "postgresql://localhost:5432/mydb" +required = true + +[[elements]] +name = "redis_url" +type = "text" +prompt = "Redis URL" +placeholder = "redis://localhost:6379" +when = "env_mode == production" + +[[elements]] +name = "sentry_dsn" +type = "text" +prompt = "Sentry DSN (error tracking)" +when = "env_mode == production" +help = "Leave empty to disable error tracking" diff --git a/examples/README.md b/examples/README.md index 5dbc9c5..d6de58c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -189,6 +189,41 @@ cat examples/12-agent-execution/README.md - [Tests](../tests/agent/) - Agent validation tests - [Core Examples](../crates/typedialog-agent/typedialog-ag-core/examples/) - Rust API usage +### 13. **Conditional Logic** → [`13-conditional-logic/`](13-conditional-logic/) + +Complete guide to all conditional operators and expressions. + +**Operators demonstrated:** + +| Category | Operators | Example | +|----------|-----------|---------| +| **Comparison** | `==`, `!=`, `>`, `<`, `>=`, `<=` | `port >= 1024` | +| **String** | `contains`, `startswith`, `endswith` | `url startswith https` | +| **Array** | `in` | `rust in detected_languages` | +| **File System** | `file_exists()`, `!file_exists()` | `!file_exists(Dockerfile)` | + +**Features:** + +- Dynamic field visibility based on user input +- Conditional fragment loading +- Multi-condition scenarios +- Type-safe comparisons + +**Running the example:** + +```bash +# CLI +cargo run --bin typedialog -- examples/13-conditional-logic/conditional-demo.toml + +# TUI +cargo run --bin typedialog-tui -- examples/13-conditional-logic/conditional-demo.toml + +# Web +cargo run --bin typedialog-web -- examples/13-conditional-logic/conditional-demo.toml +``` + +**Best for:** Dynamic forms, adaptive UX, configuration wizards + ## Learning Path ```