circulator 2.1.5 → 2.1.6
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 +8 -4
- data/README.md +66 -0
- data/exe/circulator-diagram +119 -21
- data/lib/circulator/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 128d49edd784a3a0cc8ebc72ee4ca9be581bbea4ab57ff795a7c7435a52e3ddb
|
|
4
|
+
data.tar.gz: ae4a7ca3dd48197e944c95516f862e9f08cbc082ad098d0ef3d589fe351d59c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5792c5c3fc86dc3a7d75fd619229426a3a8ae8198bee46fe28d7ec54f3d78ca374732c71281177fb53a8e54d5f1ed256af8163cd70420739b4450fb0f5b0589d
|
|
7
|
+
data.tar.gz: f7fc9e7bf3e10331306a3161c7feff64e09b0dd1933f52f054f98076256c2287e40c6ddd7ac7b6c7eee39bb55442ca3f3045179cc889378d87b9c59eb74dc971
|
data/CHANGELOG.md
CHANGED
|
@@ -5,10 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [2.1.
|
|
8
|
+
## [2.1.6] - 2025-12-08
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Dependabot config to follow a 14 day cooldown. (6aabc0a)
|
|
9
13
|
|
|
10
14
|
### Added
|
|
11
15
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
16
|
+
- Documentation for extension system and Circulator.extension API (be7c6fc)
|
|
17
|
+
- --all flag to automatically discover and generate diagrams for all classes with Circulator flows (f4dbbfc)
|
|
18
|
+
- Automatic eager loading of Rails application classes when using --all (f4dbbfc)
|
data/README.md
CHANGED
|
@@ -285,10 +285,52 @@ class Payment
|
|
|
285
285
|
end
|
|
286
286
|
```
|
|
287
287
|
|
|
288
|
+
#### Extending Flows
|
|
289
|
+
|
|
290
|
+
You can extend existing flows using `Circulator.extension`:
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
class Document
|
|
294
|
+
extend Circulator
|
|
295
|
+
|
|
296
|
+
attr_accessor :status
|
|
297
|
+
|
|
298
|
+
flow :status do
|
|
299
|
+
state :draft do
|
|
300
|
+
action :submit, to: :review
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
state :review do
|
|
304
|
+
action :approve, to: :approved
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
state :approved
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Add additional states and transitions
|
|
312
|
+
Circulator.extension(:Document, :status) do
|
|
313
|
+
state :review do
|
|
314
|
+
action :reject, to: :rejected
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
state :rejected do
|
|
318
|
+
action :revise, to: :draft
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
doc = Document.new
|
|
323
|
+
doc.status = :review
|
|
324
|
+
doc.status_reject # => :rejected (from extension)
|
|
325
|
+
doc.status_revise # => :draft (from extension)
|
|
326
|
+
```
|
|
327
|
+
|
|
288
328
|
### Generating Diagrams
|
|
289
329
|
|
|
290
330
|
You can generate diagrams for your Circulator models using the `circulator-diagram` executable. By default, it will generate a DOT file. You can also generate a PlantUML file by passing the `-f plantuml` option.
|
|
291
331
|
|
|
332
|
+
#### Generate a diagram for a specific model:
|
|
333
|
+
|
|
292
334
|
```bash
|
|
293
335
|
bundle exec circulator-diagram MODEL_NAME
|
|
294
336
|
```
|
|
@@ -297,6 +339,30 @@ bundle exec circulator-diagram MODEL_NAME
|
|
|
297
339
|
bundle exec circulator-diagram MODEL_NAME -f plantuml
|
|
298
340
|
```
|
|
299
341
|
|
|
342
|
+
#### Generate diagrams for all models with Circulator flows:
|
|
343
|
+
|
|
344
|
+
Use the `--all` option to automatically find and generate diagrams for all classes that have Circulator flows defined:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
bundle exec circulator-diagram --all
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
bundle exec circulator-diagram --all -f plantuml
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
The `--all` option will:
|
|
355
|
+
- Automatically discover all classes with Circulator flows (including classes that inherit from a parent that extends Circulator)
|
|
356
|
+
- Eager load Rails application classes if running in a Rails environment
|
|
357
|
+
- Generate diagrams for each class found
|
|
358
|
+
- Use the same output directory and format options as single-model generation
|
|
359
|
+
|
|
360
|
+
#### Other options:
|
|
361
|
+
|
|
362
|
+
- `-d, --directory DIRECTORY` - Specify output directory (default: `docs`)
|
|
363
|
+
- `-s, --separate` - Generate separate diagram files for each flow attribute
|
|
364
|
+
- `-r, --require FILE` - Require a file before loading models (e.g., `config/environment`)
|
|
365
|
+
|
|
300
366
|
## Why Circulator?
|
|
301
367
|
|
|
302
368
|
Circulator distinguishes itself from other Ruby state machine libraries through its simplicity and flexibility:
|
data/exe/circulator-diagram
CHANGED
|
@@ -8,17 +8,21 @@ require_relative "../lib/circulator/dot"
|
|
|
8
8
|
require_relative "../lib/circulator/plantuml"
|
|
9
9
|
|
|
10
10
|
# Parse command-line options
|
|
11
|
-
options = {format: "dot", require: nil, directory: "docs", separate: false}
|
|
11
|
+
options = {format: "dot", require: nil, directory: "docs", separate: false, all: false}
|
|
12
12
|
parser = OptionParser.new do |opts|
|
|
13
|
-
opts.banner = "Usage: circulator-diagram MODEL_NAME [options]"
|
|
13
|
+
opts.banner = "Usage: circulator-diagram [MODEL_NAME | --all] [options]"
|
|
14
14
|
opts.separator ""
|
|
15
15
|
opts.separator "Generate diagram files for Circulator state machine flows"
|
|
16
16
|
opts.separator ""
|
|
17
17
|
opts.separator "Arguments:"
|
|
18
|
-
opts.separator " MODEL_NAME Name of the model class with Circulator flows"
|
|
18
|
+
opts.separator " MODEL_NAME Name of the model class with Circulator flows (required unless --all is used)"
|
|
19
19
|
opts.separator ""
|
|
20
20
|
opts.separator "Options:"
|
|
21
21
|
|
|
22
|
+
opts.on("-a", "--all", "Generate diagrams for all classes with Circulator in their ancestry") do
|
|
23
|
+
options[:all] = true
|
|
24
|
+
end
|
|
25
|
+
|
|
22
26
|
opts.on("-f", "--format FORMAT", ["dot", "plantuml"], "Output format (dot, plantuml). Default: dot") do |format|
|
|
23
27
|
options[:format] = format
|
|
24
28
|
end
|
|
@@ -54,15 +58,15 @@ rescue OptionParser::InvalidOption => e
|
|
|
54
58
|
exit 1
|
|
55
59
|
end
|
|
56
60
|
|
|
57
|
-
# Check for required model name argument
|
|
58
|
-
if ARGV.empty?
|
|
59
|
-
warn "Error: MODEL_NAME is required"
|
|
61
|
+
# Check for required model name argument or --all flag
|
|
62
|
+
if ARGV.empty? && !options[:all]
|
|
63
|
+
warn "Error: Either MODEL_NAME or --all is required"
|
|
60
64
|
warn ""
|
|
61
65
|
warn parser
|
|
62
66
|
exit 1
|
|
63
67
|
end
|
|
64
68
|
|
|
65
|
-
model_name = ARGV[0]
|
|
69
|
+
model_name = ARGV[0] unless options[:all]
|
|
66
70
|
|
|
67
71
|
# Load the application environment
|
|
68
72
|
# Priority: -r option > config/environment.rb > nothing
|
|
@@ -79,22 +83,63 @@ elsif File.exist?("config/environment.rb")
|
|
|
79
83
|
require File.expand_path("config/environment.rb")
|
|
80
84
|
end
|
|
81
85
|
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
# Find all classes with Circulator in their ancestry
|
|
87
|
+
def find_circulator_classes
|
|
88
|
+
classes = []
|
|
89
|
+
|
|
90
|
+
# Try to force-load classes that might not be loaded yet
|
|
91
|
+
# This is especially important for Rails apps with lazy loading
|
|
92
|
+
if defined?(Rails) && Rails.application
|
|
93
|
+
begin
|
|
94
|
+
Rails.application.eager_load!
|
|
95
|
+
rescue => e
|
|
96
|
+
warn "Warning: Could not eager load Rails application: #{e.message}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
ObjectSpace.each_object(Class) do |klass|
|
|
101
|
+
# Skip anonymous classes and classes without names
|
|
102
|
+
next if klass.name.nil? || klass.name.empty?
|
|
103
|
+
|
|
104
|
+
# Check if the class responds to :flows (set up when Circulator is extended)
|
|
105
|
+
next unless klass.respond_to?(:flows)
|
|
106
|
+
|
|
107
|
+
begin
|
|
108
|
+
flows = klass.flows
|
|
109
|
+
next if flows.nil? || flows.empty?
|
|
110
|
+
rescue => e
|
|
111
|
+
# Skip classes where flows method raises an error
|
|
112
|
+
warn "Warning: Could not get flows for #{klass.name}: #{e.class} - #{e.message}"
|
|
113
|
+
next
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
classes << klass
|
|
117
|
+
rescue => e
|
|
118
|
+
# Skip any class that causes an error during inspection
|
|
119
|
+
if ENV["DEBUG"]
|
|
120
|
+
warn "Warning: Error inspecting class #{begin
|
|
121
|
+
klass.name
|
|
122
|
+
rescue
|
|
123
|
+
"unknown"
|
|
124
|
+
end}: #{e.class} - #{e.message}"
|
|
125
|
+
end
|
|
126
|
+
next
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
classes.sort_by(&:name)
|
|
89
130
|
end
|
|
90
131
|
|
|
91
|
-
# Generate diagram
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
132
|
+
# Generate diagram for a single model class
|
|
133
|
+
def generate_diagram_for_class(model_class, options)
|
|
134
|
+
begin
|
|
135
|
+
generator = case options[:format]
|
|
136
|
+
when "plantuml"
|
|
137
|
+
Circulator::PlantUml.new(model_class)
|
|
138
|
+
else
|
|
139
|
+
Circulator::Dot.new(model_class)
|
|
140
|
+
end
|
|
141
|
+
rescue => e
|
|
142
|
+
raise "Failed to initialize diagram generator for #{model_class.name}: #{e.class} - #{e.message}"
|
|
98
143
|
end
|
|
99
144
|
|
|
100
145
|
# Determine base output filename and extension
|
|
@@ -163,6 +208,59 @@ begin
|
|
|
163
208
|
puts " dot -Tpng #{output_file} -o #{File.join(options[:directory], base_name)}.png"
|
|
164
209
|
end
|
|
165
210
|
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Collect classes to generate diagrams for
|
|
214
|
+
begin
|
|
215
|
+
if options[:all]
|
|
216
|
+
# Find all classes with Circulator in their ancestry
|
|
217
|
+
begin
|
|
218
|
+
classes_to_process = find_circulator_classes
|
|
219
|
+
rescue => e
|
|
220
|
+
warn "Error finding Circulator classes: #{e.class} - #{e.message}"
|
|
221
|
+
warn e.backtrace.first(10).join("\n")
|
|
222
|
+
exit 1
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
if classes_to_process.empty?
|
|
226
|
+
warn "No classes with Circulator flows found"
|
|
227
|
+
exit 1
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
puts "Found #{classes_to_process.size} class(es) with Circulator flows:"
|
|
231
|
+
classes_to_process.each do |klass|
|
|
232
|
+
puts " - #{klass.name}"
|
|
233
|
+
end
|
|
234
|
+
puts ""
|
|
235
|
+
else
|
|
236
|
+
# Try to constantize the model name
|
|
237
|
+
begin
|
|
238
|
+
model_class = Object.const_get(model_name)
|
|
239
|
+
rescue NameError
|
|
240
|
+
warn "Error: Model '#{model_name}' not found"
|
|
241
|
+
warn "Make sure the model is loaded in your environment"
|
|
242
|
+
exit 1
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
classes_to_process = [model_class]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Generate diagrams for all classes in the array
|
|
249
|
+
classes_to_process.each do |model_class|
|
|
250
|
+
if classes_to_process.size > 1
|
|
251
|
+
puts "Generating diagram for #{model_class.name}..."
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
begin
|
|
255
|
+
generate_diagram_for_class(model_class, options)
|
|
256
|
+
rescue => e
|
|
257
|
+
warn "Error generating diagram for #{model_class.name}: #{e.class} - #{e.message}"
|
|
258
|
+
warn e.backtrace.first(5).join("\n") if ENV["DEBUG"]
|
|
259
|
+
next
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
puts "" if classes_to_process.size > 1
|
|
263
|
+
end
|
|
166
264
|
|
|
167
265
|
exit 0
|
|
168
266
|
rescue ArgumentError => e
|
data/lib/circulator/version.rb
CHANGED