<div align="center"><img src="https://raw.githubusercontent.com/dcompoze/cellbook/main/cellbook.svg" width="15%"></div>
## Cellbook
Dynamic computational notebook environment in plain Rust.
- Cells are defined as `async` functions with `#[cell]` macro annotations
- Cells are compiled as a `dylib` crate and dynamically reloaded on changes
- `cargo-cellbook` CLI utility provides a TUI runner and automatic reloader
- Cells have access to a shared store which retains the cell context across reloads
- Cell output is stored and can be viewed in the TUI runner
- Integrates with external applications to view images, plots, graphs, etc.
## Installation
```bash
cargo install cargo-cellbook
```
To create and run a new cellbook project use:
```bash
cargo cellbook init <project-name>
cd <project-name>
cargo cellbook run
```
## Notebook structure
The notebook consists of an `#[init]` function (runs on load/reload) and individual `#[cell]` functions loaded in source order.
```rust
use cellbook::{cell, init, load, store, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Stats {
mean: f64,
count: usize,
}
#[init]
async fn setup() -> Result<()> {
Ok(())
}
#[cell]
async fn load_data() -> Result<()> {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
store!(data)?;
println!("Loaded {} values", data.len());
Ok(())
}
#[cell]
async fn compute_stats() -> Result<()> {
let data: Vec<f64> = load!(data)?;
let stats = Stats {
mean: data.iter().sum::<f64>() / data.len() as f64,
count: data.len(),
};
println!("Mean: {:.2}", stats.mean);
store!(stats)?;
Ok(())
}
```
## Context store
Cells can store persistent data in the shared store using `store!()`, `load!()`, `remove!()`, `consume!()` convenience macros.
Values in the store are serialized with [postcard](https://crates.io/crates/postcard), hence stored types must implement serde's `Serialize` and `Deserialize` traits.
```rust
// Store a value (variable name becomes the key)
store!(data)?;
// Store with explicit key
store!(my_key = some_value)?;
// Load a value (type has to be specified)
let data: Vec<f64> = load!(data)?;
// Remove a value from the store
remove!(data);
// Load and remove the value from the store
let data: Vec<f64> = consume!(data)?;
```
## Crates
| `./cellbook` | Core library with shared context store, cell registry and declarative macros. |
| `./cellbook-macros` | Proc macro crate which implements `#[cell]` and `#[init]` macros. |
| `./cargo-cellbook` | Cellbook project runner and command line utility. |
| `./examples` | Cellbook usage examples and tests. |
## Configuration
Configuration is loaded in the following order:
- Built-in defaults
- Global config at `$XDG_CONFIG_HOME/cellbook/config.toml` (or platform-specific config dir)
- Local config at `./Cellbook.toml`
Only fields present in a config file are overridden.
The global configuration file is created with default values on first run:
```toml
[general]
auto_reload = true
debounce_ms = 500
show_timings = false
#image_viewer = "eog"
[keybindings]
quit = "q"
clear_context = "x"
view_output = "o"
view_error = "e"
reload = "r"
edit = "E"
run_cell = "Enter"
navigate_down = ["Down", "j"]
navigate_up = ["Up", "k"]
```
Keybindings can be a single key or an array of alternative keys.
Supported key names include single characters and `Enter`, `Esc`, `Tab`, `Space`, `Backspace`, `Delete`, `Up`, `Down`, `Left`, `Right`, `Home`, `End`, `PageUp`, `PageDown`, `F1`, etc.
## Interface
The `cargo cellbook run` command opens the terminal-based cellbook runner interface:
<div align="center"><img src="https://raw.githubusercontent.com/dcompoze/cellbook/main/screenshot.png" width="100%"></div>
It allows running/editing/reloading cells, inspecting cell output, viewing images and more.
It also shows what types are stored in the shared context store.