DIB notebooks

moonrockz/notebooks/dib reads and writes the plain-text .dib format used by .NET Interactive and Polyglot Notebooks. Behavior follows the upstream code-submission parser and its format tests.

The runnable usage guide contains complete, tested examples for parsing, construction, custom registries, document metadata, directive navigation, errors and normalization. The shared navigation guide covers generic walks and folds.

let book = @dib.parse("#!markdown\n# Results\n#!csharp\n1 + 1\n")
let code = book.cells_for_kernel("c#")
let serialized = book.to_string()

Cells and selectors

Each immutable cell has source(), kernel() and a common kind(). Markdown uses the pseudo-kernel markdown; other registered kernels produce code cells. DIB stores source without persisted execution outputs or attachments.

A selector must start at column zero and match a registered kernel name or alias exactly. #!fsharp starts a cell; #!fsharp --help is source. Commands such as #!share, #!connect and unknown #!something remain source. The parser does not execute directives or infer new kernels from #!connect.

The standard registry includes C#, F#, PowerShell, JavaScript, HTML, SQL, KQL, HTTP, value and Mermaid kernels, plus Markdown. Aliases include cs, c#, C#, fs, f#, F#, powershell, js and md. Matching is case-sensitive. Unmarked source uses C# by default. An explicit registry can change it:

let kernel = @dib.KernelName("example")
let registry = @dib.KernelRegistry(
  [@dib.KernelInfo(kernel, aliases=[@dib.KernelName("ex")])],
  default_kernel=kernel,
)
let book = @dib.parse("#!ex\nrun()", registry~)

Registry construction checks duplicate names and aliases and validates the default kernel. All names and aliases are typed KernelName values. Markdown is available independently of the execution environment. Notebook construction resolves cell aliases to their canonical kernel names.

Metadata

The first recognized metadata header introduces document metadata. Headers are exact, lowercase and at column zero. The section continues until the next line beginning with #!, or the end of the document.

Header Input format
#!meta Standard DIB JSON
#!meta json JSON
#!meta yaml YAML mapping
#!meta toml TOML table
#!meta ion Ion struct

Format-qualified headers are Moon-Lander extensions. There is no format auto-detection under bare #!meta. All inputs normalize to the shared Ion metadata model. The top-level value must be an object, mapping, table or struct with unique textual keys. Empty or comment-only YAML and TOML sections become empty structs. YAML accepts a single document with string mapping keys. Malformed input raises InvalidMetadata with the section's first payload line and a parser diagnostic. Later metadata headers remain cell source.

#!meta
{"title":"Example","kernelInfo":{"defaultKernelName":"csharp","items":[{"name":"custom","aliases":["alt"]}]}}

#!alt
custom()

kernelInfo.items registers custom names and aliases. Metadata declarations take precedence over environment kernels of the same name. The registry also recognizes default-kernel metadata in polyglot_notebook and the legacy dotnet_interactive namespace. A default declared in a leading metadata section applies to the notebook's initial kernel.

Metadata is the shared Ion-backed @notebooks.Metadata type, re-exported here. metadata.get(key) returns an independent Ion value, and to_ion() exports the full struct. Use Metadata::from_ion(value) for native values or Metadata(json) to import JSON. get_json and to_json are explicit adapters that reject Ion-only data with MetadataError::NotJson.

YAML and TOML integers become Ion integers; real numbers become Ion floats, including infinity and NaN. YAML dates follow the YAML parser's string resolution. TOML dates and datetimes become Ion timestamps. Local datetimes retain an unknown offset, and date-free local times become strings annotated toml_local_time, since Ion has no date-free time type. Native Ion input also retains exact decimals, typed nulls, annotations, blobs and other Ion values. The original YAML/TOML syntax and comments are not retained.

book.to_string() uses MetadataOutput::Auto. It emits standard JSON metadata when parsing that JSON recreates the same Ion value; otherwise it emits #!meta ion. To choose explicitly:

let ion_source = book.to_string(metadata_output=@dib.MetadataOutput::Ion)
let json_source = book.to_string(metadata_output=@dib.MetadataOutput::Json)

Explicit JSON output raises InvalidMetadata if conversion would lose information. YAML and TOML are input formats; output uses JSON or Ion.

Writing and construction

let cell = @dib.Cell::Cell("Console.WriteLine(1);", kernel=@dib.KernelName("csharp"))
let book = @dib.Notebook::Notebook([cell])

LF, CRLF and CR normalize to LF. Leading and trailing empty lines are trimmed from cells; whitespace-only lines and internal blank lines remain. Empty sections are omitted, and parsing an empty document yields one empty cell. Writing omits empty cells, emits canonical selectors, and adds a blank line after each selector and between cells. A leading BOM is accepted.

to_string() raises UnrepresentableSource if a constructed cell contains a registered selector that would change its structure when read again. It identifies the cell index and the one-based line within the cell. This avoids silently splitting source into more cells. If cell source contains a later recognized metadata directive, the writer emits a document metadata header, using {} when needed, so that directive remains source on the next read.

When custom kernels come from the caller's registry rather than document metadata, supply that same registry on subsequent reads. The writer preserves metadata and does not inject environment configuration. Use book.registry() to retain that context. Persist custom kernel definitions in #!meta when a file needs to describe its own selectors.

Specialized navigation

Cell.directives() returns immutable directive values with name, arguments, original text and a one-based line relative to cell source. cells_for_kernel(name) resolves aliases before selecting cells.

Cell implements @notebooks.CellView for mixed-format collections and runtime capability inspection. cell.mime_capability() returns None, because DIB stores no MIME bundles. Its shared view returns the cell's kind, Some(source) and None for cell-local metadata. It can be stored beside Jupyter cells in a ReadOnlyArray[&@notebooks.CellView].

Node has Notebook, Cell and Directive variants. Notebook children are cells in source order; cell children are directives in source order. Directive nodes are leaves. Kernel selectors are cell properties, not source directives.

Use book.cursor(), node.at(path), walk, walk_cursor and book.fold, or pass book.root() to the shared @notebooks functions. Cursors retain @dib.Node, so a caller can pattern match Directive(directive) and inspect its arguments during otherwise generic traversal.

View source on GitHub