gv_fsm 0.1.0 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/bin/gv_fsm +46 -11
  3. data/lib/gv_fsm.rb +36 -6
  4. data/lib/templates.rb +188 -66
  5. data/lib/version.rb +1 -1
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74b717895ae52d4a28c56c12944e18db81886463858bd42d9507a6cbb3c1309e
4
- data.tar.gz: f929706aace8b53639fdd8e67aa3096be88994c551266d4c7805196144595e26
3
+ metadata.gz: 48a490d68d563a441424cfdd79c6c6051d30d4b6e55765239936aed794a898a5
4
+ data.tar.gz: fc16d72b89d439ae9b18f021e46a9cbf1c5caf4b48ed71a45bad2db72998005b
5
5
  SHA512:
6
- metadata.gz: a807c54fa79bda61a07df1fee06b8dac62d193d92eb7e056a44c56b144fd008f63e983b0ea28e18d1a7008637a5cf5c64dd21a430f5133cb03f8ca62a75af021
7
- data.tar.gz: 6c471ada43c67ef29bdf12d7797b8f4d4bd23aa4e37bcc78ff7e56f0c91657c01066bec2ee5ee5d64a8c5ce0ac50845c4a156e7cf5ebc5d2af28413d25be3a40
6
+ metadata.gz: ff52872fa8b822d0b63085cf1721bff88123d64112d3ba1103bdaf6da7e5eb1e801f5fed4526d029128b0853ab1527c45f1fc625d61cc37a9350c81709c5a578
7
+ data.tar.gz: 2d4c28d766e9c34bbe379af163f3302f53501870497df608142c7b67e9f02059846944d17ecac780c4d732a68be00eb790625ffbbd6d2d90b4040c764ea6dcea
data/bin/gv_fsm CHANGED
@@ -6,11 +6,12 @@ require 'optparse'
6
6
  sm = GV_FSM::FSM.new
7
7
 
8
8
  options = {header: true, source: true}
9
- OptionParser.new do |parser|
9
+ op = OptionParser.new do |parser|
10
10
  parser.banner =<<~EOB
11
11
  Graphviz to Finite State Machine generator
12
12
  Version: #{GV_FSM::VERSION}
13
13
  See also https://github.com/pbosetti/gv_fsm
14
+
14
15
  Usage: gv_fsm [options] scheme.dot
15
16
  EOB
16
17
 
@@ -35,18 +36,52 @@ OptionParser.new do |parser|
35
36
  options[:header] = false
36
37
  end
37
38
 
38
- end.parse!
39
+ parser.on("-x", "--prefix PREFIX", "Prepend PREFIX to names of generated functions and objects") do |p|
40
+ sm.prefix = p
41
+ end
42
+
43
+ parser.on("-i", "--ino", "Generate a single .ino file (for Arduino)") do
44
+ sm.ino = true
45
+ end
46
+
47
+ parser.on("-l", "--no-syslog", "Omit syslog calls in stub functions") do
48
+ sm.syslog = false
49
+ end
50
+
51
+ parser.on("-h", "--help", "Prints this help") do
52
+ puts parser
53
+ exit
54
+ end
55
+
56
+ end
39
57
 
40
- raise ArgumentError, "I need the path to a Graphviz file!" unless ARGV[0]
41
- raise ArgumentError, "#{ARGV[0]} does not look like a Graphviz file!" unless File.extname(ARGV[0]) == ".dot"
58
+ op.parse!
59
+
60
+ unless ARGV[0]
61
+ STDERR.puts "ERROR: I need the path to a Graphviz file!\n\n"
62
+ STDERR.puts op
63
+ exit
64
+ end
65
+ unless File.extname(ARGV[0]) == ".dot"
66
+ STDERR.puts "ERROR: #{ARGV[0]} does not look like a Graphviz file!\n\n"
67
+ STDERR.puts op
68
+ exit
69
+ end
42
70
 
43
71
  sm.parse(ARGV[0])
72
+
44
73
  puts "Parsed #{sm.dotfile}.\nGenerating C stub for states: #{sm.states_list.join(", ")}."
45
- if options[:header] then
46
- sm.generate_h
47
- puts "Generated header #{sm.cname}.h"
48
- end
49
- if options[:source] then
50
- sm.generate_c
51
- puts "Generated source #{sm.cname}.c"
74
+ if sm.ino then
75
+ sm.generate_ino
76
+ puts "Generated .ino file #{sm.cname}.ino"
77
+ else
78
+ if options[:header] then
79
+ sm.generate_h
80
+ puts "Generated header #{sm.cname}.h"
81
+ end
82
+ if options[:source] then
83
+ sm.generate_c
84
+ puts "Generated source #{sm.cname}.c"
85
+ end
52
86
  end
87
+
@@ -2,20 +2,27 @@
2
2
 
3
3
  require 'ruby-graphviz'
4
4
  require 'erb'
5
-
5
+ require 'pry'
6
6
  require File.expand_path('../templates.rb', __FILE__)
7
7
  require File.expand_path("../version.rb", __FILE__)
8
8
 
9
9
  module GV_FSM
10
10
  class FSM
11
- attr_reader :states, :transitions, :dotfile
12
- attr_accessor :project_name, :description, :cname
11
+ attr_reader :states, :transitions, :dotfile, :prefix
12
+ attr_accessor :project_name, :description, :cname, :syslog, :ino
13
13
  include GV_FSM::Templates
14
14
 
15
15
  def initialize(filename = nil)
16
+ @prefix = ""
17
+ @syslog = true
18
+ @ino = false
16
19
  parse(filename) if filename
17
20
  end
18
21
 
22
+ def prefix=(v)
23
+ @prefix = v + '_'
24
+ end
25
+
19
26
  def parse(filename)
20
27
  raise ArgumentError, "File must be in .dot format" unless File.extname(filename) == ".dot"
21
28
  @cname = File.basename(filename, ".dot") unless (@cname and ! @cname.empty?)
@@ -25,12 +32,17 @@ module GV_FSM
25
32
  GraphViz.parse(filename) do |g|
26
33
  g.each_node do |id|
27
34
  n = g.get_node(id)
35
+ binding.pry
28
36
  label = n[:label].source.empty? ? "do_#{id}" : n[:label].source
29
- @states << {id: id, function: label}
37
+ @states << {id: id, function: @prefix+label}
30
38
  end
31
39
  g.each_edge do |e|
32
40
  from = e.node_one
33
41
  to = e.node_two
42
+ unless e[:label] then
43
+ @transitions << {from: from, to: to, function: nil}
44
+ next
45
+ end
34
46
  case e[:label].source
35
47
  when ""
36
48
  label = nil
@@ -39,7 +51,7 @@ module GV_FSM
39
51
  else
40
52
  label = e[:label].source
41
53
  end
42
- @transitions << {from: from, to: to, function: label}
54
+ @transitions << {from: from, to: to, function: label ? @prefix+label : nil}
43
55
  end
44
56
  end
45
57
  end
@@ -55,7 +67,7 @@ module GV_FSM
55
67
  def transition_functions_list
56
68
  lst = []
57
69
  @transitions.each do |t|
58
- if !lst.include? t[:function] then
70
+ if t[:function] and !lst.include? t[:function] then
59
71
  lst << (t[:function] or "NULL")
60
72
  end
61
73
  end
@@ -82,6 +94,15 @@ module GV_FSM
82
94
  return dest
83
95
  end
84
96
 
97
+ def transitions_paths
98
+ path = {}
99
+ @transitions.each do |t|
100
+ path[t[:function]] = [] unless path[t[:function]]
101
+ path[t[:function]] << {from: t[:from], to: t[:to]}
102
+ end
103
+ return path
104
+ end
105
+
85
106
  def generate_c(filename = @cname)
86
107
  File.open("#{filename}.c", "w") do |f|
87
108
  f.puts ERB.new(HEADER, 0, "<>").result(binding)
@@ -95,6 +116,15 @@ module GV_FSM
95
116
  f.puts ERB.new(HH, 0, "<>").result(binding)
96
117
  end
97
118
  end
119
+
120
+ def generate_ino(filename=@cname)
121
+ @syslog = false
122
+ File.open("#{filename}.ino", "w") do |f|
123
+ f.puts ERB.new(HEADER, 0, "<>").result(binding)
124
+ f.puts ERB.new(HH, 0, "<>").result(binding)
125
+ f.puts ERB.new(CC, 0, "<>").result(binding)
126
+ end
127
+ end
98
128
  end
99
129
 
100
130
  end
@@ -2,139 +2,261 @@
2
2
  module GV_FSM
3
3
  module Templates
4
4
  HEADER =<<~EOHEADER
5
- // Finite State Machine
6
- // Project: <%= self.project_name or self.dotfile %>
7
- // Description: <%= self.description or "<none given>" %>
8
- //
9
- // Generated by gv_fsm ruby gem, see https://rubygems.org/gems/gv_fsm
10
- // gv_fsm version <%= GV_FSM::VERSION %>
11
- // Generation date: <%= Time.now %>
12
- // Generated from: <%= self.dotfile %>
13
- // The finite state machine has:
14
- // <%= self.states.count %> states
15
- // <%= self.transitions.count %> transitions
5
+ /******************************************************************************
6
+ Finite State Machine
7
+ Project: <%= @project_name or @dotfile %>
8
+ Description: <%= @description or "<none given>" %>
9
+
10
+ Generated by gv_fsm ruby gem, see https://rubygems.org/gems/gv_fsm
11
+ gv_fsm version <%= GV_FSM::VERSION %>
12
+ Generation date: <%= Time.now %>
13
+ Generated from: <%= @dotfile %>
14
+ The finite state machine has:
15
+ <%= @states.count %> states
16
+ <%= transition_functions_list.select {|e| e != 'NULL'}.count %> transition functions
17
+ <% if @prefix != '' %>
18
+ Functions and types have been generated with prefix "<%= @prefix %>"
19
+ <% end %>
20
+ ******************************************************************************/
16
21
 
17
22
  EOHEADER
18
23
 
19
24
  HH =<<~EOH
20
- #ifndef <%= self.cname.upcase %>_H
21
- #define <%= self.cname.upcase %>_H
25
+ <% if !@ino then %>
26
+ #ifndef <%= @cname.upcase %>_H
27
+ #define <%= @cname.upcase %>_H
22
28
  #include <stdlib.h>
29
+ <% end %>
30
+
31
+ // State data object
32
+ // By default set to void; override this typedef or load the proper
33
+ // header if you need
34
+ typedef void <%= @prefix %>state_data_t;
35
+ <% if !@ino then %>
36
+
37
+ // NOTHING SHALL BE CHANGED AFTER THIS LINE!
38
+ <% end %>
23
39
 
24
40
  // List of states
25
41
  typedef enum {
26
- <% self.states.each_with_index do |s, i| %>
27
- STATE_<%= s[:id].upcase %><%= i == 0 ? " = 0" : "" %>,
42
+ <% @states.each_with_index do |s, i| %>
43
+ <%= @prefix.upcase %>STATE_<%= s[:id].upcase %><%= i == 0 ? " = 0" : "" %>,
28
44
  <% end %>
29
- NUM_STATES,
30
- NO_CHANGE
31
- } state_t;
32
-
33
- const char *state_names[] = {<%= self.states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
45
+ <%= @prefix.upcase %>NUM_STATES,
46
+ <%= @prefix.upcase %>NO_CHANGE
47
+ } <%= @prefix %>state_t;
34
48
 
49
+ // State human-readable names
50
+ extern const char *state_names[];
51
+
52
+ <% if transition_functions_list.count > 0 then %>
35
53
  // State function and state transition prototypes
36
- typedef state_t state_func_t(void *data);
37
- typedef void transition_func_t(void *data);
54
+ typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
55
+ typedef void transition_func_t(<%= @prefix %>state_data_t *data);
56
+ <% else %>
57
+ // State function prototype
58
+ typedef <%= @prefix %>state_t state_func_t(<%= @prefix %>state_data_t *data);
59
+ <%end %>
38
60
 
39
- // state functions
40
- <% self.states.each do |s| %>
41
- state_t <%= s[:function] %>(void *data);
61
+ // State functions
62
+ <% dest = destinations.dup %>
63
+ <% @states.each do |s| %>
64
+ <% stable = true if dest[s[:id]].include? s[:id] %>
65
+ <% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} %>
66
+ <% if dest[s[:id]].empty? or stable then
67
+ dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
68
+ end %>
69
+ // Function to be executed in state <%= s[:id] %>
70
+ // valid return states: <%= dest[s[:id]].join(", ") %>
71
+ <%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data);
42
72
  <% end %>
43
73
 
44
- // transition functions
45
- <% self.transition_functions_list.each do |t| %>
46
- <% next if t == "NULL" %>
47
- void <%= t %>(void *data);
48
- <% end %>
49
74
 
50
75
  // List of state functions
51
- state_func_t *const state_table[NUM_STATES] = {
52
- <%= self.state_functions_list.join(",\n ")%>
53
- };
76
+ extern state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES];
77
+
78
+
79
+ <% if transition_functions_list.count > 0 then %>
80
+ // Transition functions
81
+ <% transition_functions_list.each do |t| %>
82
+ <% next if t == "NULL" %>
83
+ void <%= t %>(<%= @prefix %>state_data_t *data);
84
+ <% end %>
54
85
 
55
86
  // Table of transition functions
56
- transition_func_t *const transition_table[NUM_STATES][NUM_STATES] = {
57
- <% sl = self.states_list %>
58
- <% fw = self.transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
59
- <% sw = self.states_list.max {|a, b| a.length <=> b.length}.length %>
60
- /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
61
- <% self.transitions_map.each_with_index do |l, i| %>
62
- /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
87
+ extern transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES];
88
+ <% else %>
89
+ // No transition functions
63
90
  <% end %>
64
- };
65
91
 
66
92
  // state manager
67
- state_t run_state(state_t cur_state, void *data);
93
+ <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, state_data_t *data);
68
94
 
95
+ <% if !@ino then %>
69
96
  #endif
97
+ <% end %>
70
98
  EOH
71
99
 
72
100
  CC =<<~EOC
101
+ <% if !@ino then %>
102
+ <% if @syslog then %>
73
103
  #include <syslog.h>
74
- #include "<%= self.cname %>.h"
104
+ <% end %>
105
+ #include "<%= @cname %>.h"
106
+ <% end %>
75
107
 
76
- // State functions
77
- <% dest = self.destinations.dup %>
78
- <% self.states.each do |s| %>
108
+ <% placeholder = "Your Code Here" %>
109
+ // SEARCH FOR <%= placeholder %> FOR CODE INSERTION POINTS!
110
+
111
+ // GLOBALS
112
+ // State human-readable names
113
+ const char *state_names[] = {<%= states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
114
+
115
+ // List of state functions
116
+ <% fw = state_functions_list.max {|a, b| a.length <=> b.length}.length %>
117
+ state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES] = {
118
+ <% @states.each do |s| %>
119
+ <%= (s[:function] + ',').ljust(fw+1) %> // in state <%= s[:id] %>
120
+ <% end %>
121
+ };
122
+ <% if transition_functions_list.count > 0 then %>
123
+
124
+ // Table of transition functions
125
+ transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES] = {
126
+ <% sl = states_list %>
127
+ <% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
128
+ <% sw = [states_list, "states:"].flatten.max {|a, b| a.length <=> b.length}.length %>
129
+ /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
130
+ <% transitions_map.each_with_index do |l, i| %>
131
+ /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
132
+ <% end %>
133
+ };
134
+ <% else %>
135
+ // No transition functions
136
+ <% end %>
137
+
138
+ // ____ _ _
139
+ // / ___|| |_ __ _| |_ ___
140
+ // \\___ \\| __/ _` | __/ _ \\
141
+ // ___) | || (_| | || __/
142
+ // |____/ \\__\\__,_|\\__\\___|
143
+ //
144
+ // __ _ _
145
+ // / _|_ _ _ __ ___| |_(_) ___ _ __ ___
146
+ // | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
147
+ // | _| |_| | | | | (__| |_| | (_) | | | \\__ \\
148
+ // |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
149
+ //
150
+ <% dest = destinations.dup %>
151
+ <% @states.each do |s| %>
79
152
  <% stable = true if dest[s[:id]].include? s[:id] %>
80
- <% dest[s[:id]].map! {|n| "STATE_"+n.upcase} %>
153
+ <% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} %>
81
154
  <% if dest[s[:id]].empty? or stable then
82
- dest[s[:id]].unshift "NO_CHANGE"
155
+ dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
83
156
  end %>
84
- state_t <%= s[:function] %>(void *data) {
85
- state_t next_state = <%= dest[s[:id]].first %>;
157
+ // Function to be executed in state <%= s[:id] %>
158
+ // valid return states: <%= dest[s[:id]].join(", ") %>
159
+ <%= @prefix %>state_t <%= s[:function] %>(<%= @prefix %>state_data_t *data) {
160
+ <%= @prefix %>state_t next_state = <%= dest[s[:id]].first %>;
86
161
 
162
+ <% if @syslog then %>
87
163
  syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
88
- /* Your code here */
164
+ <% end %>
165
+ /* <%= placeholder %> */
89
166
 
90
- // valid return states: <%= dest[s[:id]].join(", ") %>
91
167
  switch (next_state) {
92
168
  <% dest[s[:id]].each do |str| %>
93
169
  case <%= str %>:
94
170
  <% end %>
95
171
  break;
96
172
  default:
173
+ <% if @syslog then %>
97
174
  syslog(LOG_WARNING, "[FSM] Cannot pass from <%= s[:id] %> to %s, remaining in this state", state_names[next_state]);
98
- next_state = NO_CHANGE;
175
+ <% end %>
176
+ next_state = <%= @prefix.upcase %>NO_CHANGE;
99
177
  }
100
178
  return next_state;
101
179
  }
102
180
 
103
181
  <% end %>
104
182
 
105
- // Transition functions
106
- <% self.transition_functions_list.each do |t| %>
183
+ <% if transition_functions_list.count > 0 then %>
184
+ // _____ _ _ _
185
+ // |_ _| __ __ _ _ __ ___(_) |_(_) ___ _ __
186
+ // | || '__/ _` | '_ \\/ __| | __| |/ _ \\| '_ \\
187
+ // | || | | (_| | | | \\__ \\ | |_| | (_) | | | |
188
+ // |_||_| \\__,_|_| |_|___/_|\\__|_|\\___/|_| |_|
189
+ //
190
+ // __ _ _
191
+ // / _|_ _ _ __ ___| |_(_) ___ _ __ ___
192
+ // | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
193
+ // | _| |_| | | | | (__| |_| | (_) | | | \\__ \\
194
+ // |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
195
+ //
196
+
197
+ <% transition_functions_list.each do |t| %>
107
198
  <% next if t == "NULL" %>
108
- void <%= t %>(void *data) {
199
+ <% tpaths = transitions_paths[t] %>
200
+ // This function is called in <%= tpaths.count %> transition<%= tpaths.count == 1 ? '' : 's' %>:
201
+ <% tpaths.each_with_index do |e, i| %>
202
+ // <%= i+1 %>. from <%= e[:from] %> to <%= e[:to] %>
203
+ <% end %>
204
+ void <%= t %>(<%= @prefix %>state_data_t *data) {
205
+ <% if @syslog then %>
109
206
  syslog(LOG_INFO, "[FSM] State transition <%= t %>");
110
- /* Your code here */
207
+ <% end %>
208
+ /* <%= placeholder %> */
111
209
  }
112
210
 
211
+ <% end %>
113
212
  <% end %>
114
213
 
115
- // State manager
116
- state_t run_state(state_t cur_state, void *data) {
117
- state_t new_state = state_table[cur_state](data);
118
- transition_func_t *transition = transition_table[cur_state][new_state];
214
+ // ____ _ _
215
+ // / ___|| |_ __ _| |_ ___
216
+ // \\___ \\| __/ _` | __/ _ \\
217
+ // ___) | || (_| | || __/
218
+ // |____/ \\__\\__,_|\\__\\___|
219
+ //
220
+ //
221
+ // _ __ ___ __ _ _ __ __ _ __ _ ___ _ __
222
+ // | '_ ` _ \\ / _` | '_ \\ / _` |/ _` |/ _ \\ '__|
223
+ // | | | | | | (_| | | | | (_| | (_| | __/ |
224
+ // |_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_|
225
+ // |___/
226
+
227
+ <%= @prefix %>state_t <%= @prefix %>run_state(<%= @prefix %>state_t cur_state, <%= @prefix %>state_data_t *data) {
228
+ <%= @prefix %>state_t new_state = <%= @prefix %>state_table[cur_state](data);
229
+ <% if transition_functions_list.count > 0 then %>
230
+ transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
119
231
  if (transition)
120
232
  transition(data);
121
- return new_state == NO_CHANGE ? cur_state : new_state;
233
+ <% end %>
234
+ return new_state == <%= @prefix.upcase %>NO_CHANGE ? cur_state : new_state;
122
235
  };
123
236
 
124
-
237
+ <% if @ino then %>
238
+ /* Example usage:
239
+ <% else %>
125
240
  #ifdef TEST_MAIN
126
241
  #include <unistd.h>
127
242
  int main() {
128
- state_t cur_state = STATE_INIT;
243
+ <% end %>
244
+ <%= @prefix %>state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
245
+ <% if @syslog then %>
129
246
  openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
130
247
  syslog(LOG_INFO, "Starting SM");
248
+ <% end %>
131
249
  do {
132
- cur_state = run_state(cur_state, NULL);
250
+ cur_state = <%= @prefix %>run_state(cur_state, NULL);
133
251
  sleep(1);
134
- } while (cur_state != STATE_STOP);
252
+ } while (cur_state != <%= @prefix.upcase %>STATE_STOP);
253
+ <% if !@ino then %>
135
254
  return 0;
136
255
  }
137
256
  #endif
257
+ <% else %>
258
+ */
259
+ <% end %>
138
260
  EOC
139
261
  end
140
262
  end
@@ -1,3 +1,3 @@
1
1
  module GV_FSM
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.3"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gv_fsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paolo Bosetti
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-08 00:00:00.000000000 Z
11
+ date: 2020-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-graphviz