Skill: add-rust-svgbob
File references (@path) in this skill are relative to this directory.
What I do
I add ASCII art diagrams to Rust /// doc comments using the svgbobdoc crate's transform!() macro. The ASCII art inside ```svgbob code fences is converted to inline SVG at compile time and embedded as data:image/svg+xml data URIs — no external files needed, works on both local cargo doc and docs.rs.
The diagram lives in the source code itself — anyone reading the Rust file sees the ASCII art directly.
When to use me
Use this when:
- You have a Rust library with structural/architectural concepts best explained with a diagram
- You want quick flowcharts, process diagrams, or data flow visuals without external image files
- The diagram is simple enough to represent with ASCII box-drawing characters
- You want diagrams that are visible in the source code (not hidden in external PNGs)
- You maintain a crate where
svgbobdoc's dependency weight is acceptable
Not suitable for:
- Real data visualizations (scatter plots, line charts) — use
embed-doc-imageinstead - Complex diagrams with curved paths or fine detail — ASCII art has limitations
- Projects on very old Rust compilers (< 1.54)
Prerequisites
| Requirement | Purpose |
|---|---|
| Rust ≥ 1.54 or nightly | For inner attribute + proc macro support |
svgbobdoc in dependencies | The proc macro crate |
enable feature on svgbobdoc | Activates the svgbob SVG renderer |
doc-images feature flag (or custom) | Feature-gating for compatibility |
| docs.rs metadata configured | Ensures SVGs render on docs.rs |
How I work
Step 1: Check current state
Read Cargo.toml to check if svgbobdoc is already configured:
- Is
svgbobdocin[dependencies]withfeatures = ["enable"]? - Is it optional (recommended)?
- Is there a
doc-imagesor similar feature to enable it? - Does
[package.metadata.docs.rs]includefeatures = ["doc-images"]?
Check source files for existing ```svgbob usage or svgbobdoc::transform!() calls.
Step 2: Set up infrastructure (if missing)
If svgbobdoc is not configured, add it:
Cargo.toml additions:
[dependencies]
svgbobdoc = { version = "0.3", optional = true, features = ["enable"] }
[features]
doc-images = ["dep:svgbobdoc"]
[package.metadata.docs.rs]
features = ["doc-images"]
If embed-doc-image is also used, combine under the same feature:
[features]
doc-images = ["dep:embed-doc-image", "dep:svgbobdoc"]
Step 3: Scan the project
Look in <project>/src/ recursively for *.rs files. Identify diagram candidates — types and functions where an ASCII diagram would clarify the concept.
Step 4: Identify diagram candidates
A function/type/module is a "diagram candidate" when:
| Signal | Example |
|---|---|
| Data flow | Input → Processing → Output |
| Process steps | Algorithm with clear stages |
| Coordinate mapping | (x, y) → transformed coordinates |
| Geometric transformation | Rotation, projection, scaling |
| Tree/graph structure | Binary tree, netlist, dependency graph |
| Architecture | Module hierarchy, pipeline stages |
| State machine | States and transitions |
| Comparison | Before vs After, two alternatives |
| Layout | Floorplan, memory layout, packing |
Step 5: Design the ASCII diagram
svgbob rendering capabilities:
| ASCII element | svgbob rendering |
|---|---|
.─. / '─' | Rounded rectangle borders |
│ / ─ | Lines |
/ / \ | Diagonal lines |
► / ▼ / ▲ / ◄ | Arrow heads (Unicode) |
-> / <- | Arrow connectors |
=== / ~~~ | Double/wavy lines |
( ) / [ ] | Curved/angled brackets |
○ / ● | Circle markers |
... | Dashed connectors |
Design tips for clear diagrams:
- Keep it compact — 15-25 rows max per diagram
- Use box borders —
.───.for top,'───'for bottom - Label arrows — Add text next to
──►connectors - Align with Unicode box-drawing —
─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼ - Test the diagram — Use the svgbob editor to preview
Example — a simple data flow:
.───────────. .───────────. .───────────.
│ Input │────►│ Process │────►│ Output │
'───────────' '───────────' '───────────'
Step 6: Add the diagram
For struct-level docs:
/// Existing description.
///
/// The transformation flow:
#[cfg_attr(feature = "doc-images", doc = svgbobdoc::transform!(
/// ```svgbob
/// .───────────. .───────────.
/// │ Step 1 │────►│ Step 2 │
/// '───────────' '───────────'
/// ```
))]
pub struct MyType { ... }
For function-level docs:
/// Description.
#[cfg_attr(feature = "doc-images", doc = svgbobdoc::transform!(
/// ```svgbob
/// .───────────. .───────────.
/// │ Input x │────►│ Output f(x)│
/// '───────────' '───────────'
/// ```
))]
pub fn my_function(x: f64) -> f64 { ... }
IMPORTANT: The /// lines inside transform!() must be preserved exactly. The macro parses /// as doc comment lines and processes ```svgbob blocks within them.
Step 7: Build and verify
# Build with diagrams
cargo doc --features doc-images --no-deps --open
Verification checklist:
-
cargo build(without features) passes — no svgbobdoc dependency leak -
cargo build --features doc-imagespasses —transform!()macro runs -
cargo doc --features doc-images --no-depscompletes with zero warnings - Generated HTML contains
data:image/svg+xml;base64,...URIs - SVGs render correctly in browser (proper arrows, boxes, text)
# Verify SVG data URIs on each struct page
grep -c "data:image/svg" target/doc/my_crate/struct.MyType.html
Step 8: Check both feature states
cargo build # Must pass (svgbobdoc not linked)
cargo build --features doc-images # Must pass (transform! runs)
cargo test --all-features # All tests must pass
Configuration Reference
Basic Cargo.toml (svgbobdoc only)
Minimal setup for a project using only svgbobdoc:
[package]
name = "my-crate"
version = "0.1.0"
edition = "2024"
[dependencies]
svgbobdoc = { version = "0.3", optional = true, features = ["enable"] }
[features]
doc-images = ["dep:svgbobdoc"]
[package.metadata.docs.rs]
features = ["doc-images"]
Combined with embed-doc-image
When using both svgbobdoc (ASCII diagrams) and embed-doc-image (PNG/SVG figures):
[dependencies]
embed-doc-image = { version = "0.1", optional = true }
svgbobdoc = { version = "0.3", optional = true, features = ["enable"] }
[features]
doc-images = ["dep:embed-doc-image", "dep:svgbobdoc"]
[package.metadata.docs.rs]
features = ["doc-images"]
Both share the same doc-images feature — one cargo doc --features doc-images enables both.
Custom feature names
If your project already has a doc-images feature for other purposes, use a different name:
[features]
default = []
svg-docs = ["dep:svgbobdoc"] # Custom name
doc-images = ["dep:embed-doc-image"] # Existing name
Then in source code:
#[cfg_attr(feature = "svg-docs", doc = svgbobdoc::transform!(
/// ```svgbob
/// diagram
/// ```
))]
pub struct MyType;
Feature resolution with dep: syntax
The dep: prefix in feature definitions was stabilised in Rust 1.60. It explicitly references an optional dependency:
# Modern syntax (Rust ≥ 1.60) — recommended:
doc-images = ["dep:svgbobdoc"]
# Legacy syntax — works but less explicit:
doc-images = ["svgbobdoc"]
The dep: form is preferred — it prevents ambiguity between dependency and feature names.
docs.rs metadata options
| Setting | Purpose |
|---|---|
features = ["doc-images"] | Enables svgbobdoc during doc build |
rustdoc-args = ["--html-in-header", "src/doc-header.html"] | Injects KaTeX/JS headers |
all-features = true | Builds docs with ALL features (use with care) |
[package.metadata.docs.rs]
features = ["doc-images"]
rustdoc-args = ["--html-in-header", "src/doc-header.html"]
Conditional compilation with cfg_attr
The core pattern:
#[cfg_attr(
feature = "doc-images", // condition
doc = svgbobdoc::transform!( // attribute to emit
/// ```svgbob
/// diagram
/// ```
)
)]
How it works at each build:
| Build command | cfg_attr evaluates to | Result |
|---|---|---|
cargo build | feature = "doc-images" is false | Attribute suppressed — no macro, no svgbob dep |
cargo build --features doc-images | feature = "doc-images" is true | #[doc = svgbobdoc::transform!(...)] emits — SVG generated |
cargo doc --features doc-images | Same as above | SVG data URIs embedded in HTML |
| docs.rs (metadata sets features) | Same as above | SVG renders on docs.rs |
Checking feature resolution
Verify the feature is correctly wired:
# Check that svgbobdoc is NOT linked in default build
cargo build --verbose 2>&1 | grep svgbobdoc
# (should produce no output)
# Check that svgbobdoc IS linked with feature
cargo build --features doc-images --verbose 2>&1 | grep svgbobdoc
# Output: "Compiling svgbobdoc v0.3.0"
svgbob ASCII Art Syntax Guide
Box-drawing characters
The svgbob renderer recognises these Unicode box-drawing characters and converts them to SVG paths:
| Character | Code point | Renders as |
|---|---|---|
─ | U+2500 | Horizontal line |
│ | U+2502 | Vertical line |
┌ | U+250C | Top-left corner |
┐ | U+2510 | Top-right corner |
└ | U+2514 | Bottom-left corner |
┘ | U+2518 | Bottom-right corner |
├ | U+251C | Left T-junction |
┤ | U+2524 | Right T-junction |
┬ | U+252C | Top T-junction |
┴ | U+2534 | Bottom T-junction |
┼ | U+253C | Cross junction |
╱ | U+2571 | Diagonal up-right |
╲ | U+2572 | Diagonal up-left |
◄ | U+25C4 | Left arrow head |
► | U+25BA | Right arrow head |
▲ | U+25B2 | Up arrow head |
▼ | U+25BC | Down arrow head |
● | U+25CF | Filled circle |
○ | U+25CB | Empty circle |
═ | U+2550 | Double horizontal |
║ | U+2551 | Double vertical |
≈ | U+2248 | Wavy line |
→ | U+2192 | Rightwards arrow |
Creating boxes
Rounded rectangle (most common for diagram nodes):
.───────────.
│ content │
'───────────'
- Use
.for top-left,'for top-right corners - Use
'for bottom-left,.for bottom-right corners - Fill the top/bottom borders with
─ - Fill side borders with
│
Sharp rectangle:
┌───────────┐
│ content │
└───────────┘
Connecting boxes with arrows
Horizontal arrow:
.───. .───.
│ A │────────►│ B │
'───' '───'
Vertical arrow:
.───.
│ A │
'───'
│
▼
.───.
│ B │
'───'
Arrow from specific side:
.───.
│ B │
'───'
▲
│
.───. │
│ A │──────┘
'───'
Adding labels to arrows
Place text next to the arrow line. svgbob automatically positions it:
.───. condition .───.
│ A │══════════════════►│ B │
'───' '───'
.───. false .───.
│ A │──────────────►│ B │
'───' '───'
Multi-line text in boxes
svgbob wraps text naturally based on box width:
.──────────────────.
│ This text will │
│ wrap inside the │
│ box boundaries │
'──────────────────'
Diagonal lines
.───. .───.
│ A │╲ │ C │
'───' ╲ '───'
╲
╲ .───.
╲│ B │
╲'───'
Trees and hierarchies
Binary tree:
.───.
│ R │
'───'
╱ ╲
.───. .───.
│ L │ │ R │
'───' '───'
╱ ╲ ╲
.───. .───. .───.
│LL │ │LR │ │RR │
'───' '───' '───'
Indented hierarchy (no lines):
.───────────────.
│ Root │
│ ├── Child A │
│ ├── Child B │
│ └── Child C │
'───────────────'
Grids and matrices
.──────┬──────┬──────.
│ a │ b │ c │
├──────┼──────┼──────┤
│ d │ e │ f │
├──────┼──────┼──────┤
│ g │ h │ i │
'──────┴──────┴──────'
Comparison layouts
Side-by-side:
.─────────────. .─────────────.
│ Before │ │ After │
│ .───────. │ │ .───────. │
│ │ Old │ │ │ │ New │ │
│ '───────' │ │ '───────' │
'─────────────' '─────────────'
Before → After (sequential):
.───────────────. .───────────────.
│ Slow path │────►│ Fast path │
│ O(n²) │ │ O(n log n) │
'───────────────' '───────────────'
Database / Table schemas
.──────────────────────.
│ Table: users │
├──────────────────────┤
│ id INTEGER │──► PK
│ name TEXT │
│ email TEXT │──► UNIQUE
│ created_at DATETIME │
'──────────────────────'
Flowchart with decision
.───────────.
│ Start │
'─────┬─────'
│
▼
.───────────.
│ Process │
'─────┬─────'
│
▼
.───────────. yes .───────────.
│ Check? │─────────►│ Option A │
'─────┬─────' '───────────'
│
│ no
▼
.───────────.
│ Option B │
'───────────'
Layered architecture
.───────────────────────────.
│ Application Layer │
├───────────────────────────┤
│ Business Logic Layer │
├───────────────────────────┤
│ Data Access Layer │
├───────────────────────────┤
│ Database │
'───────────────────────────'
Timing / sequence diagram (simple)
Client───┬──────────────────┐
│ Request │
│─────────────────►│
│ │
│◄─────────────────│
│ Response │
│ │
Best practices for readable ASCII art
-
Use a monospace font in your editor — the diagram must align properly in source code
-
Tab width = 8 — or use spaces only. Tabs in ASCII art often break alignment
-
Pad box contents — leave at least one space on each side of text inside boxes:
✅ .──────. ❌ .──────. │ ok │ │bad │ '──────' '──────' -
Align arrow connectors — arrow lines should point exactly at the middle of the target box side
-
Test widths — keep within 60-70 characters to avoid horizontal scroll in
cargo doc -
Escape
[brackets in text — rustdoc interprets[text]as intra-doc links. Inside```svgbobblocks this is less of an issue because svgbob code fences are inside thetransform!()macro which outputs a string, but be aware that bracket content may be parsed depending on context -
Preview in the svgbob editor — use https://ivanceras.github.io/svgbob-editor/ before committing
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Using ` | instead of│` | Thin ASCII pipe rendered instead of solid line |
Using - instead of ─ | Dashed line instead of solid | Use Unicode ─ (U+2500) |
Forgetting /// prefix | Macro doesn't parse the lines | Every line must start with /// |
Missing cfg_attr | svgbobdoc becomes required dep | Always wrap in cfg_attr(feature = "...", ...) |
| Arrow not touching box | Gap in the diagram | Extend the line to touch the box border |
| Mixing tabs and spaces | Misaligned rendering | Use spaces only for ASCII art |
| Trailing spaces trimmed | Unexpected gaps | Preserve trailing spaces for alignment |
Domain-specific diagram templates
Sequence Generators (LDS)
| Concept | Template |
|---|---|
| Radical inverse | Digit reversal: n → base-b digits → reverse → fraction |
| VdC → Circle | φ(n) → θ = 2π·φ → (cos θ, sin θ) |
| VdC → Disk | (φ₂, φ₃) → (θ, r) → (r·cosθ, r·sinθ) |
| VdC → Sphere | (φ₂, φ₃) → (z, θ) → cyl. proj. → (x, y, z) |
| Hopf fibration | (φ₂, φ₃, φ₅) → (η, ψ, φ) → S³ point |
Diagram: Two VdC values mapping to a 2D point
.───────────. θ = 2π·φ₂(n)
│ φ₂(n) │───► angle θ
'───────────'
.───────────. r = √φ₃(n) .───────────────.
│ φ₃(n) │───► radius r ──────►│ (r·cosθ, │
'───────────' │ r·sinθ) │
'───────────────'
Data Flow / Pipeline
| Concept | Template |
|---|---|
| Simple pipe | Input ──► Process ──► Output |
| Branching | Input ──► [A ──► OutA, B ──► OutB] |
| Merge | [A, B] ──► Merge ──► Output |
| Feedback | Process ──► Output ──► (feedback) ──► Input |
.──────────. .───────────. .──────────.
│ Source │───►│ Transform │───►│ Sink │
'──────────' '───────────' '──────────'
Geometric Transformations
| Concept | Template |
|---|---|
| Rotation | (x, y) ──► θ ──► (x·cosθ - y·sinθ, x·sinθ + y·cosθ) |
| Scaling | (x, y) ──► s ──► (s·x, s·y) |
| Translation | (x, y) ──► (dx, dy) ──► (x+dx, y+dy) |
| Projection | (x, y, z) ──► perspective ──► (x', y') |
.───────────. .───────────────.
│ (x, y) │────────►│ (x', y') │
'───────────' Rotate '──────────────'
Tree Structures
| Concept | Template |
|---|---|
| Binary tree | Root with left/right children |
| Quad tree | Subdivided rectangle |
| Hierarchy | Parent with children list |
.───.
│ R │
'───'
╱ ╲
.───. .───.
│ A │ │ B │
'───' '───'
╱ ╲ ╲
.───. .───. .───.
│C │ │D │ │E │
'───' '───' '───'
Comparison / Side-by-Side
.────────────────────.
│ Before │
│ .───────────. │
│ │ slow │ │
│ '───────────' │
'────────────────────'
│
▼
.────────────────────.
│ After │
│ .───────────. │
│ │ fast! │ │
│ '───────────' │
'────────────────────'
State Machine
.───────────. event A .───────────.
│ State 1 │───────────►│ State 2 │
'───────────' '───────────'
▲ │
│ event B │
└────────────────────────┘
Crate-Level Diagram Gallery
When adding multiple diagrams to a project, organize them in a table in the crate-level docs:
//! | Type | Diagram |
//! |------|---------|
//! | [`VdCorput`] | Digit reversal flow |
//! | [`Circle`] | Angle mapping to S¹ |
//! | [`Disk`] | Polar mapping to D² |
Each diagram is added with its own #[cfg_attr(feature = "doc-images", doc = svgbobdoc::transform!(...))].
CRITICAL RULES
Rule 1: Never destroy existing documentation
Always append diagrams to existing doc comments. Never delete existing doc text, examples, or section headers. Insert the #[cfg_attr(feature = "doc-images", doc = svgbobdoc::transform!(...))] after the existing /// doc comment block and before the #[derive(...)] or item definition.
Rule 2: Use ```svgbob code fences
The transform!() macro looks for ```svgbob blocks to convert to SVG. Regular ```text or ``` blocks are NOT converted. The language hint must be svgbob:
✅ Correct:
/// ```svgbob
/// .─.
/// │A│
/// '─'
/// ```
❌ Will NOT be processed:
/// ```text
/// .─.
/// │A│
/// '─'
/// ```
Rule 3: Feature-gate ALL transform!() calls
Every svgbobdoc::transform!(...) must be wrapped in:
#[cfg_attr(feature = "doc-images", doc = svgbobdoc::transform!(
/// ```svgbob
/// ...
/// ```
))]
Without the cfg_attr, the proc macro runs on every build, forcing svgbobdoc to be a non-optional dependency.
Rule 4: Preserve /// prefix inside transform!()
The transform!() macro parses /// as doc comment lines. Each line inside the macro must start with ///:
✅ Correct:
#[doc = svgbobdoc::transform!(
/// ```svgbob
/// diagram
/// ```
)]
❌ Wrong — missing /// prefix:
#[doc = svgbobdoc::transform!(
"``svgbob
diagram
``"
)]
Rule 5: Match doc-images feature name to Cargo.toml
The feature name in cfg_attr must match the feature defined in Cargo.toml. If your project uses a different feature name (e.g., svg-docs), use that instead:
[features]
svg-docs = ["dep:svgbobdoc"]
#[cfg_attr(feature = "svg-docs", doc = svgbobdoc::transform!(...))]
Rule 6: Use Unicode box-drawing characters
svgbob renders Unicode box-drawing characters (─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼ ╱ ╲ ◄ ► ▼ ▲ ● ○) into proper SVG lines and shapes. Use these characters rather than ASCII approximations:
| Good (Unicode) | Bad (ASCII) |
|---|---|
───► | ---> |
.───. | .---. |
│ text │ | ` |
'───' | '---' |
Rule 7: Test the diagram before embedding
Use the svgbob editor to preview your ASCII art before adding it to source code. This catches rendering issues early.
Rule 8: Keep diagrams simple
A good svgbob diagram should be:
- ≤ 25 lines tall (fits on one screen)
- ≤ 60 characters wide (fits in standard doc width)
- ≤ 15 elements (boxes, arrows, labels)
- Self-explanatory with minimal labels
If your diagram is more complex, consider splitting it into multiple smaller diagrams or using embed-doc-image with an external SVG.
Rule 9: Verify both feature states
cargo build # Must pass
cargo build --features doc-images # Must pass
cargo doc --features doc-images --no-deps # Zero warnings
Rule 10: Coordinate with embed-doc-image
If the project also uses embed-doc-image:
- Both crates can share the same
doc-imagesfeature svgbobdochandles structural/flow diagramsembed-doc-imagehandles real plots and screenshots- Both produce
data:URIs that render seamlessly together