Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

“Everything is connected!” — Dirk Gently

Introduction

gently is a Rust port of Perl’s Graph::Easy: build graphs (nodes, edges, groups), lay them out deterministically on a flat surface, and render them as ASCII art, HTML, or Graphviz DOT — plus parse graphs from the Graph::Easy text format and from DOT.

Compatibility is gated differentially against the original Perl tool via a pinned oracle (Graph::Easy v0.69, just oracle-verify): an output only ships when it is byte-identical to what the oracle produces. Where gently still differs, the known hard cases page is the authoritative status.

Quickstart

Build the CLI and render a graph to ASCII art — the Graph::Easy text format goes in on stdin, the layout comes out:

cargo build -p gently-cli
echo '[ Bonn ] --> [ Berlin ] --> [ Hamburg ]' | target/debug/gently --as ascii
+------+     +--------+     +---------+
| Bonn | --> | Berlin | --> | Hamburg |
+------+     +--------+     +---------+

--as selects the output format (so does the output filename’s extension, as in upstream). Rendering from a file argument instead of stdin:

target/debug/gently --as ascii examples/simple-chain/input.gv

Formats

StageFormats
ParseGraph::Easy text, Graphviz DOT
Render via CLI (--as)ascii, html, txt, dot/graphviz
Render via libraryboxart (CLI wiring pending)

Asking for an unwired format (e.g. --as boxart, --as svg) errors with gently: unknown output format and exit 255 until a renderer is wired to the CLI.

Where to go next

  • Examples — worked, byte-verified input/output pairs.
  • Gallery — every example as ASCII art and SVG.
  • Release status — version and commit this book was built from (generated; the repository is authoritative).
  • The repository README carries the contributor gates and the full compatibility methodology.

Release Status

Generated artifact (just docs-sync, from workspace Cargo.toml + git). Do not hand-edit — the repo source files remain authoritative.

  • Version: v0.2.3
  • Built from: 6965326

examples

Worked Graph::Easy text-format examples, each a directory with:

  • input.gv — the graph source (Graph::Easy text format)
  • expected.txt — the committed, byte-verified ASCII-art output

Every expected.txt is the recorded output of the pinned oracle (Graph::Easy v0.69 @ ededa3d7…, PERL_HASH_SEED=0) — mined from the differential fixture corpus in tests/fixtures/graph-easy/, pin header stripped — and just examples-verify re-renders each input.gv with the gently CLI and fails on any byte mismatch. The oracle’s ASCII art is also gently’s, so what you see in expected.txt is what the CLI prints.

exampleone-liner
simple-chain/the smallest useful graph — a linear chain a --> b --> c
edge-styles/edge style attributes: solid ------> and double ========> arrows
edge-attributes/inline edge attributes { style: bold; label: foo; } on an edge
diamond/branching and re-joining: fan-out from a, fan-in to d (the diamond layout)
self-loop/an edge from a node back to itself
parallel-edges/two separate edges between the same pair of nodes
isolated-node/an isolated node laid out next to an edge-connected pair

Every example also appears as a visual gallery entry — CLI ASCII art plus an SVG rendering from gently’s DOT output — in docs/gallery.md.

Build the CLI first (see the main README quickstart), then run any example through it:

cargo build -p gently-cli
gently --as ascii < examples/simple-chain/input.gv

Known rendering divergences from the oracle are tracked separately — see docs/known-hard-cases.md. Examples are only published for inputs where gently is byte-identical to the oracle.

Adding an example

New examples must be byte-identical to the pinned oracle before they are published — non-identical renders become divergence rows in docs/known-hard-cases.md instead. The step-by-step maintainer workflow lives in the repository at .wai/resources/maintainer-docs/contributing-examples.md.

gallery

A visual gallery of the published examples: each example shown three ways — the graph source, the CLI’s ASCII render (the byte-verified expected.txt), and an SVG rendering.

How the SVG is produced

Upstream Graph::Easy v0.69 ships no SVG module, so SVG is out of scope for gently’s v1 renderers. What you see here instead is gently’s own library Graphviz renderer (gently_core::render::graphviz, spec-pinned ge.graphviz_render) emitting DOT source, piped through dot -Tsvg:

examples/<name>/input.gv → text parser → graphviz render (DOT) → dot -Tsvg → docs/gallery/<name>.svg

The committed SVGs are drift-gated: just gallery-verify regenerates every SVG through exactly this path and fails on any byte mismatch — the same culture as just examples-verify for the ASCII output.


diamond

Input:

[ a ] --> [ b ]
[ a ] --> [ c ]
[ b ] --> [ d ]
[ c ] --> [ d ]

CLI ASCII render:

+---+     +---+     +---+
| a | --> | b | --> | d |
+---+     +---+     +---+
  |                   ^
  |                   |
  v                   |
+---+                 |
| c | ----------------+
+---+

SVG:

edge-attributes

Input:

[ AB ] --> { style: bold; label: foo; } [ ABC ]

CLI ASCII render:

+----+  foo   +-----+
| AB | #####> | ABC |
+----+        +-----+

SVG:

edge-styles

Input:

[ Bonn ] -- solid --> [ Berlin ]
[ Berlin ] == double ==> [ Rostock ]

CLI ASCII render:

+------+  solid   +--------+  double   +---------+
| Bonn | -------> | Berlin | ========> | Rostock |
+------+          +--------+           +---------+

SVG:

isolated-node

Input:

[ a ] --> [ b ]
[ d ]
[ b ] --> [ c ]

CLI ASCII render:

+---+     +---+     +---+
| a | --> | b | --> | c |
+---+     +---+     +---+
+---+
| d |
+---+

SVG:

parallel-edges

Input:

[ a ] --> [ b ]
[ a ] --> [ b ]

CLI ASCII render:


  +---------+
  |         v
+---+     +---+
| a | --> | b |
+---+     +---+

SVG:

self-loop

Input:

[ a ] --> [ a ]

CLI ASCII render:


  +--+
  v  |
+------+
|  a   |
+------+

SVG:

simple-chain

Input:

[ a ] --> [ b ]
[ b ] --> [ c ]

CLI ASCII render:

+---+     +---+     +---+
| a | --> | b | --> | c |
+---+     +---+     +---+

SVG:


Where the truth lives

The ASCII renders above are the committed, byte-verified examples/<name>/expected.txt files, kept honest by just examples-verify; known gaps are tracked in docs/known-hard-cases.md.

Known hard cases — compatibility status

gently is gated byte-identically against the pinned Graph::Easy oracle (v0.69 @ ededa3d7…). The cases below are where the two currently differ. Each row tells you when you’ll see it — if your graph doesn’t match one of these triggers, gently’s output should be byte-identical to the oracle’s. Resolved divergences and verification internals live in the maintainer history (.wai/resources/maintainer-docs/compatibility-history.md in the repository).

Open divergences

you’ll see this whenwhat differs (oracle → gently)bead
you use an indexed port with the back direction, wrapping outside the bounding box ({ end: back,N; } on a west edge)indexed { start,end: front,N } ports render byte-identically; the back (west) wrap outside the bounding box does not yetgently-byq (residual)
you print a graph as Graph::Easy text (--as txt) where an edge starts or ends at an anonymous group, e.g. ( [ Bonn ] ) -> [ Berlin ]the oracle’s as_txt omits the group-only-connected node; gently lists it as a plain node. (The minimal variant is adjudicated as an oracle defect — see the maintainer history.)gently-58m
you parse DOT with a bare scope nested inside a named subgraph, e.g. digraph G { subgraph outer { { x } } }the oracle puts x inside group outer (dashed group box); gently keeps x ungrouped. Top-level bare scopes are ungrouped in bothgently-fb1

Byte-compat scope-off (deliberate, no divergence claims)

Per the byte-compat scope contract, three input classes are outside byte-equality with the oracle by design:

  • Hash-randomized inputs. Upstream renders some inputs differently across Perl hash seeds even under a pinned seed; those classes (e.g. the POD edge-style block, the three-node fan) are verified as claim probes, never as byte fixtures.
  • Beyond-envelope sizes. The oracle needs superlinear time (~10 s at 200 nodes), so large-graph behavior (e.g. 1000 nodes) is verified against gently’s own structural invariants and performance budgets, not oracle bytes.
  • Round-trip gaps the oracle itself has. as_graphviz always directs edges, named groups round-trip as cluster0..N, and group-endpoint edges crash upstream’s as_graphviz — byte-compat on these is unsatisfiable by construction, so round-trip claims are worded per upstream behavior instead.

How this is verified

  • just oracle-verify — differential gate over the pinned-oracle fixture corpus (tests/fixtures/graph-easy/)
  • just examples-verify — every published example stays byte-identical to its committed expected output

Each divergence is tracked in the issue tracker; the full resolved history and the reproduction/adjudication pipeline are documented in the maintainer history (.wai/resources/maintainer-docs/compatibility-history.md).