gv_fsm 0.0.1 → 0.1.2

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 +52 -11
  3. data/lib/gv_fsm.rb +92 -57
  4. data/lib/templates.rb +185 -132
  5. data/lib/version.rb +3 -0
  6. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e22ea784bd92deabd21520f674e347e1edbebcdb3f1acba0256b62994c97a0f
4
- data.tar.gz: ab0a2f310beed50a681582ce1b64fd32edb49ab5329150e7f152dbd8d6e99daf
3
+ metadata.gz: 450afef3dccacff09f92f297a5b5bcbdfb9d1c960bea0d546bcafe7123f5cebd
4
+ data.tar.gz: 1fa684d748abecd8c8f17ac2a2b7ca17c83506003eb2cc143596668a32d0a1ea
5
5
  SHA512:
6
- metadata.gz: 9ee624e62959b91f95cf9d5ac3aa02a7172424f8738ea615bb8193aec2631a6a4d31354c60a9958470f9198105406c65f3658c3191d60c345ec0a4a799c76abe
7
- data.tar.gz: da45ed138e0ae9d2f54937614ad91bd903aeac8b352761eb0c996b8888ad2079a73b280c09a4185d3dadde6d60fb4045723fd8156d589abd6ce535dfe8b6a8ae
6
+ metadata.gz: 98d883a1bff3afaae6fe1531a8f702b6600c97bd37fa4260b3eef02ecccfaf978ab3a9acc096a0c62d9d95c2b4450df105a5cc38f1a03f624984813c9ac5bcb9
7
+ data.tar.gz: 20af124b281a5aeba975d5124c4a552480d03ea9d4cb13903f4c60f3408c28876adf12a040455dd48afb98ebed8dedb0ee65121e6e93e0d3c9d854a43e4fea57
data/bin/gv_fsm CHANGED
@@ -1,19 +1,60 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "gv_fsm"
4
+ require 'optparse'
4
5
 
5
- SM = FSM.new(ARGV[0])
6
- SM.project_name = "test project"
7
- SM.description = "FSM designed to test the generator"
8
- SM.cname = "test_fsm"
6
+ sm = GV_FSM::FSM.new
9
7
 
10
- File.open("#{SM.cname}.h", "w") do |f|
11
- f.puts ERB.new(Templates::HEADER, 0, "<>").result
12
- f.puts ERB.new(Templates::HH, 0, "<>").result
13
- end
8
+ options = {header: true, source: true}
9
+ OptionParser.new do |parser|
10
+ parser.banner =<<~EOB
11
+ Graphviz to Finite State Machine generator
12
+ Version: #{GV_FSM::VERSION}
13
+ See also https://github.com/pbosetti/gv_fsm
14
+
15
+ Usage: gv_fsm [options] scheme.dot
16
+ EOB
17
+
18
+ parser.on("-p", "--project PROJECT_NAME",
19
+ "Set the project name to PROJECT_NAME") do |pn|
20
+ sm.project_name = pn
21
+ end
22
+
23
+ parser.on("-d", "--description DESCRIPTION", "Use DESCRITION string in header") do |desc|
24
+ sm.description = desc
25
+ end
26
+
27
+ parser.on("-o", "--output_file NAME", "Use NAME for generated .c and .h files") do |f|
28
+ sm.cname = f
29
+ end
30
+
31
+ parser.on("-h", "--header-only", "Only generate header file") do
32
+ options[:source] = false
33
+ end
34
+
35
+ parser.on("-s", "--source-only", "Only generate source file") do
36
+ options[:header] = false
37
+ end
14
38
 
15
- File.open("#{SM.cname}.c", "w") do |f|
16
- f.puts ERB.new(Templates::HEADER, 0, "<>").result
17
- f.puts ERB.new(Templates::CC, 0, "<>").result
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
+ end.parse!
44
+
45
+ raise ArgumentError, "I need the path to a Graphviz file!" unless ARGV[0]
46
+ raise ArgumentError, "#{ARGV[0]} does not look like a Graphviz file!" unless File.extname(ARGV[0]) == ".dot"
47
+
48
+ sm.parse(ARGV[0])
49
+
50
+ puts "Parsed #{sm.dotfile}.\nGenerating C stub for states: #{sm.states_list.join(", ")}."
51
+ if options[:header] then
52
+ sm.generate_h
53
+ puts "Generated header #{sm.cname}.h"
54
+ end
55
+ if options[:source] then
56
+ sm.generate_c
57
+ puts "Generated source #{sm.cname}.c"
18
58
  end
19
59
 
60
+
@@ -3,78 +3,113 @@
3
3
  require 'ruby-graphviz'
4
4
  require 'erb'
5
5
 
6
- require './templates.rb'
6
+ require File.expand_path('../templates.rb', __FILE__)
7
+ require File.expand_path("../version.rb", __FILE__)
7
8
 
8
- class FSM
9
- attr_reader :states, :transitions, :dotfile
10
- attr_accessor :project_name, :description, :cname
9
+ module GV_FSM
10
+ class FSM
11
+ attr_reader :states, :transitions, :dotfile, :prefix
12
+ attr_accessor :project_name, :description, :cname
13
+ include GV_FSM::Templates
11
14
 
12
- def initialize(filename)
13
- parse(filename)
14
- end
15
+ def initialize(filename = nil)
16
+ @prefix = ""
17
+ parse(filename) if filename
18
+ end
19
+
20
+ def prefix=(v)
21
+ @prefix = v + '_'
22
+ end
15
23
 
16
- def parse(filename)
17
- @dotfile = filename
18
- @states = []
19
- @transitions = []
20
- GraphViz.parse(filename) do |g|
21
- g.each_node do |id|
22
- n = g.get_node(id)
23
- label = n[:label].source.empty? ? "do_#{id}" : n[:label].source
24
- @states << {id: id, function: label}
24
+ def parse(filename)
25
+ raise ArgumentError, "File must be in .dot format" unless File.extname(filename) == ".dot"
26
+ @cname = File.basename(filename, ".dot") unless (@cname and ! @cname.empty?)
27
+ @dotfile = filename
28
+ @states = []
29
+ @transitions = []
30
+ GraphViz.parse(filename) do |g|
31
+ g.each_node do |id|
32
+ n = g.get_node(id)
33
+ label = n[:label].source.empty? ? "do_#{id}" : n[:label].source
34
+ @states << {id: id, function: @prefix+label}
35
+ end
36
+ g.each_edge do |e|
37
+ from = e.node_one
38
+ to = e.node_two
39
+ next unless e[:label]
40
+ case e[:label].source
41
+ when ""
42
+ label = nil
43
+ when /[#]/
44
+ label = "#{from}_to_#{to}"
45
+ else
46
+ label = e[:label].source
47
+ end
48
+ @transitions << {from: from, to: to, function: label ? @prefix+label : nil}
49
+ end
25
50
  end
26
- g.each_edge do |e|
27
- from = e.node_one
28
- to = e.node_two
29
- case e[:label].source
30
- when ""
31
- label = nil
32
- when /[#]/
33
- label = "#{from}_to_#{to}"
34
- else
35
- label = e[:label].source
51
+ end
52
+
53
+ def state_functions_list
54
+ @states.map {|s| s[:function]}
55
+ end
56
+
57
+ def states_list
58
+ @states.map {|s| s[:id]}
59
+ end
60
+
61
+ def transition_functions_list
62
+ lst = []
63
+ @transitions.each do |t|
64
+ if !lst.include? t[:function] then
65
+ lst << (t[:function] or "NULL")
36
66
  end
37
- @transitions << {from: from, to: to, function: label}
38
67
  end
68
+ return lst
39
69
  end
40
- end
41
70
 
42
- def state_functions_list
43
- @states.map {|s| s[:function]}
44
- end
71
+ def transitions_map
72
+ idx = {}
73
+ map = Array.new(@states.count)
74
+ map.map! {|e| e = Array.new(@states.count, "NULL")}
75
+ states_list.each_with_index {|s, i| idx[s] = i }
76
+ @transitions.each do |t|
77
+ map[idx[t[:from]]][idx[t[:to]]] = (t[:function] or "NULL")
78
+ end
79
+ map
80
+ end
45
81
 
46
- def states_list
47
- @states.map {|s| s[:id]}
48
- end
82
+ def destinations
83
+ dest = Hash[states_list.map {|x| [x, []]}]
84
+ @transitions.each do |t|
85
+ dest[t[:from]] = [] unless dest[t[:from]]
86
+ dest[t[:from]] << t[:to]
87
+ end
88
+ return dest
89
+ end
49
90
 
50
- def transition_functions_list
51
- lst = []
52
- @transitions.each do |t|
53
- if !lst.include? t[:function] then
54
- lst << (t[:function] or "NULL")
91
+ def transitions_paths
92
+ path = {}
93
+ @transitions.each do |t|
94
+ path[t[:function]] = [] unless path[t[:function]]
95
+ path[t[:function]] << {from: t[:from], to: t[:to]}
55
96
  end
97
+ return path
56
98
  end
57
- return lst
58
- end
59
99
 
60
- def transitions_map
61
- idx = {}
62
- map = Array.new(@states.count)
63
- map.map! {|e| e = Array.new(@states.count, "NULL")}
64
- states_list.each_with_index {|s, i| idx[s] = i }
65
- @transitions.each do |t|
66
- map[idx[t[:from]]][idx[t[:to]]] = (t[:function] or "NULL")
100
+ def generate_c(filename = @cname)
101
+ File.open("#{filename}.c", "w") do |f|
102
+ f.puts ERB.new(HEADER, 0, "<>").result(binding)
103
+ f.puts ERB.new(CC, 0, "<>").result(binding)
104
+ end
67
105
  end
68
- map
69
- end
70
106
 
71
- def destinations
72
- dest = Hash[states_list.map {|x| [x, []]}]
73
- @transitions.each do |t|
74
- dest[t[:from]] = [] unless dest[t[:from]]
75
- dest[t[:from]] << t[:to]
107
+ def generate_h(filename = @cname)
108
+ File.open("#{filename}.h", "w") do |f|
109
+ f.puts ERB.new(HEADER, 0, "<>").result(binding)
110
+ f.puts ERB.new(HH, 0, "<>").result(binding)
111
+ end
76
112
  end
77
- return dest
78
113
  end
79
- end
80
114
 
115
+ end
@@ -1,136 +1,189 @@
1
1
 
2
- module Templates
3
- HEADER =<<~EOHEADER
4
- // Finite State Machine
5
- // Project: <%= SM.project_name or SM.dotfile %>
6
- // Description: <%= SM.description or "<none given>" %>
7
- //
8
- // Generation date: <%= Time.now %>
9
- // Generated from: <%= SM.dotfile %>
10
- // The finite state machine has:
11
- // <%= SM.states.count %> states
12
- // <%= SM.transitions.count %> transitions
13
-
14
- EOHEADER
15
-
16
- HH =<<~EOH
17
- #ifndef <%= SM.cname.upcase %>_H
18
- #define <%= SM.cname.upcase %>_H
19
- #include <stdlib.h>
20
-
21
- // List of states
22
- typedef enum {
23
- <% SM.states.each_with_index do |s, i| %>
24
- STATE_<%= s[:id].upcase %><%= i == 0 ? " = 0" : "" %>,
25
- <% end %>
26
- NUM_STATES,
27
- NO_CHANGE
28
- } state_t;
29
-
30
- const char *state_names[] = {<%= SM.states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
31
-
32
- // State function and state transition prototypes
33
- typedef state_t state_func_t(void *data);
34
- typedef void transition_func_t(void *data);
35
-
36
- // state functions
37
- <% SM.states.each do |s| %>
38
- state_t <%= s[:function] %>(void *data);
39
- <% end %>
40
-
41
- // transition functions
42
- <% SM.transition_functions_list.each do |t| %>
43
- <% next if t == "NULL" %>
44
- void <%= t %>(void *data);
45
- <% end %>
46
-
47
- // List of state functions
48
- state_func_t *const state_table[NUM_STATES] = {
49
- <%= SM.state_functions_list.join(",\n ")%>
50
- };
51
-
52
- // Table of transition functions
53
- transition_func_t *const transition_table[NUM_STATES][NUM_STATES] = {
54
- <% sl = SM.states_list %>
55
- <% fw = SM.transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
56
- <% sw = SM.states_list.max {|a, b| a.length <=> b.length}.length %>
57
- /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
58
- <% SM.transitions_map.each_with_index do |l, i| %>
59
- /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
60
- <% end %>
61
- };
62
-
63
- // state manager
64
- state_t run_state(state_t cur_state, void *data);
65
-
66
- #endif
67
- EOH
68
-
69
- CC =<<~EOC
70
- #include <syslog.h>
71
- #include "<%= SM.cname %>.h"
72
-
73
- // State functions
74
- <% dest = SM.destinations.dup %>
75
- <% SM.states.each do |s| %>
76
- <% stable = true if dest[s[:id]].include? s[:id] %>
77
- <% dest[s[:id]].map! {|n| "STATE_"+n.upcase} %>
78
- <% if dest[s[:id]].empty? or stable then
79
- dest[s[:id]].unshift "NO_CHANGE"
80
- end %>
81
- state_t <%= s[:function] %>(void *data) {
82
- state_t next_state = <%= dest[s[:id]].first %>;
83
-
84
- syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
85
- /* Your code here */
2
+ module GV_FSM
3
+ module Templates
4
+ HEADER =<<~EOHEADER
5
+ // Finite State Machine
6
+ // Project: <%= @project_name or @dotfile %>
7
+ // Description: <%= @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: <%= @dotfile %>
13
+ // The finite state machine has:
14
+ // <%= @states.count %> states
15
+ // <%= @transitions.count %> transitions
16
+ // <%= transition_functions_list.select {|e| e != 'NULL'}.count %> transition functions
17
+
18
+ EOHEADER
19
+
20
+ HH =<<~EOH
21
+ #ifndef <%= @cname.upcase %>_H
22
+ #define <%= @cname.upcase %>_H
23
+ #include <stdlib.h>
24
+
25
+ // List of states
26
+ typedef enum {
27
+ <% @states.each_with_index do |s, i| %>
28
+ <%= @prefix.upcase %>STATE_<%= s[:id].upcase %><%= i == 0 ? " = 0" : "" %>,
29
+ <% end %>
30
+ <%= @prefix.upcase %>NUM_STATES,
31
+ <%= @prefix.upcase %>NO_CHANGE
32
+ } state_t;
33
+
34
+ const char *state_names[] = {<%= states_list.map {|sn| '"'+sn+'"'}.join(", ") %>};
35
+
36
+ // State function and state transition prototypes
37
+ typedef state_t state_func_t(void *data);
38
+ typedef void transition_func_t(void *data);
39
+
40
+ // state functions
41
+ <% @states.each do |s| %>
42
+ state_t <%= s[:function] %>(void *data);
43
+ <% end %>
44
+
45
+ // List of state functions
46
+ state_func_t *const <%= @prefix %>state_table[<%= @prefix.upcase %>NUM_STATES] = {
47
+ <%= state_functions_list.join(",\n ")%>
48
+ };
86
49
 
87
- // valid return states: <%= dest[s[:id]].join(", ") %>
88
- switch (next_state) {
89
- <% dest[s[:id]].each do |str| %>
90
- case <%= str %>:
91
- <% end %>
92
- break;
93
- default:
94
- syslog(LOG_WARNING, "[FSM] Cannot pass from <%= s[:id] %> to %s, remaining in this state", state_names[next_state]);
95
- next_state = NO_CHANGE;
50
+ <% if transition_functions_list.count > 0 then %>
51
+ // transition functions
52
+ <% transition_functions_list.each do |t| %>
53
+ <% next if t == "NULL" %>
54
+ void <%= t %>(void *data);
55
+ <% end %>
56
+
57
+ // Table of transition functions
58
+ transition_func_t *const <%= @prefix %>transition_table[<%= @prefix.upcase %>NUM_STATES][<%= @prefix.upcase %>NUM_STATES] = {
59
+ <% sl = states_list %>
60
+ <% fw = transition_functions_list.max {|a, b| a.length <=> b.length}.length %>
61
+ <% sw = states_list.max {|a, b| a.length <=> b.length}.length %>
62
+ /* <%= "states:".ljust(sw) %> <%= sl.map {|e| e.ljust(fw) }.join(", ") %> */
63
+ <% transitions_map.each_with_index do |l, i| %>
64
+ /* <%= sl[i].ljust(sw) %> */ {<%= l.map {|e| e.ljust(fw)}.join(", ") %>},
65
+ <% end %>
66
+ };
67
+ <% else %>
68
+ // No transition functions
69
+ <% end %>
70
+
71
+ // state manager
72
+ state_t <%= @prefix %>run_state(state_t cur_state, void *data);
73
+
74
+ #endif
75
+ EOH
76
+
77
+ CC =<<~EOC
78
+ #include <syslog.h>
79
+ #include "<%= @cname %>.h"
80
+
81
+ // ____ _ _
82
+ // / ___|| |_ __ _| |_ ___
83
+ // \\___ \\| __/ _` | __/ _ \\
84
+ // ___) | || (_| | || __/
85
+ // |____/ \\__\\__,_|\\__\\___|
86
+ //
87
+ // __ _ _
88
+ // / _|_ _ _ __ ___| |_(_) ___ _ __ ___
89
+ // | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
90
+ // | _| |_| | | | | (__| |_| | (_) | | | \\__ \\
91
+ // |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
92
+ //
93
+ <% dest = destinations.dup %>
94
+ <% @states.each do |s| %>
95
+ <% stable = true if dest[s[:id]].include? s[:id] %>
96
+ <% dest[s[:id]].map! {|n| (@prefix+"STATE_"+n).upcase} %>
97
+ <% if dest[s[:id]].empty? or stable then
98
+ dest[s[:id]].unshift @prefix.upcase+"NO_CHANGE"
99
+ end %>
100
+ // To be executed in state <%= s[:id] %>
101
+ state_t <%= s[:function] %>(void *data) {
102
+ state_t next_state = <%= dest[s[:id]].first %>;
103
+
104
+ syslog(LOG_INFO, "[FSM] In state <%= s[:id] %>");
105
+ /* Your code here */
106
+
107
+ // valid return states: <%= dest[s[:id]].join(", ") %>
108
+ switch (next_state) {
109
+ <% dest[s[:id]].each do |str| %>
110
+ case <%= str %>:
111
+ <% end %>
112
+ break;
113
+ default:
114
+ syslog(LOG_WARNING, "[FSM] Cannot pass from <%= s[:id] %> to %s, remaining in this state", state_names[next_state]);
115
+ next_state = <%= @prefix.upcase %>NO_CHANGE;
116
+ }
117
+ return next_state;
118
+ }
119
+
120
+ <% end %>
121
+
122
+ <% if transition_functions_list.count > 0 then %>
123
+ // _____ _ _ _
124
+ // |_ _| __ __ _ _ __ ___(_) |_(_) ___ _ __
125
+ // | || '__/ _` | '_ \\/ __| | __| |/ _ \\| '_ \\
126
+ // | || | | (_| | | | \\__ \\ | |_| | (_) | | | |
127
+ // |_||_| \\__,_|_| |_|___/_|\\__|_|\\___/|_| |_|
128
+ //
129
+ // __ _ _
130
+ // / _|_ _ _ __ ___| |_(_) ___ _ __ ___
131
+ // | |_| | | | '_ \\ / __| __| |/ _ \\| '_ \\/ __|
132
+ // | _| |_| | | | | (__| |_| | (_) | | | \\__ \\
133
+ // |_| \\__,_|_| |_|\\___|\\__|_|\\___/|_| |_|___/
134
+ //
135
+
136
+ <% transition_functions_list.each do |t| %>
137
+ <% next if t == "NULL" %>
138
+ // This function is called in transitions:
139
+ <% transitions_paths[t].each do |e| %>
140
+ // from <%= e[:from] %> to <%= e[:to] %>
141
+ <% end %>
142
+ void <%= t %>(void *data) {
143
+ syslog(LOG_INFO, "[FSM] State transition <%= t %>");
144
+ /* Your code here */
145
+ }
146
+
147
+ <% end %>
148
+ <% end %>
149
+
150
+ // ____ _ _
151
+ // / ___|| |_ __ _| |_ ___
152
+ // \\___ \\| __/ _` | __/ _ \\
153
+ // ___) | || (_| | || __/
154
+ // |____/ \\__\\__,_|\\__\\___|
155
+ //
156
+ //
157
+ // _ __ ___ __ _ _ __ __ _ __ _ ___ _ __
158
+ // | '_ ` _ \\ / _` | '_ \\ / _` |/ _` |/ _ \\ '__|
159
+ // | | | | | | (_| | | | | (_| | (_| | __/ |
160
+ // |_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_|
161
+ // |___/
162
+
163
+ state_t <%= @prefix %>run_state(state_t cur_state, void *data) {
164
+ state_t new_state = <%= @prefix %>state_table[cur_state](data);
165
+ <% if transition_functions_list.count > 0 then %>
166
+ transition_func_t *transition = <%= @prefix %>transition_table[cur_state][new_state];
167
+ if (transition)
168
+ transition(data);
169
+ <% end %>
170
+ return new_state == <%= @prefix.upcase %>NO_CHANGE ? cur_state : new_state;
171
+ };
172
+
173
+
174
+ #ifdef TEST_MAIN
175
+ #include <unistd.h>
176
+ int main() {
177
+ state_t cur_state = <%= @prefix.upcase %>STATE_INIT;
178
+ openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
179
+ syslog(LOG_INFO, "Starting SM");
180
+ do {
181
+ cur_state = run_state(cur_state, NULL);
182
+ sleep(1);
183
+ } while (cur_state != <%= @prefix.upcase %>STATE_STOP);
184
+ return 0;
96
185
  }
97
- return next_state;
98
- }
99
-
100
- <% end %>
101
-
102
- // Transition functions
103
- <% SM.transition_functions_list.each do |t| %>
104
- <% next if t == "NULL" %>
105
- void <%= t %>(void *data) {
106
- syslog(LOG_INFO, "[FSM] State transition <%= t %>");
107
- /* Your code here */
108
- }
109
-
110
- <% end %>
111
-
112
- // State manager
113
- state_t run_state(state_t cur_state, void *data) {
114
- state_t new_state = state_table[cur_state](data);
115
- transition_func_t *transition = transition_table[cur_state][new_state];
116
- if (transition)
117
- transition(data);
118
- return new_state == NO_CHANGE ? cur_state : new_state;
119
- };
120
-
121
-
122
- #ifdef TEST_MAIN
123
- #include <unistd.h>
124
- int main() {
125
- state_t cur_state = STATE_INIT;
126
- openlog("SM", LOG_PID | LOG_PERROR, LOG_USER);
127
- syslog(LOG_INFO, "Starting SM");
128
- do {
129
- cur_state = run_state(cur_state, NULL);
130
- sleep(1);
131
- } while (cur_state != STATE_STOP);
132
- return 0;
133
- }
134
- #endif
135
- EOC
186
+ #endif
187
+ EOC
188
+ end
136
189
  end
@@ -0,0 +1,3 @@
1
+ module GV_FSM
2
+ VERSION = "0.1.2"
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.0.1
4
+ version: 0.1.2
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-28 00:00:00.000000000 Z
11
+ date: 2020-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-graphviz
@@ -35,7 +35,8 @@ files:
35
35
  - bin/gv_fsm
36
36
  - lib/gv_fsm.rb
37
37
  - lib/templates.rb
38
- homepage: https://rubygems.org/gems/gv_fsm
38
+ - lib/version.rb
39
+ homepage: https://github.com/pbosetti/gv_fsm
39
40
  licenses:
40
41
  - MIT
41
42
  metadata: {}