# nu_plugin_nickel Nushell plugin for seamless Nickel configuration language integration. Load, evaluate, format, and validate Nickel files directly from Nushell scripts. ## Quick Start ```bash # Build and register the plugin just build-plugin nu_plugin_nickel just register-plugin nu_plugin_nickel # Verify installation plugin list | where name == "nickel-export" ``` ## Overview 5 commands for working with Nickel files: | Command | Purpose | |---------|---------| | `nickel-export` | Export Nickel to JSON/YAML (with smart type conversion) | | `nickel-eval` | Evaluate with automatic caching (for config loading) | | `nickel-format` | Format Nickel files (in-place) | | `nickel-validate` | Validate all Nickel files in a directory | | `nickel-cache-status` | Show cache statistics | ## Core Concept: Smart Type Conversion The plugin converts output intelligently based on whether you specify a format: ``` nickel-export config.ncl → Nushell object (parsed JSON) nickel-export config.ncl -f json → Raw JSON string nickel-export config.ncl -f yaml → Raw YAML string ``` **Why?** Default behavior gives you structured data for programming. Explicit `-f` gives you raw output for external tools. --- ## Complete Usage Guide ### 1. Load Configuration as Object (Most Common) **Without `-f` flag → Returns Nushell object** ```nu # Load configuration into a variable let config = nickel-export workspace/config.ncl # Access nested values with cell paths $config.database.host # "localhost" $config.database.port # 5432 $config.database.username # "admin" # Work with arrays $config.servers | length # 3 $config.servers | map {|s| $s.name} # Filter and transform $config.services | where enabled == true | each {|svc| {name: $svc.name, port: $svc.port}} ``` ### 2. Get Raw Output (For External Tools) **With `-f` flag → Returns raw string** ```nu # Export as raw JSON string let json = nickel-export config.ncl -f json $json | save output.json # Export as raw YAML string nickel-export config.ncl -f yaml | save config.yaml # Pipe to external tools nickel-export config.ncl -f json | jq '.database' nickel-export config.ncl -f json | curl -X POST -d @- http://api.example.com ``` ### 3. Primary Config Loader with Caching **`nickel-eval` is optimized for configuration loading** ```nu # Load with automatic caching (80-90% hit rate) let cfg = nickel-eval workspace/provisioning.ncl # Works just like nickel-export (same smart conversion) $cfg.infrastructure.cloud_provider # "aws" $cfg.infrastructure.region # "us-east-1" # Caching is transparent nickel-eval config.ncl # First call: 100-200ms nickel-eval config.ncl # Subsequent calls: 1-5ms ``` ### 4. Format Nickel Files ```nu # Format a single file (modifies in place) nickel-format config.ncl # Format multiple files glob "**/*.ncl" | each {|f| nickel-format $f} ``` ### 5. Validate Nickel Project ```nu # Validate all .ncl files in directory nickel-validate ./workspace/config # Validate current directory nickel-validate # Output: # ✅ workspace/config/main.ncl # ✅ workspace/config/lib.ncl # ✅ workspace/config/vars.ncl ``` ### 6. Check Cache Status ```nu nickel-cache-status # Output: # ╭──────────────────────────────────────────────────╮ # │ cache_dir: ~/.cache/provisioning/config-cache/ │ # │ entries: 42 │ # │ enabled: true │ # ╰──────────────────────────────────────────────────╯ ``` --- ## Real-World Examples ### Example 1: Multi-Environment Configuration ```nu # Load environment-specific config let env = "production" let config = nickel-eval "config/provisioning-($env).ncl" # Use in deployment def deploy [service: string] { let svc_config = $config.services | where name == $service | first print $"Deploying ($service) to ($svc_config.region)" print $" Image: ($svc_config.docker.image)" print $" Replicas: ($svc_config.replicas)" print $" Port: ($svc_config.port)" } deploy "api-server" ``` ### Example 2: Generate Kubernetes Manifests ```nu # Load infrastructure config let infra = nickel-eval "infrastructure.ncl" # Generate K8s manifests $infra.services | each {|svc| { apiVersion: "apps/v1" kind: "Deployment" metadata: {name: $svc.name} spec: { replicas: $svc.replicas template: { spec: { containers: [{ name: $svc.name image: $svc.docker.image ports: [{containerPort: $svc.port}] }] } } } } } | each {|manifest| $manifest | to json | save "k8s/($manifest.metadata.name).yaml" } ``` ### Example 3: Configuration Validation Script ```nu def validate-config [config-path: path] { # Validate syntax nickel-validate $config-path | print # Load and check required fields let config = nickel-eval $config-path let required = ["database", "services", "infrastructure"] $required | each {|field| if ($config | has $field) { print $"✅ ($field): present" } else { print $"❌ ($field): MISSING" } } # Check configuration consistency let db_replicas = $config.database.replicas let svc_replicas = ($config.services | map {|s| $s.replicas} | math sum) if $db_replicas >= $svc_replicas { print "✅ Database replicas sufficient" } else { print "❌ WARNING: Services exceed database capacity" } } validate-config "workspace/config.ncl" ``` ### Example 4: Generate Configuration from Template ```nu # Load base config template let template = nickel-eval "templates/base.ncl" # Customize for specific environment let prod_config = { environment: "production" debug: false replicas: ($template.replicas * 3) services: ($template.services | map {|s| $s | merge { replicas: 5 resources: { memory: "2Gi" cpu: "1000m" } } }) } # Export as JSON $prod_config | to json | save "production-config.json" ``` ### Example 5: Merge Multiple Configurations ```nu # Load base config let base = nickel-eval "config/base.ncl" # Load environment-specific overrides let env_overrides = nickel-eval "config/($env).ncl" # Load local customizations let local = nickel-eval "config/local.ncl" # Merge with precedence: local > env > base let final_config = $base | merge $env_overrides | merge $local print $"Final configuration:" print $final_config ``` --- ## Command Reference ### nickel-export ```nu nickel-export [-f FORMAT] [-o OUTPUT] ``` **Arguments:** - `FILE` - Path to Nickel file (required) **Flags:** - `-f, --format` - Output format: `json` (default), `yaml` (optional) - `-o, --output` - Save to file instead of returning value (optional) **Return Type:** - Without `-f`: Nushell object (Record/List) - With `-f json`: Raw JSON string - With `-f yaml`: Raw YAML string **Examples:** ```nu nickel-export config.ncl # → object nickel-export config.ncl -f json # → JSON string nickel-export config.ncl -f yaml -o out.yaml ``` ### nickel-eval ```nu nickel-eval [-f FORMAT] [--cache] ``` **Arguments:** - `FILE` - Path to Nickel file (required) **Flags:** - `-f, --format` - Output format: `json` (default), `yaml` (optional) - `--cache` - Use caching (enabled by default, flag for future use) **Return Type:** - Without `-f`: Nushell object (with caching) - With `-f json`: Raw JSON string (with caching) - With `-f yaml`: Raw YAML string (with caching) **Examples:** ```nu nickel-eval workspace/config.ncl # → cached object nickel-eval config.ncl -f json # → cached JSON string let cfg = nickel-eval config.ncl $cfg.database.host ``` ### nickel-format ```nu nickel-format ``` **Arguments:** - `FILE` - Path to Nickel file to format (required) **Examples:** ```nu nickel-format config.ncl glob "**/*.ncl" | each {|f| nickel-format $f} ``` ### nickel-validate ```nu nickel-validate [DIR] ``` **Arguments:** - `DIR` - Directory to validate (optional, defaults to current directory) **Examples:** ```nu nickel-validate ./workspace/config nickel-validate ``` ### nickel-cache-status ```nu nickel-cache-status ``` Returns record with cache information: - `cache_dir` - Cache directory path - `entries` - Number of cached entries - `enabled` - Whether caching is enabled **Examples:** ```nu nickel-cache-status let cache = nickel-cache-status print $"Cache has ($cache.entries) entries at ($cache.cache_dir)" ``` --- ## Type Conversion Details ### Without `-f` Flag (Object Mode) The plugin converts Nickel output to Nushell types: ``` JSON Input → Nushell Type ───────────────────────────────────── {"key": "value"} → {key: "value"} [1, 2, 3] → [1, 2, 3] "string" → "string" 123 → 123 true → true null → null ``` Enables full Nushell data access: ```nu let config = nickel-export config.ncl # Cell path access $config.database.host # Filtering $config.services | where enabled == true # Transformation $config.services | map {|s| {name: $s.name, port: $s.port}} # Custom functions def get-service [name: string] { $config.services | where name == $name | first } ``` ### With `-f` Flag (Raw Mode) Returns unprocessed string: ```nu nickel-export config.ncl -f json # Returns: "{\"database\":{\"host\":\"localhost\", ...}}" nickel-export config.ncl -f yaml # Returns: "database:\n host: localhost\n ..." ``` Use for: - Saving to files with specific format - Piping to external JSON/YAML tools - API calls requiring raw format - Integration with non-Nushell tools --- ## Caching ### Automatic Caching with `nickel-eval` Results are cached using content-addressed storage: ``` Cache location: ~/.cache/provisioning/config-cache/ Cache key: SHA256(file_content + format) First call: ~100-200ms Cached calls: ~1-5ms ``` **Characteristics:** - Non-blocking (errors are silently ignored) - Transparent (no configuration needed) - Hit rate: ~80-90% in typical workflows - Per-format caching (json and yaml cached separately) **Manual cache inspection:** ```nu let status = nickel-cache-status print $"Cache entries: ($status.entries)" print $"Cache location: ($status.cache_dir)" # List cache files ls ($status.cache_dir) ``` --- ## Troubleshooting ### Plugin Not Found ``` Error: Unknown command 'nickel-export' ``` **Solution:** ```bash # Register the plugin just register-plugin nu_plugin_nickel # Verify registration plugin list | grep nickel ``` ### Nickel Binary Not Found ``` Error: Nickel execution failed: No such file or directory ``` **Solution:** Ensure `nickel` CLI is installed and in PATH: ```bash # Check if nickel is available which nickel # Install nickel (macOS) brew install nickel-lang/nickel/nickel # Or build from source cargo install nickel ``` ### File Not Found ``` Error: Nickel file not found: config.ncl ``` **Solution:** Use absolute path or verify file exists: ```nu # Use absolute path nickel-export /absolute/path/to/config.ncl # Verify file exists ls config.ncl ``` ### Cache Issues ``` # Clear cache if needed rm -rf ~/.cache/provisioning/config-cache/* # Check cache status nickel-cache-status ``` ### JSON Parsing Error If `-f json` returns parsing error, the Nickel file may not export valid JSON: ```nu # Test with raw Nickel output nickel-export config.ncl -f json | print ``` --- ## Architecture ### Design Pattern: CLI Wrapper The plugin uses an elegant **CLI wrapper** pattern: ``` Nushell Script ↓ nickel-export/eval command ↓ Command::new("nickel") ↓ Nickel official CLI ↓ Module resolution (guaranteed correct) ↓ JSON/YAML output ↓ Smart type conversion ↓ Nushell object or raw string ``` **Benefits:** - ✅ Module resolution guaranteed correct (official CLI) - ✅ Works with all Nickel versions automatically - ✅ All Nickel CLI features automatically supported - ✅ Zero maintenance burden **Trade-offs:** - ⚠️ Requires `nickel` binary in PATH - ⚠️ ~100-200ms per evaluation (mitigated by caching) ### Type Conversion Flow ```rust nickel export /file.ncl --format json ↓ Captures stdout (JSON string) ↓ serde_json::from_str (parse) ↓ json_value_to_nu_value (convert recursively) ├── Object → Record ├── Array → List ├── String → String ├── Number → Int or Float ├── Boolean → Bool └── Null → Nothing ↓ Returns nu_protocol::Value ↓ Nushell receives properly typed data ``` --- ## Integration Examples ### With Provisioning System ```nu # Load provisioning config let prov = nickel-eval "workspace/provisioning.ncl" # Deploy infrastructure def deploy [] { for region in $prov.regions { print $"Deploying to ($region.name)..." # Your deployment logic } } # Validate configuration before deploy def validate [] { nickel-validate "workspace/provisioning.ncl" } ``` ### In Nushell Configuration ```nu # env.nu or config.nu # Load environment from Nickel let env-config = nickel-eval "~/.config/nushell/environment.ncl" # Set environment variables $env.MY_VAR = $env-config.my_var $env.DATABASE_URL = $env-config.database.url ``` ### In CI/CD Pipelines ```nu # GitHub Actions / GitLab CI script # Load config let config = nickel-eval ".provisioning/ci-config.ncl" # Check if tests should run if $config.run_tests { print "Running tests..." } # Set deployment target export DEPLOY_TARGET = $config.deployment.target ``` --- ## Performance Tips 1. **Use `nickel-eval` for repeated access** ```nu # ❌ Bad: 3 separate evaluations print ($config.database | nickel-eval) print ($config.services | nickel-eval) # ✅ Good: Single evaluation, cached let cfg = nickel-eval config.ncl print $cfg.database print $cfg.services ``` 2. **Avoid format conversion loops** ```nu # ❌ Bad: Converts each time (1..100) | each {|i| nickel-export config.ncl | ...} # ✅ Good: Convert once let cfg = nickel-eval config.ncl (1..100) | each {|i| ... $cfg ...} ``` 3. **Use raw output for large datasets** ```nu # ❌ Bad: Large object in memory let big = nickel-export huge-config.ncl # ✅ Good: Stream raw JSON nickel-export huge-config.ncl -f json | jq '.items[]' ``` --- ## Requirements - **Nushell**: 0.110.0 or later - **Nickel CLI**: Latest version (install via `brew` or `cargo`) - **Rust**: For building the plugin (if not using pre-built binary) --- ## Building from Source ```bash cd nu_plugin_nickel cargo build --release ``` Binary will be at: `target/release/nu_plugin_nickel` --- ## Testing ```bash # Run unit tests cargo test # Verify compilation cargo check # Run clippy linting cargo clippy -- -D warnings ``` --- ## License MIT --- ## Further Reading - [Nickel Official Documentation](https://nickel-lang.org/) - [Nushell Plugin Development](https://www.nushell.sh/book/plugins.html) - [Architecture Decision Record](./adr-001-nickel-cli-wrapper-architecture.md)