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 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: