commit 3338d91571992a4f6b87c17e0bd33a23d9ae36c8
parent 45ac6a4201d3e18fb659e25c5123c58a3db7f513
Author: Erik Loualiche <eloualiche@users.noreply.github.com>
Date: Sat, 21 Mar 2026 21:14:35 -0400
Merge pull request #15 from LouLouLibs/docs/restructure-pages
docs: restructure into installation, examples, and API reference
Diffstat:
6 files changed, 453 insertions(+), 146 deletions(-)
diff --git a/docs/make.jl b/docs/make.jl
@@ -15,12 +15,9 @@ makedocs(
authors = "LouLouLibs Contributors",
pages = [
"Home" => "index.md",
- "Manual" => [
- "man/quickstart.md",
- ],
- "Library" => [
- "lib/public.md",
- ]
+ "Quick Examples" => "man/examples.md",
+ "Detailed Examples" => "man/detailed.md",
+ "API Reference" => "lib/public.md",
]
)
diff --git a/docs/src/index.md b/docs/src/index.md
@@ -1,17 +1,31 @@
# NickelEval.jl
-Julia bindings for the [Nickel](https://nickel-lang.org/) configuration language.
+Julia bindings for the [Nickel](https://nickel-lang.org/) configuration language, using the official Nickel C API.
+
+Evaluate Nickel code directly from Julia and get back native Julia types — no CLI, no intermediate files, no serialization overhead.
## 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 C API** using the official Nickel C API — no CLI needed
+- **Direct evaluation** of Nickel expressions and files via the C API
+- **Native type mapping** — records become `Dict`, arrays become `Vector`, enums become `NickelEnum`
+- **Typed evaluation** — request results as `Dict{String,Int}`, `Vector{Float64}`, `NamedTuple`, etc.
+- **Export to JSON, TOML, YAML** — serialize Nickel configurations to standard formats
+- **File evaluation with imports** — evaluate `.ncl` files that reference other Nickel files
+
+## Documentation
+
+```@contents
+Pages = [
+ "man/examples.md",
+ "man/detailed.md",
+ "lib/public.md",
+]
+Depth = 1
+```
## Installation
-### From LouLouLibs Registry (Recommended)
+### From the LouLouLibs Registry
```julia
using Pkg
@@ -19,44 +33,53 @@ Pkg.Registry.add(url="https://github.com/LouLouLibs/loulouJL")
Pkg.add("NickelEval")
```
-### From GitHub URL
+### From GitHub
```julia
using Pkg
Pkg.add(url="https://github.com/LouLouLibs/NickelEval.jl")
```
-No external tools are required. The Nickel evaluator is bundled as a pre-built native library.
+Pre-built native libraries are provided for **macOS (Apple Silicon)** and **Linux (x86\_64)**. On supported platforms, the library downloads automatically when first needed.
-## Quick Example
+### Building from Source
+
+If the pre-built binary doesn't work on your system — or if no binary is available for your platform — you can build the Nickel C API library from source. This requires [Rust](https://rustup.rs/).
```julia
using NickelEval
+build_ffi()
+```
-# Simple evaluation
-nickel_eval("1 + 2") # => 3
+This clones the Nickel repository, compiles the C API library with `cargo`, and installs it into the package's `deps/` directory. The FFI is re-initialized automatically — no Julia restart needed.
-# Records return Dict{String, Any}
-config = nickel_eval("{ host = \"localhost\", port = 8080 }")
-config["host"] # => "localhost"
-config["port"] # => 8080
+You can also trigger the build during package installation:
+
+```julia
+ENV["NICKELEVAL_BUILD_FFI"] = "true"
+using Pkg
+Pkg.build("NickelEval")
+```
-# Typed evaluation
-nickel_eval("{ x = 1, y = 2 }", Dict{String, Int})
-# => Dict{String, Int64}("x" => 1, "y" => 2)
+### Older Linux Systems (glibc Compatibility)
-# Export to TOML
-nickel_to_toml("{ name = \"myapp\", version = \"1.0\" }")
-# => "name = \"myapp\"\nversion = \"1.0\"\n"
+The pre-built Linux binary is compiled against a relatively recent version of glibc. On older distributions — CentOS 7, older Ubuntu LTS, or many HPC clusters — you may see an error like:
+
+```
+/lib64/libm.so.6: version `GLIBC_2.29' not found
```
-## Why Nickel?
+The fix is to build from source:
-[Nickel](https://nickel-lang.org/) is a configuration language designed to be:
+```julia
+using NickelEval
+build_ffi()
+```
+
+This compiles Nickel against your system's glibc, producing a compatible binary. The only requirement is a working Rust toolchain (`cargo`), which can be installed without root access via [rustup](https://rustup.rs/):
-- **Programmable**: Functions, let bindings, and standard library
-- **Typed**: Optional contracts for validation
-- **Mergeable**: Combine configurations with `&`
-- **Safe**: No side effects, pure functional
+```bash
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+```
-NickelEval.jl lets you leverage Nickel's power directly in your Julia workflows.
+After installing Rust, restart your Julia session and run `build_ffi()`.
diff --git a/docs/src/lib/public.md b/docs/src/lib/public.md
@@ -19,6 +19,7 @@ nickel_to_yaml
```@docs
check_ffi_available
+build_ffi
```
## String Macro
diff --git a/docs/src/man/detailed.md b/docs/src/man/detailed.md
@@ -0,0 +1,279 @@
+# Detailed Examples
+
+## Type Conversions
+
+### Nickel to Julia Type Mapping
+
+| Nickel Type | Julia Type |
+|:------------|:-----------|
+| Null | `nothing` |
+| Bool | `Bool` |
+| Number (integer) | `Int64` |
+| Number (float) | `Float64` |
+| String | `String` |
+| Array | `Vector{Any}` |
+| Record | `Dict{String, Any}` |
+| Enum (tag only) | `NickelEnum(tag, nothing)` |
+| Enum (with argument) | `NickelEnum(tag, arg)` |
+
+### Typed Evaluation
+
+Pass a type as the second argument to `nickel_eval` to convert the result:
+
+```julia
+nickel_eval("42", Int) # => 42::Int64
+nickel_eval("3.14", Float64) # => 3.14::Float64
+nickel_eval("\"hello\"", String) # => "hello"::String
+```
+
+#### Typed Dicts
+
+```julia
+nickel_eval("{ a = 1, b = 2 }", Dict{String, Int})
+# => Dict{String, Int64}("a" => 1, "b" => 2)
+
+nickel_eval("{ x = 1.5, y = 2.5 }", Dict{Symbol, Float64})
+# => Dict{Symbol, Float64}(:x => 1.5, :y => 2.5)
+```
+
+#### Typed Vectors
+
+```julia
+nickel_eval("[1, 2, 3]", Vector{Int})
+# => [1, 2, 3]::Vector{Int64}
+
+nickel_eval("[\"a\", \"b\", \"c\"]", Vector{String})
+# => ["a", "b", "c"]::Vector{String}
+```
+
+#### NamedTuples
+
+Records can be converted to `NamedTuple` for convenient field access:
+
+```julia
+server = nickel_eval(
+ "{ host = \"localhost\", port = 8080 }",
+ @NamedTuple{host::String, port::Int}
+)
+server.host # => "localhost"
+server.port # => 8080
+```
+
+## Enums — `NickelEnum`
+
+Nickel enums are represented as `NickelEnum`, a struct with two fields:
+- `tag::Symbol` — the variant name
+- `arg::Any` — the payload (`nothing` for bare tags)
+
+### Simple Enum Tags
+
+```julia
+result = nickel_eval("let x = 'Foo in x")
+result.tag # => :Foo
+result.arg # => nothing
+```
+
+`NickelEnum` supports direct comparison with `Symbol`:
+
+```julia
+result == :Foo # => true
+result == :Bar # => false
+```
+
+### Enums with Arguments
+
+Enum variants can carry a value of any type:
+
+```julia
+# Integer payload
+result = nickel_eval("let x = 'Some 42 in x")
+result.tag # => :Some
+result.arg # => 42
+
+# String payload
+result = nickel_eval("let x = 'Message \"hello\" in x")
+result.tag # => :Message
+result.arg # => "hello"
+
+# Record payload
+result = nickel_eval("let x = 'Ok { value = 123, status = \"done\" } in x")
+result.tag # => :Ok
+result.arg["value"] # => 123
+result.arg["status"] # => "done"
+
+# Array payload
+result = nickel_eval("let x = 'Batch [1, 2, 3] in x")
+result.arg # => Any[1, 2, 3]
+```
+
+### Nested Enums
+
+Enums can appear inside records, arrays, or other enums:
+
+```julia
+# Enum inside a record
+result = nickel_eval("{ status = 'Active, result = 'Ok 42 }")
+result["status"] == :Active # => true
+result["result"].arg # => 42
+
+# Enum inside another enum
+result = nickel_eval("let x = 'Container { inner = 'Value 42 } in x")
+result.tag # => :Container
+result.arg["inner"].tag # => :Value
+result.arg["inner"].arg # => 42
+
+# Array of enums
+result = nickel_eval("let x = 'List ['Some 1, 'None, 'Some 3] in x")
+result.arg[1].tag # => :Some
+result.arg[1].arg # => 1
+result.arg[2] == :None # => true
+```
+
+### Pattern Matching
+
+Nickel's `match` resolves before reaching Julia — you get back the matched value:
+
+```julia
+result = nickel_eval("""
+let x = 'Some 42 in
+x |> match {
+ 'Some v => v,
+ 'None => 0
+}
+""")
+# => 42
+
+result = nickel_eval("""
+let x = 'Some 42 in
+x |> match {
+ 'Some v => 'Doubled (v * 2),
+ 'None => 'Zero 0
+}
+""")
+result.tag # => :Doubled
+result.arg # => 84
+```
+
+### Pretty Printing
+
+`NickelEnum` displays in Nickel's own syntax:
+
+```julia
+repr(nickel_eval("let x = 'None in x")) # => "'None"
+repr(nickel_eval("let x = 'Some 42 in x")) # => "'Some 42"
+repr(nickel_eval("let x = 'Msg \"hi\" in x")) # => "'Msg \"hi\""
+```
+
+### Real-World Patterns
+
+#### Result Type
+
+```julia
+code = """
+let divide = fun a b =>
+ if b == 0 then
+ 'Err "division by zero"
+ else
+ 'Ok (a / b)
+in
+divide 10 2
+"""
+result = nickel_eval(code)
+result == :Ok # => true
+result.arg # => 5
+```
+
+#### Option Type
+
+```julia
+code = """
+let find = fun arr pred =>
+ let matches = std.array.filter pred arr in
+ if std.array.length matches == 0 then
+ 'None
+ else
+ 'Some (std.array.first matches)
+in
+find [1, 2, 3, 4] (fun x => x > 2)
+"""
+result = nickel_eval(code)
+result == :Some # => true
+result.arg # => 3
+```
+
+#### State Machine
+
+```julia
+result = nickel_eval("""
+let state = 'Running { progress = 75, task = "downloading" } in state
+""")
+result.tag # => :Running
+result.arg["progress"] # => 75
+result.arg["task"] # => "downloading"
+```
+
+## Export Formats
+
+Evaluate Nickel code and serialize to JSON, TOML, or YAML:
+
+```julia
+nickel_to_json("{ name = \"myapp\", version = \"1.0\" }")
+# => "{\n \"name\": \"myapp\",\n \"version\": \"1.0\"\n}"
+
+nickel_to_yaml("{ name = \"myapp\", version = \"1.0\" }")
+# => "name: myapp\nversion: '1.0'\n"
+
+nickel_to_toml("{ name = \"myapp\", version = \"1.0\" }")
+# => "name = \"myapp\"\nversion = \"1.0\"\n"
+```
+
+## File Evaluation with Imports
+
+Nickel files can import other Nickel files. `nickel_eval_file` resolves imports relative to the file's directory.
+
+Given these files:
+
+```nickel
+# shared.ncl
+{
+ project_name = "MyProject",
+ version = "1.0.0"
+}
+```
+
+```nickel
+# config.ncl
+let shared = import "shared.ncl" in
+{
+ name = shared.project_name,
+ version = shared.version,
+ debug = true
+}
+```
+
+```julia
+config = nickel_eval_file("config.ncl")
+config["name"] # => "MyProject"
+config["version"] # => "1.0.0"
+config["debug"] # => true
+```
+
+Subdirectory imports also work:
+
+```nickel
+# lib/utils.ncl
+{
+ helper = fun x => x * 2
+}
+```
+
+```nickel
+# main.ncl
+let utils = import "lib/utils.ncl" in
+{ result = utils.helper 21 }
+```
+
+```julia
+nickel_eval_file("main.ncl")
+# => Dict("result" => 42)
+```
diff --git a/docs/src/man/examples.md b/docs/src/man/examples.md
@@ -0,0 +1,119 @@
+# Quick Examples
+
+## Evaluating Expressions
+
+```julia
+using NickelEval
+
+nickel_eval("1 + 2") # => 3
+nickel_eval("true") # => true
+nickel_eval("\"hello\"") # => "hello"
+nickel_eval("null") # => nothing
+```
+
+Integers return `Int64`, decimals return `Float64`:
+
+```julia
+nickel_eval("42") # => 42 (Int64)
+nickel_eval("3.14") # => 3.14 (Float64)
+```
+
+## Records
+
+Nickel records map to `Dict{String, Any}`:
+
+```julia
+config = nickel_eval("""
+{
+ host = "localhost",
+ port = 8080,
+ debug = true
+}
+""")
+
+config["host"] # => "localhost"
+config["port"] # => 8080
+config["debug"] # => true
+```
+
+Nested records work as expected:
+
+```julia
+result = nickel_eval("{ database = { host = \"localhost\", port = 5432 } }")
+result["database"]["host"] # => "localhost"
+```
+
+## Arrays
+
+Nickel arrays map to `Vector{Any}`:
+
+```julia
+nickel_eval("[1, 2, 3]") # => Any[1, 2, 3]
+nickel_eval("[\"a\", \"b\", \"c\"]") # => Any["a", "b", "c"]
+```
+
+Use the Nickel standard library for transformations:
+
+```julia
+nickel_eval("[1, 2, 3] |> std.array.map (fun x => x * 2)")
+# => Any[2, 4, 6]
+```
+
+## Let Bindings and Functions
+
+```julia
+nickel_eval("let x = 10 in x * 2") # => 20
+
+nickel_eval("""
+let double = fun x => x * 2 in
+double 21
+""") # => 42
+
+nickel_eval("""
+let add = fun x y => x + y in
+add 3 4
+""") # => 7
+```
+
+## Record Merge
+
+Nickel's `&` operator merges records:
+
+```julia
+nickel_eval("{ a = 1 } & { b = 2 }")
+# => Dict("a" => 1, "b" => 2)
+```
+
+## String Macro
+
+The `ncl"..."` macro provides a shorthand for `nickel_eval`:
+
+```julia
+ncl"1 + 1" # => 2
+ncl"{ x = 10 }"["x"] # => 10
+ncl"[1, 2, 3]" # => Any[1, 2, 3]
+```
+
+## Evaluating Files
+
+Evaluate `.ncl` files directly. Imports are resolved relative to the file's directory:
+
+```julia
+# config.ncl contains: { host = "localhost", port = 8080 }
+config = nickel_eval_file("config.ncl")
+config["host"] # => "localhost"
+```
+
+## Error Handling
+
+Invalid Nickel code throws a `NickelError`:
+
+```julia
+try
+ nickel_eval("{ x = }")
+catch e
+ if e isa NickelError
+ println(e.message) # Nickel's formatted error message
+ end
+end
+```
diff --git a/docs/src/man/quickstart.md b/docs/src/man/quickstart.md
@@ -1,112 +0,0 @@
-# Quick Start
-
-## Installation
-
-### From LouLouLibs Registry (Recommended)
-
-```julia
-using Pkg
-Pkg.Registry.add(url="https://github.com/LouLouLibs/loulouJL")
-Pkg.add("NickelEval")
-```
-
-### From GitHub URL
-
-```julia
-using Pkg
-Pkg.add(url="https://github.com/LouLouLibs/NickelEval.jl")
-```
-
-No external tools or CLI are required. NickelEval uses the official Nickel C API,
-bundled as a pre-built native library for your platform.
-
-## 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 `Dict{String, Any}`:
-
-```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 }")
-# => Dict{String, Any}("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
-```