chore: add docs and example about nickel-roundtrip
This commit is contained in:
parent
2e75e2106c
commit
f4d3a6472b
332
docs/nickel.md
332
docs/nickel.md
@ -104,15 +104,22 @@ let rendered = template_engine.render("form.j2", &context)?;
|
||||
|
||||
### roundtrip
|
||||
|
||||
Idempotent read/write for Nickel schemas.
|
||||
Idempotent read/write for Nickel schemas - complete workflow from `.ncl` → form → `.ncl`.
|
||||
|
||||
```rust
|
||||
use typedialog_core::nickel::RoundtripConfig;
|
||||
|
||||
let config = RoundtripConfig::default();
|
||||
let schema = parser.parse_file("form.ncl")?;
|
||||
let modified = transform(schema)?;
|
||||
serializer.write_file("form.ncl", &modified, &config)?;
|
||||
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:**
|
||||
@ -121,6 +128,13 @@ serializer.write_file("form.ncl", &modified, &config)?;
|
||||
- Formatting
|
||||
- Import structure
|
||||
- Custom layouts
|
||||
- Field contracts and validators
|
||||
|
||||
**Returns:**
|
||||
|
||||
- `RoundtripSummary` with diff viewer
|
||||
- Validation status
|
||||
- Change detection (what fields changed)
|
||||
|
||||
## Schema Structure
|
||||
|
||||
@ -188,6 +202,314 @@ serializer.write_file("form.ncl", &modified, &config)?;
|
||||
}
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Nickel Integration
|
||||
|
||||
Type-safe form schema generation using Nickel configuration language.
|
||||
Type-safe form schema generation and roundtrip workflows using Nickel configuration language.
|
||||
|
||||
## Files
|
||||
|
||||
@ -10,10 +10,12 @@ Type-safe form schema generation using Nickel configuration language.
|
||||
## About Nickel
|
||||
|
||||
Nickel is a powerful configuration language that provides:
|
||||
|
||||
- Strong typing
|
||||
- Validation rules
|
||||
- Reusable schemas
|
||||
- Inheritance and composition
|
||||
- **Roundtrip support** - Edit existing configs via forms
|
||||
|
||||
## Usage
|
||||
|
||||
@ -30,6 +32,44 @@ nickel eval nickel_schema.ncl > form_config.toml
|
||||
cargo run -p typedialog-web -- --config form_config.toml
|
||||
```
|
||||
|
||||
### Roundtrip Workflow (Edit Existing Configs)
|
||||
|
||||
**New!** Edit existing Nickel configurations through interactive forms:
|
||||
|
||||
```bash
|
||||
# CLI backend (command-line prompts)
|
||||
typedialog nickel-roundtrip \
|
||||
--input config.ncl \
|
||||
--form ci-form.toml \
|
||||
--output config.ncl \
|
||||
--ncl-template config.ncl.j2
|
||||
|
||||
# TUI backend (full-screen terminal UI)
|
||||
typedialog-tui nickel-roundtrip \
|
||||
--input config.ncl \
|
||||
--form ci-form.toml \
|
||||
--output config.ncl \
|
||||
--ncl-template config.ncl.j2
|
||||
|
||||
# Web backend (browser-based form with HTML diff)
|
||||
typedialog-web nickel-roundtrip \
|
||||
--input config.ncl \
|
||||
--form ci-form.toml \
|
||||
--output config.ncl \
|
||||
--ncl-template config.ncl.j2
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
- ✓ Load existing values from `.ncl` files
|
||||
- ✓ Pre-populate form fields with current config
|
||||
- ✓ Generate new `.ncl` using templates
|
||||
- ✓ Show diff summary (what changed)
|
||||
- ✓ Automatic validation with `nickel typecheck`
|
||||
- ✓ HTML summary page (web backend only)
|
||||
|
||||
**See complete example:** `../08-nickel-roundtrip/`
|
||||
|
||||
### Example Nickel Schema
|
||||
|
||||
```nickel
|
||||
@ -57,11 +97,13 @@ cargo run -p typedialog-web -- --config form_config.toml
|
||||
|
||||
## Advanced Features
|
||||
|
||||
- Field inheritance
|
||||
- Custom validators
|
||||
- Conditional schemas
|
||||
- Template-driven generation
|
||||
- Schema composition
|
||||
- **Field inheritance** - Reuse common field definitions
|
||||
- **Custom validators** - Built-in schema validation
|
||||
- **Conditional schemas** - Dynamic form generation
|
||||
- **Template-driven generation** - Tera template support
|
||||
- **Schema composition** - Combine multiple schemas
|
||||
- **Roundtrip editing** - Edit existing configs via forms (NEW!)
|
||||
- **Diff viewer** - See what changed after editing (NEW!)
|
||||
|
||||
## Learn More
|
||||
|
||||
|
||||
25
examples/08-nickel-roundtrip/01-generate-initial-config.sh
Executable file
25
examples/08-nickel-roundtrip/01-generate-initial-config.sh
Executable file
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "📝 Generating initial CI configuration..."
|
||||
echo ""
|
||||
|
||||
# config.ncl is already provided as the initial state
|
||||
if [ -f "config.ncl" ]; then
|
||||
echo "✅ Initial config.ncl already exists"
|
||||
echo ""
|
||||
echo "Current configuration:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
cat config.ncl
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " ./02-roundtrip-cli.sh # Edit with CLI backend"
|
||||
echo " ./03-roundtrip-tui.sh # Edit with TUI backend"
|
||||
echo " ./04-roundtrip-web.sh # Edit with Web backend"
|
||||
else
|
||||
echo "❌ config.ncl not found!"
|
||||
exit 1
|
||||
fi
|
||||
33
examples/08-nickel-roundtrip/02-roundtrip-cli.sh
Executable file
33
examples/08-nickel-roundtrip/02-roundtrip-cli.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "🔧 Running nickel-roundtrip with CLI backend..."
|
||||
echo ""
|
||||
|
||||
# Check if config.ncl exists
|
||||
if [ ! -f "config.ncl" ]; then
|
||||
echo "❌ config.ncl not found! Run ./01-generate-initial-config.sh first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Backup current config
|
||||
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 \
|
||||
--output config.ncl \
|
||||
--ncl-template config.ncl.j2 \
|
||||
--verbose
|
||||
|
||||
echo ""
|
||||
echo "✅ Roundtrip completed!"
|
||||
echo ""
|
||||
echo "Review changes:"
|
||||
echo " cat config.ncl"
|
||||
echo ""
|
||||
echo "Run again:"
|
||||
echo " ./02-roundtrip-cli.sh"
|
||||
33
examples/08-nickel-roundtrip/03-roundtrip-tui.sh
Executable file
33
examples/08-nickel-roundtrip/03-roundtrip-tui.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "🎨 Running nickel-roundtrip with TUI backend..."
|
||||
echo ""
|
||||
|
||||
# Check if config.ncl exists
|
||||
if [ ! -f "config.ncl" ]; then
|
||||
echo "❌ config.ncl not found! Run ./01-generate-initial-config.sh first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Backup current config
|
||||
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 \
|
||||
--output config.ncl \
|
||||
--ncl-template config.ncl.j2 \
|
||||
--verbose
|
||||
|
||||
echo ""
|
||||
echo "✅ Roundtrip completed!"
|
||||
echo ""
|
||||
echo "Review changes:"
|
||||
echo " cat config.ncl"
|
||||
echo ""
|
||||
echo "Run again:"
|
||||
echo " ./03-roundtrip-tui.sh"
|
||||
49
examples/08-nickel-roundtrip/04-roundtrip-web.sh
Executable file
49
examples/08-nickel-roundtrip/04-roundtrip-web.sh
Executable file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "🌐 Running nickel-roundtrip with Web backend..."
|
||||
echo ""
|
||||
|
||||
# Check if config.ncl exists
|
||||
if [ ! -f "config.ncl" ]; then
|
||||
echo "❌ config.ncl not found! Run ./01-generate-initial-config.sh first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Backup current config
|
||||
cp config.ncl "config.ncl.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
echo "Starting web server on http://localhost:8080"
|
||||
echo "Your browser should open automatically..."
|
||||
echo ""
|
||||
echo "Features:"
|
||||
echo " ✓ All fields pre-populated with current values"
|
||||
echo " ✓ Real-time validation"
|
||||
echo " ✓ HTML diff viewer on submit"
|
||||
echo " ✓ Download button for config.ncl"
|
||||
echo " ✓ Auto-close after 30 seconds"
|
||||
echo ""
|
||||
echo "Press Ctrl+C after form is submitted to exit"
|
||||
echo ""
|
||||
|
||||
# Open browser (macOS)
|
||||
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 \
|
||||
--output config.ncl \
|
||||
--ncl-template config.ncl.j2 \
|
||||
--verbose
|
||||
|
||||
echo ""
|
||||
echo "✅ Roundtrip completed!"
|
||||
echo ""
|
||||
echo "Review changes:"
|
||||
echo " cat config.ncl"
|
||||
echo ""
|
||||
echo "Run again:"
|
||||
echo " ./04-roundtrip-web.sh"
|
||||
260
examples/08-nickel-roundtrip/README.md
Normal file
260
examples/08-nickel-roundtrip/README.md
Normal file
@ -0,0 +1,260 @@
|
||||
# Nickel Roundtrip Example
|
||||
|
||||
Complete example demonstrating the roundtrip workflow: load existing configuration, edit via interactive form, and regenerate with diff viewer.
|
||||
|
||||
## Scenario
|
||||
|
||||
CI configuration management - edit GitHub Actions settings through a form interface while preserving the Nickel structure.
|
||||
|
||||
## Files
|
||||
|
||||
- **config.ncl** - Input/output Nickel configuration
|
||||
- **ci-form.toml** - Form definition with `nickel_path` mappings
|
||||
- **config.ncl.j2** - Tera template for generating output
|
||||
- **01-generate-initial-config.sh** - Create initial config
|
||||
- **02-roundtrip-cli.sh** - Edit via CLI backend
|
||||
- **03-roundtrip-tui.sh** - Edit via TUI backend
|
||||
- **04-roundtrip-web.sh** - Edit via Web backend
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Generate initial configuration
|
||||
./01-generate-initial-config.sh
|
||||
|
||||
# 2. Edit with your preferred backend:
|
||||
|
||||
# CLI (command-line prompts)
|
||||
./02-roundtrip-cli.sh
|
||||
|
||||
# TUI (full-screen terminal UI)
|
||||
./03-roundtrip-tui.sh
|
||||
|
||||
# Web (browser-based form)
|
||||
./04-roundtrip-web.sh
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### `nickel_path` Mapping
|
||||
|
||||
Every field in `ci-form.toml` has a `nickel_path` attribute mapping to the Nickel structure:
|
||||
|
||||
```toml
|
||||
[[elements]]
|
||||
name = "parallel_jobs"
|
||||
type = "text"
|
||||
prompt = "Parallel Jobs"
|
||||
default = "4"
|
||||
nickel_path = ["ci", "github_actions", "parallel_jobs"]
|
||||
```
|
||||
|
||||
Maps to:
|
||||
|
||||
```nickel
|
||||
{
|
||||
ci = {
|
||||
github_actions = {
|
||||
parallel_jobs = 4
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Template Rendering
|
||||
|
||||
The `config.ncl.j2` template uses form values to generate valid Nickel:
|
||||
|
||||
```jinja2
|
||||
{
|
||||
project = {
|
||||
name = "{{ project_name }}",
|
||||
description = "{{ project_description }}",
|
||||
},
|
||||
|
||||
ci = {
|
||||
github_actions = {
|
||||
enabled = {{ enable_github_actions }},
|
||||
parallel_jobs = {{ parallel_jobs }},
|
||||
timeout_minutes = {{ timeout_minutes }},
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Summary with Diff
|
||||
|
||||
After editing, see what changed:
|
||||
|
||||
```text
|
||||
╔════════════════════════════════════════════════════════════╗
|
||||
║ ✅ Configuration Saved Successfully! ║
|
||||
╠════════════════════════════════════════════════════════════╣
|
||||
║ 📄 File: config.ncl ║
|
||||
║ ✓ Validation: ✓ PASSED ║
|
||||
║ 📊 Fields: 3/12 changed, 9 unchanged ║
|
||||
╠════════════════════════════════════════════════════════════╣
|
||||
║ 📋 What Changed: ║
|
||||
║ ├─ parallel_jobs: 4 → 8 ║
|
||||
║ ├─ timeout_minutes: 60 → 120 ║
|
||||
║ ├─ enable_cache: false → true ║
|
||||
╠════════════════════════════════════════════════════════════╣
|
||||
║ 💡 Next Steps: ║
|
||||
║ • Review: cat config.ncl ║
|
||||
║ • Apply CI tools: ./setup-ci.sh ║
|
||||
║ • Re-configure: ./ci-configure.sh ║
|
||||
╚════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
## Workflow Details
|
||||
|
||||
### 1. Load Defaults
|
||||
|
||||
Roundtrip reads `config.ncl` and extracts values using `nickel_path`:
|
||||
|
||||
```rust
|
||||
// Internally:
|
||||
nickel export config.ncl // → JSON
|
||||
extract_value_by_path(json, ["ci", "github_actions", "parallel_jobs"]) // → 4
|
||||
```
|
||||
|
||||
### 2. Populate Form
|
||||
|
||||
Form fields get pre-filled with current values:
|
||||
|
||||
- Text fields show current text
|
||||
- Numbers show current numbers
|
||||
- Booleans show current true/false
|
||||
- Select fields pre-select current option
|
||||
|
||||
### 3. Edit Interactively
|
||||
|
||||
User edits via chosen backend (CLI/TUI/Web).
|
||||
|
||||
### 4. Generate Output
|
||||
|
||||
Template renders with new values:
|
||||
|
||||
```jinja2
|
||||
parallel_jobs = {{ parallel_jobs }}, // User changed 4 → 8
|
||||
```
|
||||
|
||||
### 5. Validate
|
||||
|
||||
Automatic validation:
|
||||
|
||||
```bash
|
||||
nickel typecheck config.ncl
|
||||
```
|
||||
|
||||
### 6. Show Summary
|
||||
|
||||
Terminal summary (all backends) + HTML summary (web only).
|
||||
|
||||
## Backend Comparison
|
||||
|
||||
| Feature | CLI | TUI | Web |
|
||||
|---------|-----|-----|-----|
|
||||
| Terminal UI | ✓ | ✓ | ✓ (summary only) |
|
||||
| Browser UI | ✗ | ✗ | ✓ |
|
||||
| Pre-populated values | ✓ | ✓ | ✓ |
|
||||
| Real-time validation | ✗ | ✓ | ✓ |
|
||||
| HTML diff viewer | ✗ | ✗ | ✓ |
|
||||
| Download button | ✗ | ✗ | ✓ |
|
||||
|
||||
## Customization
|
||||
|
||||
### Add More Fields
|
||||
|
||||
1. **Update form:**
|
||||
|
||||
```toml
|
||||
[[elements]]
|
||||
name = "rust_version"
|
||||
type = "select"
|
||||
prompt = "Rust Version"
|
||||
options = [
|
||||
{ value = "stable", label = "Stable" },
|
||||
{ value = "nightly", label = "Nightly" }
|
||||
]
|
||||
nickel_path = ["ci", "rust", "version"]
|
||||
```
|
||||
|
||||
2. **Update template:**
|
||||
|
||||
```jinja2
|
||||
ci = {
|
||||
rust = {
|
||||
version = "{{ rust_version }}",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Update initial config:**
|
||||
|
||||
```nickel
|
||||
{
|
||||
ci = {
|
||||
rust = {
|
||||
version = "stable"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Change Template Logic
|
||||
|
||||
Add conditionals:
|
||||
|
||||
```jinja2
|
||||
{% if enable_cache %}
|
||||
cache = {
|
||||
enabled = true,
|
||||
paths = {{ cache_paths | json }},
|
||||
},
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### Disable Validation
|
||||
|
||||
```bash
|
||||
typedialog nickel-roundtrip \
|
||||
--input config.ncl \
|
||||
--form ci-form.toml \
|
||||
--output config.ncl \
|
||||
--ncl-template config.ncl.j2 \
|
||||
--no-validate # ← Skip nickel typecheck
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**No defaults loaded:**
|
||||
|
||||
```bash
|
||||
# Check nickel export works
|
||||
nickel export config.ncl
|
||||
|
||||
# Verify all fields have nickel_path
|
||||
grep "nickel_path" ci-form.toml | wc -l
|
||||
```
|
||||
|
||||
**Template errors:**
|
||||
|
||||
```bash
|
||||
# Test template separately
|
||||
echo '{"project_name": "test"}' | \
|
||||
tera --template config.ncl.j2
|
||||
```
|
||||
|
||||
**Validation fails:**
|
||||
|
||||
```bash
|
||||
# Check manually
|
||||
nickel typecheck config.ncl
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
- [nickel.md](../../docs/nickel.md) - Full roundtrip documentation
|
||||
- [Nickel Language](https://nickel-lang.org) - Nickel reference
|
||||
- [Tera Templates](https://tera.netlify.app) - Template syntax
|
||||
237
examples/08-nickel-roundtrip/ci-form.toml
Normal file
237
examples/08-nickel-roundtrip/ci-form.toml
Normal file
@ -0,0 +1,237 @@
|
||||
name = "CI Configuration Editor"
|
||||
description = "Interactive form for editing CI/CD configuration"
|
||||
display_mode = "complete"
|
||||
|
||||
# =============================================================================
|
||||
# PROJECT INFORMATION
|
||||
# =============================================================================
|
||||
|
||||
[[elements]]
|
||||
name = "project_header"
|
||||
type = "section_header"
|
||||
title = "📦 Project Information"
|
||||
border_top = true
|
||||
border_bottom = true
|
||||
|
||||
[[elements]]
|
||||
name = "project_name"
|
||||
type = "text"
|
||||
prompt = "Project Name"
|
||||
required = true
|
||||
default = "my-rust-project"
|
||||
nickel_path = ["project", "name"]
|
||||
|
||||
[[elements]]
|
||||
name = "project_description"
|
||||
type = "text"
|
||||
prompt = "Project Description"
|
||||
required = true
|
||||
default = "A Rust project with CI/CD"
|
||||
nickel_path = ["project", "description"]
|
||||
|
||||
[[elements]]
|
||||
name = "project_repository"
|
||||
type = "text"
|
||||
prompt = "Repository URL"
|
||||
required = true
|
||||
default = "https://github.com/example/my-rust-project"
|
||||
nickel_path = ["project", "repository"]
|
||||
|
||||
# =============================================================================
|
||||
# GITHUB ACTIONS CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
[[elements]]
|
||||
name = "github_header"
|
||||
type = "section_header"
|
||||
title = "🚀 GitHub Actions Configuration"
|
||||
border_top = true
|
||||
border_bottom = true
|
||||
|
||||
[[elements]]
|
||||
name = "enable_github_actions"
|
||||
type = "confirm"
|
||||
prompt = "Enable GitHub Actions CI?"
|
||||
default = true
|
||||
nickel_path = ["ci", "github_actions", "enabled"]
|
||||
|
||||
[[elements]]
|
||||
name = "parallel_jobs"
|
||||
type = "text"
|
||||
prompt = "Parallel Jobs"
|
||||
default = "4"
|
||||
when = "enable_github_actions == true"
|
||||
help = "Number of parallel CI jobs (1-20)"
|
||||
nickel_path = ["ci", "github_actions", "parallel_jobs"]
|
||||
|
||||
[[elements]]
|
||||
name = "timeout_minutes"
|
||||
type = "text"
|
||||
prompt = "Job Timeout (minutes)"
|
||||
default = "60"
|
||||
when = "enable_github_actions == true"
|
||||
help = "Maximum duration for each job"
|
||||
nickel_path = ["ci", "github_actions", "timeout_minutes"]
|
||||
|
||||
# =============================================================================
|
||||
# RUST CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
[[elements]]
|
||||
name = "rust_header"
|
||||
type = "section_header"
|
||||
title = "🦀 Rust Configuration"
|
||||
border_top = true
|
||||
border_bottom = true
|
||||
when = "enable_github_actions == true"
|
||||
|
||||
[[elements]]
|
||||
name = "rust_version"
|
||||
type = "select"
|
||||
prompt = "Rust Version"
|
||||
default = "stable"
|
||||
when = "enable_github_actions == true"
|
||||
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" },
|
||||
]
|
||||
nickel_path = ["ci", "github_actions", "rust", "version"]
|
||||
|
||||
# =============================================================================
|
||||
# CACHING
|
||||
# =============================================================================
|
||||
|
||||
[[elements]]
|
||||
name = "cache_header"
|
||||
type = "section_header"
|
||||
title = "💾 Build Cache"
|
||||
border_top = true
|
||||
border_bottom = true
|
||||
when = "enable_github_actions == true"
|
||||
|
||||
[[elements]]
|
||||
name = "enable_cache"
|
||||
type = "confirm"
|
||||
prompt = "Enable dependency caching?"
|
||||
default = false
|
||||
when = "enable_github_actions == true"
|
||||
help = "Cache Cargo dependencies to speed up builds"
|
||||
nickel_path = ["ci", "github_actions", "cache", "enabled"]
|
||||
|
||||
# =============================================================================
|
||||
# TOOLS CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
[[elements]]
|
||||
name = "tools_header"
|
||||
type = "section_header"
|
||||
title = "🔧 CI Tools"
|
||||
border_top = true
|
||||
border_bottom = true
|
||||
when = "enable_github_actions == true"
|
||||
|
||||
[[elements]]
|
||||
name = "enable_clippy"
|
||||
type = "confirm"
|
||||
prompt = "Enable cargo clippy (linter)?"
|
||||
default = true
|
||||
when = "enable_github_actions == true"
|
||||
help = "Run Rust linter on code"
|
||||
nickel_path = ["ci", "github_actions", "tools", "clippy", "enabled"]
|
||||
|
||||
[[elements]]
|
||||
name = "clippy_args"
|
||||
type = "text"
|
||||
prompt = "Clippy Arguments"
|
||||
default = "-D warnings"
|
||||
when = "enable_github_actions == true && enable_clippy == true"
|
||||
help = "Command-line arguments for clippy"
|
||||
nickel_path = ["ci", "github_actions", "tools", "clippy", "args"]
|
||||
|
||||
[[elements]]
|
||||
name = "enable_rustfmt"
|
||||
type = "confirm"
|
||||
prompt = "Enable rustfmt (formatter)?"
|
||||
default = true
|
||||
when = "enable_github_actions == true"
|
||||
help = "Check code formatting"
|
||||
nickel_path = ["ci", "github_actions", "tools", "rustfmt", "enabled"]
|
||||
|
||||
[[elements]]
|
||||
name = "rustfmt_edition"
|
||||
type = "select"
|
||||
prompt = "Rust Edition for rustfmt"
|
||||
default = "2021"
|
||||
when = "enable_github_actions == true && enable_rustfmt == true"
|
||||
options = [
|
||||
{ value = "2015", label = "2015" },
|
||||
{ value = "2018", label = "2018" },
|
||||
{ value = "2021", label = "2021" },
|
||||
]
|
||||
nickel_path = ["ci", "github_actions", "tools", "rustfmt", "edition"]
|
||||
|
||||
[[elements]]
|
||||
name = "enable_cargo_audit"
|
||||
type = "confirm"
|
||||
prompt = "Enable cargo-audit (security)?"
|
||||
default = false
|
||||
when = "enable_github_actions == true"
|
||||
help = "Scan for security vulnerabilities"
|
||||
nickel_path = ["ci", "github_actions", "tools", "cargo_audit", "enabled"]
|
||||
|
||||
# =============================================================================
|
||||
# DEPLOYMENT
|
||||
# =============================================================================
|
||||
|
||||
[[elements]]
|
||||
name = "deployment_header"
|
||||
type = "section_header"
|
||||
title = "🚢 Deployment"
|
||||
border_top = true
|
||||
border_bottom = true
|
||||
|
||||
[[elements]]
|
||||
name = "enable_deployment"
|
||||
type = "confirm"
|
||||
prompt = "Enable deployment?"
|
||||
default = false
|
||||
help = "Automatically deploy on successful builds"
|
||||
nickel_path = ["deployment", "enabled"]
|
||||
|
||||
[[elements]]
|
||||
name = "deployment_environment"
|
||||
type = "select"
|
||||
prompt = "Deployment Environment"
|
||||
default = "production"
|
||||
when = "enable_deployment == true"
|
||||
options = [
|
||||
{ value = "development", label = "Development" },
|
||||
{ value = "staging", label = "Staging" },
|
||||
{ value = "production", label = "Production" },
|
||||
]
|
||||
nickel_path = ["deployment", "environment"]
|
||||
|
||||
[[elements]]
|
||||
name = "auto_deploy"
|
||||
type = "confirm"
|
||||
prompt = "Auto-deploy on main branch?"
|
||||
default = false
|
||||
when = "enable_deployment == true"
|
||||
help = "Automatically deploy when main branch is updated"
|
||||
nickel_path = ["deployment", "auto_deploy"]
|
||||
|
||||
# =============================================================================
|
||||
# SUMMARY
|
||||
# =============================================================================
|
||||
|
||||
[[elements]]
|
||||
name = "summary_header"
|
||||
type = "section_header"
|
||||
title = "✅ Review & Save"
|
||||
border_top = true
|
||||
|
||||
[[elements]]
|
||||
name = "summary"
|
||||
type = "section"
|
||||
content = "Review your configuration above. Click Submit to save and see what changed."
|
||||
48
examples/08-nickel-roundtrip/config.ncl
Normal file
48
examples/08-nickel-roundtrip/config.ncl
Normal file
@ -0,0 +1,48 @@
|
||||
# CI Configuration
|
||||
# Generated by TypeDialog - Edit via ci-configure.sh
|
||||
|
||||
{
|
||||
project = {
|
||||
name = "my-rust-project",
|
||||
description = "A Rust project with CI/CD",
|
||||
repository = "https://github.com/example/my-rust-project",
|
||||
},
|
||||
|
||||
ci = {
|
||||
github_actions = {
|
||||
enabled = true,
|
||||
parallel_jobs = 4,
|
||||
timeout_minutes = 60,
|
||||
|
||||
rust = {
|
||||
version = "stable",
|
||||
targets = ["x86_64-unknown-linux-gnu"],
|
||||
},
|
||||
|
||||
cache = {
|
||||
enabled = false,
|
||||
paths = [],
|
||||
},
|
||||
|
||||
tools = {
|
||||
clippy = {
|
||||
enabled = true,
|
||||
args = ["-D", "warnings"],
|
||||
},
|
||||
rustfmt = {
|
||||
enabled = true,
|
||||
edition = "2021",
|
||||
},
|
||||
cargo_audit = {
|
||||
enabled = false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
deployment = {
|
||||
enabled = false,
|
||||
environment = "production",
|
||||
auto_deploy = false,
|
||||
},
|
||||
}
|
||||
52
examples/08-nickel-roundtrip/config.ncl.j2
Normal file
52
examples/08-nickel-roundtrip/config.ncl.j2
Normal file
@ -0,0 +1,52 @@
|
||||
# CI Configuration
|
||||
# Generated by TypeDialog - Edit via ci-configure.sh
|
||||
|
||||
{
|
||||
project = {
|
||||
name = "{{ project_name }}",
|
||||
description = "{{ project_description }}",
|
||||
repository = "{{ project_repository }}",
|
||||
},
|
||||
|
||||
ci = {
|
||||
github_actions = {
|
||||
enabled = {{ enable_github_actions }},
|
||||
parallel_jobs = {{ parallel_jobs }},
|
||||
timeout_minutes = {{ timeout_minutes }},
|
||||
|
||||
rust = {
|
||||
version = "{{ rust_version }}",
|
||||
targets = ["x86_64-unknown-linux-gnu"],
|
||||
},
|
||||
|
||||
cache = {
|
||||
enabled = {{ enable_cache }},
|
||||
paths = [],
|
||||
},
|
||||
|
||||
tools = {
|
||||
clippy = {
|
||||
enabled = {{ enable_clippy }},
|
||||
{% if enable_clippy and clippy_args %}
|
||||
args = {{ clippy_args | split(pat=" ") | json }},
|
||||
{% else %}
|
||||
args = [],
|
||||
{% endif %}
|
||||
},
|
||||
rustfmt = {
|
||||
enabled = {{ enable_rustfmt }},
|
||||
edition = "{{ rustfmt_edition }}",
|
||||
},
|
||||
cargo_audit = {
|
||||
enabled = {{ enable_cargo_audit }},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
deployment = {
|
||||
enabled = {{ enable_deployment }},
|
||||
environment = "{{ deployment_environment }}",
|
||||
auto_deploy = {{ auto_deploy }},
|
||||
},
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user