TypeDialog Logo
# Nickel Schema Support Full support for type-safe form schemas using the Nickel language. ## Overview TypeDialog integrates Nickel for: - **Type-safe schemas** - Validate form structure at parse time - **Contract validation** - Enforce type and content contracts - **I18n extraction** - Auto-extract translatable strings - **Template rendering** - Generate forms via Tera templates - **Roundtrip support** - Idempotent read/write transformations ## Quick Start ### Prerequisites Install Nickel CLI (optional, for schema validation): ```bash cargo install nickel-lang # Or download from: https://github.com/nickel-lang/nickel/releases ``` ### Run Examples ```bash # List all Nickel examples just test::list | grep nickel # Run basic Nickel schema cargo run --example basic_nickel_schema # Run with roundtrip (read, transform, write) cargo run --example nickel_roundtrip # Run with i18n extraction cargo run --example nickel_i18n_extraction ``` See [examples/07-nickel-generation/](../examples/07-nickel-generation/) for complete examples. ## Core Modules ### contract_parser Parse and validate Nickel contracts. ```rust use typedialog_core::nickel::ContractParser; let parser = ContractParser::new(); let contracts = parser.parse(nickel_schema)?; ``` **Validates:** - Type correctness - Field constraints - Required vs optional fields - Custom predicates ### i18n_extractor Automatically extract translatable strings from Nickel schemas. ```rust use typedialog_core::nickel::I18nExtractor; let extractor = I18nExtractor::new(); let strings = extractor.extract(nickel_ast)?; // Output: Map of translatable strings with locations ``` **Extracts:** - Field labels - Help text - Error messages - Custom metadata ### template_renderer Render Nickel schemas using Tera templates. ```rust use typedialog_core::nickel::NickelTemplateContext; let context = NickelTemplateContext::from_schema(schema)?; let rendered = template_engine.render("form.j2", &context)?; ``` **Supports:** - Tera template syntax - Schema introspection - Custom filters - Conditional rendering ### roundtrip Idempotent read/write for Nickel schemas - complete workflow from `.ncl` → form → `.ncl`. ```rust use typedialog_core::nickel::RoundtripConfig; let mut config = RoundtripConfig::with_template( input_ncl, // Path to existing .ncl file form_toml, // Path to form definition output_ncl, // Path for generated .ncl template, // Optional .ncl.j2 template ); config.validate = true; config.verbose = true; // Execute with any backend (CLI, TUI, Web) let result = config.execute_with_backend(backend.as_mut()).await?; ``` **Preserves:** - Comments - Formatting - Import structure - Custom layouts - Field contracts and validators **Returns:** - `RoundtripSummary` with diff viewer - Validation status - Change detection (what fields changed) ## Schema Structure ### Basic Form ```nicl { title = "User Registration", fields = { name = { type = "text", label = "Full Name", required = true, }, email = { type = "email", label = "Email Address", required = true, }, } } ``` ### With Contracts ```nicl { fields = { password = { type = "text", label = "Password", contracts = [ { predicate = |s| string.length s >= 8, error = "Min 8 chars" }, { predicate = |s| string.contains s "!", error = "Need special char" }, ] } } } ``` ### With I18n ```nicl { i18n = "en-US", fields = { name = { label = "i18n:form.fields.name", help = "i18n:form.fields.name.help", } } } ``` ### With Templates ```nicl { template = "form.j2", context = { theme = "dark", layout = "grid", }, fields = { ... } } ``` ## Roundtrip Workflow The roundtrip workflow enables interactive reconfiguration of existing Nickel files through forms, preserving structure and generating human-readable diffs. ### Roundtrip Overview **Workflow:** `config.ncl` (input) → **Form** (edit) → `config.ncl` (output) 1. **Load** existing `.ncl` configuration 2. **Extract** default values using `nickel_path` mappings 3. **Populate** form with current values 4. **Edit** via CLI/TUI/Web interface 5. **Generate** new `.ncl` using template (or preserve contracts) 6. **Validate** with `nickel typecheck` 7. **Show** summary with diff viewer ### The `nickel_path` Attribute **Critical:** Fields MUST have `nickel_path` to participate in roundtrip. Maps form field names to nested Nickel structure: ```toml [[elements]] name = "parallel_jobs" type = "text" prompt = "Parallel Jobs" default = "4" nickel_path = ["ci", "github_actions", "parallel_jobs"] ``` Extracts from: ```nickel { ci = { github_actions = { parallel_jobs = 4 } } } ``` **Rules:** - Array of strings representing path - Each element is a nested key - Top-level: `["field_name"]` - Nested: `["parent", "child", "field"]` - Arrays: Use RepeatingGroup with `nickel_path = ["array_name"]` ### Roundtrip with CLI Backend ```bash typedialog nickel-roundtrip \ --input config.ncl \ --form ci-form.toml \ --output config.ncl \ --ncl-template config.ncl.j2 \ --verbose ``` **Output:** ```text ╔════════════════════════════════════════════════════════════╗ ║ ✅ Configuration Saved Successfully! ║ ╠════════════════════════════════════════════════════════════╣ ║ 📄 File: config.ncl ║ ║ ✓ Validation: ✓ PASSED ║ ║ 📊 Fields: 5/27 changed, 22 unchanged ║ ╠════════════════════════════════════════════════════════════╣ ║ 📋 What Changed: ║ ║ ├─ parallel_jobs: 4 → 8 ║ ║ ├─ timeout_minutes: 60 → 120 ║ ║ ├─ enable_clippy: true → false ║ ║ ├─ rust_version: stable → nightly ║ ║ ├─ cache_enabled: false → true ║ ╠════════════════════════════════════════════════════════════╣ ║ 💡 Next Steps: ║ ║ • Review: cat config.ncl ║ ║ • Apply CI tools: ./setup-ci.sh ║ ║ • Re-configure: ./ci-configure.sh ║ ╚════════════════════════════════════════════════════════════╝ ``` ### Roundtrip with TUI Backend ```bash typedialog-tui nickel-roundtrip \ --input config.ncl \ --form ci-form.toml \ --output config.ncl \ --ncl-template config.ncl.j2 ``` Interactive TUI form + terminal summary (same as CLI). ### Roundtrip with Web Backend ```bash typedialog-web nickel-roundtrip \ --input config.ncl \ --form ci-form.toml \ --output config.ncl \ --ncl-template config.ncl.j2 \ --verbose ``` **Features:** - Opens browser to `http://localhost:8080` - All fields pre-populated with current values - Real-time validation - **Summary page** on submit: - Visual diff viewer (old → new) - Field statistics - Download button for generated config - Auto-close after 30 seconds **Terminal output:** ```text Starting interactive form on http://localhost:8080 Complete the form and submit to continue... Web UI available at http://localhost:8080 [web] Complete form initialized with 63 default values ╔════════════════════════════════════════════════════════════╗ ║ ✅ Configuration Saved Successfully! ║ ╠════════════════════════════════════════════════════════════╣ ... ``` ### Form Requirements For roundtrip to work, your form MUST: 1. **Include `nickel_path` on ALL fields:** ```toml [[elements]] name = "project_name" nickel_path = ["project", "name"] # ✅ Required ``` 2. **Use correct path syntax:** ```toml # Flat field nickel_path = ["enable_ci"] # Nested field nickel_path = ["ci", "github_actions", "timeout"] # Array field (RepeatingGroup) nickel_path = ["ci", "tools", "linters"] ``` 3. **Match template variables:** ```jinja2 # Template: config.ncl.j2 { project = { name = "{{ project_name }}", # ← Form field "project_name" }, ci = { enabled = {{ enable_ci }}, # ← Form field "enable_ci" } } ``` ### Template Support Use Tera templates (`.ncl.j2`) for complex output structures: **Template:** `config.ncl.j2` ```jinja2 # Generated by TypeDialog let imports = import "ci/lib.ncl" let validators = import "ci/validators.ncl" { project = { name = "{{ project_name }}", version = "{{ project_version }}", }, ci = { github_actions = { enabled = {{ enable_github_actions }}, parallel_jobs = {{ parallel_jobs }}, timeout_minutes = {{ timeout_minutes }}, {% if enable_cache %} cache = { enabled = true, paths = {{ cache_paths | json }}, }, {% endif %} }, tools = { {% for tool in ci_tools %} {{ tool.name }} = { enabled = {{ tool.enabled }}, version = "{{ tool.version }}", }, {% endfor %} }, } } | validators.CiConfig ``` **Form values are injected automatically.** ### Complete Example See `examples/08-nickel-roundtrip/` for a full CI configuration workflow: ```bash cd examples/08-nickel-roundtrip/ # Initial setup ./01-generate-initial-config.sh # Edit with CLI ./02-roundtrip-cli.sh # Edit with TUI ./03-roundtrip-tui.sh # Edit with Web ./04-roundtrip-web.sh ``` ### Validation Roundtrip automatically validates output: ```bash typedialog nickel-roundtrip \ --input config.ncl \ --form form.toml \ --output config.ncl \ --ncl-template template.ncl.j2 # Runs: nickel typecheck config.ncl ``` **Disable validation:** ```bash typedialog nickel-roundtrip ... --no-validate ``` ### Summary Output All backends generate summaries showing: - **Total fields:** How many fields in the form - **Changed fields:** Fields with different values - **Unchanged fields:** Fields that kept the same value - **Validation status:** Pass/fail from `nickel typecheck` - **Change list:** Detailed old → new for each change **Verbose mode** shows ALL changes (not just first 10): ```bash typedialog nickel-roundtrip ... --verbose ``` ### Roundtrip Troubleshooting **Issue: "No default values loaded"** ✓ Check all fields have `nickel_path`: ```bash grep -r "nickel_path" form.toml ``` **Issue: "Field not found in output"** ✓ Verify template includes the field: ```bash grep "{{ field_name }}" template.ncl.j2 ``` **Issue: "Validation failed"** ✓ Check Nickel syntax manually: ```bash nickel typecheck config.ncl ``` **Issue: "Values not showing in web form"** ✓ Ensure `nickel export` works on input: ```bash nickel export config.ncl ``` ## Building with Nickel Build project with Nickel support: ```bash # Build (includes Nickel feature by default) just build::default # Test Nickel modules just test::core # Generate docs just dev::docs ``` ## Examples Complete examples in [examples/07-nickel-generation/](../examples/07-nickel-generation/): ### Schemas - **simple.ncl** - Basic form schema - **complex.ncl** - Advanced with sections and groups - **conditional.ncl** - Conditional field visibility - **i18n.ncl** - Internationalization example ### Templates - **config.ncl.j2** - Configuration form template - **deployment.ncl.j2** - Deployment spec template - **service_spec.ncl.j2** - Service specification template ### Usage ```bash cd examples/07-nickel-generation/ # Validate schema nickel export schemas/simple.ncl # Parse and render cargo run --manifest-path ../../Cargo.toml \ --example nickel_form_generation < schemas/simple.ncl ``` ## Integration Patterns ### CLI Backend ```bash typedialog --format nickel form.ncl ``` ### TUI Backend ```bash typedialog-tui form.ncl ``` ### Web Backend ```bash typedialog-web --config form.ncl ``` ## Advanced Topics ### Custom Contracts Define custom validation predicates: ```nicl { fields = { age = { type = "number", contracts = [ { predicate = |n| n >= 18, error = "Must be 18+" } ] } } } ``` ### Reusable Components ```nicl let address_fields = { street = { type = "text", label = "Street" }, city = { type = "text", label = "City" }, zip = { type = "text", label = "ZIP" }, }; { fields = address_fields & { country = { type = "select", label = "Country" } } } ``` ### Schema Inheritance Extend base schemas: ```nicl let base = import "schemas/base.ncl"; base & { fields = base.fields & { custom_field = { ... } } } ``` ### I18n Management Extract and manage translations: ```bash # Extract all translatable strings cargo run --example nickel_i18n_extraction < form.ncl > strings.json # Update translations # (use with translation management system) ``` ## Performance Considerations ### Parsing - Schemas parsed once at startup - AST cached in memory - Validation is O(n) in field count ### Rendering - Templates compiled once - Context generation O(n) - Output streamed for large forms ### Roundtrip - Preserves source formatting - Minimal allocations - In-place transformations ## Troubleshooting ### Schema validation fails Check syntax with Nickel CLI: ```bash nickel export form.ncl ``` ### Contract errors Verify predicates are boolean functions: ```nicl # ❌ Wrong contracts = [ { predicate = |s| string.length s, error = "..." } ] # ✓ Correct contracts = [ { predicate = |s| string.length s >= 8, error = "..." } ] ``` ### Template rendering fails Ensure Tera template exists and context matches: ```bash # Verify template ls -la templates/form.j2 # Check context keys cargo run --example nickel_template_context < form.ncl ``` ### I18n strings not extracted Verify string format: ```nicl # ❌ Plain string label = "Name" # ✓ I18n reference label = "i18n:form.name" ``` ## Best Practices 1. **Schema Organization** - Keep schemas modular - Use `import` for shared components - Version schemas in git 2. **Contracts** - Keep predicates simple - Provide clear error messages - Test edge cases 3. **I18n** - Use consistent key naming - Extract before translations - Validate all strings are referenced 4. **Templates** - Keep templates simple - Use filters for formatting - Test with multiple data types 5. **Roundtrip** - Always test read/write cycles - Preserve comments with care - Validate output structure ## API Reference ### ContractParser ```rust pub struct ContractParser; impl ContractParser { pub fn new() -> Self; pub fn parse(&self, source: &str) -> Result; } pub struct ParsedContracts { pub fields: Map, pub global: Vec, } ``` ### I18nExtractor ```rust pub struct I18nExtractor; impl I18nExtractor { pub fn new() -> Self; pub fn extract(&self, ast: &NickelAst) -> Result; } ``` ### NickelTemplateContext ```rust pub struct NickelTemplateContext { pub schema: NickelSchemaIR, pub fields: Map, } impl NickelTemplateContext { pub fn from_schema(schema: NickelSchemaIR) -> Result; } ``` ### RoundtripConfig ```rust pub struct RoundtripConfig { pub preserve_comments: bool, pub preserve_formatting: bool, pub indent: String, } impl Default for RoundtripConfig { fn default() -> Self { Self { preserve_comments: true, preserve_formatting: true, indent: " ".to_string(), } } } ``` ## Next Steps - [development.md](development.md) - Build and test - [configuration.md](configuration.md) - Backend configuration - [Examples](../examples/07-nickel-generation/) - Complete examples - [Nickel Language](https://nickel-lang.org) - Language reference --- **Latest Update**: December 2024 **Status**: Stable