aasm_statecharts 1.0.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 +7 -0
- data/README.md +0 -0
- data/bin/aasm_statecharts +101 -0
- data/lib/aasm_statechart.rb +140 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e89b5b44cb179e336bb467bbe9c0af6198cbb4f5
|
4
|
+
data.tar.gz: cea3f2ae3f920efe4a13e8d47d71566215248c43
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 437de932bc28fc77a87015af178618b023fb70e8de11ea407f25885b59b809277c9dbebf57094380952b1e540d8f98d8396a3286dc87ac040101bd5e481ea339
|
7
|
+
data.tar.gz: bf0f765d8eb824967cbcceb54139b1e8020088f741bb2eb7f718014236eb779cc24fca8c68191e200b82176f99f5f0d0e9ca270c909c9c8c5af9cd4421c9bb0c
|
data/README.md
ADDED
File without changes
|
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
begin
|
5
|
+
require 'bundler'
|
6
|
+
Bundler.setup
|
7
|
+
rescue Exception
|
8
|
+
end
|
9
|
+
|
10
|
+
here = File.expand_path(File.dirname __FILE__)
|
11
|
+
$:<< "#{here}/../lib"
|
12
|
+
|
13
|
+
require 'optparse'
|
14
|
+
require 'aasm_statechart'
|
15
|
+
|
16
|
+
|
17
|
+
def parse_command_line!
|
18
|
+
options = {
|
19
|
+
all: false,
|
20
|
+
directory: './doc',
|
21
|
+
format: 'png',
|
22
|
+
}
|
23
|
+
|
24
|
+
formats = AasmStatechart::Renderer::FORMATS.join(', ')
|
25
|
+
|
26
|
+
parser = OptionParser.new do |opts|
|
27
|
+
opts.banner = "Usage: aasm_statechart [options] <model> [models ...]"
|
28
|
+
|
29
|
+
opts.on('-a', '--all', "Render all models using AASM") do
|
30
|
+
options[:all] = true
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('-d', '--directory directory',
|
34
|
+
"Output to a specific directory (default: #{options[:directory]})") do |directory|
|
35
|
+
options[:directory] = directory
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on('-t', '--file-type type',
|
39
|
+
"Output in the specified format (default: #{options[:format]}), which must be one of the following: #{formats}.") do |format|
|
40
|
+
format = format.downcase
|
41
|
+
|
42
|
+
if !AasmStatechart::Renderer::FORMATS.include?(format)
|
43
|
+
puts "error: #{format} is not a recognized file format."
|
44
|
+
puts "The file format must be one of the following: #{formats}."
|
45
|
+
exit(1)
|
46
|
+
end
|
47
|
+
|
48
|
+
options[:format] = format
|
49
|
+
end
|
50
|
+
|
51
|
+
opts
|
52
|
+
end
|
53
|
+
|
54
|
+
parser.parse!
|
55
|
+
|
56
|
+
if !options[:all] && ARGV.empty?
|
57
|
+
puts parser
|
58
|
+
exit(1)
|
59
|
+
end
|
60
|
+
|
61
|
+
[options, ARGV]
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_rails!
|
65
|
+
if !File.exists? './config/environment.rb'
|
66
|
+
script_name = File.basename $PROGRAM_NAME
|
67
|
+
puts 'error: unable to find ./config/environment.rb.'
|
68
|
+
puts "Please run #{script_name} from the root of your Rails application."
|
69
|
+
exit(1)
|
70
|
+
end
|
71
|
+
|
72
|
+
require './config/environment'
|
73
|
+
end
|
74
|
+
|
75
|
+
def ensure_directory! directory
|
76
|
+
Dir.mkdir(directory) unless Dir.exists? directory
|
77
|
+
end
|
78
|
+
|
79
|
+
def collect_models(model_names)
|
80
|
+
model_names.map { |model_name| model_name.classify.constantize }
|
81
|
+
end
|
82
|
+
|
83
|
+
def collect_all_models
|
84
|
+
Rails::Application.subclasses.first.eager_load!
|
85
|
+
ActiveRecord::Base.subclasses.select { |klass| klass.respond_to? :aasm }
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
options, model_names = parse_command_line!
|
90
|
+
load_rails!
|
91
|
+
ensure_directory! options[:directory]
|
92
|
+
|
93
|
+
models = options[:all] ? collect_all_models : collect_models(model_names)
|
94
|
+
|
95
|
+
models.each do |klass|
|
96
|
+
name = klass.name.underscore
|
97
|
+
renderer = AasmStatechart::Renderer.new(klass)
|
98
|
+
filename = "#{options[:directory]}/#{name}.#{options[:format]}"
|
99
|
+
renderer.save(filename, format: options[:format])
|
100
|
+
puts " * rendered #{name} to #{filename}"
|
101
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
# @author Brendan MacDonell
|
4
|
+
|
5
|
+
require 'graphviz'
|
6
|
+
|
7
|
+
|
8
|
+
module AasmStatechart
|
9
|
+
class Renderer
|
10
|
+
FORMATS = GraphViz::Constants::FORMATS
|
11
|
+
|
12
|
+
GRAPH_STYLE = {
|
13
|
+
rankdir: :TB,
|
14
|
+
}
|
15
|
+
|
16
|
+
NODE_STYLE = {
|
17
|
+
shape: :Mrecord,
|
18
|
+
fontname: 'Arial',
|
19
|
+
fontsize: 10,
|
20
|
+
penwidth: 0.7,
|
21
|
+
}
|
22
|
+
|
23
|
+
EDGE_STYLE = {
|
24
|
+
dir: :forward,
|
25
|
+
fontname: 'Arial',
|
26
|
+
fontsize: 9,
|
27
|
+
penwidth: 0.7,
|
28
|
+
}
|
29
|
+
|
30
|
+
START_NODE_STYLE = {
|
31
|
+
shape: :circle,
|
32
|
+
label: '',
|
33
|
+
style: :filled,
|
34
|
+
color: 'black',
|
35
|
+
fillcolor: 'black',
|
36
|
+
fixedsize: true,
|
37
|
+
width: 0.25,
|
38
|
+
height: 0.25,
|
39
|
+
}
|
40
|
+
|
41
|
+
END_NODE_STYLE = {
|
42
|
+
shape: :doublecircle,
|
43
|
+
label: '',
|
44
|
+
style: :filled,
|
45
|
+
color: 'black',
|
46
|
+
fillcolor: 'black',
|
47
|
+
fixedsize: true,
|
48
|
+
width: 0.20,
|
49
|
+
height: 0.20,
|
50
|
+
}
|
51
|
+
|
52
|
+
ENTER_CALLBACKS = [:before_enter, :enter, :after_enter, :after_commit]
|
53
|
+
EXIT_CALLBACKS = [:before_exit, :exit, :after_exit]
|
54
|
+
TRANSITION_CALLBACKS = [:before, :on_transition, :after]
|
55
|
+
|
56
|
+
def initialize(klass)
|
57
|
+
@start_node = nil
|
58
|
+
@end_node = nil
|
59
|
+
|
60
|
+
@graph = GraphViz.new(:statechart)
|
61
|
+
@graph.type = 'digraph'
|
62
|
+
|
63
|
+
# ruby-graphviz is missing an API to set styles in bulk, so set them here
|
64
|
+
GRAPH_STYLE.each { |k, v| @graph.graph[k] = v }
|
65
|
+
NODE_STYLE.each { |k, v| @graph.node[k] = v }
|
66
|
+
EDGE_STYLE.each { |k, v| @graph.edge[k] = v }
|
67
|
+
|
68
|
+
klass.aasm.states.each { |state| render_state(state) }
|
69
|
+
klass.aasm.events.each { |name, event| render_event(name, event) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def save(filename, format: 'png')
|
73
|
+
@graph.output({format => filename})
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def start_node
|
79
|
+
if @start_node.nil?
|
80
|
+
@start_node = @graph.add_nodes(SecureRandom.uuid, **START_NODE_STYLE)
|
81
|
+
end
|
82
|
+
|
83
|
+
@start_node
|
84
|
+
end
|
85
|
+
|
86
|
+
def end_node
|
87
|
+
if @end_node.nil?
|
88
|
+
@end_node = @graph.add_nodes(SecureRandom.uuid, **END_NODE_STYLE)
|
89
|
+
end
|
90
|
+
|
91
|
+
@end_node
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_options(options, keys)
|
95
|
+
options
|
96
|
+
.select { |key| keys.include? key }
|
97
|
+
.values
|
98
|
+
.flatten
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_callbacks(options, keys)
|
102
|
+
get_options(options, keys)
|
103
|
+
.map { |callback| "#{callback}();" }
|
104
|
+
.join(' ')
|
105
|
+
end
|
106
|
+
|
107
|
+
def render_state(state)
|
108
|
+
enter_callbacks = get_callbacks(state.options, ENTER_CALLBACKS)
|
109
|
+
exit_callbacks = get_callbacks(state.options, EXIT_CALLBACKS)
|
110
|
+
|
111
|
+
callbacks_list = []
|
112
|
+
callbacks_list << "entry / #{enter_callbacks}" if enter_callbacks.present?
|
113
|
+
callbacks_list << "exit / #{exit_callbacks}" if exit_callbacks.present?
|
114
|
+
label = "{#{state.display_name}|#{callbacks_list.join('\l')}}"
|
115
|
+
|
116
|
+
node = @graph.add_nodes(state.name.to_s, label: label)
|
117
|
+
|
118
|
+
if state.options.fetch(:initial, false)
|
119
|
+
@graph.add_edges(start_node, node)
|
120
|
+
elsif state.options.fetch(:final, false)
|
121
|
+
@graph.add_edges(node, end_node)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def render_event(name, event)
|
126
|
+
event.transitions.each do |transition|
|
127
|
+
chunks = [name]
|
128
|
+
|
129
|
+
guard = transition.options.fetch(:guard, nil)
|
130
|
+
chunks << "[#{guard}]" if guard
|
131
|
+
callbacks = get_callbacks(transition.options, TRANSITION_CALLBACKS)
|
132
|
+
chunks << '/' << callbacks if callbacks.present?
|
133
|
+
|
134
|
+
label = " #{chunks.join(' ')} "
|
135
|
+
|
136
|
+
@graph.add_edges(transition.from.to_s, transition.to.to_s, label: label)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aasm_statecharts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brendan MacDonell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aasm
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby-graphviz
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
description: Generate UML-style state charts from AASM state machines
|
56
|
+
email: brendan@macdonell.net
|
57
|
+
executables:
|
58
|
+
- aasm_statecharts
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- README.md
|
63
|
+
- bin/aasm_statecharts
|
64
|
+
- lib/aasm_statechart.rb
|
65
|
+
homepage: http://rubygems.org/gems/aasm_statecharts
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.2.2
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: AASM statecharts
|
89
|
+
test_files: []
|
90
|
+
has_rdoc:
|