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.