graphomaton 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +74 -8
- data/README.md +426 -44
- data/SECURITY.md +47 -0
- data/docs/architecture.md +30 -0
- data/docs/cli.md +27 -0
- data/docs/custom-exporters.md +36 -0
- data/docs/exporters.md +17 -0
- data/docs/input-schema.md +26 -0
- data/docs/migration-1.1.md +19 -0
- data/docs/performance.md +19 -0
- data/docs/releasing.md +19 -0
- data/exe/graphomaton +9 -0
- data/lib/graphomaton/atomic_file.rb +26 -0
- data/lib/graphomaton/cli/config.rb +102 -0
- data/lib/graphomaton/cli.rb +841 -0
- data/lib/graphomaton/errors.rb +11 -0
- data/lib/graphomaton/exporter_registry.rb +127 -0
- data/lib/graphomaton/exporters/dot.rb +255 -18
- data/lib/graphomaton/exporters/mermaid.rb +705 -25
- data/lib/graphomaton/exporters/pdf.rb +131 -0
- data/lib/graphomaton/exporters/plantuml.rb +250 -13
- data/lib/graphomaton/exporters/png.rb +172 -0
- data/lib/graphomaton/exporters/svg.rb +2775 -231
- data/lib/graphomaton/exporters/webp.rb +185 -0
- data/lib/graphomaton/exporters.rb +11 -4
- data/lib/graphomaton/identifier_allocator.rb +33 -0
- data/lib/graphomaton/input_policy.rb +82 -0
- data/lib/graphomaton/layout/force_tree.rb +127 -0
- data/lib/graphomaton/model.rb +218 -0
- data/lib/graphomaton/process_runner.rb +154 -0
- data/lib/graphomaton/url_policy.rb +40 -0
- data/lib/graphomaton/version.rb +1 -1
- data/lib/graphomaton.rb +2869 -54
- data/sig/graphomaton.rbs +127 -0
- metadata +34 -24
- data/.codespellignore +0 -0
- data/.rspec +0 -1
- data/CODE_OF_CONDUCT.md +0 -132
- data/Rakefile +0 -8
- data/sample/basic.rb +0 -30
- data/sample/complex.rb +0 -32
- data/sample/long_names.rb +0 -20
- data/sample/nfa.rb +0 -28
- data/sample/skip_states.rb +0 -23
- data/spec/exporters/dot_spec.rb +0 -146
- data/spec/exporters/mermaid_spec.rb +0 -154
- data/spec/exporters/plantuml_spec.rb +0 -144
- data/spec/exporters/svg_spec.rb +0 -314
- data/spec/graphomaton_edge_cases_spec.rb +0 -322
- data/spec/graphomaton_spec.rb +0 -371
- data/spec/spec_helper.rb +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8992f0a2ab8bf57fc92a7480dc29c39fa94188e952bef61a865f421fde5a9b91
|
|
4
|
+
data.tar.gz: 1f7b78192ad4e692cff03ce8c45bb0cacf33195d7294612ca06a9bf695b409b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7360eaf0c5ce3676e505559b066d429fa1f1df0219e922c2cacc2d62578a4bf94f07ac51d385abdcf61a5c03979def0f98088ac28e7081841722a2f975b39984
|
|
7
|
+
data.tar.gz: d8bacee19d445a50d62449970b435ab5917093da8a27b7df7c41b7d59f8aa46c419de2754ddacbddae67b18bc00060e28f204a5c8a377b95c306b8203cef1abb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,82 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All notable changes to Graphomaton are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
5
|
+
follows [Semantic Versioning](https://semver.org/).
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## [Unreleased]
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- Improve output format for SVG files.
|
|
9
|
+
## [1.1.0] - 2026-08-13
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
### Added
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
- Immutable model records, update/remove APIs, graph revision caches, structured
|
|
14
|
+
labels, diagnostics, validation profiles, exporter capabilities, semantic-loss
|
|
15
|
+
reporting, render results, IO output, and RBS signatures.
|
|
16
|
+
- Obstacle-aware curves, adaptive self-loops, spatial label indexing, Barnes-Hut
|
|
17
|
+
force approximation, deterministic force-layout separation, and convergence
|
|
18
|
+
detection.
|
|
19
|
+
- CLI commands for validation, discovery, diagnostics, health checks, config
|
|
20
|
+
files, shell completions, and man-page output.
|
|
21
|
+
- Bounded stdin/stdout workflows, no-clobber protection, format-specific option
|
|
22
|
+
errors, structured exit statuses, and `--version`.
|
|
23
|
+
- Nonce-based CSP support, trusted local asset inlining for self-contained HTML,
|
|
24
|
+
and deterministic HTML asset loading with localized generated UI.
|
|
25
|
+
- Restricted gem packaging, gem installation smoke tests, renderer integration
|
|
26
|
+
jobs, release automation, and immutable GitHub Actions pins.
|
|
13
27
|
|
|
14
|
-
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Curved and orthogonal edges are routed from geometry instead of insertion
|
|
31
|
+
order, and SVG viewBoxes include rendered paths, shapes, rotated labels, and
|
|
32
|
+
text content.
|
|
33
|
+
- PNG scaling is separated from logical layout and SVG geometry; layout
|
|
34
|
+
diagnostics and metadata survive PNG, PDF, and WebP conversion.
|
|
35
|
+
- SCC analysis is iterative and linear-time. Fixed manual positions remain clear
|
|
36
|
+
of automatic layouts, and dense graphs receive adaptive spacing.
|
|
37
|
+
- Structured transition labels and format-independent pseudostate kinds are kept
|
|
38
|
+
in the model, while custom exporter registration now produces renderable
|
|
39
|
+
exporters and rejects ambiguous schema aliases.
|
|
40
|
+
- HTML pan/zoom controls are more accessible, and text exporters terminate
|
|
41
|
+
output with a newline after normalizing CR/LF label boundaries.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- Corrected force-layout attraction direction, boundary clamping, and analysis
|
|
46
|
+
of undefined transition endpoints.
|
|
47
|
+
- Isolated states are declared in DOT, Mermaid, and PlantUML output.
|
|
48
|
+
- DOT, Mermaid, PlantUML, and Graphviz layout identifiers are collision-free,
|
|
49
|
+
including reserved names, hostile text, mixed-type model IDs, and groups.
|
|
50
|
+
- SVG parallel-transition merging preserves presentation metadata and uses
|
|
51
|
+
tuple endpoint keys; edge endpoints now respect ellipse, diamond, bar, and
|
|
52
|
+
rounded-rectangle boundaries.
|
|
53
|
+
- Input validation now rejects duplicate states, malformed transition tuples,
|
|
54
|
+
conflicting initial states, invalid state hierarchies, ignored value types,
|
|
55
|
+
and non-finite rendering numbers.
|
|
56
|
+
- Partial state upserts preserve omitted coordinates and effective no-op updates
|
|
57
|
+
do not advance the graph revision.
|
|
58
|
+
|
|
59
|
+
### Security
|
|
60
|
+
|
|
61
|
+
- Added URL, HTML JavaScript, theme, and SVG style policies, including safe
|
|
62
|
+
handling of JavaScript strings, trusted local assets, Windows asset paths, and
|
|
63
|
+
unsafe URL schemes; Mermaid.js is pinned to 10.9.8.
|
|
64
|
+
- External processes are bounded by timeout and stdout/stderr limits, with
|
|
65
|
+
portable executable discovery and process termination on Windows.
|
|
66
|
+
- JSON and YAML input, state and transition counts, metadata depth, label size,
|
|
67
|
+
hierarchy depth, and converter resources are bounded.
|
|
68
|
+
- Exported files are written atomically, and embedded scripts enforce size,
|
|
69
|
+
encoding, and digest checks.
|
|
70
|
+
|
|
71
|
+
## [1.0.0] - 2025-12-23
|
|
72
|
+
|
|
73
|
+
- Add support for multiple output styles, including DOT, Mermaid, and PlantUML.
|
|
74
|
+
- Improve SVG output formatting.
|
|
75
|
+
|
|
76
|
+
## [0.1.1] - 2025-08-26
|
|
77
|
+
|
|
78
|
+
- Fix the gemspec dependency declaration for `rexml`.
|
|
79
|
+
|
|
80
|
+
## [0.1.0] - 2025-08-26
|
|
15
81
|
|
|
16
82
|
- Initial release
|
data/README.md
CHANGED
|
@@ -1,105 +1,487 @@
|
|
|
1
1
|
# Graphomaton [](https://badge.fury.io/rb/graphomaton) [](https://github.com/ydah/graphomaton/actions/workflows/ci.yml)
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A Ruby toolkit for validating, analyzing, laying out, and exporting finite state machines as SVG, PNG, PDF, WebP, Mermaid.js HTML, GraphViz DOT, and PlantUML.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
Add this line to your application's Gemfile:
|
|
10
|
-
|
|
11
9
|
```ruby
|
|
12
10
|
gem 'graphomaton'
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
And then execute:
|
|
16
|
-
|
|
17
13
|
```bash
|
|
18
14
|
bundle install
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Or install it yourself as:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
15
|
+
# or
|
|
24
16
|
gem install graphomaton
|
|
25
17
|
```
|
|
26
18
|
|
|
27
|
-
##
|
|
19
|
+
## Quick start
|
|
28
20
|
|
|
29
21
|
```ruby
|
|
30
22
|
require 'graphomaton'
|
|
31
23
|
|
|
32
|
-
# Create a DFA that accepts strings ending with 'ab'
|
|
33
24
|
automaton = Graphomaton.new
|
|
34
|
-
|
|
35
|
-
# Add states
|
|
36
|
-
automaton.add_state('q0')
|
|
25
|
+
automaton.add_state('q0', label: 'Start')
|
|
37
26
|
automaton.add_state('q1')
|
|
38
|
-
automaton.add_state('q2')
|
|
39
|
-
|
|
40
|
-
# Set initial and final states
|
|
27
|
+
automaton.add_state('q2', label: 'Accept')
|
|
41
28
|
automaton.set_initial('q0')
|
|
42
29
|
automaton.add_final('q2')
|
|
43
30
|
|
|
44
|
-
# Add transitions
|
|
45
31
|
automaton.add_transition('q0', 'q1', 'a')
|
|
46
32
|
automaton.add_transition('q1', 'q2', 'b')
|
|
47
|
-
automaton.add_transition('q0', '
|
|
48
|
-
|
|
49
|
-
automaton.
|
|
50
|
-
automaton.
|
|
33
|
+
automaton.add_transition('q0', 'q2', :epsilon)
|
|
34
|
+
|
|
35
|
+
automaton.save_svg('diagram.svg')
|
|
36
|
+
automaton.save_html('diagram.html')
|
|
37
|
+
automaton.save_dot('diagram.dot')
|
|
38
|
+
automaton.save_plantuml('diagram.puml')
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Use `render` and `save` when the format is selected dynamically:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
automaton.render(format: :svg, width: 800, height: 600)
|
|
45
|
+
automaton.save('diagram.svg', format: :svg, width: 800, height: 600)
|
|
46
|
+
```
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
automaton.
|
|
56
|
-
automaton.
|
|
48
|
+
Validate and inspect the automaton before rendering:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
automaton.validate!
|
|
52
|
+
automaton.layout_warnings(800, 600)
|
|
53
|
+
automaton.reachable_states
|
|
54
|
+
automaton.dead_states
|
|
55
|
+
automaton.trap_states
|
|
57
56
|
```
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
## Loading data
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
Build an automaton from Hash, JSON, or YAML:
|
|
62
61
|
|
|
63
|
-
#### 1. SVG (Native)
|
|
64
62
|
```ruby
|
|
65
|
-
automaton
|
|
63
|
+
automaton = Graphomaton.from_hash(
|
|
64
|
+
states: [
|
|
65
|
+
{ id: 'q0', label: 'Start', initial: true },
|
|
66
|
+
{ id: 'q1', final: true }
|
|
67
|
+
],
|
|
68
|
+
transitions: [
|
|
69
|
+
{ from: 'q0', to: 'q1', label: 'a', line_style: 'dashed' }
|
|
70
|
+
]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
Graphomaton.from_json(File.read('automaton.json'))
|
|
74
|
+
Graphomaton.from_yaml(File.read('automaton.yml'))
|
|
66
75
|
```
|
|
67
|
-
Generates a standalone SVG file with custom rendering.
|
|
68
76
|
|
|
69
|
-
|
|
77
|
+
JSON and YAML parsing is bounded by default to 10 MiB, 10,000 states, and
|
|
78
|
+
100,000 transitions. Lower or raise those limits explicitly for trusted input:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
Graphomaton.from_json(
|
|
82
|
+
File.open('automaton.json'),
|
|
83
|
+
max_input_bytes: 2 * 1024 * 1024,
|
|
84
|
+
max_states: 2_000,
|
|
85
|
+
max_transitions: 20_000
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
YAML aliases are disabled by default. Only enable `aliases: true` for trusted
|
|
90
|
+
documents that require them.
|
|
91
|
+
|
|
92
|
+
## Examples
|
|
93
|
+
|
|
94
|
+
### Styled SVG with metadata
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
automaton = Graphomaton.new
|
|
98
|
+
automaton.add_state(
|
|
99
|
+
'idle',
|
|
100
|
+
label: 'Idle',
|
|
101
|
+
metadata: { tooltip: 'Waiting for work', group: 'runtime', icon: 'I' }
|
|
102
|
+
)
|
|
103
|
+
automaton.add_state(
|
|
104
|
+
'running',
|
|
105
|
+
label: 'Running',
|
|
106
|
+
metadata: { tooltip: 'Processing job', group: 'runtime', url: 'https://example.com/runbook' }
|
|
107
|
+
)
|
|
108
|
+
automaton.add_state('failed', label: 'Failed', style: { fill: '#fee2e2', stroke: '#dc2626' })
|
|
109
|
+
|
|
110
|
+
automaton.set_initial('idle')
|
|
111
|
+
automaton.add_transition('idle', 'running', 'start', metadata: { bundle: 'happy-path' })
|
|
112
|
+
automaton.add_transition('running', 'idle', 'finish', metadata: { bundle: 'happy-path' })
|
|
113
|
+
automaton.add_transition('running', 'failed', 'error', line_style: :dashed)
|
|
114
|
+
|
|
115
|
+
automaton.save_svg(
|
|
116
|
+
'runtime.svg',
|
|
117
|
+
900,
|
|
118
|
+
500,
|
|
119
|
+
layout: :layered,
|
|
120
|
+
direction: :lr,
|
|
121
|
+
theme: :ocean,
|
|
122
|
+
edge_style: :spline,
|
|
123
|
+
label_tooltips: true,
|
|
124
|
+
html_tooltips: true
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Manual coordinates
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
automaton = Graphomaton.new
|
|
132
|
+
automaton.add_state('north', 300, 80)
|
|
133
|
+
automaton.add_state('east', 520, 260)
|
|
134
|
+
automaton.add_state('south', 300, 440)
|
|
135
|
+
automaton.add_state('west', 80, 260)
|
|
136
|
+
|
|
137
|
+
automaton.add_transition('north', 'east', 'turn')
|
|
138
|
+
automaton.add_transition('east', 'south', 'turn')
|
|
139
|
+
automaton.add_transition('south', 'west', 'turn')
|
|
140
|
+
automaton.add_transition('west', 'north', 'turn')
|
|
141
|
+
|
|
142
|
+
automaton.save_svg('manual.svg', 600, 520, layout: :manual, fit: :contain)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Folded groups
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
automaton = Graphomaton.new
|
|
149
|
+
automaton.add_state('parse', metadata: { group: 'frontend' })
|
|
150
|
+
automaton.add_state('validate', metadata: { group: 'frontend' })
|
|
151
|
+
automaton.add_state('execute', metadata: { group: 'backend' })
|
|
152
|
+
automaton.add_state('persist', metadata: { group: 'backend' })
|
|
153
|
+
|
|
154
|
+
automaton.add_transition('parse', 'validate', 'ok')
|
|
155
|
+
automaton.add_transition('validate', 'execute', 'accepted')
|
|
156
|
+
automaton.add_transition('execute', 'persist', 'done')
|
|
157
|
+
|
|
158
|
+
automaton.save_svg('folded.svg', layout: :layered, fold_groups: true)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### YAML input for the CLI
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
states:
|
|
165
|
+
- id: idle
|
|
166
|
+
label: Idle
|
|
167
|
+
initial: true
|
|
168
|
+
metadata:
|
|
169
|
+
group: runtime
|
|
170
|
+
tooltip: Waiting for work
|
|
171
|
+
- id: running
|
|
172
|
+
label: Running
|
|
173
|
+
metadata:
|
|
174
|
+
group: runtime
|
|
175
|
+
- id: done
|
|
176
|
+
final: true
|
|
177
|
+
transitions:
|
|
178
|
+
- from: idle
|
|
179
|
+
to: running
|
|
180
|
+
label: start
|
|
181
|
+
- from: running
|
|
182
|
+
to: done
|
|
183
|
+
label: finish
|
|
184
|
+
line_style: dashed
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
graphomaton --input automaton.yml --output runtime.svg --layout layered --direction lr --theme ocean
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Export several formats
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
outputs = {
|
|
195
|
+
svg: 'diagram.svg',
|
|
196
|
+
html: 'diagram.html',
|
|
197
|
+
dot: 'diagram.dot',
|
|
198
|
+
plantuml: 'diagram.puml'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
outputs.each do |format, path|
|
|
202
|
+
automaton.save(path, format: format)
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## CLI
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
graphomaton --input automaton.yml --output diagram.svg
|
|
210
|
+
graphomaton --input automaton.yml --output diagram.svg --layout-warnings
|
|
211
|
+
graphomaton --input automaton.json --output diagram.png --format png --theme dark --scale 2 --timeout 15 --max-output-bytes 33554432
|
|
212
|
+
graphomaton --input automaton.yml --output diagram.html --title "Automaton" --show-source --pan-zoom
|
|
213
|
+
graphomaton --input automaton.yml --output diagram.dot --rank-constraints
|
|
214
|
+
cat automaton.yml | graphomaton --input - --output - --format svg > diagram.svg
|
|
215
|
+
graphomaton --version
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The command interface also supports structured workflows:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
graphomaton render automaton.yml diagram.svg
|
|
222
|
+
graphomaton validate automaton.yml --diagnostics json
|
|
223
|
+
graphomaton validate automaton.yml --fail-on-warning
|
|
224
|
+
graphomaton list formats
|
|
225
|
+
graphomaton list layouts
|
|
226
|
+
graphomaton doctor
|
|
227
|
+
graphomaton completion zsh > _graphomaton
|
|
228
|
+
graphomaton man > graphomaton.1
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Project defaults may be stored in `.graphomaton.yml`; command-line options override
|
|
232
|
+
environment variables, which override the config file:
|
|
233
|
+
|
|
234
|
+
```yaml
|
|
235
|
+
svg:
|
|
236
|
+
theme: dark
|
|
237
|
+
layout: layered
|
|
238
|
+
labels:
|
|
239
|
+
wrap: true
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The CLI validates state references and hierarchy by default. `--no-validate` is available for intentionally deferred or partial diagrams. Standard input auto-detects JSON documents that start with `{` or `[`, otherwise YAML is assumed; use `--input-format` to override it. `--format` is required when `--output -` is used. Input limits can be lowered with `--max-input-bytes`, `--max-states`, `--max-transitions`, `--max-metadata-depth`, `--max-label-length`, and `--max-group-depth`. Use `--no-clobber` to protect an existing output (and `--force` to opt back into replacement). Usage, parse, validation, conversion, and security failures use distinct exit statuses and are reported without backtraces.
|
|
243
|
+
|
|
244
|
+
Common SVG options:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
graphomaton --input automaton.yml --output diagram.svg --layout layered --direction lr
|
|
248
|
+
graphomaton --input automaton.yml --output diagram.svg --layout force --node-spacing 140 --force-iterations 80 --layout-seed 42
|
|
249
|
+
graphomaton --input automaton.yml --output diagram.svg --layout graphviz --graphviz-command dot
|
|
250
|
+
graphomaton --input automaton.yml --output diagram.svg --responsive --fit cover --auto-size
|
|
251
|
+
graphomaton --input automaton.yml --output diagram.svg --state-shape ellipse --edge-style spline --arrow-shape vee
|
|
252
|
+
graphomaton --input automaton.yml --output diagram.svg --wrap-labels --state-wrap --label-tooltips --html-tooltips
|
|
253
|
+
graphomaton --input automaton.yml --output diagram.svg --highlight-unreachable --unreachable-zone right --highlight-dead-states
|
|
254
|
+
graphomaton --input automaton.yml --output diagram.svg --scc-groups --fold-groups
|
|
255
|
+
graphomaton --input automaton.yml --output diagram.svg --theme-file theme.yml
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Theme utilities:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
graphomaton --list-themes
|
|
262
|
+
graphomaton --theme-gallery --output theme_gallery.html
|
|
263
|
+
graphomaton --theme-gallery --theme-gallery-animated --theme-file theme.yml --output theme_gallery.html
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
For a self-contained HTML file, provide trusted local classic-script assets:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
graphomaton render automaton.yml diagram.html \
|
|
270
|
+
--cdn ./vendor/mermaid.min.js \
|
|
271
|
+
--self-contained --mermaid-sha256 HEX_DIGEST \
|
|
272
|
+
--nonce generated-nonce-123 --csp
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`--self-contained` does not download assets. It embeds the local files named by
|
|
276
|
+
`--cdn` and, when MathJax is enabled, `--mathjax-cdn`. These files execute with the
|
|
277
|
+
document's authority and must be trusted. `--nonce` plus `--csp` applies the nonce
|
|
278
|
+
to every generated script and style and emits a CSP meta tag. The optional
|
|
279
|
+
`--mermaid-sha256` and `--mathjax-sha256` values detect local asset replacement
|
|
280
|
+
before inlining.
|
|
281
|
+
|
|
282
|
+
## Themes
|
|
283
|
+
|
|
284
|
+
Native SVG and SVG-backed outputs support named or custom themes:
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
automaton.save_svg('diagram.svg', theme: :dark)
|
|
288
|
+
automaton.save_png('diagram.png', theme: :forest)
|
|
289
|
+
automaton.save_svg('diagram.svg', theme: :auto) # follows prefers-color-scheme
|
|
290
|
+
|
|
291
|
+
theme = Graphomaton.theme_from_yaml(File.read('theme.yml'))
|
|
292
|
+
automaton.save_svg('diagram.svg', theme: theme)
|
|
293
|
+
|
|
294
|
+
Graphomaton::Theme.save_gallery_html('theme_gallery.html')
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Built-in themes:
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
light, dark, forest, ocean, high_contrast, color_blind, print, minimal, academic, presentation, auto
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## SVG output
|
|
304
|
+
|
|
305
|
+
SVG is Graphomaton's native renderer. It supports multiple layouts, styling options, metadata-driven annotations, and converter-backed raster/vector outputs.
|
|
306
|
+
|
|
307
|
+
### Layouts
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
automaton.save_svg('diagram.svg', 800, 600, layout: :linear, direction: :lr)
|
|
311
|
+
automaton.save_svg('diagram.svg', 800, 600, layout: :circle)
|
|
312
|
+
automaton.save_svg('diagram.svg', 800, 600, layout: :grid)
|
|
313
|
+
automaton.save_svg('diagram.svg', 800, 600, layout: :layered)
|
|
314
|
+
automaton.save_svg('diagram.svg', 800, 600, layout: :bfs)
|
|
315
|
+
automaton.save_svg('diagram.svg', 800, 600, layout: :force, layout_seed: 42)
|
|
316
|
+
automaton.save_svg('diagram.svg', 800, 600, layout: :graphviz, graphviz_command: 'dot')
|
|
317
|
+
automaton.save_svg('diagram.svg', 800, 600, layout: :manual)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Layout notes:
|
|
321
|
+
|
|
322
|
+
- `direction` accepts `:lr`, `:tb`, `:rl`, and `:bt`.
|
|
323
|
+
- `layout` accepts `:linear`, `:circle`, `:grid`, `:layered`, `:bfs`, `:force`, `:graphviz`, `:dot`, and `:manual`.
|
|
324
|
+
- `:layered` and `:bfs` use deterministic barycenter ordering to reduce crossings.
|
|
325
|
+
- `:force` accepts `padding`, `node_spacing`, `rank_spacing`, `force_iterations`, and `layout_seed`.
|
|
326
|
+
- `:graphviz` and `:dot` call `dot -Tplain` through `graphviz_command:` and normalize returned node coordinates into the SVG canvas.
|
|
327
|
+
- `preserve_manual_positions: false` lets automatic layouts reposition states with explicit coordinates.
|
|
328
|
+
- `fit: :contain` fits positions into the canvas; `fit: :cover` stretches positions to use the canvas.
|
|
329
|
+
- `auto_size: true` expands the SVG viewport around rendered positions.
|
|
330
|
+
|
|
331
|
+
### Styling and labels
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
automaton.save_svg('diagram.svg', state_shape: :ellipse)
|
|
335
|
+
automaton.save_svg('diagram.svg', state_stroke_width: 3, transition_stroke_width: 2)
|
|
336
|
+
automaton.save_svg('diagram.svg', edge_style: :orthogonal)
|
|
337
|
+
automaton.save_svg('diagram.svg', arrow_shape: :vee, arrow_size: 14)
|
|
338
|
+
automaton.save_svg('diagram.svg', state_effect: :shadow)
|
|
339
|
+
automaton.save_svg('diagram.svg', font_family: '"Noto Sans JP", sans-serif', state_font_weight: 700)
|
|
340
|
+
|
|
341
|
+
automaton.save_svg('diagram.svg', wrap: true, max_transition_label_width: 120)
|
|
342
|
+
automaton.save_svg('diagram.svg', state_wrap: true, max_state_label_width: 120)
|
|
343
|
+
automaton.save_svg('diagram.svg', label_tooltips: true, html_tooltips: true)
|
|
344
|
+
automaton.save_svg('diagram.svg', rotate_labels: true)
|
|
345
|
+
automaton.save_svg('diagram.svg', label_background: false)
|
|
346
|
+
automaton.save_svg('diagram.svg', label_padding: 16, label_radius: 8, label_border: true)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Other SVG options:
|
|
350
|
+
|
|
351
|
+
- Default SVG root and marker IDs are deterministic for the graph, layout, and theme. `svg_id:` sets an explicit prefix and should be unique when embedding duplicate diagrams in one HTML document.
|
|
352
|
+
- Embedded CSS selectors and animation names are scoped to the SVG root ID.
|
|
353
|
+
- `css_variables: true` emits theme values as CSS variables.
|
|
354
|
+
- `embed_styles: false` skips the embedded style block.
|
|
355
|
+
- `xml_declaration: true`, `pretty: true`, and `minify: true` control serialization.
|
|
356
|
+
- `initial_arrow_length`, `initial_arrow_label`, `final_arrow_length`, `final_arrow_label`, and `show_final_arrows` control native start/end arrows.
|
|
357
|
+
|
|
358
|
+
### Metadata
|
|
359
|
+
|
|
360
|
+
State and transition metadata can enrich generated diagrams without changing state IDs:
|
|
361
|
+
|
|
362
|
+
```ruby
|
|
363
|
+
automaton.add_state(
|
|
364
|
+
'q0',
|
|
365
|
+
label: 'Start',
|
|
366
|
+
kind: 'choice',
|
|
367
|
+
metadata: {
|
|
368
|
+
tooltip: 'Entry point',
|
|
369
|
+
url: 'https://example.com',
|
|
370
|
+
group: 'main',
|
|
371
|
+
icon: 'S'
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
automaton.add_transition(
|
|
376
|
+
'q0',
|
|
377
|
+
'q1',
|
|
378
|
+
'next',
|
|
379
|
+
metadata: {
|
|
380
|
+
tooltip: 'Main path',
|
|
381
|
+
bundle: 'primary'
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`kind:` is format-independent and accepts `normal`, `choice`, `fork`, or `join`.
|
|
387
|
+
Exporter-specific shape overrides belong under `metadata.svg`, `metadata.dot`,
|
|
388
|
+
`metadata.mermaid`, or `metadata.plantuml` and do not affect other formats.
|
|
389
|
+
|
|
390
|
+
Metadata behavior:
|
|
391
|
+
|
|
392
|
+
- `label` changes the display name while preserving the state ID for transitions.
|
|
393
|
+
- `tooltip` or `description` becomes SVG tooltip text.
|
|
394
|
+
- `url` or `href` creates SVG links and DOT URL attributes.
|
|
395
|
+
- Link URLs may use `https`, `http`, `mailto`, a relative path, or a fragment. Executable and local-file schemes such as `javascript:`, `data:`, and `file:` are rejected.
|
|
396
|
+
- SVG `style` accepts `fill`, `stroke`, stroke settings, opacity settings, font settings, and `color`; raw CSS properties, `url(...)`, and declaration-breaking values are rejected.
|
|
397
|
+
- `group` or `cluster` renders SVG background groups, Mermaid/PlantUML composite states, and DOT clusters.
|
|
398
|
+
- `icon` renders a compact SVG icon label inside the state.
|
|
399
|
+
- `bundle` routes native SVG edges through a shared control point and emits `data-bundle`.
|
|
400
|
+
- `kind` defines a format-independent `choice`, `fork`, or `join` pseudostate. Format-specific nested metadata remains available for overrides.
|
|
401
|
+
- `parent` defines semantic hierarchy and `group` defines visual grouping; validation rejects setting both on one state, missing parents, and parent cycles.
|
|
402
|
+
- `fold_groups: true` collapses grouped SVG states into compound nodes, hides internal transitions, and rewrites external transitions to the folded node.
|
|
403
|
+
- `scc_groups: true` renders SVG groups around strongly connected components.
|
|
404
|
+
|
|
405
|
+
## Output formats
|
|
406
|
+
|
|
407
|
+
| Format | Method | Notes |
|
|
408
|
+
| --- | --- | --- |
|
|
409
|
+
| SVG | `save_svg` | Native renderer. |
|
|
410
|
+
| PNG | `save_png` | Converts native SVG. Requires `rsvg-convert`, `magick`, or `convert`. |
|
|
411
|
+
| PDF | `save_pdf` | Converts native SVG. Requires `rsvg-convert`, `magick`, or `convert`. |
|
|
412
|
+
| WebP | `save_webp` | Converts native SVG. Uses librsvg plus ImageMagick when available, or ImageMagick directly. |
|
|
413
|
+
| HTML | `save_html` | Mermaid.js state diagram in an HTML page. |
|
|
414
|
+
| DOT | `save_dot` | GraphViz DOT source. |
|
|
415
|
+
| PlantUML | `save_plantuml` | PlantUML state diagram source. |
|
|
416
|
+
|
|
417
|
+
### Converter-backed formats
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
420
|
+
automaton.save_png('diagram.png', 800, 600, scale: 2.0, converter: :magick)
|
|
421
|
+
automaton.save_pdf('diagram.pdf', 800, 600, converter: :magick, timeout: 15, max_output_bytes: 32 * 1024 * 1024)
|
|
422
|
+
automaton.save_webp('diagram.webp', 800, 600, converter: :magick)
|
|
423
|
+
automaton.save_webp('diagram.webp', 800, 600, converter: :rsvg_magick)
|
|
424
|
+
|
|
425
|
+
Graphomaton.png_available?(converter: :auto)
|
|
426
|
+
Graphomaton.pdf_available?(converter: :auto)
|
|
427
|
+
Graphomaton.webp_available?(converter: :auto)
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
PNG `scale:` changes only output pixel density. The logical SVG viewBox, state size, and layout remain unchanged.
|
|
431
|
+
Converter processes default to a 30-second timeout, a 64 MiB output limit, and a 1 MiB diagnostic limit. Timeout and output-limit failures terminate the process group and raise the format-specific `ConversionError`.
|
|
432
|
+
|
|
433
|
+
### HTML with Mermaid.js
|
|
434
|
+
|
|
70
435
|
```ruby
|
|
71
436
|
automaton.save_html('diagram.html')
|
|
437
|
+
automaton.save_html('diagram.html', show_source: true)
|
|
438
|
+
automaton.save_html('diagram.html', theme: :auto)
|
|
439
|
+
automaton.save_html('diagram.html', cdn: './mermaid.min.js', inline_mermaid: true)
|
|
440
|
+
automaton.save_html('diagram.html', pan_zoom: true)
|
|
441
|
+
automaton.save_html('diagram.html', mathjax: true)
|
|
442
|
+
automaton.save_html('diagram.html', notes: true, class_defs: true)
|
|
72
443
|
```
|
|
73
|
-
Generates an HTML file with embedded Mermaid.js state diagram. The diagram is rendered in the browser using Mermaid.js from CDN.
|
|
74
444
|
|
|
75
|
-
|
|
445
|
+
By default, HTML output uses the pinned Mermaid.js 10.9.8 module with Mermaid's strict security level. Remote asset URLs must use HTTPS; relative and absolute local paths are also accepted. `offline: true` requires `cdn:` to name a local classic `.js` build; ES module `.mjs` assets are rejected in offline and inline modes. `inline_mermaid: true` reads and embeds the complete file named by `cdn:`, so use it only with a trusted local JavaScript file. Generated UI is English by default and supports `lang: 'en'` or `lang: 'ja'`.
|
|
446
|
+
|
|
447
|
+
### GraphViz DOT
|
|
76
448
|
|
|
77
|
-
#### 3. GraphViz (DOT)
|
|
78
449
|
```ruby
|
|
79
450
|
automaton.save_dot('diagram.dot')
|
|
451
|
+
automaton.save_dot('diagram.dot', theme: :ocean)
|
|
452
|
+
automaton.save_dot('diagram.dot', rank_constraints: true)
|
|
80
453
|
```
|
|
81
|
-
|
|
454
|
+
|
|
82
455
|
```bash
|
|
83
456
|
dot -Tpng diagram.dot -o diagram.png
|
|
84
457
|
dot -Tsvg diagram.dot -o diagram.svg
|
|
85
458
|
dot -Tpdf diagram.dot -o diagram.pdf
|
|
459
|
+
neato -Tsvg diagram.dot -o diagram-neato.svg
|
|
460
|
+
sfdp -Tsvg diagram.dot -o diagram-sfdp.svg
|
|
86
461
|
```
|
|
87
462
|
|
|
88
|
-
|
|
463
|
+
Use `dot` for ranked state-machine layouts. Use `neato` or `sfdp` for dense graphs where free spreading is preferred.
|
|
464
|
+
|
|
465
|
+
### PlantUML
|
|
466
|
+
|
|
89
467
|
```ruby
|
|
90
468
|
automaton.save_plantuml('diagram.puml')
|
|
469
|
+
automaton.save_plantuml('diagram.puml', theme: :forest)
|
|
470
|
+
automaton.save_plantuml('diagram.puml', notes: true)
|
|
91
471
|
```
|
|
92
|
-
|
|
472
|
+
|
|
93
473
|
```bash
|
|
94
|
-
# Using PlantUML JAR
|
|
95
474
|
java -jar plantuml.jar diagram.puml
|
|
96
|
-
|
|
97
|
-
# Using online server
|
|
98
475
|
curl -X POST --data-binary @diagram.puml https://www.plantuml.com/plantuml/png > diagram.png
|
|
99
476
|
```
|
|
100
477
|
|
|
101
478
|
## Contributing
|
|
102
479
|
|
|
480
|
+
Architecture, input schema, CLI, exporter capabilities, performance limits,
|
|
481
|
+
migration, release operations, and custom exporter integration are documented in
|
|
482
|
+
the [`docs/`](docs/) directory. See [Custom exporters](docs/custom-exporters.md)
|
|
483
|
+
when adding an application-specific output format.
|
|
484
|
+
|
|
103
485
|
Bug reports and pull requests are welcome on GitHub at https://github.com/ydah/graphomaton.
|
|
104
486
|
|
|
105
487
|
## License
|
|
@@ -108,4 +490,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
108
490
|
|
|
109
491
|
## Code of Conduct
|
|
110
492
|
|
|
111
|
-
Everyone interacting in the Graphomaton project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ydah/graphomaton/blob/main/CODE_OF_CONDUCT.md).
|
|
493
|
+
Everyone interacting in the Graphomaton project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/ydah/graphomaton/blob/main/CODE_OF_CONDUCT.md).
|