nclq

Query Nickel files with nickel expressions.

Top-level fields are automatically in scope — no import boilerplate. Write the expression, point at the file, get the result.

# Count rows — no imports, no jq
nclq 'rows |> std.array.length' cog_tabulation.ncl
# => 357

# Filter + project
nclq 'rows' data.ncl --where 'score>10' --fields id,name --limit 5

# Unique values
nclq 'rows' data.ncl --unique category

# Inspect nested config
nclq config.ncl --list-fields --depth 2

Install

Requires nickel on $PATH and an OCaml toolchain.

dune build && scripts/install.sh

Installs to ~/.local/bin/nclq.

How it works

nclq generates nickel code and pipes it to nickel eval. In single-file mode, it discovers top-level fields and binds them into scope. Built-in flags (--where, --sort-by, etc.) generate additional nickel wrapping code. Type discovery uses a preliminary eval so --where score>10 knows score is a number.

Built-in flags

Flags compose with the expression result in a fixed pipeline:

where → sort-by → unique → fields → limit → count

FlagPurpose
--where CONDFilter: field=val, field>val, field~substr. Repeatable, ANDed.
--sort-by FIELDSort by field. Type auto-detected. Add --desc for descending.
--unique FIELDExtract unique values of a field.
--fields F1,F2Project named fields from records.
--limit NTruncate arrays to first N elements.
--countReturn count instead of data.
--list-fieldsList field names. --depth N for nested.
--format FMTOutput as nickel, json, yaml, or toml.
--rawStrip quotes from string output.
--compactSingle-line output.
-f NAME=FILEMulti-file mode with named bindings.

Examples

# View a file
nclq data.ncl --compact

# Filter rows where agg_level is 1 and description contains "Revenue"
nclq 'rows' cog_tabulation.ncl --where agg_level=1 --where 'description~Revenue'

# Top 3 by score, descending
nclq 'rows' data.ncl --sort-by score --desc --limit 3

# Count matching rows
nclq 'rows' data.ncl --where 'description~Education' --count

# Cross-reference two files
nclq 'tab.rows |> std.array.filter (fun r =>
       std.array.elem r.id (cb.codes |> std.array.map (fun c => c.id)))' \
  -f tab=tabulation.ncl -f cb=codebook.ncl

# Pipe from stdin
echo '{ x = 1, y = 2 }' | nclq 'x + y'

# JSON export for jq
nclq 'rows' data.ncl --where 'score>10' --format json | jq '.[].id'

Why

The nickel CLI can evaluate, export, and typecheck .ncl files, but it can't filter or inspect data without serializing to JSON and piping through jq. In a research monorepo with nickel files exceeding 4,000 lines, every ad-hoc query required a verbose pipeline:

nickel export --format json input.ncl | jq '[.rows[] | select(.item_code == null)]'

nclq replaces this with:

nclq 'rows' input.ncl --where 'item_code!=null'