gv_fsm 0.1.0 → 0.2.3

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