CLAUDE.md (6651B)
1 # NickelEval.jl Development Guide 2 3 ## Project Overview 4 5 NickelEval.jl provides Julia bindings for the [Nickel](https://nickel-lang.org/) configuration language using the official Nickel C API. 6 7 ## Architecture 8 9 ``` 10 NickelEval/ 11 ├── src/ 12 │ ├── NickelEval.jl # Main module 13 │ ├── libnickel.jl # Generated ccall wrappers (Clang.jl from nickel_lang.h) 14 │ └── ffi.jl # High-level Julia API (nickel_eval, tree-walk, etc.) 15 ├── deps/ 16 │ ├── build.jl # Build nickel-lang from source (fallback) 17 │ ├── generate_bindings.jl # Clang.jl binding regeneration (dev tool) 18 │ └── nickel_lang.h # C header from cbindgen 19 ├── Artifacts.toml # Pre-built library URLs/hashes (aarch64-darwin, x86_64-linux) 20 ├── .github/workflows/ 21 │ ├── CI.yml # Julia tests 22 │ ├── Documentation.yml 23 │ └── build-ffi.yml # Cross-platform FFI builds 24 └── test/ 25 └── runtests.jl 26 ``` 27 28 ## Key Design Decisions 29 30 ### 1. Official Nickel C API 31 32 NickelEval uses the official C API exposed by the `nickel-lang` crate (v2.0.0+) built with `--features capi`. This provides: 33 - A stable, supported interface to the Nickel evaluator 34 - Tree-walk value extraction without a custom binary protocol 35 - No Nickel CLI dependency 36 37 ### 2. Types via C API Tree-Walk 38 39 The C API is walked recursively on the Julia side using `ccall`. Values are converted to Julia native types: 40 - Records → `Dict{String, Any}` 41 - Arrays → `Vector{Any}` 42 - Integers → `Int64`, Floats → `Float64` 43 - Enums → `NickelEnum(tag, arg)` 44 45 ### 3. Avoid `unwrap()` in Rust 46 47 Use proper error handling in any Rust glue code: 48 ```rust 49 // Bad 50 let f = value.to_f64().unwrap(); 51 52 // Good 53 let f = f64::try_from(value).map_err(|e| format!("Error: {:?}", e))?; 54 ``` 55 56 ## Building 57 58 ### Nickel C API Library 59 60 ```bash 61 cd rust/nickel-lang 62 cargo build -p nickel-lang --features capi --release 63 cp target/release/libnickel_lang.dylib ../../deps/ # macOS 64 # or libnickel_lang.so on Linux 65 ``` 66 67 ### Running Tests 68 69 ```bash 70 # Julia tests 71 julia --project=. -e 'using Pkg; Pkg.test()' 72 ``` 73 74 ## Release Process 75 76 **Before tagging a new version, ALL CI workflows must pass:** 77 78 1. Run tests locally: `julia --project=. -e 'using Pkg; Pkg.test()'` 79 2. Push changes to main 80 3. Wait for CI to complete and verify all workflows pass (both CI and Documentation) 81 4. Only then tag and register the new version 82 83 ```bash 84 # Check CI status before tagging 85 gh run list --repo LouLouLibs/NickelEval.jl --limit 5 86 87 # All workflows should show "success" before proceeding with: 88 git tag -a vX.Y.Z -m "vX.Y.Z: Description" 89 git push origin vX.Y.Z 90 ``` 91 92 ### Version Bumping Checklist 93 94 1. Update `version` in `Project.toml` 95 2. Update `## Current Version` in `TODO.md` 96 3. Commit these changes 97 4. Wait for CI to pass 98 5. Tag the release 99 6. If FFI changed, build and upload new artifacts (see FFI Artifact Release below) 100 7. Update loulouJL registry with correct tree SHA 101 102 ### FFI Artifact Release 103 104 When the C API library changes, new pre-built binaries must be released: 105 106 1. **Trigger the build workflow:** 107 ```bash 108 gh workflow run build-ffi.yml --ref main 109 ``` 110 111 2. **Download built artifacts** from the workflow run (2 platforms: aarch64-darwin, x86_64-linux) 112 113 3. **Create GitHub Release** and upload the `.tar.gz` files 114 115 4. **Calculate tree hashes** for each artifact: 116 ```bash 117 # For each tarball: 118 tar -xzf libnickel_lang-PLATFORM.tar.gz 119 julia -e 'using Pkg; println(Pkg.GitTools.tree_hash("."))' 120 ``` 121 122 5. **Update Artifacts.toml** with new SHA256 checksums and tree hashes 123 124 ### Artifacts.toml Format 125 126 Use the `[[artifact_name]]` array format with platform properties: 127 128 ```toml 129 [[libnickel_lang]] 130 arch = "aarch64" 131 git-tree-sha1 = "TREE_HASH_HERE" 132 os = "macos" 133 lazy = true 134 135 [[libnickel_lang.download]] 136 url = "https://github.com/LouLouLibs/NickelEval.jl/releases/download/vX.Y.Z/libnickel_lang-aarch64-apple-darwin.tar.gz" 137 sha256 = "SHA256_HASH_HERE" 138 ``` 139 140 Platform values: 141 - `os`: "macos", "linux" 142 - `arch`: "aarch64", "x86_64" 143 144 ### Documentation Requirements 145 146 Any new exported function must be added to `docs/src/lib/public.md` in the appropriate section to avoid documentation build failures. 147 148 ### Registry (loulouJL) 149 150 Location: `/Users/loulou/Dropbox/projects_code/julia_packages/loulouJL/N/NickelEval/` 151 152 **Files to update:** 153 154 1. **Versions.toml** - Add new version entry: 155 ```toml 156 ["X.Y.Z"] 157 git-tree-sha1 = "TREE_SHA_HERE" 158 ``` 159 Get tree SHA: `git rev-parse vX.Y.Z^{tree}` 160 161 2. **Deps.toml** - If dependencies changed, add new version range: 162 ```toml 163 ["X.Y-0"] 164 Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" 165 LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" 166 ``` 167 **Important:** Version ranges must not overlap. Use `"X.Y-Z.W"` for ranges. 168 169 3. **Compat.toml** - If compat bounds changed, update accordingly. 170 171 **Registry format rules:** 172 - Section headers must be quoted: `["0.5"]` not `[0.5]` 173 - Version ranges: `"0.4-0.5"` (from 0.4 to 0.5), `"0.5-0"` (from 0.5 to end of 0.x) 174 - No spaces in ranges 175 - Ranges must not overlap for the same dependency 176 177 ## API Functions 178 179 ### Evaluation 180 181 - `nickel_eval(code)` - Evaluate Nickel code, returns Julia-native types 182 - `nickel_eval(code, T)` - Evaluate and convert to type `T` 183 - `nickel_eval_file(path)` - Evaluate a `.ncl` file (supports imports) 184 - `check_ffi_available()` - Check if the C API library is loaded 185 186 ### Export 187 188 - `nickel_to_json(code)` - Export to JSON string 189 - `nickel_to_toml(code)` - Export to TOML string 190 - `nickel_to_yaml(code)` - Export to YAML string 191 192 ### String Macro 193 194 - `ncl"..."` / `@ncl_str` - Evaluate inline Nickel code 195 196 ## Type Conversion 197 198 | Nickel Type | Julia Type | 199 |-------------|------------| 200 | Null | `nothing` | 201 | Bool | `Bool` | 202 | Number (integer) | `Int64` | 203 | Number (float) | `Float64` | 204 | String | `String` | 205 | Array | `Vector{Any}` | 206 | Record | `Dict{String, Any}` | 207 | Enum | `NickelEnum(tag, arg)` | 208 209 ## Nickel Language Reference 210 211 Common patterns used in tests: 212 213 ```nickel 214 # Let bindings 215 let x = 1 in x + 2 216 217 # Functions 218 let double = fun x => x * 2 in double 21 219 220 # Records 221 { name = "test", value = 42 } 222 223 # Record merge 224 { a = 1 } & { b = 2 } 225 226 # Arrays 227 [1, 2, 3] 228 229 # Array operations 230 [1, 2, 3] |> std.array.map (fun x => x * 2) 231 232 # Nested structures 233 { outer = { inner = 42 } } 234 ``` 235 236 ## Dependencies 237 238 ### Julia 239 - Artifacts (stdlib) 240 - LazyArtifacts (stdlib) 241 242 ### Rust (for building C API library locally) 243 - nickel-lang = "2.0.0" with `--features capi` 244 245 ## Future Improvements 246 247 1. Support for Nickel contracts/types in Julia 248 2. Streaming evaluation for large configs 249 3. REPL integration