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,
}