From a963adbf5b8c05662c1f2138daa8b52582a3f083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Pe=CC=81rez?= Date: Sun, 8 Mar 2026 23:20:50 +0000 Subject: [PATCH] feat(forms): migrate all form definitions and configs to Nickel (.ncl) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all TOML form definitions in examples/ and config/ with type-checked Nickel equivalents. Update cli_loader to prefer .ncl (via nickel export) over .toml in config search order. TOML support retained as fallback — no breaking change. - El loader usa nickel export --format json + serde_json como puente — evita reimplementar un parser Nickel en Rust y aprovecha el binario ya existente. - El orden de búsqueda .ncl > .toml permite migración incremental: cualquier config vieja sigue funcionando sin tocarla. - Los contratos Nickel (| default, | String) en los configs sustituyen la validación que antes era implícita en el parsing TOML — el error llega antes (en nickel export) con mensajes más descriptivos. --- CHANGELOG.md | 23 + README.md | 39 +- assets/web/README.md | 86 ++ assets/web/architecture-diagram.html | 1 + assets/web/index.html | 1 + assets/web/minify.sh | 101 +++ assets/web/src/architecture-diagram.html | 173 ++++ assets/web/src/index.html | 807 ++++++++++++++++++ assets/web/src/typedialog_architecture.svg | 253 ++++++ .../web/src/typedialog_architecture_white.svg | 229 +++++ assets/web/typedialog_architecture.svg | 253 ++++++ assets/web/typedialog_architecture_white.svg | 229 +++++ assets/web/typedialog_logo_h.svg | 58 ++ config/ag/config.ncl | 33 + config/ag/default.toml | 39 - config/ag/dev.ncl | 14 + config/ag/dev.toml | 32 - config/ag/production.ncl | 29 + config/ag/production.toml | 37 - config/ag/server-default.toml | 8 - config/ag/server.ncl | 4 + config/ai/config.ncl | 29 + config/ai/default.toml | 66 -- config/ai/dev.ncl | 18 + config/ai/dev.toml | 31 - config/ai/production.ncl | 27 + config/ai/production.toml | 34 - config/cli/config.ncl | 27 + config/cli/default.toml | 30 - config/cli/dev.ncl | 18 + config/cli/dev.toml | 32 - config/cli/production.ncl | 25 + config/cli/production.toml | 37 - config/prov-gen/config.ncl | 32 + config/prov-gen/default.toml | 42 - config/prov-gen/dev.ncl | 27 + config/prov-gen/dev.toml | 32 - config/prov-gen/production.ncl | 33 + config/prov-gen/production.toml | 41 - config/tui/config.ncl | 39 + config/tui/default.toml | 46 - config/tui/dev.ncl | 16 + config/tui/dev.toml | 45 - config/tui/production.ncl | 21 + config/tui/production.toml | 48 -- config/web/config.ncl | 39 + config/web/default.toml | 48 -- config/web/dev.ncl | 28 + config/web/dev.toml | 50 -- config/web/production.ncl | 42 + config/web/production.toml | 65 -- .../typedialog-core/src/config/cli_loader.rs | 111 ++- .../typedialog-core/src/encryption_bridge.rs | 1 + .../src/form_parser/executor.rs | 198 ++--- crates/typedialog-core/src/form_parser/mod.rs | 5 +- .../typedialog-core/src/form_parser/parser.rs | 28 + .../typedialog-core/src/form_parser/types.rs | 16 + crates/typedialog-core/src/helpers.rs | 6 + .../typedialog-core/src/nickel/contracts.rs | 412 +-------- crates/typedialog-core/src/nickel/mod.rs | 1 - .../typedialog-core/src/nickel/roundtrip.rs | 51 +- .../src/nickel/toml_generator.rs | 2 + .../tests/encryption_integration.rs | 5 + .../tests/nickel_integration.rs | 60 +- .../tests/proptest_validation.rs | 322 ------- .../src/generator/validator_generator.rs | 9 +- crates/typedialog-tui/src/commands/form.rs | 3 +- crates/typedialog-web/src/main.rs | 17 +- crates/typedialog/src/commands/form.rs | 3 +- docs/adr/README.md | 21 + docs/adr/adr-001-nickel-form-definition.md | 146 ++++ docs/architecture.md | 61 +- examples/01-basic/base_form.ncl | 25 + examples/01-basic/base_from.toml | 57 -- examples/01-basic/debug_simple.ncl | 8 + examples/01-basic/debug_simple.toml | 17 - examples/01-basic/form.ncl | 25 + examples/01-basic/form.toml | 60 -- examples/01-basic/form_with_grouped_items.ncl | 50 ++ .../01-basic/form_with_grouped_items.toml | 155 ---- examples/01-basic/form_with_sections.ncl | 61 ++ examples/01-basic/form_with_sections.toml | 142 --- examples/02-advanced/conditional_form.ncl | 59 ++ examples/02-advanced/conditional_form.toml | 117 --- examples/02-advanced/conditional_sections.ncl | 75 ++ .../02-advanced/conditional_sections.toml | 184 ---- examples/02-advanced/display_items_demo.ncl | 35 + examples/02-advanced/display_items_demo.toml | 78 -- .../02-multiselect-display-modes/form.ncl | 49 ++ .../02-multiselect-display-modes/form.toml | 75 -- examples/03-styling/custom_border_form.ncl | 13 + examples/03-styling/custom_border_form.toml | 43 - examples/03-styling/fancy_borders_form.ncl | 20 + examples/03-styling/fancy_borders_form.toml | 61 -- examples/04-backends/tui/tui_survey_form.ncl | 85 ++ examples/04-backends/tui/tui_survey_form.toml | 226 ----- .../04-backends/web/web_registration_form.ncl | 70 ++ .../web/web_registration_form.toml | 283 ------ examples/05-fragments/agreement_section.ncl | 10 + examples/05-fragments/agreement_section.toml | 45 - examples/05-fragments/array-trackers.ncl | 38 + examples/05-fragments/array-trackers.toml | 102 --- examples/05-fragments/constraints.ncl | 14 + examples/05-fragments/constraints.toml | 20 - .../05-fragments/custom_border_section.ncl | 7 + .../05-fragments/custom_border_section.toml | 25 - .../05-fragments/employee_info_section.ncl | 18 + .../05-fragments/employee_info_section.toml | 58 -- examples/05-fragments/enterprise_section.ncl | 9 + examples/05-fragments/enterprise_section.toml | 38 - .../05-fragments/fancy_border_section.ncl | 7 + .../05-fragments/fancy_border_section.toml | 34 - .../form_with_groups_includes.ncl | 23 + .../form_with_groups_includes.toml | 52 -- .../fragments/tracker-http-item.ncl | 11 + .../fragments/tracker-http-item.toml | 35 - .../fragments/tracker-udp-item.ncl | 6 + .../fragments/tracker-udp-item.toml | 23 - examples/05-fragments/header.ncl | 5 + examples/05-fragments/header.toml | 15 - examples/05-fragments/premium_section.ncl | 12 + examples/05-fragments/premium_section.toml | 29 - examples/05-fragments/support_section.ncl | 13 + examples/05-fragments/support_section.toml | 35 - examples/06-i18n/en-US.toml | 31 - examples/06-i18n/es-ES.toml | 31 - .../i18n/registration_i18n.ncl | 18 + .../i18n/registration_i18n.toml | 58 -- .../06-integrations/i18n/test_i18n_form.ncl | 13 + .../06-integrations/i18n/test_i18n_form.toml | 22 - examples/07-nickel-generation/arrays-form.ncl | 46 + .../07-nickel-generation/arrays-form.toml | 198 ----- .../fragments/api-endpoint-item.ncl | 7 + .../fragments/api-endpoint-item.toml | 29 - .../fragments/user-item.ncl | 13 + .../fragments/user-item.toml | 42 - examples/08-encryption/credentials.ncl | 25 + examples/08-encryption/credentials.toml | 138 --- examples/08-encryption/multi-backend-sops.ncl | 40 + .../08-encryption/multi-backend-sops.toml | 192 ----- examples/08-encryption/simple-login.ncl | 9 + examples/08-encryption/simple-login.toml | 40 - .../08-nickel-roundtrip/02-roundtrip-cli.sh | 2 +- .../08-nickel-roundtrip/03-roundtrip-tui.sh | 2 +- .../08-nickel-roundtrip/04-roundtrip-web.sh | 2 +- examples/08-nickel-roundtrip/ci-form.ncl | 59 ++ examples/08-nickel-roundtrip/ci-form.toml | 237 ----- .../employee_onboarding_form.ncl | 13 + .../employee_onboarding_form.toml | 30 - examples/09-templates/onboarding_template.ncl | 28 + .../09-templates/onboarding_template.toml | 68 -- .../user_registration/registration_form.ncl | 25 + .../user_registration/registration_form.toml | 57 -- .../user_registration_form.ncl | 35 + .../user_registration_form.toml | 80 -- .../mode-b-config/project-spec.ncl | 78 ++ .../mode-b-config/project-spec.toml | 181 ---- .../13-conditional-logic/conditional-demo.ncl | 84 ++ .../conditional-demo.toml | 198 ----- .../fragments/environment-setup.ncl | 24 + .../fragments/environment-setup.toml | 72 -- .../advanced-validators.ncl | 62 ++ .../advanced-validators.toml | 243 ------ .../basic-validators.ncl | 47 + .../basic-validators.toml | 171 ---- .../conditional-validators.ncl | 93 ++ .../conditional-validators.toml | 311 ------- .../fragments/team-member.ncl | 21 + .../fragments/team-member.toml | 48 -- .../employee-registration.ncl | 101 +++ .../employee-registration.toml | 301 ------- .../architecture-form.ncl | 83 ++ .../architecture-form.toml | 155 ---- .../confirmation-form.ncl | 47 + .../confirmation-form.toml | 119 --- .../recommendations-form.ncl | 48 ++ .../recommendations-form.toml | 125 --- .../validation-rules.ncl | 92 ++ .../validation-rules.toml | 99 --- examples/17-advanced-i18n/checkout-form.ncl | 54 ++ examples/17-advanced-i18n/checkout-form.toml | 134 --- examples/17-advanced-i18n/i18n-config.ncl | 144 ++++ examples/17-advanced-i18n/i18n-config.toml | 164 ---- justfiles/dev.just | 11 +- 184 files changed, 5247 insertions(+), 7405 deletions(-) create mode 100644 assets/web/README.md create mode 100644 assets/web/architecture-diagram.html create mode 100644 assets/web/index.html create mode 100755 assets/web/minify.sh create mode 100644 assets/web/src/architecture-diagram.html create mode 100644 assets/web/src/index.html create mode 100644 assets/web/src/typedialog_architecture.svg create mode 100644 assets/web/src/typedialog_architecture_white.svg create mode 100644 assets/web/typedialog_architecture.svg create mode 100644 assets/web/typedialog_architecture_white.svg create mode 100644 assets/web/typedialog_logo_h.svg create mode 100644 config/ag/config.ncl delete mode 100644 config/ag/default.toml create mode 100644 config/ag/dev.ncl delete mode 100644 config/ag/dev.toml create mode 100644 config/ag/production.ncl delete mode 100644 config/ag/production.toml delete mode 100644 config/ag/server-default.toml create mode 100644 config/ag/server.ncl create mode 100644 config/ai/config.ncl delete mode 100644 config/ai/default.toml create mode 100644 config/ai/dev.ncl delete mode 100644 config/ai/dev.toml create mode 100644 config/ai/production.ncl delete mode 100644 config/ai/production.toml create mode 100644 config/cli/config.ncl delete mode 100644 config/cli/default.toml create mode 100644 config/cli/dev.ncl delete mode 100644 config/cli/dev.toml create mode 100644 config/cli/production.ncl delete mode 100644 config/cli/production.toml create mode 100644 config/prov-gen/config.ncl delete mode 100644 config/prov-gen/default.toml create mode 100644 config/prov-gen/dev.ncl delete mode 100644 config/prov-gen/dev.toml create mode 100644 config/prov-gen/production.ncl delete mode 100644 config/prov-gen/production.toml create mode 100644 config/tui/config.ncl delete mode 100644 config/tui/default.toml create mode 100644 config/tui/dev.ncl delete mode 100644 config/tui/dev.toml create mode 100644 config/tui/production.ncl delete mode 100644 config/tui/production.toml create mode 100644 config/web/config.ncl delete mode 100644 config/web/default.toml create mode 100644 config/web/dev.ncl delete mode 100644 config/web/dev.toml create mode 100644 config/web/production.ncl delete mode 100644 config/web/production.toml delete mode 100644 crates/typedialog-core/tests/proptest_validation.rs create mode 100644 docs/adr/README.md create mode 100644 docs/adr/adr-001-nickel-form-definition.md create mode 100644 examples/01-basic/base_form.ncl delete mode 100644 examples/01-basic/base_from.toml create mode 100644 examples/01-basic/debug_simple.ncl delete mode 100644 examples/01-basic/debug_simple.toml create mode 100644 examples/01-basic/form.ncl delete mode 100644 examples/01-basic/form.toml create mode 100644 examples/01-basic/form_with_grouped_items.ncl delete mode 100644 examples/01-basic/form_with_grouped_items.toml create mode 100644 examples/01-basic/form_with_sections.ncl delete mode 100644 examples/01-basic/form_with_sections.toml create mode 100644 examples/02-advanced/conditional_form.ncl delete mode 100644 examples/02-advanced/conditional_form.toml create mode 100644 examples/02-advanced/conditional_sections.ncl delete mode 100644 examples/02-advanced/conditional_sections.toml create mode 100644 examples/02-advanced/display_items_demo.ncl delete mode 100644 examples/02-advanced/display_items_demo.toml create mode 100644 examples/02-multiselect-display-modes/form.ncl delete mode 100644 examples/02-multiselect-display-modes/form.toml create mode 100644 examples/03-styling/custom_border_form.ncl delete mode 100644 examples/03-styling/custom_border_form.toml create mode 100644 examples/03-styling/fancy_borders_form.ncl delete mode 100644 examples/03-styling/fancy_borders_form.toml create mode 100644 examples/04-backends/tui/tui_survey_form.ncl delete mode 100644 examples/04-backends/tui/tui_survey_form.toml create mode 100644 examples/04-backends/web/web_registration_form.ncl delete mode 100644 examples/04-backends/web/web_registration_form.toml create mode 100644 examples/05-fragments/agreement_section.ncl delete mode 100644 examples/05-fragments/agreement_section.toml create mode 100644 examples/05-fragments/array-trackers.ncl delete mode 100644 examples/05-fragments/array-trackers.toml create mode 100644 examples/05-fragments/constraints.ncl delete mode 100644 examples/05-fragments/constraints.toml create mode 100644 examples/05-fragments/custom_border_section.ncl delete mode 100644 examples/05-fragments/custom_border_section.toml create mode 100644 examples/05-fragments/employee_info_section.ncl delete mode 100644 examples/05-fragments/employee_info_section.toml create mode 100644 examples/05-fragments/enterprise_section.ncl delete mode 100644 examples/05-fragments/enterprise_section.toml create mode 100644 examples/05-fragments/fancy_border_section.ncl delete mode 100644 examples/05-fragments/fancy_border_section.toml create mode 100644 examples/05-fragments/form_with_groups_includes.ncl delete mode 100644 examples/05-fragments/form_with_groups_includes.toml create mode 100644 examples/05-fragments/fragments/tracker-http-item.ncl delete mode 100644 examples/05-fragments/fragments/tracker-http-item.toml create mode 100644 examples/05-fragments/fragments/tracker-udp-item.ncl delete mode 100644 examples/05-fragments/fragments/tracker-udp-item.toml create mode 100644 examples/05-fragments/header.ncl delete mode 100644 examples/05-fragments/header.toml create mode 100644 examples/05-fragments/premium_section.ncl delete mode 100644 examples/05-fragments/premium_section.toml create mode 100644 examples/05-fragments/support_section.ncl delete mode 100644 examples/05-fragments/support_section.toml delete mode 100644 examples/06-i18n/en-US.toml delete mode 100644 examples/06-i18n/es-ES.toml create mode 100644 examples/06-integrations/i18n/registration_i18n.ncl delete mode 100644 examples/06-integrations/i18n/registration_i18n.toml create mode 100644 examples/06-integrations/i18n/test_i18n_form.ncl delete mode 100644 examples/06-integrations/i18n/test_i18n_form.toml create mode 100644 examples/07-nickel-generation/arrays-form.ncl delete mode 100644 examples/07-nickel-generation/arrays-form.toml create mode 100644 examples/07-nickel-generation/fragments/api-endpoint-item.ncl delete mode 100644 examples/07-nickel-generation/fragments/api-endpoint-item.toml create mode 100644 examples/07-nickel-generation/fragments/user-item.ncl delete mode 100644 examples/07-nickel-generation/fragments/user-item.toml create mode 100644 examples/08-encryption/credentials.ncl delete mode 100644 examples/08-encryption/credentials.toml create mode 100644 examples/08-encryption/multi-backend-sops.ncl delete mode 100644 examples/08-encryption/multi-backend-sops.toml create mode 100644 examples/08-encryption/simple-login.ncl delete mode 100644 examples/08-encryption/simple-login.toml create mode 100644 examples/08-nickel-roundtrip/ci-form.ncl delete mode 100644 examples/08-nickel-roundtrip/ci-form.toml create mode 100644 examples/09-templates/employee_onboarding/employee_onboarding_form.ncl delete mode 100644 examples/09-templates/employee_onboarding/employee_onboarding_form.toml create mode 100644 examples/09-templates/onboarding_template.ncl delete mode 100644 examples/09-templates/onboarding_template.toml create mode 100644 examples/09-templates/user_registration/registration_form.ncl delete mode 100644 examples/09-templates/user_registration/registration_form.toml create mode 100644 examples/09-templates/user_registration/user_registration_form.ncl delete mode 100644 examples/09-templates/user_registration/user_registration_form.toml create mode 100644 examples/11-prov-gen/mode-b-config/project-spec.ncl delete mode 100644 examples/11-prov-gen/mode-b-config/project-spec.toml create mode 100644 examples/13-conditional-logic/conditional-demo.ncl delete mode 100644 examples/13-conditional-logic/conditional-demo.toml create mode 100644 examples/13-conditional-logic/fragments/environment-setup.ncl delete mode 100644 examples/13-conditional-logic/fragments/environment-setup.toml create mode 100644 examples/14-validators-and-contracts/advanced-validators.ncl delete mode 100644 examples/14-validators-and-contracts/advanced-validators.toml create mode 100644 examples/14-validators-and-contracts/basic-validators.ncl delete mode 100644 examples/14-validators-and-contracts/basic-validators.toml create mode 100644 examples/14-validators-and-contracts/conditional-validators.ncl delete mode 100644 examples/14-validators-and-contracts/conditional-validators.toml create mode 100644 examples/14-validators-and-contracts/fragments/team-member.ncl delete mode 100644 examples/14-validators-and-contracts/fragments/team-member.toml create mode 100644 examples/15-cross-backend-same-form/employee-registration.ncl delete mode 100644 examples/15-cross-backend-same-form/employee-registration.toml create mode 100644 examples/16-agent-form-integration/architecture-form.ncl delete mode 100644 examples/16-agent-form-integration/architecture-form.toml create mode 100644 examples/16-agent-form-integration/confirmation-form.ncl delete mode 100644 examples/16-agent-form-integration/confirmation-form.toml create mode 100644 examples/16-agent-form-integration/recommendations-form.ncl delete mode 100644 examples/16-agent-form-integration/recommendations-form.toml create mode 100644 examples/16-agent-form-integration/validation-rules.ncl delete mode 100644 examples/16-agent-form-integration/validation-rules.toml create mode 100644 examples/17-advanced-i18n/checkout-form.ncl delete mode 100644 examples/17-advanced-i18n/checkout-form.toml create mode 100644 examples/17-advanced-i18n/i18n-config.ncl delete mode 100644 examples/17-advanced-i18n/i18n-config.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b90bd2..7790358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ ## [Unreleased] +### Changed - Nickel as primary form and config format + +All form definitions and backend configs migrated from TOML to Nickel (`.ncl`). + +**Examples** (`examples/01-basic/` through `examples/17-advanced-i18n/`): all `.toml` form files +replaced with type-checked `.ncl` equivalents using Nickel contracts and `| default` annotations. + +**Backend configs** (`config/cli/`, `config/tui/`, `config/web/`, `config/ai/`, `config/ag/`, +`config/prov-gen/`): `default.toml`, `dev.toml`, `production.toml` replaced with `config.ncl`, +`dev.ncl`, `production.ncl`. Each expresses schema constraints inline (`| default`, `| String`, +`| Bool`) rather than raw TOML values. + +**Config loader** (`crates/typedialog-core/src/config/cli_loader.rs`): search order updated to +prefer `.ncl` over `.toml`. NCL files are loaded via `nickel export --format json` and deserialised +with `serde_json`; TOML path retained as fallback. New order: + +1. `{backend}/{TYPEDIALOG_ENV}.ncl` +2. `{backend}/{TYPEDIALOG_ENV}.toml` +3. `{backend}/config.ncl` +4. `{backend}/config.toml` + +TOML form definitions remain supported — no breaking change to the parser or any backend. + ### Refactored - Eliminate duplicated field execution logic **Single source of truth for CLI field dispatch** diff --git a/README.md b/README.md index 3bffb8c..cf1c514 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - **6 Backends**: CLI (inquire), TUI (ratatui), Web (axum), AI (RAG/embeddings), Agent (LLM execution), Prov-gen (IaC generation) - **8 Prompt Types**: text, confirm, select, multi-select, password, date, editor, custom -- **Declarative Forms**: TOML-based definitions with fragments & composition +- **Declarative Forms**: Nickel (`.ncl`) definitions with type contracts, fragments & composition (TOML also supported) - **4 Output Formats**: JSON, YAML, TOML, Nickel with roundtrip conversion - **Zero Runtime Dependencies**: Core library works standalone @@ -68,7 +68,7 @@ just test::all cargo run --example form # Run with defaults pre-loaded -typedialog form config.toml --defaults defaults.json +typedialog form config.ncl --defaults defaults.json ```text ## Unified Command Interface @@ -77,15 +77,15 @@ All backends are accessible through the **single `typedialog` command** with aut ```bash # Each backend can be invoked directly via typedialog -typedialog web form config.toml --port 8080 # Web backend (browser forms) -typedialog tui config.toml # TUI backend (terminal UI) +typedialog web form config.ncl --port 8080 # Web backend (browser forms) +typedialog tui config.ncl # TUI backend (terminal UI) typedialog ai serve --port 8765 # AI backend (RAG assistant) typedialog ag run agent.mdx # Agent backend (LLM agents) typedialog prov-gen generate --spec project.ncl # Provisioning generator # Or use specific binaries if you prefer -typedialog-web form config.toml --port 8080 -typedialog-tui config.toml +typedialog-web form config.ncl --port 8080 +typedialog-tui config.ncl typedialog-ai serve --port 8765 typedialog-ag run agent.mdx typedialog-prov-gen generate --spec project.ncl @@ -98,7 +98,7 @@ typedialog ag -h typedialog prov-gen -h ```text -All backends produce identical JSON output from the same TOML form definition, making it easy to switch between interfaces without changing your data. +All backends produce identical JSON output from the same form definition (Nickel or TOML), making it easy to switch between interfaces without changing your data. ## Backends at a Glance @@ -117,7 +117,7 @@ typedialog select "Choose role" Admin User Guest typedialog text "Email" --format json # Pre-populate form with defaults -typedialog form schema.toml --defaults config.json --format json +typedialog form schema.ncl --defaults config.json --format json ```text **Use for:** Scripts, CI/CD pipelines, server tools, piping between tools @@ -129,10 +129,10 @@ Full terminal UI with keyboard navigation and mouse support. ```bash # Via dispatcher -typedialog tui config.toml +typedialog tui config.ncl # Or run directly -typedialog-tui config.toml +typedialog-tui config.ncl cargo run -p typedialog-tui --example form_with_autocompletion ```text @@ -145,11 +145,11 @@ HTTP server with browser-based forms. ```bash # Via dispatcher -typedialog web form config.toml --port 8080 +typedialog web form config.ncl --port 8080 # Or run directly -typedialog-web form config.toml --port 8080 -cargo run -p typedialog-web -- --config config/web/dev.toml +typedialog-web form config.ncl --port 8080 +cargo run -p typedialog-web -- --config config/web/dev.ncl # Open http://localhost:8080 ```text @@ -168,10 +168,10 @@ typedialog ai serve --port 8765 typedialog-ai serve --port 8765 # Query knowledge base -typedialog-ai --config config/ai/dev.toml --query "How do I configure encryption?" +typedialog-ai --config config/ai/dev.ncl --query "How do I configure encryption?" # Build knowledge graph -typedialog-ai --config config/ai/production.toml --build-graph ./docs +typedialog-ai --config config/ai/production.ncl --build-graph ./docs ```text **Use for:** Documentation search, context-aware assistance, knowledge retrieval, semantic search @@ -319,13 +319,10 @@ typedialog-prov-gen --name myproject --dry-run Generate interactive forms from Nickel schemas, collect user input, and produce validated configuration output: ```bash -# 1. Define schema in Nickel -nickel eval config.ncl > schema.toml +# 1. Define form directly in Nickel +typedialog form config.ncl --backend tui -# 2. Run interactive form -typedialog form schema.toml --backend tui - -# 3. Get validated output in any format +# 2. Get validated output in any format # JSON, YAML, TOML, or back to Nickel with type preservation ```text diff --git a/assets/web/README.md b/assets/web/README.md new file mode 100644 index 0000000..321575b --- /dev/null +++ b/assets/web/README.md @@ -0,0 +1,86 @@ +# TypeDialog Web Assets + +Web-based landing page, architecture diagram, and static content for TypeDialog. + +## Directory Structure + +```text +assets/web/ +├── src/ +│ ├── index.html # Source HTML (readable) +│ └── architecture-diagram.html # Source architecture viewer +├── index.html # Minified/Production HTML +├── architecture-diagram.html # Minified architecture viewer +├── typedialog_architecture.svg # Architecture diagram (dark mode) +├── typedialog_architecture_white.svg # Architecture diagram (light mode) +├── minify.sh # Build script +└── README.md +``` + +## Files + +### `src/index.html` - Source Landing Page + +- Inline CSS and JavaScript (no external dependencies beyond Inter font) +- Bilingual content (English/Spanish) with localStorage persistence +- Dark/light theme toggle with logo swap +- Responsive design (mobile-first) +- Link to architecture diagram viewer + +### `src/architecture-diagram.html` - Architecture Viewer + +- Full-page SVG architecture diagram +- Dark/light theme toggle (swaps between two SVG variants) +- Back-link to landing page +- Shares theme preference with landing page via localStorage + +### Architecture SVGs + +Two variants of the architecture diagram: + +- `typedialog_architecture.svg` - Dark background (#0f0f1a) +- `typedialog_architecture_white.svg` - Light background (#ffffff) + +Both show the complete TypeDialog architecture: + +- Form Definitions layer (Nickel + TOML + load_form) +- typedialog-core (three-phase execution, core modules, BackendFactory) +- 6 Backends (CLI, TUI, Web, AI, Agent, Prov-Gen) +- Output formats (JSON, YAML, TOML, Nickel Roundtrip) +- LLM Providers (Claude, OpenAI, Gemini, Ollama) +- Integrations (Nushell, Nickel Contracts, Tera, Multi-Cloud, CI/CD) + +## Development + +Edit source files in `src/`, then regenerate minified versions: + +```bash +chmod +x assets/web/minify.sh +./assets/web/minify.sh +``` + +## Deployment + +Serve from any static web server: + +```bash +# Rust +cargo install static-web-server +static-web-server -d assets/web/ + +# Python +python3 -m http.server --directory assets/web + +# Node.js +npx http-server assets/web +``` + +Logo SVGs are referenced from the parent `assets/` directory via relative paths (`../typedialog_logo_h.svg`). + +## Features + +- **Responsive**: Mobile-first with media queries +- **Performance**: Inline CSS/JS, no frameworks, minified production +- **Bilingual**: EN/ES with dynamic switching +- **Theming**: Dark/light with localStorage persistence and logo swap +- **Architecture**: SVG diagram with dark/light variants showing full system diff --git a/assets/web/architecture-diagram.html b/assets/web/architecture-diagram.html new file mode 100644 index 0000000..35bc679 --- /dev/null +++ b/assets/web/architecture-diagram.html @@ -0,0 +1 @@ +TypeDialog — Architecture
TypeDialog Architecture - Dark Mode
diff --git a/assets/web/index.html b/assets/web/index.html new file mode 100644 index 0000000..3f2ec9d --- /dev/null +++ b/assets/web/index.html @@ -0,0 +1 @@ +TypeDialog
ARCHITECTURE
3,818 Tests | 6 Backends | 4 LLM Providers
TypeDialog

Typed dialogs for inputs, forms and schemas you can trust

Create Type-Safe
Interactive Dialogs

Declarative forms with Nickel/TOML definitions, 6 backends (CLI, TUI, Web, AI, Agent, Prov-Gen), and type-safe validation. From interactive prompts to infrastructure generation.
One schema. Every surface.

What TypeDialog Solves

01

Per-Backend Code

One Nickel or TOML definition drives CLI, TUI, Web, and AI. No per-backend form code. The schema is the single source of truth.

02

Fail-Open Validation

Nickel contracts validate every predicate at load time. Unknown predicates cause hard failures, not silent passes. No parallel Rust reimplementation.

03

Hidden I/O in Execution

Fragment loading happens once at load_form(). The three-phase executor is pure — no filesystem access, no side effects during user interaction.

04

Manual IaC Assembly

Interactive forms generate validated infrastructure configurations for 6 cloud providers. 7-layer validation pipeline from forms to final JSON.

How It Works

<>

Declarative Forms

Define forms in Nickel (.ncl) or TOML (.toml). Nickel provides contracts, imports, and type-safe composition. TOML provides zero-dependency simplicity. Both produce identical FormDefinition structs.

Three-Phase Execution

Phase 1: Execute selector fields that control conditionals. Phase 2: Build element list (pure, no I/O). Phase 3: Dispatch to backend with when/when_false evaluation. Complete or field-by-field rendering modes.

🔌

BackendFactory

Compile-time feature gates (#[cfg(feature)]) eliminate dead backend code. Runtime BackendType match dispatches to Box<dyn FormBackend>. Auto-detection via TYPEDIALOG_BACKEND env var with CLI fallback.

🤖

AI & Agent Backends

AI backend with RAG, embeddings, and semantic search. Agent backend executes .agent.mdx files with multi-LLM support (Claude, OpenAI, Gemini, Ollama). Template variables, file imports, streaming output.

Infrastructure Generation

Prov-Gen transforms form answers into IaC configurations. 6 cloud providers (AWS, GCP, Azure, Hetzner, UpCloud, LXD). 7-layer validation: Forms → Constraints → Values → Validators → Schemas → Defaults → JSON.

🔄

Nickel Roundtrip

Read .ncl schemas, collect user input via any backend, generate validated .ncl output preserving contracts. ContractParser extracts validators. TemplateRenderer preserves formatting. when_false ensures all schema fields have values.

Technology Stack

Rust (8 crates)Nickel ContractsTOML FormsInquire (CLI)Ratatui (TUI)Axum (Web)Fluent i18nTera TemplatesNushell PluginClaude APIOpenAI APIGemini APIOllama (local)Multi-Cloud IaC

Backends

CLIinquire — interactive prompts
TUIratatui — terminal UI
Webaxum — HTTP forms
AIRAG & embeddings
AgentMulti-LLM execution
Prov-GenIaC generation

Type-safe dialogs for every surface

Built with Rust | Open Source | MIT License

Explore on GitHub →

TypeDialog

Typed dialogs for inputs, forms and schemas you can trust

Multi-Backend Form Orchestration | Nickel + TOML | 6 Backends | 4 LLM Providers

diff --git a/assets/web/minify.sh b/assets/web/minify.sh new file mode 100755 index 0000000..1093f13 --- /dev/null +++ b/assets/web/minify.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# Minify HTML files from src/ to production versions +# Usage: ./minify.sh + +set -e + +BASE_DIR="$(dirname "$0")" +FILES=("index.html" "architecture-diagram.html") + +minify_file() { + local filename="$1" + local SRC_FILE="${BASE_DIR}/src/${filename}" + local OUT_FILE="${BASE_DIR}/${filename}" + local TEMP_FILE="${OUT_FILE}.tmp" + + if [ ! -f "$SRC_FILE" ]; then + echo " Source file not found: $SRC_FILE (skipping)" + return 0 + fi + + echo "" + echo " Minifying ${filename}..." + echo " Input: $SRC_FILE" + echo " Output: $OUT_FILE" + +perl -e " +use strict; +use warnings; + +open(my \$fh, '<', '$SRC_FILE') or die \$!; +my \$content = do { local \$/; <\$fh> }; +close(\$fh); + +# Remove HTML comments +\$content =~ s///gs; + +# Compress CSS (remove spaces and comments) +\$content =~ s/(]*>)(.*?)(<\/style>)/ + my \$before = \$1; + my \$style = \$2; + my \$after = \$3; + \$style =~ s{\/\*.*?\*\/}{}gs; + \$style =~ s{\s+}{ }gs; + \$style =~ s{\s*([{}:;,>+~])\s*}{\$1}gs; + \$before . \$style . \$after; +/gies; + +# Compress JavaScript (remove comments and extra spaces) +\$content =~ s/(]*>)(.*?)(<\/script>)/ + my \$before = \$1; + my \$script = \$2; + my \$after = \$3; + \$script =~ s{\/\/.*\$}{}gm; + \$script =~ s{\s+}{ }gs; + \$script =~ s{\s*([{}();,])\s*}{\$1}gs; + \$before . \$script . \$after; +/gies; + +# Remove whitespace between tags +\$content =~ s/>\s+', '$TEMP_FILE') or die \$!; +print \$out \$content; +close(\$out); +" || { + echo " Minification failed" + rm -f "$TEMP_FILE" + exit 1 +} + + mv "$TEMP_FILE" "$OUT_FILE" + + # Show statistics + original=$(wc -c < "$SRC_FILE") + minified=$(wc -c < "$OUT_FILE") + saved=$((original - minified)) + percent=$((saved * 100 / original)) + + echo "" + echo " Compression statistics:" + printf " Original: %6d bytes\n" "$original" + printf " Minified: %6d bytes\n" "$minified" + printf " Saved: %6d bytes (%d%%)\n" "$saved" "$percent" + echo " ${filename} ready for production" +} + +echo "Starting HTML minification..." + +for file in "${FILES[@]}"; do + minify_file "$file" +done + +echo "" +echo "All files minified." +echo "" diff --git a/assets/web/src/architecture-diagram.html b/assets/web/src/architecture-diagram.html new file mode 100644 index 0000000..c9dc272 --- /dev/null +++ b/assets/web/src/architecture-diagram.html @@ -0,0 +1,173 @@ + + + + + + TypeDialog — Architecture + + + + + +
+ TypeDialog Architecture - Dark Mode + +
+ + + + diff --git a/assets/web/src/index.html b/assets/web/src/index.html new file mode 100644 index 0000000..27f6b14 --- /dev/null +++ b/assets/web/src/index.html @@ -0,0 +1,807 @@ + + + + + + TypeDialog + + + + +
+ +
+ + + + ARCHITECTURE +
+ +
+ + +
+ 3,818 Tests | 6 Backends | 4 LLM Providers + +
+ TypeDialog + +
+ +

Typed dialogs for inputs, forms and schemas you can trust

+ +

Create Type-Safe
Interactive Dialogs

+ +

+ Declarative forms + with Nickel/TOML definitions, 6 backends (CLI, TUI, Web, AI, Agent, Prov-Gen), and type-safe validation. From interactive prompts to infrastructure generation. +
+ One schema. Every surface. +

+
+ + +
+

+ What TypeDialog Solves +

+
+
+
01
+

Per-Backend Code

+

One Nickel or TOML definition drives CLI, TUI, Web, and AI. No per-backend form code. The schema is the single source of truth.

+
+
+
02
+

Fail-Open Validation

+

Nickel contracts validate every predicate at load time. Unknown predicates cause hard failures, not silent passes. No parallel Rust reimplementation.

+
+
+
03
+

Hidden I/O in Execution

+

Fragment loading happens once at load_form(). The three-phase executor is pure — no filesystem access, no side effects during user interaction.

+
+
+
04
+

Manual IaC Assembly

+

Interactive forms generate validated infrastructure configurations for 6 cloud providers. 7-layer validation pipeline from forms to final JSON.

+
+
+
+ + +
+

+ How It Works +

+
+ +
+
<>
+

Declarative Forms

+

Define forms in Nickel (.ncl) or TOML (.toml). Nickel provides contracts, imports, and type-safe composition. TOML provides zero-dependency simplicity. Both produce identical FormDefinition structs.

+
+ +
+
+

Three-Phase Execution

+

Phase 1: Execute selector fields that control conditionals. Phase 2: Build element list (pure, no I/O). Phase 3: Dispatch to backend with when/when_false evaluation. Complete or field-by-field rendering modes.

+
+ +
+
🔌
+

BackendFactory

+

Compile-time feature gates (#[cfg(feature)]) eliminate dead backend code. Runtime BackendType match dispatches to Box<dyn FormBackend>. Auto-detection via TYPEDIALOG_BACKEND env var with CLI fallback.

+
+ +
+
🤖
+

AI & Agent Backends

+

AI backend with RAG, embeddings, and semantic search. Agent backend executes .agent.mdx files with multi-LLM support (Claude, OpenAI, Gemini, Ollama). Template variables, file imports, streaming output.

+
+ +
+
+

Infrastructure Generation

+

Prov-Gen transforms form answers into IaC configurations. 6 cloud providers (AWS, GCP, Azure, Hetzner, UpCloud, LXD). 7-layer validation: Forms → Constraints → Values → Validators → Schemas → Defaults → JSON.

+
+ +
+
🔄
+

Nickel Roundtrip

+

Read .ncl schemas, collect user input via any backend, generate validated .ncl output preserving contracts. ContractParser extracts validators. TemplateRenderer preserves formatting. when_false ensures all schema fields have values.

+
+ +
+
+ + +
+

+ Technology Stack +

+
+ Rust (8 crates) + Nickel Contracts + TOML Forms + Inquire (CLI) + Ratatui (TUI) + Axum (Web) + Fluent i18n + Tera Templates + Nushell Plugin + Claude API + OpenAI API + Gemini API + Ollama (local) + Multi-Cloud IaC +
+
+ + +
+

+ Backends +

+
+
+ CLI + inquire — interactive prompts +
+
+ TUI + ratatui — terminal UI +
+
+ Web + axum — HTTP forms +
+
+ AI + RAG & embeddings +
+
+ Agent + Multi-LLM execution +
+
+ Prov-Gen + IaC generation +
+
+
+ + +
+

Type-safe dialogs for every surface

+

Built with Rust | Open Source | MIT License

+ Explore on GitHub → +
+ + +
+

TypeDialog

+

Typed dialogs for inputs, forms and schemas you can trust

+

Multi-Backend Form Orchestration | Nickel + TOML | 6 Backends | 4 LLM Providers

+
+ +
+ + + + diff --git a/assets/web/src/typedialog_architecture.svg b/assets/web/src/typedialog_architecture.svg new file mode 100644 index 0000000..e4c8908 --- /dev/null +++ b/assets/web/src/typedialog_architecture.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + TypeDialog Architecture + Multi-Backend Form Orchestration Layer + + + FORM DEFINITIONS + + + + + Nickel (.ncl) + nickel export --format json + + + + TOML (.toml) + serde direct deserialization + + + + load_form() — Unified Entry Point + Extension dispatch: .ncl → subprocess | .toml → serde | Fail-fast on errors + + + + + FormDefinition + + + TYPEDIALOG-CORE + + + + THREE-PHASE EXECUTION + + + + Phase 1: Selectors + Identify & execute + + + + Phase 2: Element List + Pure, no I/O + + + + Phase 3: Dispatch + when/when_false eval + + + + + + + + + CORE MODULES + + + + form_parser + TOML/Nickel parsing + Field definitions + + + + validation + Nickel contracts + Pre/post conditions + + + + i18n (Fluent) + Locale detection + .ftl translations + + + + encryption + Field-level encrypt + External services + + + + templates (Tera) + Jinja2-compatible + Variable rendering + + + + RenderContext + results: HashMap + locale + + + + BackendFactory + #[cfg(feature)] compile-time + + runtime BackendType match + + + + + Box<dyn FormBackend> + + trait FormBackend: Send + Sync { execute_field, execute_form_complete } + + + BACKENDS (6 CRATES) + + + + + CLI + inquire 0.9 + Interactive prompts + Scripts, CI/CD + + + + TUI + ratatui + Terminal UI + Keyboard + Mouse + + + + Web + axum + HTTP server + Browser forms + + + + AI + RAG + embeddings + Semantic search + Knowledge graph + + + + Agent + Multi-LLM execution + .agent.mdx files + Streaming, templates + + + + Prov-Gen + IaC generation + AWS, GCP, Azure + Hetzner, UpCloud, LXD + + + + + HashMap<String, Value> + + + OUTPUT FORMATS + + + + + JSON + + + YAML + + + TOML + + + Nickel Roundtrip + + + LLM PROVIDERS + + + + Claude + + + OpenAI + + + Gemini + + + Ollama (local) + + + INTEGRATIONS + + + + Nushell Plugin + + + Nickel Contracts + + + Template Engine (Tera) + + + Multi-Cloud APIs + + + CI/CD (GitHub + Woodpecker) + + + 8 crates | 6 backends | 3,818 tests | 4 output formats | 4 LLM providers | 6 cloud targets + + + diff --git a/assets/web/src/typedialog_architecture_white.svg b/assets/web/src/typedialog_architecture_white.svg new file mode 100644 index 0000000..63f7daf --- /dev/null +++ b/assets/web/src/typedialog_architecture_white.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + TypeDialog Architecture + Multi-Backend Form Orchestration Layer + + + FORM DEFINITIONS + + + + + Nickel (.ncl) + nickel export --format json + + + + TOML (.toml) + serde direct deserialization + + + + load_form() — Unified Entry Point + Extension dispatch: .ncl → subprocess | .toml → serde | Fail-fast on errors + + + + + FormDefinition + + + TYPEDIALOG-CORE + + + + THREE-PHASE EXECUTION + + + Phase 1: Selectors + Identify & execute + + + Phase 2: Element List + Pure, no I/O + + + Phase 3: Dispatch + when/when_false eval + + + + + + + + CORE MODULES + + + form_parser + TOML/Nickel parsing + Field definitions + + + validation + Nickel contracts + Pre/post conditions + + + i18n (Fluent) + Locale detection + .ftl translations + + + encryption + Field-level encrypt + External services + + + templates (Tera) + Jinja2-compatible + Variable rendering + + + + RenderContext + results: HashMap + locale + + + + BackendFactory + #[cfg(feature)] compile-time + + runtime BackendType match + + + + + Box<dyn FormBackend> + + trait FormBackend: Send + Sync { execute_field, execute_form_complete } + + + BACKENDS (6 CRATES) + + + + CLI + inquire 0.9 + Interactive prompts + Scripts, CI/CD + + + TUI + ratatui + Terminal UI + Keyboard + Mouse + + + Web + axum + HTTP server + Browser forms + + + AI + RAG + embeddings + Semantic search + Knowledge graph + + + Agent + Multi-LLM execution + .agent.mdx files + Streaming, templates + + + Prov-Gen + IaC generation + AWS, GCP, Azure + Hetzner, UpCloud, LXD + + + + + HashMap<String, Value> + + + OUTPUT FORMATS + + + + JSON + + + YAML + + + TOML + + + Nickel Roundtrip + + + LLM PROVIDERS + + + + Claude + + + OpenAI + + + Gemini + + + Ollama (local) + + + INTEGRATIONS + + + + Nushell Plugin + + + Nickel Contracts + + + Template Engine (Tera) + + + Multi-Cloud APIs + + + CI/CD (GitHub + Woodpecker) + + + 8 crates | 6 backends | 3,818 tests | 4 output formats | 4 LLM providers | 6 cloud targets + + + diff --git a/assets/web/typedialog_architecture.svg b/assets/web/typedialog_architecture.svg new file mode 100644 index 0000000..e4c8908 --- /dev/null +++ b/assets/web/typedialog_architecture.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + TypeDialog Architecture + Multi-Backend Form Orchestration Layer + + + FORM DEFINITIONS + + + + + Nickel (.ncl) + nickel export --format json + + + + TOML (.toml) + serde direct deserialization + + + + load_form() — Unified Entry Point + Extension dispatch: .ncl → subprocess | .toml → serde | Fail-fast on errors + + + + + FormDefinition + + + TYPEDIALOG-CORE + + + + THREE-PHASE EXECUTION + + + + Phase 1: Selectors + Identify & execute + + + + Phase 2: Element List + Pure, no I/O + + + + Phase 3: Dispatch + when/when_false eval + + + + + + + + + CORE MODULES + + + + form_parser + TOML/Nickel parsing + Field definitions + + + + validation + Nickel contracts + Pre/post conditions + + + + i18n (Fluent) + Locale detection + .ftl translations + + + + encryption + Field-level encrypt + External services + + + + templates (Tera) + Jinja2-compatible + Variable rendering + + + + RenderContext + results: HashMap + locale + + + + BackendFactory + #[cfg(feature)] compile-time + + runtime BackendType match + + + + + Box<dyn FormBackend> + + trait FormBackend: Send + Sync { execute_field, execute_form_complete } + + + BACKENDS (6 CRATES) + + + + + CLI + inquire 0.9 + Interactive prompts + Scripts, CI/CD + + + + TUI + ratatui + Terminal UI + Keyboard + Mouse + + + + Web + axum + HTTP server + Browser forms + + + + AI + RAG + embeddings + Semantic search + Knowledge graph + + + + Agent + Multi-LLM execution + .agent.mdx files + Streaming, templates + + + + Prov-Gen + IaC generation + AWS, GCP, Azure + Hetzner, UpCloud, LXD + + + + + HashMap<String, Value> + + + OUTPUT FORMATS + + + + + JSON + + + YAML + + + TOML + + + Nickel Roundtrip + + + LLM PROVIDERS + + + + Claude + + + OpenAI + + + Gemini + + + Ollama (local) + + + INTEGRATIONS + + + + Nushell Plugin + + + Nickel Contracts + + + Template Engine (Tera) + + + Multi-Cloud APIs + + + CI/CD (GitHub + Woodpecker) + + + 8 crates | 6 backends | 3,818 tests | 4 output formats | 4 LLM providers | 6 cloud targets + + + diff --git a/assets/web/typedialog_architecture_white.svg b/assets/web/typedialog_architecture_white.svg new file mode 100644 index 0000000..63f7daf --- /dev/null +++ b/assets/web/typedialog_architecture_white.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + TypeDialog Architecture + Multi-Backend Form Orchestration Layer + + + FORM DEFINITIONS + + + + + Nickel (.ncl) + nickel export --format json + + + + TOML (.toml) + serde direct deserialization + + + + load_form() — Unified Entry Point + Extension dispatch: .ncl → subprocess | .toml → serde | Fail-fast on errors + + + + + FormDefinition + + + TYPEDIALOG-CORE + + + + THREE-PHASE EXECUTION + + + Phase 1: Selectors + Identify & execute + + + Phase 2: Element List + Pure, no I/O + + + Phase 3: Dispatch + when/when_false eval + + + + + + + + CORE MODULES + + + form_parser + TOML/Nickel parsing + Field definitions + + + validation + Nickel contracts + Pre/post conditions + + + i18n (Fluent) + Locale detection + .ftl translations + + + encryption + Field-level encrypt + External services + + + templates (Tera) + Jinja2-compatible + Variable rendering + + + + RenderContext + results: HashMap + locale + + + + BackendFactory + #[cfg(feature)] compile-time + + runtime BackendType match + + + + + Box<dyn FormBackend> + + trait FormBackend: Send + Sync { execute_field, execute_form_complete } + + + BACKENDS (6 CRATES) + + + + CLI + inquire 0.9 + Interactive prompts + Scripts, CI/CD + + + TUI + ratatui + Terminal UI + Keyboard + Mouse + + + Web + axum + HTTP server + Browser forms + + + AI + RAG + embeddings + Semantic search + Knowledge graph + + + Agent + Multi-LLM execution + .agent.mdx files + Streaming, templates + + + Prov-Gen + IaC generation + AWS, GCP, Azure + Hetzner, UpCloud, LXD + + + + + HashMap<String, Value> + + + OUTPUT FORMATS + + + + JSON + + + YAML + + + TOML + + + Nickel Roundtrip + + + LLM PROVIDERS + + + + Claude + + + OpenAI + + + Gemini + + + Ollama (local) + + + INTEGRATIONS + + + + Nushell Plugin + + + Nickel Contracts + + + Template Engine (Tera) + + + Multi-Cloud APIs + + + CI/CD (GitHub + Woodpecker) + + + 8 crates | 6 backends | 3,818 tests | 4 output formats | 4 LLM providers | 6 cloud targets + + + diff --git a/assets/web/typedialog_logo_h.svg b/assets/web/typedialog_logo_h.svg new file mode 100644 index 0000000..b6e35a3 --- /dev/null +++ b/assets/web/typedialog_logo_h.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + TypeDialog + diff --git a/config/ag/config.ncl b/config/ag/config.ncl new file mode 100644 index 0000000..c8620b4 --- /dev/null +++ b/config/ag/config.ncl @@ -0,0 +1,33 @@ +{ + agent = { + default_provider | default = "claude", + models = { + claude | default = "claude-3-5-haiku-20241022", + openai | default = "gpt-4o-mini", + gemini | default = "gemini-2.0-flash-exp", + ollama | default = "llama2", + }, + defaults = { + max_tokens | default = 4096, + temperature | default = 0.7, + streaming | default = true, + }, + template = { + engine | default = "tera", + strict_variables | default = false, + }, + validation = { + enabled | default = true, + strict | default = false, + }, + output = { + format | default = "markdown", + color | default = true, + timestamp | default = false, + }, + logging = { + level | default = "info", + file | default = false, + }, + }, +} diff --git a/config/ag/default.toml b/config/ag/default.toml deleted file mode 100644 index 604aef6..0000000 --- a/config/ag/default.toml +++ /dev/null @@ -1,39 +0,0 @@ -# TypeDialog Agent - Default Configuration - -[agent] -# Default LLM provider (claude, openai, gemini, ollama) -default_provider = "claude" - - # Default model per provider - [agent.models] - claude = "claude-3-5-haiku-20241022" - gemini = "gemini-2.0-flash-exp" - ollama = "llama2" - openai = "gpt-4o-mini" - - # Default settings - [agent.defaults] - max_tokens = 4096 - streaming = true - temperature = 0.7 - - # Template settings - [agent.template] - engine = "tera" # Jinja2-compatible - strict_variables = false - - # Validation settings - [agent.validation] - enabled = true - strict = false - - # Output settings - [agent.output] - color = true - format = "markdown" - timestamp = false - - # Logging - [agent.logging] - file = false - level = "info" diff --git a/config/ag/dev.ncl b/config/ag/dev.ncl new file mode 100644 index 0000000..24598c1 --- /dev/null +++ b/config/ag/dev.ncl @@ -0,0 +1,14 @@ +(import "./config.ncl") +& { + agent = { + default_provider = "ollama", + defaults.max_tokens = 2048, + template.strict_variables = true, + validation.strict = true, + output.timestamp = true, + logging = { + level = "debug", + file = true, + }, + }, +} diff --git a/config/ag/dev.toml b/config/ag/dev.toml deleted file mode 100644 index 5e5f7c0..0000000 --- a/config/ag/dev.toml +++ /dev/null @@ -1,32 +0,0 @@ -# TypeDialog Agent - Development Configuration - -[agent] -default_provider = "ollama" # Use local for dev - - [agent.models] - claude = "claude-3-5-haiku-20241022" - gemini = "gemini-2.0-flash-exp" - ollama = "llama2" - openai = "gpt-4o-mini" - - [agent.defaults] - max_tokens = 2048 # Lower for dev - streaming = true - temperature = 0.7 - - [agent.template] - engine = "tera" - strict_variables = true # Catch template errors in dev - - [agent.validation] - enabled = true - strict = true # Strict validation in dev - - [agent.output] - color = true - format = "markdown" - timestamp = true - - [agent.logging] - file = true - level = "debug" # Verbose in dev diff --git a/config/ag/production.ncl b/config/ag/production.ncl new file mode 100644 index 0000000..55729e0 --- /dev/null +++ b/config/ag/production.ncl @@ -0,0 +1,29 @@ +(import "./config.ncl") +& { + agent = { + default_provider = "claude", + models = { + claude = "claude-3-5-sonnet-20241022", + gemini = "gemini-1.5-pro", + openai = "gpt-4o", + }, + defaults = { + max_tokens = 8192, + temperature = 0.3, + }, + template.strict_variables = true, + validation.strict = true, + output = { + color = false, + timestamp = true, + }, + logging = { + level = "warn", + file = true, + }, + rate_limit = { + enabled = true, + max_requests_per_minute = 60, + }, + }, +} diff --git a/config/ag/production.toml b/config/ag/production.toml deleted file mode 100644 index e7c0ce3..0000000 --- a/config/ag/production.toml +++ /dev/null @@ -1,37 +0,0 @@ -# TypeDialog Agent - Production Configuration - -[agent] -default_provider = "claude" - - [agent.models] - claude = "claude-3-5-sonnet-20241022" # Higher quality for production - gemini = "gemini-1.5-pro" - ollama = "llama2" - openai = "gpt-4o" - - [agent.defaults] - max_tokens = 8192 - streaming = true - temperature = 0.3 # More consistent - - [agent.template] - engine = "tera" - strict_variables = true - - [agent.validation] - enabled = true - strict = true - - [agent.output] - color = false # No color in production logs - format = "markdown" - timestamp = true - - [agent.logging] - file = true - level = "warn" # Less verbose in production - - # Rate limiting (production) - [agent.rate_limit] - enabled = true - max_requests_per_minute = 60 diff --git a/config/ag/server-default.toml b/config/ag/server-default.toml deleted file mode 100644 index 2ec48fa..0000000 --- a/config/ag/server-default.toml +++ /dev/null @@ -1,8 +0,0 @@ -# TypeDialog Agent Server Configuration -# Copy to ~/.config/typedialog/agent-server/config.toml - -# Server host (default: 127.0.0.1) -host = "127.0.0.1" - -# Server port (default: 8765) -port = 8765 diff --git a/config/ag/server.ncl b/config/ag/server.ncl new file mode 100644 index 0000000..0cd11d8 --- /dev/null +++ b/config/ag/server.ncl @@ -0,0 +1,4 @@ +{ + host | default = "127.0.0.1", + port | default = 8765, +} diff --git a/config/ai/config.ncl b/config/ai/config.ncl new file mode 100644 index 0000000..059924d --- /dev/null +++ b/config/ai/config.ncl @@ -0,0 +1,29 @@ +{ + llm = { + provider | default = "openai", + model | default = "gpt-3.5-turbo", + api_endpoint | default = "", + generation = { + temperature | default = 0.7, + max_tokens | default = 2048, + top_p | default = 0.9, + }, + }, + rag = { + enabled | default = true, + index_path | default = "~/.config/typedialog/ai/rag-index", + embedding_dims | default = 384, + cache_size | default = 1000, + }, + microservice = { + host | default = "127.0.0.1", + port | default = 3001, + enable_cors | default = false, + enable_websocket | default = true, + }, + appearance = { + interaction_mode | default = "interactive", + show_suggestions | default = true, + suggestion_confidence_threshold | default = 0.5, + }, +} diff --git a/config/ai/default.toml b/config/ai/default.toml deleted file mode 100644 index e6da7a5..0000000 --- a/config/ai/default.toml +++ /dev/null @@ -1,66 +0,0 @@ -# AI Backend - Default Configuration -# Provides intelligent form assistance using LLM + RAG system - -[llm] -# LLM Provider: openai, anthropic, ollama -provider = "openai" - -# Model to use for the selected provider -# OpenAI: gpt-4, gpt-3.5-turbo -# Anthropic: claude-3-opus, claude-3-sonnet, claude-3-haiku -# Ollama: depends on locally installed models -model = "gpt-3.5-turbo" - -# API endpoint (optional, uses provider defaults if not set) -# OpenAI: https://api.openai.com/v1 -# Anthropic: https://api.anthropic.com/v1 -# Ollama: http://localhost:11434/api -api_endpoint = "" - - [llm.generation] - # Temperature: 0.0-2.0, higher = more creative, lower = more focused - temperature = 0.7 - - # Maximum tokens in response - max_tokens = 2048 - - # Top-p (nucleus) sampling: 0.0-1.0 - top_p = 0.9 - -[rag] -# Enable RAG (Retrieval-Augmented Generation) system -enabled = true - -# Index directory for cached embeddings and vector store -# If relative path, resolved from ~/.config/typedialog/ai/ -index_path = "~/.config/typedialog/ai/rag-index" - -# Embedding dimensions: 384, 768, 1024 -embedding_dims = 384 - -# Cache size for vector store (approximate, in embeddings) -cache_size = 1000 - -[microservice] -# HTTP server settings -host = "127.0.0.1" -port = 3001 - -# Enable CORS for web clients -enable_cors = false - -# WebSocket support for streaming responses -enable_websocket = true - -[appearance] -# Interaction mode: interactive, autocomplete, validate_only -# - interactive: LLM suggests, user can override -# - autocomplete: LLM generates all values -# - validate_only: User provides, LLM validates -interaction_mode = "interactive" - -# Show LLM suggestions in user-facing prompts -show_suggestions = true - -# Confidence threshold for suggestions (0.0-1.0) -suggestion_confidence_threshold = 0.5 diff --git a/config/ai/dev.ncl b/config/ai/dev.ncl new file mode 100644 index 0000000..9f7818e --- /dev/null +++ b/config/ai/dev.ncl @@ -0,0 +1,18 @@ +(import "./config.ncl") +& { + llm = { + provider = "ollama", + model = "llama2", + api_endpoint = "http://localhost:11434/api", + generation = { + temperature = 0.5, + max_tokens = 1024, + }, + }, + rag = { + index_path = "~/.config/typedialog/ai/rag-index-dev", + cache_size = 500, + }, + microservice.enable_cors = true, + appearance.suggestion_confidence_threshold = 0.3, +} diff --git a/config/ai/dev.toml b/config/ai/dev.toml deleted file mode 100644 index a6284d9..0000000 --- a/config/ai/dev.toml +++ /dev/null @@ -1,31 +0,0 @@ -# AI Backend - Development Configuration -# Inherits defaults from default.toml, override values here for local development - -[llm] -# Use ollama for local development (requires local Ollama instance) -api_endpoint = "http://localhost:11434/api" -model = "llama2" # Or whatever model you have installed locally -provider = "ollama" - - [llm.generation] - # Faster responses for iteration - max_tokens = 1024 - temperature = 0.5 - -[rag] -# Enable RAG for development -cache_size = 500 -embedding_dims = 384 -enabled = true -index_path = "~/.config/typedialog/ai/rag-index-dev" - -[microservice] -enable_cors = true # Allow localhost:3000, localhost:5173, etc. -enable_websocket = true -host = "127.0.0.1" -port = 3001 - -[appearance] -interaction_mode = "interactive" -show_suggestions = true -suggestion_confidence_threshold = 0.3 # Lower threshold for dev feedback diff --git a/config/ai/production.ncl b/config/ai/production.ncl new file mode 100644 index 0000000..cc6c5a6 --- /dev/null +++ b/config/ai/production.ncl @@ -0,0 +1,27 @@ +(import "./config.ncl") +& { + llm = { + provider = "anthropic", + model = "claude-3-sonnet-20240229", + api_endpoint = "", + generation = { + temperature = 0.3, + max_tokens = 1024, + top_p = 0.95, + }, + }, + rag = { + index_path = "/var/lib/typedialog/ai/rag-index", + embedding_dims = 768, + cache_size = 10000, + }, + microservice = { + host = "0.0.0.0", + enable_cors = false, + }, + appearance = { + interaction_mode = "validate_only", + show_suggestions = false, + suggestion_confidence_threshold = 0.8, + }, +} diff --git a/config/ai/production.toml b/config/ai/production.toml deleted file mode 100644 index b2cc6f8..0000000 --- a/config/ai/production.toml +++ /dev/null @@ -1,34 +0,0 @@ -# AI Backend - Production Configuration -# Optimized for reliability, cost, and performance at scale - -[llm] -# Production uses high-quality, stable models -api_endpoint = "" # Uses provider defaults (api.anthropic.com) -model = "claude-3-sonnet-20240229" -provider = "anthropic" - - [llm.generation] - # Conservative settings for production - max_tokens = 1024 # Reasonable limit for cost control - temperature = 0.3 # More focused, less random - top_p = 0.95 - -[rag] -# Production RAG system with larger cache -cache_size = 10000 # Larger cache for frequently accessed data -embedding_dims = 768 # Higher quality embeddings -enabled = true -index_path = "/var/lib/typedialog/ai/rag-index" # System-wide index path - -[microservice] -# Listen on all interfaces for container deployments -enable_cors = false # Restrict CORS for security -enable_websocket = true -host = "0.0.0.0" -port = 3001 - -[appearance] -# Production uses validation mode -interaction_mode = "validate_only" -show_suggestions = false # Don't show raw LLM output to users -suggestion_confidence_threshold = 0.8 # Only very confident suggestions diff --git a/config/cli/config.ncl b/config/cli/config.ncl new file mode 100644 index 0000000..e086101 --- /dev/null +++ b/config/cli/config.ncl @@ -0,0 +1,27 @@ +{ + form = { + title | default = "CLI Form", + description | default = "Standard command-line interface form", + validation = { + show_errors_inline | default = true, + validate_on_change | default = true, + strict_validation | default = false, + }, + }, + output = { + format | default = "json", + pretty_print | default = true, + debug_output | default = false, + }, + terminal = { + use_raw_mode | default = true, + enable_mouse | default = false, + use_color | default = true, + }, + appearance = { + theme | default = "default", + show_help | default = true, + show_placeholders | default = true, + show_field_types | default = false, + }, +} diff --git a/config/cli/default.toml b/config/cli/default.toml deleted file mode 100644 index 1d32f2f..0000000 --- a/config/cli/default.toml +++ /dev/null @@ -1,30 +0,0 @@ -# CLI Backend - Default Configuration -# Used for standard command-line form rendering - -[form] -description = "Standard command-line interface form" -title = "CLI Form" - - [form.validation] - show_errors_inline = true - validate_on_change = true - -[output] -format = "json" -pretty_print = true - -[terminal] -# Use raw mode for better terminal control -use_raw_mode = true -# Enable mouse support if available -enable_mouse = false -# Use color output -use_color = true - -[appearance] -# Theme: default, monochrome, dark -theme = "default" -# Show field help text -show_help = true -# Show field placeholders -show_placeholders = true diff --git a/config/cli/dev.ncl b/config/cli/dev.ncl new file mode 100644 index 0000000..4f208a3 --- /dev/null +++ b/config/cli/dev.ncl @@ -0,0 +1,18 @@ +(import "./config.ncl") +& { + form = { + title = "CLI Form (Dev)", + description = "Development CLI form with debugging enabled", + validation.strict_validation = true, + }, + output = { + debug_output = true, + }, + terminal.enable_mouse = true, + appearance.show_field_types = true, + debug = { + enabled = true, + log_level = "info", + trace_execution = false, + }, +} diff --git a/config/cli/dev.toml b/config/cli/dev.toml deleted file mode 100644 index 7b52edc..0000000 --- a/config/cli/dev.toml +++ /dev/null @@ -1,32 +0,0 @@ -# CLI Backend - Development Configuration -# Extended configuration for development and testing - -[form] -description = "Development CLI form with debugging enabled" -title = "CLI Form (Dev)" - - [form.validation] - show_errors_inline = true - strict_validation = true - validate_on_change = true - -[output] -debug_output = true -format = "json" -pretty_print = true - -[terminal] -enable_mouse = true -use_color = true -use_raw_mode = true - -[appearance] -show_field_types = true -show_help = true -show_placeholders = true -theme = "default" - -[debug] -enabled = true -log_level = "info" -trace_execution = false diff --git a/config/cli/production.ncl b/config/cli/production.ncl new file mode 100644 index 0000000..3bdbae0 --- /dev/null +++ b/config/cli/production.ncl @@ -0,0 +1,25 @@ +(import "./config.ncl") +& { + form = { + title = "Form", + description = "", + validation.strict_validation = true, + }, + output = { + pretty_print = false, + debug_output = false, + }, + terminal.enable_mouse = false, + appearance = { + show_help = false, + show_placeholders = false, + }, + logging = { + file = "/var/log/typedialog/cli.log", + level = "error", + }, + timeout = { + max_duration = 3600, + input_timeout = 300, + }, +} diff --git a/config/cli/production.toml b/config/cli/production.toml deleted file mode 100644 index 330ee7b..0000000 --- a/config/cli/production.toml +++ /dev/null @@ -1,37 +0,0 @@ -# CLI Backend - Production Configuration -# Optimized for production deployment - -[form] -description = "" -title = "Form" - - [form.validation] - show_errors_inline = true - strict_validation = true - validate_on_change = true - -[output] -format = "json" -pretty_print = false -# Suppress debugging info -debug_output = false - -[terminal] -enable_mouse = false -use_color = true -use_raw_mode = true - -[appearance] -show_help = false -show_placeholders = false -theme = "default" - -[logging] -file = "/var/log/typedialog/cli.log" -level = "error" - -[timeout] -# Maximum form completion time (seconds) -max_duration = 3600 -# Field input timeout -input_timeout = 300 diff --git a/config/prov-gen/config.ncl b/config/prov-gen/config.ncl new file mode 100644 index 0000000..b7da2e0 --- /dev/null +++ b/config/prov-gen/config.ncl @@ -0,0 +1,32 @@ +{ + provisioning = { + output_dir | default = "./provisioning", + default_providers | default = ["aws", "hetzner"], + generation = { + dry_run | default = false, + overwrite | default = false, + verbose | default = false, + }, + templates = { + base_path | default = "crates/typedialog-prov-gen/templates", + }, + infrastructure = { + environment | default = "development", + region | default = "us-east-1", + }, + nickel = { + generate_defaults | default = true, + use_constraints | default = true, + validate_schemas | default = true, + }, + ai = { + enabled | default = false, + provider | default = "claude", + model | default = "claude-3-5-sonnet-20241022", + }, + logging = { + level | default = "info", + file | default = false, + }, + }, +} diff --git a/config/prov-gen/default.toml b/config/prov-gen/default.toml deleted file mode 100644 index 9474496..0000000 --- a/config/prov-gen/default.toml +++ /dev/null @@ -1,42 +0,0 @@ -# TypeDialog Provisioning Generator - Default Configuration - -[provisioning] -# Default output directory -output_dir = "./provisioning" - -# Default providers to include -default_providers = ["aws", "hetzner"] - - # Generation settings - [provisioning.generation] - dry_run = false - overwrite = false - verbose = false - - # Template settings - [provisioning.templates] - # Use local path in development; installed binaries use ~/.config/typedialog/prov-gen/templates - base_path = "crates/typedialog-prov-gen/templates" - # custom_path = "path/to/custom/templates" # Uncomment to override - - # Infrastructure defaults - [provisioning.infrastructure] - environment = "development" - region = "us-east-1" - - # Nickel integration - [provisioning.nickel] - generate_defaults = true - use_constraints = true - validate_schemas = true - - # AI assistance - [provisioning.ai] - enabled = false - model = "claude-3-5-sonnet-20241022" - provider = "claude" - - # Logging - [provisioning.logging] - file = false - level = "info" diff --git a/config/prov-gen/dev.ncl b/config/prov-gen/dev.ncl new file mode 100644 index 0000000..90277e7 --- /dev/null +++ b/config/prov-gen/dev.ncl @@ -0,0 +1,27 @@ +(import "./config.ncl") +& { + provisioning = { + default_providers = ["hetzner", "lxd"], + generation = { + overwrite = true, + verbose = true, + }, + templates = { + base_path = "templates", + custom_path = "./custom-templates", + }, + infrastructure = { + environment = "development", + region = "eu-central-1", + }, + ai = { + enabled = true, + provider = "ollama", + model = "llama2", + }, + logging = { + level = "debug", + file = true, + }, + }, +} diff --git a/config/prov-gen/dev.toml b/config/prov-gen/dev.toml deleted file mode 100644 index 5ce3fcb..0000000 --- a/config/prov-gen/dev.toml +++ /dev/null @@ -1,32 +0,0 @@ -# TypeDialog Provisioning Generator - Development Configuration - -[provisioning] -default_providers = ["hetzner", "lxd"] # Cheaper for dev -output_dir = "./provisioning" - - [provisioning.generation] - dry_run = false - overwrite = true # Allow overwrite in dev - verbose = true # Verbose in dev - - [provisioning.templates] - base_path = "templates" - custom_path = "./custom-templates" - - [provisioning.infrastructure] - environment = "development" - region = "eu-central-1" - - [provisioning.nickel] - generate_defaults = true - use_constraints = true - validate_schemas = true - - [provisioning.ai] - enabled = true # Enable AI in dev - model = "llama2" - provider = "ollama" # Use local for dev - - [provisioning.logging] - file = true - level = "debug" diff --git a/config/prov-gen/production.ncl b/config/prov-gen/production.ncl new file mode 100644 index 0000000..4ba3957 --- /dev/null +++ b/config/prov-gen/production.ncl @@ -0,0 +1,33 @@ +(import "./config.ncl") +& { + provisioning = { + default_providers = ["aws", "gcp"], + generation = { + dry_run = false, + overwrite = false, + verbose = false, + }, + templates.base_path = "templates", + infrastructure = { + environment = "production", + region = "us-east-1", + }, + ai = { + enabled = true, + provider = "claude", + model = "claude-3-5-sonnet-20241022", + }, + logging = { + level = "warn", + file = true, + }, + validation = { + strict = true, + require_tests = true, + }, + security = { + require_encryption = true, + scan_templates = true, + }, + }, +} diff --git a/config/prov-gen/production.toml b/config/prov-gen/production.toml deleted file mode 100644 index 0d9673f..0000000 --- a/config/prov-gen/production.toml +++ /dev/null @@ -1,41 +0,0 @@ -# TypeDialog Provisioning Generator - Production Configuration - -[provisioning] -default_providers = ["aws", "gcp"] -output_dir = "./provisioning" - - [provisioning.generation] - dry_run = false - overwrite = false # Require explicit --force - verbose = false - - [provisioning.templates] - base_path = "templates" - # custom_path = "" # Optional: set custom templates path - - [provisioning.infrastructure] - environment = "production" - region = "us-east-1" - - [provisioning.nickel] - generate_defaults = true - use_constraints = true - validate_schemas = true - - [provisioning.ai] - enabled = true - model = "claude-3-5-sonnet-20241022" - provider = "claude" - - [provisioning.logging] - file = true - level = "warn" - - # Production-specific settings - [provisioning.validation] - require_tests = true - strict = true - - [provisioning.security] - require_encryption = true - scan_templates = true diff --git a/config/tui/config.ncl b/config/tui/config.ncl new file mode 100644 index 0000000..eb9612c --- /dev/null +++ b/config/tui/config.ncl @@ -0,0 +1,39 @@ +{ + form = { + title | default = "TUI Form", + description | default = "Interactive terminal user interface form", + validation = { + show_errors_inline | default = true, + validate_on_change | default = true, + strict_validation | default = false, + }, + }, + output = { + format | default = "json", + pretty_print | default = true, + debug_output | default = false, + }, + terminal = { + use_raw_mode | default = true, + enable_mouse | default = true, + enable_scrolling | default = true, + height | default = -1, + width | default = -1, + }, + ui = { + show_borders | default = true, + show_focus | default = true, + highlight_on_hover | default = true, + enable_animations | default = true, + show_field_indices | default = false, + }, + appearance = { + theme | default = "default", + border_style | default = "rounded", + color_scheme | default = "default", + }, + keyboard = { + vi_mode | default = false, + emacs_mode | default = false, + }, +} diff --git a/config/tui/default.toml b/config/tui/default.toml deleted file mode 100644 index 431df1c..0000000 --- a/config/tui/default.toml +++ /dev/null @@ -1,46 +0,0 @@ -# TUI Backend - Default Configuration -# Terminal User Interface rendering - -[form] -description = "Interactive terminal user interface form" -title = "TUI Form" - - [form.validation] - show_errors_inline = true - validate_on_change = true - -[output] -format = "json" -pretty_print = true - -[terminal] -# Full TUI features -enable_mouse = true -enable_scrolling = true -use_raw_mode = true -# Fixed height (-1 = auto) -height = -1 -# Fixed width (-1 = auto) -width = -1 - -[ui] -# Show field borders -show_borders = true -# Show field focus indicator -show_focus = true -# Highlight on hover -highlight_on_hover = true -# Animation enabled -enable_animations = true - -[appearance] -border_style = "rounded" -theme = "default" -# Color scheme: default, dark, light, high_contrast -color_scheme = "default" - -[keyboard] -# Vi-style navigation (hjkl) -vi_mode = false -# Emacs-style navigation -emacs_mode = false diff --git a/config/tui/dev.ncl b/config/tui/dev.ncl new file mode 100644 index 0000000..6da478d --- /dev/null +++ b/config/tui/dev.ncl @@ -0,0 +1,16 @@ +(import "./config.ncl") +& { + form = { + title = "TUI Form (Dev)", + description = "Development TUI form with all features enabled", + validation.strict_validation = true, + }, + output.debug_output = true, + ui.show_field_indices = true, + appearance.border_style = "double", + debug = { + enabled = true, + log_level = "debug", + trace_rendering = false, + }, +} diff --git a/config/tui/dev.toml b/config/tui/dev.toml deleted file mode 100644 index a32b59b..0000000 --- a/config/tui/dev.toml +++ /dev/null @@ -1,45 +0,0 @@ -# TUI Backend - Development Configuration -# Extended TUI features for development - -[form] -description = "Development TUI form with all features enabled" -title = "TUI Form (Dev)" - - [form.validation] - show_errors_inline = true - strict_validation = true - validate_on_change = true - -[output] -debug_output = true -format = "json" -pretty_print = true - -[terminal] -enable_mouse = true -enable_scrolling = true -height = -1 -use_raw_mode = true -width = -1 - -[ui] -enable_animations = true -highlight_on_hover = true -show_borders = true -show_focus = true -# Show field indices for debugging -show_field_indices = true - -[appearance] -border_style = "double" -color_scheme = "default" -theme = "default" - -[keyboard] -emacs_mode = false -vi_mode = false - -[debug] -enabled = true -log_level = "debug" -trace_rendering = false diff --git a/config/tui/production.ncl b/config/tui/production.ncl new file mode 100644 index 0000000..40eb5e9 --- /dev/null +++ b/config/tui/production.ncl @@ -0,0 +1,21 @@ +(import "./config.ncl") +& { + form = { + title = "", + description = "", + validation.strict_validation = true, + }, + output = { + pretty_print = false, + debug_output = false, + }, + ui.enable_animations = false, + logging = { + file = "/var/log/typedialog/tui.log", + level = "error", + }, + performance = { + render_throttle = 16, + max_fps = 60, + }, +} diff --git a/config/tui/production.toml b/config/tui/production.toml deleted file mode 100644 index 8a427b3..0000000 --- a/config/tui/production.toml +++ /dev/null @@ -1,48 +0,0 @@ -# TUI Backend - Production Configuration -# Optimized TUI for production deployment - -[form] -description = "" -title = "" - - [form.validation] - show_errors_inline = true - strict_validation = true - validate_on_change = true - -[output] -debug_output = false -format = "json" -pretty_print = false - -[terminal] -enable_mouse = true -enable_scrolling = true -height = -1 -use_raw_mode = true -width = -1 - -[ui] -enable_animations = false -highlight_on_hover = true -show_borders = true -show_focus = true - -[appearance] -border_style = "rounded" -color_scheme = "default" -theme = "default" - -[keyboard] -emacs_mode = false -vi_mode = false - -[logging] -file = "/var/log/typedialog/tui.log" -level = "error" - -[performance] -# Render throttle (milliseconds) -render_throttle = 16 -# Max update frequency (Hz) -max_fps = 60 diff --git a/config/web/config.ncl b/config/web/config.ncl new file mode 100644 index 0000000..9a78e45 --- /dev/null +++ b/config/web/config.ncl @@ -0,0 +1,39 @@ +{ + server = { + host | default = "localhost", + port | default = 3000, + cors_enabled | default = true, + cors_origins | default = ["localhost", "127.0.0.1"], + debug | default = false, + hot_reload | default = false, + }, + form = { + title | default = "Web Form", + description | default = "Interactive web form", + validation = { + client_validation | default = true, + show_errors_inline | default = true, + validate_on_change | default = true, + }, + }, + output = { + format | default = "json", + }, + html = { + css_framework | default = "none", + inline_styles | default = false, + responsive | default = true, + dark_mode | default = true, + }, + submission = { + method | default = "post", + webhook_url | default = "", + redirect_on_success | default = false, + redirect_url | default = "", + }, + security = { + csrf_enabled | default = true, + rate_limit | default = 0, + require_https | default = false, + }, +} diff --git a/config/web/default.toml b/config/web/default.toml deleted file mode 100644 index 809624d..0000000 --- a/config/web/default.toml +++ /dev/null @@ -1,48 +0,0 @@ -# Web Backend - Default Configuration -# HTTP server and web form rendering - -[server] -host = "localhost" -port = 3000 -# CORS settings -cors_enabled = true -cors_origins = ["localhost", "127.0.0.1"] - -[form] -description = "Interactive web form" -title = "Web Form" - - [form.validation] - client_validation = true - show_errors_inline = true - validate_on_change = true - -[output] -format = "json" - -[html] -# CSS framework: bootstrap, tailwind, none -css_framework = "none" -# Include inline styles -inline_styles = false -# Mobile responsive -responsive = true -# Dark mode support -dark_mode = true - -[submission] -# Submission method: post, put, patch -method = "post" -# Optional webhook URL for submissions -webhook_url = "" -# Redirect after submission -redirect_on_success = false -redirect_url = "" - -[security] -# CSRF protection -csrf_enabled = true -# Rate limiting (requests per minute) -rate_limit = 0 -# Require HTTPS -require_https = false diff --git a/config/web/dev.ncl b/config/web/dev.ncl new file mode 100644 index 0000000..38fa0bc --- /dev/null +++ b/config/web/dev.ncl @@ -0,0 +1,28 @@ +(import "./config.ncl") +& { + server = { + host = "0.0.0.0", + debug = true, + hot_reload = true, + }, + form = { + title = "Web Form (Dev)", + description = "Development web form", + }, + html = { + inline_styles = true, + show_field_metadata = true, + }, + submission = { + webhook_url = "http://localhost:8000/webhook", + log_submissions = true, + }, + logging = { + file = "/tmp/typedialog-web.log", + level = "debug", + }, + api = { + enable_docs = true, + docs_path = "/docs", + }, +} diff --git a/config/web/dev.toml b/config/web/dev.toml deleted file mode 100644 index 4971589..0000000 --- a/config/web/dev.toml +++ /dev/null @@ -1,50 +0,0 @@ -# Web Backend - Development Configuration -# Enhanced features for development - -[server] -host = "0.0.0.0" -port = 3000 -# Enable hot reload -hot_reload = true -# Debug mode -debug = true - -[form] -description = "Development web form" -title = "Web Form (Dev)" - - [form.validation] - client_validation = true - show_errors_inline = true - validate_on_change = true - -[output] -format = "json" - -[html] -css_framework = "none" -dark_mode = true -inline_styles = true -responsive = true -# Show field metadata -show_field_metadata = true - -[submission] -log_submissions = true -method = "post" -redirect_on_success = false -webhook_url = "http://localhost:8000/webhook" - -[security] -csrf_enabled = true -rate_limit = 0 -require_https = false - -[logging] -file = "/tmp/typedialog-web.log" -level = "debug" - -[api] -# API documentation enabled -docs_path = "/docs" -enable_docs = true diff --git a/config/web/production.ncl b/config/web/production.ncl new file mode 100644 index 0000000..6ed191d --- /dev/null +++ b/config/web/production.ncl @@ -0,0 +1,42 @@ +(import "./config.ncl") +& { + server = { + host = "0.0.0.0", + port = 8080, + debug = false, + hot_reload = false, + workers = 4, + }, + form = { + title = "", + description = "", + }, + html.inline_styles = false, + submission = { + method = "post", + redirect_on_success = true, + redirect_url = "https://example.com/thank-you", + webhook_url = "${TYPEDIALOG_WEBHOOK_URL}", + }, + security = { + csrf_enabled = true, + rate_limit = 100, + require_https = true, + add_security_headers = true, + }, + logging = { + file = "/var/log/typedialog/web.log", + level = "error", + }, + performance = { + cache_static = true, + cache_ttl = 3600, + enable_compression = true, + compression_threshold = 1024, + }, + tls = { + enabled = false, + cert_path = "/etc/typedialog/cert.pem", + key_path = "/etc/typedialog/key.pem", + }, +} diff --git a/config/web/production.toml b/config/web/production.toml deleted file mode 100644 index 71df1f9..0000000 --- a/config/web/production.toml +++ /dev/null @@ -1,65 +0,0 @@ -# Web Backend - Production Configuration -# Hardened configuration for production deployment - -[server] -host = "0.0.0.0" -port = 8080 -# Disable development features -debug = false -hot_reload = false -# Worker threads -workers = 4 - -[form] -description = "" -title = "" - - [form.validation] - client_validation = true - show_errors_inline = true - validate_on_change = true - -[output] -format = "json" - -[html] -css_framework = "none" -dark_mode = true -inline_styles = false -responsive = true - -[submission] -method = "post" -# Required: webhook for production submissions -redirect_on_success = true -redirect_url = "https://example.com/thank-you" -webhook_url = "https://api.example.com/forms" - -[security] -# Strict CSRF protection -csrf_enabled = true -# Rate limiting: requests per minute per IP -rate_limit = 100 -# Require HTTPS -require_https = true -# Security headers -add_security_headers = true - -[logging] -file = "/var/log/typedialog/web.log" -level = "error" - -[performance] -# Cache static assets -cache_static = true -cache_ttl = 3600 -# Enable gzip compression -enable_compression = true -# Minimum response size for compression (bytes) -compression_threshold = 1024 - -[tls] -# Optional TLS configuration -cert_path = "/etc/typedialog/cert.pem" -enabled = false -key_path = "/etc/typedialog/key.pem" diff --git a/crates/typedialog-core/src/config/cli_loader.rs b/crates/typedialog-core/src/config/cli_loader.rs index b3de4bf..228a486 100644 --- a/crates/typedialog-core/src/config/cli_loader.rs +++ b/crates/typedialog-core/src/config/cli_loader.rs @@ -2,6 +2,7 @@ //! //! Provides a unified pattern for loading backend configuration files //! with support for both explicit `-c FILE` and environment-based search. +//! Supports both `.ncl` (via `nickel export`) and `.toml` formats. use crate::error::{Error, Result}; use std::path::Path; @@ -9,10 +10,12 @@ use std::path::Path; /// Load backend-specific configuration file /// /// If `cli_config_path` is provided, uses that file exclusively. -/// Otherwise, searches in order: -/// 1. `~/.config/typedialog/{backend_name}/{TYPEDIALOG_ENV}.toml` -/// 2. `~/.config/typedialog/{backend_name}/config.toml` -/// 3. Returns default value +/// Otherwise, searches in order (NCL preferred over TOML): +/// 1. `~/.config/typedialog/{backend_name}/{TYPEDIALOG_ENV}.ncl` +/// 2. `~/.config/typedialog/{backend_name}/{TYPEDIALOG_ENV}.toml` +/// 3. `~/.config/typedialog/{backend_name}/config.ncl` +/// 4. `~/.config/typedialog/{backend_name}/config.toml` +/// 5. Returns default value /// /// # Arguments /// * `backend_name` - Name of backend (cli, tui, web, ai) @@ -37,7 +40,7 @@ use std::path::Path; /// let config = load_backend_config::("cli", None, CliConfig::default())?; /// /// // With explicit path -/// let path = PathBuf::from("custom.toml"); +/// let path = PathBuf::from("custom.ncl"); /// let config = load_backend_config::("cli", Some(path.as_path()), CliConfig::default())?; /// # Ok(()) /// # } @@ -50,12 +53,10 @@ pub fn load_backend_config( where T: serde::de::DeserializeOwned + Default, { - // If CLI path provided, use it exclusively if let Some(path) = cli_config_path { return load_from_file(path); } - // Otherwise try search order let config_dir = dirs::config_dir() .unwrap_or_else(|| { std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string())) @@ -63,29 +64,73 @@ where .join("typedialog") .join(backend_name); - // Try environment-specific config first let env = std::env::var("TYPEDIALOG_ENV").unwrap_or_else(|_| "default".to_string()); - let env_config_path = config_dir.join(format!("{}.toml", env)); - if env_config_path.exists() { - if let Ok(config) = load_from_file::(&env_config_path) { - return Ok(config); + + // NCL env-specific → TOML env-specific → NCL generic → TOML generic + let candidates = [ + config_dir.join(format!("{env}.ncl")), + config_dir.join(format!("{env}.toml")), + config_dir.join("config.ncl"), + config_dir.join("config.toml"), + ]; + + for path in &candidates { + if path.exists() { + if let Ok(config) = load_from_file::(path) { + return Ok(config); + } } } - // Try generic config.toml - let generic_config_path = config_dir.join("config.toml"); - if generic_config_path.exists() { - if let Ok(config) = load_from_file::(&generic_config_path) { - return Ok(config); - } - } - - // Return default Ok(default) } -/// Load configuration from TOML file +/// Dispatch to NCL or TOML loader by extension. fn load_from_file(path: &Path) -> Result +where + T: serde::de::DeserializeOwned, +{ + match path.extension().and_then(|e| e.to_str()) { + Some("ncl") => load_from_ncl(path), + _ => load_from_toml(path), + } +} + +/// Export NCL via `nickel export --format json` and deserialise. +fn load_from_ncl(path: &Path) -> Result +where + T: serde::de::DeserializeOwned, +{ + let output = std::process::Command::new("nickel") + .args(["export", "--format", "json", &path.to_string_lossy()]) + .output() + .map_err(|e| { + Error::validation_failed(format!( + "Failed to run `nickel export` for '{}': {}", + path.display(), + e + )) + })?; + + if !output.status.success() { + return Err(Error::validation_failed(format!( + "nickel export failed for '{}': {}", + path.display(), + String::from_utf8_lossy(&output.stderr) + ))); + } + + serde_json::from_slice(&output.stdout).map_err(|e| { + Error::validation_failed(format!( + "Failed to deserialize NCL config '{}': {}", + path.display(), + e + )) + }) +} + +/// Parse TOML file and deserialise. +fn load_from_toml(path: &Path) -> Result where T: serde::de::DeserializeOwned, { @@ -123,30 +168,24 @@ mod tests { value: 42, }; let config = load_backend_config::("test", None, default.clone()).unwrap(); - // Should return default when no config found assert_eq!(config, default); } #[test] - fn test_load_with_explicit_path() { - // Create test config file + fn test_load_with_explicit_toml_path() { let temp_dir = std::env::temp_dir(); let test_config_path = temp_dir.join("test-backend-config.toml"); - let test_content = r#" -name = "test" -value = 100 -"#; - std::fs::write(&test_config_path, test_content).ok(); + std::fs::write(&test_config_path, "name = \"test\"\nvalue = 100\n").ok(); - let default = TestConfig::default(); - let config = - load_backend_config::("test", Some(test_config_path.as_path()), default) - .unwrap(); + let config = load_backend_config::( + "test", + Some(test_config_path.as_path()), + TestConfig::default(), + ) + .unwrap(); assert_eq!(config.name, "test"); assert_eq!(config.value, 100); - - // Cleanup std::fs::remove_file(test_config_path).ok(); } } diff --git a/crates/typedialog-core/src/encryption_bridge.rs b/crates/typedialog-core/src/encryption_bridge.rs index 1de0657..f4f8757 100644 --- a/crates/typedialog-core/src/encryption_bridge.rs +++ b/crates/typedialog-core/src/encryption_bridge.rs @@ -204,6 +204,7 @@ mod tests { week_start: None, order: 0, when: None, + when_false: Default::default(), i18n: None, group: None, nickel_contract: None, diff --git a/crates/typedialog-core/src/form_parser/executor.rs b/crates/typedialog-core/src/form_parser/executor.rs index fe7939c..5b45064 100644 --- a/crates/typedialog-core/src/form_parser/executor.rs +++ b/crates/typedialog-core/src/form_parser/executor.rs @@ -3,14 +3,15 @@ //! Handles form execution with various backends and execution modes. use crate::error::Result; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::path::Path; use std::sync::Arc; use super::conditions::evaluate_condition; -use super::parser::load_elements_from_file; use super::translation::{translate_display_item, translate_field_definition}; -use super::types::{DisplayItem, DisplayMode, FieldDefinition, FormDefinition, FormElement}; +use super::types::{ + DisplayItem, DisplayMode, FieldDefinition, FormDefinition, FormElement, WhenFalse, +}; #[cfg(feature = "i18n")] use crate::i18n::I18nBundle; @@ -82,60 +83,31 @@ pub fn render_display_item(item: &DisplayItem, results: &HashMap Result> { let mut results = HashMap::new(); - // Print form header if let Some(desc) = &form.description { println!("\n{}\n{}\n", form.name, desc); } else { println!("\n{}\n", form.name); } - // Migrate legacy format to unified elements form.migrate_to_elements(); - // Expand groups with includes using the unified expand_includes function - let expanded_form = super::fragments::expand_includes(form, base_dir)?; - - // Build ordered element map - let mut element_map: BTreeMap = BTreeMap::new(); - let mut order_counter = 0; - - for element in expanded_form.elements { - let order = match &element { - FormElement::Item(item) => { - if item.order == 0 { - order_counter += 1; - order_counter - 1 - } else { - item.order - } - } - FormElement::Field(field) => { - if field.order == 0 { - order_counter += 1; - order_counter - 1 - } else { - field.order - } - } - }; - element_map.insert(order, element); - } - - // Process elements in order - for (_, element) in element_map.iter() { + for element in &form.elements { match element { FormElement::Item(item) => { render_display_item(item, &results); } FormElement::Field(field) => { - // Check if field should be shown based on conditional if let Some(condition) = &field.when { if !evaluate_condition(condition, &results) { - // Field condition not met, skip it + if field.when_false == WhenFalse::Default { + if let Some(default) = &field.default { + results.insert(field.name.clone(), serde_json::json!(default)); + } + } continue; } } @@ -161,102 +133,41 @@ pub fn execute(form: FormDefinition) -> Result, ) -> Result> { - use super::parser::{parse_toml, resolve_constraints_in_content}; - let path_ref = path.as_ref(); - let content = std::fs::read_to_string(path_ref)?; - - // Get the directory of the current file for relative path resolution let base_dir = path_ref.parent().unwrap_or_else(|| Path::new(".")); - - // Resolve constraint interpolations before parsing - let resolved_content = resolve_constraints_in_content(&content, base_dir)?; - let form = parse_toml(&resolved_content)?; - + let form = super::parser::load_from_file(path_ref)?; execute_with_base_dir(form, base_dir) } -/// Build element list from form definition with lazy loading of fragments -fn build_element_list( - form: &FormDefinition, - base_dir: &Path, - _results: &HashMap, -) -> Result> { - let mut element_list: Vec<(usize, FormElement)> = Vec::new(); - let mut order_counter = 0; +/// Build ordered element list from form definition. +/// +/// Elements are returned in declaration order (array index). No file I/O: group expansion +/// via Nickel imports or `expand_includes` must happen before form execution. +fn build_element_list(form: &FormDefinition) -> Vec<(usize, FormElement)> { + form.elements.iter().cloned().enumerate().collect() +} - // Process unified elements (expand groups and maintain insertion order) - for element in form.elements.iter() { - match element { - FormElement::Item(item) => { - let mut item_clone = item.as_ref().clone(); - - // Handle group type with includes - if item.item_type == "group" { - let group_condition = item.when.clone(); - if let Some(includes) = &item.includes { - for include_path in includes { - // Load elements from fragment (unified format) - // Note: We load ALL fragments regardless of condition - // Phase 3 filtering will hide/show based on conditions - match load_elements_from_file(include_path, base_dir) { - Ok(loaded_elements) => { - for mut loaded_element in loaded_elements { - // Apply group condition to loaded elements if they don't have one - if let Some(ref condition) = group_condition { - match &mut loaded_element { - FormElement::Item(ref mut loaded_item) => { - if loaded_item.when.is_none() { - loaded_item.when = Some(condition.clone()); - } - } - FormElement::Field(ref mut loaded_field) => { - if loaded_field.when.is_none() { - loaded_field.when = Some(condition.clone()); - } - } - } - } - // Assign order based on position counter (insertion order) - match &mut loaded_element { - FormElement::Item(ref mut loaded_item) => { - loaded_item.order = order_counter; - } - FormElement::Field(ref mut loaded_field) => { - loaded_field.order = order_counter; - } - } - order_counter += 1; - element_list.push((order_counter - 1, loaded_element)); - } - } - Err(_) => { - // Fragment failed to load, skip silently - } - } - } +/// Inject default values into results for fields whose `when` condition is false +/// and `when_false = default`. Called after complete-form execution where the backend +/// never sees the hidden fields. +fn apply_when_false_defaults( + element_list: &[(usize, FormElement)], + results: &mut HashMap, +) { + for (_, element) in element_list { + if let FormElement::Field(field) = element { + if let Some(condition) = &field.when { + if !evaluate_condition(condition, results) && field.when_false == WhenFalse::Default + { + if let Some(default) = &field.default { + results + .entry(field.name.clone()) + .or_insert_with(|| serde_json::json!(default)); } - } else { - // Non-group items get order from position counter (insertion order) - item_clone.order = order_counter; - order_counter += 1; - element_list.push((item_clone.order, FormElement::Item(Box::new(item_clone)))); } } - FormElement::Field(field) => { - let mut field_clone = field.as_ref().clone(); - // Assign order based on position counter (insertion order) - field_clone.order = order_counter; - order_counter += 1; - element_list.push((field_clone.order, FormElement::Field(Box::new(field_clone)))); - } } } - - // No need to sort - elements are already in insertion order from the counter - // element_list is already sorted by construction - - Ok(element_list) } /// Recompute visible elements based on current results @@ -277,15 +188,14 @@ fn build_element_list( /// based on current conditions and lazy loading rules pub fn recompute_visible_elements( form: &FormDefinition, - base_dir: &Path, + _base_dir: &Path, results: &HashMap, ) -> Result<(Vec, Vec)> { // Clone and migrate form to ensure elements are populated let mut form_clone = form.clone(); form_clone.migrate_to_elements(); - // Build complete element list with lazy loading - let element_list = build_element_list(&form_clone, base_dir, results)?; + let element_list = build_element_list(&form_clone); // Separate and filter items and fields based on conditions let mut visible_items = Vec::new(); @@ -375,10 +285,9 @@ pub async fn execute_with_backend_complete( } } - // PHASE 2: Build element list with lazy loading based on Phase 1 results - let element_list = build_element_list(&form, base_dir, &results)?; + let element_list = build_element_list(&form); - // PHASE 3: Execute remaining fields based on display mode + // Execute remaining fields based on display mode if form.display_mode == DisplayMode::Complete { // Complete mode: pass all fields to backend for complete form display let items: Vec<&DisplayItem> = element_list @@ -442,6 +351,12 @@ pub async fn execute_with_backend_complete( if let Some(condition) = &field.when { if !evaluate_condition(condition, &results) { + if field.when_false == WhenFalse::Default { + if let Some(default) = &field.default { + Arc::make_mut(&mut results) + .insert(field.name.clone(), serde_json::json!(default)); + } + } continue; } } @@ -479,7 +394,7 @@ pub async fn execute_with_backend_two_phase_with_defaults( mut form: FormDefinition, backend: &mut dyn crate::backends::FormBackend, i18n_bundle: Option<&I18nBundle>, - base_dir: &Path, + _base_dir: &Path, initial_values: Option>, ) -> Result> { use crate::backends::RenderContext; @@ -521,8 +436,7 @@ pub async fn execute_with_backend_two_phase_with_defaults( } } - // PHASE 2: Build element list with lazy loading based on Phase 1 results - let mut element_list = build_element_list(&form, base_dir, &results)?; + let mut element_list = build_element_list(&form); // Apply initial_values to field.default for all expanded elements // This ensures defaults from nickel-roundtrip input files are shown in the UI @@ -563,6 +477,12 @@ pub async fn execute_with_backend_two_phase_with_defaults( if let Some(condition) = &field.when { if !evaluate_condition(condition, &results) { + if field.when_false == WhenFalse::Default { + if let Some(default) = &field.default { + Arc::make_mut(&mut results) + .insert(field.name.clone(), serde_json::json!(default)); + } + } continue; } } @@ -637,8 +557,7 @@ pub async fn execute_with_backend_i18n_with_defaults( // Initialize backend backend.initialize().await?; - // Build element list directly (no two-phase) - let element_list = build_element_list(&form, base_dir, &results)?; + let element_list = build_element_list(&form); // Check display mode and execute accordingly if form.display_mode == DisplayMode::Complete { @@ -686,9 +605,10 @@ pub async fn execute_with_backend_i18n_with_defaults( .map(|f| translate_field_definition(f, i18n_bundle)) .collect(); - let complete_results = backend + let mut complete_results = backend .execute_form_complete(&form, base_dir, items_owned, fields_owned, initial_backup) .await?; + apply_when_false_defaults(&element_list, &mut complete_results); results = Arc::new(complete_results); } else { // Field-by-field mode @@ -707,6 +627,12 @@ pub async fn execute_with_backend_i18n_with_defaults( FormElement::Field(field) => { if let Some(condition) = &field.when { if !evaluate_condition(condition, &results) { + if field.when_false == WhenFalse::Default { + if let Some(default) = &field.default { + Arc::make_mut(&mut results) + .insert(field.name.clone(), serde_json::json!(default)); + } + } continue; } } diff --git a/crates/typedialog-core/src/form_parser/mod.rs b/crates/typedialog-core/src/form_parser/mod.rs index bcde2e9..0127681 100644 --- a/crates/typedialog-core/src/form_parser/mod.rs +++ b/crates/typedialog-core/src/form_parser/mod.rs @@ -19,11 +19,12 @@ mod types; // Re-export public types pub use types::{ - DisplayItem, DisplayMode, FieldDefinition, FieldType, FormDefinition, FormElement, SelectOption, + DisplayItem, DisplayMode, FieldDefinition, FieldType, FormDefinition, FormElement, + SelectOption, WhenFalse, }; // Re-export public functions - parser -pub use parser::{load_from_file, parse_toml}; +pub use parser::{load_form, load_from_file, load_from_ncl, parse_toml}; // Re-export public functions - executor (async backend paths, always available) pub use executor::{ diff --git a/crates/typedialog-core/src/form_parser/parser.rs b/crates/typedialog-core/src/form_parser/parser.rs index fb8f118..a5bdd45 100644 --- a/crates/typedialog-core/src/form_parser/parser.rs +++ b/crates/typedialog-core/src/form_parser/parser.rs @@ -142,6 +142,34 @@ pub fn parse_toml(content: &str) -> Result { toml::from_str(content).map_err(|e| e.into()) } +/// Load form from a Nickel (.ncl) file via `nickel export --format json` +/// +/// Invokes the `nickel` CLI as a subprocess. If the file contains contract violations +/// or syntax errors, `nickel export` fails and this function returns an error, aborting +/// the caller before any form execution begins. +pub fn load_from_ncl(path: impl AsRef) -> Result { + use crate::nickel::NickelCli; + let json = NickelCli::export(path.as_ref())?; + serde_json::from_value(json).map_err(|e| { + crate::error::ErrorWrapper::new(format!( + "Failed to deserialize Nickel export as FormDefinition: {}", + e + )) + }) +} + +/// Load a form from either a `.ncl` or `.toml` file, detected by extension +/// +/// `.ncl` files are loaded via `nickel export` (schema-validated at load time). +/// All other extensions fall back to TOML parsing. +pub fn load_form(path: impl AsRef) -> Result { + let path = path.as_ref(); + match path.extension().and_then(|e| e.to_str()) { + Some("ncl") => load_from_ncl(path), + _ => load_from_file(path), + } +} + /// Load form from TOML file (returns FormDefinition, doesn't execute) pub fn load_from_file(path: impl AsRef) -> Result { let path_ref = path.as_ref(); diff --git a/crates/typedialog-core/src/form_parser/types.rs b/crates/typedialog-core/src/form_parser/types.rs index ff6eb7e..872f5f9 100644 --- a/crates/typedialog-core/src/form_parser/types.rs +++ b/crates/typedialog-core/src/form_parser/types.rs @@ -476,6 +476,11 @@ pub struct FieldDefinition { pub order: usize, /// Optional conditional display (e.g., "role == admin", "country != US") pub when: Option, + /// Output behavior when `when` condition is false + /// - `exclude` (default): field absent from results + /// - `default`: field injected with its default value into results + #[serde(default)] + pub when_false: WhenFalse, /// Optional flag indicating if prompt/placeholder/options are i18n keys pub i18n: Option, /// Optional semantic grouping for form organization @@ -565,6 +570,17 @@ pub enum FieldType { RepeatingGroup, } +/// Output behavior for a field when its `when` condition evaluates to false +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum WhenFalse { + /// Field is excluded from results entirely (default) + #[default] + Exclude, + /// Field's `default` value is injected into results without prompting + Default, +} + /// Form display mode - how fields are presented to user #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] diff --git a/crates/typedialog-core/src/helpers.rs b/crates/typedialog-core/src/helpers.rs index 54cc012..1d3398b 100644 --- a/crates/typedialog-core/src/helpers.rs +++ b/crates/typedialog-core/src/helpers.rs @@ -444,6 +444,7 @@ mod tests { week_start: None, order: 0, when: None, + when_false: Default::default(), i18n: None, group: None, nickel_contract: None, @@ -499,6 +500,7 @@ mod tests { week_start: None, order: 0, when: None, + when_false: Default::default(), i18n: None, group: None, nickel_contract: None, @@ -554,6 +556,7 @@ mod tests { week_start: None, order: 0, when: None, + when_false: Default::default(), i18n: None, group: None, nickel_contract: None, @@ -610,6 +613,7 @@ mod tests { week_start: None, order: 0, when: None, + when_false: Default::default(), i18n: None, group: None, nickel_contract: None, @@ -649,6 +653,7 @@ mod tests { week_start: None, order: 1, when: None, + when_false: Default::default(), i18n: None, group: None, nickel_contract: None, @@ -700,6 +705,7 @@ mod tests { week_start: None, order: 0, when: None, + when_false: Default::default(), i18n: None, group: None, nickel_contract: None, diff --git a/crates/typedialog-core/src/nickel/contracts.rs b/crates/typedialog-core/src/nickel/contracts.rs index 393c3d1..0b2ff84 100644 --- a/crates/typedialog-core/src/nickel/contracts.rs +++ b/crates/typedialog-core/src/nickel/contracts.rs @@ -1,327 +1,8 @@ -//! Contract Validator and Analyzer +//! Contract Analyzer //! -//! Validates Nickel contracts and predicates against JSON values. -//! Analyzes contracts to infer conditional expressions for form generation. -//! -//! Supported validation predicates: -//! - `std.string.NonEmpty` - Non-empty string -//! - `std.string.length.min N` - Minimum string length -//! - `std.string.length.max N` - Maximum string length -//! - `std.string.Email` - Valid email address format -//! - `std.string.Url` - Valid URL format (http/https/ftp/ftps) -//! - `std.number.between A B` - Number in range [A, B] -//! - `std.number.greater_than N` - Number > N -//! - `std.number.less_than N` - Number < N +//! Analyzes Nickel contracts to infer conditional expressions for form generation. use super::schema_ir::{NickelFieldIR, NickelSchemaIR, NickelType}; -use crate::error::ErrorWrapper; -use crate::Result; -use serde_json::Value; - -/// Validator for Nickel contracts and predicates -pub struct ContractValidator; - -impl ContractValidator { - /// Validate a value against a Nickel contract - /// - /// # Arguments - /// - /// * `value` - The JSON value to validate - /// * `contract` - The Nickel contract string (e.g., "String | std.string.NonEmpty") - /// - /// # Returns - /// - /// Ok if validation succeeds, Err with descriptive message if it fails - pub fn validate(value: &Value, contract: &str) -> Result<()> { - // Extract the predicate from the contract (after the pipe) - let predicate = contract - .rfind('|') - .map(|i| contract[i + 1..].trim()) - .unwrap_or(contract); - - // Match common predicates - if predicate.contains("std.string.NonEmpty") { - return Self::validate_non_empty_string(value); - } - - if predicate.contains("std.string.length.min") { - if let Some(n) = Self::extract_number(predicate, "std.string.length.min") { - return Self::validate_min_length(value, n); - } - } - - if predicate.contains("std.string.length.max") { - if let Some(n) = Self::extract_number(predicate, "std.string.length.max") { - return Self::validate_max_length(value, n); - } - } - - if predicate.contains("std.number.between") { - if let Some((a, b)) = Self::extract_range(predicate) { - return Self::validate_between(value, a, b); - } - } - - if predicate.contains("std.number.greater_than") { - if let Some(n) = Self::extract_number(predicate, "std.number.greater_than") { - return Self::validate_greater_than(value, n as f64); - } - } - - if predicate.contains("std.number.less_than") { - if let Some(n) = Self::extract_number(predicate, "std.number.less_than") { - return Self::validate_less_than(value, n as f64); - } - } - - if predicate.contains("std.string.Email") { - return Self::validate_email(value); - } - - if predicate.contains("std.string.Url") { - return Self::validate_url(value); - } - - // Unknown predicate - pass validation - Ok(()) - } - - /// Validate that string is non-empty - fn validate_non_empty_string(value: &Value) -> Result<()> { - match value { - Value::String(s) => { - if s.is_empty() { - Err(ErrorWrapper::new( - "String must not be empty (std.string.NonEmpty)".to_string(), - )) - } else { - Ok(()) - } - } - _ => Err(ErrorWrapper::new("Expected string value".to_string())), - } - } - - /// Validate minimum string length - fn validate_min_length(value: &Value, min: usize) -> Result<()> { - match value { - Value::String(s) => { - if s.len() < min { - Err(ErrorWrapper::new(format!( - "String must be at least {} characters (std.string.length.min {})", - min, min - ))) - } else { - Ok(()) - } - } - _ => Err(ErrorWrapper::new("Expected string value".to_string())), - } - } - - /// Validate maximum string length - fn validate_max_length(value: &Value, max: usize) -> Result<()> { - match value { - Value::String(s) => { - if s.len() > max { - Err(ErrorWrapper::new(format!( - "String must be at most {} characters (std.string.length.max {})", - max, max - ))) - } else { - Ok(()) - } - } - _ => Err(ErrorWrapper::new("Expected string value".to_string())), - } - } - - /// Validate number is in range [a, b] - fn validate_between(value: &Value, a: f64, b: f64) -> Result<()> { - match value { - Value::Number(n) => { - if let Some(num) = n.as_f64() { - if num >= a && num <= b { - Ok(()) - } else { - Err(ErrorWrapper::new(format!( - "Number must be between {} and {} (std.number.between {} {})", - a, b, a, b - ))) - } - } else { - Err(ErrorWrapper::new("Invalid number value".to_string())) - } - } - _ => Err(ErrorWrapper::new("Expected number value".to_string())), - } - } - - /// Validate number is greater than n - fn validate_greater_than(value: &Value, n: f64) -> Result<()> { - match value { - Value::Number(num) => { - if let Some(val) = num.as_f64() { - if val > n { - Ok(()) - } else { - Err(ErrorWrapper::new(format!( - "Number must be greater than {} (std.number.greater_than {})", - n, n - ))) - } - } else { - Err(ErrorWrapper::new("Invalid number value".to_string())) - } - } - _ => Err(ErrorWrapper::new("Expected number value".to_string())), - } - } - - /// Validate number is less than n - fn validate_less_than(value: &Value, n: f64) -> Result<()> { - match value { - Value::Number(num) => { - if let Some(val) = num.as_f64() { - if val < n { - Ok(()) - } else { - Err(ErrorWrapper::new(format!( - "Number must be less than {} (std.number.less_than {})", - n, n - ))) - } - } else { - Err(ErrorWrapper::new("Invalid number value".to_string())) - } - } - _ => Err(ErrorWrapper::new("Expected number value".to_string())), - } - } - - /// Extract a single number from predicate string - fn extract_number(predicate: &str, pattern: &str) -> Option { - let start = predicate.find(pattern)? + pattern.len(); - let rest = &predicate[start..]; - - // Extract digits after the pattern - rest.split_whitespace() - .next() - .and_then(|s| s.trim_matches(|c: char| !c.is_ascii_digit()).parse().ok()) - } - - /// Extract a range (a, b) from between predicate - fn extract_range(predicate: &str) -> Option<(f64, f64)> { - // Parse patterns like "std.number.between 0 100" or "std.number.between 0.5 99.9" - let start = predicate.find("std.number.between")? + "std.number.between".len(); - let rest = predicate[start..].trim(); - - let parts: Vec<&str> = rest.split_whitespace().collect(); - if parts.len() < 2 { - return None; - } - - let a = parts[0].parse::().ok()?; - let b = parts[1].parse::().ok()?; - - Some((a, b)) - } - - /// Validate email address format - /// - /// Uses a simple regex pattern to check basic email format. - /// Pattern: local@domain where local can contain alphanumeric, dots, hyphens, underscores - /// and domain must have at least one dot. - pub fn validate_email(value: &Value) -> Result<()> { - match value { - Value::String(s) => { - if s.is_empty() { - return Err(ErrorWrapper::new( - "Email address cannot be empty".to_string(), - )); - } - - let at_count = s.matches('@').count(); - if at_count != 1 { - return Err(ErrorWrapper::new( - "Email must contain exactly one @ symbol".to_string(), - )); - } - - let parts: Vec<&str> = s.split('@').collect(); - let local = parts[0]; - let domain = parts[1]; - - if local.is_empty() { - return Err(ErrorWrapper::new( - "Email local part cannot be empty".to_string(), - )); - } - - if domain.is_empty() { - return Err(ErrorWrapper::new( - "Email domain cannot be empty".to_string(), - )); - } - - if !domain.contains('.') { - return Err(ErrorWrapper::new( - "Email domain must contain at least one dot".to_string(), - )); - } - - if domain.starts_with('.') || domain.ends_with('.') { - return Err(ErrorWrapper::new( - "Email domain cannot start or end with a dot".to_string(), - )); - } - - Ok(()) - } - _ => Err(ErrorWrapper::new( - "Expected string value for email".to_string(), - )), - } - } - - /// Validate URL format - /// - /// Checks for basic URL structure: scheme://host with optional path. - /// Accepted schemes: http, https, ftp, ftps. - pub fn validate_url(value: &Value) -> Result<()> { - match value { - Value::String(s) => { - if s.is_empty() { - return Err(ErrorWrapper::new("URL cannot be empty".to_string())); - } - - let valid_schemes = ["http://", "https://", "ftp://", "ftps://"]; - let has_valid_scheme = valid_schemes.iter().any(|scheme| s.starts_with(scheme)); - - if !has_valid_scheme { - return Err(ErrorWrapper::new(format!( - "URL must start with one of: {}", - valid_schemes.join(", ") - ))); - } - - let scheme_end = s.find("://").unwrap() + 3; - let rest = &s[scheme_end..]; - - if rest.is_empty() { - return Err(ErrorWrapper::new( - "URL must contain a host after the scheme".to_string(), - )); - } - - Ok(()) - } - _ => Err(ErrorWrapper::new( - "Expected string value for URL".to_string(), - )), - } - } -} /// Analyzer for inferring conditional expressions from Nickel contracts pub struct ContractAnalyzer; @@ -332,26 +13,18 @@ impl ContractAnalyzer { /// Returns a `when` expression (e.g., "tls_enabled == true") if the field appears to be /// conditionally dependent on another field's value, typically a boolean enable flag. pub fn infer_condition(field: &NickelFieldIR, schema: &NickelSchemaIR) -> Option { - // Only optional fields might have conditionals if !field.optional { return None; } - - // Try to find a boolean field that enables this optional field Self::find_boolean_dependency(field, schema) } - /// Find a boolean field that enables this optional field based on naming convention fn find_boolean_dependency(field: &NickelFieldIR, schema: &NickelSchemaIR) -> Option { - // Extract prefix: "tls_cert_path" -> "tls" let field_prefix = Self::extract_prefix(&field.flat_name)?; - // Look for a boolean field like "tls_enabled" for candidate in &schema.fields { if candidate.nickel_type == NickelType::Bool { let candidate_prefix = Self::extract_prefix(&candidate.flat_name)?; - - // Match pattern: same prefix + "_enabled" suffix if candidate_prefix == field_prefix && candidate.flat_name.ends_with("_enabled") { return Some(format!("{} == true", candidate.flat_name)); } @@ -361,9 +34,6 @@ impl ContractAnalyzer { None } - /// Extract the common prefix from a field name - /// "tls_cert_path" -> "tls" - /// "database_url" -> "database" fn extract_prefix(flat_name: &str) -> Option { flat_name .split('_') @@ -372,81 +42,3 @@ impl ContractAnalyzer { .map(|s| s.to_string()) } } - -#[cfg(test)] -mod tests { - use super::*; - use serde_json::json; - - #[test] - fn test_validate_non_empty_string() { - let result = ContractValidator::validate(&json!("hello"), "String | std.string.NonEmpty"); - assert!(result.is_ok()); - - let result = ContractValidator::validate(&json!(""), "String | std.string.NonEmpty"); - assert!(result.is_err()); - } - - #[test] - fn test_validate_min_length() { - let result = - ContractValidator::validate(&json!("hello"), "String | std.string.length.min 3"); - assert!(result.is_ok()); - - let result = ContractValidator::validate(&json!("hi"), "String | std.string.length.min 3"); - assert!(result.is_err()); - } - - #[test] - fn test_validate_max_length() { - let result = ContractValidator::validate(&json!("hi"), "String | std.string.length.max 3"); - assert!(result.is_ok()); - - let result = - ContractValidator::validate(&json!("hello"), "String | std.string.length.max 3"); - assert!(result.is_err()); - } - - #[test] - fn test_validate_between() { - let result = ContractValidator::validate(&json!(50), "Number | std.number.between 0 100"); - assert!(result.is_ok()); - - let result = ContractValidator::validate(&json!(150), "Number | std.number.between 0 100"); - assert!(result.is_err()); - } - - #[test] - fn test_validate_greater_than() { - let result = ContractValidator::validate(&json!(50), "Number | std.number.greater_than 10"); - assert!(result.is_ok()); - - let result = ContractValidator::validate(&json!(5), "Number | std.number.greater_than 10"); - assert!(result.is_err()); - } - - #[test] - fn test_validate_less_than() { - let result = ContractValidator::validate(&json!(5), "Number | std.number.less_than 10"); - assert!(result.is_ok()); - - let result = ContractValidator::validate(&json!(50), "Number | std.number.less_than 10"); - assert!(result.is_err()); - } - - #[test] - fn test_extract_range() { - let range = ContractValidator::extract_range("std.number.between 0 100"); - assert_eq!(range, Some((0.0, 100.0))); - - let range = ContractValidator::extract_range("std.number.between 0.5 99.9"); - assert_eq!(range, Some((0.5, 99.9))); - } - - #[test] - fn test_unknown_predicate_passes() { - let result = - ContractValidator::validate(&json!("anything"), "String | some.unknown.predicate"); - assert!(result.is_ok()); - } -} diff --git a/crates/typedialog-core/src/nickel/mod.rs b/crates/typedialog-core/src/nickel/mod.rs index 498d054..c5dff02 100644 --- a/crates/typedialog-core/src/nickel/mod.rs +++ b/crates/typedialog-core/src/nickel/mod.rs @@ -48,7 +48,6 @@ pub mod types; pub use alias_generator::AliasGenerator; pub use cli::NickelCli; pub use contract_parser::{ContractParser, ParsedContracts}; -pub use contracts::ContractValidator; pub use defaults_extractor::DefaultsExtractor; pub use encryption_contract_parser::EncryptionContractParser; pub use field_mapper::FieldMapper; diff --git a/crates/typedialog-core/src/nickel/roundtrip.rs b/crates/typedialog-core/src/nickel/roundtrip.rs index f1cd48b..df51384 100644 --- a/crates/typedialog-core/src/nickel/roundtrip.rs +++ b/crates/typedialog-core/src/nickel/roundtrip.rs @@ -361,23 +361,13 @@ impl RoundtripConfig { backend: &mut dyn FormBackend, initial_values: HashMap, ) -> Result> { - // Read form definition - let form_content = fs::read_to_string(form_path).map_err(|e| { - crate::error::ErrorWrapper::new(format!("Failed to read form file: {}", e)) - })?; - - // Parse TOML form definition - let mut form = form_parser::parse_toml(&form_content)?; + // Read and parse form definition (.ncl or .toml by extension) + let mut form = form_parser::load_form(form_path)?; // Migrate to unified elements format if needed form.migrate_to_elements(); - // NOTE: We don't apply defaults here because execute_with_backend_two_phase_with_defaults - // will call build_element_list which reloads fragments from disk, losing any modifications. - // Instead, we pass initial_values to execute_with_backend_two_phase_with_defaults - // which will apply them after fragment expansion. - - // Extract base directory for resolving relative paths (includes, fragments) + // Extract base directory for resolving relative paths let base_dir = form_path.parent().unwrap_or_else(|| Path::new(".")); // execute_with_backend_i18n_with_defaults dispatches on display_mode, @@ -399,23 +389,17 @@ impl RoundtripConfig { ) -> Result> { use std::collections::BTreeMap; - // Read form definition - let form_content = fs::read_to_string(form_path).map_err(|e| { - crate::error::ErrorWrapper::new(format!("Failed to read form file: {}", e)) - })?; - - // Parse TOML form definition - let mut form = form_parser::parse_toml(&form_content)?; + // Read and parse form definition (.ncl or .toml by extension) + let mut form = form_parser::load_form(form_path)?; // Migrate to unified elements format form.migrate_to_elements(); - // Extract base directory for resolving relative paths (includes, fragments) - let base_dir = form_path.parent().unwrap_or_else(|| Path::new(".")); + // Extract base directory for resolving relative paths + let _base_dir = form_path.parent().unwrap_or_else(|| Path::new(".")); - // CRITICAL FIX: Expand fragments BEFORE applying defaults - // This ensures defaults are applied to real fields, not to groups with includes - let mut expanded_form = form_parser::expand_includes(form, base_dir)?; + // let mut expanded_form = form_parser::expand_includes(form, _base_dir)?; // rescued: fragment lazy-loading pre-ADR-001 + let mut expanded_form = form; // Now apply initial values as defaults to the EXPANDED fields for element in &mut expanded_form.elements { @@ -512,20 +496,15 @@ impl RoundtripConfig { } }; - // Load form to get field definitions with nickel_path - let form_content = fs::read_to_string(form_path).map_err(|e| { - crate::error::ErrorWrapper::new(format!("Failed to read form file: {}", e)) - })?; - - let mut form = form_parser::parse_toml(&form_content)?; + // Load form to get field definitions with nickel_path (.ncl or .toml by extension) + let mut form = form_parser::load_form(form_path)?; form.migrate_to_elements(); - // Extract base directory for resolving fragment includes - let base_dir = form_path.parent().unwrap_or_else(|| Path::new(".")); + // Extract base directory for resolving paths + let _base_dir = form_path.parent().unwrap_or_else(|| Path::new(".")); - // Expand fragments to get ALL fields (including those in conditional groups) - // Uses expand_includes to process group elements with includes - let expanded_form = form_parser::expand_includes(form, base_dir)?; + // let expanded_form = form_parser::expand_includes(form, _base_dir)?; // rescued: fragment lazy-loading pre-ADR-001 + let expanded_form = form; // Extract field definitions that have nickel_path let fields_with_paths: Vec<_> = expanded_form diff --git a/crates/typedialog-core/src/nickel/toml_generator.rs b/crates/typedialog-core/src/nickel/toml_generator.rs index ae06312..c4987ad 100644 --- a/crates/typedialog-core/src/nickel/toml_generator.rs +++ b/crates/typedialog-core/src/nickel/toml_generator.rs @@ -332,6 +332,7 @@ impl TomlGenerator { week_start: None, order, when: when_condition, + when_false: Default::default(), i18n: None, group: field.group.clone(), nickel_contract: field.contract.clone(), @@ -491,6 +492,7 @@ impl TomlGenerator { week_start: None, order, when: None, + when_false: Default::default(), i18n: None, group: field.group.clone(), nickel_contract: field.contract.clone(), diff --git a/crates/typedialog-core/tests/encryption_integration.rs b/crates/typedialog-core/tests/encryption_integration.rs index 1c048a1..9bb3b51 100644 --- a/crates/typedialog-core/tests/encryption_integration.rs +++ b/crates/typedialog-core/tests/encryption_integration.rs @@ -55,6 +55,7 @@ mod encryption_tests { sensitive: Some(sensitive), encryption_backend: None, encryption_config: None, + when_false: Default::default(), } } @@ -160,6 +161,7 @@ mod encryption_tests { sensitive: None, // Not explicitly set encryption_backend: None, encryption_config: None, + when_false: Default::default(), }; assert!( @@ -218,6 +220,7 @@ mod encryption_tests { sensitive: Some(false), // Explicitly NOT sensitive encryption_backend: None, encryption_config: None, + when_false: Default::default(), }; assert!( @@ -275,6 +278,7 @@ mod encryption_tests { sensitive: Some(true), encryption_backend: None, encryption_config: None, + when_false: Default::default(), }; let mut backend_config = HashMap::new(); @@ -370,6 +374,7 @@ mod age_roundtrip_tests { sensitive: Some(true), encryption_backend: Some("age".to_string()), encryption_config: None, + when_false: Default::default(), } } diff --git a/crates/typedialog-core/tests/nickel_integration.rs b/crates/typedialog-core/tests/nickel_integration.rs index 97bebb2..13623f5 100644 --- a/crates/typedialog-core/tests/nickel_integration.rs +++ b/crates/typedialog-core/tests/nickel_integration.rs @@ -10,8 +10,8 @@ use serde_json::json; use std::collections::HashMap; use typedialog_core::form_parser; use typedialog_core::nickel::{ - ContractValidator, MetadataParser, NickelFieldIR, NickelSchemaIR, NickelSerializer, NickelType, - TemplateEngine, TomlGenerator, + MetadataParser, NickelFieldIR, NickelSchemaIR, NickelSerializer, NickelType, TemplateEngine, + TomlGenerator, }; #[test] @@ -211,48 +211,6 @@ fn test_array_field_serialization() { assert!(nickel_output.contains("]")); } -#[test] -fn test_contract_validation_non_empty_string() { - // Valid non-empty string - let result = ContractValidator::validate(&json!("hello"), "String | std.string.NonEmpty"); - assert!(result.is_ok()); - - // Empty string should fail - let result = ContractValidator::validate(&json!(""), "String | std.string.NonEmpty"); - assert!(result.is_err()); -} - -#[test] -fn test_contract_validation_number_range() { - // Valid number in range - let result = ContractValidator::validate(&json!(50), "Number | std.number.between 0 100"); - assert!(result.is_ok()); - - // Number out of range - let result = ContractValidator::validate(&json!(150), "Number | std.number.between 0 100"); - assert!(result.is_err()); -} - -#[test] -fn test_contract_validation_string_length() { - // Valid length - let result = ContractValidator::validate(&json!("hello"), "String | std.string.length.min 3"); - assert!(result.is_ok()); - - // Too short - let result = ContractValidator::validate(&json!("hi"), "String | std.string.length.min 3"); - assert!(result.is_err()); - - // Valid max length - let result = ContractValidator::validate(&json!("hi"), "String | std.string.length.max 5"); - assert!(result.is_ok()); - - // Too long - let result = - ContractValidator::validate(&json!("hello world"), "String | std.string.length.max 5"); - assert!(result.is_err()); -} - #[test] fn test_form_definition_from_schema_ir() { // Create schema @@ -672,13 +630,7 @@ fn test_full_workflow_integration() { results.insert("server_host".to_string(), json!("0.0.0.0")); results.insert("server_port".to_string(), json!(3000)); - // Step 4: Validate contracts - assert!(ContractValidator::validate(&json!("MyApp"), "String | std.string.NonEmpty").is_ok()); - assert!( - ContractValidator::validate(&json!(3000), "Number | std.number.between 1 65535").is_ok() - ); - - // Step 5: Serialize to Nickel + // Step 4: Serialize to Nickel let nickel_output = NickelSerializer::serialize(&results, &schema).expect("Serialization failed"); @@ -1231,6 +1183,7 @@ fn test_encryption_roundtrip_with_redaction() { sensitive: Some(false), encryption_backend: None, encryption_config: None, + when_false: Default::default(), }, form_parser::FieldDefinition { name: "password".to_string(), @@ -1270,6 +1223,7 @@ fn test_encryption_roundtrip_with_redaction() { sensitive: Some(true), encryption_backend: Some("age".to_string()), encryption_config: None, + when_false: Default::default(), }, form_parser::FieldDefinition { name: "api_key".to_string(), @@ -1309,6 +1263,7 @@ fn test_encryption_roundtrip_with_redaction() { sensitive: Some(true), encryption_backend: None, encryption_config: None, + when_false: Default::default(), }, ]; @@ -1386,6 +1341,7 @@ fn test_encryption_auto_detection_from_field_type() { sensitive: None, // Not explicitly set encryption_backend: None, encryption_config: None, + when_false: Default::default(), }; assert!( @@ -1451,6 +1407,7 @@ fn test_sensitive_field_explicit_override() { sensitive: Some(false), // Explicitly override encryption_backend: None, encryption_config: None, + when_false: Default::default(), }; assert!( @@ -1522,6 +1479,7 @@ fn test_mixed_sensitive_and_non_sensitive_fields() { sensitive, encryption_backend: None, encryption_config: None, + when_false: Default::default(), } }; diff --git a/crates/typedialog-core/tests/proptest_validation.rs b/crates/typedialog-core/tests/proptest_validation.rs deleted file mode 100644 index 2da0f98..0000000 --- a/crates/typedialog-core/tests/proptest_validation.rs +++ /dev/null @@ -1,322 +0,0 @@ -use proptest::prelude::*; -use serde_json::json; -use typedialog_core::nickel::ContractValidator; - -proptest! { - #[test] - fn test_email_validation_never_panics(input in "\\PC*") { - let value = json!(input); - drop(ContractValidator::validate_email(&value)); - } - - #[test] - fn test_valid_emails_accepted( - local in "[a-zA-Z0-9._-]{1,20}", - domain_parts in prop::collection::vec("[a-zA-Z0-9-]{1,10}", 2..5), - tld in "[a-z]{2,6}" - ) { - let domain = format!("{}.{}", domain_parts.join("."), tld); - let email = format!("{}@{}", local, domain); - let value = json!(email); - - let result = ContractValidator::validate_email(&value); - prop_assert!( - result.is_ok(), - "Valid email format '{}' was rejected: {:?}", - email, - result.err() - ); - } - - #[test] - fn test_invalid_emails_rejected_no_at(input in "[a-zA-Z0-9._-]+") { - let value = json!(input); - let result = ContractValidator::validate_email(&value); - prop_assert!(result.is_err(), "Email without @ symbol should be rejected"); - } - - #[test] - fn test_invalid_emails_rejected_multiple_at( - local in "[a-zA-Z0-9._-]{1,10}", - domain in "[a-zA-Z0-9.-]{1,20}" - ) { - let email = format!("{}@{}@extra", local, domain); - let value = json!(email); - let result = ContractValidator::validate_email(&value); - prop_assert!(result.is_err(), "Email with multiple @ symbols should be rejected"); - } - - #[test] - fn test_invalid_emails_rejected_no_domain_dot( - local in "[a-zA-Z0-9._-]{1,10}", - domain in "[a-zA-Z0-9-]{1,10}" - ) { - let email = format!("{}@{}", local, domain); - let value = json!(email); - let result = ContractValidator::validate_email(&value); - prop_assert!( - result.is_err(), - "Email without dot in domain '{}' should be rejected", - email - ); - } - - #[test] - fn test_url_validation_never_panics(input in "\\PC*") { - let value = json!(input); - drop(ContractValidator::validate_url(&value)); - } - - #[test] - fn test_valid_urls_accepted( - scheme in prop::sample::select(&["http://", "https://", "ftp://", "ftps://"]), - host in "[a-zA-Z0-9.-]{1,30}", - path in prop::option::of("[a-zA-Z0-9/_-]{0,50}") - ) { - let url = if let Some(p) = path { - format!("{}{}/{}", scheme, host, p) - } else { - format!("{}{}", scheme, host) - }; - - let value = json!(url); - let result = ContractValidator::validate_url(&value); - prop_assert!( - result.is_ok(), - "Valid URL format '{}' was rejected: {:?}", - url, - result.err() - ); - } - - #[test] - fn test_invalid_urls_rejected_no_scheme( - host in "[a-zA-Z0-9.-]{1,20}", - path in "[a-zA-Z0-9/_-]{0,20}" - ) { - let url = format!("{}/{}", host, path); - let value = json!(url); - let result = ContractValidator::validate_url(&value); - prop_assert!(result.is_err(), "URL without scheme should be rejected"); - } - - #[test] - fn test_invalid_urls_rejected_invalid_scheme( - scheme in "[a-z]{3,8}", - host in "[a-zA-Z0-9.-]{1,20}" - ) { - prop_assume!(!["http", "https", "ftp", "ftps"].contains(&scheme.as_str())); - let url = format!("{}://{}", scheme, host); - let value = json!(url); - let result = ContractValidator::validate_url(&value); - prop_assert!( - result.is_err(), - "URL with invalid scheme '{}' should be rejected", - scheme - ); - } - - #[test] - fn test_invalid_urls_rejected_empty_host( - scheme in prop::sample::select(&["http://", "https://", "ftp://", "ftps://"]) - ) { - let url = scheme.to_string(); - let value = json!(url); - let result = ContractValidator::validate_url(&value); - prop_assert!(result.is_err(), "URL with empty host should be rejected"); - } - - #[test] - fn test_email_validation_type_safety(value in prop_oneof![ - Just(json!(42)), - Just(json!(true)), - Just(json!(null)), - Just(json!([])), - Just(json!({})), - ]) { - let result = ContractValidator::validate_email(&value); - prop_assert!( - result.is_err(), - "Email validation should reject non-string types" - ); - } - - #[test] - fn test_url_validation_type_safety(value in prop_oneof![ - Just(json!(42)), - Just(json!(true)), - Just(json!(null)), - Just(json!([])), - Just(json!({})), - ]) { - let result = ContractValidator::validate_url(&value); - prop_assert!( - result.is_err(), - "URL validation should reject non-string types" - ); - } - - #[test] - fn test_empty_string_email_rejected(whitespace in "[ \\t\\n\\r]*") { - let value = json!(whitespace); - let result = ContractValidator::validate_email(&value); - prop_assert!( - result.is_err(), - "Empty or whitespace-only email should be rejected" - ); - } - - #[test] - fn test_empty_string_url_rejected(whitespace in "[ \\t\\n\\r]*") { - let value = json!(whitespace); - let result = ContractValidator::validate_url(&value); - prop_assert!( - result.is_err(), - "Empty or whitespace-only URL should be rejected" - ); - } - - #[test] - fn test_email_domain_boundary_dots_rejected( - local in "[a-zA-Z0-9._-]{1,10}", - domain in "[a-zA-Z0-9-]{1,10}", - tld in "[a-z]{2,6}" - ) { - let email_start_dot = format!("{}@.{}.{}", local, domain, tld); - let email_end_dot = format!("{}@{}.{}.", local, domain, tld); - - let value_start = json!(email_start_dot); - let result_start = ContractValidator::validate_email(&value_start); - prop_assert!( - result_start.is_err(), - "Email with domain starting with dot should be rejected" - ); - - let value_end = json!(email_end_dot); - let result_end = ContractValidator::validate_email(&value_end); - prop_assert!( - result_end.is_err(), - "Email with domain ending with dot should be rejected" - ); - } - - #[test] - fn test_contract_validate_integration_email( - local in "[a-zA-Z0-9._-]{1,15}", - domain in "[a-zA-Z0-9.-]{3,20}\\.[a-z]{2,6}" - ) { - let email = format!("{}@{}", local, domain); - let value = json!(email); - - let result = ContractValidator::validate(&value, "String | std.string.Email"); - prop_assert!( - result.is_ok() || result.is_err(), - "Contract validation should never panic for any email input" - ); - } - - #[test] - fn test_contract_validate_integration_url( - scheme in prop::sample::select(&["http", "https", "ftp", "ftps", "gopher", "file"]), - host in "[a-zA-Z0-9.-]{1,30}" - ) { - let url = format!("{}://{}", scheme, host); - let value = json!(url); - - let result = ContractValidator::validate(&value, "String | std.string.Url"); - prop_assert!( - result.is_ok() || result.is_err(), - "Contract validation should never panic for any URL input" - ); - } -} - -#[test] -fn test_known_valid_emails() { - let valid_emails = vec![ - "user@example.com", - "test.user@example.com", - "user+tag@example.com", - "user_name@example.co.uk", - "a@b.c", - "very.long.email.address@subdomain.example.org", - ]; - - for email in valid_emails { - let value = json!(email); - let result = ContractValidator::validate_email(&value); - assert!( - result.is_ok(), - "Valid email '{}' was rejected: {:?}", - email, - result.err() - ); - } -} - -#[test] -fn test_known_invalid_emails() { - let invalid_emails = vec![ - "", - "@", - "@@", - "user@", - "@example.com", - "user", - "user@@example.com", - "user@example", - "user@.example.com", - "user@example.com.", - ]; - - for email in invalid_emails { - let value = json!(email); - let result = ContractValidator::validate_email(&value); - assert!(result.is_err(), "Invalid email '{}' was accepted", email); - } -} - -#[test] -fn test_known_valid_urls() { - let valid_urls = vec![ - "http://example.com", - "https://example.com", - "https://example.com/path", - "https://subdomain.example.com/path/to/resource", - "ftp://ftp.example.com", - "ftps://secure.example.com", - "http://localhost", - "https://192.168.1.1", - ]; - - for url in valid_urls { - let value = json!(url); - let result = ContractValidator::validate_url(&value); - assert!( - result.is_ok(), - "Valid URL '{}' was rejected: {:?}", - url, - result.err() - ); - } -} - -#[test] -fn test_known_invalid_urls() { - let invalid_urls = vec![ - "", - "http://", - "https://", - "example.com", - "ftp:/example.com", - "gopher://example.com", - "file://example.com", - "htp://example.com", - ]; - - for url in invalid_urls { - let value = json!(url); - let result = ContractValidator::validate_url(&value); - assert!(result.is_err(), "Invalid URL '{}' was accepted", url); - } -} diff --git a/crates/typedialog-prov-gen/src/generator/validator_generator.rs b/crates/typedialog-prov-gen/src/generator/validator_generator.rs index 412d9cd..9dbde66 100644 --- a/crates/typedialog-prov-gen/src/generator/validator_generator.rs +++ b/crates/typedialog-prov-gen/src/generator/validator_generator.rs @@ -284,13 +284,8 @@ impl ValidatorGenerator { fn get_common_validator_for_field(field: &crate::models::ConfigField) -> Option { match field.field_type { FieldType::Number => { - if field.min.is_some() && field.max.is_some() { - Some(format!( - "common.Range {} {} \"{}\"", - field.min.unwrap(), - field.max.unwrap(), - field.name - )) + if let (Some(min), Some(max)) = (field.min, field.max) { + Some(format!("common.Range {} {} \"{}\"", min, max, field.name)) } else if field.min == Some(0) { Some(format!("common.NonNegativeNumber \"{}\"", field.name)) } else if field.min == Some(1) { diff --git a/crates/typedialog-tui/src/commands/form.rs b/crates/typedialog-tui/src/commands/form.rs index a1c0a90..c64ba6d 100644 --- a/crates/typedialog-tui/src/commands/form.rs +++ b/crates/typedialog-tui/src/commands/form.rs @@ -19,8 +19,7 @@ pub async fn execute_form( output_file: &Option, cli_locale: &Option, ) -> Result<()> { - let toml_content = fs::read_to_string(&config).map_err(Error::io)?; - let mut form = form_parser::parse_toml(&toml_content)?; + let mut form = form_parser::load_form(&config)?; // TUI backend uses unified elements array internally, migrate if using legacy format form.migrate_to_elements(); diff --git a/crates/typedialog-web/src/main.rs b/crates/typedialog-web/src/main.rs index a40235e..8ba6a6f 100644 --- a/crates/typedialog-web/src/main.rs +++ b/crates/typedialog-web/src/main.rs @@ -376,9 +376,7 @@ async fn execute_form( cli_locale: &Option, open_browser: bool, ) -> Result<()> { - let toml_content = fs::read_to_string(&config).map_err(Error::io)?; - - let mut form = form_parser::parse_toml(&toml_content)?; + let mut form = form_parser::load_form(&config)?; // Auto-detect format from output filename if not explicitly json let actual_format = detect_output_format(output.as_ref(), format); @@ -389,8 +387,7 @@ async fn execute_form( // Extract base directory for resolving relative paths in includes let base_dir = config.parent().unwrap_or_else(|| std::path::Path::new(".")); - // Expand groups with includes to load fragment files - form = form_parser::expand_includes(form, base_dir)?; + // form = form_parser::expand_includes(form, base_dir)?; // handled by load_form for .ncl // Load default values from JSON, TOML, or .ncl file if provided let initial_values = if let Some(defaults_path_input) = defaults { @@ -603,11 +600,7 @@ async fn nickel_roundtrip_cmd( eprintln!("[roundtrip] Loading form: {}", form_path.display()); } - let form_content = fs::read_to_string(&form_path) - .map_err(|e| Error::validation_failed(format!("Failed to read form: {}", e)))?; - - let mut form = form_parser::parse_toml(&form_content) - .map_err(|e| Error::validation_failed(format!("Failed to parse form: {}", e)))?; + let mut form = form_parser::load_form(&form_path)?; // Web backend uses unified elements array internally, migrate if using legacy format form.migrate_to_elements(); @@ -616,9 +609,7 @@ async fn nickel_roundtrip_cmd( .parent() .unwrap_or_else(|| std::path::Path::new(".")); - // Expand groups with includes to load fragment files - form = form_parser::expand_includes(form, form_base_dir) - .map_err(|e| Error::validation_failed(format!("Failed to expand includes: {}", e)))?; + // form = form_parser::expand_includes(form, form_base_dir)?; // handled by load_form for .ncl // Step 3: Load default values from input .ncl if exists let initial_values = if input.exists() { diff --git a/crates/typedialog/src/commands/form.rs b/crates/typedialog/src/commands/form.rs index c43586f..01d9f44 100644 --- a/crates/typedialog/src/commands/form.rs +++ b/crates/typedialog/src/commands/form.rs @@ -85,8 +85,7 @@ pub async fn execute_form( vault_token: Option, vault_key_path: Option, ) -> Result<()> { - let toml_content = fs::read_to_string(&config).map_err(Error::io)?; - let form = form_parser::parse_toml(&toml_content)?; + let form = form_parser::load_form(&config)?; let base_dir = config.parent().unwrap_or_else(|| std::path::Path::new(".")); // Auto-detect format from output filename if not explicitly specified diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..adfd262 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,21 @@ +# Architecture Decision Records + +ADRs documenting key architectural decisions and their rationale for TypeDialog. + +## ADR Index + +- **[ADR-001: Nickel as First-Class Form Definition Format](./adr-001-nickel-form-definition.md)** — + Nickel (`.ncl`) forms loaded via `nickel export` subprocess, replacing `ContractValidator`, + fragment lazy-loading, and `${constraint.*}` interpolation. TOML remains supported. + +## Decision Format + +Each ADR follows this structure: + +- **Status**: Accepted, Proposed, Deprecated, Superseded +- **Context**: Problem statement and constraints +- **Decision**: The chosen approach +- **Rationale**: Numbered list of reasons +- **Consequences**: Benefits and trade-offs +- **Implementation**: Code showing how it works +- **Alternatives Considered**: Rejected options with reasoning diff --git a/docs/adr/adr-001-nickel-form-definition.md b/docs/adr/adr-001-nickel-form-definition.md new file mode 100644 index 0000000..b3dfcf9 --- /dev/null +++ b/docs/adr/adr-001-nickel-form-definition.md @@ -0,0 +1,146 @@ +# ADR-001: Nickel as First-Class Form Definition Format + +**Status**: Accepted | **Date**: 2026-03-01 | **Supersedes**: None + +## Context + +TypeDialog forms were defined exclusively in TOML. TOML works well as a runtime serialization +target — zero overhead, pure data, direct `serde` deserialization — but has no type system, no +schema language, and no composition mechanism. Three structural problems accumulated: + +**Incomplete contract validation.** `ContractValidator` was a Rust reimplementation of a subset +of Nickel stdlib predicates. The critical failure mode was fail-open: unknown predicates returned +`Ok(())` silently. A field with `String | std.string.IpAddr` received no validation, no error. +Every new predicate required a parallel Rust implementation guaranteed to drift from the Nickel +reference. + +**I/O coupled to form execution.** Fragment lazy-loading (`includes = ["tls.toml"]`) triggered +`std::fs::read_to_string` inside the executor loop. Execution had hidden I/O side-effects. The +two-phase execution design (selectors first, then load fragments) existed specifically to work +around not knowing which fragments to load before Phase 1 ran. `${constraint.*}` interpolation +required a raw-string pre-pass before `toml::from_str` could be called. + +**No static guarantees at load time.** A malformed TOML form was only detected when the executor +reached the bad field, mid-interaction, after the user had already answered several questions. + +## Decision + +Support Nickel (`.ncl`) as a first-class form definition format alongside TOML. Forms in Nickel +are loaded via `nickel export --format json` as a subprocess, deserializing the JSON output +directly into `FormDefinition`. If `nickel export` fails for any reason — contract violation, +syntax error, missing import — the process aborts before any interaction begins. + +TOML remains fully supported. No migration is required for existing forms. + +Three structural changes accompany this decision: + +1. Remove `ContractValidator`. Validation is the Nickel interpreter's responsibility. +2. Replace fragment lazy-loading with Nickel native imports. `build_element_list` becomes a + pure in-memory operation. +3. Add `when_false` to `FieldDefinition` to control output behavior for conditionally-hidden + fields, enabling correct Nickel roundtrip for all schema fields. + +## Rationale + +1. **Fail-safe by default** — `nickel export` failing hard is better than fail-open validation. + The user receives Nickel's own error message, which is precise and references the contract. +2. **Single source of truth for contracts** — The Nickel interpreter evaluates every predicate in + the stdlib. No parallel Rust implementation, no drift, no coverage gaps. +3. **Static composition at load time** — Nickel's module system (`import`, `let`, merge operators) + replaces runtime fragment loading. The executor receives a fully composed form; it performs no I/O. +4. **Proven pattern** — Config loading in typedialog already uses `nickel export` via subprocess. + The contract between the Nickel CLI and the Rust consumer is established and tested. +5. **Backward compatibility** — TOML continues to work unchanged. Adoption of `.ncl` is opt-in + per form, not a flag day. + +## Consequences + +- **Positive**: + - Form validation is complete: every Nickel contract is evaluated by the Nickel interpreter + - `build_element_list` is pure; executor has no I/O side-effects + - `${constraint.*}` interpolation eliminated; Nickel handles constant references natively + - Two-phase execution simplified: Phase 2 iterates an in-memory list, no file I/O + - Form authors gain Nickel's full composition model: imports, let bindings, merge operators + +- **Negative**: + - `.ncl` forms require the `nickel` CLI binary on `PATH` at runtime + - TOML forms do not benefit from the new static guarantees + - `when_false = "default"` requires form authors to declare `default` values on all + conditionally-hidden fields used in Nickel roundtrip + +## Implementation + +### Loading a `.ncl` form + +```rust +pub fn load_form(path: impl AsRef) -> Result { + match path.as_ref().extension().and_then(|e| e.to_str()) { + Some("ncl") => load_from_ncl(path), + _ => load_from_file(path), // TOML path, unchanged + } +} + +pub fn load_from_ncl(path: impl AsRef) -> Result { + let json = NickelCli::export(path.as_ref())?; // aborts if nickel export fails + serde_json::from_value(json).map_err(|e| ErrorWrapper::new(format!( + "Failed to deserialize Nickel export as FormDefinition: {}", e + ))) +} +``` + +### Form defined in Nickel + +```nickel +# form.ncl +let tls = import "fragments/tls.ncl" in +{ + name = "Server Configuration", + elements = [ + { type = "field", name = "host", field_type = "text", prompt = "Host: " }, + { type = "field", name = "tls_enabled", field_type = "confirm", prompt = "Enable TLS?" }, + ] @ tls.elements, +} +``` + +`nickel export --format json form.ncl` produces the fully composed `FormDefinition` JSON. +If `tls.ncl` is missing or has a type error, the subprocess exits non-zero and the process +aborts immediately. + +### `when_false` for conditional fields in roundtrip + +```toml +[fields.tls_cert_path] +when = "tls_enabled == true" +when_false = "default" # inject field.default into results when condition is false +default = "/etc/ssl/cert.pem" +``` + +When `tls_enabled` is false, `tls_cert_path` is never shown. With `when_false = "default"`, +the executor inserts `"/etc/ssl/cert.pem"` into results, so the roundtrip writer always has a +value for every field in the Nickel schema regardless of which branches the user navigated. + +## Related ADRs + +- No prior ADRs. This is the first recorded decision. + +## Alternatives Considered + +1. **Expand `ContractValidator`** — Rejected. Every new predicate requires a parallel Rust + implementation. Fail-open default is a correctness hazard. Duplication introduces drift + by construction. + +2. **`nickel-lang-core` as a library crate** — Considered. Would eliminate the subprocess and + the `PATH` dependency. Rejected: `nickel-lang-core` has no stable crates.io API surface; + linking the Nickel runtime increases binary size for all backends; the subprocess model is + already proven in the config-loading path. + +3. **Per-field `nickel check` during interaction** — Considered for interactive validation per + keystroke. Rejected: form execution is stateful (answer N may be required to evaluate + contract N+1). Subprocess overhead per field is not acceptable. Load-time `nickel export` + plus post-execution `nickel typecheck` (existing roundtrip path) covers both ends without + per-field cost. + +4. **JSON Schema or CUE as schema layer over TOML** — Rejected. Structurally inferior to Nickel + contracts for configuration data: no gradual types, no first-class functions, no `std` + predicate library. Adopting a second schema language while Nickel is already a first-class + citizen adds complexity without comparable expressiveness. diff --git a/docs/architecture.md b/docs/architecture.md index dd25cff..cd598b7 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -13,8 +13,8 @@ The comparison point is relevant: Inquire (`inquire = "0.9"`) is one of TypeDial ```text ┌─────────────────────────────────────────────────────┐ │ typedialog │ -│ Declarative TOML forms + i18n + templates + AI │ -│ BackendFactory → dispatch by feature │ +│ Declarative forms (Nickel / TOML) + i18n + AI │ +│ load_form → BackendFactory → dispatch by feature │ ├──────────────┬──────────────┬──────────────────────-┤ │ CLI backend │ TUI backend │ Web backend │ │ (Inquire) │ (Ratatui) │ (Axum) │ @@ -33,7 +33,7 @@ The comparison point is relevant: Inquire (`inquire = "0.9"`) is one of TypeDial ### Differentiators -**Unified schema** — a single TOML file describes the same form for CLI, TUI, and HTTP. No per-backend code. +**Unified schema** — a single Nickel (`.ncl`) or TOML file describes the same form for CLI, TUI, and HTTP. No per-backend code. See [ADR-001](./adr/adr-001-nickel-form-definition.md). **Nushell as integration runtime** — outputs flow as structured data into Nu pipelines via the Nushell plugin protocol (`nu-plugin = "0.110.0"`), not as text. @@ -43,6 +43,40 @@ The comparison point is relevant: Inquire (`inquire = "0.9"`) is one of TypeDial --- +## Form Loading + +### `load_form` — Unified Entry Point + +All binaries and roundtrip paths call `form_parser::load_form(path)`. Extension determines the loader: + +```rust +pub fn load_form(path: impl AsRef) -> Result { + match path.as_ref().extension().and_then(|e| e.to_str()) { + Some("ncl") => load_from_ncl(path), + _ => load_from_file(path), // TOML path, unchanged + } +} +``` + +`.ncl` forms are loaded via `nickel export --format json` as a subprocess. If the export fails for any reason — contract violation, syntax error, missing import — the process aborts before any interaction begins. TOML forms are deserialized directly via `serde`. + +Fragment lazy-loading (`includes:` in TOML groups) is replaced by Nickel native imports. `build_element_list` is now a pure in-memory operation with no I/O. The functions `expand_includes` and `load_fragment_form` remain available in `fragments.rs` as rescue code. + +### `when_false` — Conditional Field Output + +Fields with a `when:` condition that evaluates to false are skipped by default. `when_false = "default"` instructs the executor to inject the field's `default` value into results even when the condition is false: + +```toml +[fields.tls_cert_path] +when = "tls_enabled == true" +when_false = "default" +default = "/etc/ssl/cert.pem" +``` + +This ensures Nickel roundtrip writers always have a value for every schema field regardless of which branches the user navigated. + +--- + ## BackendFactory ### The `FormBackend` Trait @@ -126,26 +160,27 @@ receives this as `previous_results` and uses it for `options_from` filtering in **Phase 1 — Selector fields first** -Fields that control `when:` conditionals are identified and executed before loading any fragments. -This ensures conditional branches are known before lazy-loading dependent content. +Fields that control `when:` conditionals are identified and executed before Phase 2. +This ensures conditional branches are known before iterating the element list. ```rust let selector_field_names = identify_selector_fields(&form); // execute only selectors → populate results ``` -**Phase 2 — Build element list with lazy loading** +**Phase 2 — Build element list (pure)** -With Phase 1 results known, fragments (`includes:`) are loaded only if their controlling condition is met. +With Phase 1 results known, the element list is built from the fully-composed in-memory form. +No I/O occurs here — fragments were resolved by `load_form` before execution began. ```rust -let element_list = build_element_list(&form, base_dir, &results)?; +let element_list = build_element_list(&form); ``` **Phase 3 — Execute remaining fields** -Iterates the element list, evaluates `when:` conditions per-element, and dispatches to the backend. -Two sub-modes: +Iterates the element list, evaluates `when:` conditions per-element, applies `when_false` defaults +for skipped fields, and dispatches to the backend. Two sub-modes: - `DisplayMode::Complete` — passes all items and fields to `execute_form_complete` at once (used by TUI and Web for reactive rendering) @@ -156,13 +191,13 @@ Two sub-modes: ```text typedialog binary │ -├── parse TOML → FormDefinition +├── form_parser::load_form(path) → FormDefinition (.ncl via nickel export | .toml via serde) ├── BackendFactory::create(BackendType::Cli) → Box ├── execute_with_backend_two_phase_with_defaults(form, backend, i18n, base_dir, defaults) │ │ │ ├── Phase 1: selector fields → backend.execute_field() -│ ├── Phase 2: build_element_list() with lazy fragment loading -│ └── Phase 3: iterate → render_display_item() / execute_field() +│ ├── Phase 2: build_element_list() — pure, no I/O +│ └── Phase 3: iterate → when/when_false → render_display_item() / execute_field() │ └── HashMap → JSON / YAML / TOML output ``` diff --git a/examples/01-basic/base_form.ncl b/examples/01-basic/base_form.ncl new file mode 100644 index 0000000..3fef833 --- /dev/null +++ b/examples/01-basic/base_form.ncl @@ -0,0 +1,25 @@ +{ + name = "Simple Contact Form", + description = "A basic contact form with all field types", + elements = [ + { type = "text", name = "name", prompt = "Your name", required = true, placeholder = "John Doe" }, + { type = "text", name = "email", prompt = "Your email", required = true, placeholder = "john@example.com" }, + { type = "text", name = "subject", prompt = "Subject", required = true }, + { type = "editor", name = "message", prompt = "Your message", file_extension = "txt" }, + { type = "select", name = "priority", prompt = "Priority level", required = true, options = [ + { value = "Low", label = "Low Priority - Can wait" }, + { value = "Medium", label = "Medium Priority - Normal schedule" }, + { value = "High", label = "High Priority - Urgent attention" }, + { value = "Urgent", label = "Urgent - Critical issue" }, + ] + }, + { type = "multiselect", name = "category", prompt = "Message categories", options = [ + { value = "Bug Report", label = "🐛 Bug Report" }, + { value = "Feature Request", label = "✨ Feature Request" }, + { value = "Documentation", label = "📚 Documentation" }, + { value = "Other", label = "❓ Other" }, + ] + }, + { type = "confirm", name = "newsletter", prompt = "Subscribe to updates?", default = false }, + ], +} diff --git a/examples/01-basic/base_from.toml b/examples/01-basic/base_from.toml deleted file mode 100644 index c37f0e1..0000000 --- a/examples/01-basic/base_from.toml +++ /dev/null @@ -1,57 +0,0 @@ -description = "A basic contact form with all field types" -name = "Simple Contact Form" - -[[elements]] -name = "name" -placeholder = "John Doe" -prompt = "Your name" -required = true -type = "text" - -[[elements]] -name = "email" -placeholder = "john@example.com" -prompt = "Your email" -required = true -type = "text" - -[[elements]] -name = "subject" -prompt = "Subject" -required = true -type = "text" - -[[elements]] -file_extension = "txt" -name = "message" -prompt = "Your message" -type = "editor" - -[[elements]] -name = "priority" -options = [ - { value = "Low", label = "Low Priority - Can wait" }, - { value = "Medium", label = "Medium Priority - Normal schedule" }, - { value = "High", label = "High Priority - Urgent attention" }, - { value = "Urgent", label = "Urgent - Critical issue" }, -] -prompt = "Priority level" -required = true -type = "select" - -[[elements]] -name = "category" -options = [ - { value = "Bug Report", label = "🐛 Bug Report" }, - { value = "Feature Request", label = "✨ Feature Request" }, - { value = "Documentation", label = "📚 Documentation" }, - { value = "Other", label = "❓ Other" }, -] -prompt = "Message categories" -type = "multiselect" - -[[elements]] -default = false -name = "newsletter" -prompt = "Subscribe to updates?" -type = "confirm" diff --git a/examples/01-basic/debug_simple.ncl b/examples/01-basic/debug_simple.ncl new file mode 100644 index 0000000..6b32f96 --- /dev/null +++ b/examples/01-basic/debug_simple.ncl @@ -0,0 +1,8 @@ +{ + name = "Simple Debug Form", + description = "Two fields to test form execution flow", + elements = [ + { type = "text", name = "first_name", prompt = "First Name", required = true }, + { type = "text", name = "last_name", prompt = "Last Name", required = true }, + ], +} diff --git a/examples/01-basic/debug_simple.toml b/examples/01-basic/debug_simple.toml deleted file mode 100644 index a102c48..0000000 --- a/examples/01-basic/debug_simple.toml +++ /dev/null @@ -1,17 +0,0 @@ -description = "Two fields to test form execution flow" -locale = "en-US" -name = "Simple Debug Form" - -[[elements]] -name = "first_name" -order = 1 -prompt = "First Name" -required = true -type = "text" - -[[elements]] -name = "last_name" -order = 2 -prompt = "Last Name" -required = true -type = "text" diff --git a/examples/01-basic/form.ncl b/examples/01-basic/form.ncl new file mode 100644 index 0000000..a56ad73 --- /dev/null +++ b/examples/01-basic/form.ncl @@ -0,0 +1,25 @@ +{ + name = "Simple Contact Form", + description = "A basic contact form with all field types", + elements = [ + { type = "text", name = "name", prompt = "Your name", required = true, placeholder = "John Doe" }, + { type = "text", name = "email", prompt = "Your email", required = true, placeholder = "john@example.com" }, + { type = "text", name = "subject", prompt = "Subject", required = true }, + { type = "editor", name = "message", prompt = "Your message", file_extension = "txt" }, + { type = "select", name = "priority", prompt = "Priority level", required = true, options = [ + { value = "Low", label = "Low Priority - Can wait" }, + { value = "Medium", label = "Medium Priority - Normal schedule" }, + { value = "High", label = "High Priority - Urgent attention" }, + { value = "Urgent", label = "Urgent - Critical issue" }, + ] + }, + { type = "multiselect", name = "category", prompt = "Message categories", display_mode = "grid", searchable = true, default = "Bug Report", options = [ + { value = "Bug Report", label = "🐛 Bug Report" }, + { value = "Feature Request", label = "✨ Feature Request" }, + { value = "Documentation", label = "📚 Documentation" }, + { value = "Other", label = "❓ Other" }, + ] + }, + { type = "confirm", name = "newsletter", prompt = "Subscribe to updates?", default = false }, + ], +} diff --git a/examples/01-basic/form.toml b/examples/01-basic/form.toml deleted file mode 100644 index 3343cc2..0000000 --- a/examples/01-basic/form.toml +++ /dev/null @@ -1,60 +0,0 @@ -description = "A basic contact form with all field types" -name = "Simple Contact Form" - -[[elements]] -name = "name" -placeholder = "John Doe" -prompt = "Your name" -required = true -type = "text" - -[[elements]] -name = "email" -placeholder = "john@example.com" -prompt = "Your email" -required = true -type = "text" - -[[elements]] -name = "subject" -prompt = "Subject" -required = true -type = "text" - -[[elements]] -file_extension = "txt" -name = "message" -prompt = "Your message" -type = "editor" - -[[elements]] -name = "priority" -options = [ - { value = "Low", label = "Low Priority - Can wait" }, - { value = "Medium", label = "Medium Priority - Normal schedule" }, - { value = "High", label = "High Priority - Urgent attention" }, - { value = "Urgent", label = "Urgent - Critical issue" }, -] -prompt = "Priority level" -required = true -type = "select" - -[[elements]] -default = "Bug Report" -display_mode = "grid" -name = "category" -options = [ - { value = "Bug Report", label = "🐛 Bug Report" }, - { value = "Feature Request", label = "✨ Feature Request" }, - { value = "Documentation", label = "📚 Documentation" }, - { value = "Other", label = "❓ Other" }, -] -prompt = "Message categories" -searchable = true -type = "multiselect" - -[[elements]] -default = "false" -name = "newsletter" -prompt = "Subscribe to updates?" -type = "confirm" diff --git a/examples/01-basic/form_with_grouped_items.ncl b/examples/01-basic/form_with_grouped_items.ncl new file mode 100644 index 0000000..4aaa7e7 --- /dev/null +++ b/examples/01-basic/form_with_grouped_items.ncl @@ -0,0 +1,50 @@ +{ + name = "Form with Grouped Display Items", + description = "Demonstrates grouping related display items together", + elements = [ + # Main header (no group - always shown) + { type = "header", name = "main_header", title = "✨ Grouped Items Example", align = "center", border_top = true, border_bottom = true }, + + # Account selection + { type = "select", name = "account_type", prompt = "Select account type", required = true, options = [ + { value = "Personal", label = "Personal - Individual Users" }, + { value = "Premium", label = "Premium - Growing Teams" }, + { value = "Enterprise", label = "Enterprise - Large Organizations" }, + ] + }, + + # PREMIUM GROUP + { type = "section", name = "premium_header", title = "🌟 Premium Features", border_top = true, group = "premium", when = "account_type == Premium" }, + { type = "section", name = "premium_features", content = "✓ Unlimited storage\n✓ Advanced analytics\n✓ Priority support\n✓ Custom branding", group = "premium", when = "account_type == Premium" }, + { type = "section", name = "premium_price", content = "Pricing: $29/month", border_bottom = true, group = "premium", when = "account_type == Premium" }, + { type = "select", name = "premium_payment", prompt = "Payment method", required = true, when = "account_type == Premium", options = [ + { value = "Credit Card", label = "💳 Credit Card" }, + { value = "Bank Transfer", label = "🏦 Bank Transfer" }, + { value = "PayPal", label = "🅿️ PayPal" }, + ] + }, + + # ENTERPRISE GROUP + { type = "section", name = "enterprise_header", title = "🏛️ Enterprise Solution", border_top = true, group = "enterprise", when = "account_type == Enterprise" }, + { type = "section", name = "enterprise_features", content = "✓ Unlimited everything\n✓ Dedicated support\n✓ Custom integration\n✓ SLA guarantee\n✓ On-premise option", group = "enterprise", when = "account_type == Enterprise" }, + { type = "section", name = "enterprise_note", content = "⚠️ Requires enterprise agreement", group = "enterprise", when = "account_type == Enterprise" }, + { type = "section", name = "enterprise_contact_info", content = "Contact our sales team for pricing", border_bottom = true, group = "enterprise", when = "account_type == Enterprise" }, + { type = "text", name = "enterprise_contact_email", prompt = "Your email", required = true, when = "account_type == Enterprise" }, + + # SUPPORT GROUP + { type = "section", name = "support_section_header", title = "📞 Support Options", border_top = true, group = "support" }, + { type = "section", name = "support_info", content = "Choose your preferred support level", group = "support" }, + { type = "select", name = "support_level", prompt = "Support Level", required = true, group = "support", options = [ + { value = "Basic", label = "Basic - Email only" }, + { value = "Standard", label = "Standard - Email & chat" }, + { value = "Premium", label = "Premium - Phone & live support" }, + ] + }, + { type = "section", name = "support_footer", content = "Support is available 24/7", border_bottom = true, group = "support" }, + + # FINAL GROUP + { type = "section", name = "final_header", title = "✅ Complete Registration", border_top = true, group = "final" }, + { type = "confirm", name = "agree_terms", prompt = "I agree to the terms and conditions", required = true, group = "final" }, + { type = "cta", name = "final_cta", title = "Ready?", content = "Click submit to complete your registration", align = "center", border_top = true, border_bottom = true, group = "final" }, + ], +} diff --git a/examples/01-basic/form_with_grouped_items.toml b/examples/01-basic/form_with_grouped_items.toml deleted file mode 100644 index bdb78b1..0000000 --- a/examples/01-basic/form_with_grouped_items.toml +++ /dev/null @@ -1,155 +0,0 @@ -description = "Demonstrates grouping related display items together" -name = "Form with Grouped Display Items" - -# Main header (no group - always shown) -[[elements]] -align = "center" -border_bottom = true -border_top = true -name = "main_header" -title = "✨ Grouped Items Example" -type = "header" - -# Account selection -[[elements]] -name = "account_type" -options = [ - { value = "Personal", label = "Personal - Individual Users" }, - { value = "Premium", label = "Premium - Growing Teams" }, - { value = "Enterprise", label = "Enterprise - Large Organizations" }, -] -prompt = "Select account type" -required = true -type = "select" - -# PREMIUM GROUP - All these items are grouped together -[[elements]] -border_top = true -group = "premium" -name = "premium_header" -title = "🌟 Premium Features" -type = "section" -when = "account_type == Premium" - -[[elements]] -content = "✓ Unlimited storage\n✓ Advanced analytics\n✓ Priority support\n✓ Custom branding" -group = "premium" -name = "premium_features" -type = "section" -when = "account_type == Premium" - -[[elements]] -border_bottom = true -content = "Pricing: $29/month" -group = "premium" -name = "premium_price" -type = "section" -when = "account_type == Premium" - -[[elements]] -name = "premium_payment" -options = [ - { value = "Credit Card", label = "💳 Credit Card" }, - { value = "Bank Transfer", label = "🏦 Bank Transfer" }, - { value = "PayPal", label = "🅿️ PayPal" }, -] -prompt = "Payment method" -required = true -type = "select" -when = "account_type == Premium" - -# ENTERPRISE GROUP - All these items grouped together -[[elements]] -border_top = true -group = "enterprise" -name = "enterprise_header" -title = "🏛️ Enterprise Solution" -type = "section" -when = "account_type == Enterprise" - -[[elements]] -content = "✓ Unlimited everything\n✓ Dedicated support\n✓ Custom integration\n✓ SLA guarantee\n✓ On-premise option" -group = "enterprise" -name = "enterprise_features" -type = "section" -when = "account_type == Enterprise" - -[[elements]] -content = "⚠️ Requires enterprise agreement" -group = "enterprise" -name = "enterprise_note" -type = "section" -when = "account_type == Enterprise" - -[[elements]] -border_bottom = true -content = "Contact our sales team for pricing" -group = "enterprise" -name = "enterprise_contact_info" -type = "section" -when = "account_type == Enterprise" - -[[elements]] -name = "enterprise_contact_email" -prompt = "Your email" -required = true -type = "text" -when = "account_type == Enterprise" - -# SUPPORT GROUP - Organized support options -[[elements]] -border_top = true -group = "support" -name = "support_section_header" -title = "📞 Support Options" -type = "section" - -[[elements]] -content = "Choose your preferred support level" -group = "support" -name = "support_info" -type = "section" - -[[elements]] -group = "support" -name = "support_level" -options = [ - { value = "Basic", label = "Basic - Email only" }, - { value = "Standard", label = "Standard - Email & chat" }, - { value = "Premium", label = "Premium - Phone & live support" }, -] -prompt = "Support Level" -required = true -type = "select" - -[[elements]] -border_bottom = true -content = "Support is available 24/7" -group = "support" -name = "support_footer" -type = "section" - -# FINAL GROUP - Completion items -[[elements]] -border_top = true -group = "final" -name = "final_header" -title = "✅ Complete Registration" -type = "section" - -[[elements]] -group = "final" -name = "agree_terms" -prompt = "I agree to the terms and conditions" -required = true -type = "confirm" - -[[elements]] -align = "center" -border_bottom = true -border_top = true -content = "Click submit to complete your registration" -group = "final" -name = "final_cta" -title = "Ready?" -type = "cta" diff --git a/examples/01-basic/form_with_sections.ncl b/examples/01-basic/form_with_sections.ncl new file mode 100644 index 0000000..50fce5f --- /dev/null +++ b/examples/01-basic/form_with_sections.ncl @@ -0,0 +1,61 @@ +{ + name = "Professional Service Registration", + description = "Multi-section registration form with headers and CTAs", + elements = [ + # Header section + { type = "header", name = "main_header", title = "🎯 Professional Services Registration", align = "center", border_top = true, border_bottom = true }, + + # Welcome section + { type = "section", name = "welcome", content = "Welcome to our professional services platform. Please fill in your information to get started." }, + + # Contact information section header + { type = "section_header", name = "contact_header", title = "📋 Contact Information", border_top = true, margin_left = 0 }, + + # Contact fields + { type = "text", name = "full_name", prompt = "Full Name", required = true }, + { type = "text", name = "email", prompt = "Email Address", required = true }, + { type = "text", name = "phone", prompt = "Phone Number", required = false }, + + # Services section + { type = "section_header", name = "services_header", title = "🔧 Services Selection", border_top = true, margin_left = 0 }, + { type = "select", name = "primary_service", prompt = "Primary Service Needed", required = true, options = [ + { value = "Consulting", label = "💼 Consulting & Strategy" }, + { value = "Development", label = "🚀 Development & Engineering" }, + { value = "Design", label = "🎨 Design & UX" }, + { value = "Support", label = "🛠️ Support & Maintenance" }, + ] + }, + { type = "multiselect", name = "additional_services", prompt = "Additional Services", options = [ + { value = "Training", label = "📚 Training Programs" }, + { value = "Documentation", label = "📖 Documentation" }, + { value = "Maintenance", label = "🔧 Maintenance & Support" }, + { value = "Security Audit", label = "🔐 Security Audit" }, + ] + }, + + # Preferences section + { type = "section_header", name = "prefs_header", title = "⚙️ Preferences", border_top = true, margin_left = 0 }, + { type = "select", name = "experience_level", prompt = "Your Experience Level", options = [ + { value = "Beginner", label = "Beginner - Just getting started" }, + { value = "Intermediate", label = "Intermediate - Some experience" }, + { value = "Advanced", label = "Advanced - Deep knowledge" }, + { value = "Expert", label = "Expert - Full mastery" }, + ] + }, + { type = "select", name = "preferred_contact", prompt = "Preferred Contact Method", options = [ + { value = "Email", label = "📧 Email" }, + { value = "Phone", label = "📞 Phone" }, + { value = "Video Call", label = "🎥 Video Call" }, + ] + }, + + # Agreement section + { type = "section_header", name = "agreement_header", title = "📜 Agreement", border_top = true, margin_left = 0 }, + { type = "confirm", name = "agree_terms", prompt = "I agree to the terms and conditions", required = true, default = false }, + { type = "confirm", name = "agree_privacy", prompt = "I agree to the privacy policy", required = true, default = false }, + { type = "confirm", name = "marketing_consent", prompt = "I consent to receive marketing communications", default = false }, + + # Footer with CTA + { type = "cta", name = "final_cta", title = "Thank you for your information!", content = "Click submit to complete your registration.", align = "center", border_top = true, border_bottom = true }, + ], +} diff --git a/examples/01-basic/form_with_sections.toml b/examples/01-basic/form_with_sections.toml deleted file mode 100644 index 65e2017..0000000 --- a/examples/01-basic/form_with_sections.toml +++ /dev/null @@ -1,142 +0,0 @@ -description = "Multi-section registration form with headers and CTAs" -name = "Professional Service Registration" - -# Header section -[[elements]] -align = "center" -border_bottom = true -border_top = true -name = "main_header" -title = "🎯 Professional Services Registration" -type = "header" - -# Welcome section -[[elements]] -content = "Welcome to our professional services platform. Please fill in your information to get started." -name = "welcome" -type = "section" - -# Contact information section header -[[elements]] -border_top = true -margin_left = 0 -name = "contact_header" -title = "📋 Contact Information" -type = "section_header" - -# Contact fields -[[elements]] -name = "full_name" -prompt = "Full Name" -required = true -type = "text" - -[[elements]] -name = "email" -prompt = "Email Address" -required = true -type = "text" - -[[elements]] -name = "phone" -prompt = "Phone Number" -required = false -type = "text" - -# Services section -[[elements]] -border_top = true -margin_left = 0 -name = "services_header" -title = "🔧 Services Selection" -type = "section_header" - -[[elements]] -name = "primary_service" -options = [ - { value = "Consulting", label = "💼 Consulting & Strategy" }, - { value = "Development", label = "🚀 Development & Engineering" }, - { value = "Design", label = "🎨 Design & UX" }, - { value = "Support", label = "🛠️ Support & Maintenance" }, -] -prompt = "Primary Service Needed" -required = true -type = "select" - -[[elements]] -name = "additional_services" -options = [ - { value = "Training", label = "📚 Training Programs" }, - { value = "Documentation", label = "📖 Documentation" }, - { value = "Maintenance", label = "🔧 Maintenance & Support" }, - { value = "Security Audit", label = "🔐 Security Audit" }, -] -prompt = "Additional Services" -type = "multiselect" - -# Preferences section -[[elements]] -border_top = true -margin_left = 0 -name = "prefs_header" -title = "⚙️ Preferences" -type = "section_header" - -[[elements]] -name = "experience_level" -options = [ - { value = "Beginner", label = "Beginner - Just getting started" }, - { value = "Intermediate", label = "Intermediate - Some experience" }, - { value = "Advanced", label = "Advanced - Deep knowledge" }, - { value = "Expert", label = "Expert - Full mastery" }, -] -prompt = "Your Experience Level" -type = "select" - -[[elements]] -name = "preferred_contact" -options = [ - { value = "Email", label = "📧 Email" }, - { value = "Phone", label = "📞 Phone" }, - { value = "Video Call", label = "🎥 Video Call" }, -] -prompt = "Preferred Contact Method" -type = "select" - -# Agreement section -[[elements]] -border_top = true -margin_left = 0 -name = "agreement_header" -title = "📜 Agreement" -type = "section_header" - -[[elements]] -default = false -name = "agree_terms" -prompt = "I agree to the terms and conditions" -required = true -type = "confirm" - -[[elements]] -default = false -name = "agree_privacy" -prompt = "I agree to the privacy policy" -required = true -type = "confirm" - -[[elements]] -default = false -name = "marketing_consent" -prompt = "I consent to receive marketing communications" -type = "confirm" - -# Footer with CTA -[[elements]] -align = "center" -border_bottom = true -border_top = true -content = "Click submit to complete your registration." -name = "final_cta" -title = "Thank you for your information!" -type = "cta" diff --git a/examples/02-advanced/conditional_form.ncl b/examples/02-advanced/conditional_form.ncl new file mode 100644 index 0000000..593f50d --- /dev/null +++ b/examples/02-advanced/conditional_form.ncl @@ -0,0 +1,59 @@ +{ + name = "User Account Setup", + description = "Setup account with conditional fields based on user type", + elements = [ + # First, select account type + { type = "select", name = "account_type", prompt = "What type of account do you want?", required = true, options = [ + { value = "Personal", label = "Personal - Individual use" }, + { value = "Business", label = "Business - Company account" }, + { value = "Developer", label = "Developer - Technical team" }, + ] + }, + + # Business name is only shown if account_type == Business + { type = "text", name = "business_name", prompt = "Enter your business name", required = true, placeholder = "Acme Corporation", when = "account_type == Business" }, + { type = "text", name = "business_registration", prompt = "Business registration number", placeholder = "123-456-789", when = "account_type == Business" }, + + # Developer specific fields + { type = "text", name = "github_username", prompt = "GitHub username (optional)", when = "account_type == Developer" }, + { type = "select", name = "preferred_language", prompt = "Preferred programming language", when = "account_type == Developer", options = [ + { value = "Rust", label = "🦀 Rust - Systems programming" }, + { value = "Python", label = "🐍 Python - Data & scripting" }, + { value = "Go", label = "🐹 Go - Cloud native" }, + { value = "Java", label = "☕ Java - Enterprise" }, + { value = "JavaScript", label = "🟨 JavaScript - Web development" }, + ] + }, + + # Email (required for all) + { type = "text", name = "email", prompt = "Email address", required = true, placeholder = "user@example.com" }, + + # Enable 2FA + { type = "confirm", name = "enable_2fa", prompt = "Enable two-factor authentication?", required = true, default = true }, + + # 2FA method only if enabled + { type = "select", name = "2fa_method", prompt = "Choose 2FA method", required = true, when = "enable_2fa == true", options = [ + { value = "TOTP", label = "🔐 TOTP - Authenticator app" }, + { value = "SMS", label = "📱 SMS - Text message" }, + { value = "Email", label = "📧 Email" }, + ] + }, + + # Phone number for SMS 2FA + { type = "text", name = "phone_number", prompt = "Phone number (for SMS 2FA)", required = true, placeholder = "+1234567890", when = "2fa_method == SMS" }, + + # Newsletter subscription + { type = "confirm", name = "subscribe_newsletter", prompt = "Subscribe to our newsletter?", default = false }, + + # Newsletter frequency (only if subscribed) + { type = "select", name = "newsletter_frequency", prompt = "How often would you like to receive newsletters?", required = true, when = "subscribe_newsletter == true", options = [ + { value = "Weekly", label = "📬 Weekly - Every 7 days" }, + { value = "Monthly", label = "📅 Monthly - Once per month" }, + { value = "Quarterly", label = "📊 Quarterly - Every 3 months" }, + ] + }, + + # Terms and conditions (always required) + { type = "confirm", name = "agree_terms", prompt = "I agree to the terms and conditions *", required = true, default = false }, + ], +} diff --git a/examples/02-advanced/conditional_form.toml b/examples/02-advanced/conditional_form.toml deleted file mode 100644 index 3b6f186..0000000 --- a/examples/02-advanced/conditional_form.toml +++ /dev/null @@ -1,117 +0,0 @@ -description = "Setup account with conditional fields based on user type" -name = "User Account Setup" - -# First, select account type -[[elements]] -name = "account_type" -options = [ - { value = "Personal", label = "Personal - Individual use" }, - { value = "Business", label = "Business - Company account" }, - { value = "Developer", label = "Developer - Technical team" }, -] -prompt = "What type of account do you want?" -required = true -type = "select" - -# Business name is only shown if account_type == Business -[[elements]] -name = "business_name" -placeholder = "Acme Corporation" -prompt = "Enter your business name" -required = true -type = "text" -when = "account_type == Business" - -# Business registration is only needed for Business -[[elements]] -name = "business_registration" -placeholder = "123-456-789" -prompt = "Business registration number" -type = "text" -when = "account_type == Business" - -# Developer specific fields -[[elements]] -name = "github_username" -prompt = "GitHub username (optional)" -type = "text" -when = "account_type == Developer" - -[[elements]] -name = "preferred_language" -options = [ - { value = "Rust", label = "🦀 Rust - Systems programming" }, - { value = "Python", label = "🐍 Python - Data & scripting" }, - { value = "Go", label = "🐹 Go - Cloud native" }, - { value = "Java", label = "☕ Java - Enterprise" }, - { value = "JavaScript", label = "🟨 JavaScript - Web development" }, -] -prompt = "Preferred programming language" -type = "select" -when = "account_type == Developer" - -# Email (required for all) -[[elements]] -name = "email" -placeholder = "user@example.com" -prompt = "Email address" -required = true -type = "text" - -# Enable 2FA -[[elements]] -default = true -name = "enable_2fa" -prompt = "Enable two-factor authentication?" -required = true -type = "confirm" - -# 2FA method only if enabled -[[elements]] -name = "2fa_method" -options = [ - { value = "TOTP", label = "🔐 TOTP - Authenticator app" }, - { value = "SMS", label = "📱 SMS - Text message" }, - { value = "Email", label = "📧 Email" }, -] -prompt = "Choose 2FA method" -required = true -type = "select" -when = "enable_2fa == true" - -# Phone number for SMS 2FA -[[elements]] -name = "phone_number" -placeholder = "+1234567890" -prompt = "Phone number (for SMS 2FA)" -required = true -type = "text" -when = "2fa_method == SMS" - -# Newsletter subscription -[[elements]] -default = false -name = "subscribe_newsletter" -prompt = "Subscribe to our newsletter?" -type = "confirm" - -# Newsletter frequency (only if subscribed) -[[elements]] -name = "newsletter_frequency" -options = [ - { value = "Weekly", label = "📬 Weekly - Every 7 days" }, - { value = "Monthly", label = "📅 Monthly - Once per month" }, - { value = "Quarterly", label = "📊 Quarterly - Every 3 months" }, -] -prompt = "How often would you like to receive newsletters?" -required = true -type = "select" -when = "subscribe_newsletter == true" - -# Terms and conditions (always required) -[[elements]] -default = false -name = "agree_terms" -prompt = "I agree to the terms and conditions *" -required = true -type = "confirm" diff --git a/examples/02-advanced/conditional_sections.ncl b/examples/02-advanced/conditional_sections.ncl new file mode 100644 index 0000000..27c0693 --- /dev/null +++ b/examples/02-advanced/conditional_sections.ncl @@ -0,0 +1,75 @@ +{ + name = "Dynamic Section Management", + description = "Form with sections that appear/disappear based on selections", + elements = [ + # Main header (always visible) + { type = "header", name = "main_header", title = "✨ Dynamic Form with Conditional Sections", align = "center", border_top = true, border_bottom = true }, + + # Instructions (always visible) + { type = "section", name = "instructions", content = "Select your preferences below. Additional sections will appear based on your choices.", margin_left = 2 }, + + # Account type selection + { type = "select", name = "account_type", prompt = "What type of account do you need?", required = true, options = [ + { value = "Personal", label = "Personal - Individual use" }, + { value = "Business", label = "Business - Small to medium teams" }, + { value = "Enterprise", label = "Enterprise - Large organizations" }, + ] + }, + + # Business section (only if account_type == Business) + { type = "section", name = "business_section_header", title = "🏢 Business Information", border_top = true, when = "account_type == Business" }, + { type = "text", name = "company_name", prompt = "Company Name", required = true, when = "account_type == Business" }, + { type = "select", name = "company_size", prompt = "Company Size", when = "account_type == Business", options = [ + { value = "1-10", label = "1-10 - Startup" }, + { value = "11-50", label = "11-50 - Small business" }, + { value = "51-200", label = "51-200 - Growth stage" }, + { value = "200+", label = "200+ - Enterprise scale" }, + ] + }, + + # Enterprise section (only if account_type == Enterprise) + { type = "section", name = "enterprise_section_header", title = "🏛️ Enterprise Setup", border_top = true, when = "account_type == Enterprise" }, + { type = "section", name = "enterprise_warning", content = "⚠️ Enterprise accounts require additional verification and support setup.", when = "account_type == Enterprise" }, + { type = "text", name = "enterprise_contact", prompt = "Enterprise Account Manager Email", required = true, when = "account_type == Enterprise" }, + + # Infrastructure selection (visible for Business) + { type = "section", name = "infrastructure_header", title = "🔧 Infrastructure Preferences", border_top = true, when = "account_type == Business" }, + { type = "section", name = "infrastructure_header_enterprise", title = "🔧 Infrastructure Preferences", border_top = true, when = "account_type == Enterprise" }, + { type = "select", name = "hosting_preference", prompt = "Preferred Hosting", when = "account_type == Business", options = [ + { value = "Cloud", label = "☁️ Cloud - AWS/Azure/GCP" }, + { value = "On-Premise", label = "🏢 On-Premise - Your data center" }, + { value = "Hybrid", label = "🔀 Hybrid - Mix of both" }, + ] + }, + { type = "select", name = "hosting_preference_enterprise", prompt = "Preferred Hosting", when = "account_type == Enterprise", options = [ + { value = "Cloud", label = "☁️ Cloud - AWS/Azure/GCP" }, + { value = "On-Premise", label = "🏢 On-Premise - Your data center" }, + { value = "Hybrid", label = "🔀 Hybrid - Mix of both" }, + { value = "Multi-Cloud", label = "🌐 Multi-Cloud - Multiple providers" }, + ] + }, + + # Support level selection + { type = "section", name = "support_header", title = "💬 Support Options", border_top = true, margin_left = 0 }, + { type = "select", name = "support_level", prompt = "Support Level", required = true, options = [ + { value = "Community", label = "👥 Community - Free community support" }, + { value = "Basic", label = "🛠️ Basic - Email support" }, + { value = "Premium", label = "⭐ Premium - 24/7 phone & email" }, + { value = "Enterprise", label = "🏛️ Enterprise - Dedicated team" }, + ] + }, + + # Premium support details + { type = "section", name = "premium_support_info", title = "⭐ Premium Support Includes", content = "✓ 24/7 Phone Support\n✓ Dedicated Account Manager\n✓ Priority Queue\n✓ SLA Guarantees", border_top = true, when = "support_level == Premium" }, + + # Enterprise support details + { type = "section", name = "enterprise_support_info", title = "⭐⭐ Enterprise Support Includes", content = "✓ 24/7 Dedicated Support Line\n✓ Dedicated Technical Team\n✓ Custom SLA\n✓ Onsite Support Available", border_top = true, when = "support_level == Enterprise" }, + + { type = "text", name = "support_email", prompt = "Support Contact Email", required = true, when = "support_level == Premium" }, + { type = "text", name = "support_email_enterprise", prompt = "Support Contact Email", required = true, when = "support_level == Enterprise" }, + + # Final section + { type = "section", name = "final_section", title = "✅ Ready to Complete", content = "Review your selections above and click submit to create your account.", align = "center", border_top = true, border_bottom = true }, + { type = "confirm", name = "agree_terms", prompt = "I agree to the terms and conditions", required = true }, + ], +} diff --git a/examples/02-advanced/conditional_sections.toml b/examples/02-advanced/conditional_sections.toml deleted file mode 100644 index b7f1f82..0000000 --- a/examples/02-advanced/conditional_sections.toml +++ /dev/null @@ -1,184 +0,0 @@ -description = "Form with sections that appear/disappear based on selections" -name = "Dynamic Section Management" - -# Main header (always visible) -[[elements]] -align = "center" -border_bottom = true -border_top = true -name = "main_header" -title = "✨ Dynamic Form with Conditional Sections" -type = "header" - -# Instructions (always visible) -[[elements]] -content = "Select your preferences below. Additional sections will appear based on your choices." -margin_left = 2 -name = "instructions" -type = "section" - -# Account type selection -[[elements]] -name = "account_type" -options = [ - { value = "Personal", label = "Personal - Individual use" }, - { value = "Business", label = "Business - Small to medium teams" }, - { value = "Enterprise", label = "Enterprise - Large organizations" }, -] -prompt = "What type of account do you need?" -required = true -type = "select" - -# Business section (only if account_type == Business) -[[elements]] -border_top = true -name = "business_section_header" -title = "🏢 Business Information" -type = "section" -when = "account_type == Business" - -[[elements]] -name = "company_name" -prompt = "Company Name" -required = true -type = "text" -when = "account_type == Business" - -[[elements]] -name = "company_size" -options = [ - { value = "1-10", label = "1-10 - Startup" }, - { value = "11-50", label = "11-50 - Small business" }, - { value = "51-200", label = "51-200 - Growth stage" }, - { value = "200+", label = "200+ - Enterprise scale" }, -] -prompt = "Company Size" -type = "select" -when = "account_type == Business" - -# Enterprise section (only if account_type == Enterprise) -[[elements]] -border_top = true -name = "enterprise_section_header" -title = "🏛️ Enterprise Setup" -type = "section" -when = "account_type == Enterprise" - -[[elements]] -content = "⚠️ Enterprise accounts require additional verification and support setup." -name = "enterprise_warning" -type = "section" -when = "account_type == Enterprise" - -[[elements]] -name = "enterprise_contact" -prompt = "Enterprise Account Manager Email" -required = true -type = "text" -when = "account_type == Enterprise" - -# Infrastructure selection (visible for Business & Enterprise) -[[elements]] -border_top = true -name = "infrastructure_header" -title = "🔧 Infrastructure Preferences" -type = "section" -when = "account_type == Business" - -[[elements]] -border_top = true -name = "infrastructure_header_enterprise" -title = "🔧 Infrastructure Preferences" -type = "section" -when = "account_type == Enterprise" - -[[elements]] -name = "hosting_preference" -options = [ - { value = "Cloud", label = "☁️ Cloud - AWS/Azure/GCP" }, - { value = "On-Premise", label = "🏢 On-Premise - Your data center" }, - { value = "Hybrid", label = "🔀 Hybrid - Mix of both" }, -] -prompt = "Preferred Hosting" -type = "select" -when = "account_type == Business" - -[[elements]] -name = "hosting_preference_enterprise" -options = [ - { value = "Cloud", label = "☁️ Cloud - AWS/Azure/GCP" }, - { value = "On-Premise", label = "🏢 On-Premise - Your data center" }, - { value = "Hybrid", label = "🔀 Hybrid - Mix of both" }, - { value = "Multi-Cloud", label = "🌐 Multi-Cloud - Multiple providers" }, -] -prompt = "Preferred Hosting" -type = "select" -when = "account_type == Enterprise" - -# Support level selection -[[elements]] -border_top = true -margin_left = 0 -name = "support_header" -title = "💬 Support Options" -type = "section" - -[[elements]] -name = "support_level" -options = [ - { value = "Community", label = "👥 Community - Free community support" }, - { value = "Basic", label = "🛠️ Basic - Email support" }, - { value = "Premium", label = "⭐ Premium - 24/7 phone & email" }, - { value = "Enterprise", label = "🏛️ Enterprise - Dedicated team" }, -] -prompt = "Support Level" -required = true -type = "select" - -# Premium support details (only if support_level == Premium) -[[elements]] -border_top = true -content = "✓ 24/7 Phone Support\n✓ Dedicated Account Manager\n✓ Priority Queue\n✓ SLA Guarantees" -name = "premium_support_info" -title = "⭐ Premium Support Includes" -type = "section" -when = "support_level == Premium" - -# Enterprise support details (only if support_level == Enterprise) -[[elements]] -border_top = true -content = "✓ 24/7 Dedicated Support Line\n✓ Dedicated Technical Team\n✓ Custom SLA\n✓ Onsite Support Available" -name = "enterprise_support_info" -title = "⭐⭐ Enterprise Support Includes" -type = "section" -when = "support_level == Enterprise" - -[[elements]] -name = "support_email" -prompt = "Support Contact Email" -required = true -type = "text" -when = "support_level == Premium" - -[[elements]] -name = "support_email_enterprise" -prompt = "Support Contact Email" -required = true -type = "text" -when = "support_level == Enterprise" - -# Final section -[[elements]] -align = "center" -border_bottom = true -border_top = true -content = "Review your selections above and click submit to create your account." -name = "final_section" -title = "✅ Ready to Complete" -type = "section" - -[[elements]] -name = "agree_terms" -prompt = "I agree to the terms and conditions" -required = true -type = "confirm" diff --git a/examples/02-advanced/display_items_demo.ncl b/examples/02-advanced/display_items_demo.ncl new file mode 100644 index 0000000..f30f621 --- /dev/null +++ b/examples/02-advanced/display_items_demo.ncl @@ -0,0 +1,35 @@ +{ + name = "Display Items Showcase", + description = "Demonstrates all display item types and attributes", + elements = [ + # Basic Header + { type = "header", name = "header_basic", title = "Basic Header" }, + + # Header with borders + { type = "header", name = "header_bordered", title = "Header with Borders", border_top = true, border_bottom = true }, + + # Header centered + { type = "header", name = "header_centered", title = "Centered Header", align = "center", border_top = true, border_bottom = true }, + + # Simple section with content + { type = "section", name = "info_section", content = "This is a simple information section. It contains text that guides the user." }, + + # Section with borders + { type = "section", name = "important_info", title = "Important Information", content = "This section has both title and content with a border on top.", border_top = true }, + + # Multi-line content section + { type = "section", name = "multiline_section", title = "Features", content = "✓ Feature One\n✓ Feature Two\n✓ Feature Three\n✓ Feature Four", border_bottom = true }, + + # Example field + { type = "text", name = "example_field", prompt = "Enter something" }, + + # Left-aligned section + { type = "section", name = "instructions", content = "Please follow the instructions above.", margin_left = 2 }, + + # Call To Action + { type = "cta", name = "cta_submit", title = "Ready?", content = "Click submit when you're done!", align = "center", border_top = true, border_bottom = true }, + + # Right-aligned footer + { type = "footer", name = "footer", content = "© 2024 Your Company. All rights reserved.", align = "right", margin_left = 0 }, + ], +} diff --git a/examples/02-advanced/display_items_demo.toml b/examples/02-advanced/display_items_demo.toml deleted file mode 100644 index 7326b85..0000000 --- a/examples/02-advanced/display_items_demo.toml +++ /dev/null @@ -1,78 +0,0 @@ -description = "Demonstrates all display item types and attributes" -name = "Display Items Showcase" - -# Basic Header -[[elements]] -name = "header_basic" -title = "Basic Header" -type = "header" - -# Header with borders -[[elements]] -border_bottom = true -border_top = true -name = "header_bordered" -title = "Header with Borders" -type = "header" - -# Header centered -[[elements]] -align = "center" -border_bottom = true -border_top = true -name = "header_centered" -title = "Centered Header" -type = "header" - -# Simple section with content -[[elements]] -content = "This is a simple information section. It contains text that guides the user." -name = "info_section" -type = "section" - -# Section with borders -[[elements]] -border_top = true -content = "This section has both title and content with a border on top." -name = "important_info" -title = "Important Information" -type = "section" - -# Multi-line content section -[[elements]] -border_bottom = true -content = "✓ Feature One\n✓ Feature Two\n✓ Feature Three\n✓ Feature Four" -name = "multiline_section" -title = "Features" -type = "section" - -# Example field -[[elements]] -name = "example_field" -prompt = "Enter something" -type = "text" - -# Left-aligned section -[[elements]] -content = "Please follow the instructions above." -margin_left = 2 -name = "instructions" -type = "section" - -# Call To Action -[[elements]] -align = "center" -border_bottom = true -border_top = true -content = "Click submit when you're done!" -name = "cta_submit" -title = "Ready?" -type = "cta" - -# Right-aligned footer -[[elements]] -align = "right" -content = "© 2024 Your Company. All rights reserved." -margin_left = 0 -name = "footer" -type = "footer" diff --git a/examples/02-multiselect-display-modes/form.ncl b/examples/02-multiselect-display-modes/form.ncl new file mode 100644 index 0000000..1118de7 --- /dev/null +++ b/examples/02-multiselect-display-modes/form.ncl @@ -0,0 +1,49 @@ +{ + name = "MultiSelect Display Modes Demo", + description = "Demonstrates different display modes and features for multiselect fields", + elements = [ + # List mode (default) - vertical checkboxes + { type = "multiselect", name = "features_list", prompt = "Features (List mode - default)", default = "logging", help = "Vertical checkbox list", options = [ + { value = "logging", label = "📝 Logging" }, + { value = "metrics", label = "📊 Metrics" }, + { value = "tracing", label = "🔍 Tracing" }, + { value = "profiling", label = "⚡ Profiling" }, + ] + }, + + # Grid mode - responsive grid layout + { type = "multiselect", name = "languages_grid", prompt = "Programming Languages (Grid mode)", display_mode = "grid", searchable = true, default = "rust,python", help = "Responsive grid with icons", options = [ + { value = "rust", label = "🦀 Rust" }, + { value = "python", label = "🐍 Python" }, + { value = "javascript", label = "📜 JavaScript" }, + { value = "go", label = "🐹 Go" }, + { value = "java", label = "☕ Java" }, + { value = "csharp", label = "🔵 C#" }, + ] + }, + + # Dropdown mode - native select multiple with search + { type = "multiselect", name = "frameworks", prompt = "Web Frameworks", display_mode = "dropdown", searchable = true, required = true, min_selected = 1, max_selected = 3, help = "Use dropdown mode for 10+ options", options = [ + { value = "react", label = "React" }, + { value = "vue", label = "Vue" }, + { value = "angular", label = "Angular" }, + { value = "svelte", label = "Svelte" }, + { value = "nextjs", label = "Next.js" }, + { value = "nuxt", label = "Nuxt" }, + { value = "astro", label = "Astro" }, + { value = "remix", label = "Remix" }, + { value = "gatsby", label = "Gatsby" }, + { value = "qwik", label = "Qwik" }, + ] + }, + + # Example with min/max validation + { type = "multiselect", name = "permissions", prompt = "User Permissions", display_mode = "grid", min_selected = 1, max_selected = 3, default = "read", help = "Select between 1 and 3 permissions", options = [ + { value = "read", label = "📖 Read" }, + { value = "write", label = "✏️ Write" }, + { value = "delete", label = "🗑️ Delete" }, + { value = "admin", label = "🔑 Admin" }, + ] + }, + ], +} diff --git a/examples/02-multiselect-display-modes/form.toml b/examples/02-multiselect-display-modes/form.toml deleted file mode 100644 index e60944c..0000000 --- a/examples/02-multiselect-display-modes/form.toml +++ /dev/null @@ -1,75 +0,0 @@ -description = "Demonstrates different display modes and features for multiselect fields" -name = "MultiSelect Display Modes Demo" - -# List mode (default) - vertical checkboxes -[[elements]] -default = "logging" -help = "Vertical checkbox list" -name = "features_list" -options = [ - { value = "logging", label = "📝 Logging" }, - { value = "metrics", label = "📊 Metrics" }, - { value = "tracing", label = "🔍 Tracing" }, - { value = "profiling", label = "⚡ Profiling" }, -] -prompt = "Features (List mode - default)" -type = "multiselect" - -# Grid mode - responsive grid layout -[[elements]] -default = "rust,python" -display_mode = "grid" -help = "Responsive grid with icons" -name = "languages_grid" -options = [ - { value = "rust", label = "🦀 Rust" }, - { value = "python", label = "🐍 Python" }, - { value = "javascript", label = "📜 JavaScript" }, - { value = "go", label = "🐹 Go" }, - { value = "java", label = "☕ Java" }, - { value = "csharp", label = "🔵 C#" }, -] -prompt = "Programming Languages (Grid mode)" -searchable = true -type = "multiselect" - -# Dropdown mode - native select multiple with search -[[elements]] -display_mode = "dropdown" -help = "Use dropdown mode for 10+ options" -max_selected = 3 -min_selected = 1 -name = "frameworks" -options = [ - { value = "react", label = "React" }, - { value = "vue", label = "Vue" }, - { value = "angular", label = "Angular" }, - { value = "svelte", label = "Svelte" }, - { value = "nextjs", label = "Next.js" }, - { value = "nuxt", label = "Nuxt" }, - { value = "astro", label = "Astro" }, - { value = "remix", label = "Remix" }, - { value = "gatsby", label = "Gatsby" }, - { value = "qwik", label = "Qwik" }, -] -prompt = "Web Frameworks" -required = true -searchable = true -type = "multiselect" - -# Example with min/max validation -[[elements]] -default = "read" -display_mode = "grid" -help = "Select between 1 and 3 permissions" -max_selected = 3 -min_selected = 1 -name = "permissions" -options = [ - { value = "read", label = "📖 Read" }, - { value = "write", label = "✏️ Write" }, - { value = "delete", label = "🗑️ Delete" }, - { value = "admin", label = "🔑 Admin" }, -] -prompt = "User Permissions" -type = "multiselect" diff --git a/examples/03-styling/custom_border_form.ncl b/examples/03-styling/custom_border_form.ncl new file mode 100644 index 0000000..e09fa0f --- /dev/null +++ b/examples/03-styling/custom_border_form.ncl @@ -0,0 +1,13 @@ +let header = import "../05-fragments/header.ncl" in +let custom_border = import "../05-fragments/custom_border_section.ncl" in +{ + name = "Custom Border Demo Form", + description = "Demonstrates custom border_top_char, border_top_len, border_bottom_char, border_bottom_len", + elements = + header.elements + @ custom_border.elements + @ [ + { type = "text", name = "project_name", prompt = "Project name", required = true }, + { type = "section", name = "footer", title = "✓ Complete!", content = "Thank you for filling out this form", border_top = true, border_top_char = "─", border_top_l = "┌", border_top_len = 40, border_top_r = "┐", border_bottom = true, border_bottom_char = "─", border_bottom_l = "└", border_bottom_len = 40, border_bottom_r = "┘", margin_left = 2 }, + ], +} diff --git a/examples/03-styling/custom_border_form.toml b/examples/03-styling/custom_border_form.toml deleted file mode 100644 index 11be7e0..0000000 --- a/examples/03-styling/custom_border_form.toml +++ /dev/null @@ -1,43 +0,0 @@ -description = "Demonstrates custom border_top_char, border_top_len, border_bottom_char, border_bottom_len" -name = "Custom Border Demo Form" - -# Standard border with default ═ -[[elements]] -includes = ["fragments/header.toml"] -name = "header_group" -order = 1 -type = "group" - -# Custom border with different top and bottom styles -[[elements]] -includes = ["fragments/custom_border_section.toml"] -name = "custom_group" -order = 2 -type = "group" - -# Simple field -[[elements]] -name = "project_name" -order = 3 -prompt = "Project name" -required = true -type = "text" - -# Different border styles with corners and margin -[[elements]] -border_bottom = true -border_bottom_char = "─" -border_bottom_l = "└" -border_bottom_len = 40 -border_bottom_r = "┘" -border_top = true -border_top_char = "─" -border_top_l = "┌" -border_top_len = 40 -border_top_r = "┐" -content = "Thank you for filling out this form" -margin_left = 2 -name = "footer" -order = 4 -title = "✓ Complete!" -type = "section" diff --git a/examples/03-styling/fancy_borders_form.ncl b/examples/03-styling/fancy_borders_form.ncl new file mode 100644 index 0000000..e255b35 --- /dev/null +++ b/examples/03-styling/fancy_borders_form.ncl @@ -0,0 +1,20 @@ +let fancy_border = import "../05-fragments/fancy_border_section.ncl" in +{ + name = "Fancy Borders Demo", + description = "Demonstrates custom corner characters with fancy Unicode borders", + elements = + [ + # Fancy bordered header - border at margin 0, content at margin 2 + { type = "section", name = "fancy_header", title = "✨ Welcome to Fancy Forms ✨", border_top = true, border_top_char = "─", border_top_l = "╭", border_top_len = 35, border_top_r = "╮", border_bottom = true, border_bottom_char = "─", border_bottom_l = "╰", border_bottom_len = 35, border_bottom_r = "╯", border_margin_left = 0, content_margin_left = 2 }, + ] + @ fancy_border.elements + @ [ + { type = "select", name = "favorite_style", prompt = "Your favorite border style", required = true, options = [ + { value = "Fancy Unicode", label = "✨ Fancy Unicode - Modern look" }, + { value = "Simple ASCII", label = "📝 Simple ASCII - Classic style" }, + { value = "Mixed Styles", label = "🎨 Mixed Styles - Custom borders" }, + ] + }, + { type = "section", name = "box_footer", title = "✓ All Done!", content = "Thanks for exploring fancy borders!", border_top = true, border_top_char = "─", border_top_l = "┌", border_top_len = 40, border_top_r = "┐", border_bottom = true, border_bottom_char = "─", border_bottom_l = "└", border_bottom_len = 40, border_bottom_r = "┘", border_margin_left = 0, content_margin_left = 2 }, + ], +} diff --git a/examples/03-styling/fancy_borders_form.toml b/examples/03-styling/fancy_borders_form.toml deleted file mode 100644 index c4d375f..0000000 --- a/examples/03-styling/fancy_borders_form.toml +++ /dev/null @@ -1,61 +0,0 @@ -description = "Demonstrates custom corner characters with fancy Unicode borders" -name = "Fancy Borders Demo" - -# Fancy bordered header - border at margin 0, content at margin 2 -[[elements]] -border_bottom = true -border_bottom_char = "─" -border_bottom_l = "╰" -border_bottom_len = 35 -border_bottom_r = "╯" -border_margin_left = 0 -border_top = true -border_top_char = "─" -border_top_l = "╭" -border_top_len = 35 -border_top_r = "╮" -content_margin_left = 2 -name = "fancy_header" -order = 1 -title = "✨ Welcome to Fancy Forms ✨" -type = "section" - -# Include fancy border fragment - margin settings are in the fragment items -[[elements]] -includes = ["fragments/fancy_border_section.toml"] -name = "fancy_group" -order = 2 -type = "group" - -# Simple field -[[elements]] -name = "favorite_style" -options = [ - { value = "Fancy Unicode", label = "✨ Fancy Unicode - Modern look" }, - { value = "Simple ASCII", label = "📝 Simple ASCII - Classic style" }, - { value = "Mixed Styles", label = "🎨 Mixed Styles - Custom borders" }, -] -order = 3 -prompt = "Your favorite border style" -required = true -type = "select" - -# Box border for footer - border at 0, content at 2 -[[elements]] -border_bottom = true -border_bottom_char = "─" -border_bottom_l = "└" -border_bottom_len = 40 -border_bottom_r = "┘" -border_margin_left = 0 -border_top = true -border_top_char = "─" -border_top_l = "┌" -border_top_len = 40 -border_top_r = "┐" -content = "Thanks for exploring fancy borders!" -content_margin_left = 2 -name = "box_footer" -order = 4 -title = "✓ All Done!" -type = "section" diff --git a/examples/04-backends/tui/tui_survey_form.ncl b/examples/04-backends/tui/tui_survey_form.ncl new file mode 100644 index 0000000..090112e --- /dev/null +++ b/examples/04-backends/tui/tui_survey_form.ncl @@ -0,0 +1,85 @@ +{ + name = "User Experience Survey", + description = "Comprehensive TUI survey with visual elements and grouped sections", + elements = [ + # Welcome header with fancy Unicode borders + { type = "section", name = "welcome_header", title = "📋 User Experience Survey", content = "Help us improve by sharing your feedback", border_top = true, border_top_char = "─", border_top_l = "╭", border_top_len = 45, border_top_r = "╮", border_bottom = true, border_bottom_char = "─", border_bottom_l = "╰", border_bottom_len = 45, border_bottom_r = "╯", border_margin_left = 0, content_margin_left = 2 }, + + # Personal Information section header + { type = "section_header", name = "personal_info_header", title = "👤 Personal Information", margin_left = 2 }, + + # Personal information fields + { type = "text", name = "full_name", prompt = "Full name", required = true, placeholder = "Jane Smith", group = "personal_info" }, + { type = "text", name = "email", prompt = "Email address", required = true, placeholder = "jane@example.com", group = "personal_info" }, + { type = "text", name = "phone", prompt = "Phone number (optional)", placeholder = "+1-555-0123", group = "personal_info" }, + + # Product experience section + { type = "section_header", name = "product_header", title = "⭐ Product Experience", margin_left = 2 }, + { type = "select", name = "overall_satisfaction", prompt = "Overall satisfaction with product", required = true, group = "product", options = [ + { value = "Very Unsatisfied", label = "😢 Very Unsatisfied" }, + { value = "Unsatisfied", label = "😟 Unsatisfied" }, + { value = "Neutral", label = "😐 Neutral" }, + { value = "Satisfied", label = "😊 Satisfied" }, + { value = "Very Satisfied", label = "😍 Very Satisfied" }, + ] + }, + { type = "select", name = "usage_frequency", prompt = "How often do you use this product?", required = true, group = "product", options = [ + { value = "Daily", label = "📅 Daily" }, + { value = "Weekly", label = "📊 Weekly" }, + { value = "Monthly", label = "📆 Monthly" }, + { value = "Occasionally", label = "📝 Occasionally" }, + { value = "Never", label = "🚫 Never" }, + ] + }, + { type = "multiselect", name = "features_used", prompt = "Which features do you use? (select all that apply)", page_size = 5, vim_mode = true, group = "product", options = [ + { value = "Dashboard", label = "📊 Dashboard" }, + { value = "Analytics", label = "📈 Analytics" }, + { value = "Reporting", label = "📑 Reporting" }, + { value = "API Integration", label = "🔌 API Integration" }, + { value = "Mobile App", label = "📱 Mobile App" }, + { value = "Notifications", label = "🔔 Notifications" }, + { value = "Collaboration Tools", label = "👥 Collaboration Tools" }, + ] + }, + + # Feedback section + { type = "section_header", name = "feedback_header", title = "💬 Feedback", margin_left = 2 }, + { type = "editor", name = "improvements", prompt = "What improvements would you suggest?", file_extension = "txt", prefix_text = "# Please describe desired improvements\n", group = "feedback" }, + { type = "select", name = "biggest_pain_point", prompt = "What's your biggest pain point?", required = true, group = "feedback", options = [ + { value = "Performance issues", label = "⚡ Performance issues" }, + { value = "Confusing UI/UX", label = "🎨 Confusing UI/UX" }, + { value = "Missing features", label = "❌ Missing features" }, + { value = "Documentation", label = "📖 Documentation" }, + { value = "Customer support", label = "🆘 Customer support" }, + { value = "Pricing", label = "💰 Pricing" }, + { value = "Other", label = "❓ Other" }, + ] + }, + + # Preferences section + { type = "section_header", name = "preferences_header", title = "⚙️ Preferences", margin_left = 2 }, + { type = "select", name = "contact_preference", prompt = "Preferred contact method", required = true, default = "Email", group = "preferences", options = [ + { value = "Email", label = "📧 Email" }, + { value = "Phone", label = "📞 Phone" }, + { value = "SMS", label = "💬 SMS" }, + { value = "In-app notification", label = "🔔 In-app notification" }, + { value = "No contact", label = "🚫 No contact" }, + ] + }, + { type = "confirm", name = "newsletter_opt_in", prompt = "Subscribe to our newsletter for updates?", default = false, group = "preferences" }, + { type = "confirm", name = "beta_features", prompt = "Interested in testing beta features?", default = false, group = "preferences" }, + + # Advanced options (conditional) + { type = "multiselect", name = "device_types", prompt = "Which devices do you use? (optional)", page_size = 4, vim_mode = true, when = "beta_features == true", group = "preferences", options = [ + { value = "Desktop", label = "🖥️ Desktop" }, + { value = "Laptop", label = "💻 Laptop" }, + { value = "Tablet", label = "📱 Tablet" }, + { value = "Mobile", label = "📲 Mobile Phone" }, + { value = "Smartwatch", label = "⌚ Smartwatch" }, + ] + }, + + # Closing footer + { type = "section", name = "closing_footer", title = "✓ Thank You!", content = "Your feedback helps us build better products", border_top = true, border_top_char = "─", border_top_l = "┌", border_top_len = 50, border_top_r = "┐", border_bottom = true, border_bottom_char = "─", border_bottom_l = "└", border_bottom_len = 50, border_bottom_r = "┘", border_margin_left = 0, content_margin_left = 2 }, + ], +} diff --git a/examples/04-backends/tui/tui_survey_form.toml b/examples/04-backends/tui/tui_survey_form.toml deleted file mode 100644 index f720e3f..0000000 --- a/examples/04-backends/tui/tui_survey_form.toml +++ /dev/null @@ -1,226 +0,0 @@ -description = "Comprehensive TUI survey with visual elements and grouped sections" -locale = "en-US" -name = "User Experience Survey" - -# Welcome header with fancy Unicode borders -[[items]] -border_bottom = true -border_bottom_char = "─" -border_bottom_l = "╰" -border_bottom_len = 45 -border_bottom_r = "╯" -border_margin_left = 0 -border_top = true -border_top_char = "─" -border_top_l = "╭" -border_top_len = 45 -border_top_r = "╮" -content = "Help us improve by sharing your feedback" -content_margin_left = 2 -name = "welcome_header" -order = 1 -title = "📋 User Experience Survey" -type = "section" - -# Personal Information section header -[[items]] -margin_left = 2 -name = "personal_info_header" -order = 2 -title = "👤 Personal Information" -type = "section_header" - -# Personal information fields -[[fields]] -group = "personal_info" -name = "full_name" -order = 3 -placeholder = "Jane Smith" -prompt = "Full name" -required = true -type = "text" - -[[fields]] -group = "personal_info" -name = "email" -order = 4 -placeholder = "jane@example.com" -prompt = "Email address" -required = true -type = "text" - -[[fields]] -group = "personal_info" -name = "phone" -order = 5 -placeholder = "+1-555-0123" -prompt = "Phone number (optional)" -type = "text" - -# Product experience section -[[items]] -margin_left = 2 -name = "product_header" -order = 6 -title = "⭐ Product Experience" -type = "section_header" - -[[fields]] -group = "product" -name = "overall_satisfaction" -options = [ - { value = "Very Unsatisfied", label = "😢 Very Unsatisfied" }, - { value = "Unsatisfied", label = "😟 Unsatisfied" }, - { value = "Neutral", label = "😐 Neutral" }, - { value = "Satisfied", label = "😊 Satisfied" }, - { value = "Very Satisfied", label = "😍 Very Satisfied" }, -] -order = 7 -prompt = "Overall satisfaction with product" -required = true -type = "select" - -[[fields]] -group = "product" -name = "usage_frequency" -options = [ - { value = "Daily", label = "📅 Daily" }, - { value = "Weekly", label = "📊 Weekly" }, - { value = "Monthly", label = "📆 Monthly" }, - { value = "Occasionally", label = "📝 Occasionally" }, - { value = "Never", label = "🚫 Never" }, -] -order = 8 -prompt = "How often do you use this product?" -required = true -type = "select" - -[[fields]] -group = "product" -name = "features_used" -options = [ - { value = "Dashboard", label = "📊 Dashboard" }, - { value = "Analytics", label = "📈 Analytics" }, - { value = "Reporting", label = "📑 Reporting" }, - { value = "API Integration", label = "🔌 API Integration" }, - { value = "Mobile App", label = "📱 Mobile App" }, - { value = "Notifications", label = "🔔 Notifications" }, - { value = "Collaboration Tools", label = "👥 Collaboration Tools" }, -] -order = 9 -page_size = 5 -prompt = "Which features do you use? (select all that apply)" -type = "multiselect" -vim_mode = true - -# Feedback section -[[items]] -margin_left = 2 -name = "feedback_header" -order = 10 -title = "💬 Feedback" -type = "section_header" - -[[fields]] -file_extension = "txt" -group = "feedback" -name = "improvements" -order = 11 -prefix_text = "# Please describe desired improvements\n" -prompt = "What improvements would you suggest?" -type = "editor" - -[[fields]] -group = "feedback" -name = "biggest_pain_point" -options = [ - { value = "Performance issues", label = "⚡ Performance issues" }, - { value = "Confusing UI/UX", label = "🎨 Confusing UI/UX" }, - { value = "Missing features", label = "❌ Missing features" }, - { value = "Documentation", label = "📖 Documentation" }, - { value = "Customer support", label = "🆘 Customer support" }, - { value = "Pricing", label = "💰 Pricing" }, - { value = "Other", label = "❓ Other" }, -] -order = 12 -prompt = "What's your biggest pain point?" -required = true -type = "select" - -# Preferences section -[[items]] -margin_left = 2 -name = "preferences_header" -order = 13 -title = "⚙️ Preferences" -type = "section_header" - -[[fields]] -default = "Email" -group = "preferences" -name = "contact_preference" -options = [ - { value = "Email", label = "📧 Email" }, - { value = "Phone", label = "📞 Phone" }, - { value = "SMS", label = "💬 SMS" }, - { value = "In-app notification", label = "🔔 In-app notification" }, - { value = "No contact", label = "🚫 No contact" }, -] -order = 14 -prompt = "Preferred contact method" -required = true -type = "select" - -[[fields]] -default = "false" -group = "preferences" -name = "newsletter_opt_in" -order = 15 -prompt = "Subscribe to our newsletter for updates?" -type = "confirm" - -[[fields]] -default = "false" -group = "preferences" -name = "beta_features" -order = 16 -prompt = "Interested in testing beta features?" -type = "confirm" - -# Advanced options (conditional) -[[fields]] -group = "preferences" -name = "device_types" -options = [ - { value = "Desktop", label = "🖥️ Desktop" }, - { value = "Laptop", label = "💻 Laptop" }, - { value = "Tablet", label = "📱 Tablet" }, - { value = "Mobile", label = "📲 Mobile Phone" }, - { value = "Smartwatch", label = "⌚ Smartwatch" }, -] -order = 17 -page_size = 4 -prompt = "Which devices do you use? (optional)" -type = "multiselect" -vim_mode = true -when = "beta_features == true" - -# Closing footer -[[items]] -border_bottom = true -border_bottom_char = "─" -border_bottom_l = "└" -border_bottom_len = 50 -border_bottom_r = "┘" -border_margin_left = 0 -border_top = true -border_top_char = "─" -border_top_l = "┌" -border_top_len = 50 -border_top_r = "┐" -content = "Your feedback helps us build better products" -content_margin_left = 2 -name = "closing_footer" -order = 100 -title = "✓ Thank You!" -type = "section" diff --git a/examples/04-backends/web/web_registration_form.ncl b/examples/04-backends/web/web_registration_form.ncl new file mode 100644 index 0000000..3545f41 --- /dev/null +++ b/examples/04-backends/web/web_registration_form.ncl @@ -0,0 +1,70 @@ +{ + name = "User Registration", + description = "Web-optimized registration form with account setup and preferences", + elements = [ + # Account Credentials Section + { type = "section_header", name = "account_section", title = "Create Your Account" }, + { type = "text", name = "username", prompt = "Username", required = true, placeholder = "Choose a unique username", group = "account" }, + { type = "text", name = "email", prompt = "Email Address", required = true, placeholder = "your.email@example.com", group = "account" }, + { type = "password", name = "password", prompt = "Password", required = true, group = "account" }, + { type = "password", name = "confirm_password", prompt = "Confirm Password", required = true, group = "account" }, + + # Profile Information Section + { type = "section_header", name = "profile_section", title = "Profile Information" }, + { type = "text", name = "first_name", prompt = "First Name", required = true, placeholder = "Jane", group = "profile" }, + { type = "text", name = "last_name", prompt = "Last Name", required = true, placeholder = "Smith", group = "profile" }, + { type = "text", name = "display_name", prompt = "Display Name (optional)", placeholder = "How you'll appear to other users", group = "profile" }, + { type = "date", name = "birth_date", prompt = "Date of Birth", min_date = "1950-01-01", max_date = "2006-12-31", week_start = "Sun", group = "profile" }, + { type = "editor", name = "bio", prompt = "About Me", file_extension = "md", group = "profile" }, + + # Account Settings Section + { type = "section_header", name = "settings_section", title = "Account Settings" }, + { type = "select", name = "account_type", prompt = "Account Type", required = true, default = "Personal", group = "settings", options = [ + { value = "Personal", label = "👤 Personal - Individual use" }, + { value = "Business", label = "💼 Business - Small team" }, + { value = "Organization", label = "🏢 Organization - Large team" }, + ] + }, + { type = "select", name = "subscription_plan", prompt = "Subscription Plan", required = true, default = "Free", group = "settings", options = [ + { value = "Free", label = "🆓 Free - Essential features" }, + { value = "Pro", label = "⭐ Pro - Advanced features" }, + { value = "Enterprise", label = "🏛️ Enterprise - Full suite" }, + ] + }, + { type = "text", name = "company_name", prompt = "Company Name", required = true, placeholder = "Your Organization", when = "account_type != Personal", group = "settings" }, + { type = "text", name = "company_url", prompt = "Company Website", placeholder = "https://example.com", when = "account_type != Personal", group = "settings" }, + { type = "confirm", name = "api_access", prompt = "Enable API Access?", default = false, when = "subscription_plan != Free", group = "settings" }, + { type = "custom", name = "team_members_count", prompt = "How many team members?", custom_type = "i32", default = "1", when = "subscription_plan == Enterprise", group = "settings" }, + + # Preferences Section + { type = "section_header", name = "preferences_section", title = "Communication Preferences" }, + { type = "confirm", name = "notifications_email", prompt = "Send me email notifications?", default = true, group = "preferences" }, + { type = "confirm", name = "marketing_emails", prompt = "Subscribe to marketing emails and updates?", default = false, group = "preferences" }, + { type = "select", name = "notification_frequency", prompt = "Email notification frequency", default = "Daily Digest", when = "notifications_email == true", group = "preferences", options = [ + { value = "Immediate", label = "⚡ Immediate" }, + { value = "Daily Digest", label = "📅 Daily Digest" }, + { value = "Weekly Summary", label = "📊 Weekly Summary" }, + { value = "Monthly Summary", label = "📈 Monthly Summary" }, + { value = "Never", label = "🚫 Never" }, + ] + }, + { type = "multiselect", name = "interests", prompt = "Topics you're interested in:", page_size = 4, group = "preferences", options = [ + { value = "Product Updates", label = "🚀 Product Updates" }, + { value = "Security Alerts", label = "🔒 Security Alerts" }, + { value = "Performance Tips", label = "⚡ Performance Tips" }, + { value = "Community News", label = "👥 Community News" }, + { value = "Educational Content", label = "📚 Educational Content" }, + { value = "Exclusive Offers", label = "🎁 Exclusive Offers" }, + ] + }, + + # Privacy & Legal Section + { type = "section_header", name = "legal_section", title = "Privacy & Legal" }, + { type = "confirm", name = "terms_accepted", prompt = "I agree to the Terms of Service", required = true, group = "legal" }, + { type = "confirm", name = "privacy_policy_accepted", prompt = "I have read and accept the Privacy Policy", required = true, group = "legal" }, + { type = "confirm", name = "data_processing", prompt = "I consent to data processing as described", required = true, group = "legal" }, + { type = "confirm", name = "enable_secondary_contact", prompt = "Add secondary contact information?", default = false, group = "legal" }, + { type = "text", name = "secondary_email", prompt = "Secondary Email Address", placeholder = "alternative@example.com", when = "enable_secondary_contact == true", group = "legal" }, + { type = "text", name = "recovery_phone", prompt = "Recovery Phone Number", placeholder = "+1-555-0123", when = "enable_secondary_contact == true", group = "legal" }, + ], +} diff --git a/examples/04-backends/web/web_registration_form.toml b/examples/04-backends/web/web_registration_form.toml deleted file mode 100644 index d088248..0000000 --- a/examples/04-backends/web/web_registration_form.toml +++ /dev/null @@ -1,283 +0,0 @@ -description = "Web-optimized registration form with account setup and preferences" -locale = "en-US" -name = "User Registration" - -# Account Credentials Section -[[items]] -name = "account_section" -order = 1 -title = "Create Your Account" -type = "section_header" - -[[fields]] -group = "account" -name = "username" -order = 2 -placeholder = "Choose a unique username" -prompt = "Username" -required = true -type = "text" - -[[fields]] -group = "account" -name = "email" -order = 3 -placeholder = "your.email@example.com" -prompt = "Email Address" -required = true -type = "text" - -[[fields]] -group = "account" -name = "password" -order = 4 -prompt = "Password" -required = true -type = "password" - -[[fields]] -group = "account" -name = "confirm_password" -order = 5 -prompt = "Confirm Password" -required = true -type = "password" - -# Profile Information Section -[[items]] -name = "profile_section" -order = 10 -title = "Profile Information" -type = "section_header" - -[[fields]] -group = "profile" -name = "first_name" -order = 11 -placeholder = "Jane" -prompt = "First Name" -required = true -type = "text" - -[[fields]] -group = "profile" -name = "last_name" -order = 12 -placeholder = "Smith" -prompt = "Last Name" -required = true -type = "text" - -[[fields]] -group = "profile" -name = "display_name" -order = 13 -placeholder = "How you'll appear to other users" -prompt = "Display Name (optional)" -type = "text" - -[[fields]] -group = "profile" -max_date = "2006-12-31" -min_date = "1950-01-01" -name = "birth_date" -order = 14 -prompt = "Date of Birth" -type = "date" -week_start = "Sun" - -[[fields]] -file_extension = "md" -group = "profile" -name = "bio" -order = 15 -prompt = "About Me" -type = "editor" - -# Account Settings Section -[[items]] -name = "settings_section" -order = 20 -title = "Account Settings" -type = "section_header" - -[[fields]] -default = "Personal" -group = "settings" -name = "account_type" -options = [ - { value = "Personal", label = "👤 Personal - Individual use" }, - { value = "Business", label = "💼 Business - Small team" }, - { value = "Organization", label = "🏢 Organization - Large team" }, -] -order = 21 -prompt = "Account Type" -required = true -type = "select" - -[[fields]] -default = "Free" -group = "settings" -name = "subscription_plan" -options = [ - { value = "Free", label = "🆓 Free - Essential features" }, - { value = "Pro", label = "⭐ Pro - Advanced features" }, - { value = "Enterprise", label = "🏛️ Enterprise - Full suite" }, -] -order = 22 -prompt = "Subscription Plan" -required = true -type = "select" - -# Conditional fields for Business/Organization accounts -[[fields]] -group = "settings" -name = "company_name" -order = 23 -placeholder = "Your Organization" -prompt = "Company Name" -required = true -type = "text" -when = "account_type != Personal" - -[[fields]] -group = "settings" -name = "company_url" -order = 24 -placeholder = "https://example.com" -prompt = "Company Website" -type = "text" -when = "account_type != Personal" - -# Premium features (conditional on subscription) -[[fields]] -default = "false" -group = "settings" -name = "api_access" -order = 25 -prompt = "Enable API Access?" -type = "confirm" -when = "subscription_plan != Free" - -[[fields]] -custom_type = "i32" -default = "1" -group = "settings" -name = "team_members_count" -order = 26 -prompt = "How many team members?" -type = "custom" -when = "subscription_plan == Enterprise" - -# Preferences Section -[[items]] -name = "preferences_section" -order = 30 -title = "Communication Preferences" -type = "section_header" - -[[fields]] -default = "true" -group = "preferences" -name = "notifications_email" -order = 31 -prompt = "Send me email notifications?" -type = "confirm" - -[[fields]] -default = "false" -group = "preferences" -name = "marketing_emails" -order = 32 -prompt = "Subscribe to marketing emails and updates?" -type = "confirm" - -[[fields]] -default = "Daily Digest" -group = "preferences" -name = "notification_frequency" -options = [ - { value = "Immediate", label = "⚡ Immediate" }, - { value = "Daily Digest", label = "📅 Daily Digest" }, - { value = "Weekly Summary", label = "📊 Weekly Summary" }, - { value = "Monthly Summary", label = "📈 Monthly Summary" }, - { value = "Never", label = "🚫 Never" }, -] -order = 33 -prompt = "Email notification frequency" -type = "select" -when = "notifications_email == true" - -[[fields]] -group = "preferences" -name = "interests" -options = [ - { value = "Product Updates", label = "🚀 Product Updates" }, - { value = "Security Alerts", label = "🔒 Security Alerts" }, - { value = "Performance Tips", label = "⚡ Performance Tips" }, - { value = "Community News", label = "👥 Community News" }, - { value = "Educational Content", label = "📚 Educational Content" }, - { value = "Exclusive Offers", label = "🎁 Exclusive Offers" }, -] -order = 34 -page_size = 4 -prompt = "Topics you're interested in:" -type = "multiselect" - -# Privacy & Legal Section -[[items]] -name = "legal_section" -order = 40 -title = "Privacy & Legal" -type = "section_header" - -[[fields]] -group = "legal" -name = "terms_accepted" -order = 41 -prompt = "I agree to the Terms of Service" -required = true -type = "confirm" - -[[fields]] -group = "legal" -name = "privacy_policy_accepted" -order = 42 -prompt = "I have read and accept the Privacy Policy" -required = true -type = "confirm" - -[[fields]] -group = "legal" -name = "data_processing" -order = 43 -prompt = "I consent to data processing as described" -required = true -type = "confirm" - -# Optional secondary contact -[[fields]] -default = "false" -group = "legal" -name = "enable_secondary_contact" -order = 44 -prompt = "Add secondary contact information?" -type = "confirm" - -[[fields]] -group = "legal" -name = "secondary_email" -order = 45 -placeholder = "alternative@example.com" -prompt = "Secondary Email Address" -type = "text" -when = "enable_secondary_contact == true" - -[[fields]] -group = "legal" -name = "recovery_phone" -order = 46 -placeholder = "+1-555-0123" -prompt = "Recovery Phone Number" -type = "text" -when = "enable_secondary_contact == true" diff --git a/examples/05-fragments/agreement_section.ncl b/examples/05-fragments/agreement_section.ncl new file mode 100644 index 0000000..2edb161 --- /dev/null +++ b/examples/05-fragments/agreement_section.ncl @@ -0,0 +1,10 @@ +{ + elements = [ + { type = "section", name = "agreement_header", title = "✅ Terms & Conditions", border_top = true, margin_left = 2 }, + { type = "section", name = "agreement_info", content = "Please review and agree to our terms before proceeding", margin_left = 2 }, + { type = "confirm", name = "agree_terms", prompt = "I agree to the terms and conditions", required = true }, + { type = "confirm", name = "agree_privacy", prompt = "I agree to the privacy policy", required = true }, + { type = "confirm", name = "agree_marketing", prompt = "I consent to receive marketing communications", default = false }, + { type = "section", name = "agreement_footer", content = "Click submit to complete your registration", align = "center", border_bottom = true }, + ], +} diff --git a/examples/05-fragments/agreement_section.toml b/examples/05-fragments/agreement_section.toml deleted file mode 100644 index ba46381..0000000 --- a/examples/05-fragments/agreement_section.toml +++ /dev/null @@ -1,45 +0,0 @@ -name = "agreement_fragment" - -[[elements]] -border_top = true -margin_left = 2 -name = "agreement_header" -order = 1 -title = "✅ Terms & Conditions" -type = "section" - -[[elements]] -content = "Please review and agree to our terms before proceeding" -margin_left = 2 -name = "agreement_info" -order = 2 -type = "section" - -[[elements]] -name = "agree_terms" -order = 3 -prompt = "I agree to the terms and conditions" -required = true -type = "confirm" - -[[elements]] -name = "agree_privacy" -order = 4 -prompt = "I agree to the privacy policy" -required = true -type = "confirm" - -[[elements]] -default = "false" -name = "agree_marketing" -order = 5 -prompt = "I consent to receive marketing communications" -type = "confirm" - -[[elements]] -align = "center" -border_bottom = true -content = "Click submit to complete your registration" -name = "agreement_footer" -order = 6 -type = "section" diff --git a/examples/05-fragments/array-trackers.ncl b/examples/05-fragments/array-trackers.ncl new file mode 100644 index 0000000..8dbc6f4 --- /dev/null +++ b/examples/05-fragments/array-trackers.ncl @@ -0,0 +1,38 @@ +{ + name = "Tracker Configuration with Arrays", + description = "Example showing RepeatingGroup arrays for multiple trackers", + display_mode = "complete", + elements = [ + # Header + { type = "section_header", name = "main_header", title = "🎯 Tracker Configuration", border_top = true, border_bottom = true }, + + # Introduction + { type = "section", name = "intro", content = "Configure multiple UDP and HTTP tracker listeners. You can add, edit, or delete trackers as needed." }, + + # Tracker mode selection + { type = "select", name = "tracker_mode", prompt = "Tracker Mode", required = true, default = "public", options = [ + { value = "public", label = "Public Tracker" }, + { value = "private", label = "Private Tracker" }, + ] + }, + + # UDP Trackers array + { type = "repeatinggroup", name = "udp_trackers", prompt = "UDP Tracker Listeners", required = false, default_items = 1, min_items = 0, max_items = 4, unique = true, fragment = "fragments/tracker-udp-item.toml", help = "Add UDP tracker listener addresses (must be unique). Standard BitTorrent port is 6969." }, + + # HTTP Trackers array + { type = "repeatinggroup", name = "http_trackers", prompt = "HTTP Tracker Listeners", required = false, default_items = 1, min_items = 0, max_items = 4, unique = true, fragment = "fragments/tracker-http-item.toml", help = "Add HTTP tracker listener addresses (must be unique). Standard HTTP port is 80, HTTPS is 443." }, + + # API Configuration section + { type = "section_header", name = "api_header", title = "📡 API Configuration", border_top = true, border_bottom = true }, + + # API Token + { type = "password", name = "api_token", prompt = "Admin API Token", required = true, help = "Secure token for API authentication" }, + + # API Port + { type = "text", name = "api_port", prompt = "API Port", required = true, default = "1212", placeholder = "1212" }, + + # Summary + { type = "section_header", name = "summary_header", title = "✅ Configuration Summary", border_top = true }, + { type = "section", name = "summary", content = "Review your tracker configuration above. Click submit to save settings." }, + ], +} diff --git a/examples/05-fragments/array-trackers.toml b/examples/05-fragments/array-trackers.toml deleted file mode 100644 index 5da9800..0000000 --- a/examples/05-fragments/array-trackers.toml +++ /dev/null @@ -1,102 +0,0 @@ -description = "Example showing RepeatingGroup arrays for multiple trackers" -display_mode = "complete" -name = "Tracker Configuration with Arrays" - -# Header -[[elements]] -border_bottom = true -border_top = true -name = "main_header" -title = "🎯 Tracker Configuration" -type = "section_header" - -# Introduction -[[elements]] -content = "Configure multiple UDP and HTTP tracker listeners. You can add, edit, or delete trackers as needed." -name = "intro" -type = "section" - -# Tracker mode selection -[[elements]] -default = "public" -name = "tracker_mode" -options = [ - { value = "public", label = "Public Tracker" }, - { value = "private", label = "Private Tracker" }, -] -order = 1 -prompt = "Tracker Mode" -required = true -type = "select" - -# UDP Trackers array -# max_items loaded from constraints.toml via constraint interpolation -# See constraints.toml to change max_items (default: 4) -[[elements]] -default_items = 1 -fragment = "fragments/tracker-udp-item.toml" -help = "Add UDP tracker listener addresses (must be unique). Standard BitTorrent port is 6969." -max_items = "${constraint.tracker.udp.max_items}" -min_items = 0 -name = "udp_trackers" -order = 2 -prompt = "UDP Tracker Listeners" -required = false -type = "repeatinggroup" -unique = true - -# HTTP Trackers array -# max_items loaded from constraints.toml via constraint interpolation -# See constraints.toml to change max_items (default: 4) -[[elements]] -default_items = 1 -fragment = "fragments/tracker-http-item.toml" -help = "Add HTTP tracker listener addresses (must be unique). Standard HTTP port is 80, HTTPS is 443." -max_items = "${constraint.tracker.http.max_items}" -min_items = 0 -name = "http_trackers" -order = 3 -prompt = "HTTP Tracker Listeners" -required = false -type = "repeatinggroup" -unique = true - -# API Configuration section -[[elements]] -border_bottom = true -border_top = true -name = "api_header" -title = "📡 API Configuration" -type = "section_header" - -# API Token -[[elements]] -help = "Secure token for API authentication" -name = "api_token" -order = 4 -prompt = "Admin API Token" -required = true -type = "password" - -# API Port -[[elements]] -default = "1212" -name = "api_port" -order = 5 -placeholder = "1212" -prompt = "API Port" -required = true -type = "text" - -# Summary -[[elements]] -border_top = true -name = "summary_header" -title = "✅ Configuration Summary" -type = "section_header" - -[[elements]] -content = "Review your tracker configuration above. Click submit to save settings." -name = "summary" -order = 6 -type = "section" diff --git a/examples/05-fragments/constraints.ncl b/examples/05-fragments/constraints.ncl new file mode 100644 index 0000000..e20538c --- /dev/null +++ b/examples/05-fragments/constraints.ncl @@ -0,0 +1,14 @@ +{ + tracker = { + udp = { + max_items = 4, + min_items = 0, + unique = true, + }, + http = { + max_items = 4, + min_items = 0, + unique = true, + }, + }, +} diff --git a/examples/05-fragments/constraints.toml b/examples/05-fragments/constraints.toml deleted file mode 100644 index 7ecdce4..0000000 --- a/examples/05-fragments/constraints.toml +++ /dev/null @@ -1,20 +0,0 @@ -# Validation Constraints for Tracker Arrays -# Single source of truth for min/max items and uniqueness rules -# -# This file is imported by array-trackers.toml via constraint interpolation: -# max_items = "${constraint.tracker.udp.max_items}" -# max_items = "${constraint.tracker.http.max_items}" -# -# Change these values and the form will automatically use them! - -[tracker.udp] -# UDP tracker listeners - BitTorrent standard port 6969 -max_items = 4 -min_items = 0 -unique = true - -[tracker.http] -# HTTP tracker listeners - Standard HTTP/HTTPS ports 80/443 -max_items = 4 -min_items = 0 -unique = true diff --git a/examples/05-fragments/custom_border_section.ncl b/examples/05-fragments/custom_border_section.ncl new file mode 100644 index 0000000..a0e7e15 --- /dev/null +++ b/examples/05-fragments/custom_border_section.ncl @@ -0,0 +1,7 @@ +{ + elements = [ + { type = "section", name = "custom_section_header", title = "🎨 Custom Border Styles", border_top = true, border_top_char = "-", border_top_len = 50 }, + { type = "section", name = "custom_info", content = "Top border uses '-' (50 chars), bottom border uses '*' (40 chars)" }, + { type = "section", name = "custom_section_footer", content = "You can have different styles for top and bottom borders!", border_bottom = true, border_bottom_char = "*", border_bottom_len = 40 }, + ], +} diff --git a/examples/05-fragments/custom_border_section.toml b/examples/05-fragments/custom_border_section.toml deleted file mode 100644 index 5b217f0..0000000 --- a/examples/05-fragments/custom_border_section.toml +++ /dev/null @@ -1,25 +0,0 @@ -name = "custom_border_fragment" - -[[elements]] -border_top = true -border_top_char = "-" -border_top_len = 50 -name = "custom_section_header" -order = 1 -title = "🎨 Custom Border Styles" -type = "section" - -[[elements]] -content = "Top border uses '-' (50 chars), bottom border uses '*' (40 chars)" -name = "custom_info" -order = 2 -type = "section" - -[[elements]] -border_bottom = true -border_bottom_char = "*" -border_bottom_len = 40 -content = "You can have different styles for top and bottom borders!" -name = "custom_section_footer" -order = 3 -type = "section" diff --git a/examples/05-fragments/employee_info_section.ncl b/examples/05-fragments/employee_info_section.ncl new file mode 100644 index 0000000..ac50e1e --- /dev/null +++ b/examples/05-fragments/employee_info_section.ncl @@ -0,0 +1,18 @@ +{ + elements = [ + { type = "section", name = "employee_header", title = "👤 Employee Information", border_top = true, margin_left = 2 }, + { type = "section", name = "employee_info", content = "Please provide your employment details", margin_left = 2 }, + { type = "text", name = "employee_name", prompt = "Full Name", required = true }, + { type = "text", name = "employee_email", prompt = "Work Email", required = true }, + { type = "select", name = "employee_department", prompt = "Department", required = true, options = [ + { value = "Engineering", label = "💻 Engineering" }, + { value = "Sales", label = "💼 Sales" }, + { value = "Marketing", label = "📢 Marketing" }, + { value = "HR", label = "👥 Human Resources" }, + { value = "Finance", label = "💰 Finance" }, + ] + }, + { type = "date", name = "employee_start_date", prompt = "Start Date", required = true }, + { type = "section", name = "employee_footer", content = "Please ensure all information is accurate", border_bottom = true }, + ], +} diff --git a/examples/05-fragments/employee_info_section.toml b/examples/05-fragments/employee_info_section.toml deleted file mode 100644 index 1105e3b..0000000 --- a/examples/05-fragments/employee_info_section.toml +++ /dev/null @@ -1,58 +0,0 @@ -name = "employee_fragment" - -[[elements]] -border_top = true -margin_left = 2 -name = "employee_header" -order = 1 -title = "👤 Employee Information" -type = "section" - -[[elements]] -content = "Please provide your employment details" -margin_left = 2 -name = "employee_info" -order = 2 -type = "section" - -[[elements]] -name = "employee_name" -order = 3 -prompt = "Full Name" -required = true -type = "text" - -[[elements]] -name = "employee_email" -order = 4 -prompt = "Work Email" -required = true -type = "text" - -[[elements]] -name = "employee_department" -options = [ - { value = "Engineering", label = "💻 Engineering" }, - { value = "Sales", label = "💼 Sales" }, - { value = "Marketing", label = "📢 Marketing" }, - { value = "HR", label = "👥 Human Resources" }, - { value = "Finance", label = "💰 Finance" }, -] -order = 5 -prompt = "Department" -required = true -type = "select" - -[[elements]] -name = "employee_start_date" -order = 6 -prompt = "Start Date" -required = true -type = "date" - -[[elements]] -border_bottom = true -content = "Please ensure all information is accurate" -name = "employee_footer" -order = 7 -type = "section" diff --git a/examples/05-fragments/enterprise_section.ncl b/examples/05-fragments/enterprise_section.ncl new file mode 100644 index 0000000..38bbb3c --- /dev/null +++ b/examples/05-fragments/enterprise_section.ncl @@ -0,0 +1,9 @@ +{ + elements = [ + { type = "section", name = "enterprise_header", title = "🏛️ Enterprise Solution", border_top = true, margin_left = 2 }, + { type = "section", name = "enterprise_warning", content = "⚠️ Requires enterprise agreement and custom setup", margin_left = 2 }, + { type = "section", name = "enterprise_features", content = "✓ Unlimited everything\n✓ Dedicated support team\n✓ Custom integration\n✓ SLA guarantee\n✓ On-premise option\n✓ 24/7 phone support", border_bottom = true, margin_left = 2 }, + { type = "text", name = "enterprise_contact_name", prompt = "Enterprise contact person", required = true }, + { type = "text", name = "enterprise_contact_email", prompt = "Contact email", required = true }, + ], +} diff --git a/examples/05-fragments/enterprise_section.toml b/examples/05-fragments/enterprise_section.toml deleted file mode 100644 index b40d534..0000000 --- a/examples/05-fragments/enterprise_section.toml +++ /dev/null @@ -1,38 +0,0 @@ -name = "enterprise_fragment" - -[[elements]] -border_top = true -margin_left = 2 -name = "enterprise_header" -order = 1 -title = "🏛️ Enterprise Solution" -type = "section" - -[[elements]] -content = "⚠️ Requires enterprise agreement and custom setup" -margin_left = 2 -name = "enterprise_warning" -order = 2 -type = "section" - -[[elements]] -border_bottom = true -content = "✓ Unlimited everything\n✓ Dedicated support team\n✓ Custom integration\n✓ SLA guarantee\n✓ On-premise option\n✓ 24/7 phone support" -margin_left = 2 -name = "enterprise_features" -order = 3 -type = "section" - -[[elements]] -name = "enterprise_contact_name" -order = 4 -prompt = "Enterprise contact person" -required = true -type = "text" - -[[elements]] -name = "enterprise_contact_email" -order = 5 -prompt = "Contact email" -required = true -type = "text" diff --git a/examples/05-fragments/fancy_border_section.ncl b/examples/05-fragments/fancy_border_section.ncl new file mode 100644 index 0000000..a8e72b0 --- /dev/null +++ b/examples/05-fragments/fancy_border_section.ncl @@ -0,0 +1,7 @@ +{ + elements = [ + { type = "section", name = "fancy_header", title = "╔════════ FANCY BORDER ════════╗", border_top = true, border_top_char = "═", border_top_l = "╔", border_top_len = 35, border_top_r = "╗", border_margin_left = 0, content_margin_left = 4 }, + { type = "section", name = "fancy_content", content = "This demonstrates fancy corner characters and borders", margin_left = 4 }, + { type = "section", name = "fancy_footer", content = "You can create beautiful box designs with Unicode!", border_bottom = true, border_bottom_char = "═", border_bottom_l = "╚", border_bottom_len = 35, border_bottom_r = "╝", border_margin_left = 0, content_margin_left = 4 }, + ], +} diff --git a/examples/05-fragments/fancy_border_section.toml b/examples/05-fragments/fancy_border_section.toml deleted file mode 100644 index 60a0ed4..0000000 --- a/examples/05-fragments/fancy_border_section.toml +++ /dev/null @@ -1,34 +0,0 @@ -name = "fancy_border_fragment" - -[[elements]] -border_margin_left = 0 -border_top = true -border_top_char = "═" -border_top_l = "╔" -border_top_len = 35 -border_top_r = "╗" -content_margin_left = 4 -name = "fancy_header" -order = 1 -title = "╔════════ FANCY BORDER ════════╗" -type = "section" - -[[elements]] -content = "This demonstrates fancy corner characters and borders" -margin_left = 4 -name = "fancy_content" -order = 2 -type = "section" - -[[elements]] -border_bottom = true -border_bottom_char = "═" -border_bottom_l = "╚" -border_bottom_len = 35 -border_bottom_r = "╝" -border_margin_left = 0 -content = "You can create beautiful box designs with Unicode!" -content_margin_left = 4 -name = "fancy_footer" -order = 3 -type = "section" diff --git a/examples/05-fragments/form_with_groups_includes.ncl b/examples/05-fragments/form_with_groups_includes.ncl new file mode 100644 index 0000000..edd8e49 --- /dev/null +++ b/examples/05-fragments/form_with_groups_includes.ncl @@ -0,0 +1,23 @@ +let header = import "./header.ncl" in +let premium = import "./premium_section.ncl" in +let enterprise = import "./enterprise_section.ncl" in +let support = import "./support_section.ncl" in +let agreement = import "./agreement_section.ncl" in +{ + name = "Modular Form with Groups & Includes", + description = "Compose form from reusable fragment files", + elements = + header.elements + @ [ + { type = "select", name = "account_plan", prompt = "Select your plan", required = true, options = [ + { value = "Personal", label = "Personal - Individual Users" }, + { value = "Premium", label = "Premium - Growing Teams" }, + { value = "Enterprise", label = "Enterprise - Large Organizations" }, + ] + }, + ] + @ (std.array.map (fun e => e & { when = "account_plan == Premium" }) premium.elements) + @ (std.array.map (fun e => e & { when = "account_plan == Enterprise" }) enterprise.elements) + @ support.elements + @ agreement.elements, +} diff --git a/examples/05-fragments/form_with_groups_includes.toml b/examples/05-fragments/form_with_groups_includes.toml deleted file mode 100644 index 990b7bd..0000000 --- a/examples/05-fragments/form_with_groups_includes.toml +++ /dev/null @@ -1,52 +0,0 @@ -description = "Compose form from reusable fragment files" -name = "Modular Form with Groups & Includes" - -# Include main header from fragment (paths relative to this file) -[[elements]] -includes = ["fragments/header.toml"] -name = "main_group" -order = 1 -type = "group" - -# Account type selection -[[elements]] -name = "account_plan" -options = [ - { value = "Personal", label = "Personal - Individual Users" }, - { value = "Premium", label = "Premium - Growing Teams" }, - { value = "Enterprise", label = "Enterprise - Large Organizations" }, -] -order = 2 -prompt = "Select your plan" -required = true -type = "select" - -# Premium section - conditionally loaded from fragment -[[elements]] -includes = ["fragments/premium_section.toml"] -name = "premium_group" -order = 3 -type = "group" -when = "account_plan == Premium" - -# Enterprise section - conditionally loaded from fragment -[[elements]] -includes = ["fragments/enterprise_section.toml"] -name = "enterprise_group" -order = 4 -type = "group" -when = "account_plan == Enterprise" - -# Support section - always included from fragment -[[elements]] -includes = ["fragments/support_section.toml"] -name = "support_group" -order = 5 -type = "group" - -# Agreement section - always included from fragment -[[elements]] -includes = ["fragments/agreement_section.toml"] -name = "agreement_group" -order = 6 -type = "group" diff --git a/examples/05-fragments/fragments/tracker-http-item.ncl b/examples/05-fragments/fragments/tracker-http-item.ncl new file mode 100644 index 0000000..c8f02a3 --- /dev/null +++ b/examples/05-fragments/fragments/tracker-http-item.ncl @@ -0,0 +1,11 @@ +{ + elements = [ + { type = "text", name = "bind_address", prompt = "HTTP Bind Address", required = true, default = "0.0.0.0:7070", placeholder = "0.0.0.0:7070", help = "Format: :. Standard port is 80 for HTTP, 443 for HTTPS" }, + { type = "select", name = "protocol", prompt = "Protocol", required = true, default = "http", options = [ + { value = "http", label = "HTTP (unencrypted)" }, + { value = "https", label = "HTTPS (encrypted)" }, + ] + }, + { type = "text", name = "workers", prompt = "Worker Threads", required = false, default = "8", placeholder = "8", help = "Number of concurrent worker threads" }, + ], +} diff --git a/examples/05-fragments/fragments/tracker-http-item.toml b/examples/05-fragments/fragments/tracker-http-item.toml deleted file mode 100644 index d36ef28..0000000 --- a/examples/05-fragments/fragments/tracker-http-item.toml +++ /dev/null @@ -1,35 +0,0 @@ -description = "HTTP Tracker listener configuration" -display_mode = "complete" -name = "tracker_http_item" - -[[elements]] -default = "0.0.0.0:7070" -help = "Format: :. Standard port is 80 for HTTP, 443 for HTTPS" -name = "bind_address" -order = 1 -placeholder = "0.0.0.0:7070" -prompt = "HTTP Bind Address" -required = true -type = "text" - -[[elements]] -default = "http" -name = "protocol" -options = [ - { value = "http", label = "HTTP (unencrypted)" }, - { value = "https", label = "HTTPS (encrypted)" }, -] -order = 2 -prompt = "Protocol" -required = true -type = "select" - -[[elements]] -default = "8" -help = "Number of concurrent worker threads" -name = "workers" -order = 3 -placeholder = "8" -prompt = "Worker Threads" -required = false -type = "text" diff --git a/examples/05-fragments/fragments/tracker-udp-item.ncl b/examples/05-fragments/fragments/tracker-udp-item.ncl new file mode 100644 index 0000000..0a1be9e --- /dev/null +++ b/examples/05-fragments/fragments/tracker-udp-item.ncl @@ -0,0 +1,6 @@ +{ + elements = [ + { type = "text", name = "bind_address", prompt = "UDP Bind Address", required = true, default = "0.0.0.0:6969", placeholder = "0.0.0.0:6969", help = "Format: :. Use 0.0.0.0 to listen on all interfaces" }, + { type = "text", name = "workers", prompt = "Worker Threads", required = false, default = "4", placeholder = "4", help = "Number of concurrent worker threads" }, + ], +} diff --git a/examples/05-fragments/fragments/tracker-udp-item.toml b/examples/05-fragments/fragments/tracker-udp-item.toml deleted file mode 100644 index 29910b9..0000000 --- a/examples/05-fragments/fragments/tracker-udp-item.toml +++ /dev/null @@ -1,23 +0,0 @@ -description = "UDP Tracker listener configuration" -display_mode = "complete" -name = "tracker_udp_item" - -[[elements]] -default = "0.0.0.0:6969" -help = "Format: :. Use 0.0.0.0 to listen on all interfaces" -name = "bind_address" -order = 1 -placeholder = "0.0.0.0:6969" -prompt = "UDP Bind Address" -required = true -type = "text" - -[[elements]] -default = "4" -help = "Number of concurrent worker threads" -name = "workers" -order = 2 -placeholder = "4" -prompt = "Worker Threads" -required = false -type = "text" diff --git a/examples/05-fragments/header.ncl b/examples/05-fragments/header.ncl new file mode 100644 index 0000000..bd5f8cb --- /dev/null +++ b/examples/05-fragments/header.ncl @@ -0,0 +1,5 @@ +{ + elements = [ + { type = "header", name = "main_header", title = "✨ Form with Modular Sections", align = "center", border_top = true, border_top_char = "═", border_top_len = 60, border_bottom = true, border_bottom_char = "═", border_bottom_len = 60, margin_left = 0 }, + ], +} diff --git a/examples/05-fragments/header.toml b/examples/05-fragments/header.toml deleted file mode 100644 index 6afbb86..0000000 --- a/examples/05-fragments/header.toml +++ /dev/null @@ -1,15 +0,0 @@ -name = "header_fragment" - -[[elements]] -align = "center" -border_bottom = true -border_bottom_char = "═" -border_bottom_len = 60 -border_top = true -border_top_char = "═" -border_top_len = 60 -margin_left = 0 -name = "main_header" -order = 1 -title = "✨ Form with Modular Sections" -type = "header" diff --git a/examples/05-fragments/premium_section.ncl b/examples/05-fragments/premium_section.ncl new file mode 100644 index 0000000..3ee45fb --- /dev/null +++ b/examples/05-fragments/premium_section.ncl @@ -0,0 +1,12 @@ +{ + elements = [ + { type = "section", name = "premium_header", title = "🌟 Premium Features", border_top = true, margin_left = 2 }, + { type = "section", name = "premium_features", content = "✓ Unlimited storage\n✓ Advanced analytics\n✓ Priority support\n✓ Custom branding\n✓ API access", border_bottom = true, margin_left = 2 }, + { type = "select", name = "premium_payment_method", prompt = "Payment method", required = true, options = [ + { value = "Credit Card", label = "💳 Credit Card" }, + { value = "Bank Transfer", label = "🏦 Bank Transfer" }, + { value = "PayPal", label = "🅿️ PayPal" }, + ] + }, + ], +} diff --git a/examples/05-fragments/premium_section.toml b/examples/05-fragments/premium_section.toml deleted file mode 100644 index d7b671d..0000000 --- a/examples/05-fragments/premium_section.toml +++ /dev/null @@ -1,29 +0,0 @@ -name = "premium_fragment" - -[[elements]] -border_top = true -margin_left = 2 -name = "premium_header" -order = 1 -title = "🌟 Premium Features" -type = "section" - -[[elements]] -border_bottom = true -content = "✓ Unlimited storage\n✓ Advanced analytics\n✓ Priority support\n✓ Custom branding\n✓ API access" -margin_left = 2 -name = "premium_features" -order = 2 -type = "section" - -[[elements]] -name = "premium_payment_method" -options = [ - { value = "Credit Card", label = "💳 Credit Card" }, - { value = "Bank Transfer", label = "🏦 Bank Transfer" }, - { value = "PayPal", label = "🅿️ PayPal" }, -] -order = 3 -prompt = "Payment method" -required = true -type = "select" diff --git a/examples/05-fragments/support_section.ncl b/examples/05-fragments/support_section.ncl new file mode 100644 index 0000000..648adb7 --- /dev/null +++ b/examples/05-fragments/support_section.ncl @@ -0,0 +1,13 @@ +{ + elements = [ + { type = "section", name = "support_header", title = "📞 Support Options", border_top = true, margin_left = 2 }, + { type = "section", name = "support_info", content = "Choose your preferred support level", margin_left = 2 }, + { type = "select", name = "support_level", prompt = "Support Level", required = true, options = [ + { value = "Basic", label = "Basic - Email only" }, + { value = "Standard", label = "Standard - Email & chat" }, + { value = "Premium", label = "Premium - Phone & live support" }, + ] + }, + { type = "section", name = "support_footer", content = "Support is available 24/7 for Premium plans", border_bottom = true }, + ], +} diff --git a/examples/05-fragments/support_section.toml b/examples/05-fragments/support_section.toml deleted file mode 100644 index a43fff7..0000000 --- a/examples/05-fragments/support_section.toml +++ /dev/null @@ -1,35 +0,0 @@ -name = "support_fragment" - -[[elements]] -border_top = true -margin_left = 2 -name = "support_header" -order = 1 -title = "📞 Support Options" -type = "section" - -[[elements]] -content = "Choose your preferred support level" -margin_left = 2 -name = "support_info" -order = 2 -type = "section" - -[[elements]] -name = "support_level" -options = [ - { value = "Basic", label = "Basic - Email only" }, - { value = "Standard", label = "Standard - Email & chat" }, - { value = "Premium", label = "Premium - Phone & live support" }, -] -order = 3 -prompt = "Support Level" -required = true -type = "select" - -[[elements]] -border_bottom = true -content = "Support is available 24/7 for Premium plans" -name = "support_footer" -order = 4 -type = "section" diff --git a/examples/06-i18n/en-US.toml b/examples/06-i18n/en-US.toml deleted file mode 100644 index 34e7846..0000000 --- a/examples/06-i18n/en-US.toml +++ /dev/null @@ -1,31 +0,0 @@ -# English translations (alternative TOML format) - -[forms.registration] -description = "Create a new user account" -email-label = "Email Address" -email-placeholder = "user@example.com" -email-prompt = "Please enter your email address" -title = "User Registration" -username-label = "Username" -username-placeholder = "user123" -username-prompt = "Please enter a username" - - [forms.registration.roles] - admin = "Administrator" - developer = "Developer" - guest = "Guest" - user = "Regular User" - -[forms.employee-onboarding] -department-prompt = "Which department are you joining?" -description = "Complete your onboarding process" -full-name-prompt = "What is your full name?" -start-date-prompt = "What is your start date?" -title = "Employee Onboarding" -welcome = "Welcome to the team!" - -[forms.feedback] -contact-prompt = "Can we contact you with follow-up questions?" -improvement-prompt = "What could we improve?" -overall-satisfaction-prompt = "How satisfied are you with our service?" -title = "Feedback Form" diff --git a/examples/06-i18n/es-ES.toml b/examples/06-i18n/es-ES.toml deleted file mode 100644 index 17e7940..0000000 --- a/examples/06-i18n/es-ES.toml +++ /dev/null @@ -1,31 +0,0 @@ -# Traducciones al español (formato TOML alternativo) - -[forms.registration] -description = "Crear una nueva cuenta de usuario" -email-label = "Correo electrónico" -email-placeholder = "usuario@ejemplo.com" -email-prompt = "Por favor, ingrese su correo electrónico" -title = "Registro de Usuario" -username-label = "Nombre de usuario" -username-placeholder = "usuario123" -username-prompt = "Por favor, ingrese su nombre de usuario" - - [forms.registration.roles] - admin = "Administrador" - developer = "Desarrollador" - guest = "Invitado" - user = "Usuario Regular" - -[forms.employee-onboarding] -department-prompt = "¿A cuál departamento se está uniendo?" -description = "Complete su proceso de incorporación" -full-name-prompt = "¿Cuál es su nombre completo?" -start-date-prompt = "¿Cuál es su fecha de inicio?" -title = "Incorporación de Empleado" -welcome = "¡Bienvenido al equipo!" - -[forms.feedback] -contact-prompt = "¿Podemos contactarlo con preguntas de seguimiento?" -improvement-prompt = "¿Qué podríamos mejorar?" -overall-satisfaction-prompt = "¿Cuán satisfecho está con nuestro servicio?" -title = "Formulario de Retroalimentación" diff --git a/examples/06-integrations/i18n/registration_i18n.ncl b/examples/06-integrations/i18n/registration_i18n.ncl new file mode 100644 index 0000000..357fe90 --- /dev/null +++ b/examples/06-integrations/i18n/registration_i18n.ncl @@ -0,0 +1,18 @@ +{ + name = "user_registration", + description = "Register a new user account with multi-language support", + elements = [ + { type = "header", name = "header", title = "forms.registration.title", border_top = true, border_bottom = true, i18n = true }, + { type = "section", name = "description", content = "Complete the form to create your account" }, + { type = "text", name = "username", prompt = "forms.registration.username-prompt", required = true, placeholder = "forms.registration.username-placeholder", i18n = true }, + { type = "text", name = "email", prompt = "forms.registration.email-prompt", required = true, placeholder = "forms.registration.email-placeholder", i18n = true }, + { type = "select", name = "role", prompt = "forms.registration.role-prompt", required = true, i18n = true, options = [ + { value = "admin", label = "forms.registration.roles.admin" }, + { value = "user", label = "forms.registration.roles.user" }, + { value = "guest", label = "forms.registration.roles.guest" }, + ] + }, + { type = "confirm", name = "agree_terms", prompt = "forms.registration.registration-confirm-prompt", required = true, default = false, i18n = true }, + { type = "footer", name = "footer", content = "Your data will be securely stored and protected.", border_top = true }, + ], +} diff --git a/examples/06-integrations/i18n/registration_i18n.toml b/examples/06-integrations/i18n/registration_i18n.toml deleted file mode 100644 index 14a8ac7..0000000 --- a/examples/06-integrations/i18n/registration_i18n.toml +++ /dev/null @@ -1,58 +0,0 @@ -description = "Register a new user account with multi-language support" -locale = "es-ES" -name = "user_registration" - -[[items]] -border_bottom = true -border_top = true -i18n = true -name = "header" -title = "forms.registration.title" -type = "header" - -[[items]] -content = "Complete the form to create your account" -name = "description" -type = "section" - -[[fields]] -i18n = true -name = "username" -placeholder = "forms.registration.username-placeholder" -prompt = "forms.registration.username-prompt" -required = true -type = "text" - -[[fields]] -i18n = true -name = "email" -placeholder = "forms.registration.email-placeholder" -prompt = "forms.registration.email-prompt" -required = true -type = "text" - -[[fields]] -i18n = true -name = "role" -options = [ - { value = "admin", label = "forms.registration.roles.admin" }, - { value = "user", label = "forms.registration.roles.user" }, - { value = "guest", label = "forms.registration.roles.guest" }, -] -prompt = "forms.registration.role-prompt" -required = true -type = "select" - -[[fields]] -default = false -i18n = true -name = "agree_terms" -prompt = "forms.registration.registration-confirm-prompt" -required = true -type = "confirm" - -[[items]] -border_top = true -content = "Your data will be securely stored and protected." -name = "footer" -type = "footer" diff --git a/examples/06-integrations/i18n/test_i18n_form.ncl b/examples/06-integrations/i18n/test_i18n_form.ncl new file mode 100644 index 0000000..03759dd --- /dev/null +++ b/examples/06-integrations/i18n/test_i18n_form.ncl @@ -0,0 +1,13 @@ +{ + name = "test_i18n", + description = "Test i18n form", + elements = [ + { type = "text", name = "username", prompt = "registration-username-prompt", placeholder = "registration-username-placeholder", i18n = true }, + { type = "select", name = "role", prompt = "role-prompt", default = "user", i18n = true, options = [ + { value = "admin", label = "role-admin" }, + { value = "user", label = "role-user" }, + { value = "guest", label = "role-guest" }, + ] + }, + ], +} diff --git a/examples/06-integrations/i18n/test_i18n_form.toml b/examples/06-integrations/i18n/test_i18n_form.toml deleted file mode 100644 index 2a34ef9..0000000 --- a/examples/06-integrations/i18n/test_i18n_form.toml +++ /dev/null @@ -1,22 +0,0 @@ -description = "Test i18n form" -locale = "es-ES" -name = "test_i18n" - -[[fields]] -i18n = true -name = "username" -placeholder = "registration-username-placeholder" -prompt = "registration-username-prompt" -type = "text" - -[[fields]] -default = "user" -i18n = true -name = "role" -options = [ - { value = "admin", label = "role-admin" }, - { value = "user", label = "role-user" }, - { value = "guest", label = "role-guest" }, -] -prompt = "role-prompt" -type = "select" diff --git a/examples/07-nickel-generation/arrays-form.ncl b/examples/07-nickel-generation/arrays-form.ncl new file mode 100644 index 0000000..a0c1317 --- /dev/null +++ b/examples/07-nickel-generation/arrays-form.ncl @@ -0,0 +1,46 @@ +{ + name = "Tracker Configuration with Arrays", + description = "Complete example showing RepeatingGroup arrays in action with Nickel schema integration", + display_mode = "complete", + elements = [ + # Header + { type = "section_header", name = "main_header", title = "🎯 Tracker & API Configuration", border_top = true, border_bottom = true }, + { type = "section", name = "intro", content = "Configure tracker listeners and API endpoints. You can add multiple UDP/HTTP listeners and manage users and API endpoints dynamically." }, + + # TRACKER CONFIGURATION + { type = "section_header", name = "tracker_section_header", title = "📡 Tracker Configuration", border_top = true }, + { type = "select", name = "tracker_mode", prompt = "Tracker Mode", required = true, default = "public", options = [ + { value = "public", label = "Public Tracker (anyone can use)" }, + { value = "private", label = "Private Tracker (registered users only)" }, + ] + }, + { type = "repeatinggroup", name = "udp_trackers", prompt = "UDP Tracker Listeners", required = false, default_items = 1, min_items = 0, max_items = 10, unique = true, fragment = "fragments/tracker-udp-item.toml", nickel_path = ["udp_trackers"], help = "Add UDP tracker listener addresses (must be unique). Standard BitTorrent port is 6969." }, + { type = "repeatinggroup", name = "http_trackers", prompt = "HTTP Tracker Listeners", required = false, default_items = 1, min_items = 0, max_items = 10, unique = true, fragment = "fragments/tracker-http-item.toml", nickel_path = ["http_trackers"], help = "Add HTTP tracker listener addresses (must be unique). Standard ports: 80 (HTTP), 443 (HTTPS)" }, + + # API CONFIGURATION + { type = "section_header", name = "api_section_header", title = "📡 API Configuration", border_top = true, border_bottom = true }, + { type = "password", name = "api_token", prompt = "Admin API Token", required = true, nickel_path = ["api_token"], help = "Secure token for API authentication and access control" }, + { type = "text", name = "api_port", prompt = "API Port", required = true, default = "1212", placeholder = "1212", nickel_path = ["api_port"], help = "Port number for API server (1024-65535). Standard: 1212" }, + { type = "repeatinggroup", name = "api_endpoints", prompt = "API Endpoints", required = false, default_items = 0, min_items = 0, max_items = 20, unique = true, fragment = "fragments/api-endpoint-item.toml", nickel_path = ["api_endpoints"], help = "Define custom API endpoints (must be unique). Each endpoint path must be different." }, + + # USER MANAGEMENT + { type = "section_header", name = "users_section_header", title = "👥 User Management", border_top = true, border_bottom = true }, + { type = "repeatinggroup", name = "users", prompt = "User Accounts", required = false, default_items = 0, min_items = 0, max_items = 100, unique = true, fragment = "fragments/user-item.toml", nickel_path = ["users"], help = "Add user accounts (must be unique). Each username must be different." }, + + # OPTIONAL FEATURES + { type = "section_header", name = "features_section_header", title = "⚙️ Optional Features", border_top = true, border_bottom = true }, + { type = "confirm", name = "enable_metrics", prompt = "Enable metrics collection", default = false, nickel_path = ["enable_metrics"], help = "Track performance metrics and statistics" }, + { type = "confirm", name = "enable_logging", prompt = "Enable logging", default = true, nickel_path = ["enable_logging"], help = "Log application events and errors" }, + { type = "select", name = "log_level", prompt = "Log Level", default = "info", when = "enable_logging == true", nickel_path = ["log_level"], options = [ + { value = "debug", label = "Debug - All events" }, + { value = "info", label = "Info - Important events" }, + { value = "warn", label = "Warn - Warnings and errors" }, + { value = "error", label = "Error - Errors only" }, + ] + }, + + # SUMMARY + { type = "section_header", name = "summary_header", title = "✅ Configuration Summary", border_top = true }, + { type = "section", name = "summary", content = "Review your configuration above. All settings will be validated against the Nickel schema before saving." }, + ], +} diff --git a/examples/07-nickel-generation/arrays-form.toml b/examples/07-nickel-generation/arrays-form.toml deleted file mode 100644 index 2d24ad7..0000000 --- a/examples/07-nickel-generation/arrays-form.toml +++ /dev/null @@ -1,198 +0,0 @@ -description = "Complete example showing RepeatingGroup arrays in action with Nickel schema integration" -display_mode = "complete" -name = "Tracker Configuration with Arrays" - -# Header -[[elements]] -border_bottom = true -border_top = true -name = "main_header" -title = "🎯 Tracker & API Configuration" -type = "section_header" - -[[elements]] -content = "Configure tracker listeners and API endpoints. You can add multiple UDP/HTTP listeners and manage users and API endpoints dynamically." -name = "intro" -type = "section" - -# ======================================================== -# TRACKER CONFIGURATION -# ======================================================== - -[[elements]] -border_top = true -name = "tracker_section_header" -title = "📡 Tracker Configuration" -type = "section_header" - -[[elements]] -default = "public" -name = "tracker_mode" -options = [ - { value = "public", label = "Public Tracker (anyone can use)" }, - { value = "private", label = "Private Tracker (registered users only)" }, -] -order = 1 -prompt = "Tracker Mode" -required = true -type = "select" - -# UDP Trackers - Array of listeners -[[elements]] -default_items = 1 -fragment = "fragments/tracker-udp-item.toml" -help = "Add UDP tracker listener addresses (must be unique). Standard BitTorrent port is 6969." -max_items = 10 -min_items = 0 -name = "udp_trackers" -nickel_path = ["udp_trackers"] -order = 2 -prompt = "UDP Tracker Listeners" -required = false -type = "repeatinggroup" -unique = true - -# HTTP Trackers - Array of listeners -[[elements]] -default_items = 1 -fragment = "fragments/tracker-http-item.toml" -help = "Add HTTP tracker listener addresses (must be unique). Standard ports: 80 (HTTP), 443 (HTTPS)" -max_items = 10 -min_items = 0 -name = "http_trackers" -nickel_path = ["http_trackers"] -order = 3 -prompt = "HTTP Tracker Listeners" -required = false -type = "repeatinggroup" -unique = true - -# ======================================================== -# API CONFIGURATION -# ======================================================== - -[[elements]] -border_bottom = true -border_top = true -name = "api_section_header" -title = "📡 API Configuration" -type = "section_header" - -[[elements]] -help = "Secure token for API authentication and access control" -name = "api_token" -nickel_path = ["api_token"] -order = 4 -prompt = "Admin API Token" -required = true -type = "password" - -[[elements]] -default = "1212" -help = "Port number for API server (1024-65535). Standard: 1212" -name = "api_port" -nickel_path = ["api_port"] -order = 5 -placeholder = "1212" -prompt = "API Port" -required = true -type = "text" - -# API Endpoints - Array of exposed endpoints -[[elements]] -default_items = 0 -fragment = "fragments/api-endpoint-item.toml" -help = "Define custom API endpoints (must be unique). Each endpoint path must be different." -max_items = 20 -min_items = 0 -name = "api_endpoints" -nickel_path = ["api_endpoints"] -order = 6 -prompt = "API Endpoints" -required = false -type = "repeatinggroup" -unique = true - -# ======================================================== -# USER MANAGEMENT -# ======================================================== - -[[elements]] -border_bottom = true -border_top = true -name = "users_section_header" -title = "👥 User Management" -type = "section_header" - -[[elements]] -default_items = 0 -fragment = "fragments/user-item.toml" -help = "Add user accounts (must be unique). Each username must be different." -max_items = 100 -min_items = 0 -name = "users" -nickel_path = ["users"] -order = 7 -prompt = "User Accounts" -required = false -type = "repeatinggroup" -unique = true - -# ======================================================== -# OPTIONAL FEATURES -# ======================================================== - -[[elements]] -border_bottom = true -border_top = true -name = "features_section_header" -title = "⚙️ Optional Features" -type = "section_header" - -[[elements]] -default = false -help = "Track performance metrics and statistics" -name = "enable_metrics" -nickel_path = ["enable_metrics"] -order = 8 -prompt = "Enable metrics collection" -type = "confirm" - -[[elements]] -default = true -help = "Log application events and errors" -name = "enable_logging" -nickel_path = ["enable_logging"] -order = 9 -prompt = "Enable logging" -type = "confirm" - -[[elements]] -default = "info" -name = "log_level" -nickel_path = ["log_level"] -options = [ - { value = "debug", label = "Debug - All events" }, - { value = "info", label = "Info - Important events" }, - { value = "warn", label = "Warn - Warnings and errors" }, - { value = "error", label = "Error - Errors only" }, -] -order = 10 -prompt = "Log Level" -type = "select" -when = "enable_logging == true" - -# ======================================================== -# SUMMARY -# ======================================================== - -[[elements]] -border_top = true -name = "summary_header" -title = "✅ Configuration Summary" -type = "section_header" - -[[elements]] -content = "Review your configuration above. All settings will be validated against the Nickel schema before saving." -name = "summary" -type = "section" diff --git a/examples/07-nickel-generation/fragments/api-endpoint-item.ncl b/examples/07-nickel-generation/fragments/api-endpoint-item.ncl new file mode 100644 index 0000000..c9fc285 --- /dev/null +++ b/examples/07-nickel-generation/fragments/api-endpoint-item.ncl @@ -0,0 +1,7 @@ +{ + elements = [ + { type = "text", name = "path", prompt = "Endpoint Path", required = true, placeholder = "/api/v1/stats", help = "API endpoint path (e.g., /api/v1/stats, /api/v2/submit)" }, + { type = "confirm", name = "require_auth", prompt = "Require Authentication", default = true, help = "Require admin token for this endpoint" }, + { type = "text", name = "rate_limit", prompt = "Rate Limit (req/min)", default = "100", placeholder = "100", help = "Maximum requests per minute (0 = unlimited)" }, + ], +} diff --git a/examples/07-nickel-generation/fragments/api-endpoint-item.toml b/examples/07-nickel-generation/fragments/api-endpoint-item.toml deleted file mode 100644 index 2a8e8ac..0000000 --- a/examples/07-nickel-generation/fragments/api-endpoint-item.toml +++ /dev/null @@ -1,29 +0,0 @@ -description = "API Endpoint configuration" -display_mode = "complete" -name = "api_endpoint_item" - -[[elements]] -help = "API endpoint path (e.g., /api/v1/stats, /api/v2/submit)" -name = "path" -order = 1 -placeholder = "/api/v1/stats" -prompt = "Endpoint Path" -required = true -type = "text" - -[[elements]] -default = true -help = "Require admin token for this endpoint" -name = "require_auth" -order = 2 -prompt = "Require Authentication" -type = "confirm" - -[[elements]] -default = "100" -help = "Maximum requests per minute (0 = unlimited)" -name = "rate_limit" -order = 3 -placeholder = "100" -prompt = "Rate Limit (req/min)" -type = "text" diff --git a/examples/07-nickel-generation/fragments/user-item.ncl b/examples/07-nickel-generation/fragments/user-item.ncl new file mode 100644 index 0000000..91723a1 --- /dev/null +++ b/examples/07-nickel-generation/fragments/user-item.ncl @@ -0,0 +1,13 @@ +{ + elements = [ + { type = "text", name = "username", prompt = "Username", required = true, placeholder = "john_doe", help = "Unique username (alphanumeric and underscores)" }, + { type = "text", name = "email", prompt = "Email Address", required = true, placeholder = "user@example.com", help = "User email address" }, + { type = "select", name = "role", prompt = "User Role", required = true, default = "user", options = [ + { value = "admin", label = "Admin (full access)" }, + { value = "moderator", label = "Moderator (moderation only)" }, + { value = "user", label = "User (basic access)" }, + ] + }, + { type = "confirm", name = "active", prompt = "Active", default = true, help = "Enable or disable user account" }, + ], +} diff --git a/examples/07-nickel-generation/fragments/user-item.toml b/examples/07-nickel-generation/fragments/user-item.toml deleted file mode 100644 index 5f903d6..0000000 --- a/examples/07-nickel-generation/fragments/user-item.toml +++ /dev/null @@ -1,42 +0,0 @@ -description = "User account configuration" -display_mode = "complete" -name = "user_item" - -[[elements]] -help = "Unique username (alphanumeric and underscores)" -name = "username" -order = 1 -placeholder = "john_doe" -prompt = "Username" -required = true -type = "text" - -[[elements]] -help = "User email address" -name = "email" -order = 2 -placeholder = "user@example.com" -prompt = "Email Address" -required = true -type = "text" - -[[elements]] -default = "user" -name = "role" -options = [ - { value = "admin", label = "Admin (full access)" }, - { value = "moderator", label = "Moderator (moderation only)" }, - { value = "user", label = "User (basic access)" }, -] -order = 3 -prompt = "User Role" -required = true -type = "select" - -[[elements]] -default = true -help = "Enable or disable user account" -name = "active" -order = 4 -prompt = "Active" -type = "confirm" diff --git a/examples/08-encryption/credentials.ncl b/examples/08-encryption/credentials.ncl new file mode 100644 index 0000000..9db09be --- /dev/null +++ b/examples/08-encryption/credentials.ncl @@ -0,0 +1,25 @@ +{ + name = "user_credentials", + description = "User credentials with encryption support", + display_mode = "complete", + elements = [ + # Non-sensitive fields (will be output as plaintext) + { type = "text", name = "username", prompt = "Username", required = true, sensitive = false }, + { type = "text", name = "email", prompt = "Email address", required = true, sensitive = false }, + { type = "text", name = "company", prompt = "Company (optional)", required = false, sensitive = false }, + + # Sensitive fields - Auto-detected (FieldType::Password = sensitive by default) + { type = "password", name = "password", prompt = "Password", required = true }, + { type = "password", name = "confirm_password", prompt = "Confirm password", required = true }, + + # Sensitive fields - Explicit (sensitive = true) + { type = "text", name = "api_token", prompt = "API Token", required = false, sensitive = true, encryption_backend = "age" }, + { type = "editor", name = "ssh_key", prompt = "SSH Private Key (multiline)", required = false, sensitive = true }, + { type = "text", name = "database_url", prompt = "Database Connection String", required = false, sensitive = true }, + { type = "text", name = "vault_token", prompt = "Vault Token (encrypted with SOPS)", required = false, sensitive = true, encryption_backend = "sops" }, + { type = "text", name = "kms_key_id", prompt = "AWS KMS Key ID (encrypted with AWS KMS)", required = false, sensitive = true, encryption_backend = "awskms", encryption_config = { key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", region = "us-east-1" } }, + + # Non-sensitive field (explicit override) + { type = "password", name = "demo_password", prompt = "Demo password (shown in plaintext)", required = false, sensitive = false }, + ], +} diff --git a/examples/08-encryption/credentials.toml b/examples/08-encryption/credentials.toml deleted file mode 100644 index 092acd1..0000000 --- a/examples/08-encryption/credentials.toml +++ /dev/null @@ -1,138 +0,0 @@ -# Encryption Demo Form -# -# This form demonstrates the encryption and redaction pipeline in typedialog. -# Fields marked as "sensitive" will be: -# - Redacted to [REDACTED] with --redact flag -# - Encrypted with --encrypt flag (requires Age, SOPS, SecretumVault, or KMS backend) -# -# Usage: -# # Redaction mode (no encryption service needed) -# typedialog form examples/08-encryption/credentials.toml --redact --format json -# -# # Age encryption (local, requires ~/.age/key.txt) -# typedialog form examples/08-encryption/credentials.toml \ -# --encrypt --backend age --key-file ~/.age/key.txt --format json -# -# # SOPS encryption (supports AWS/GCP/Azure KMS via .sops.yaml) -# export AWS_REGION=us-east-1 -# typedialog form examples/08-encryption/credentials.toml \ -# --encrypt --backend sops --format json -# -# # SecretumVault encryption (post-quantum cryptography ready) -# export VAULT_ADDR=https://vault.internal:8200 -# export VAULT_TOKEN=hvs.CAAA... -# typedialog form examples/08-encryption/credentials.toml \ -# --encrypt --backend secretumvault --format json - -description = "User credentials with encryption support" -display_mode = "complete" -name = "user_credentials" - -# ============================================================================ -# Non-sensitive fields (will be output as plaintext) -# ============================================================================ - -[[fields]] -name = "username" -prompt = "Username" -required = true -sensitive = false -type = "text" - -[[fields]] -name = "email" -prompt = "Email address" -required = true -sensitive = false -type = "text" - -[[fields]] -name = "company" -prompt = "Company (optional)" -required = false -sensitive = false -type = "text" - -# ============================================================================ -# Sensitive fields - Auto-detected (FieldType::Password = sensitive by default) -# ============================================================================ - -[[fields]] -name = "password" -prompt = "Password" -required = true -type = "password" -# sensitive not specified - auto-detected as true from FieldType::Password - -[[fields]] -name = "confirm_password" -prompt = "Confirm password" -required = true -type = "password" - -# ============================================================================ -# Sensitive fields - Explicit (sensitive = true) -# These are non-password fields but marked sensitive -# ============================================================================ - -[[fields]] -encryption_backend = "age" -name = "api_token" -prompt = "API Token" -required = false -sensitive = true -type = "text" - -[[fields]] -name = "ssh_key" -prompt = "SSH Private Key (multiline)" -required = false -sensitive = true -type = "editor" - -[[fields]] -name = "database_url" -prompt = "Database Connection String" -required = false -sensitive = true -type = "text" - -# ============================================================================ -# Encryption configuration per field (optional) -# If not specified, uses CLI --backend flag or global default -# ============================================================================ - -[[fields]] -encryption_backend = "sops" -name = "vault_token" -prompt = "Vault Token (encrypted with SOPS)" -required = false -sensitive = true -type = "text" -# Note: SOPS reads configuration from .sops.yaml in current directory or parent -# No additional config needed - SOPS uses .sops.yaml for KMS setup - -[[fields]] -encryption_backend = "awskms" -name = "kms_key_id" -prompt = "AWS KMS Key ID (encrypted with AWS KMS)" -required = false -sensitive = true -type = "text" - - [fields.encryption_config] - key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012" - region = "us-east-1" - - # ============================================================================ - # Non-sensitive field (explicit override) - # Note: This field is type=password but marked as NOT sensitive - # Will be output as plaintext (useful for test/demo passwords) - # ============================================================================ - -[[fields]] -name = "demo_password" -prompt = "Demo password (shown in plaintext)" -required = false -sensitive = false -type = "password" diff --git a/examples/08-encryption/multi-backend-sops.ncl b/examples/08-encryption/multi-backend-sops.ncl new file mode 100644 index 0000000..00f2acc --- /dev/null +++ b/examples/08-encryption/multi-backend-sops.ncl @@ -0,0 +1,40 @@ +{ + name = "multi_backend_config", + description = "Configuration with multiple encryption backends for different environments", + display_mode = "complete", + elements = [ + # Application Configuration (Non-sensitive) + { type = "text", name = "app_name", prompt = "Application name", required = true, sensitive = false }, + { type = "select", name = "environment", prompt = "Environment", required = true, sensitive = false, options = [ + { value = "development", label = "development" }, + { value = "staging", label = "staging" }, + { value = "production", label = "production" }, + ] + }, + { type = "select", name = "log_level", prompt = "Log level", required = false, sensitive = false, options = [ + { value = "debug", label = "debug" }, + { value = "info", label = "info" }, + { value = "warn", label = "warn" }, + { value = "error", label = "error" }, + ] + }, + + # Database Configuration + { type = "text", name = "db_host", prompt = "Database hostname", required = true, sensitive = false }, + { type = "text", name = "db_port", prompt = "Database port", required = false, default = "5432", sensitive = false }, + { type = "text", name = "db_username", prompt = "Database username", required = true, sensitive = false }, + { type = "password", name = "db_password", prompt = "Database password (encrypted with SOPS)", required = true, sensitive = true, encryption_backend = "sops" }, + + # API Keys and Tokens + { type = "text", name = "api_key", prompt = "API Key (encrypted with Age)", required = false, sensitive = true, encryption_backend = "age" }, + { type = "password", name = "api_secret", prompt = "API Secret (encrypted with Age)", required = false, sensitive = true, encryption_backend = "age" }, + + # Enterprise/Production Secrets + { type = "password", name = "master_key", prompt = "Master encryption key (AWS KMS protected)", required = false, sensitive = true, encryption_backend = "awskms", encryption_config = { key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", region = "us-east-1" } }, + { type = "password", name = "root_token", prompt = "Root access token (AWS KMS protected)", required = false, sensitive = true, encryption_backend = "awskms", encryption_config = { key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", region = "us-east-1" } }, + + # Certificate and Key Material + { type = "editor", name = "tls_cert", prompt = "TLS Certificate (SecretumVault with PQC)", required = false, sensitive = true, encryption_backend = "secretumvault" }, + { type = "editor", name = "tls_key", prompt = "TLS Private Key (SecretumVault with PQC)", required = false, sensitive = true, encryption_backend = "secretumvault" }, + ], +} diff --git a/examples/08-encryption/multi-backend-sops.toml b/examples/08-encryption/multi-backend-sops.toml deleted file mode 100644 index 6c17a04..0000000 --- a/examples/08-encryption/multi-backend-sops.toml +++ /dev/null @@ -1,192 +0,0 @@ -# Multi-Backend Encryption with SOPS Focus -# -# This example demonstrates how different sensitive fields can use different -# encryption backends in the same form. Useful for multi-environment deployments: -# - Development: Age (local, no external service) -# - Staging: SOPS (team collaboration, key management) -# - Production: SecretumVault or direct AWS KMS (enterprise) -# -# Usage: -# -# Development (Age - local): -# age-keygen -o ~/.age/key.txt # Only needed once -# typedialog form examples/08-encryption/multi-backend-sops.toml \ -# --encrypt --backend age --key-file ~/.age/key.txt --format json -# -# Staging (SOPS - AWS KMS via .sops.yaml): -# # Create .sops.yaml -# cat > .sops.yaml << 'EOF' -# creation_rules: -# - path_regex: .* -# kms: arn:aws:kms:us-east-1:ACCOUNT:key/KEY_ID -# EOF -# export AWS_REGION=us-east-1 -# typedialog form examples/08-encryption/multi-backend-sops.toml \ -# --encrypt --backend sops --format json -# -# Production (SecretumVault - post-quantum): -# export VAULT_ADDR=https://vault.prod:8200 -# export VAULT_TOKEN=hvs.token -# typedialog form examples/08-encryption/multi-backend-sops.toml \ -# --encrypt --backend secretumvault --format json -# - -description = "Configuration with multiple encryption backends for different environments" -display_mode = "complete" -name = "multi_backend_config" - -# ============================================================================ -# Application Configuration (Non-sensitive) -# ============================================================================ - -[[fields]] -name = "app_name" -prompt = "Application name" -required = true -sensitive = false -type = "text" - -[[fields]] -name = "environment" -options = ["development", "staging", "production"] -prompt = "Environment" -required = true -sensitive = false -type = "select" - -[[fields]] -name = "log_level" -options = ["debug", "info", "warn", "error"] -prompt = "Log level" -required = false -sensitive = false -type = "select" - -# ============================================================================ -# Database Configuration -# Field-level backend: SOPS (team-friendly, multi-KMS support) -# ============================================================================ - -[[fields]] -name = "db_host" -prompt = "Database hostname" -required = true -sensitive = false -type = "text" - -[[fields]] -default = "5432" -name = "db_port" -prompt = "Database port" -required = false -sensitive = false -type = "text" - -[[fields]] -name = "db_username" -prompt = "Database username" -required = true -sensitive = false -type = "text" - -[[fields]] -encryption_backend = "sops" -name = "db_password" -prompt = "Database password (encrypted with SOPS)" -required = true -sensitive = true -type = "password" -# Note: SOPS configuration comes from .sops.yaml -# Supports AWS KMS, GCP KMS, Azure Key Vault via that config - -# ============================================================================ -# API Keys and Tokens -# Field-level backend: Age (simple, local) -# These might be development tokens that don't need KMS -# ============================================================================ - -[[fields]] -encryption_backend = "age" -name = "api_key" -prompt = "API Key (encrypted with Age)" -required = false -sensitive = true -type = "text" - -[[fields]] -encryption_backend = "age" -name = "api_secret" -prompt = "API Secret (encrypted with Age)" -required = false -sensitive = true -type = "password" - -# ============================================================================ -# Enterprise/Production Secrets -# Field-level backend: AWS KMS (direct cloud integration) -# These are critical secrets that require cloud KMS -# ============================================================================ - -[[fields]] -encryption_backend = "awskms" -name = "master_key" -prompt = "Master encryption key (AWS KMS protected)" -required = false -sensitive = true -type = "password" - - [fields.encryption_config] - key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012" - region = "us-east-1" - -[[fields]] -encryption_backend = "awskms" -name = "root_token" -prompt = "Root access token (AWS KMS protected)" -required = false -sensitive = true -type = "password" - - [fields.encryption_config] - key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012" - region = "us-east-1" - - # ============================================================================ - # Certificate and Key Material - # Field-level backend: SecretumVault (post-quantum, enterprise) - # Uses Transit Engine for encryption with PQC support - # ============================================================================ - -[[fields]] -encryption_backend = "secretumvault" -name = "tls_cert" -prompt = "TLS Certificate (SecretumVault with PQC)" -required = false -sensitive = true -type = "editor" - -[[fields]] -encryption_backend = "secretumvault" -name = "tls_key" -prompt = "TLS Private Key (SecretumVault with PQC)" -required = false -sensitive = true -type = "editor" - -# ============================================================================ -# Configuration Summary -# ============================================================================ -# This form demonstrates backend selection per field: -# -# Age Backend: API keys (simple, local) -# SOPS Backend: Database password (team collaboration) -# AWS KMS: Critical production tokens -# SecretumVault: TLS materials (post-quantum ready) -# -# Same form works for all environments with proper CLI flags: -# --encrypt --backend age # Dev -# --encrypt --backend sops # Staging (requires .sops.yaml) -# --encrypt --backend secretumvault # Production -# -# Field-level encryption_backend overrides CLI --backend for that specific field -# This allows mixing backends even within the same form execution. diff --git a/examples/08-encryption/simple-login.ncl b/examples/08-encryption/simple-login.ncl new file mode 100644 index 0000000..53b7707 --- /dev/null +++ b/examples/08-encryption/simple-login.ncl @@ -0,0 +1,9 @@ +{ + name = "login", + description = "Simple login form with password encryption", + display_mode = "complete", + elements = [ + { type = "text", name = "username", prompt = "Username", required = true, sensitive = false }, + { type = "password", name = "password", prompt = "Password", required = true }, + ], +} diff --git a/examples/08-encryption/simple-login.toml b/examples/08-encryption/simple-login.toml deleted file mode 100644 index 0b7f459..0000000 --- a/examples/08-encryption/simple-login.toml +++ /dev/null @@ -1,40 +0,0 @@ -# Simple Login Form with Encryption -# -# Minimal example for quick testing of encryption features -# -# Test redaction (no service required): -# typedialog form examples/08-encryption/simple-login.toml --redact --format json -# -# Test Age encryption (requires ~/.age/key.txt): -# typedialog form examples/08-encryption/simple-login.toml \ -# --encrypt --backend age --key-file ~/.age/key.txt --format json -# -# Test SOPS encryption (requires .sops.yaml and KMS credentials): -# export AWS_REGION=us-east-1 -# typedialog form examples/08-encryption/simple-login.toml \ -# --encrypt --backend sops --format json -# -# Test SecretumVault encryption (requires vault service): -# export VAULT_ADDR=https://vault:8200 -# export VAULT_TOKEN=hvs.token... -# typedialog form examples/08-encryption/simple-login.toml \ -# --encrypt --backend secretumvault --format json -# - -description = "Simple login form with password encryption" -display_mode = "complete" -name = "login" - -[[fields]] -name = "username" -prompt = "Username" -required = true -sensitive = false -type = "text" - -[[fields]] -name = "password" -prompt = "Password" -required = true -type = "password" -# sensitive: auto-detected as true from FieldType::Password diff --git a/examples/08-nickel-roundtrip/02-roundtrip-cli.sh b/examples/08-nickel-roundtrip/02-roundtrip-cli.sh index 1cdaced..38bebf7 100755 --- a/examples/08-nickel-roundtrip/02-roundtrip-cli.sh +++ b/examples/08-nickel-roundtrip/02-roundtrip-cli.sh @@ -18,7 +18,7 @@ cp config.ncl "config.ncl.backup.$(date +%Y%m%d_%H%M%S)" # Run roundtrip with CLI backend cargo run -p typedialog --release -- nickel-roundtrip \ --input config.ncl \ - --form ci-form.toml \ + --form ci-form.ncl \ --output config.ncl \ --ncl-template config.ncl.j2 \ --verbose diff --git a/examples/08-nickel-roundtrip/03-roundtrip-tui.sh b/examples/08-nickel-roundtrip/03-roundtrip-tui.sh index 7839242..b4b46d2 100755 --- a/examples/08-nickel-roundtrip/03-roundtrip-tui.sh +++ b/examples/08-nickel-roundtrip/03-roundtrip-tui.sh @@ -18,7 +18,7 @@ cp config.ncl "config.ncl.backup.$(date +%Y%m%d_%H%M%S)" # Run roundtrip with TUI backend cargo run -p typedialog-tui --release -- nickel-roundtrip \ --input config.ncl \ - --form ci-form.toml \ + --form ci-form.ncl \ --output config.ncl \ --ncl-template config.ncl.j2 \ --verbose diff --git a/examples/08-nickel-roundtrip/04-roundtrip-web.sh b/examples/08-nickel-roundtrip/04-roundtrip-web.sh index a03d6b7..61e7ece 100755 --- a/examples/08-nickel-roundtrip/04-roundtrip-web.sh +++ b/examples/08-nickel-roundtrip/04-roundtrip-web.sh @@ -34,7 +34,7 @@ sleep 2 && open "http://localhost:8080" & # Run roundtrip with Web backend cargo run -p typedialog-web --release -- nickel-roundtrip \ --input config.ncl \ - --form ci-form.toml \ + --form ci-form.ncl \ --output config.ncl \ --ncl-template config.ncl.j2 \ --verbose diff --git a/examples/08-nickel-roundtrip/ci-form.ncl b/examples/08-nickel-roundtrip/ci-form.ncl new file mode 100644 index 0000000..b8912aa --- /dev/null +++ b/examples/08-nickel-roundtrip/ci-form.ncl @@ -0,0 +1,59 @@ +{ + name = "CI Configuration Editor", + description = "Interactive form for editing CI/CD configuration", + display_mode = "complete", + elements = [ + # PROJECT INFORMATION + { type = "section_header", name = "project_header", title = "📦 Project Information", border_top = true, border_bottom = true }, + { type = "text", name = "project_name", prompt = "Project Name", required = true, default = "my-rust-project", nickel_path = ["project", "name"] }, + { type = "text", name = "project_description", prompt = "Project Description", required = true, default = "A Rust project with CI/CD", nickel_path = ["project", "description"] }, + { type = "text", name = "project_repository", prompt = "Repository URL", required = true, default = "https://github.com/example/my-rust-project", nickel_path = ["project", "repository"] }, + + # GITHUB ACTIONS CONFIGURATION + { type = "section_header", name = "github_header", title = "🚀 GitHub Actions Configuration", border_top = true, border_bottom = true }, + { type = "confirm", name = "enable_github_actions", prompt = "Enable GitHub Actions CI?", default = true, nickel_path = ["ci", "github_actions", "enabled"] }, + { type = "text", name = "parallel_jobs", prompt = "Parallel Jobs", default = "4", when = "enable_github_actions == true", nickel_path = ["ci", "github_actions", "parallel_jobs"], help = "Number of parallel CI jobs (1-20)" }, + { type = "text", name = "timeout_minutes", prompt = "Job Timeout (minutes)", default = "60", when = "enable_github_actions == true", nickel_path = ["ci", "github_actions", "timeout_minutes"], help = "Maximum duration for each job" }, + + # RUST CONFIGURATION + { type = "section_header", name = "rust_header", title = "🦀 Rust Configuration", border_top = true, border_bottom = true, when = "enable_github_actions == true" }, + { type = "select", name = "rust_version", prompt = "Rust Version", default = "stable", when = "enable_github_actions == true", nickel_path = ["ci", "github_actions", "rust", "version"], options = [ + { value = "stable", label = "Stable - Latest stable release" }, + { value = "nightly", label = "Nightly - Cutting edge features" }, + { value = "1.70.0", label = "1.70.0 - Specific version" }, + ] + }, + + # CACHING + { type = "section_header", name = "cache_header", title = "💾 Build Cache", border_top = true, border_bottom = true, when = "enable_github_actions == true" }, + { type = "confirm", name = "enable_cache", prompt = "Enable dependency caching?", default = false, when = "enable_github_actions == true", nickel_path = ["ci", "github_actions", "cache", "enabled"], help = "Cache Cargo dependencies to speed up builds" }, + + # TOOLS CONFIGURATION + { type = "section_header", name = "tools_header", title = "🔧 CI Tools", border_top = true, border_bottom = true, when = "enable_github_actions == true" }, + { type = "confirm", name = "enable_clippy", prompt = "Enable cargo clippy (linter)?", default = true, when = "enable_github_actions == true", nickel_path = ["ci", "github_actions", "tools", "clippy", "enabled"], help = "Run Rust linter on code" }, + { type = "text", name = "clippy_args", prompt = "Clippy Arguments", default = "-D warnings", when = "enable_github_actions == true && enable_clippy == true", nickel_path = ["ci", "github_actions", "tools", "clippy", "args"], help = "Command-line arguments for clippy" }, + { type = "confirm", name = "enable_rustfmt", prompt = "Enable rustfmt (formatter)?", default = true, when = "enable_github_actions == true", nickel_path = ["ci", "github_actions", "tools", "rustfmt", "enabled"], help = "Check code formatting" }, + { type = "select", name = "rustfmt_edition", prompt = "Rust Edition for rustfmt", default = "2021", when = "enable_github_actions == true && enable_rustfmt == true", nickel_path = ["ci", "github_actions", "tools", "rustfmt", "edition"], options = [ + { value = "2015", label = "2015" }, + { value = "2018", label = "2018" }, + { value = "2021", label = "2021" }, + ] + }, + { type = "confirm", name = "enable_cargo_audit", prompt = "Enable cargo-audit (security)?", default = false, when = "enable_github_actions == true", nickel_path = ["ci", "github_actions", "tools", "cargo_audit", "enabled"], help = "Scan for security vulnerabilities" }, + + # DEPLOYMENT + { type = "section_header", name = "deployment_header", title = "🚢 Deployment", border_top = true, border_bottom = true }, + { type = "confirm", name = "enable_deployment", prompt = "Enable deployment?", default = false, nickel_path = ["deployment", "enabled"], help = "Automatically deploy on successful builds" }, + { type = "select", name = "deployment_environment", prompt = "Deployment Environment", default = "production", when = "enable_deployment == true", nickel_path = ["deployment", "environment"], options = [ + { value = "development", label = "Development" }, + { value = "staging", label = "Staging" }, + { value = "production", label = "Production" }, + ] + }, + { type = "confirm", name = "auto_deploy", prompt = "Auto-deploy on main branch?", default = false, when = "enable_deployment == true", nickel_path = ["deployment", "auto_deploy"], help = "Automatically deploy when main branch is updated" }, + + # SUMMARY + { type = "section_header", name = "summary_header", title = "✅ Review & Save", border_top = true }, + { type = "section", name = "summary", content = "Review your configuration above. Click Submit to save and see what changed." }, + ], +} diff --git a/examples/08-nickel-roundtrip/ci-form.toml b/examples/08-nickel-roundtrip/ci-form.toml deleted file mode 100644 index 0123081..0000000 --- a/examples/08-nickel-roundtrip/ci-form.toml +++ /dev/null @@ -1,237 +0,0 @@ -description = "Interactive form for editing CI/CD configuration" -display_mode = "complete" -name = "CI Configuration Editor" - -# ============================================================================= -# PROJECT INFORMATION -# ============================================================================= - -[[elements]] -border_bottom = true -border_top = true -name = "project_header" -title = "📦 Project Information" -type = "section_header" - -[[elements]] -default = "my-rust-project" -name = "project_name" -nickel_path = ["project", "name"] -prompt = "Project Name" -required = true -type = "text" - -[[elements]] -default = "A Rust project with CI/CD" -name = "project_description" -nickel_path = ["project", "description"] -prompt = "Project Description" -required = true -type = "text" - -[[elements]] -default = "https://github.com/example/my-rust-project" -name = "project_repository" -nickel_path = ["project", "repository"] -prompt = "Repository URL" -required = true -type = "text" - -# ============================================================================= -# GITHUB ACTIONS CONFIGURATION -# ============================================================================= - -[[elements]] -border_bottom = true -border_top = true -name = "github_header" -title = "🚀 GitHub Actions Configuration" -type = "section_header" - -[[elements]] -default = true -name = "enable_github_actions" -nickel_path = ["ci", "github_actions", "enabled"] -prompt = "Enable GitHub Actions CI?" -type = "confirm" - -[[elements]] -default = "4" -help = "Number of parallel CI jobs (1-20)" -name = "parallel_jobs" -nickel_path = ["ci", "github_actions", "parallel_jobs"] -prompt = "Parallel Jobs" -type = "text" -when = "enable_github_actions == true" - -[[elements]] -default = "60" -help = "Maximum duration for each job" -name = "timeout_minutes" -nickel_path = ["ci", "github_actions", "timeout_minutes"] -prompt = "Job Timeout (minutes)" -type = "text" -when = "enable_github_actions == true" - -# ============================================================================= -# RUST CONFIGURATION -# ============================================================================= - -[[elements]] -border_bottom = true -border_top = true -name = "rust_header" -title = "🦀 Rust Configuration" -type = "section_header" -when = "enable_github_actions == true" - -[[elements]] -default = "stable" -name = "rust_version" -nickel_path = ["ci", "github_actions", "rust", "version"] -options = [ - { value = "stable", label = "Stable - Latest stable release" }, - { value = "nightly", label = "Nightly - Cutting edge features" }, - { value = "1.70.0", label = "1.70.0 - Specific version" }, -] -prompt = "Rust Version" -type = "select" -when = "enable_github_actions == true" - -# ============================================================================= -# CACHING -# ============================================================================= - -[[elements]] -border_bottom = true -border_top = true -name = "cache_header" -title = "💾 Build Cache" -type = "section_header" -when = "enable_github_actions == true" - -[[elements]] -default = false -help = "Cache Cargo dependencies to speed up builds" -name = "enable_cache" -nickel_path = ["ci", "github_actions", "cache", "enabled"] -prompt = "Enable dependency caching?" -type = "confirm" -when = "enable_github_actions == true" - -# ============================================================================= -# TOOLS CONFIGURATION -# ============================================================================= - -[[elements]] -border_bottom = true -border_top = true -name = "tools_header" -title = "🔧 CI Tools" -type = "section_header" -when = "enable_github_actions == true" - -[[elements]] -default = true -help = "Run Rust linter on code" -name = "enable_clippy" -nickel_path = ["ci", "github_actions", "tools", "clippy", "enabled"] -prompt = "Enable cargo clippy (linter)?" -type = "confirm" -when = "enable_github_actions == true" - -[[elements]] -default = "-D warnings" -help = "Command-line arguments for clippy" -name = "clippy_args" -nickel_path = ["ci", "github_actions", "tools", "clippy", "args"] -prompt = "Clippy Arguments" -type = "text" -when = "enable_github_actions == true && enable_clippy == true" - -[[elements]] -default = true -help = "Check code formatting" -name = "enable_rustfmt" -nickel_path = ["ci", "github_actions", "tools", "rustfmt", "enabled"] -prompt = "Enable rustfmt (formatter)?" -type = "confirm" -when = "enable_github_actions == true" - -[[elements]] -default = "2021" -name = "rustfmt_edition" -nickel_path = ["ci", "github_actions", "tools", "rustfmt", "edition"] -options = [ - { value = "2015", label = "2015" }, - { value = "2018", label = "2018" }, - { value = "2021", label = "2021" }, -] -prompt = "Rust Edition for rustfmt" -type = "select" -when = "enable_github_actions == true && enable_rustfmt == true" - -[[elements]] -default = false -help = "Scan for security vulnerabilities" -name = "enable_cargo_audit" -nickel_path = ["ci", "github_actions", "tools", "cargo_audit", "enabled"] -prompt = "Enable cargo-audit (security)?" -type = "confirm" -when = "enable_github_actions == true" - -# ============================================================================= -# DEPLOYMENT -# ============================================================================= - -[[elements]] -border_bottom = true -border_top = true -name = "deployment_header" -title = "🚢 Deployment" -type = "section_header" - -[[elements]] -default = false -help = "Automatically deploy on successful builds" -name = "enable_deployment" -nickel_path = ["deployment", "enabled"] -prompt = "Enable deployment?" -type = "confirm" - -[[elements]] -default = "production" -name = "deployment_environment" -nickel_path = ["deployment", "environment"] -options = [ - { value = "development", label = "Development" }, - { value = "staging", label = "Staging" }, - { value = "production", label = "Production" }, -] -prompt = "Deployment Environment" -type = "select" -when = "enable_deployment == true" - -[[elements]] -default = false -help = "Automatically deploy when main branch is updated" -name = "auto_deploy" -nickel_path = ["deployment", "auto_deploy"] -prompt = "Auto-deploy on main branch?" -type = "confirm" -when = "enable_deployment == true" - -# ============================================================================= -# SUMMARY -# ============================================================================= - -[[elements]] -border_top = true -name = "summary_header" -title = "✅ Review & Save" -type = "section_header" - -[[elements]] -content = "Review your configuration above. Click Submit to save and see what changed." -name = "summary" -type = "section" diff --git a/examples/09-templates/employee_onboarding/employee_onboarding_form.ncl b/examples/09-templates/employee_onboarding/employee_onboarding_form.ncl new file mode 100644 index 0000000..1c8ea6f --- /dev/null +++ b/examples/09-templates/employee_onboarding/employee_onboarding_form.ncl @@ -0,0 +1,13 @@ +let header = import "../../05-fragments/header.ncl" in +let employee_info = import "../../05-fragments/employee_info_section.ncl" in +let support = import "../../05-fragments/support_section.ncl" in +let agreement = import "../../05-fragments/agreement_section.ncl" in +{ + name = "Employee Onboarding Form", + description = "Compose employee form from reusable fragments", + elements = + header.elements + @ employee_info.elements + @ support.elements + @ agreement.elements, +} diff --git a/examples/09-templates/employee_onboarding/employee_onboarding_form.toml b/examples/09-templates/employee_onboarding/employee_onboarding_form.toml deleted file mode 100644 index d9bc932..0000000 --- a/examples/09-templates/employee_onboarding/employee_onboarding_form.toml +++ /dev/null @@ -1,30 +0,0 @@ -description = "Compose employee form from reusable fragments" -name = "Employee Onboarding Form" - -# Include main header (path relative to this file's directory) -[[items]] -includes = ["fragments/header.toml"] -name = "header_group" -order = 1 -type = "group" - -# Employee information section with fields -[[items]] -includes = ["fragments/employee_info_section.toml"] -name = "employee_group" -order = 2 -type = "group" - -# Reuse support section from previous form -[[items]] -includes = ["fragments/support_section.toml"] -name = "support_group" -order = 3 -type = "group" - -# Reuse agreement section from previous form -[[items]] -includes = ["fragments/agreement_section.toml"] -name = "agreement_group" -order = 4 -type = "group" diff --git a/examples/09-templates/onboarding_template.ncl b/examples/09-templates/onboarding_template.ncl new file mode 100644 index 0000000..5ed03d7 --- /dev/null +++ b/examples/09-templates/onboarding_template.ncl @@ -0,0 +1,28 @@ +{ + name = "employee_onboarding", + description = "Employee onboarding with dynamic templates", + elements = [ + { type = "section", name = "welcome", template = "Welcome to the team, {{ env.USER }}!" }, + { type = "section", name = "instructions", content = "Please complete the following information to get started." }, + { type = "text", name = "full_name", prompt = "What is your full name?", required = true }, + { type = "text", name = "email", prompt = "Enter your work email address", required = true, default = "{{ env.USER }}@company.com" }, + { type = "select", name = "department", prompt = "Which department are you joining?", required = true, options = [ + { value = "Engineering", label = "💻 Engineering" }, + { value = "Marketing", label = "📢 Marketing" }, + { value = "Sales", label = "💼 Sales" }, + { value = "Support", label = "🛠️ Support" }, + { value = "HR", label = "👥 Human Resources" }, + ] + }, + { type = "date", name = "start_date", prompt = "What is your start date?", required = true }, + { type = "text", name = "manager_name", prompt = "Who is your manager?", required = true }, + { type = "select", name = "office_location", prompt = "Which office location?", required = true, options = [ + { value = "New York", label = "🗽 New York" }, + { value = "San Francisco", label = "🌉 San Francisco" }, + { value = "London", label = "🇬🇧 London" }, + { value = "Remote", label = "🏠 Remote" }, + ] + }, + { type = "footer", name = "closing", content = "Thank you for completing your onboarding! Welcome aboard!", border_top = true }, + ], +} diff --git a/examples/09-templates/onboarding_template.toml b/examples/09-templates/onboarding_template.toml deleted file mode 100644 index 71af253..0000000 --- a/examples/09-templates/onboarding_template.toml +++ /dev/null @@ -1,68 +0,0 @@ -description = "Employee onboarding with dynamic templates" -name = "employee_onboarding" - -[[elements]] -name = "welcome" -template = "Welcome to the team, {{ env.USER }}!" -type = "section" - -[[elements]] -content = "Please complete the following information to get started." -name = "instructions" -type = "section" - -[[elements]] -name = "full_name" -prompt = "What is your full name?" -required = true -type = "text" - -[[elements]] -default = "{{ env.USER }}@company.com" -name = "email" -prompt = "Enter your work email address" -required = true -type = "text" - -[[elements]] -name = "department" -options = [ - { value = "Engineering", label = "💻 Engineering" }, - { value = "Marketing", label = "📢 Marketing" }, - { value = "Sales", label = "💼 Sales" }, - { value = "Support", label = "🛠️ Support" }, - { value = "HR", label = "👥 Human Resources" }, -] -prompt = "Which department are you joining?" -required = true -type = "select" - -[[elements]] -name = "start_date" -prompt = "What is your start date?" -required = true -type = "date" - -[[elements]] -name = "manager_name" -prompt = "Who is your manager?" -required = true -type = "text" - -[[elements]] -name = "office_location" -options = [ - { value = "New York", label = "🗽 New York" }, - { value = "San Francisco", label = "🌉 San Francisco" }, - { value = "London", label = "🇬🇧 London" }, - { value = "Remote", label = "🏠 Remote" }, -] -prompt = "Which office location?" -required = true -type = "select" - -[[elements]] -border_top = true -content = "Thank you for completing your onboarding! Welcome aboard!" -name = "closing" -type = "footer" diff --git a/examples/09-templates/user_registration/registration_form.ncl b/examples/09-templates/user_registration/registration_form.ncl new file mode 100644 index 0000000..efa99a8 --- /dev/null +++ b/examples/09-templates/user_registration/registration_form.ncl @@ -0,0 +1,25 @@ +{ + name = "user_registration", + description = "Simple user registration form", + elements = [ + { type = "text", name = "username", prompt = "Enter username", placeholder = "john_doe" }, + { type = "text", name = "email", prompt = "Enter email", placeholder = "user@example.com" }, + { type = "password", name = "password", prompt = "Enter password" }, + { type = "password", name = "confirm_password", prompt = "Confirm password" }, + { type = "confirm", name = "agree_terms", prompt = "Do you agree to the terms and conditions?", default = false }, + { type = "confirm", name = "newsletter", prompt = "Subscribe to our newsletter?", default = true }, + { type = "select", name = "role", prompt = "Select your role", options = [ + { value = "Admin", label = "👑 Administrator" }, + { value = "User", label = "👤 Regular User" }, + { value = "Guest", label = "👁️ Guest" }, + ] + }, + { type = "multiselect", name = "interests", prompt = "Select your interests", options = [ + { value = "Technology", label = "💻 Technology" }, + { value = "Design", label = "🎨 Design" }, + { value = "Business", label = "💼 Business" }, + { value = "Marketing", label = "📢 Marketing" }, + ] + }, + ], +} diff --git a/examples/09-templates/user_registration/registration_form.toml b/examples/09-templates/user_registration/registration_form.toml deleted file mode 100644 index d23e1cb..0000000 --- a/examples/09-templates/user_registration/registration_form.toml +++ /dev/null @@ -1,57 +0,0 @@ -description = "Simple user registration form" -name = "user_registration" - -[[fields]] -name = "username" -placeholder = "john_doe" -prompt = "Enter username" -type = "text" - -[[fields]] -name = "email" -placeholder = "user@example.com" -prompt = "Enter email" -type = "text" - -[[fields]] -name = "password" -prompt = "Enter password" -type = "password" - -[[fields]] -name = "confirm_password" -prompt = "Confirm password" -type = "password" - -[[fields]] -default = "false" -name = "agree_terms" -prompt = "Do you agree to the terms and conditions?" -type = "confirm" - -[[fields]] -default = "true" -name = "newsletter" -prompt = "Subscribe to our newsletter?" -type = "confirm" - -[[fields]] -name = "role" -options = [ - { value = "Admin", label = "👑 Administrator" }, - { value = "User", label = "👤 Regular User" }, - { value = "Guest", label = "👁️ Guest" }, -] -prompt = "Select your role" -type = "select" - -[[fields]] -name = "interests" -options = [ - { value = "Technology", label = "💻 Technology" }, - { value = "Design", label = "🎨 Design" }, - { value = "Business", label = "💼 Business" }, - { value = "Marketing", label = "📢 Marketing" }, -] -prompt = "Select your interests" -type = "multiselect" diff --git a/examples/09-templates/user_registration/user_registration_form.ncl b/examples/09-templates/user_registration/user_registration_form.ncl new file mode 100644 index 0000000..9df0eaf --- /dev/null +++ b/examples/09-templates/user_registration/user_registration_form.ncl @@ -0,0 +1,35 @@ +{ + name = "user_registration", + description = "User registration form with autocompletion examples", + elements = [ + { type = "text", name = "username", prompt = "Enter username", required = true }, + { type = "text", name = "email", prompt = "Enter your email address", required = true, placeholder = "user@example.com" }, + { type = "password", name = "password", prompt = "Enter password", required = true }, + { type = "password", name = "confirm_password", prompt = "Confirm password", required = true }, + { type = "select", name = "country", prompt = "Select your country", options = [ + { value = "United States", label = "🇺🇸 United States" }, + { value = "Canada", label = "🇨🇦 Canada" }, + { value = "Mexico", label = "🇲🇽 Mexico" }, + { value = "United Kingdom", label = "🇬🇧 United Kingdom" }, + { value = "Germany", label = "🇩🇪 Germany" }, + { value = "France", label = "🇫🇷 France" }, + { value = "Spain", label = "🇪🇸 Spain" }, + { value = "Italy", label = "🇮🇹 Italy" }, + { value = "Other", label = "🌍 Other" }, + ] + }, + { type = "text", name = "company", prompt = "Enter company name", placeholder = "Acme Corporation" }, + { type = "text", name = "website", prompt = "Enter company website", placeholder = "https://example.com" }, + { type = "confirm", name = "subscribe", prompt = "Subscribe to newsletter?", default = true }, + { type = "multiselect", name = "interests", prompt = "Select your interests", options = [ + { value = "Technology", label = "💻 Technology" }, + { value = "Design", label = "🎨 Design" }, + { value = "Marketing", label = "📢 Marketing" }, + { value = "Business", label = "💼 Business" }, + { value = "Development", label = "🔧 Development" }, + { value = "Data Science", label = "📊 Data Science" }, + ] + }, + { type = "confirm", name = "agreed_terms", prompt = "Do you agree to the terms and conditions?", default = false }, + ], +} diff --git a/examples/09-templates/user_registration/user_registration_form.toml b/examples/09-templates/user_registration/user_registration_form.toml deleted file mode 100644 index 79424be..0000000 --- a/examples/09-templates/user_registration/user_registration_form.toml +++ /dev/null @@ -1,80 +0,0 @@ -description = "User registration form with autocompletion examples" -name = "user_registration" - -[[fields]] -name = "username" -prompt = "Enter username" -required = true -type = "text" - -[[fields]] -name = "email" -placeholder = "user@example.com" -prompt = "Enter your email address" -required = true -type = "text" - -[[fields]] -name = "password" -prompt = "Enter password" -required = true -type = "password" - -[[fields]] -name = "confirm_password" -prompt = "Confirm password" -required = true -type = "password" - -[[fields]] -name = "country" -options = [ - { value = "United States", label = "🇺🇸 United States" }, - { value = "Canada", label = "🇨🇦 Canada" }, - { value = "Mexico", label = "🇲🇽 Mexico" }, - { value = "United Kingdom", label = "🇬🇧 United Kingdom" }, - { value = "Germany", label = "🇩🇪 Germany" }, - { value = "France", label = "🇫🇷 France" }, - { value = "Spain", label = "🇪🇸 Spain" }, - { value = "Italy", label = "🇮🇹 Italy" }, - { value = "Other", label = "🌍 Other" }, -] -prompt = "Select your country" -type = "select" - -[[fields]] -name = "company" -placeholder = "Acme Corporation" -prompt = "Enter company name" -type = "text" - -[[fields]] -name = "website" -placeholder = "https://example.com" -prompt = "Enter company website" -type = "text" - -[[fields]] -default = true -name = "subscribe" -prompt = "Subscribe to newsletter?" -type = "confirm" - -[[fields]] -name = "interests" -options = [ - { value = "Technology", label = "💻 Technology" }, - { value = "Design", label = "🎨 Design" }, - { value = "Marketing", label = "📢 Marketing" }, - { value = "Business", label = "💼 Business" }, - { value = "Development", label = "🔧 Development" }, - { value = "Data Science", label = "📊 Data Science" }, -] -prompt = "Select your interests" -type = "multiselect" - -[[fields]] -default = false -name = "agreed_terms" -prompt = "Do you agree to the terms and conditions?" -type = "confirm" diff --git a/examples/11-prov-gen/mode-b-config/project-spec.ncl b/examples/11-prov-gen/mode-b-config/project-spec.ncl new file mode 100644 index 0000000..0fa6be0 --- /dev/null +++ b/examples/11-prov-gen/mode-b-config/project-spec.ncl @@ -0,0 +1,78 @@ +{ + project = { + name = "microservice-platform", + description = "Distributed microservice platform with event streaming", + type = "Microservice", + }, + infrastructure = { + ssh = true, + databases = [{ type = "postgres", required = true }], + monitoring = ["prometheus", "grafana"], + cloud_providers = ["aws", "gcp"], + cache_enabled = true, + }, + features = { + api_gateway = { + enabled = true, + description = "REST API gateway with rate limiting", + fields = [ + { name = "bind_address", type = "Text", prompt = "API Gateway bind address", default = "0.0.0.0:8080", required = true, help = "Interface and port for API requests" }, + { name = "rate_limit", type = "Number", prompt = "Rate limit (requests/second)", default = 100, min = 1, max = 10000, help = "Maximum requests per second per client" }, + { name = "timeout_seconds", type = "Number", prompt = "Request timeout", default = 30, min = 5, max = 300 }, + ], + }, + event_streaming = { + enabled = true, + description = "Event-driven architecture with message queue", + fields = [ + { name = "broker_url", type = "Text", prompt = "Message broker URL", required = true, placeholder = "kafka://localhost:9092", help = "Connection string for event broker" }, + { name = "consumer_group", type = "Text", prompt = "Consumer group ID", default = "microservice-platform", required = true }, + { name = "max_concurrent_consumers", type = "Number", prompt = "Max concurrent consumers", default = 10, min = 1, max = 100 }, + ], + }, + authentication = { + enabled = true, + description = "JWT-based authentication with role-based access", + fields = [ + { name = "jwt_secret", type = "Password", prompt = "JWT signing secret", required = true, sensitive = true, encryption_backend = "age", help = "Secret key for JWT token signing and verification" }, + { name = "jwt_expiry_hours", type = "Number", prompt = "JWT token expiry (hours)", default = 24, min = 1, max = 720 }, + { name = "allow_refresh_tokens", type = "Confirm", prompt = "Allow token refresh?", default = true, help = "Enable token refresh mechanism" }, + ], + }, + observability = { + enabled = true, + description = "Comprehensive logging and tracing", + fields = [ + { name = "log_level", type = "Select", prompt = "Log level", default = "info", options = ["trace", "debug", "info", "warn", "error"] }, + { name = "enable_distributed_tracing", type = "Confirm", prompt = "Enable distributed tracing?", default = true }, + { name = "trace_sample_rate", type = "Number", prompt = "Trace sampling rate (0.0-1.0)", default = 0.1, min = 0.0, max = 1.0, help = "Percentage of requests to trace (0.0-1.0)" }, + ], + }, + database_migrations = { + enabled = true, + description = "Database schema and migration management", + fields = [ + { name = "migration_directory", type = "Text", prompt = "Migrations directory", default = "./migrations", help = "Path to SQL migration files" }, + { name = "auto_migrate_on_startup", type = "Confirm", prompt = "Auto-migrate on startup?", default = false, help = "Automatically run pending migrations when service starts" }, + ], + }, + }, + constraints = { + api_gateway = { + bind_address = { min_length = 3, max_length = 50 }, + rate_limit = { min = 1, max = 10000 }, + }, + event_streaming = { + consumer_group = { min_length = 1, max_length = 100 }, + }, + authentication = { + jwt_secret = { min_length = 32, max_length = 256 }, + }, + observability = { + trace_sample_rate = { min = 0.0, max = 1.0 }, + }, + database_migrations = { + migration_directory = { min_length = 1, max_length = 255 }, + }, + }, +} diff --git a/examples/11-prov-gen/mode-b-config/project-spec.toml b/examples/11-prov-gen/mode-b-config/project-spec.toml deleted file mode 100644 index 7fc29fa..0000000 --- a/examples/11-prov-gen/mode-b-config/project-spec.toml +++ /dev/null @@ -1,181 +0,0 @@ -# Project Specification for Mode B Example -# This file defines a provisioning configuration without relying on Cargo.toml - -[project] -description = "Distributed microservice platform with event streaming" -name = "microservice-platform" -type = "Microservice" - -[infrastructure] -# SSH configuration for remote deployments -ssh = true - - # Database configuration - [[infrastructure.databases]] - required = true - type = "postgres" - - # Monitoring stack - monitoring = ["prometheus", "grafana"] - - # Cloud provider options - cloud_providers = ["aws", "gcp"] - - # Optional services - cache_enabled = true - - # Domain Features - define what your service actually does - -[features.api_gateway] -description = "REST API gateway with rate limiting" -enabled = true - - [[features.api_gateway.fields]] - default = "0.0.0.0:8080" - help = "Interface and port for API requests" - name = "bind_address" - prompt = "API Gateway bind address" - required = true - type = "Text" - - [[features.api_gateway.fields]] - default = 100 - help = "Maximum requests per second per client" - max = 10000 - min = 1 - name = "rate_limit" - prompt = "Rate limit (requests/second)" - type = "Number" - - [[features.api_gateway.fields]] - default = 30 - max = 300 - min = 5 - name = "timeout_seconds" - prompt = "Request timeout" - type = "Number" - -[features.event_streaming] -description = "Event-driven architecture with message queue" -enabled = true - - [[features.event_streaming.fields]] - help = "Connection string for event broker" - name = "broker_url" - placeholder = "kafka://localhost:9092" - prompt = "Message broker URL" - required = true - type = "Text" - - [[features.event_streaming.fields]] - default = "microservice-platform" - name = "consumer_group" - prompt = "Consumer group ID" - required = true - type = "Text" - - [[features.event_streaming.fields]] - default = 10 - max = 100 - min = 1 - name = "max_concurrent_consumers" - prompt = "Max concurrent consumers" - type = "Number" - -[features.authentication] -description = "JWT-based authentication with role-based access" -enabled = true - - [[features.authentication.fields]] - encryption_backend = "age" - help = "Secret key for JWT token signing and verification" - name = "jwt_secret" - prompt = "JWT signing secret" - required = true - sensitive = true - type = "Password" - - [[features.authentication.fields]] - default = 24 - max = 720 - min = 1 - name = "jwt_expiry_hours" - prompt = "JWT token expiry (hours)" - type = "Number" - - [[features.authentication.fields]] - default = true - help = "Enable token refresh mechanism" - name = "allow_refresh_tokens" - prompt = "Allow token refresh?" - type = "Confirm" - -[features.observability] -description = "Comprehensive logging and tracing" -enabled = true - - [[features.observability.fields]] - default = "info" - name = "log_level" - options = ["trace", "debug", "info", "warn", "error"] - prompt = "Log level" - type = "Select" - - [[features.observability.fields]] - default = true - name = "enable_distributed_tracing" - prompt = "Enable distributed tracing?" - type = "Confirm" - - [[features.observability.fields]] - default = 0.1 - help = "Percentage of requests to trace (0.0-1.0)" - max = 1.0 - min = 0.0 - name = "trace_sample_rate" - prompt = "Trace sampling rate (0.0-1.0)" - type = "Number" - -[features.database_migrations] -description = "Database schema and migration management" -enabled = true - - [[features.database_migrations.fields]] - default = "./migrations" - help = "Path to SQL migration files" - name = "migration_directory" - prompt = "Migrations directory" - type = "Text" - - [[features.database_migrations.fields]] - default = false - help = "Automatically run pending migrations when service starts" - name = "auto_migrate_on_startup" - prompt = "Auto-migrate on startup?" - type = "Confirm" - - # Constraints - single source of truth for validation bounds - -[constraints.api_gateway.bind_address] -max_length = 50 -min_length = 3 - -[constraints.api_gateway.rate_limit] -max = 10000 -min = 1 - -[constraints.event_streaming.consumer_group] -max_length = 100 -min_length = 1 - -[constraints.authentication.jwt_secret] -max_length = 256 -min_length = 32 - -[constraints.observability.trace_sample_rate] -max = 1.0 -min = 0.0 - -[constraints.database_migrations.migration_directory] -max_length = 255 -min_length = 1 diff --git a/examples/13-conditional-logic/conditional-demo.ncl b/examples/13-conditional-logic/conditional-demo.ncl new file mode 100644 index 0000000..c654b28 --- /dev/null +++ b/examples/13-conditional-logic/conditional-demo.ncl @@ -0,0 +1,84 @@ +let env_setup = import "./fragments/environment-setup.ncl" in +{ + name = "conditional_demo", + description = "Complete demonstration of conditional field visibility", + elements = + [ + # COMPARISON OPERATORS + { type = "select", name = "database_driver", prompt = "Select database driver", required = true, options = [ + { value = "sqlite", label = "SQLite (embedded)" }, + { value = "mysql", label = "MySQL" }, + { value = "postgresql", label = "PostgreSQL" }, + ] + }, + + # Equality (==) + { type = "text", name = "mysql_config", prompt = "MySQL connection string", placeholder = "mysql://localhost:3306/db", when = "database_driver == mysql" }, + + # Inequality (!=) + { type = "section", name = "server_warning", content = "⚠️ You selected a server-based database. Ensure the server is running.", when = "database_driver != sqlite" }, + + # NUMERIC COMPARISONS + { type = "text", name = "server_port", prompt = "Server port", required = true, default = "8080" }, + + # Greater than (>) + { type = "section", name = "high_port_warning", content = "⚠️ Port > 10000 is uncommon. Double-check your configuration.", when = "server_port > 10000" }, + + # Less than (<) + { type = "section", name = "privileged_port_warning", content = "⚠️ Port < 1024 requires root/admin privileges.", when = "server_port < 1024" }, + + # Greater than or equal (>=) + { type = "section", name = "standard_port_notice", content = "✓ Using standard user port range (>= 1024)", when = "server_port >= 1024" }, + + # Less than or equal (<=) + { type = "section", name = "low_port_range", content = "Using low port range (<= 5000)", when = "server_port <= 5000" }, + + # STRING OPERATORS + { type = "text", name = "project_url", prompt = "Project repository URL", placeholder = "https://github.com/user/repo" }, + + # startswith + { type = "section", name = "https_notice", content = "✓ Secure HTTPS URL detected", when = "project_url startswith https" }, + + # endswith + { type = "text", name = "github_specific", prompt = "GitHub Actions enabled?", when = "project_url endswith github.com" }, + + # contains + { type = "confirm", name = "gitlab_ci", prompt = "Enable GitLab CI integration?", when = "project_url contains gitlab" }, + + # ARRAY MEMBERSHIP (in) + { type = "multiselect", name = "detected_languages", 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 + { type = "select", name = "rust_toolchain", prompt = "Rust toolchain version", when = "rust in detected_languages", options = [ + { value = "stable", label = "Stable" }, + { value = "nightly", label = "Nightly" }, + { value = "beta", label = "Beta" }, + ] + }, + { type = "confirm", name = "python_venv", prompt = "Use Python virtual environment?", default = true, when = "python in detected_languages" }, + { type = "select", name = "nodejs_version", 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 + { type = "section", name = "dockerfile_exists_notice", content = "✓ Dockerfile found in current directory", when = "file_exists(Dockerfile)" }, + { type = "confirm", name = "create_dockerfile", prompt = "No Dockerfile found. Create one?", default = true, when = "!file_exists(Dockerfile)" }, + { type = "confirm", name = "use_existing_config", prompt = "Existing .env file found. Use existing configuration?", when = "file_exists(.env)" }, + ] + # env_setup group when = "!file_exists(.env)" — fragment has per-element conditions, inline as-is + @ env_setup.elements + @ [ + # COMBINED CONDITIONS + { type = "section", name = "rust_docker_setup", content = "🦀 Rust + Docker detected. Consider using rust:alpine base image.", when = "rust in detected_languages" }, + { type = "section", name = "production_ready_check", content = "✅ Production-ready configuration detected (HTTPS + standard port)", when = "server_port >= 1024" }, + ], +} diff --git a/examples/13-conditional-logic/conditional-demo.toml b/examples/13-conditional-logic/conditional-demo.toml deleted file mode 100644 index 2ecf798..0000000 --- a/examples/13-conditional-logic/conditional-demo.toml +++ /dev/null @@ -1,198 +0,0 @@ -# Conditional Logic Demo -# Demonstrates all supported conditional operators in TypeDialog - -description = "Complete demonstration of conditional field visibility" -name = "conditional_demo" - -# ==================== -# COMPARISON OPERATORS -# ==================== - -[[elements]] -name = "database_driver" -options = [ - { value = "sqlite", label = "SQLite (embedded)" }, - { value = "mysql", label = "MySQL" }, - { value = "postgresql", label = "PostgreSQL" }, -] -prompt = "Select database driver" -required = true -type = "select" - -# Equality (==) -[[elements]] -name = "mysql_config" -placeholder = "mysql://localhost:3306/db" -prompt = "MySQL connection string" -type = "text" -when = "database_driver == mysql" - -# Inequality (!=) -[[elements]] -content = "⚠️ You selected a server-based database. Ensure the server is running." -name = "server_warning" -type = "section" -when = "database_driver != sqlite" - -# ==================== -# NUMERIC COMPARISONS -# ==================== - -[[elements]] -default = "8080" -name = "server_port" -prompt = "Server port" -required = true -type = "text" - -# Greater than (>) -[[elements]] -content = "⚠️ Port > 10000 is uncommon. Double-check your configuration." -name = "high_port_warning" -type = "section" -when = "server_port > 10000" - -# Less than (<) -[[elements]] -content = "⚠️ Port < 1024 requires root/admin privileges." -name = "privileged_port_warning" -type = "section" -when = "server_port < 1024" - -# Greater than or equal (>=) -[[elements]] -content = "✓ Using standard user port range (>= 1024)" -name = "standard_port_notice" -type = "section" -when = "server_port >= 1024" - -# Less than or equal (<=) -[[elements]] -content = "Using low port range (<= 5000)" -name = "low_port_range" -type = "section" -when = "server_port <= 5000" - -# ==================== -# STRING OPERATORS -# ==================== - -[[elements]] -name = "project_url" -placeholder = "https://github.com/user/repo" -prompt = "Project repository URL" -type = "text" - -# startswith -[[elements]] -content = "✓ Secure HTTPS URL detected" -name = "https_notice" -type = "section" -when = "project_url startswith https" - -# endswith -[[elements]] -name = "github_specific" -prompt = "GitHub Actions enabled?" -type = "text" -when = "project_url endswith github.com" - -# contains -[[elements]] -name = "gitlab_ci" -prompt = "Enable GitLab CI integration?" -type = "confirm" -when = "project_url contains gitlab" - -# ==================== -# ARRAY MEMBERSHIP (in) -# ==================== - -[[elements]] -display_mode = "grid" -name = "detected_languages" -options = [ - { value = "rust", label = "🦀 Rust" }, - { value = "python", label = "🐍 Python" }, - { value = "javascript", label = "📜 JavaScript" }, - { value = "go", label = "🐹 Go" }, -] -prompt = "Which languages are used in your project?" -type = "multiselect" - -# Array membership check -[[elements]] -name = "rust_toolchain" -options = [ - { value = "stable", label = "Stable" }, - { value = "nightly", label = "Nightly" }, - { value = "beta", label = "Beta" }, -] -prompt = "Rust toolchain version" -type = "select" -when = "rust in detected_languages" - -[[elements]] -default = true -name = "python_venv" -prompt = "Use Python virtual environment?" -type = "confirm" -when = "python in detected_languages" - -[[elements]] -name = "nodejs_version" -options = [ - { value = "18", label = "Node.js 18 LTS" }, - { value = "20", label = "Node.js 20 LTS" }, - { value = "latest", label = "Latest" }, -] -prompt = "Node.js version" -type = "select" -when = "javascript in detected_languages" - -# ==================== -# FILE SYSTEM CONDITIONS -# ==================== - -# file_exists(path) -[[elements]] -content = "✓ Dockerfile found in current directory" -name = "dockerfile_exists_notice" -type = "section" -when = "file_exists(Dockerfile)" - -# !file_exists(path) - negation -[[elements]] -default = true -name = "create_dockerfile" -prompt = "No Dockerfile found. Create one?" -type = "confirm" -when = "!file_exists(Dockerfile)" - -[[elements]] -name = "use_existing_config" -prompt = "Existing .env file found. Use existing configuration?" -type = "confirm" -when = "file_exists(.env)" - -[[elements]] -includes = ["fragments/environment-setup.toml"] -name = "env_setup" -type = "group" -when = "!file_exists(.env)" - -# ==================== -# COMBINED CONDITIONS -# ==================== - -[[elements]] -content = "🦀 Rust + Docker detected. Consider using rust:alpine base image." -name = "rust_docker_setup" -type = "section" -when = "rust in detected_languages" - -[[elements]] -content = "✅ Production-ready configuration detected (HTTPS + standard port)" -name = "production_ready_check" -type = "section" -when = "server_port >= 1024" diff --git a/examples/13-conditional-logic/fragments/environment-setup.ncl b/examples/13-conditional-logic/fragments/environment-setup.ncl new file mode 100644 index 0000000..b03dcbd --- /dev/null +++ b/examples/13-conditional-logic/fragments/environment-setup.ncl @@ -0,0 +1,24 @@ +{ + elements = [ + { type = "section_header", name = "env_header", title = "🔧 Environment Configuration", border_top = true, border_bottom = true }, + { type = "select", name = "env_mode", prompt = "Environment mode", required = true, options = [ + { value = "development", label = "Development" }, + { value = "staging", label = "Staging" }, + { value = "production", label = "Production" }, + ] + }, + { type = "select", name = "log_level", 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" }, + ] + }, + { type = "confirm", name = "debug_mode", prompt = "Enable debug mode?", default = true, when = "env_mode == development" }, + { type = "password", name = "api_key", prompt = "API Key", required = true, help = "Your application API key" }, + { type = "text", name = "database_url", prompt = "Database URL", required = true, placeholder = "postgresql://localhost:5432/mydb" }, + { type = "text", name = "redis_url", prompt = "Redis URL", placeholder = "redis://localhost:6379", when = "env_mode == production" }, + { type = "text", name = "sentry_dsn", prompt = "Sentry DSN (error tracking)", when = "env_mode == production", help = "Leave empty to disable error tracking" }, + ], +} diff --git a/examples/13-conditional-logic/fragments/environment-setup.toml b/examples/13-conditional-logic/fragments/environment-setup.toml deleted file mode 100644 index 1a05418..0000000 --- a/examples/13-conditional-logic/fragments/environment-setup.toml +++ /dev/null @@ -1,72 +0,0 @@ -# Environment Setup Fragment -# Loaded conditionally when .env file doesn't exist - -description = "Environment variables configuration" -display_mode = "complete" -name = "environment_setup" - -[[elements]] -border_bottom = true -border_top = true -name = "env_header" -title = "🔧 Environment Configuration" -type = "section_header" - -[[elements]] -name = "env_mode" -options = [ - { value = "development", label = "Development" }, - { value = "staging", label = "Staging" }, - { value = "production", label = "Production" }, -] -prompt = "Environment mode" -required = true -type = "select" - -[[elements]] -default = "info" -name = "log_level" -options = [ - { value = "trace", label = "Trace (verbose)" }, - { value = "debug", label = "Debug" }, - { value = "info", label = "Info" }, - { value = "warn", label = "Warning" }, - { value = "error", label = "Error" }, -] -prompt = "Log level" -type = "select" - -[[elements]] -default = true -name = "debug_mode" -prompt = "Enable debug mode?" -type = "confirm" -when = "env_mode == development" - -[[elements]] -help = "Your application API key" -name = "api_key" -prompt = "API Key" -required = true -type = "password" - -[[elements]] -name = "database_url" -placeholder = "postgresql://localhost:5432/mydb" -prompt = "Database URL" -required = true -type = "text" - -[[elements]] -name = "redis_url" -placeholder = "redis://localhost:6379" -prompt = "Redis URL" -type = "text" -when = "env_mode == production" - -[[elements]] -help = "Leave empty to disable error tracking" -name = "sentry_dsn" -prompt = "Sentry DSN (error tracking)" -type = "text" -when = "env_mode == production" diff --git a/examples/14-validators-and-contracts/advanced-validators.ncl b/examples/14-validators-and-contracts/advanced-validators.ncl new file mode 100644 index 0000000..ad48416 --- /dev/null +++ b/examples/14-validators-and-contracts/advanced-validators.ncl @@ -0,0 +1,62 @@ +{ + name = "Advanced Validators & Complex Constraints", + description = "Master complex validation patterns and field dependencies", + elements = [ + # SMART DEFAULTS - Environment Variable Injection + { type = "text", name = "current_user", prompt = "Current user", default = "{{ env.USER }}", placeholder = "Auto-detected", help = "Auto-populated from environment, read-only" }, + { type = "text", name = "home_directory", prompt = "Home directory", default = "{{ env.HOME }}", placeholder = "Auto-detected", help = "Auto-populated from environment" }, + + # ARRAY WITH UNIQUENESS CONSTRAINT + { type = "repeating-group", name = "team_members", prompt = "Team Members", min_items = 1, max_items = 10, fragment = "fragments/team-member.toml", help = "Add unique team members (no duplicates allowed)" }, + + # FIELD DEPENDENCIES - Dynamic Options + { type = "select", name = "platform", prompt = "Target Platform", required = true, help = "Select platform first, then languages will be filtered", options = [ + { value = "web", label = "Web" }, + { value = "mobile", label = "Mobile" }, + { value = "desktop", label = "Desktop" }, + { value = "embedded", label = "Embedded" }, + ] + }, + { type = "select", name = "language", prompt = "Programming Language", required = true, options_from = "platform", help = "Options depend on selected platform", options = [ + { value = "javascript", label = "JavaScript (Web)" }, + { value = "typescript", label = "TypeScript (Web)" }, + { value = "python", label = "Python (Web)" }, + { value = "rust", label = "Rust (Web)" }, + { value = "kotlin", label = "Kotlin (Mobile)" }, + { value = "swift", label = "Swift (Mobile)" }, + { value = "dart", label = "Dart (Mobile)" }, + { value = "csharp", label = "C# (Desktop)" }, + { value = "cpp", label = "C++ (Desktop)" }, + { value = "c", label = "C (Embedded)" }, + { value = "assembly", label = "Assembly (Embedded)" }, + ] + }, + + # CONDITIONAL VALIDATION - Required based on other fields + { type = "confirm", name = "use_custom_domain", prompt = "Use custom domain", default = false, help = "Enable to specify a custom domain" }, + { type = "text", name = "custom_domain", prompt = "Custom domain", required = true, when = "use_custom_domain == true", placeholder = "example.com", help = "Only required if 'Use custom domain' is checked" }, + + # MULTISELECT WITH CONSTRAINTS + { type = "select", name = "role", prompt = "User Role", required = true, help = "Determines available permissions", options = [ + { value = "admin", label = "Administrator" }, + { value = "editor", label = "Editor" }, + { value = "viewer", label = "Viewer" }, + ] + }, + { type = "multiselect", name = "permissions", prompt = "Permissions (select 1-3)", required = true, min_selected = 1, max_selected = 3, help = "Select between 1 and 3 permissions", options = [ + { value = "read", label = "📖 Read" }, + { value = "write", label = "✏️ Write" }, + { value = "delete", label = "🗑️ Delete" }, + { value = "admin", label = "🔑 Admin" }, + { value = "audit", label = "🔍 Audit" }, + ] + }, + + # NESTED VALIDATION - Complex Object (Server Configuration section) + { type = "text", name = "hostname", prompt = "Hostname", required = true, placeholder = "example.com", help = "Server hostname (required)" }, + { type = "text", name = "port", prompt = "Port", required = true, placeholder = "8080", help = "Port number (1024-65535)" }, + { type = "text", name = "max_connections", prompt = "Max connections", placeholder = "1000", help = "Maximum concurrent connections (1-10000)" }, + { type = "confirm", name = "enable_ssl", prompt = "Enable SSL/TLS", default = true, help = "Require HTTPS connections" }, + { type = "text", name = "ssl_certificate_path", prompt = "SSL certificate path", required = true, when = "enable_ssl == true", placeholder = "/etc/ssl/certs/cert.pem", help = "Path to SSL certificate (required when SSL enabled)" }, + ], +} diff --git a/examples/14-validators-and-contracts/advanced-validators.toml b/examples/14-validators-and-contracts/advanced-validators.toml deleted file mode 100644 index 77815c6..0000000 --- a/examples/14-validators-and-contracts/advanced-validators.toml +++ /dev/null @@ -1,243 +0,0 @@ -# Advanced Validators and Complex Constraints -# -# This example demonstrates sophisticated validation patterns: -# - Field dependencies (options_from) -# - Smart defaults (environment variables, templates) -# - Uniqueness constraints for arrays -# - Multi-field validation rules -# - Form-level constraints - -name = "Advanced Validators & Complex Constraints" -description = "Master complex validation patterns and field dependencies" - -# ==================================================================== -# SMART DEFAULTS - Environment Variable Injection -# ==================================================================== - -[[elements]] -name = "current_user" -type = "text" -prompt = "Current user" -help = "Auto-populated from environment, read-only" -default = "{{ env.USER }}" -placeholder = "Auto-detected" - -[[elements]] -name = "home_directory" -type = "text" -prompt = "Home directory" -help = "Auto-populated from environment" -default = "{{ env.HOME }}" -placeholder = "Auto-detected" - -# ==================================================================== -# ARRAY WITH UNIQUENESS CONSTRAINT -# ==================================================================== - -[[elements]] -name = "team_members" -type = "repeating-group" -prompt = "Team Members" -help = "Add unique team members (no duplicates allowed)" -min_items = 1 -max_items = 10 -fragment = "fragments/team-member.toml" - -# ==================================================================== -# FIELD DEPENDENCIES - Dynamic Options -# ==================================================================== - -[[elements]] -name = "platform" -type = "select" -prompt = "Target Platform" -help = "Select platform first, then languages will be filtered" -required = true -options = [ - { value = "web", label = "Web" }, - { value = "mobile", label = "Mobile" }, - { value = "desktop", label = "Desktop" }, - { value = "embedded", label = "Embedded" }, -] - -[[elements]] -name = "language" -type = "select" -prompt = "Programming Language" -help = "Options depend on selected platform" -required = true -options_from = "platform" -options = [ - # Web languages - { value = "javascript", label = "JavaScript (Web)" }, - { value = "typescript", label = "TypeScript (Web)" }, - { value = "python", label = "Python (Web)" }, - { value = "rust", label = "Rust (Web)" }, - # Mobile languages - { value = "kotlin", label = "Kotlin (Mobile)" }, - { value = "swift", label = "Swift (Mobile)" }, - { value = "dart", label = "Dart (Mobile)" }, - # Desktop languages - { value = "csharp", label = "C# (Desktop)" }, - { value = "cpp", label = "C++ (Desktop)" }, - # Embedded languages - { value = "c", label = "C (Embedded)" }, - { value = "assembly", label = "Assembly (Embedded)" }, -] - -# ==================================================================== -# CONDITIONAL VALIDATION - Required based on other fields -# ==================================================================== - -[[elements]] -name = "use_custom_domain" -type = "confirm" -prompt = "Use custom domain" -help = "Enable to specify a custom domain" -default = "false" - -[[elements]] -name = "custom_domain" -type = "text" -prompt = "Custom domain" -help = "Only required if 'Use custom domain' is checked" -when = "use_custom_domain == true" -required = true -placeholder = "example.com" - -# ==================================================================== -# MULTISELECT WITH CONSTRAINTS - Teams/Permissions -# ==================================================================== - -[[elements]] -name = "role" -type = "select" -prompt = "User Role" -help = "Determines available permissions" -required = true -options = [ - { value = "admin", label = "Administrator" }, - { value = "editor", label = "Editor" }, - { value = "viewer", label = "Viewer" }, -] - -[[elements]] -name = "permissions" -type = "multiselect" -prompt = "Permissions (select 1-3)" -help = "Select between 1 and 3 permissions" -min_selected = 1 -max_selected = 3 -required = true -options = [ - { value = "read", label = "📖 Read" }, - { value = "write", label = "✏️ Write" }, - { value = "delete", label = "🗑️ Delete" }, - { value = "admin", label = "🔑 Admin" }, - { value = "audit", label = "🔍 Audit" }, -] - -# ==================================================================== -# NESTED VALIDATION - Complex Object -# ==================================================================== - -[[sections]] -title = "Server Configuration" -description = "Configure server details with validation" - -[[sections.elements]] -name = "hostname" -type = "text" -prompt = "Hostname" -help = "Server hostname (required)" -required = true -placeholder = "example.com" - -[[sections.elements]] -name = "port" -type = "text" -prompt = "Port" -help = "Port number (1024-65535)" -required = true -placeholder = "8080" - -[[sections.elements]] -name = "max_connections" -type = "text" -prompt = "Max connections" -help = "Maximum concurrent connections (1-10000)" -placeholder = "1000" - -[[sections.elements]] -name = "enable_ssl" -type = "confirm" -prompt = "Enable SSL/TLS" -help = "Require HTTPS connections" -default = "true" - -[[sections.elements]] -name = "ssl_certificate_path" -type = "text" -prompt = "SSL certificate path" -help = "Path to SSL certificate (required when SSL enabled)" -when = "enable_ssl == true" -required = true -placeholder = "/etc/ssl/certs/cert.pem" - -# ==================================================================== -# VALIDATION CONSTRAINTS (Configuration) -# ==================================================================== - -[constraints] -# Port ranges -min_port = 1024 -max_port = 65535 -privileged_port_limit = 1024 - -# Connection limits -min_connections = 1 -max_connections = 10000 -default_connections = 1000 - -# Domain validation -domain_min_length = 3 -domain_max_length = 253 - -# Field length constraints -hostname_min = 3 -hostname_max = 253 - -password_min_length = 8 -password_min_uppercase = 1 -password_min_lowercase = 1 -password_min_digits = 1 -password_min_special = 0 - -# Array constraints -max_team_members = 50 -max_permissions = 5 -max_deployments = 20 - -# File size constraints (in MB) -max_config_file_size = 50 -max_backup_file_size = 1000 - -# Business logic constraints -max_free_tier_users = 5 -max_projects_per_user = 20 - -[constraints.regex_patterns] -# Email pattern -email = "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$" - -# Hostname pattern (alphanumeric, hyphens, dots) -hostname = "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$" - -# URL pattern -url = "^https?://(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$" - -# Port pattern -port = "^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$" - -# Version pattern (semver) -version = "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9]+)?$" diff --git a/examples/14-validators-and-contracts/basic-validators.ncl b/examples/14-validators-and-contracts/basic-validators.ncl new file mode 100644 index 0000000..21feffc --- /dev/null +++ b/examples/14-validators-and-contracts/basic-validators.ncl @@ -0,0 +1,47 @@ +{ + name = "Basic Validators & Constraints", + description = "Learn fundamental validation patterns with min/max/required", + elements = [ + # REQUIRED FIELDS + { type = "text", name = "username", prompt = "Username", required = true, help = "Required field - cannot be empty" }, + { type = "text", name = "email", prompt = "Email address", required = true, help = "Email is required for account creation" }, + + # TEXT FIELD LENGTH VALIDATION + { type = "password", name = "password", prompt = "Password", required = true, placeholder = "At least 8 characters, include uppercase/lowercase/numbers", help = "Minimum 8 characters (enforced by external validation)" }, + + # NUMERIC RANGE VALIDATION + { type = "text", name = "age", prompt = "Age", required = true, placeholder = "18-100", help = "Must be between 18 and 100" }, + { type = "text", name = "port", prompt = "Server Port", placeholder = "1024-65535", help = "Port number must be between 1024 and 65535" }, + + # SELECT WITH REQUIRED CONSTRAINT + { type = "select", name = "country", prompt = "Country", required = true, help = "You must select a country", options = [ + { value = "us", label = "United States" }, + { value = "uk", label = "United Kingdom" }, + { value = "ca", label = "Canada" }, + { value = "au", label = "Australia" }, + { value = "de", label = "Germany" }, + { value = "fr", label = "France" }, + { value = "jp", label = "Japan" }, + ] + }, + + # MULTISELECT WITH MIN/MAX CONSTRAINTS + { type = "multiselect", name = "interests", prompt = "Select your interests (choose 2-4)", required = true, min_selected = 2, max_selected = 4, help = "Select between 2 and 4 interests", options = [ + { value = "technology", label = "🚀 Technology" }, + { value = "music", label = "🎵 Music" }, + { value = "sports", label = "⚽ Sports" }, + { value = "travel", label = "✈️ Travel" }, + { value = "cooking", label = "👨‍🍳 Cooking" }, + { value = "reading", label = "📚 Reading" }, + ] + }, + + # DATE RANGE VALIDATION + { type = "date", name = "birth_date", prompt = "Date of birth", required = true, min_date = "1950-01-01", max_date = "2006-12-31", help = "Must be between 1950 and today" }, + { type = "date", name = "start_date", prompt = "Project start date", min_date = "today", help = "Must be today or later" }, + + # CONFIRM FIELD (boolean validation) + { type = "confirm", name = "agree_terms", prompt = "I agree to the terms and conditions", required = true, default = false, help = "You must accept the terms to continue" }, + { type = "confirm", name = "subscribe", prompt = "Subscribe to newsletter", default = false, help = "Optional - not required" }, + ], +} diff --git a/examples/14-validators-and-contracts/basic-validators.toml b/examples/14-validators-and-contracts/basic-validators.toml deleted file mode 100644 index c8a3ef3..0000000 --- a/examples/14-validators-and-contracts/basic-validators.toml +++ /dev/null @@ -1,171 +0,0 @@ -# Basic Validators and Constraints Example -# -# TypeDialog provides built-in validators for common validation patterns: -# - required: Field must have a value -# - min/max: Numeric range limits -# - min_items/max_items: Array size constraints -# - min_selected/max_selected: Multiselect constraints -# - unique: Array items must be unique -# - min_date/max_date: Date range constraints -# -# This example demonstrates foundational validation patterns. - -name = "Basic Validators & Constraints" -description = "Learn fundamental validation patterns with min/max/required" - -# ==================================================================== -# REQUIRED FIELDS -# ==================================================================== - -[[elements]] -name = "username" -type = "text" -prompt = "Username" -help = "Required field - cannot be empty" -required = true - -[[elements]] -name = "email" -type = "text" -prompt = "Email address" -help = "Email is required for account creation" -required = true - -# ==================================================================== -# TEXT FIELD LENGTH VALIDATION (via placeholder hints) -# ==================================================================== - -[[elements]] -name = "password" -type = "password" -prompt = "Password" -help = "Minimum 8 characters (enforced by external validation)" -required = true -placeholder = "At least 8 characters, include uppercase/lowercase/numbers" - -# ==================================================================== -# NUMERIC RANGE VALIDATION -# ==================================================================== - -[[elements]] -name = "age" -type = "text" -prompt = "Age" -help = "Must be between 18 and 100" -required = true -placeholder = "18-100" - -[[elements]] -name = "port" -type = "text" -prompt = "Server Port" -help = "Port number must be between 1024 and 65535" -placeholder = "1024-65535" - -# ==================================================================== -# SELECT WITH REQUIRED CONSTRAINT -# ==================================================================== - -[[elements]] -name = "country" -type = "select" -prompt = "Country" -help = "You must select a country" -required = true -options = [ - { value = "us", label = "United States" }, - { value = "uk", label = "United Kingdom" }, - { value = "ca", label = "Canada" }, - { value = "au", label = "Australia" }, - { value = "de", label = "Germany" }, - { value = "fr", label = "France" }, - { value = "jp", label = "Japan" }, -] - -# ==================================================================== -# MULTISELECT WITH MIN/MAX CONSTRAINTS -# ==================================================================== - -[[elements]] -name = "interests" -type = "multiselect" -prompt = "Select your interests (choose 2-4)" -help = "Select between 2 and 4 interests" -min_selected = 2 -max_selected = 4 -required = true -options = [ - { value = "technology", label = "🚀 Technology" }, - { value = "music", label = "🎵 Music" }, - { value = "sports", label = "⚽ Sports" }, - { value = "travel", label = "✈️ Travel" }, - { value = "cooking", label = "👨‍🍳 Cooking" }, - { value = "reading", label = "📚 Reading" }, -] - -# ==================================================================== -# DATE RANGE VALIDATION -# ==================================================================== - -[[elements]] -name = "birth_date" -type = "date" -prompt = "Date of birth" -help = "Must be between 1950 and today" -required = true -min_date = "1950-01-01" -max_date = "2006-12-31" - -[[elements]] -name = "start_date" -type = "date" -prompt = "Project start date" -help = "Must be today or later" -min_date = "today" - -# ==================================================================== -# CONFIRM FIELD (boolean validation) -# ==================================================================== - -[[elements]] -name = "agree_terms" -type = "confirm" -prompt = "I agree to the terms and conditions" -help = "You must accept the terms to continue" -required = true -default = "false" - -[[elements]] -name = "subscribe" -type = "confirm" -prompt = "Subscribe to newsletter" -help = "Optional - not required" -default = "false" - -# ==================================================================== -# CONSTRAINTS (DRY source of truth for limits) -# ==================================================================== -# Constraints provide a single source of truth for validation limits -# They can be overridden per-field but encourage reuse - -[constraints] -# Username constraints -username_min_length = 3 -username_max_length = 32 - -# Password constraints -password_min_length = 8 -password_min_uppercase = 1 -password_min_numbers = 1 - -# Age constraints -age_min = 18 -age_max = 100 - -# Port constraints -port_min = 1024 -port_max = 65535 - -# File limits -file_max_size_mb = 10 -file_max_count = 5 diff --git a/examples/14-validators-and-contracts/conditional-validators.ncl b/examples/14-validators-and-contracts/conditional-validators.ncl new file mode 100644 index 0000000..9b2559b --- /dev/null +++ b/examples/14-validators-and-contracts/conditional-validators.ncl @@ -0,0 +1,93 @@ +{ + name = "Conditional Validators", + description = "Master field visibility and conditional requirements", + elements = [ + # SECTION 1: Account Type Selection + { type = "select", name = "account_type", prompt = "Account Type", required = true, default = "personal", help = "Choose your account type (all types require different details)", options = [ + { value = "personal", label = "👤 Personal" }, + { value = "business", label = "🏢 Business" }, + { value = "nonprofit", label = "🤝 Non-profit" }, + { value = "education", label = "🎓 Education" }, + ] + }, + + # SECTION 2: Personal Account Fields + { type = "text", name = "first_name", prompt = "First name", required = true, when = "account_type == personal || account_type == education", placeholder = "John", help = "Your first name" }, + { type = "text", name = "last_name", prompt = "Last name", required = true, when = "account_type == personal || account_type == education", placeholder = "Doe", help = "Your last name" }, + + # SECTION 3: Business Account Fields + { type = "text", name = "company_name", prompt = "Company name", required = true, when = "account_type == business || account_type == nonprofit", placeholder = "Acme Corporation", help = "Official company name (required for business accounts)" }, + { type = "select", name = "company_type", prompt = "Company type", required = true, when = "account_type == business", help = "Type of organization", options = [ + { value = "startup", label = "Startup" }, + { value = "scaleup", label = "Scale-up" }, + { value = "enterprise", label = "Enterprise" }, + { value = "agency", label = "Agency" }, + { value = "consulting", label = "Consulting" }, + ] + }, + { type = "text", name = "business_registration_number", prompt = "Business registration number", required = true, when = "account_type == business", placeholder = "12345678", help = "Required for business accounts" }, + { type = "text", name = "tax_id", prompt = "Tax ID / VAT Number", when = "account_type == business", placeholder = "US-TAX-123456", help = "For invoicing purposes" }, + + # SECTION 4: Non-profit Specific Fields + { type = "select", name = "nonprofit_category", prompt = "Non-profit category", required = true, when = "account_type == nonprofit", help = "Category of non-profit organization", options = [ + { value = "charity", label = "Charity" }, + { value = "educational", label = "Educational" }, + { value = "healthcare", label = "Healthcare" }, + { value = "environmental", label = "Environmental" }, + { value = "other", label = "Other" }, + ] + }, + { type = "text", name = "nonprofit_registration", prompt = "Non-profit registration number", required = true, when = "account_type == nonprofit", placeholder = "EIN-12-3456789", help = "Government registration number" }, + + # SECTION 5: Education Account Fields + { type = "text", name = "institution_name", prompt = "Institution name", required = true, when = "account_type == education", placeholder = "University of California", help = "Name of your school or university" }, + { type = "select", name = "institution_role", prompt = "Your role", required = true, when = "account_type == education", help = "Your role at the institution", options = [ + { value = "student", label = "Student" }, + { value = "faculty", label = "Faculty" }, + { value = "staff", label = "Staff" }, + { value = "admin", label = "Administrator" }, + ] + }, + { type = "text", name = "student_id", prompt = "Student ID", required = true, when = "account_type == education && institution_role == student", placeholder = "SID-123456", help = "Your student identification number" }, + + # SECTION 6: Universal Account Fields + { type = "text", name = "email", prompt = "Email address", required = true, placeholder = "email@example.com", help = "Required for all account types" }, + { type = "text", name = "phone", prompt = "Phone number", placeholder = "+1 (555) 123-4567", help = "Optional contact number" }, + + # SECTION 7: Authentication & Security + { type = "confirm", name = "enable_2fa", prompt = "Enable two-factor authentication", default = false, help = "Add an extra layer of security" }, + { type = "select", name = "2fa_method", prompt = "Two-factor authentication method", required = true, when = "enable_2fa == true", help = "Choose your preferred 2FA method", options = [ + { value = "email", label = "📧 Email" }, + { value = "sms", label = "📱 SMS" }, + { value = "authenticator", label = "🔐 Authenticator App" }, + { value = "security_key", label = "🔑 Security Key" }, + ] + }, + { type = "text", name = "backup_email", prompt = "Backup email", required = true, when = "2fa_method == sms", placeholder = "backup@example.com", help = "For account recovery (required if SMS 2FA is chosen)" }, + { type = "text", name = "phone_for_2fa", prompt = "Phone number for 2FA", required = true, when = "2fa_method == sms", placeholder = "+1 (555) 123-4567", help = "Phone number to receive SMS codes" }, + + # SECTION 8: Nested Conditional Validation (3 levels) + { type = "confirm", name = "enable_api", prompt = "Enable API access", default = false, help = "Allow API connections" }, + { type = "select", name = "api_type", prompt = "API type", required = true, when = "enable_api == true", help = "Choose API authentication method", options = [ + { value = "oauth2", label = "OAuth 2.0" }, + { value = "apikey", label = "API Key" }, + { value = "jwt", label = "JWT Token" }, + { value = "mutual_tls", label = "Mutual TLS" }, + ] + }, + { type = "text", name = "oauth2_client_id", prompt = "OAuth 2.0 Client ID", required = true, when = "enable_api == true && api_type == oauth2", placeholder = "client_abc123", help = "Your OAuth2 client identifier" }, + { type = "password", name = "oauth2_client_secret", prompt = "OAuth 2.0 Client Secret", required = true, when = "enable_api == true && api_type == oauth2", placeholder = "secret_xyz789", help = "Keep this secret safe" }, + { type = "password", name = "api_key", prompt = "API Key", required = true, when = "enable_api == true && api_type == apikey", placeholder = "sk-123456789abcdef", help = "Your API authentication key" }, + + # SECTION 9: Terms and Conditions + { type = "confirm", name = "agree_terms", prompt = "I agree to the terms of service", required = true, default = false, help = "You must accept to proceed" }, + { type = "confirm", name = "subscribe_newsletter", prompt = "Subscribe to our newsletter", default = false, when = "agree_terms == true", help = "Optional - get updates and offers" }, + { type = "select", name = "newsletter_frequency", prompt = "Newsletter frequency", required = true, default = "weekly", when = "agree_terms == true && subscribe_newsletter == true", help = "How often to receive newsletters", options = [ + { value = "daily", label = "📅 Daily" }, + { value = "weekly", label = "📆 Weekly" }, + { value = "monthly", label = "🗓️ Monthly" }, + { value = "quarterly", label = "📊 Quarterly" }, + ] + }, + ], +} diff --git a/examples/14-validators-and-contracts/conditional-validators.toml b/examples/14-validators-and-contracts/conditional-validators.toml deleted file mode 100644 index 3b37eae..0000000 --- a/examples/14-validators-and-contracts/conditional-validators.toml +++ /dev/null @@ -1,311 +0,0 @@ -# Conditional Validators - Smart Field Visibility and Requirements -# -# Use the `when` clause to show/hide fields based on other field values. -# Fields shown conditionally can also have their own validation rules. -# -# This example demonstrates: -# - Conditional field visibility (when clause) -# - Conditional requirements (when + required) -# - Cascading conditions (multiple levels) -# - Smart defaults based on conditions - -name = "Conditional Validators" -description = "Master field visibility and conditional requirements" - -# ==================================================================== -# SECTION 1: Account Type Selection -# ==================================================================== - -[[elements]] -name = "account_type" -type = "select" -prompt = "Account Type" -help = "Choose your account type (all types require different details)" -required = true -default = "personal" -options = [ - { value = "personal", label = "👤 Personal" }, - { value = "business", label = "🏢 Business" }, - { value = "nonprofit", label = "🤝 Non-profit" }, - { value = "education", label = "🎓 Education" }, -] - -# ==================================================================== -# SECTION 2: Personal Account Fields -# ==================================================================== - -[[elements]] -name = "first_name" -type = "text" -prompt = "First name" -help = "Your first name" -when = "account_type == personal || account_type == education" -required = true -placeholder = "John" - -[[elements]] -name = "last_name" -type = "text" -prompt = "Last name" -help = "Your last name" -when = "account_type == personal || account_type == education" -required = true -placeholder = "Doe" - -# ==================================================================== -# SECTION 3: Business Account Fields -# ==================================================================== - -[[elements]] -name = "company_name" -type = "text" -prompt = "Company name" -help = "Official company name (required for business accounts)" -when = "account_type == business || account_type == nonprofit" -required = true -placeholder = "Acme Corporation" - -[[elements]] -name = "company_type" -type = "select" -prompt = "Company type" -help = "Type of organization" -when = "account_type == business" -required = true -options = [ - { value = "startup", label = "Startup" }, - { value = "scaleup", label = "Scale-up" }, - { value = "enterprise", label = "Enterprise" }, - { value = "agency", label = "Agency" }, - { value = "consulting", label = "Consulting" }, -] - -[[elements]] -name = "business_registration_number" -type = "text" -prompt = "Business registration number" -help = "Required for business accounts" -when = "account_type == business" -required = true -placeholder = "12345678" - -[[elements]] -name = "tax_id" -type = "text" -prompt = "Tax ID / VAT Number" -help = "For invoicing purposes" -when = "account_type == business" -placeholder = "US-TAX-123456" - -# ==================================================================== -# SECTION 4: Non-profit Specific Fields -# ==================================================================== - -[[elements]] -name = "nonprofit_category" -type = "select" -prompt = "Non-profit category" -help = "Category of non-profit organization" -when = "account_type == nonprofit" -required = true -options = [ - { value = "charity", label = "Charity" }, - { value = "educational", label = "Educational" }, - { value = "healthcare", label = "Healthcare" }, - { value = "environmental", label = "Environmental" }, - { value = "other", label = "Other" }, -] - -[[elements]] -name = "nonprofit_registration" -type = "text" -prompt = "Non-profit registration number" -help = "Government registration number" -when = "account_type == nonprofit" -required = true -placeholder = "EIN-12-3456789" - -# ==================================================================== -# SECTION 5: Education Account Fields -# ==================================================================== - -[[elements]] -name = "institution_name" -type = "text" -prompt = "Institution name" -help = "Name of your school or university" -when = "account_type == education" -required = true -placeholder = "University of California" - -[[elements]] -name = "institution_role" -type = "select" -prompt = "Your role" -help = "Your role at the institution" -when = "account_type == education" -required = true -options = [ - { value = "student", label = "Student" }, - { value = "faculty", label = "Faculty" }, - { value = "staff", label = "Staff" }, - { value = "admin", label = "Administrator" }, -] - -[[elements]] -name = "student_id" -type = "text" -prompt = "Student ID" -help = "Your student identification number" -when = "account_type == education && institution_role == student" -required = true -placeholder = "SID-123456" - -# ==================================================================== -# SECTION 6: Universal Account Fields -# ==================================================================== - -[[elements]] -name = "email" -type = "text" -prompt = "Email address" -help = "Required for all account types" -required = true -placeholder = "email@example.com" - -[[elements]] -name = "phone" -type = "text" -prompt = "Phone number" -help = "Optional contact number" -placeholder = "+1 (555) 123-4567" - -# ==================================================================== -# SECTION 7: Authentication & Security -# ==================================================================== - -[[elements]] -name = "enable_2fa" -type = "confirm" -prompt = "Enable two-factor authentication" -help = "Add an extra layer of security" -default = "false" - -[[elements]] -name = "2fa_method" -type = "select" -prompt = "Two-factor authentication method" -help = "Choose your preferred 2FA method" -when = "enable_2fa == true" -required = true -options = [ - { value = "email", label = "📧 Email" }, - { value = "sms", label = "📱 SMS" }, - { value = "authenticator", label = "🔐 Authenticator App" }, - { value = "security_key", label = "🔑 Security Key" }, -] - -[[elements]] -name = "backup_email" -type = "text" -prompt = "Backup email" -help = "For account recovery (required if SMS 2FA is chosen)" -when = "2fa_method == sms" -required = true -placeholder = "backup@example.com" - -[[elements]] -name = "phone_for_2fa" -type = "text" -prompt = "Phone number for 2FA" -help = "Phone number to receive SMS codes" -when = "2fa_method == sms" -required = true -placeholder = "+1 (555) 123-4567" - -# ==================================================================== -# SECTION 8: Nested Conditional Validation (3 levels) -# ==================================================================== - -[[elements]] -name = "enable_api" -type = "confirm" -prompt = "Enable API access" -help = "Allow API connections" -default = "false" - -[[elements]] -name = "api_type" -type = "select" -prompt = "API type" -help = "Choose API authentication method" -when = "enable_api == true" -required = true -options = [ - { value = "oauth2", label = "OAuth 2.0" }, - { value = "apikey", label = "API Key" }, - { value = "jwt", label = "JWT Token" }, - { value = "mutual_tls", label = "Mutual TLS" }, -] - -[[elements]] -name = "oauth2_client_id" -type = "text" -prompt = "OAuth 2.0 Client ID" -help = "Your OAuth2 client identifier" -when = "enable_api == true && api_type == oauth2" -required = true -placeholder = "client_abc123" - -[[elements]] -name = "oauth2_client_secret" -type = "password" -prompt = "OAuth 2.0 Client Secret" -help = "Keep this secret safe" -when = "enable_api == true && api_type == oauth2" -required = true -placeholder = "secret_xyz789" - -[[elements]] -name = "api_key" -type = "password" -prompt = "API Key" -help = "Your API authentication key" -when = "enable_api == true && api_type == apikey" -required = true -placeholder = "sk-123456789abcdef" - -# ==================================================================== -# SECTION 9: Terms and Conditions -# ==================================================================== - -[[elements]] -name = "agree_terms" -type = "confirm" -prompt = "I agree to the terms of service" -help = "You must accept to proceed" -required = true -default = "false" - -[[elements]] -name = "subscribe_newsletter" -type = "confirm" -prompt = "Subscribe to our newsletter" -help = "Optional - get updates and offers" -when = "agree_terms == true" -default = "false" - -[[elements]] -name = "newsletter_frequency" -type = "select" -prompt = "Newsletter frequency" -help = "How often to receive newsletters" -when = "agree_terms == true && subscribe_newsletter == true" -required = true -default = "weekly" -options = [ - { value = "daily", label = "📅 Daily" }, - { value = "weekly", label = "📆 Weekly" }, - { value = "monthly", label = "🗓️ Monthly" }, - { value = "quarterly", label = "📊 Quarterly" }, -] diff --git a/examples/14-validators-and-contracts/fragments/team-member.ncl b/examples/14-validators-and-contracts/fragments/team-member.ncl new file mode 100644 index 0000000..51cd351 --- /dev/null +++ b/examples/14-validators-and-contracts/fragments/team-member.ncl @@ -0,0 +1,21 @@ +{ + elements = [ + { type = "text", name = "name", prompt = "Member name", required = true, placeholder = "John Doe", help = "Full name of team member" }, + { type = "text", name = "email", prompt = "Email address", required = true, placeholder = "john@example.com", unique = true, help = "Work email address (must be unique)" }, + { type = "select", name = "role", prompt = "Role", required = true, help = "Team member role", options = [ + { value = "lead", label = "🎯 Team Lead" }, + { value = "senior", label = "⭐ Senior" }, + { value = "junior", label = "🌱 Junior" }, + { value = "intern", label = "📚 Intern" }, + ] + }, + { type = "multiselect", name = "skills", prompt = "Skills", min_selected = 1, max_selected = 5, help = "Select at least 1 skill", options = [ + { value = "rust", label = "🦀 Rust" }, + { value = "typescript", label = "📘 TypeScript" }, + { value = "devops", label = "🚀 DevOps" }, + { value = "database", label = "🗄️ Database" }, + { value = "frontend", label = "🎨 Frontend" }, + ] + }, + ], +} diff --git a/examples/14-validators-and-contracts/fragments/team-member.toml b/examples/14-validators-and-contracts/fragments/team-member.toml deleted file mode 100644 index 8368f34..0000000 --- a/examples/14-validators-and-contracts/fragments/team-member.toml +++ /dev/null @@ -1,48 +0,0 @@ -# Team Member Fragment -# Used by repeating-group in advanced-validators.toml -# Each array item contains these fields - -[[elements]] -name = "name" -type = "text" -prompt = "Member name" -help = "Full name of team member" -required = true -placeholder = "John Doe" - -[[elements]] -name = "email" -type = "text" -prompt = "Email address" -help = "Work email address (must be unique)" -required = true -placeholder = "john@example.com" -unique = true - -[[elements]] -name = "role" -type = "select" -prompt = "Role" -help = "Team member role" -required = true -options = [ - { value = "lead", label = "🎯 Team Lead" }, - { value = "senior", label = "⭐ Senior" }, - { value = "junior", label = "🌱 Junior" }, - { value = "intern", label = "📚 Intern" }, -] - -[[elements]] -name = "skills" -type = "multiselect" -prompt = "Skills" -help = "Select at least 1 skill" -min_selected = 1 -max_selected = 5 -options = [ - { value = "rust", label = "🦀 Rust" }, - { value = "typescript", label = "📘 TypeScript" }, - { value = "devops", label = "🚀 DevOps" }, - { value = "database", label = "🗄️ Database" }, - { value = "frontend", label = "🎨 Frontend" }, -] diff --git a/examples/15-cross-backend-same-form/employee-registration.ncl b/examples/15-cross-backend-same-form/employee-registration.ncl new file mode 100644 index 0000000..b27598e --- /dev/null +++ b/examples/15-cross-backend-same-form/employee-registration.ncl @@ -0,0 +1,101 @@ +{ + name = "Employee Registration", + description = "Complete employee onboarding form - works on CLI, TUI, and Web", + elements = [ + # SECTION 1: Personal Information + { type = "text", name = "first_name", prompt = "First name", required = true, placeholder = "John", help = "Employee's first name" }, + { type = "text", name = "last_name", prompt = "Last name", required = true, placeholder = "Doe", help = "Employee's last name" }, + { type = "text", name = "email", prompt = "Work email", required = true, placeholder = "john.doe@company.com", help = "Official company email address" }, + { type = "text", name = "phone", prompt = "Phone number", placeholder = "+1 (555) 123-4567", help = "Contact phone number (optional)" }, + { type = "date", name = "start_date", prompt = "Start date", required = true, min_date = "today", help = "First day of employment" }, + + # SECTION 2: Employment Details + { type = "select", name = "department", prompt = "Department", required = true, help = "Which department will the employee join?", options = [ + { value = "engineering", label = "🔧 Engineering" }, + { value = "product", label = "📦 Product" }, + { value = "design", label = "🎨 Design" }, + { value = "sales", label = "💼 Sales" }, + { value = "marketing", label = "📢 Marketing" }, + { value = "hr", label = "👥 Human Resources" }, + { value = "finance", label = "💰 Finance" }, + { value = "operations", label = "⚙️ Operations" }, + ] + }, + { type = "text", name = "job_title", prompt = "Job title", required = true, placeholder = "Senior Engineer", help = "Employee's position title" }, + { type = "select", name = "employment_type", prompt = "Employment type", required = true, default = "full_time", help = "Type of employment contract", options = [ + { value = "full_time", label = "Full-time" }, + { value = "part_time", label = "Part-time" }, + { value = "contract", label = "Contract" }, + { value = "intern", label = "Intern" }, + ] + }, + { type = "text", name = "manager", prompt = "Direct manager", placeholder = "Jane Smith", help = "Name of the direct manager" }, + { type = "select", name = "office_location", prompt = "Office location", required = true, help = "Primary work location", options = [ + { value = "sf", label = "🌉 San Francisco" }, + { value = "nyc", label = "🗽 New York" }, + { value = "london", label = "🇬🇧 London" }, + { value = "tokyo", label = "🗻 Tokyo" }, + { value = "remote", label = "🏠 Remote" }, + ] + }, + + # SECTION 3: Skills & Expertise + { type = "multiselect", name = "primary_skills", prompt = "Primary skills (select 2-5)", required = true, min_selected = 2, max_selected = 5, help = "Select your main areas of expertise", options = [ + { value = "rust", label = "🦀 Rust" }, + { value = "typescript", label = "📘 TypeScript" }, + { value = "python", label = "🐍 Python" }, + { value = "golang", label = "🐹 Go" }, + { value = "java", label = "☕ Java" }, + { value = "cpp", label = "⚙️ C++" }, + { value = "devops", label = "🚀 DevOps" }, + { value = "frontend", label = "🎨 Frontend" }, + { value = "backend", label = "🔧 Backend" }, + { value = "database", label = "🗄️ Database" }, + ] + }, + { type = "text", name = "years_experience", prompt = "Years of professional experience", placeholder = "5", help = "Total years working in the industry" }, + + # SECTION 4: Benefits & Preferences + { type = "select", name = "health_insurance", prompt = "Health insurance plan", required = true, help = "Choose a health insurance option", options = [ + { value = "basic", label = "Basic Plan" }, + { value = "premium", label = "Premium Plan (recommended)" }, + { value = "family", label = "Family Plan" }, + { value = "none", label = "Opt out" }, + ] + }, + { type = "select", name = "work_schedule", prompt = "Preferred work schedule", default = "standard", help = "Flexible working arrangements", options = [ + { value = "standard", label = "Standard (9-5)" }, + { value = "flexible", label = "Flexible hours" }, + { value = "compressed", label = "Compressed week (4 days)" }, + ] + }, + { type = "text", name = "remote_days", prompt = "Remote work days per week", when = "office_location != remote", placeholder = "2", help = "Number of days working remotely (0-5)" }, + { type = "multiselect", name = "equipment_needs", prompt = "Equipment needs", help = "Required equipment and hardware", options = [ + { value = "laptop", label = "💻 Laptop" }, + { value = "monitor", label = "🖥️ Monitor" }, + { value = "keyboard", label = "⌨️ Mechanical Keyboard" }, + { value = "mouse", label = "🖱️ Mouse" }, + { value = "headphones", label = "🎧 Headphones" }, + { value = "desk_stand", label = "📊 Desk Stand" }, + { value = "chair", label = "🪑 Ergonomic Chair" }, + ] + }, + + # SECTION 5: Security & Compliance + { type = "confirm", name = "enable_2fa", prompt = "Enable two-factor authentication", default = true, help = "Recommended for security" }, + { type = "select", name = "2fa_method", prompt = "2FA method", required = true, when = "enable_2fa == true", help = "Choose your two-factor authentication method", options = [ + { value = "authenticator", label = "🔐 Authenticator App" }, + { value = "sms", label = "📱 SMS" }, + { value = "security_key", label = "🔑 Security Key" }, + ] + }, + { type = "confirm", name = "vpn_required", prompt = "VPN required for data access", default = false, help = "Employee will need VPN for sensitive systems" }, + { type = "confirm", name = "agreed_policies", prompt = "I agree to company policies and code of conduct", required = true, default = false, help = "Must accept to proceed" }, + { type = "confirm", name = "privacy_consent", prompt = "I consent to privacy and data handling policies", required = true, default = false, help = "Required for employment" }, + + # SECTION 6: Additional Information + { type = "text", name = "emergency_contact", prompt = "Emergency contact name", placeholder = "Jane Doe", help = "Name of emergency contact person" }, + { type = "text", name = "emergency_phone", prompt = "Emergency contact phone", placeholder = "+1 (555) 987-6543", help = "Phone number of emergency contact" }, + { type = "editor", name = "additional_notes", prompt = "Additional notes", file_extension = "md", placeholder = "Special accommodations, preferences, etc.", help = "Any special requirements or notes (optional)" }, + ], +} diff --git a/examples/15-cross-backend-same-form/employee-registration.toml b/examples/15-cross-backend-same-form/employee-registration.toml deleted file mode 100644 index bdc22aa..0000000 --- a/examples/15-cross-backend-same-form/employee-registration.toml +++ /dev/null @@ -1,301 +0,0 @@ -# Employee Registration Form -# -# This form demonstrates cross-backend compatibility. -# The EXACT SAME form works on CLI, TUI, and Web backends. -# Only the presentation changes based on the backend capabilities. -# -# Run with: -# CLI: cargo run -p typedialog -- --config employee-registration.toml -# TUI: cargo run -p typedialog-tui -- employee-registration.toml -# Web: cargo run -p typedialog-web -- --config employee-registration.toml - -name = "Employee Registration" -description = "Complete employee onboarding form - works on CLI, TUI, and Web" - -# ==================================================================== -# SECTION 1: Personal Information -# ==================================================================== - -[[sections]] -title = "Personal Information" -description = "Basic employee details" - -[[sections.elements]] -name = "first_name" -type = "text" -prompt = "First name" -help = "Employee's first name" -required = true -placeholder = "John" - -[[sections.elements]] -name = "last_name" -type = "text" -prompt = "Last name" -help = "Employee's last name" -required = true -placeholder = "Doe" - -[[sections.elements]] -name = "email" -type = "text" -prompt = "Work email" -help = "Official company email address" -required = true -placeholder = "john.doe@company.com" - -[[sections.elements]] -name = "phone" -type = "text" -prompt = "Phone number" -help = "Contact phone number (optional)" -placeholder = "+1 (555) 123-4567" - -[[sections.elements]] -name = "start_date" -type = "date" -prompt = "Start date" -help = "First day of employment" -required = true -min_date = "today" - -# ==================================================================== -# SECTION 2: Employment Details -# ==================================================================== - -[[sections]] -title = "Employment Details" -description = "Job and department information" - -[[sections.elements]] -name = "department" -type = "select" -prompt = "Department" -help = "Which department will the employee join?" -required = true -options = [ - { value = "engineering", label = "🔧 Engineering" }, - { value = "product", label = "📦 Product" }, - { value = "design", label = "🎨 Design" }, - { value = "sales", label = "💼 Sales" }, - { value = "marketing", label = "📢 Marketing" }, - { value = "hr", label = "👥 Human Resources" }, - { value = "finance", label = "💰 Finance" }, - { value = "operations", label = "⚙️ Operations" }, -] - -[[sections.elements]] -name = "job_title" -type = "text" -prompt = "Job title" -help = "Employee's position title" -required = true -placeholder = "Senior Engineer" - -[[sections.elements]] -name = "employment_type" -type = "select" -prompt = "Employment type" -help = "Type of employment contract" -required = true -default = "full_time" -options = [ - { value = "full_time", label = "Full-time" }, - { value = "part_time", label = "Part-time" }, - { value = "contract", label = "Contract" }, - { value = "intern", label = "Intern" }, -] - -[[sections.elements]] -name = "manager" -type = "text" -prompt = "Direct manager" -help = "Name of the direct manager" -placeholder = "Jane Smith" - -[[sections.elements]] -name = "office_location" -type = "select" -prompt = "Office location" -help = "Primary work location" -required = true -options = [ - { value = "sf", label = "🌉 San Francisco" }, - { value = "nyc", label = "🗽 New York" }, - { value = "london", label = "🇬🇧 London" }, - { value = "tokyo", label = "🗻 Tokyo" }, - { value = "remote", label = "🏠 Remote" }, -] - -# ==================================================================== -# SECTION 3: Skills & Expertise -# ==================================================================== - -[[sections]] -title = "Skills & Expertise" -description = "Technical skills and areas of expertise" - -[[sections.elements]] -name = "primary_skills" -type = "multiselect" -prompt = "Primary skills (select 2-5)" -help = "Select your main areas of expertise" -min_selected = 2 -max_selected = 5 -required = true -options = [ - { value = "rust", label = "🦀 Rust" }, - { value = "typescript", label = "📘 TypeScript" }, - { value = "python", label = "🐍 Python" }, - { value = "golang", label = "🐹 Go" }, - { value = "java", label = "☕ Java" }, - { value = "cpp", label = "⚙️ C++" }, - { value = "devops", label = "🚀 DevOps" }, - { value = "frontend", label = "🎨 Frontend" }, - { value = "backend", label = "🔧 Backend" }, - { value = "database", label = "🗄️ Database" }, -] - -[[sections.elements]] -name = "years_experience" -type = "text" -prompt = "Years of professional experience" -help = "Total years working in the industry" -placeholder = "5" - -# ==================================================================== -# SECTION 4: Benefits & Preferences -# ==================================================================== - -[[sections]] -title = "Benefits & Preferences" -description = "Insurance and preference options" - -[[sections.elements]] -name = "health_insurance" -type = "select" -prompt = "Health insurance plan" -help = "Choose a health insurance option" -required = true -options = [ - { value = "basic", label = "Basic Plan" }, - { value = "premium", label = "Premium Plan (recommended)" }, - { value = "family", label = "Family Plan" }, - { value = "none", label = "Opt out" }, -] - -[[sections.elements]] -name = "work_schedule" -type = "select" -prompt = "Preferred work schedule" -help = "Flexible working arrangements" -default = "standard" -options = [ - { value = "standard", label = "Standard (9-5)" }, - { value = "flexible", label = "Flexible hours" }, - { value = "compressed", label = "Compressed week (4 days)" }, -] - -[[sections.elements]] -name = "remote_days" -type = "text" -prompt = "Remote work days per week" -help = "Number of days working remotely (0-5)" -when = "office_location != remote" -placeholder = "2" - -[[sections.elements]] -name = "equipment_needs" -type = "multiselect" -prompt = "Equipment needs" -help = "Required equipment and hardware" -options = [ - { value = "laptop", label = "💻 Laptop" }, - { value = "monitor", label = "🖥️ Monitor" }, - { value = "keyboard", label = "⌨️ Mechanical Keyboard" }, - { value = "mouse", label = "🖱️ Mouse" }, - { value = "headphones", label = "🎧 Headphones" }, - { value = "desk_stand", label = "📊 Desk Stand" }, - { value = "chair", label = "🪑 Ergonomic Chair" }, -] - -# ==================================================================== -# SECTION 5: Security & Compliance -# ==================================================================== - -[[sections]] -title = "Security & Compliance" -description = "Security settings and agreements" - -[[sections.elements]] -name = "enable_2fa" -type = "confirm" -prompt = "Enable two-factor authentication" -help = "Recommended for security" -default = "true" - -[[sections.elements]] -name = "2fa_method" -type = "select" -prompt = "2FA method" -help = "Choose your two-factor authentication method" -when = "enable_2fa == true" -required = true -options = [ - { value = "authenticator", label = "🔐 Authenticator App" }, - { value = "sms", label = "📱 SMS" }, - { value = "security_key", label = "🔑 Security Key" }, -] - -[[sections.elements]] -name = "vpn_required" -type = "confirm" -prompt = "VPN required for data access" -help = "Employee will need VPN for sensitive systems" -default = "false" - -[[sections.elements]] -name = "agreed_policies" -type = "confirm" -prompt = "I agree to company policies and code of conduct" -help = "Must accept to proceed" -required = true -default = "false" - -[[sections.elements]] -name = "privacy_consent" -type = "confirm" -prompt = "I consent to privacy and data handling policies" -help = "Required for employment" -required = true -default = "false" - -# ==================================================================== -# SECTION 6: Additional Information -# ==================================================================== - -[[sections]] -title = "Additional Information" -description = "Extra notes and preferences" - -[[sections.elements]] -name = "emergency_contact" -type = "text" -prompt = "Emergency contact name" -help = "Name of emergency contact person" -placeholder = "Jane Doe" - -[[sections.elements]] -name = "emergency_phone" -type = "text" -prompt = "Emergency contact phone" -help = "Phone number of emergency contact" -placeholder = "+1 (555) 987-6543" - -[[sections.elements]] -name = "additional_notes" -type = "editor" -prompt = "Additional notes" -help = "Any special requirements or notes (optional)" -file_extension = "md" -placeholder = "Special accommodations, preferences, etc." diff --git a/examples/16-agent-form-integration/architecture-form.ncl b/examples/16-agent-form-integration/architecture-form.ncl new file mode 100644 index 0000000..189c791 --- /dev/null +++ b/examples/16-agent-form-integration/architecture-form.ncl @@ -0,0 +1,83 @@ +{ + name = "Software Architecture Questionnaire", + description = "Determine optimal architecture for your project", + elements = [ + # Project Information + { type = "text", name = "project_name", prompt = "Project name", required = true, placeholder = "My Amazing App" }, + { type = "select", name = "project_type", prompt = "Project type", required = true, options = [ + { value = "web_app", label = "Web Application" }, + { value = "mobile_app", label = "Mobile App" }, + { value = "api", label = "REST/GraphQL API" }, + { value = "microservices", label = "Microservices Platform" }, + { value = "data_pipeline", label = "Data Pipeline" }, + { value = "iot", label = "IoT System" }, + ] + }, + { type = "text", name = "team_size", prompt = "Team size (number of developers)", required = true, placeholder = "5" }, + + # Scale Requirements + { type = "text", name = "concurrent_users", prompt = "Expected concurrent users", required = true, placeholder = "10000" }, + { type = "text", name = "daily_active_users", prompt = "Daily active users (DAU)", required = true, placeholder = "100000" }, + { type = "select", name = "data_volume", prompt = "Expected data volume", required = true, options = [ + { value = "small", label = "< 1 GB" }, + { value = "medium", label = "1 GB - 1 TB" }, + { value = "large", label = "1 TB - 100 TB" }, + { value = "huge", label = "> 100 TB" }, + ] + }, + + # Technology Constraints + { type = "multiselect", name = "deployment_platform", prompt = "Deployment platforms", required = true, min_selected = 1, options = [ + { value = "aws", label = "AWS" }, + { value = "gcp", label = "Google Cloud" }, + { value = "azure", label = "Azure" }, + { value = "kubernetes", label = "Kubernetes (self-managed)" }, + { value = "heroku", label = "Heroku" }, + { value = "on_premise", label = "On-premise" }, + ] + }, + { type = "select", name = "budget", prompt = "Annual infrastructure budget", required = true, options = [ + { value = "startup", label = "< $10K" }, + { value = "small", label = "$10K - $100K" }, + { value = "medium", label = "$100K - $1M" }, + { value = "large", label = "> $1M" }, + ] + }, + + # Performance Requirements + { type = "select", name = "target_latency", prompt = "Target API response time", required = true, options = [ + { value = "best_effort", label = "Best effort" }, + { value = "500ms", label = "< 500ms" }, + { value = "200ms", label = "< 200ms" }, + { value = "100ms", label = "< 100ms" }, + { value = "50ms", label = "< 50ms" }, + ] + }, + { type = "select", name = "availability_requirement", prompt = "Required availability", required = true, options = [ + { value = "99.0", label = "99.0% (9 hours down/year)" }, + { value = "99.9", label = "99.9% (43 min down/year)" }, + { value = "99.99", label = "99.99% (4 min down/year)" }, + { value = "99.999", label = "99.999% (26 sec down/year)" }, + ] + }, + + # Features & Capabilities + { type = "multiselect", name = "features", prompt = "Required features", required = true, min_selected = 1, options = [ + { value = "real_time", label = "Real-time updates (WebSocket)" }, + { value = "offline", label = "Offline-first capability" }, + { value = "multi_tenancy", label = "Multi-tenancy support" }, + { value = "search", label = "Full-text search" }, + { value = "analytics", label = "Analytics & reporting" }, + { value = "file_storage", label = "Large file storage" }, + ] + }, + { type = "multiselect", name = "compliance", prompt = "Compliance requirements", options = [ + { value = "gdpr", label = "GDPR" }, + { value = "hipaa", label = "HIPAA" }, + { value = "soc2", label = "SOC 2" }, + { value = "pci_dss", label = "PCI DSS" }, + { value = "iso27001", label = "ISO 27001" }, + ] + }, + ], +} diff --git a/examples/16-agent-form-integration/architecture-form.toml b/examples/16-agent-form-integration/architecture-form.toml deleted file mode 100644 index 65d2bc3..0000000 --- a/examples/16-agent-form-integration/architecture-form.toml +++ /dev/null @@ -1,155 +0,0 @@ -name = "Software Architecture Questionnaire" -description = "Determine optimal architecture for your project" - -[[sections]] -title = "Project Information" -description = "Basic details about your project" - -[[sections.elements]] -name = "project_name" -type = "text" -prompt = "Project name" -required = true -placeholder = "My Amazing App" - -[[sections.elements]] -name = "project_type" -type = "select" -prompt = "Project type" -required = true -options = [ - { value = "web_app", label = "Web Application" }, - { value = "mobile_app", label = "Mobile App" }, - { value = "api", label = "REST/GraphQL API" }, - { value = "microservices", label = "Microservices Platform" }, - { value = "data_pipeline", label = "Data Pipeline" }, - { value = "iot", label = "IoT System" }, -] - -[[sections.elements]] -name = "team_size" -type = "text" -prompt = "Team size (number of developers)" -required = true -placeholder = "5" - -[[sections]] -title = "Scale Requirements" -description = "How large will your application grow?" - -[[sections.elements]] -name = "concurrent_users" -type = "text" -prompt = "Expected concurrent users" -required = true -placeholder = "10000" - -[[sections.elements]] -name = "daily_active_users" -type = "text" -prompt = "Daily active users (DAU)" -required = true -placeholder = "100000" - -[[sections.elements]] -name = "data_volume" -type = "select" -prompt = "Expected data volume" -required = true -options = [ - { value = "small", label = "< 1 GB" }, - { value = "medium", label = "1 GB - 1 TB" }, - { value = "large", label = "1 TB - 100 TB" }, - { value = "huge", label = "> 100 TB" }, -] - -[[sections]] -title = "Technology Constraints" -description = "What are your limitations?" - -[[sections.elements]] -name = "deployment_platform" -type = "multiselect" -prompt = "Deployment platforms" -required = true -min_selected = 1 -options = [ - { value = "aws", label = "AWS" }, - { value = "gcp", label = "Google Cloud" }, - { value = "azure", label = "Azure" }, - { value = "kubernetes", label = "Kubernetes (self-managed)" }, - { value = "heroku", label = "Heroku" }, - { value = "on_premise", label = "On-premise" }, -] - -[[sections.elements]] -name = "budget" -type = "select" -prompt = "Annual infrastructure budget" -required = true -options = [ - { value = "startup", label = "< $10K" }, - { value = "small", label = "$10K - $100K" }, - { value = "medium", label = "$100K - $1M" }, - { value = "large", label = "> $1M" }, -] - -[[sections]] -title = "Performance Requirements" -description = "What are your performance goals?" - -[[sections.elements]] -name = "target_latency" -type = "select" -prompt = "Target API response time" -required = true -options = [ - { value = "best_effort", label = "Best effort" }, - { value = "500ms", label = "< 500ms" }, - { value = "200ms", label = "< 200ms" }, - { value = "100ms", label = "< 100ms" }, - { value = "50ms", label = "< 50ms" }, -] - -[[sections.elements]] -name = "availability_requirement" -type = "select" -prompt = "Required availability" -required = true -options = [ - { value = "99.0", label = "99.0% (9 hours down/year)" }, - { value = "99.9", label = "99.9% (43 min down/year)" }, - { value = "99.99", label = "99.99% (4 min down/year)" }, - { value = "99.999", label = "99.999% (26 sec down/year)" }, -] - -[[sections]] -title = "Features & Capabilities" -description = "What features are essential?" - -[[sections.elements]] -name = "features" -type = "multiselect" -prompt = "Required features" -required = true -min_selected = 1 -options = [ - { value = "real_time", label = "Real-time updates (WebSocket)" }, - { value = "offline", label = "Offline-first capability" }, - { value = "multi_tenancy", label = "Multi-tenancy support" }, - { value = "search", label = "Full-text search" }, - { value = "analytics", label = "Analytics & reporting" }, - { value = "file_storage", label = "Large file storage" }, -] - -[[sections.elements]] -name = "compliance" -type = "multiselect" -prompt = "Compliance requirements" -options = [ - { value = "gdpr", label = "GDPR" }, - { value = "hipaa", label = "HIPAA" }, - { value = "soc2", label = "SOC 2" }, - { value = "pci_dss", label = "PCI DSS" }, - { value = "iso27001", label = "ISO 27001" }, -] diff --git a/examples/16-agent-form-integration/confirmation-form.ncl b/examples/16-agent-form-integration/confirmation-form.ncl new file mode 100644 index 0000000..006a2ad --- /dev/null +++ b/examples/16-agent-form-integration/confirmation-form.ncl @@ -0,0 +1,47 @@ +{ + name = "Architecture Decision Confirmation", + description = "Final stakeholder approval before generating architecture blueprint", + elements = [ + # Architecture Components + { type = "confirm", name = "approve_database", prompt = "Approve database architecture (PostgreSQL + read replicas)", required = true }, + { type = "confirm", name = "approve_api", prompt = "Approve API architecture (GraphQL with Apollo)", required = true }, + { type = "confirm", name = "approve_deployment", prompt = "Approve deployment platform (Kubernetes on AWS EKS)", required = true }, + + # Compliance & Security + { type = "confirm", name = "approve_security", prompt = "Architecture meets security requirements", required = true }, + { type = "confirm", name = "approve_compliance", prompt = "Architecture satisfies compliance requirements (if any)", required = true }, + { type = "confirm", name = "approve_performance", prompt = "Architecture meets performance SLAs (< 200ms API, 99.9% uptime)", required = true }, + + # Team Readiness + { type = "select", name = "team_capacity", prompt = "Team readiness for this architecture", required = true, options = [ + { value = "ready", label = "Team ready now - can deploy immediately" }, + { value = "training", label = "Need 2-4 weeks training on Kubernetes" }, + { value = "hiring", label = "Need to hire platform engineers" }, + { value = "consultant", label = "Need external consulting support" }, + ] + }, + { type = "multiselect", name = "training_needs", prompt = "Training topics needed", options = [ + { value = "kubernetes", label = "Kubernetes operations" }, + { value = "graphql", label = "GraphQL API design" }, + { value = "postgres", label = "PostgreSQL administration" }, + { value = "ci_cd", label = "CI/CD pipeline setup" }, + { value = "monitoring", label = "Observability & monitoring" }, + ] + }, + + # Budget & Timeline Alignment + { type = "confirm", name = "budget_approved", prompt = "Infrastructure budget approved ($15K-25K/month)", required = true }, + { type = "confirm", name = "timeline_realistic", prompt = "Implementation timeline is realistic for team capacity", required = true }, + { type = "text", name = "stakeholder", prompt = "Name of approving stakeholder", required = true, placeholder = "e.g., Engineering Lead, CTO, Product Director" }, + + # Final Sign-Off + { type = "confirm", name = "authorize_blueprint", prompt = "Authorize generation of comprehensive architecture blueprint with ADRs", required = true }, + { type = "select", name = "next_step", prompt = "Next action after approval", required = true, options = [ + { value = "generate_adr", label = "Generate Architecture Decision Records" }, + { value = "implementation_plan", label = "Create detailed implementation plan" }, + { value = "team_review", label = "Schedule team review meeting" }, + { value = "defer", label = "Schedule for later review" }, + ] + }, + ], +} diff --git a/examples/16-agent-form-integration/confirmation-form.toml b/examples/16-agent-form-integration/confirmation-form.toml deleted file mode 100644 index 4a39951..0000000 --- a/examples/16-agent-form-integration/confirmation-form.toml +++ /dev/null @@ -1,119 +0,0 @@ -name = "Architecture Decision Confirmation" -description = "Final stakeholder approval before generating architecture blueprint" - -[[sections]] -title = "Architecture Components" -description = "Confirm each major architectural component" - -[[sections.elements]] -name = "approve_database" -type = "confirm" -prompt = "Approve database architecture (PostgreSQL + read replicas)" -required = true - -[[sections.elements]] -name = "approve_api" -type = "confirm" -prompt = "Approve API architecture (GraphQL with Apollo)" -required = true - -[[sections.elements]] -name = "approve_deployment" -type = "confirm" -prompt = "Approve deployment platform (Kubernetes on AWS EKS)" -required = true - -[[sections]] -title = "Compliance & Security" -description = "Verify non-functional requirements are met" - -[[sections.elements]] -name = "approve_security" -type = "confirm" -prompt = "Architecture meets security requirements" -required = true - -[[sections.elements]] -name = "approve_compliance" -type = "confirm" -prompt = "Architecture satisfies compliance requirements (if any)" -required = true - -[[sections.elements]] -name = "approve_performance" -type = "confirm" -prompt = "Architecture meets performance SLAs (< 200ms API, 99.9% uptime)" -required = true - -[[sections]] -title = "Team Readiness" -description = "Confirm team capacity and training needs" - -[[sections.elements]] -name = "team_capacity" -type = "select" -prompt = "Team readiness for this architecture" -required = true -options = [ - { value = "ready", label = "Team ready now - can deploy immediately" }, - { value = "training", label = "Need 2-4 weeks training on Kubernetes" }, - { value = "hiring", label = "Need to hire platform engineers" }, - { value = "consultant", label = "Need external consulting support" }, -] - -[[sections.elements]] -name = "training_needs" -type = "multiselect" -prompt = "Training topics needed" -options = [ - { value = "kubernetes", label = "Kubernetes operations" }, - { value = "graphql", label = "GraphQL API design" }, - { value = "postgres", label = "PostgreSQL administration" }, - { value = "ci_cd", label = "CI/CD pipeline setup" }, - { value = "monitoring", label = "Observability & monitoring" }, -] - -[[sections]] -title = "Budget & Timeline Alignment" -description = "Confirm resource allocation" - -[[sections.elements]] -name = "budget_approved" -type = "confirm" -prompt = "Infrastructure budget approved ($15K-25K/month)" -required = true - -[[sections.elements]] -name = "timeline_realistic" -type = "confirm" -prompt = "Implementation timeline is realistic for team capacity" -required = true - -[[sections.elements]] -name = "stakeholder" -type = "text" -prompt = "Name of approving stakeholder" -required = true -placeholder = "e.g., Engineering Lead, CTO, Product Director" - -[[sections]] -title = "Final Sign-Off" -description = "Authorize generation of architecture blueprint" - -[[sections.elements]] -name = "authorize_blueprint" -type = "confirm" -prompt = "Authorize generation of comprehensive architecture blueprint with ADRs" -required = true - -[[sections.elements]] -name = "next_step" -type = "select" -prompt = "Next action after approval" -required = true -options = [ - { value = "generate_adr", label = "Generate Architecture Decision Records" }, - { value = "implementation_plan", label = "Create detailed implementation plan" }, - { value = "team_review", label = "Schedule team review meeting" }, - { value = "defer", label = "Schedule for later review" }, -] diff --git a/examples/16-agent-form-integration/recommendations-form.ncl b/examples/16-agent-form-integration/recommendations-form.ncl new file mode 100644 index 0000000..61f1ea4 --- /dev/null +++ b/examples/16-agent-form-integration/recommendations-form.ncl @@ -0,0 +1,48 @@ +{ + name = "Architecture Recommendations Review", + description = "Review and validate agent-generated architecture recommendations", + elements = [ + # Database Architecture + { type = "select", name = "database_choice", prompt = "Database recommendation", required = true, options = [ + { value = "accept", label = "✓ Accept: PostgreSQL with read replicas" }, + { value = "modify", label = "↻ Modify: Use different database" }, + { value = "discuss", label = "? Discuss: Need more information" }, + ] + }, + { type = "text", name = "database_rationale", prompt = "Why this matters", required = false, placeholder = "Agent noted: PostgreSQL handles 10K concurrent users with ACID guarantees" }, + { type = "text", name = "database_concern", prompt = "Any concerns? (if you selected Modify)", placeholder = "e.g., too complex for our team, preference for NoSQL", help = "Leave blank if accepting" }, + + # API Architecture + { type = "select", name = "api_choice", prompt = "API pattern recommendation", required = true, options = [ + { value = "accept", label = "✓ Accept: GraphQL with Apollo Server" }, + { value = "modify", label = "↻ Modify: Use different pattern" }, + { value = "discuss", label = "? Discuss: Need more information" }, + ] + }, + { type = "text", name = "api_rationale", prompt = "Why this matters", required = false, placeholder = "Agent noted: GraphQL reduces over-fetching in microservices" }, + { type = "select", name = "api_alternative", prompt = "Alternative if modifying", help = "Only shown if Modify is selected", options = [ + { value = "rest", label = "REST API (simpler, less flexible)" }, + { value = "grpc", label = "gRPC (faster, more complex)" }, + { value = "custom", label = "Custom approach" }, + ] + }, + + # Deployment Platform + { type = "select", name = "deployment_choice", prompt = "Deployment recommendation", required = true, options = [ + { value = "accept", label = "✓ Accept: Kubernetes on AWS EKS" }, + { value = "modify", label = "↻ Modify: Use different platform" }, + { value = "discuss", label = "? Discuss: Need more information" }, + ] + }, + { type = "text", name = "deployment_rationale", prompt = "Why this matters", required = false, placeholder = "Agent noted: Team has Kubernetes experience, EKS provides managed infrastructure" }, + { type = "text", name = "deployment_concern", prompt = "Any concerns? (if you selected Modify)", placeholder = "e.g., too expensive, team prefers simpler platform", help = "Leave blank if accepting" }, + + # Architecture Trade-offs + { type = "confirm", name = "tradeoff_complexity", prompt = "Acknowledged: Microservices add operational complexity but enable independent scaling", required = true }, + { type = "confirm", name = "tradeoff_cost", prompt = "Acknowledged: EKS + PostgreSQL replicas will require $15K-25K/month infrastructure budget", required = true }, + { type = "confirm", name = "team_ready", prompt = "Team is ready for Kubernetes operational burden", required = true }, + + # Next Steps + { type = "confirm", name = "proceed_synthesis", prompt = "Proceed to synthesis phase to generate complete architecture blueprint?", required = true }, + ], +} diff --git a/examples/16-agent-form-integration/recommendations-form.toml b/examples/16-agent-form-integration/recommendations-form.toml deleted file mode 100644 index 9b215ec..0000000 --- a/examples/16-agent-form-integration/recommendations-form.toml +++ /dev/null @@ -1,125 +0,0 @@ -name = "Architecture Recommendations Review" -description = "Review and validate agent-generated architecture recommendations" - -[[sections]] -title = "Database Architecture" -description = "Agent recommendation for your data layer" - -[[sections.elements]] -name = "database_choice" -type = "select" -prompt = "Database recommendation" -required = true -options = [ - { value = "accept", label = "✓ Accept: PostgreSQL with read replicas" }, - { value = "modify", label = "↻ Modify: Use different database" }, - { value = "discuss", label = "? Discuss: Need more information" }, -] - -[[sections.elements]] -name = "database_rationale" -type = "text" -prompt = "Why this matters" -required = false -placeholder = "Agent noted: PostgreSQL handles 10K concurrent users with ACID guarantees" - -[[sections.elements]] -name = "database_concern" -type = "text" -prompt = "Any concerns? (if you selected Modify)" -help = "Leave blank if accepting" -placeholder = "e.g., too complex for our team, preference for NoSQL" - -[[sections]] -title = "API Architecture" -description = "Agent recommendation for your API layer" - -[[sections.elements]] -name = "api_choice" -type = "select" -prompt = "API pattern recommendation" -required = true -options = [ - { value = "accept", label = "✓ Accept: GraphQL with Apollo Server" }, - { value = "modify", label = "↻ Modify: Use different pattern" }, - { value = "discuss", label = "? Discuss: Need more information" }, -] - -[[sections.elements]] -name = "api_rationale" -type = "text" -prompt = "Why this matters" -required = false -placeholder = "Agent noted: GraphQL reduces over-fetching in microservices" - -[[sections.elements]] -name = "api_alternative" -type = "select" -prompt = "Alternative if modifying" -help = "Only shown if Modify is selected" -options = [ - { value = "rest", label = "REST API (simpler, less flexible)" }, - { value = "grpc", label = "gRPC (faster, more complex)" }, - { value = "custom", label = "Custom approach" }, -] - -[[sections]] -title = "Deployment Platform" -description = "Agent recommendation for infrastructure" - -[[sections.elements]] -name = "deployment_choice" -type = "select" -prompt = "Deployment recommendation" -required = true -options = [ - { value = "accept", label = "✓ Accept: Kubernetes on AWS EKS" }, - { value = "modify", label = "↻ Modify: Use different platform" }, - { value = "discuss", label = "? Discuss: Need more information" }, -] - -[[sections.elements]] -name = "deployment_rationale" -type = "text" -prompt = "Why this matters" -required = false -placeholder = "Agent noted: Team has Kubernetes experience, EKS provides managed infrastructure" - -[[sections.elements]] -name = "deployment_concern" -type = "text" -prompt = "Any concerns? (if you selected Modify)" -help = "Leave blank if accepting" -placeholder = "e.g., too expensive, team prefers simpler platform" - -[[sections]] -title = "Architecture Trade-offs" -description = "Acknowledge key decisions and constraints" - -[[sections.elements]] -name = "tradeoff_complexity" -type = "confirm" -prompt = "Acknowledged: Microservices add operational complexity but enable independent scaling" -required = true - -[[sections.elements]] -name = "tradeoff_cost" -type = "confirm" -prompt = "Acknowledged: EKS + PostgreSQL replicas will require $15K-25K/month infrastructure budget" -required = true - -[[sections.elements]] -name = "team_ready" -type = "confirm" -prompt = "Team is ready for Kubernetes operational burden" -required = true - -[[sections]] -title = "Next Steps" -description = "Approval for synthesis phase" - -[[sections.elements]] -name = "proceed_synthesis" -type = "confirm" -prompt = "Proceed to synthesis phase to generate complete architecture blueprint?" -required = true diff --git a/examples/16-agent-form-integration/validation-rules.ncl b/examples/16-agent-form-integration/validation-rules.ncl new file mode 100644 index 0000000..19b19b4 --- /dev/null +++ b/examples/16-agent-form-integration/validation-rules.ncl @@ -0,0 +1,92 @@ +{ + scale_requirements = { + min_concurrent_users = 5000, + max_concurrent_users = 100000, + min_daily_active_users = 50000, + max_daily_active_users = 1000000, + min_data_volume_gb = 100, + max_data_volume_gb = 1000000, + }, + performance_requirements = { + target_api_latency_ms = 200, + acceptable_p99_latency_ms = 500, + acceptable_p95_latency_ms = 400, + minimum_availability_percentage = 99.9, + acceptable_downtime_minutes_per_year = 43, + }, + team_constraints = { + team_size = 15, + min_kubernetes_expertise = "familiar", + min_database_expertise = "intermediate", + available_on_call_engineers = 3, + }, + budget_constraints = { + min_annual_budget_usd = 100000, + max_annual_budget_usd = 1000000, + max_monthly_infrastructure_usd = 83333, + }, + compliance_requirements = { + required_certifications = ["GDPR", "SOC 2"], + data_residency = "US or EU", + encryption_required = true, + audit_logging_required = true, + backup_retention_days = 90, + }, + database_constraints = { + must_support_acid_transactions = true, + must_support_multi_tenancy = true, + must_support_full_text_search = true, + must_support_real_time_queries = true, + maximum_replication_lag_seconds = 5, + }, + api_constraints = { + must_support_real_time_subscriptions = true, + must_support_multi_tenancy = true, + self_documenting = true, + caching_strategy_required = true, + }, + deployment_constraints = { + must_use_aws = true, + must_support_auto_scaling = true, + must_support_multi_availability_zone = true, + disaster_recovery_rto_hours = 1, + disaster_recovery_rpo_minutes = 30, + }, + operational_constraints = { + maximum_services_per_engineer = 5, + required_deployment_frequency = "daily", + required_rollback_capability = true, + maximum_mean_time_to_recovery_minutes = 15, + }, + validation_rules = { + database_can_handle_max_users = true, + database_latency_acceptable = true, + database_replication_lag_acceptable = true, + database_backup_strategy_defined = true, + api_achieves_target_latency = true, + api_provides_developer_experience = true, + api_caching_strategy_viable = true, + api_real_time_capability_present = true, + deployment_team_can_operate = true, + deployment_cost_within_budget = true, + deployment_disaster_recovery_viable = true, + deployment_compliance_possible = true, + components_work_together = true, + no_major_architectural_conflicts = true, + operational_tools_available = true, + team_training_feasible = true, + }, + risk_thresholds = { + high_risk_if_operational_overhead_score = 8, + medium_risk_if_cost_exceeds_monthly_usd = 20000, + high_risk_if_team_expertise_gap = 3, + medium_risk_if_technical_debt_accumulation_possible = true, + }, + recommended_mitigations = { + postgres_replica_lag = "Implement read-through cache (Redis) with TTL-based invalidation", + graphql_complexity = "Implement query depth limiting and complexity analysis", + kubernetes_operations = "Hire DevOps engineer or use managed control plane add-ons", + multi_tenancy_isolation = "Implement connection pooling per tenant with row-level security", + real_time_at_scale = "Use event streaming (Kafka) with WebSocket connection pooling", + }, +} diff --git a/examples/16-agent-form-integration/validation-rules.toml b/examples/16-agent-form-integration/validation-rules.toml deleted file mode 100644 index 0e3dcc0..0000000 --- a/examples/16-agent-form-integration/validation-rules.toml +++ /dev/null @@ -1,99 +0,0 @@ -# Architecture Validation Rules -# These constraints are checked by the validator agent against recommendations - -[scale_requirements] -min_concurrent_users = 5000 -max_concurrent_users = 100000 -min_daily_active_users = 50000 -max_daily_active_users = 1000000 -min_data_volume_gb = 100 -max_data_volume_gb = 1000000 - -[performance_requirements] -target_api_latency_ms = 200 -acceptable_p99_latency_ms = 500 -acceptable_p95_latency_ms = 400 -minimum_availability_percentage = 99.9 -acceptable_downtime_minutes_per_year = 43 - -[team_constraints] -team_size = 15 -min_kubernetes_expertise = "familiar" -min_database_expertise = "intermediate" -available_on_call_engineers = 3 - -[budget_constraints] -min_annual_budget_usd = 100000 -max_annual_budget_usd = 1000000 -max_monthly_infrastructure_usd = 83333 - -[compliance_requirements] -required_certifications = ["GDPR", "SOC 2"] -data_residency = "US or EU" -encryption_required = true -audit_logging_required = true -backup_retention_days = 90 - -[database_constraints] -must_support_acid_transactions = true -must_support_multi_tenancy = true -must_support_full_text_search = true -must_support_real_time_queries = true -maximum_replication_lag_seconds = 5 - -[api_constraints] -must_support_real_time_subscriptions = true -must_support_multi_tenancy = true -self_documenting = true -caching_strategy_required = true - -[deployment_constraints] -must_use_aws = true -must_support_auto_scaling = true -must_support_multi_availability_zone = true -disaster_recovery_rto_hours = 1 -disaster_recovery_rpo_minutes = 30 - -[operational_constraints] -maximum_services_per_engineer = 5 -required_deployment_frequency = "daily" -required_rollback_capability = true -maximum_mean_time_to_recovery_minutes = 15 - -[validation_rules] -# Database validation -database_can_handle_max_users = true -database_latency_acceptable = true -database_replication_lag_acceptable = true -database_backup_strategy_defined = true - -# API validation -api_achieves_target_latency = true -api_provides_developer_experience = true -api_caching_strategy_viable = true -api_real_time_capability_present = true - -# Deployment validation -deployment_team_can_operate = true -deployment_cost_within_budget = true -deployment_disaster_recovery_viable = true -deployment_compliance_possible = true - -# Integration validation -components_work_together = true -no_major_architectural_conflicts = true -operational_tools_available = true -team_training_feasible = true - -[risk_thresholds] -high_risk_if_operational_overhead_score = 8 -medium_risk_if_cost_exceeds_monthly_usd = 20000 -high_risk_if_team_expertise_gap = 3 -medium_risk_if_technical_debt_accumulation_possible = true - -[recommended_mitigations] -postgres_replica_lag = "Implement read-through cache (Redis) with TTL-based invalidation" -graphql_complexity = "Implement query depth limiting and complexity analysis" -kubernetes_operations = "Hire DevOps engineer or use managed control plane add-ons" -multi_tenancy_isolation = "Implement connection pooling per tenant with row-level security" -real_time_at_scale = "Use event streaming (Kafka) with WebSocket connection pooling" diff --git a/examples/17-advanced-i18n/checkout-form.ncl b/examples/17-advanced-i18n/checkout-form.ncl new file mode 100644 index 0000000..d0ba574 --- /dev/null +++ b/examples/17-advanced-i18n/checkout-form.ncl @@ -0,0 +1,54 @@ +{ + name = "E-Commerce Checkout", + description = "Complete checkout form demonstrating advanced i18n features", + elements = [ + # Order Summary + { type = "text", name = "item_count", prompt = "Number of items", required = true, placeholder = "1", help = "Demonstrates pluralization rules for each locale" }, + { type = "text", name = "subtotal", prompt = "Subtotal amount", required = true, placeholder = "99.99", help = "Will be formatted as currency per locale" }, + { type = "text", name = "tax_rate", prompt = "Tax rate percentage", required = true, placeholder = "7.5", help = "Tax varies per locale" }, + { type = "select", name = "shipping_method", prompt = "Shipping method", required = true, options = [ + { value = "standard", label = "Standard (5-7 days)" }, + { value = "express", label = "Express (2-3 days)" }, + { value = "overnight", label = "Overnight" }, + ] + }, + + # Delivery Information + { type = "select", name = "recipient_gender", prompt = "Recipient salutation", help = "Used for gender-aware greetings in some languages", options = [ + { value = "male", label = "Mr." }, + { value = "female", label = "Ms." }, + { value = "other", label = "Other" }, + ] + }, + { type = "text", name = "recipient_name", prompt = "Recipient name", required = true, placeholder = "John Smith" }, + { type = "date", name = "delivery_date", prompt = "Preferred delivery date", required = true, help = "Date format will adjust per locale" }, + { type = "text", name = "delivery_notes", prompt = "Special delivery instructions", placeholder = "Leave at door", help = "Optional notes for courier" }, + + # Billing Address + { type = "select", name = "country", prompt = "Country", required = true, options = [ + { value = "us", label = "🇺🇸 United States" }, + { value = "gb", label = "🇬🇧 United Kingdom" }, + { value = "es", label = "🇪🇸 Spain" }, + { value = "mx", label = "🇲🇽 Mexico" }, + { value = "br", label = "🇧🇷 Brazil" }, + { value = "pt", label = "🇵🇹 Portugal" }, + { value = "fr", label = "🇫🇷 France" }, + { value = "jp", label = "🇯🇵 Japan" }, + { value = "sa", label = "🇸🇦 Saudi Arabia" }, + ] + }, + { type = "select", name = "payment_method", prompt = "Payment method", required = true, options = [ + { value = "credit_card", label = "💳 Credit Card" }, + { value = "debit_card", label = "💳 Debit Card" }, + { value = "paypal", label = "🅿️ PayPal" }, + { value = "bank_transfer", label = "🏦 Bank Transfer" }, + { value = "local", label = "Local Payment Method" }, + ] + }, + + # Order Review + { type = "confirm", name = "agree_terms", prompt = "I agree to the terms and conditions", required = true }, + { type = "confirm", name = "subscribe_newsletter", prompt = "Subscribe to our newsletter", default = true }, + { type = "confirm", name = "save_address", prompt = "Save this address for future orders", default = true }, + ], +} diff --git a/examples/17-advanced-i18n/checkout-form.toml b/examples/17-advanced-i18n/checkout-form.toml deleted file mode 100644 index 3bff29c..0000000 --- a/examples/17-advanced-i18n/checkout-form.toml +++ /dev/null @@ -1,134 +0,0 @@ -name = "E-Commerce Checkout" -description = "Complete checkout form demonstrating advanced i18n features" -locale = "en-US" - -[[sections]] -title = "Order Summary" -description = "Review your order before checkout" - -[[sections.elements]] -name = "item_count" -type = "text" -prompt = "Number of items" -required = true -help = "Demonstrates pluralization rules for each locale" -placeholder = "1" - -[[sections.elements]] -name = "subtotal" -type = "text" -prompt = "Subtotal amount" -required = true -help = "Will be formatted as currency per locale" -placeholder = "99.99" - -[[sections.elements]] -name = "tax_rate" -type = "text" -prompt = "Tax rate percentage" -required = true -help = "Tax varies per locale" -placeholder = "7.5" - -[[sections.elements]] -name = "shipping_method" -type = "select" -prompt = "Shipping method" -required = true -options = [ - { value = "standard", label = "Standard (5-7 days)" }, - { value = "express", label = "Express (2-3 days)" }, - { value = "overnight", label = "Overnight" }, -] - -[[sections]] -title = "Delivery Information" -description = "Where should we send your order?" - -[[sections.elements]] -name = "recipient_gender" -type = "select" -prompt = "Recipient salutation" -help = "Used for gender-aware greetings in some languages" -options = [ - { value = "male", label = "Mr." }, - { value = "female", label = "Ms." }, - { value = "other", label = "Other" }, -] - -[[sections.elements]] -name = "recipient_name" -type = "text" -prompt = "Recipient name" -required = true -placeholder = "John Smith" - -[[sections.elements]] -name = "delivery_date" -type = "date" -prompt = "Preferred delivery date" -required = true -help = "Date format will adjust per locale" - -[[sections.elements]] -name = "delivery_notes" -type = "text" -prompt = "Special delivery instructions" -help = "Optional notes for courier" -placeholder = "Leave at door" - -[[sections]] -title = "Billing Address" -description = "Billing information" - -[[sections.elements]] -name = "country" -type = "select" -prompt = "Country" -required = true -options = [ - { value = "us", label = "🇺🇸 United States" }, - { value = "gb", label = "🇬🇧 United Kingdom" }, - { value = "es", label = "🇪🇸 Spain" }, - { value = "mx", label = "🇲🇽 Mexico" }, - { value = "br", label = "🇧🇷 Brazil" }, - { value = "pt", label = "🇵🇹 Portugal" }, - { value = "fr", label = "🇫🇷 France" }, - { value = "jp", label = "🇯🇵 Japan" }, - { value = "sa", label = "🇸🇦 Saudi Arabia" }, -] - -[[sections.elements]] -name = "payment_method" -type = "select" -prompt = "Payment method" -required = true -options = [ - { value = "credit_card", label = "💳 Credit Card" }, - { value = "debit_card", label = "💳 Debit Card" }, - { value = "paypal", label = "🅿️ PayPal" }, - { value = "bank_transfer", label = "🏦 Bank Transfer" }, - { value = "local", label = "Local Payment Method" }, -] - -[[sections]] -title = "Order Review" -description = "Confirm your order details" - -[[sections.elements]] -name = "agree_terms" -type = "confirm" -prompt = "I agree to the terms and conditions" -required = true - -[[sections.elements]] -name = "subscribe_newsletter" -type = "confirm" -prompt = "Subscribe to our newsletter" -default = true - -[[sections.elements]] -name = "save_address" -type = "confirm" -prompt = "Save this address for future orders" -default = true diff --git a/examples/17-advanced-i18n/i18n-config.ncl b/examples/17-advanced-i18n/i18n-config.ncl new file mode 100644 index 0000000..c7ed5a8 --- /dev/null +++ b/examples/17-advanced-i18n/i18n-config.ncl @@ -0,0 +1,144 @@ +{ + locales = { + default = "en-US", + fallback = "en-US", + en-US = { + name = "English (United States)", + direction = "ltr", + plurals = 2, + decimal_separator = ".", + thousands_separator = ",", + date_format = "MM/DD/YYYY", + currency_symbol = "$", + currency_position = "prefix", + }, + en-GB = { + name = "English (United Kingdom)", + direction = "ltr", + plurals = 2, + decimal_separator = ".", + thousands_separator = ",", + date_format = "DD/MM/YYYY", + currency_symbol = "£", + currency_position = "prefix", + }, + es-ES = { + name = "Spanish (Spain)", + direction = "ltr", + plurals = 2, + decimal_separator = ",", + thousands_separator = ".", + date_format = "DD/MM/YYYY", + currency_symbol = "€", + currency_position = "suffix", + gender_aware = true, + }, + es-MX = { + name = "Spanish (Mexico)", + direction = "ltr", + plurals = 2, + decimal_separator = ".", + thousands_separator = ",", + date_format = "DD/MM/YYYY", + currency_symbol = "$", + currency_position = "prefix", + gender_aware = true, + }, + pt-BR = { + name = "Portuguese (Brazil)", + direction = "ltr", + plurals = 2, + decimal_separator = ",", + thousands_separator = ".", + date_format = "DD/MM/YYYY", + currency_symbol = "R$", + currency_position = "prefix", + }, + pt-PT = { + name = "Portuguese (Portugal)", + direction = "ltr", + plurals = 2, + decimal_separator = ",", + thousands_separator = ".", + date_format = "DD/MM/YYYY", + currency_symbol = "€", + currency_position = "suffix", + }, + fr-FR = { + name = "French (France)", + direction = "ltr", + plurals = 3, + special_plural_rules = true, + decimal_separator = ",", + thousands_separator = ".", + date_format = "DD/MM/YYYY", + currency_symbol = "€", + currency_position = "suffix", + gender_aware = true, + }, + ja-JP = { + name = "Japanese (Japan)", + direction = "ltr", + plurals = 1, + decimal_separator = ".", + thousands_separator = ",", + date_format = "YYYY年M月D日", + currency_symbol = "¥", + currency_position = "prefix", + decimal_places = 0, + }, + ar-SA = { + name = "Arabic (Saudi Arabia)", + direction = "rtl", + plurals = 6, + decimal_separator = ".", + thousands_separator = ",", + date_format = "DD MMMM YYYY", + currency_symbol = "﷼", + currency_position = "prefix", + use_arabic_numerals = true, + }, + }, + fallback_chains = { + pt-BR = ["pt-PT", "es-ES", "en-US"], + pt-PT = ["es-ES", "en-US"], + es-MX = ["es-ES", "en-US"], + es-ES = ["en-US"], + en-GB = ["en-US"], + fr-FR = ["en-US"], + ja-JP = ["en-US"], + ar-SA = ["en-US"], + }, + features = { + pluralization = true, + gender_agreement = true, + number_formatting = true, + date_formatting = true, + rtl_support = true, + fallback_chains = true, + context_variables = true, + }, + validation = { + require_all_keys_in_default = false, + require_all_keys_in_fallback = false, + warn_missing_keys = true, + warn_incomplete_translations = true, + validate_plural_forms = true, + }, + coverage = { + critical_locales = ["en-US", "es-ES", "fr-FR", "ja-JP", "ar-SA"], + required_coverage_percent = 90, + warning_coverage_percent = 75, + }, + testing = { + test_plurals = true, + test_count_values = [0, 1, 2, 3, 5, 10, 11, 21, 100, 1000], + test_dates = true, + test_amounts = true, + test_amount_values = [1.99, 10.00, 100.50, 1234.56, 9999.99], + test_rtl = true, + rtl_test_locales = ["ar-SA"], + test_gender = true, + gender_test_locales = ["es-ES", "es-MX", "pt-BR", "pt-PT", "fr-FR", "ar-SA"], + }, +} diff --git a/examples/17-advanced-i18n/i18n-config.toml b/examples/17-advanced-i18n/i18n-config.toml deleted file mode 100644 index 6a904ed..0000000 --- a/examples/17-advanced-i18n/i18n-config.toml +++ /dev/null @@ -1,164 +0,0 @@ -# i18n Configuration for TypeDialog Example 17 -# Defines fallback chains, locale information, and translation rules - -[locales] -default = "en-US" -fallback = "en-US" - -# Locale definitions with metadata -[locales.en-US] -name = "English (United States)" -direction = "ltr" -plurals = 2 -decimal_separator = "." -thousands_separator = "," -date_format = "MM/DD/YYYY" -currency_symbol = "$" -currency_position = "prefix" - -[locales.en-GB] -name = "English (United Kingdom)" -direction = "ltr" -plurals = 2 -decimal_separator = "." -thousands_separator = "," -date_format = "DD/MM/YYYY" -currency_symbol = "£" -currency_position = "prefix" - -[locales.es-ES] -name = "Spanish (Spain)" -direction = "ltr" -plurals = 2 -decimal_separator = "," -thousands_separator = "." -date_format = "DD/MM/YYYY" -currency_symbol = "€" -currency_position = "suffix" -gender_aware = true - -[locales.es-MX] -name = "Spanish (Mexico)" -direction = "ltr" -plurals = 2 -decimal_separator = "." -thousands_separator = "," -date_format = "DD/MM/YYYY" -currency_symbol = "$" -currency_position = "prefix" -gender_aware = true - -[locales.pt-BR] -name = "Portuguese (Brazil)" -direction = "ltr" -plurals = 2 -decimal_separator = "," -thousands_separator = "." -date_format = "DD/MM/YYYY" -currency_symbol = "R$" -currency_position = "prefix" - -[locales.pt-PT] -name = "Portuguese (Portugal)" -direction = "ltr" -plurals = 2 -decimal_separator = "," -thousands_separator = "." -date_format = "DD/MM/YYYY" -currency_symbol = "€" -currency_position = "suffix" - -[locales.fr-FR] -name = "French (France)" -direction = "ltr" -plurals = 3 -special_plural_rules = true -decimal_separator = "," -thousands_separator = "." -date_format = "DD/MM/YYYY" -currency_symbol = "€" -currency_position = "suffix" -gender_aware = true - -[locales.ja-JP] -name = "Japanese (Japan)" -direction = "ltr" -plurals = 1 -decimal_separator = "." -thousands_separator = "," -date_format = "YYYY年M月D日" -currency_symbol = "¥" -currency_position = "prefix" -decimal_places = 0 - -[locales.ar-SA] -name = "Arabic (Saudi Arabia)" -direction = "rtl" -plurals = 6 -decimal_separator = "." -thousands_separator = "," -date_format = "DD MMMM YYYY" -currency_symbol = "﷼" -currency_position = "prefix" -use_arabic_numerals = true - -# Fallback chains for locale resolution -[fallback_chains] -# Portuguese Brazil → Portuguese Portugal → Spanish Spain → English -pt-BR = ["pt-PT", "es-ES", "en-US"] -# Portuguese Portugal → Spanish Spain → English -pt-PT = ["es-ES", "en-US"] -# Spanish Mexico → Spanish Spain → English -es-MX = ["es-ES", "en-US"] -# Spanish Spain → English -es-ES = ["en-US"] -# English UK → English US -en-GB = ["en-US"] -# French → English -fr-FR = ["en-US"] -# Japanese → English -ja-JP = ["en-US"] -# Arabic → English -ar-SA = ["en-US"] - -# Feature detection -[features] -pluralization = true -gender_agreement = true -number_formatting = true -date_formatting = true -rtl_support = true -fallback_chains = true -context_variables = true - -# Validation rules -[validation] -require_all_keys_in_default = false -require_all_keys_in_fallback = false -warn_missing_keys = true -warn_incomplete_translations = true -validate_plural_forms = true - -# Coverage thresholds -[coverage] -critical_locales = ["en-US", "es-ES", "fr-FR", "ja-JP", "ar-SA"] -required_coverage_percent = 90 -warning_coverage_percent = 75 - -[testing] -# Test all pluralization forms -test_plurals = true -test_count_values = [0, 1, 2, 3, 5, 10, 11, 21, 100, 1000] - -# Test with various dates and amounts -test_dates = true -test_amounts = true -test_amount_values = [1.99, 10.00, 100.50, 1234.56, 9999.99] - -# Test RTL text handling -test_rtl = true -rtl_test_locales = ["ar-SA"] - -# Test gender-aware strings -test_gender = true -gender_test_locales = ["es-ES", "es-MX", "pt-BR", "pt-PT", "fr-FR", "ar-SA"] diff --git a/justfiles/dev.just b/justfiles/dev.just index 71b8b6d..b20feb6 100644 --- a/justfiles/dev.just +++ b/justfiles/dev.just @@ -218,10 +218,13 @@ lint-all: # Audit dependencies for security [doc("Audit dependencies")] audit: - @echo "=== Auditing dependencies ===" - @command -v cargo-audit >/dev/null || (echo "cargo-audit not installed"; exit 1) - cargo audit - @echo "✓ Audit complete" + #!/bin/bash + set -e + echo "=== Auditing dependencies ===" + command -v cargo-audit >/dev/null || (echo "cargo-audit not installed"; exit 1) + export CARGO_HOME="${HOME}/.cargo" + cargo audit --db "${CARGO_HOME}/advisory-db" + echo "✓ Audit complete" # === DOCUMENTATION ===