chore(skills): add d2-diagram skill for text-to-diagram generation
Wraps d2 CLI (v0.7.1) with project defaults: theme 200 (Dark Mauve), dagre layout, PNG output. Includes render/validate/batch scripts, syntax guide, and five category templates (architecture, entity, data-flow, state, UI flow). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
---
|
||||
name: d2-diagram
|
||||
description: >
|
||||
Generate technical diagrams using d2 (text-to-diagram CLI). Use when the
|
||||
user says "create a diagram", "draw architecture", "make a flowchart",
|
||||
"diagram this", "render d2", "d2", "data flow diagram", "entity relationship",
|
||||
"state machine", "sequence diagram", "UI flow", or invokes /d2-diagram.
|
||||
Produces .d2 source files and renders them to PNG. Also use when asked
|
||||
to update, re-render, or batch render existing diagrams.
|
||||
---
|
||||
|
||||
# d2 Diagram Generation
|
||||
|
||||
Generate technical diagrams from text using d2 (v0.7.1). Pure CLI, no
|
||||
external dependencies beyond the d2 binary.
|
||||
|
||||
**Binary:** `/home/linuxbrew/.linuxbrew/bin/d2`
|
||||
|
||||
## Project Defaults
|
||||
|
||||
| Setting | Value | Override |
|
||||
|---------|-------|----------|
|
||||
| Theme | 200 (Dark Mauve) | `--theme N` |
|
||||
| Layout | dagre | `--layout elk` |
|
||||
| Padding | 100px | — |
|
||||
| Format | PNG | `--svg` |
|
||||
|
||||
## Output Convention
|
||||
|
||||
```
|
||||
docs/diagrams/
|
||||
architecture/ # System architecture, IPC, component layout
|
||||
data-flow/ # Sequence diagrams, data pipelines
|
||||
entity/ # ER diagrams, ECS component schemas
|
||||
state/ # State machines, behavior trees
|
||||
ui/ # UI navigation flow, screen transitions
|
||||
```
|
||||
|
||||
Both `.d2` source and `.png` output are tracked in git.
|
||||
|
||||
## Single Diagram Workflow
|
||||
|
||||
1. **Determine category** — architecture, data-flow, entity, state, or ui
|
||||
2. **Read template** — `references/diagram-templates.md` for the matching category
|
||||
3. **Read syntax** — `references/d2-syntax-guide.md` if unfamiliar with d2 syntax
|
||||
4. **Write .d2 source** — to `docs/diagrams/{category}/{name}.d2`
|
||||
5. **Validate** — `.claude/skills/d2-diagram/scripts/d2-render.sh validate {file}`
|
||||
6. **Render** — `.claude/skills/d2-diagram/scripts/d2-render.sh {file}`
|
||||
7. **Read SVG** — verify the output, present to user
|
||||
|
||||
### Script Usage
|
||||
|
||||
```bash
|
||||
# Render with project defaults
|
||||
.claude/skills/d2-diagram/scripts/d2-render.sh docs/diagrams/architecture/ipc-bridge.d2
|
||||
|
||||
# Validate syntax only
|
||||
.claude/skills/d2-diagram/scripts/d2-render.sh validate docs/diagrams/architecture/ipc-bridge.d2
|
||||
|
||||
# Auto-format source
|
||||
.claude/skills/d2-diagram/scripts/d2-render.sh fmt docs/diagrams/architecture/ipc-bridge.d2
|
||||
|
||||
# Sketch mode (hand-drawn look for drafts)
|
||||
.claude/skills/d2-diagram/scripts/d2-render.sh docs/diagrams/ui/flow.d2 --sketch
|
||||
|
||||
# Light theme (for printable docs)
|
||||
.claude/skills/d2-diagram/scripts/d2-render.sh docs/diagrams/entity/schema.d2 --theme 0
|
||||
|
||||
# SVG output (if specifically needed)
|
||||
.claude/skills/d2-diagram/scripts/d2-render.sh docs/diagrams/architecture/overview.d2 --svg
|
||||
```
|
||||
|
||||
## Batch Render
|
||||
|
||||
Re-render all diagrams after theme or style changes:
|
||||
|
||||
```bash
|
||||
# All diagrams
|
||||
.claude/skills/d2-diagram/scripts/d2-batch.sh
|
||||
|
||||
# One category
|
||||
.claude/skills/d2-diagram/scripts/d2-batch.sh docs/diagrams/architecture/
|
||||
|
||||
# Preview what would render
|
||||
.claude/skills/d2-diagram/scripts/d2-batch.sh --dry-run
|
||||
|
||||
# Force re-render everything
|
||||
.claude/skills/d2-diagram/scripts/d2-batch.sh --force
|
||||
```
|
||||
|
||||
Batch skips files whose PNG is newer than the `.d2` source unless `--force`.
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Variables for consistent styling
|
||||
|
||||
```d2
|
||||
vars: {
|
||||
color-bg: "#2a3040"
|
||||
color-stroke: "#333340"
|
||||
color-text: "#c8d0e0"
|
||||
color-accent: "#c8d8f0"
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-board (layers)
|
||||
|
||||
```d2
|
||||
# Base diagram here
|
||||
|
||||
layers: {
|
||||
detailed: {
|
||||
# More detailed view
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sequence diagrams
|
||||
|
||||
```d2
|
||||
shape: sequence_diagram
|
||||
client: Godot Client
|
||||
server: Rust Server
|
||||
|
||||
client -> server: TickRequest(delta)
|
||||
server -> client: WorldState(entities)
|
||||
```
|
||||
|
||||
### Imports
|
||||
|
||||
Split shared definitions into a separate file and import:
|
||||
|
||||
```d2
|
||||
...@shared-defs.d2
|
||||
```
|
||||
|
||||
## Agent Guidance
|
||||
|
||||
- **Qatux** — Architecture decision records, system overview diagrams, data
|
||||
schemas. Prefer architecture and entity templates.
|
||||
- **Tyre** — IPC bridge, ECS system flow, chunk loading pipeline, perception
|
||||
system data flow. Prefer architecture and data-flow templates.
|
||||
- **Araminta** — UI navigation flow, screen transitions, component hierarchy.
|
||||
Prefer UI flow template.
|
||||
|
||||
## References
|
||||
|
||||
- `references/d2-syntax-guide.md` — Language quick reference (shapes, edges,
|
||||
containers, styling, variables). Read when unfamiliar with d2 syntax.
|
||||
- `references/diagram-templates.md` — Five category templates with complete
|
||||
d2 source examples. Read when starting a new diagram.
|
||||
@@ -0,0 +1,212 @@
|
||||
# D2 Language Quick Reference
|
||||
|
||||
## Nodes
|
||||
|
||||
```d2
|
||||
server # Implicit label from key
|
||||
server: Simulation Server # Explicit label
|
||||
server: Simulation Server { # With properties
|
||||
shape: hexagon
|
||||
style.fill: "#2d3436"
|
||||
}
|
||||
```
|
||||
|
||||
## Edges
|
||||
|
||||
```d2
|
||||
a -> b # Directed
|
||||
a <- b # Reverse directed
|
||||
a <-> b # Bidirectional
|
||||
a -- b # Undirected
|
||||
a -> b: "label" # Labeled edge
|
||||
a -> b -> c # Chained
|
||||
```
|
||||
|
||||
## Containers (nesting)
|
||||
|
||||
```d2
|
||||
infrastructure: {
|
||||
server: Simulation Server
|
||||
database: State Store {
|
||||
shape: cylinder
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Shapes
|
||||
|
||||
| Shape | Use for |
|
||||
|-------|---------|
|
||||
| `rectangle` | Default. Components, modules, generic. |
|
||||
| `hexagon` | Systems, services, major components. |
|
||||
| `cylinder` | Databases, storage, persistent state. |
|
||||
| `diamond` | Decisions, conditions, branch points. |
|
||||
| `oval` / `circle` | Start/end states, events. |
|
||||
| `cloud` | External systems, networks. |
|
||||
| `person` | Actors, users, NPCs. |
|
||||
| `queue` | Message queues, buffers. |
|
||||
| `page` | Documents, files. |
|
||||
| `package` | Packages, modules, crates. |
|
||||
| `sql_table` | Database tables, ECS component schemas. |
|
||||
| `class` | Class diagrams, ECS system definitions. |
|
||||
| `code` | Code blocks (set `language` property). |
|
||||
| `markdown` | Rich text blocks. |
|
||||
|
||||
## SQL Tables
|
||||
|
||||
```d2
|
||||
entity: {
|
||||
shape: sql_table
|
||||
id: u64 {constraint: primary_key}
|
||||
name: String
|
||||
position: Vec2
|
||||
faction_id: u64 {constraint: foreign_key}
|
||||
}
|
||||
```
|
||||
|
||||
## Class Diagrams
|
||||
|
||||
```d2
|
||||
perception_system: {
|
||||
shape: class
|
||||
+run(world: &mut World)
|
||||
-calculate_los(entity: Entity): HashSet<Entity>
|
||||
#update_knowledge(entity: Entity, seen: HashSet<Entity>)
|
||||
}
|
||||
```
|
||||
|
||||
## Sequence Diagrams
|
||||
|
||||
```d2
|
||||
shape: sequence_diagram
|
||||
client: Godot Client
|
||||
server: Rust Server
|
||||
|
||||
client -> server: TickRequest(delta)
|
||||
server -> server: run ECS systems
|
||||
server -> client: WorldState(entities)
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
```d2
|
||||
node: Label {
|
||||
style: {
|
||||
fill: "#2d3436"
|
||||
stroke: "#333340"
|
||||
stroke-width: 2
|
||||
stroke-dash: 5 # Dashed line
|
||||
opacity: 0.8
|
||||
font-size: 14
|
||||
font-color: "#c8d0e0"
|
||||
bold: true
|
||||
italic: false
|
||||
border-radius: 4
|
||||
shadow: true
|
||||
3d: true # Rectangles only
|
||||
multiple: true # Stacked appearance
|
||||
double-border: true # Rectangles/ovals only
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Edge styling
|
||||
|
||||
```d2
|
||||
a -> b: {
|
||||
style: {
|
||||
stroke: "#c8d8f0"
|
||||
stroke-width: 2
|
||||
stroke-dash: 5
|
||||
opacity: 0.8
|
||||
animated: true # Animated flow
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
```d2
|
||||
vars: {
|
||||
color-bg: "#1a1e24"
|
||||
color-stroke: "#333340"
|
||||
color-text: "#c8d0e0"
|
||||
color-accent: "#c8d8f0"
|
||||
}
|
||||
|
||||
node: {
|
||||
style.fill: ${color-bg}
|
||||
style.stroke: ${color-stroke}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
```
|
||||
|
||||
## Direction
|
||||
|
||||
```d2
|
||||
direction: right # left-to-right (default for dagre)
|
||||
direction: down # top-to-bottom
|
||||
direction: up
|
||||
direction: left
|
||||
```
|
||||
|
||||
## Imports
|
||||
|
||||
```d2
|
||||
...@shared-defs.d2 # Spread import (inline all definitions)
|
||||
```
|
||||
|
||||
## Icons
|
||||
|
||||
```d2
|
||||
node: Label {
|
||||
icon: https://icons.terrastruct.com/essentials/time.svg
|
||||
}
|
||||
```
|
||||
|
||||
## Layers (multi-board)
|
||||
|
||||
```d2
|
||||
# Base diagram content here
|
||||
|
||||
layers: {
|
||||
detailed: {
|
||||
# More detailed view
|
||||
}
|
||||
simplified: {
|
||||
# Simplified overview
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Scenarios (animated transitions)
|
||||
|
||||
```d2
|
||||
# Base state
|
||||
|
||||
scenarios: {
|
||||
alert: {
|
||||
# Changes from base for alert state
|
||||
}
|
||||
combat: {
|
||||
# Changes from base for combat state
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
```d2
|
||||
# This is a comment
|
||||
node: Label # Inline comment
|
||||
```
|
||||
|
||||
## Project Colors (from visual-grammar-v01.md)
|
||||
|
||||
| Constant | Hex | Usage |
|
||||
|----------|-----|-------|
|
||||
| Zone 1 floor | `#1a1e24` | Dark backgrounds |
|
||||
| Zone 1 wall | `#2a3040` | Component fill |
|
||||
| Outline standard | `#333340` | Borders, strokes |
|
||||
| Insert chrome | `#c8d0e0` | Text, labels |
|
||||
| Zone 1 fixture | `#c8d8f0` | Accents, highlights |
|
||||
@@ -0,0 +1,251 @@
|
||||
# Diagram Templates
|
||||
|
||||
Copy, adapt, and render. Each template uses project colors from visual-grammar-v01.md.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture Diagram
|
||||
|
||||
System components, relationships, communication channels.
|
||||
|
||||
**When to use:** IPC bridge, perception pipeline, chunk loading, ECS system layout, client-server architecture.
|
||||
|
||||
**Agents:** Tyre (system architecture), Qatux (architecture decision records).
|
||||
|
||||
```d2
|
||||
vars: {
|
||||
color-bg: "#2a3040"
|
||||
color-stroke: "#333340"
|
||||
color-text: "#c8d0e0"
|
||||
color-accent: "#c8d8f0"
|
||||
}
|
||||
|
||||
direction: right
|
||||
|
||||
client: Godot Client {
|
||||
shape: hexagon
|
||||
style.fill: ${color-bg}
|
||||
style.font-color: ${color-text}
|
||||
|
||||
rendering: Rendering {
|
||||
style.fill: ${color-bg}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
ui: UI Layer {
|
||||
style.fill: ${color-bg}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
bridge: IPC Bridge {
|
||||
style.fill: ${color-bg}
|
||||
style.font-color: ${color-text}
|
||||
style.stroke: ${color-accent}
|
||||
}
|
||||
}
|
||||
|
||||
server: Rust Server {
|
||||
shape: hexagon
|
||||
style.fill: ${color-bg}
|
||||
style.font-color: ${color-text}
|
||||
|
||||
ecs: bevy_ecs {
|
||||
style.fill: ${color-bg}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
perception: Perception {
|
||||
style.fill: ${color-bg}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
bridge: IPC Bridge {
|
||||
style.fill: ${color-bg}
|
||||
style.font-color: ${color-text}
|
||||
style.stroke: ${color-accent}
|
||||
}
|
||||
}
|
||||
|
||||
client.bridge -> server.bridge: "stdin/stdout" {
|
||||
style.stroke: ${color-accent}
|
||||
style.stroke-dash: 5
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Entity Relationship
|
||||
|
||||
Data schemas, ECS components, knowledge graph structure.
|
||||
|
||||
**When to use:** Database tables, component definitions, entity relationships, knowledge store schema.
|
||||
|
||||
**Agents:** Tyre (ECS component design), Qatux (schema documentation).
|
||||
|
||||
```d2
|
||||
entity: Entity {
|
||||
shape: sql_table
|
||||
id: u64 {constraint: primary_key}
|
||||
name: String
|
||||
faction_id: u64 {constraint: foreign_key}
|
||||
}
|
||||
|
||||
position: Position {
|
||||
shape: sql_table
|
||||
entity_id: u64 {constraint: foreign_key}
|
||||
x: f32
|
||||
y: f32
|
||||
chunk_id: u32
|
||||
}
|
||||
|
||||
knowledge: KnowledgeEntry {
|
||||
shape: sql_table
|
||||
observer_id: u64 {constraint: foreign_key}
|
||||
subject_id: u64 {constraint: foreign_key}
|
||||
fact_type: FactType
|
||||
confidence: f32
|
||||
last_seen_tick: u64
|
||||
}
|
||||
|
||||
entity.id -> position.entity_id
|
||||
entity.id -> knowledge.observer_id
|
||||
entity.id -> knowledge.subject_id
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Sequence / Data Flow
|
||||
|
||||
Ordered interactions between systems over time.
|
||||
|
||||
**When to use:** IPC message flow, tick processing, perception update cycle, dialogue system exchanges.
|
||||
|
||||
**Agents:** Tyre (system interaction design), Qatux (protocol documentation).
|
||||
|
||||
```d2
|
||||
shape: sequence_diagram
|
||||
|
||||
client: Godot Client
|
||||
bridge: IPC Bridge
|
||||
server: Rust Server
|
||||
ecs: ECS Systems
|
||||
|
||||
client -> bridge: TickRequest(delta, input)
|
||||
bridge -> server: deserialize + dispatch
|
||||
server -> ecs: run_systems(delta)
|
||||
ecs -> ecs: perception, AI, physics
|
||||
ecs -> server: collect WorldState
|
||||
server -> bridge: serialize WorldState
|
||||
bridge -> client: WorldState(entities, events)
|
||||
client -> client: update rendering
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. State Machine
|
||||
|
||||
Entity states, transitions, conditions.
|
||||
|
||||
**When to use:** NPC behavior states, game mode transitions, dialogue state, investigation phases.
|
||||
|
||||
**Agents:** Tyre (behavior system design), Qatux (state documentation).
|
||||
|
||||
```d2
|
||||
vars: {
|
||||
color-state: "#2a3040"
|
||||
color-text: "#c8d0e0"
|
||||
color-edge: "#c8d8f0"
|
||||
color-decision: "#333340"
|
||||
}
|
||||
|
||||
idle: Idle {
|
||||
style.fill: ${color-state}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
|
||||
alert: Alert {
|
||||
style.fill: ${color-state}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
|
||||
investigate: Investigate {
|
||||
style.fill: ${color-state}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
|
||||
combat: Combat {
|
||||
style.fill: ${color-state}
|
||||
style.font-color: ${color-text}
|
||||
style.stroke: "#f0b840"
|
||||
}
|
||||
|
||||
flee: Flee {
|
||||
style.fill: ${color-state}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
|
||||
idle -> alert: "stimulus detected" { style.stroke: ${color-edge} }
|
||||
alert -> investigate: "stimulus confirmed" { style.stroke: ${color-edge} }
|
||||
alert -> idle: "timeout / stimulus lost" { style.stroke: ${color-edge}; style.stroke-dash: 5 }
|
||||
investigate -> combat: "threat confirmed" { style.stroke: "#f0b840" }
|
||||
investigate -> idle: "nothing found" { style.stroke: ${color-edge}; style.stroke-dash: 5 }
|
||||
combat -> flee: "health < threshold" { style.stroke: "#f0b840" }
|
||||
combat -> idle: "threat eliminated" { style.stroke: ${color-edge}; style.stroke-dash: 5 }
|
||||
flee -> idle: "safe distance reached" { style.stroke: ${color-edge}; style.stroke-dash: 5 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. UI Flow
|
||||
|
||||
Screen navigation, component hierarchy, interaction paths.
|
||||
|
||||
**When to use:** HUD layout relationships, menu navigation, dialogue flow, insert mode transitions.
|
||||
|
||||
**Agents:** Araminta (UI/visual design), Tyre (interface architecture), Qatux (UI documentation).
|
||||
|
||||
```d2
|
||||
vars: {
|
||||
color-screen: "#1a1e24"
|
||||
color-panel: "#2a3040"
|
||||
color-text: "#c8d0e0"
|
||||
color-nav: "#c8d8f0"
|
||||
}
|
||||
|
||||
gameplay: Gameplay {
|
||||
style.fill: ${color-screen}
|
||||
style.font-color: ${color-text}
|
||||
|
||||
hud: HUD {
|
||||
style.fill: ${color-panel}
|
||||
style.font-color: ${color-text}
|
||||
|
||||
minimap: Minimap
|
||||
monologue: Monologue Panel
|
||||
insert_display: Insert Display
|
||||
}
|
||||
|
||||
world: World View {
|
||||
style.fill: ${color-panel}
|
||||
style.font-color: ${color-text}
|
||||
}
|
||||
}
|
||||
|
||||
pause: Pause Menu {
|
||||
style.fill: ${color-screen}
|
||||
style.font-color: ${color-text}
|
||||
|
||||
inventory: Inventory
|
||||
journal: Journal
|
||||
settings: Settings
|
||||
}
|
||||
|
||||
dialogue: Dialogue Mode {
|
||||
style.fill: ${color-screen}
|
||||
style.font-color: ${color-text}
|
||||
|
||||
speaker: Speaker Panel
|
||||
responses: Response List
|
||||
}
|
||||
|
||||
gameplay -> pause: "ESC" { style.stroke: ${color-nav} }
|
||||
pause -> gameplay: "ESC / Resume" { style.stroke: ${color-nav}; style.stroke-dash: 5 }
|
||||
gameplay -> dialogue: "interact with NPC" { style.stroke: ${color-nav} }
|
||||
dialogue -> gameplay: "end conversation" { style.stroke: ${color-nav}; style.stroke-dash: 5 }
|
||||
```
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR" && git rev-parse --show-toplevel)"
|
||||
RENDER="$SCRIPT_DIR/d2-render.sh"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [directory] [options]
|
||||
|
||||
Batch render all .d2 files in a directory. Skips files whose PNG is
|
||||
newer than the source unless --force is used.
|
||||
|
||||
Options:
|
||||
--dry-run List files that would be rendered
|
||||
--force Re-render even if SVG is up to date
|
||||
--theme N Override theme for all files
|
||||
|
||||
Examples:
|
||||
$(basename "$0") # All in docs/diagrams/
|
||||
$(basename "$0") docs/diagrams/architecture/ # One category
|
||||
$(basename "$0") --dry-run # Preview
|
||||
$(basename "$0") --force # Re-render everything
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
DIR="$REPO_ROOT/docs/diagrams"
|
||||
DRY_RUN=false
|
||||
FORCE=false
|
||||
EXTRA_ARGS=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--force) FORCE=true; shift ;;
|
||||
--theme) EXTRA_ARGS+=(--theme "$2"); shift 2 ;;
|
||||
--help|-h) usage ;;
|
||||
*)
|
||||
if [[ -d "$1" ]] || [[ -d "$REPO_ROOT/$1" ]]; then
|
||||
DIR="$1"
|
||||
[[ "$DIR" != /* ]] && DIR="$REPO_ROOT/$DIR"
|
||||
else
|
||||
echo "Unknown option or directory: $1" >&2; exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ ! -d "$DIR" ]] && { echo "ERROR: Directory not found: $DIR" >&2; exit 1; }
|
||||
|
||||
RENDERED=0
|
||||
SKIPPED=0
|
||||
FAILED=0
|
||||
|
||||
while IFS= read -r -d '' d2_file; do
|
||||
png_file="${d2_file%.d2}.png"
|
||||
|
||||
# Skip if PNG is newer than source (unless --force)
|
||||
if [[ "$FORCE" != true ]] && [[ -f "$png_file" ]] && [[ "$png_file" -nt "$d2_file" ]]; then
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
rel_path="${d2_file#"$REPO_ROOT/"}"
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
echo "Would render: $rel_path"
|
||||
RENDERED=$((RENDERED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
if "$RENDER" "$d2_file" "${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}"; then
|
||||
RENDERED=$((RENDERED + 1))
|
||||
else
|
||||
echo "FAILED: $rel_path" >&2
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
done < <(find "$DIR" -name '*.d2' -print0 | sort -z)
|
||||
|
||||
echo ""
|
||||
echo "Batch complete: $RENDERED rendered, $SKIPPED skipped (up to date), $FAILED failed"
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
D2="/home/linuxbrew/.linuxbrew/bin/d2"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR" && git rev-parse --show-toplevel)"
|
||||
|
||||
DEFAULT_THEME=200
|
||||
DEFAULT_LAYOUT="dagre"
|
||||
DEFAULT_PAD=100
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [validate|fmt] <file.d2> [options]
|
||||
|
||||
Render a .d2 file to PNG with project defaults (theme $DEFAULT_THEME, $DEFAULT_LAYOUT layout).
|
||||
|
||||
Commands:
|
||||
validate <file> Check syntax without rendering
|
||||
fmt <file> Auto-format in place
|
||||
|
||||
Options:
|
||||
--theme N Override theme (default: $DEFAULT_THEME)
|
||||
--layout NAME Override layout engine (default: $DEFAULT_LAYOUT)
|
||||
--sketch Enable hand-drawn sketch mode
|
||||
--output PATH Override output path (default: input with .png extension)
|
||||
--svg Render to SVG instead of PNG
|
||||
|
||||
Examples:
|
||||
$(basename "$0") docs/diagrams/architecture/ipc-bridge.d2
|
||||
$(basename "$0") validate docs/diagrams/architecture/ipc-bridge.d2
|
||||
$(basename "$0") docs/diagrams/architecture/ipc-bridge.d2 --sketch --theme 0
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -lt 1 ]] && usage
|
||||
|
||||
# Parse subcommand
|
||||
SUBCMD=""
|
||||
case "$1" in
|
||||
validate|fmt)
|
||||
SUBCMD="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ $# -lt 1 ]] && usage
|
||||
|
||||
INPUT="$1"
|
||||
shift
|
||||
|
||||
# Resolve to absolute path
|
||||
[[ "$INPUT" != /* ]] && INPUT="$REPO_ROOT/$INPUT"
|
||||
|
||||
[[ ! -f "$INPUT" ]] && { echo "ERROR: File not found: $INPUT" >&2; exit 1; }
|
||||
|
||||
# Handle subcommands
|
||||
if [[ -n "$SUBCMD" ]]; then
|
||||
"$D2" "$SUBCMD" "$INPUT"
|
||||
echo "OK: $SUBCMD $INPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse render options
|
||||
THEME="$DEFAULT_THEME"
|
||||
LAYOUT="$DEFAULT_LAYOUT"
|
||||
SKETCH=""
|
||||
OUTPUT=""
|
||||
FORMAT="png"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--theme) THEME="$2"; shift 2 ;;
|
||||
--layout) LAYOUT="$2"; shift 2 ;;
|
||||
--sketch) SKETCH="-s"; shift ;;
|
||||
--output) OUTPUT="$2"; shift 2 ;;
|
||||
--svg) FORMAT="svg"; shift ;;
|
||||
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Derive output path
|
||||
if [[ -z "$OUTPUT" ]]; then
|
||||
OUTPUT="${INPUT%.d2}.$FORMAT"
|
||||
fi
|
||||
|
||||
# Render
|
||||
"$D2" -t "$THEME" -l "$LAYOUT" --pad "$DEFAULT_PAD" $SKETCH "$INPUT" "$OUTPUT"
|
||||
|
||||
SIZE=$(stat --printf="%s" "$OUTPUT" 2>/dev/null || stat -f%z "$OUTPUT" 2>/dev/null)
|
||||
echo "Rendered: $OUTPUT ($(( SIZE / 1024 ))KB)"
|
||||
Reference in New Issue
Block a user