gv_fsm 0.0.1 → 0.1.2

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 +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: {}