mdbook-nix-eval

This is a mdbook preprocessor designed to evaluate code blocks containing nix expressions.

```test-file.nix
builtins.langVersion
```

code blocks with nix expressions are evaluated using nix.

```nix
builtins.langVersion
```

Simple Evaluation

builtins.langVersion
5

Importing Files

Code blocks with filenames ending in ".nix" will be put into a temporary directory for each chapter.

langVersion.nix

builtins.langVersion
5

As the expression is written to a known file name, it's possible to import as usual.

let
    version = import ./langVersion.nix;
in  
"Nix Langauge Version: ${toString version}"
"Nix Langauge Version: 5"

With evaluation errors passed through

let
    version = import ./langVersion.nix;
in
"Nix Langauge Version: ${version}"
error: cannot coerce an integer to a string, at /tmp/.tmpudFalA/eval.nix:4:2

Expressions can be functions

{ pkgs ? import <nixpkgs> {} }:
pkgs.path
"/nix/store/26gcnrhl5v14s6s2syxi1ynpcmik1xvd-nixpkgs"

However, they should have defaults, otherwise they won't return an evaluation that makes sense to display.

without-defaults.nix

{ pkgs }:
pkgs.path
error: anonymous function at /tmp/.tmpudFalA/without-defaults.nix:1:1 called without required argument 'pkgs', at (string):1:1

Default-less function arg files can still be referenced later without issue

import ./without-defaults.nix { pkgs = import <nixpkgs> {}; }
"/nix/store/26gcnrhl5v14s6s2syxi1ynpcmik1xvd-nixpkgs"

More Examples

If the nix-builder has sandboxing enabled, there should be limited access to sensitive info, but... it's probably best to only run trusted expressions.

let
run = (with import <nixpkgs> {}; runCommand "foo" {} "ls -l ~/.ssh > $out");
in
builtins.readFile run
building '/nix/store/hsvgfm0h37yqirmgjnzqsw9jrrh1shrc-foo.drv'...
ls: cannot access '/homeless-shelter/.ssh': No such file or directory
builder for '/nix/store/hsvgfm0h37yqirmgjnzqsw9jrrh1shrc-foo.drv' failed with exit code 2
error: build of '/nix/store/hsvgfm0h37yqirmgjnzqsw9jrrh1shrc-foo.drv' failed

Network access is allowed in some (most?) cases, so again trusted expressions only are advised.

{ pkgs ? import <nixpkgs> {} }:
let
  gitignore = builtins.fetchurl {
    url = "https://www.toptal.com/developers/gitignore/api/jetbrains,linux,macos,git";
    name = "gitignore";
    sha256 = "0fn3632fdz5rbvbkwnn82q6qsdsq2haxc7mlbm536g69zlr41c1z";
  };
in
{
    out = builtins.elemAt (pkgs.lib.splitString "\n" (builtins.readFile gitignore)) 1;
}
{
  "out": "# Created by https://www.toptal.com/developers/gitignore/api/jetbrains,linux,macos,git"
}
builtins.readFile (with import <nixpkgs> {}; runCommand "foo" {} "date > $out; ${fping}/bin/fping -c5 1.1.1.1 >> $out")
"
Fri Mar  5 07:46:36 UTC 2021
1.1.1.1 : [0], 64 bytes, 13.8 ms (13.8 avg, 0% loss)
1.1.1.1 : [1], 64 bytes, 12.5 ms (13.1 avg, 0% loss)
1.1.1.1 : [2], 64 bytes, 12.6 ms (13.0 avg, 0% loss)
1.1.1.1 : [3], 64 bytes, 12.5 ms (12.9 avg, 0% loss)
1.1.1.1 : [4], 64 bytes, 12.6 ms (12.8 avg, 0% loss)
"

Remote Build Machines

If you have remote building enabled,

systems.nix

{
 linux = (with import <nixpkgs> { system = "x86_64-linux"; }; runCommand "foo" {} "uname > $out");
 darwin = (with import <nixpkgs> { system = "x86_64-darwin"; }; runCommand "foo" {} "uname > $out");
}
{
  "darwin": "/nix/store/jnwb5xrkyncn11y3a4b8abad6qmnysn9-foo",
  "linux": "/nix/store/psny6mvwyc4l258mqhkfahl7asf6bdhh-foo"
}
builtins.readFile (import ./systems.nix).linux
"Linux"
builtins.readFile (import ./systems.nix).darwin
"Darwin"

Supported output types

null

null
null

string

"this is a string"
"this is a string"

(note: the formatting is slightly modified to better show multiline)

"this is\na multiline string\nnote that trailing whitespace is trimmed\n"
"
this is
a multiline string
note that trailing whitespace is trimmed
"

numeric

12345
12345

lists (arrays)

[1234 6789]
[
  1234,
  6789
]

sets (objects)

{first = 1234; second = 6789;}
{
  "first": 1234,
  "second": 6789
}

functions? nope

builtins.readFile
error: cannot convert the built-in function 'readFile' to JSON