Expand description
§celq Manual
celq is a command-line tool for evaluating Common Expression Language (CEL) expressions. It processes JSON input, performs computations, and outputs results. Think of it as if jq supported CEL.
§Installation
§Pre-built Binaries
We publish pre-built binaries in celq’s GitHub Releases page.
§Homebrew (macOS)
If you are a macOS Homebrew user, then you can install celq with:
brew install IvanIsCoding/tap/celq§Installing From Source
If you want to install from source, celq publishes to crates.io.
cargo install celq§Installing With cargo-binstall
If you have cargo-binstall installed, you can install pre-built binaries directly:
cargo binstall celq§Python
celq is packaged for PyPI. Python users can install it with pip:
pip install celqIf you have uv installed, celq can be used as a tool:
uvx celq -n '"Hello World"'§NPM (Node.js/JavaScript)
Node.js users can install celq in their project with:
npm install celqThis adds celq to package.json and makes it available for scripts. It’s also possible to run single commands with:
npx celq -n '"Hello World"'§Overview
CEL expression evaluator
Usage: celq [OPTIONS] <expr|--from-file <FILE>>
Arguments:
[expr] CEL expression to evaluate
Options:
-a, --arg <name:type=value> Define argument variables, types, and values. Format: name:type=value. Supported types: int, uint, float, bool, string
-b, --boolean Return a status code based on boolean output true = 0, false = 1, exception = 2
-n, --null-input Do not read JSON input from stdin
-s, --slurp Treat all input as a single JSON document Default is to treat each line as separate NLJSON
--from-json5 Parse input as JSON5 instead of JSON
-j, --jobs <N> Parallelism level for NDJSON inputs (number of threads, -1 for all available) [default: 1]
-R, --root-var <ROOT_VAR> Variable name for the root JSON input [default: this]
-S, --sort-keys Output the fields of each object with the keys in sorted order
-f, --from-file <FILE> Read CEL expression from a file
-p, --pretty-print
-h, --help Print help§Quick Start
celq reads JSON from the input and lets users process it with CEL:
echo '["apples", "bananas", "blueberry"]' | celq 'this.filter(s, s.contains("a"))'
# Outputs: ["apples","bananas"]celq can also evaluate expressions with arguments, without reading from the input:
celq -n --arg='fruit:string=apple' 'fruit.contains("a")'
# Outputs: trueClosely related formats such as NDJSON and JSON5 are also supported.
§References
- CEL Language Definition
- cel-rust: the Rust implementation of CEL powering
celq
§Inspiration
celq is heavily inspired by:
- jq: the most popular command-line utility for dealing with JSON
- cel-python: a Python library with a CLI that heavily influenced
celq(there are discrepancies, however) - jaq: a
jqclone written in Rust
§Recipes
We provide recipes with concrete examples for celq. During the recipes, we might refer to yfinance.json:
{
"chart": {
"result": [
{
"meta": {
"currency": "USD",
"symbol": "AAPL",
"regularMarketTime":1767387600,
"fullExchangeName": "NasdaqGS",
"instrumentType": "EQUITY",
"timezone": "EST",
"exchangeTimezoneName": "America/New_York",
"regularMarketPrice": 271.01,
"regularMarketDayHigh": 277.825,
"regularMarketDayLow": 269.02,
"longName": "Apple Inc.",
"chartPreviousClose": 250.42
}
}
]
}
}This file contains the simplified response from the Yahoo Finance Unofficial JSON API.
§Reading Files
By default, celq reads from the standard input. To read from a file, use < for input redirection:
celq "this.chart.result[0].meta.symbol" < yfinance.jsonIt’s also possibile to pipe the output from cat:
cat yfinance.json | celq "this.chart.result[0].meta.symbol"Both command outputs: "AAPL".
§Writing Files
celq writes by default to the standard output. That output can be piped to a file.
For example:
cat yfinance.json | celq "this.chart.result[0].meta.longName" > out.txtCreates a file out.txt with the content "Apple Inc."
§Output JSON
celq always writes JSON to the standard output. That can become handy for transforming JSON.
Take for example:
cat yfinance.json | celq '{"symbol": this.chart.result[0].meta.longName, "price": this.chart.result[0].meta.regularMarketPrice}'The command outputs: {"price":271.01,"symbol":"Apple Inc."}
Notice that by default celq does not guarantee the key order of the output. If you require so, pass the --sort-keys option:
cat yfinance.json | celq --sort-keys '{"symbol": this.chart.result[0].meta.longName, "price": this.chart.result[0].meta.regularMarketPrice}'§Reading CEL from a file
In the previous example, the CEL expression for the JSON became long. Let’s say we saved the expression in stock.cel with the following contents:
{
"symbol": this.chart.result[0].meta.longName,
"price": this.chart.result[0].meta.regularMarketPrice
}If we pass the --from-file argument, we can load the expression and keep the command succint:
cat yfinance.json | celq --from-file stock.cel§Dealing with NDJSON
celq can deal with Newline-Delimited JSON (NDJSON). That format is also called JSON Lines (JSONL).
celq detects the content of multi-line files. Firstly, it tries to parse the input as a NDJSON where each line is a JSON value. If that fails, we parse the input as a single JSON file.
Take for example the following file, example.ndjson:
{"x": 1.5, "y": 2.5}
{"x": 3.5, "y": 4.5}Giving NDJSON as an input will also return NDJSON as an output. If we run the command:
cat example.ndjson | celq '{"xy": this.x + this.y}'We’ll get as the output:
{"xy": 4.0}
{"xy": 8.0}NDJSON input can also be processed in parallel. Passing -j -1 as an argument will enable multi-threading with all available threads. Passing -j N as an argument will enable N threads. Each thread works on a separate line of the JSON independently.
§Slurping
celq supports slurping, albeit in a more limited way than jq. If the --slurp flag is passed, each individual line of a NDJSON is treated as if it was an array entry.
For example:
cat example.json | celq --slurp "this"Outputs: [{"y":2.5,"x":1.5},{"y":4.5,"x":3.5}]. In short, it concatenated the input in a single list.
That can be convenient. Let’s say we want to access all values of x:
cat example.json | celq --slurp "this.map(t, t.x)"The command outputs: [1.5,3.5].
§Logical Calculator
celq can act as a calculator. If the -n option is provided, the tool will not read from the standard input. Combined with arguments, specified by --arg:<VARIABLE_NAME>:<VARIABLE_TYPE>=<VALUE>, this makes celq a logical calculator.
Take for example:
celq -n --arg="x:bool=true" --arg="y:bool=false" '(x || y) && !(x && y)'The command outputs: true.
§Renaming the root variable
In contrast to jq and cel-python, celq names its root variable this. The root . is an operator for CEL and leads to invalid expressions.
The root variable can be tweaked through the --root-var argument:
For example:
cat yfinance.json | celq --root-var=request "request.chart.result[0].meta.longName"Outputs: "Apple Inc.". This feature can be handy when reusing CEL snippets accross different environments, as they will not use this as a variable. That becomes particularly useful with the --from-file feature.
§Boolean output
Inspired by cel-python and test, celq also supports the boolean output feature.
If the flag --boolean or -b is passed to celq, it will set the return code based on the truthiness of the value:
0if the result is true1if the result is false2if there was an error
That can be chained with bash if statements. For example:
#!/usr/bin/env bash
FRUIT="apple"
celq -n -b --arg="fruit:string=$FRUIT" 'fruit.contains("a")' > /dev/null
rc=$?
if [ "$rc" -eq 0 ]; then
echo "$FRUIT contains the letter a"
else
echo "$FRUIT does not contain the letter a"
fiWill print: apple contains the letter a.
Note that for NDJSON inputs, celq sets the value based on the value of the last JSON in the NDJSON input.
§Chaining
Because celq outputs the same format it reads as the input, chains are easy to make. For example:
cat yfinance.json | \
celq "this.chart.result[0]" | \
celq "this.meta.symbol"Also works as a way to output "AAPL" in the command, just like in the first example. When combined with arguments and more elaborate scripts, that can make up for data pipelines.
§JSON5 Support
celq also supports JSON5, a popular JSON extension among config files. It also indirectly supports JSONC, because JSON5 is a superset of JSONC but don’t quote me on that.
To enable the JSON5 parser, pass the --from-json5 flag. For example:
echo "[1, 2, 3, 4,]" | celq --from-json5 'this.map(x, x*2)'Outputs: [2,4,6,8]. If the --from-json5 flag is not passed, the command will fail because of the trailing comma on the list. JSON5 is more lenient than JSON and allows for trailing commas and comments.
Notice that passing the --from-json5 clashes with the --slurp flag and with the NDJSON detection.
§Pretty Printing
celq by default uses a compact output. This is a contrast to jq where the compact output is an opt-in with the -c flag.
With that being said, celq can pretty-print JSON via the -p flag:
echo '{"a": 1, "b": 2}' | celq -p 'this'Outputs:
{
"a": 1,
"b": 2
}§Quirks
- Do not rely on the order of the JSON output, by default it is randomized due to Rust implementation details. If you need ordering, pass
--sort-keysas an argument - If an argument has the same name as the root variable, the root variable wins
- If an argument is repeated, the last definition wins (e.g.
--arg=x:bool=false --arg=x:bool=true,xwill be true) .does not work as a root variable name- Pretty-printing can break chaining.
celqis more limited thanjqwhen parsing NDJSON, as it relies heavily on the new-line delimiters. If you pipe the output of acelq -ptocelqagain and the original input was NDJSON with multiple lines, things will break. - Currently, the
--argsyntax only supportsint,bool,float, andstring. Support for other CEL types will be added in the future.
§Pronunciation
celq is pronounced “selk” / “selq”. Kind of like the word silk but with an e instead.