The Nickel command-line interface
The Nickel command-line interface (CLI) provides many tools for interacting with nickel files: evaluating, formatting, testing, and more. You can find brief documentation for all of these tools in the CLI itself:
$ nickel help
The Nickel interpreter CLI
Usage: nickel [OPTIONS] <COMMAND>
Commands:
eval Evaluates a Nickel program and pretty-prints the result
# etc.
$ nickel help eval
Evaluates a Nickel program and pretty-prints the result
Usage: nickel eval [OPTIONS] [FILES]... [-- <CUSTOMIZE_MODE>...]
Arguments:
# etc.
This document contains some more in-depth documentation on some of the CLI commands.
nickel doc
: Generate API documentation
When you create a nickel code for other people to use or customize, your users
might expect it to be documented. The nickel doc
command generates markdown
documentation straight from the documentation metadata and contract annotations
in your nickel source code.
For example, if the file "main.ncl" contains
{
foo
| Number
| doc "This is my field named foo."
}
then running nickel doc main.ncl
will create the file .nickel/doc/main.md
with
the contents
# `foo`
- `foo | Number`
This is my field named foo.
The "record spine"
nickel doc
works only on records: if your file evaluates to a record at the
top level, it will document its fields. If those fields evaluate to records, it
will document the fields of those records, and so on recursively. The recursion
will stop when nickel finds anything that isn't a field. For example, the following
nickel file contains a function at the top level. Even though that function
returns a record, nickel doc
will not evaluate the function and will not document
the record that it returns.
fun x => { foo | doc "This won't appear in the output of `nickel doc`" }
Documenting imports
nickel doc
evaluates imports, and if those imports evaluate to records then it
will recurse into those imports. In practice, when you document a nickel library
that is implemented across multiple files, you only want to run nickel doc
on the
main entry point. For example, if "main.ncl" contains
{
main_field | doc "My main field",
sub_record
| doc "Other stuff"
= import "inner.ncl",
}
and "inner.ncl" contains
{
inner_field | doc "My inner field",
}
then running nickel doc main.ncl
will document main_field
, sub_record
,
and inner_field
. Since you probably don't expect users to import "inner.ncl"
directly, there's no need to run nickel doc inner.ncl
.
nickel test
: Unit/documentation tests
With the nickel test
command, nickel supports using documentation examples as
tests. This command extracts markdown blocks in documentation metadata, and runs
them as nickel snippets. For example, if the file "main.ncl" contains
{
foo
| Number
| doc m%"
This is my field named foo.
## Examples
```nickel
1 + "2"
```
"%
}
then running nickel test main.ncl
will fail with the error message
testing foo/0...FAILED
test foo/0 failed
error: dynamic type error
┌─ [..]/test.ncl:1:7
│
1 │ 1 + "2"
│ ^^^ this expression has type String, but Number was expected
│
= (+) expects its 2nd argument to be a Number
1 failures
error: tests failed
Only code blocks with the "nickel" tag are tested.
Testing for expected output
In order to check that a test evaluates to a given value, terminate its
code block with a comment like # => <expected output>
. This is equivalent
to applying a std.contract.Equal <expected output>
contract, but might
look better in the rendered documentation. For example, running
nickel test
on
{
foo
| Number
| doc m%"
This is my field named foo.
## Examples
```nickel
1 + 1
# => 3
```
"%
}
will output
testing foo/0...FAILED
test foo/0 failed
error: contract broken by a value
┌─ <unknown> (generated by evaluation):1:1
│
1 │ std.contract.Equal 3
│ -------------------- expected type
│
┌─ /home/jneeman/tweag/nickel/test.ncl:1:3
│
1 │ 1 + 1
│ ^^^^^ applied to this expression
│
┌─ <unknown> (generated by evaluation):1:1
│
1 │ 2
│ - evaluated to this value
1 failures
error: tests failed
Checking for errors
Sometimes you want a test case to ensure that errors are raised when
appropriate. You can check for this by terminating a code block with
a comment like # => error: <expected error text>
, and the test runner
will check that the example raises and error, and that the error message
contains "<expected error text>" as a substring. To test for an error
without checking the error message, terminate a code block with # => error
.
For example,
{
foo
| Number
| doc m%"
This is my field named foo.
## Examples
```nickel
1 + "2"
# => error: has type String, but Number was expected
```
"%
}
Ignoring tests
To ignore a test while still keeping the code block tagged as nickel source, add the "ignore" label, like
```nickel ignore
1 | String
```
Multiline tests
If you have many short examples, you might prefer not to wrap each one in a separate code block. In this case, you can use the "multiline" label to create multiple blank-line-separated tests in a single code block. For example,
{
foo
| Number
| doc m%"
This is my field named foo.
## Examples
```nickel multiline
1 + "2"
# => error: has type String, but Number was expected
1 + 1
# => 2
1 + 2
# => 3
```
"%
}
The test expression's environment
Each test expression will be evaluated in the same environment as the field it's documenting: the name of the field will be in scope, along with the names of all the sibling fields.
For example, the following tests will succeed:
{
bar
| Number
= 2,
foo
| Number
| doc m%"
This is my field named foo.
## Examples
```nickel
foo
# => 1
```
```nickel
foo + bar
# => 3
```
"%
= 1,
}