“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
| Stage | Formats |
|---|---|
| Parse | Graph::Easy text, Graphviz DOT |
Render via CLI (--as) | ascii, html, txt, dot/graphviz |
| Render via library | boxart (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.
| example | one-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 when | what 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 yet | gently-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 both | gently-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_graphvizalways directs edges, named groups round-trip ascluster0..N, and group-endpoint edges crash upstream’sas_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).