commit 0133a50ffd040d3ad37dc8bb741881151d2df9af
parent ef49bfa252e6ffb6a345ba3f0d5e48c0511b25a2
Author: Erik Loualiche <eloualic@umn.edu>
Date: Fri, 6 Feb 2026 09:37:13 -0600
Add documentation with DocumenterVitepress
- docs/: Documentation using Documenter.jl + DocumenterVitepress
- Quick start, typed evaluation, export, and FFI guides
- GitHub Actions workflow for documentation deployment
- CI workflow for testing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
10 files changed, 651 insertions(+), 0 deletions(-)
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
@@ -0,0 +1,24 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ tags: '*'
+ pull_request:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: julia-actions/setup-julia@v2
+ with:
+ version: '1'
+ - uses: julia-actions/cache@v2
+ - name: Install Nickel
+ run: |
+ curl -L https://github.com/tweag/nickel/releases/download/1.7.0/nickel-linux-x86_64 -o /usr/local/bin/nickel
+ chmod +x /usr/local/bin/nickel
+ - name: Run tests
+ run: julia --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.test()'
diff --git a/.github/workflows/Documentation.yaml b/.github/workflows/Documentation.yaml
@@ -0,0 +1,29 @@
+name: Documentation
+
+on:
+ push:
+ branches:
+ - main
+ tags: '*'
+ pull_request:
+
+jobs:
+ build:
+ permissions:
+ actions: write
+ contents: write
+ pull-requests: read
+ statuses: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: julia-actions/setup-julia@v2
+ with:
+ version: '1'
+ - uses: julia-actions/cache@v2
+ - name: Install dependencies
+ run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
+ - name: Build and deploy
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: julia --project=docs/ docs/make.jl
diff --git a/docs/Project.toml b/docs/Project.toml
@@ -0,0 +1,4 @@
+[deps]
+Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
+DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365"
+NickelEval = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
diff --git a/docs/make.jl b/docs/make.jl
@@ -0,0 +1,35 @@
+#!/usr/bin/env julia
+
+using NickelEval
+using Documenter
+using DocumenterVitepress
+
+makedocs(
+ format = MarkdownVitepress(
+ repo = "https://github.com/LouLouLibs/NickelEval",
+ ),
+ repo = Remotes.GitHub("LouLouLibs", "NickelEval"),
+ sitename = "NickelEval.jl",
+ modules = [NickelEval],
+ authors = "LouLouLibs Contributors",
+ pages=[
+ "Home" => "index.md",
+ "Manual" => [
+ "man/quickstart.md",
+ "man/typed.md",
+ "man/export.md",
+ "man/ffi.md",
+ ],
+ "Library" => [
+ "lib/public.md",
+ ]
+ ]
+)
+
+deploydocs(;
+ repo = "github.com/LouLouLibs/NickelEval",
+ target = "build",
+ devbranch = "main",
+ branch = "gh-pages",
+ push_preview = true,
+)
diff --git a/docs/src/index.md b/docs/src/index.md
@@ -0,0 +1,65 @@
+# NickelEval.jl
+
+Julia bindings for the [Nickel](https://nickel-lang.org/) configuration language.
+
+## Features
+
+- **Evaluate Nickel code** directly from Julia
+- **Native type conversion** to Julia types (`Dict`, `NamedTuple`, custom structs)
+- **Export to multiple formats** (JSON, TOML, YAML)
+- **High-performance FFI** mode using Rust bindings
+- **Dot-access** for configuration records via `JSON.Object`
+
+## Installation
+
+```julia
+using Pkg
+Pkg.add(url="https://github.com/LouLouLibs/NickelEval")
+```
+
+**Prerequisite:** Install the Nickel CLI from [nickel-lang.org](https://nickel-lang.org/)
+
+## Quick Example
+
+```julia
+using NickelEval
+
+# Simple evaluation
+nickel_eval("1 + 2") # => 3
+
+# Records with dot-access
+config = nickel_eval("{ host = \"localhost\", port = 8080 }")
+config.host # => "localhost"
+config.port # => 8080
+
+# Typed evaluation
+nickel_eval("{ x = 1, y = 2 }", Dict{String, Int})
+# => Dict{String, Int64}("x" => 1, "y" => 2)
+
+# Export to TOML
+nickel_to_toml("{ name = \"myapp\", version = \"1.0\" }")
+# => "name = \"myapp\"\nversion = \"1.0\"\n"
+```
+
+## Why Nickel?
+
+[Nickel](https://nickel-lang.org/) is a configuration language designed to be:
+
+- **Programmable**: Functions, let bindings, and standard library
+- **Typed**: Optional contracts for validation
+- **Mergeable**: Combine configurations with `&`
+- **Safe**: No side effects, pure functional
+
+NickelEval.jl lets you leverage Nickel's power directly in your Julia workflows.
+
+## Contents
+
+```@contents
+Pages = [
+ "man/quickstart.md",
+ "man/typed.md",
+ "man/export.md",
+ "man/ffi.md",
+ "lib/public.md",
+]
+```
diff --git a/docs/src/lib/public.md b/docs/src/lib/public.md
@@ -0,0 +1,37 @@
+# Public API
+
+## Evaluation Functions
+
+```@docs
+nickel_eval
+nickel_eval_file
+nickel_read
+```
+
+## Export Functions
+
+```@docs
+nickel_export
+nickel_to_json
+nickel_to_toml
+nickel_to_yaml
+```
+
+## FFI Functions
+
+```@docs
+check_ffi_available
+nickel_eval_ffi
+```
+
+## String Macro
+
+```@docs
+@ncl_str
+```
+
+## Types
+
+```@docs
+NickelError
+```
diff --git a/docs/src/man/export.md b/docs/src/man/export.md
@@ -0,0 +1,135 @@
+# Export to Config Formats
+
+NickelEval can export Nickel code to JSON, TOML, or YAML strings for generating configuration files.
+
+## JSON Export
+
+```julia
+nickel_to_json("{ name = \"myapp\", port = 8080 }")
+```
+
+Output:
+```json
+{
+ "name": "myapp",
+ "port": 8080
+}
+```
+
+## TOML Export
+
+```julia
+nickel_to_toml("{ name = \"myapp\", port = 8080 }")
+```
+
+Output:
+```toml
+name = "myapp"
+port = 8080
+```
+
+## YAML Export
+
+```julia
+nickel_to_yaml("{ name = \"myapp\", port = 8080 }")
+```
+
+Output:
+```yaml
+name: myapp
+port: 8080
+```
+
+## Generic Export Function
+
+Use `nickel_export` with the `format` keyword:
+
+```julia
+nickel_export("{ a = 1 }"; format=:json)
+nickel_export("{ a = 1 }"; format=:toml)
+nickel_export("{ a = 1 }"; format=:yaml)
+```
+
+## Generating Config Files
+
+### Example: Generate Multiple Formats
+
+```julia
+config = """
+{
+ database = {
+ host = "localhost",
+ port = 5432,
+ name = "mydb"
+ },
+ server = {
+ host = "0.0.0.0",
+ port = 8080
+ },
+ logging = {
+ level = "info",
+ file = "/var/log/app.log"
+ }
+}
+"""
+
+# Generate TOML config
+write("config.toml", nickel_to_toml(config))
+
+# Generate YAML config
+write("config.yaml", nickel_to_yaml(config))
+
+# Generate JSON config
+write("config.json", nickel_to_json(config))
+```
+
+### Example: Environment-Specific Configs
+
+```julia
+base_config = """
+{
+ app_name = "myapp",
+ log_level = "info"
+}
+"""
+
+dev_overrides = """
+{
+ debug = true,
+ database = { host = "localhost" }
+}
+"""
+
+prod_overrides = """
+{
+ debug = false,
+ database = { host = "db.production.com" }
+}
+"""
+
+# Merge and export
+dev_config = nickel_export("$base_config & $dev_overrides"; format=:toml)
+prod_config = nickel_export("$base_config & $prod_overrides"; format=:toml)
+```
+
+## Nested Structures
+
+TOML handles nested records as sections:
+
+```julia
+nickel_to_toml("""
+{
+ server = {
+ host = "0.0.0.0",
+ port = 8080
+ }
+}
+""")
+```
+
+Output:
+```toml
+[server]
+host = "0.0.0.0"
+port = 8080
+```
diff --git a/docs/src/man/ffi.md b/docs/src/man/ffi.md
@@ -0,0 +1,112 @@
+# FFI Mode (High Performance)
+
+For repeated evaluations, NickelEval provides native FFI bindings to a Rust library that wraps `nickel-lang-core`. This eliminates subprocess overhead.
+
+## Checking FFI Availability
+
+```julia
+using NickelEval
+
+check_ffi_available() # => true or false
+```
+
+FFI is available when the compiled Rust library exists in the `deps/` folder.
+
+## Using FFI Evaluation
+
+```julia
+# Basic evaluation
+nickel_eval_ffi("1 + 2") # => 3
+
+# With dot-access
+config = nickel_eval_ffi("{ host = \"localhost\", port = 8080 }")
+config.host # => "localhost"
+
+# Typed evaluation
+nickel_eval_ffi("{ a = 1, b = 2 }", Dict{String, Int})
+# => Dict{String, Int64}("a" => 1, "b" => 2)
+```
+
+## Building the FFI Library
+
+### Requirements
+
+- Rust toolchain (install from [rustup.rs](https://rustup.rs))
+- Cargo
+
+### Build Steps
+
+```bash
+cd rust/nickel-jl
+cargo build --release
+```
+
+Then copy the library to `deps/`:
+
+```bash
+# macOS
+cp target/release/libnickel_jl.dylib ../../deps/
+
+# Linux
+cp target/release/libnickel_jl.so ../../deps/
+
+# Windows
+cp target/release/nickel_jl.dll ../../deps/
+```
+
+## Performance Comparison
+
+FFI mode is faster for repeated evaluations because it:
+
+1. **No process spawn**: Direct library calls instead of subprocess
+2. **Shared memory**: Values transfer directly without serialization
+3. **Persistent state**: Library remains loaded
+
+For single evaluations, the difference is minimal. For batch processing or interactive use, FFI mode is significantly faster.
+
+## Binary Protocol
+
+The FFI uses a binary protocol that preserves type information:
+
+| Type Tag | Nickel Type |
+|----------|-------------|
+| 0 | Null |
+| 1 | Bool |
+| 2 | Int64 |
+| 3 | Float64 |
+| 4 | String |
+| 5 | Array |
+| 6 | Record |
+
+This allows direct conversion to Julia types without JSON parsing overhead.
+
+## Fallback Behavior
+
+If FFI is not available, you can still use the subprocess-based functions:
+
+```julia
+# Always works (uses CLI)
+nickel_eval("1 + 2")
+
+# Requires FFI library
+nickel_eval_ffi("1 + 2") # Error if not built
+```
+
+## Troubleshooting
+
+### "FFI not available" Error
+
+Build the Rust library:
+
+```bash
+cd rust/nickel-jl
+cargo build --release
+cp target/release/libnickel_jl.* ../../deps/
+```
+
+### Library Not Found
+
+Ensure the library has the correct name for your platform:
+- macOS: `libnickel_jl.dylib`
+- Linux: `libnickel_jl.so`
+- Windows: `nickel_jl.dll`
diff --git a/docs/src/man/quickstart.md b/docs/src/man/quickstart.md
@@ -0,0 +1,103 @@
+# Quick Start
+
+## Installation
+
+```julia
+using Pkg
+Pkg.add(url="https://github.com/LouLouLibs/NickelEval")
+```
+
+Make sure you have the Nickel CLI installed:
+- macOS: `brew install nickel`
+- Other: See [nickel-lang.org](https://nickel-lang.org/)
+
+## Basic Usage
+
+```julia
+using NickelEval
+
+# Evaluate simple expressions
+nickel_eval("1 + 2") # => 3
+nickel_eval("true") # => true
+nickel_eval("\"hello\"") # => "hello"
+```
+
+## Working with Records
+
+Nickel records become `JSON.Object` with dot-access:
+
+```julia
+config = nickel_eval("""
+{
+ database = {
+ host = "localhost",
+ port = 5432
+ },
+ debug = true
+}
+""")
+
+config.database.host # => "localhost"
+config.database.port # => 5432
+config.debug # => true
+```
+
+## Let Bindings and Functions
+
+```julia
+# Let bindings
+nickel_eval("let x = 10 in x * 2") # => 20
+
+# Functions
+nickel_eval("""
+let double = fun x => x * 2 in
+double 21
+""") # => 42
+```
+
+## Arrays
+
+```julia
+nickel_eval("[1, 2, 3]") # => [1, 2, 3]
+
+# Array operations with std library
+nickel_eval("[1, 2, 3] |> std.array.map (fun x => x * 2)")
+# => [2, 4, 6]
+```
+
+## Record Merge
+
+```julia
+nickel_eval("{ a = 1 } & { b = 2 }")
+# => JSON.Object with a=1, b=2
+```
+
+## String Macro
+
+For inline Nickel code:
+
+```julia
+ncl"1 + 1" # => 2
+
+config = ncl"{ host = \"localhost\" }"
+config.host # => "localhost"
+```
+
+## File Evaluation
+
+```julia
+# Evaluate a .ncl file
+config = nickel_eval_file("config.ncl")
+```
+
+## Error Handling
+
+```julia
+try
+ nickel_eval("{ x = }") # syntax error
+catch e
+ if e isa NickelError
+ println("Error: ", e.message)
+ end
+end
+```
diff --git a/docs/src/man/typed.md b/docs/src/man/typed.md
@@ -0,0 +1,107 @@
+# Typed Evaluation
+
+NickelEval supports converting Nickel values directly to typed Julia values using `JSON.jl 1.0`'s native typed parsing.
+
+## Basic Types
+
+```julia
+nickel_eval("42", Int) # => 42
+nickel_eval("3.14", Float64) # => 3.14
+nickel_eval("\"hi\"", String) # => "hi"
+nickel_eval("true", Bool) # => true
+```
+
+## Typed Dictionaries
+
+### String Keys
+
+```julia
+result = nickel_eval("{ a = 1, b = 2 }", Dict{String, Int})
+# => Dict{String, Int64}("a" => 1, "b" => 2)
+
+result["a"] # => 1
+```
+
+### Symbol Keys
+
+```julia
+result = nickel_eval("{ x = 1.5, y = 2.5 }", Dict{Symbol, Float64})
+# => Dict{Symbol, Float64}(:x => 1.5, :y => 2.5)
+
+result[:x] # => 1.5
+```
+
+## Typed Arrays
+
+```julia
+nickel_eval("[1, 2, 3]", Vector{Int})
+# => [1, 2, 3]
+
+nickel_eval("[\"a\", \"b\", \"c\"]", Vector{String})
+# => ["a", "b", "c"]
+```
+
+## NamedTuples
+
+For structured configuration access:
+
+```julia
+config = nickel_eval("""
+{
+ host = "localhost",
+ port = 8080,
+ debug = true
+}
+""", @NamedTuple{host::String, port::Int, debug::Bool})
+
+# => (host = "localhost", port = 8080, debug = true)
+
+config.host # => "localhost"
+config.port # => 8080
+config.debug # => true
+```
+
+## Custom Structs
+
+Define your own types:
+
+```julia
+struct ServerConfig
+ host::String
+ port::Int
+ workers::Int
+end
+
+config = nickel_eval("""
+{
+ host = "0.0.0.0",
+ port = 3000,
+ workers = 4
+}
+""", ServerConfig)
+
+# => ServerConfig("0.0.0.0", 3000, 4)
+```
+
+## File Evaluation with Types
+
+```julia
+# config.ncl:
+# { environment = "production", max_connections = 100 }
+
+Config = @NamedTuple{environment::String, max_connections::Int}
+config = nickel_eval_file("config.ncl", Config)
+
+config.environment # => "production"
+config.max_connections # => 100
+```
+
+## The `nickel_read` Alias
+
+`nickel_read` is an alias for typed `nickel_eval`:
+
+```julia
+nickel_read("{ a = 1 }", Dict{String, Int})
+# equivalent to
+nickel_eval("{ a = 1 }", Dict{String, Int})
+```