aasm_statecharts 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: