Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Unreleased.
* Improved error messages for unmatched brackets.
* Highlighters based on Tree-sitter (e.g. those in Helix and Zed) now highlight
function calls.
* Add the [`systemd` output format](output_formats.md#systemd) for generating
systemd units from <abbr>RCL</abbr>.

## 0.13.0

Expand Down
256 changes: 256 additions & 0 deletions docs/output_formats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
# Output formats

RCL can print output in the formats below. The format can be selected with
[`--format`](rcl_evaluate.md#-f-format-format) on the command line, and with
[`format`](rcl_build.md#format) in build files.
The format names are written lowercase.

## json

Output pretty-printed <abbr>JSON</abbr>. Sets become lists, functions are not
supported. Because <abbr>RCL</abbr> is a superset of <abbr>JSON</abbr>, every
<abbr>JSON</abbr> value maps to itself. Note that <abbr>YAML</abbr> is a
superset of <abbr>JSON</abbr>, so this format is appropriate for generating
configuration for tools that accept <abbr>YAML</abbr>, such as Kubernetes and
GitHub Actions.

```rcl
{
series = "Nexus-6",
instances = {"Roy Batty", "Leon Kowalski"},
}
```
Formats as:
```
{
"instances": ["Leon Kowalski", "Roy Batty"],
"series": "Nexus-6"
}
```

> **Tip:** You can quickly select <abbr>JSON</abbr> output on the command line
> with the [`je`](rcl_evaluate.md) and [`jq`](rcl_query.md) shorthands.

## json-lines

If the document is a list, output every element as a <abbr>JSON</abbr> value on
its own line, consistent with the
<a href="https://jsonlines.org/"><abbr>JSON</abbr> lines</a> format.
Top-level values other than lists are not valid for this format.

```rcl
[
{ name = "Roy Batty", serial = "N6MAA10816" },
{ name = "Leon Kowalski", serial = "N6MAC41717" },
]
```
Formats as:
```
{"name": "Roy Batty", "serial": "N6MAA10816"}
{"name": "Leon Kowalski", "serial": "N6MAC41717"}
```

## raw

If the document is a string, output the string itself. If the document is a list
or set of strings, output each string on its own line.

```rcl
["First line", "Second line\nThird line"]
```
Formats as:
```
First line
Second line
Third line
```

> **Tip:** You can quickly select raw output on the command line with the
> [`re`](rcl_evaluate.md) and [`rq`](rcl_query.md) shorthands.

## rcl

Output pretty-printed <abbr>RCL</abbr>.

```rcl
{
"where_possible": "Record syntax will be used.",
"where impossible": "Expression form is used.",
}
```
Formats as:
```rcl
{
"where impossible": "Expression form is used.",
where_possible = "Record syntax will be used.",
}
```

## systemd

Output as a [systemd unit][sd-syntax]. The format is superficially similar to
<abbr>TOML</abbr>, but handles nested data differently. The top-level value must
always be a dict. Its keys become sections. Its values must be dicts as well,
which become the section contents:

```rcl
{
Unit = {
Description = "Example systemd unit.",
After = "network-online.target",
},
}
```
```systemd
[Unit]
After=network-online.target
Description="Example systemd unit."
```

Systemd allows repeating keys. To emit those, use a list or set value in
<abbr>RCL</abbr>:

```rcl
{
Service = {
BindReadOnlyPaths = ["/etc/resolv.conf", "/var/www"],
},
}
```
```systemd
[Service]
BindReadOnlyPaths=/etc/resolv.conf
BindReadOnlyPaths=/var/www
```

Some settings allow space-separated values. To emit those, use a nested list
or set:

```rcl
{
Service = {
ExecStart = [["/usr/bin/nsd", "-P", "", "-c", "/etc/nsd.conf"]],
},
},
```
```systemd
[Service]
ExecStart=/usr/bin/nsd -P "" -c /etc/nsd/nsd.conf
```

For some settings, systemd accepts the empty string to clear previous
assignments. While `""` works, using `null` avoids printing the quotes.
The difference between `""` and `null` is purely cosmetic.

```rcl
{
Service = {
Environment = [
null,
["LOG_LEVEL=debug", "PORT=8000"],
"ENVIRONMENT=prod",
],
},
}
```
```systemd
[Service]
Environment=
Environment=LOG_LEVEL=debug PORT=8000
Environment=ENVIRONMENT=prod
```

Finally, some units support repeated sections. To emit those, wrap the sections
in a list or set:

```rcl
{
Route = [
{ Gateway = "0.0.0.0", Table = 1 },
{ Gateway = "::", Table = 2 },
],
}
```
```systemd
[Route]
Gateway=0.0.0.0
Table=1

[Route]
Gateway=::
Table=2
```

RCL will use escape sequences and quote strings when needed to keep the unit
file valid, and to preserve values exactly. For some settings, such as
[`ExecStart=`][sd-start], systemd performs
[environment variable substitution][sd-var] and
[specifier substitution][sd-spec], which means that `$` and `%` have special
meaning for those settings. RCL does not apply any special handling for these
characters.

[sd-syntax]: https://www.freedesktop.org/software/systemd/man/latest/systemd.syntax.html
[sd-start]: https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#ExecStart=
[sd-var]: https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#Command%20lines
[sd-spec]: https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Specifiers

## toml

Alias for [`toml-1.0`](#toml-10), for maximum compatibility.

## toml-1.0

Output as <abbr>TOML 1.0</abbr>. This version is the most widely supported, but
can be less readable because inline tables cannot span multiple lines.

```rcl
{
profile = {
release = {
lto = "thin",
panic = "abort",
strip = "true",
},
},
}
```
Formats as:
```toml
[profile]
release = { lto = "thin", panic = "abort", strip = "true" }
```

## toml-1.1

Output <abbr>TOML 1.1</abbr>. This version supports multi-line tables, but it
was only released in December 2025, so it is less widely supported. The same
example as before outputs as follows:

```toml
[profile]
release = {
lto = "thin",
panic = "abort",
strip = "true",
}
```

## yaml-stream

If the document is a list, output every element as a <abbr>JSON</abbr> document,
prefixed by the <code>---</code> <abbr>YAML</abbr> document separator. Top-level
values other than lists are not valid for this format.

```rcl
[
{ name = "Roy Batty", serial = "N6MAA10816" },
{ name = "Leon Kowalski", serial = "N6MAC41717" },
]
```
Formats as:
```yaml
---
{"name": "Roy Batty", "serial": "N6MAA10816"}
---
{"name": "Leon Kowalski", "serial": "N6MAC41717"}
```
3 changes: 2 additions & 1 deletion docs/rcl_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ The value to write to the file in the desired format.
### format

The output format (`json`, `toml`, etc.). This must be one of the formats
supported by [`--format`](rcl_evaluate.md#-f-format-format).
supported by [`--format`](rcl_evaluate.md#-f-format-format), see also the
[output formats chapter](output_formats.md).

### width

Expand Down
48 changes: 10 additions & 38 deletions docs/rcl_evaluate.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,46 +27,18 @@ a line break between the banner and the output.

Output in the given format. The following formats are supported:

<dl>
<dt>json</dt>
<dd>Output pretty-printed <abbr>JSON</abbr>.</dd>

<dt>json-lines</dt>
<dd>If the document is a list, output every element as a <abbr>JSON</abbr>
value on its own line, consistent with the
<a href="https://jsonlines.org/"><abbr>JSON</abbr> lines</a> format.
Top-level values other than lists are not valid for this format.</dd>

<dt>raw</dt>
<dd>If the document is a string, output the string itself. If the document is
a list or set of strings, output each string on its own line.</dd>

<dt>rcl</dt>
<dd>Output pretty-printed <abbr>RCL</abbr>.</dd>

<dt>toml</dt>
<dd>Alias for <code>toml-1.0</code>.</dd>

<dt>toml-1.0</dt>
<dd>Output <abbr>TOML 1.0</abbr>.
This version is the most widely supported,
but can be less readable because inline tables
cannot span multiple lines.</dd>

<dt>toml-1.1</dt>
<dd>Output <abbr>TOML 1.1</abbr>.
This version supports multi-line tables,
but it was only released in December 2025,
so it is less widely supported.</dd>

<dt>yaml-stream</dt>
<dd>If the document is a list, output every element as a <abbr>JSON</abbr>
document, prefixed by the <code>---</code> <abbr>YAML</abbr> document
separator. Top-level values other than lists are not valid for this format.</dd>
</dl>
* [json](output_formats.md#json)
* [json-lines](output_formats.md#json-lines)
* [raw](output_formats.md#raw)
* [rcl](output_formats.md#rcl)
* [systemd](output_formats.md#systemd)
* [toml](output_formats.md#toml), alias for `toml-1.0`
* [toml-1.0](output_formats.md#toml-10)
* [toml-1.1](output_formats.md#toml-11)
* [yaml-stream](output_formats.md#yaml-stream)

The default output format is `rcl`. For the `je` command shorthand, the default
output format is `json`.
output format is `json`, and for `re`, the output format is `raw`.

### `--output-depfile <depfile>`

Expand Down
1 change: 1 addition & 0 deletions fuzz/dictionary_cli.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"json-lines"
"none"
"rcl"
"systemd"
"toml"
"unrestricted"
"workdir"
Expand Down
4 changes: 4 additions & 0 deletions golden/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def test_one(fname: str, fname_friendly: str, *, rewrite_output: bool) -> Option
case "error" | "types":
cmd = ["eval"]

# TODO: Move into the regular json and raw directories?
case "error_json":
cmd = ["eval", "--format=json"]

Expand All @@ -126,6 +127,9 @@ def test_one(fname: str, fname_friendly: str, *, rewrite_output: bool) -> Option
case "rcl":
cmd = ["eval", "--format=rcl"]

case "systemd":
cmd = ["eval", "--format=systemd"]

case "toml_10":
cmd = ["eval", "--format=toml-1.0"]
# For TOML, when the test case is not an error, we additionally test
Expand Down
23 changes: 23 additions & 0 deletions golden/systemd/basic_unit.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
Unit = {
Description = "Simple test unit",
},
Service = {
Type = "simple",
ExecStart = "/usr/bin/hello",
},
Install = {
WantedBy = "multi-user.target",
},
}

# output:
[Install]
WantedBy=multi-user.target

[Service]
ExecStart=/usr/bin/hello
Type=simple

[Unit]
Description="Simple test unit"
15 changes: 15 additions & 0 deletions golden/systemd/error_key_comment.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
Unit = {
"; Would be comment": "Therefore disallowed.",
},
}

# output:
stdin:1:1
1 │ {
╵ ^
in value
at key "Unit"
at key "; Would be comment"
Error: To export as systemd, keys cannot start with the comment characters '#' and ';'.
Loading