petri_net 0.8.0 → 0.9.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6280711333c42af2d42f9f72dc8d7e9addf491d0
4
- data.tar.gz: 0f5c88804ad2453ac2ac5f58ed48b402e6a46332
3
+ metadata.gz: 785d6ed616e72a8f4229c584b3625bd182dcc354
4
+ data.tar.gz: 7ae508f907786e328f3148ab86c5653bdc6e03c2
5
5
  SHA512:
6
- metadata.gz: d11cb20273590228cc3324d6e5483eb6f6d79fb2cf8e3217200fb86e4464d511126eb2c9a2843c11ec8f69a54ff061d63f3c4037fd9b7c637dabe749eda05d4c
7
- data.tar.gz: 4bf91f1daf2975b146139be896386cc1704d393f33d3d4936afc4e7e607b97ad214d09c674dd6c189c3b51b5a25a323308a78a8201bcc7e3ba5ad249c530f0b1
6
+ metadata.gz: 71ec4878553ee6f33000bf9901d2ba1b9707ad3ddb0e793812ec19bbc027ce2ea1a77ee2669f1f625210f82f13744ecd304614379c2348d76fa33288d4600ff0
7
+ data.tar.gz: 2eba62b83d60d28a62d14a67d07bb1981a2026a3f693b959f264bd7bd0c9e7f24fe6f556a7415c4db82edb9fd675aff38b8e5d28dee7a8e5634445d8691b4afa
@@ -5,7 +5,8 @@ rvm:
5
5
  - 2.0.0
6
6
 
7
7
  before_install:
8
- gem install net-sftp
8
+ - gem install net-sftp
9
+ - sudo apt-get install graphviz
9
10
 
10
11
  script:
11
- rake test
12
+ - rake test
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in petri.gemspec
4
4
  gemspec
5
+
6
+ #gem 'rgl', "~>0.4.1"
7
+ gem 'rgl', :git => 'https://github.com/monora/rgl.git'
@@ -31,6 +31,7 @@ class PetriNet::Graph::Edge < PetriNet::Base
31
31
 
32
32
  # Validates the data holded by this edge, this will be used while adding the edge to the graph
33
33
  def validate
34
+ return false unless ( @graph.nodes.has_key? @source.name and @graph.nodes.has_key? @destination.name )
34
35
  true
35
36
  end
36
37
 
@@ -43,7 +44,7 @@ class PetriNet::Graph::Edge < PetriNet::Base
43
44
  (@source == object.yource && @destination == oject.destination)
44
45
  end
45
46
  def to_s
46
- "#{@id}: #{@name} #{@source.id} -> #{@destination} )"
47
+ "#{@id}: #{@name} #{@source} -> #{@destination} )"
47
48
  end
48
49
 
49
50
  private
@@ -1,5 +1,7 @@
1
1
  require 'graphviz'
2
-
2
+ require 'graphviz/theory'
3
+ require 'rgl/adjacency'
4
+ require 'rgl/dijkstra'
3
5
  class PetriNet::InfiniteReachabilityGraphError < RuntimeError
4
6
  end
5
7
 
@@ -7,6 +9,10 @@ class PetriNet::Graph < PetriNet::Base
7
9
 
8
10
  # The PetriNet this graph belongs to
9
11
  attr_reader :net
12
+ # all nodes from this graph
13
+ attr_reader :nodes
14
+ # all edges of this graph
15
+ attr_reader :edges
10
16
 
11
17
  def initialize(net, options = Hash.new)
12
18
  @net = net
@@ -29,6 +35,10 @@ class PetriNet::Graph < PetriNet::Base
29
35
  node.graph = self
30
36
  return node.id
31
37
  end
38
+ if @objects.include? node
39
+ res = (@objects.index node) * -1
40
+ return res
41
+ end
32
42
  return false
33
43
  end
34
44
  alias_method :add_node!, :add_node
@@ -38,12 +48,24 @@ class PetriNet::Graph < PetriNet::Base
38
48
  @objects[edge.id] = edge
39
49
  @edges[edge.name] = edge.id
40
50
  edge.graph = self
51
+ edge.source.outputs << edge.id
52
+ edge.destination.inputs << edge.id
41
53
  return edge.id
42
54
  end
43
55
  return false
44
56
  end
45
57
 
46
- # Add an object to the Petri Net.
58
+ def get_edge(source, dest)
59
+ res = nil
60
+ @edges.each_value do |edge|
61
+ if @objects[edge].source == source && @objects[edge].destination == dest
62
+ res = @objects[edge]
63
+ end
64
+ end
65
+ res
66
+ end
67
+
68
+ # Add an object to the Graph.
47
69
  def <<(object)
48
70
  case object.class.to_s
49
71
  when "Array"
@@ -59,7 +81,16 @@ class PetriNet::Graph < PetriNet::Base
59
81
  end
60
82
  alias_method :add_object, :<<
61
83
 
62
- def get_node(id)
84
+ def get_node(node)
85
+ if node.class.to_s == "Fixnum"
86
+ return @objects[node]
87
+ end
88
+ if node.class.to_s == "Array"
89
+ return @objects.select{|o| o.class.to_s == "PetriNet::ReachabilityGraph::Node" && o.markings == node}.first
90
+ end
91
+ end
92
+
93
+ def get_object(id)
63
94
  @objects[id]
64
95
  end
65
96
 
@@ -71,6 +102,13 @@ class PetriNet::Graph < PetriNet::Base
71
102
  res
72
103
  end
73
104
 
105
+ def infinite?
106
+ @nodes.each_value do |node|
107
+ return true if @objects[node].infinite?
108
+ end
109
+ false
110
+ end
111
+
74
112
  def to_gv(output = 'png', filename = '')
75
113
  g = generate_gv
76
114
  if filename.empty?
@@ -95,7 +133,18 @@ class PetriNet::Graph < PetriNet::Base
95
133
  e.label = @objects[edge].transition
96
134
  end
97
135
  end
98
- g
136
+ @gv = g
137
+ end
138
+
139
+ def generate_rgl
140
+ g = RGL::DirectedAdjacencyGraph.new
141
+ @nodes.each_value do |node|
142
+ g.add_vertex @objects[node].markings.to_s
143
+ end
144
+ @edges.each_value do |edge|
145
+ g.add_edge @objects[edge].source.markings.to_s, @objects[edge].destination.markings.to_s
146
+ end
147
+ @rgl = g
99
148
  end
100
149
 
101
150
  def to_s
@@ -118,4 +167,103 @@ class PetriNet::Graph < PetriNet::Base
118
167
  return str
119
168
  end
120
169
 
170
+ def get_rgl
171
+ if @rgl.nil?
172
+ generate_rgl
173
+ end
174
+ @rgl
175
+ end
176
+
177
+ def cycles
178
+ get_rgl.cycles
179
+ end
180
+
181
+ def shortest_path(start, destination)
182
+ g = get_rgl
183
+ weights = lambda { |edge| 1 }
184
+ g.dijkstra_shortest_path(weights, start.to_s, destination.to_s)
185
+ end
186
+
187
+
188
+ def path_probability(path)
189
+ sanitize_probabilities
190
+ prob = 1
191
+ counter = 0
192
+ path.each do |node|
193
+ edge = get_edge(path[counter+1], node)
194
+ prob = prob * edge.probability unless edge.nil? # last node has no pre-edge
195
+ counter = counter += 1
196
+ end
197
+ prob
198
+ end
199
+
200
+ def node_probability(start, node)
201
+ paths = get_paths_without_loops(start, node)
202
+ prob = 0
203
+ paths.each do |path|
204
+ prob = prob + (path_probability path)
205
+ end
206
+ prob
207
+ end
208
+
209
+ def best_path(start, node)
210
+ paths = get_paths_without_loops(start, node)
211
+ prob = 0
212
+ res_path = nil
213
+ paths.each do |path|
214
+ if (path_probability path) >= prob
215
+ prob = (path_probability path)
216
+ res_path = path
217
+ end
218
+ end
219
+ [res_path,prob]
220
+ end
221
+
222
+ def worst_path(start, node)
223
+ paths = get_paths_without_loops(start, node)
224
+ prob = 1
225
+ res_path = nil
226
+ paths.each do |path|
227
+ if (path_probability path) <= prob
228
+ prob = (path_probability path)
229
+ res_path = path
230
+ end
231
+ end
232
+ [res_path,prob]
233
+ end
234
+
235
+ def get_paths_without_loops(start, goal)
236
+ get_paths_without_loops_helper(get_node(start), get_node(goal))
237
+ end
238
+
239
+ def sanitize_probabilities
240
+ @nodes.each_value do |node|
241
+ prob = 1.0
242
+ @objects[node].outputs.each do |edge|
243
+ prob = prob + @objects[edge].probability unless @objects[edge].probability.nil?
244
+ end
245
+ @objects[node].outputs.each do |edge|
246
+ @objects[edge].probability = prob/@objects[node].outputs.size if @objects[edge].probability.nil?
247
+ end
248
+ end
249
+ end
250
+
251
+
252
+ private
253
+ def get_paths_without_loops_helper(start, goal, reverse_paths = Array.new, reverse_path = Array.new)
254
+ if goal == start
255
+ reverse_path << goal
256
+ reverse_paths << reverse_path
257
+ return nil
258
+ end
259
+ if reverse_path.include? goal
260
+ return nil
261
+ end
262
+ path = Array.new
263
+ goal.inputs.each do |input|
264
+ get_paths_without_loops_helper(start, @objects[input].source, reverse_paths, reverse_path.clone << goal)
265
+ end
266
+ reverse_paths
267
+ end
268
+
121
269
  end
@@ -42,6 +42,10 @@ class PetriNet::Graph::Node < PetriNet::Base
42
42
  yield self unless block.nil?
43
43
  end
44
44
 
45
+ def infinite?
46
+ @omega_marked
47
+ end
48
+
45
49
  # Add an omega-marking to a specified place
46
50
  def add_omega object
47
51
  ret = Array.new
@@ -1,6 +1,10 @@
1
1
  require 'yaml'
2
+ require 'graphviz'
3
+ require 'matrix'
4
+ require 'bigdecimal/ludcmp'
2
5
 
3
6
  class PetriNet::Net < PetriNet::Base
7
+ include LUSolve
4
8
  # Human readable name
5
9
  attr_accessor :name
6
10
  # Storage filename
@@ -240,6 +244,9 @@ Arcs
240
244
  end
241
245
 
242
246
  def reachability_graph
247
+ if !@up_to_date
248
+ update
249
+ end
243
250
  generate_reachability_graph unless (@graph && @up_to_date)
244
251
  @graph
245
252
  end
@@ -300,6 +307,18 @@ Arcs
300
307
  @places.map{|key,pid| @objects[pid].markings.size}
301
308
  end
302
309
 
310
+ def get_marking(places)
311
+ unless places.class.to_s == "Array"
312
+ places = [places]
313
+ end
314
+ if places.first.class.to_s == "Fixnum"
315
+ places.map!{|p| get_place p}
316
+ end
317
+ res = Array.new
318
+ get_place_list.map{|place| if places.include? place.name then res << 1 else res << 0 end}
319
+ res
320
+ end
321
+
303
322
  def set_markings(markings)
304
323
  i = 0
305
324
  @places.each_value do |pid|
@@ -313,6 +332,11 @@ Arcs
313
332
  @places.map{|key,pid| @objects[pid]}
314
333
  end
315
334
 
335
+ def get_place_from_marking(marking)
336
+ return marking if marking.count(1) != 1
337
+ get_place_list[marking.index(1)].name
338
+ end
339
+
316
340
 
317
341
  def objects_size
318
342
  @objects.count{|o| !o.nil?}
@@ -346,8 +370,46 @@ Arcs
346
370
  get_transition(transition).fire
347
371
  end
348
372
 
373
+ def delta
374
+ if @delta.nil?
375
+ generate_delta
376
+ end
377
+ @delta
378
+ end
379
+
380
+ def t_invariants
381
+ delta = self.delta
382
+ zero_vector = Array.new
383
+ delta.row_count.times { zero_vector << 0 }
384
+ zero = BigDecimal("0.0")
385
+ one = BigDecimal("1.0")
386
+
387
+ ps = ludecomp(delta.t.to_a.flatten.map{|i|BigDecimal(i,16)},delta.row_count, zero, one)
388
+ x = lusolve(delta.t.to_a.flatten.map{|i|BigDecimal(i,16)},zero_vector.map{|i|BigDecimal(i,16)},ps, zero)
389
+
390
+ x
391
+ end
392
+
393
+ def s_invariant
394
+ raise "Not jet implemented"
395
+ end
396
+
349
397
  private
350
398
 
399
+ def generate_delta
400
+ d = Array.new(@places.size){Array.new(@transitions.size)}
401
+ i = 0
402
+ @places.each do |p_key,p_value|
403
+ j = 0
404
+ @transitions.each do |t_key,t_value|
405
+ d[i][j] = w0(t_value, p_value) - w0(p_value,t_value)
406
+ j += 1
407
+ end
408
+ i += 1
409
+ end
410
+ @delta = Matrix[d]
411
+ end
412
+
351
413
  def changed_structure
352
414
  @w_up_to_date = false
353
415
  @up_to_date = false
@@ -356,7 +418,6 @@ Arcs
356
418
  def changed_state
357
419
  @up_to_date = false
358
420
  end
359
-
360
421
  def reachability_helper(markings, source)
361
422
  @transitions.each_value do |tid|
362
423
  raise PetriNet::ReachabilityGraph::InfinityGraphError if @objects[tid].inputs.empty? && !@objects[tid].outputs.empty?
@@ -373,8 +434,11 @@ Arcs
373
434
  @graph.add_edge PetriNet::ReachabilityGraph::Edge.new(@graph, source: current_node, destination: infinity_node)
374
435
  next
375
436
  end
376
- @graph.add_edge PetriNet::ReachabilityGraph::Edge.new(@graph, source: source, destination: current_node)# if node_id
377
- reachability_helper get_markings, current_node if node_id
437
+ if node_id < 0
438
+ current_node = @graph.get_node node_id.abs
439
+ end
440
+ @graph.add_edge PetriNet::ReachabilityGraph::Edge.new(@graph, source: source, destination: current_node, probability: @objects[tid].probability)# if node_id
441
+ reachability_helper get_markings, current_node if node_id >= 0
378
442
  end
379
443
  set_markings markings
380
444
  end
@@ -1,6 +1,6 @@
1
1
  module PetriNet
2
2
  # Current Version
3
- VERSION = "0.8.0"
3
+ VERSION = "0.9.4"
4
4
  # True if this is a debug-version and should not be used in productive systems
5
5
  DEBUG = false
6
6
  end
@@ -17,6 +17,6 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.license = 'MIT'
19
19
  gem.add_dependency "ruby-graphviz"
20
- gem.add_dependency "graphviz"
20
+ # gem.add_dependency "rgl"
21
21
  gem.add_development_dependency "net-sftp"
22
22
  end
@@ -71,7 +71,7 @@ Edges
71
71
  @net << PetriNet::Arc.new(source:@net.get_transition('T'), destination:@net.get_place('B'))
72
72
  @net.get_place('A').add_marking
73
73
  rn = @net.generate_reachability_graph
74
- assert_equal "Reachability Graph [SimpleNet1]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n6: Node6 ([1, 0])\n7: Node7 ([0, 1])\n\nEdges\n----------------------------\n8: Edge8 6 -> 7: Node7 ([0, 1]) )\n\n", rn.to_s
74
+ assert_equal "Reachability Graph [SimpleNet1]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n6: Node6 ([1, 0])\n7: Node7 ([0, 1])\n\nEdges\n----------------------------\n8: Edge8 6: Node6 ([1, 0]) -> 7: Node7 ([0, 1]) )\n\n", rn.to_s
75
75
 
76
76
  end
77
77
 
@@ -88,7 +88,7 @@ Edges
88
88
  @net << PetriNet::Arc.new(source:@net.get_transition('T2'), destination:@net.get_place('C'))
89
89
  @net.get_place('A').add_marking
90
90
  rn = @net.generate_reachability_graph
91
- assert_equal "Reachability Graph [SimpleNet2]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n10: Node10 ([1, 0, 0])\n11: Node11 ([0, 1, 0])\n13: Node13 ([0, 0, 1])\n\nEdges\n----------------------------\n12: Edge12 10 -> 11: Node11 ([0, 1, 0]) )\n14: Edge14 10 -> 13: Node13 ([0, 0, 1]) )\n\n", rn.to_s
91
+ assert_equal "Reachability Graph [SimpleNet2]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n10: Node10 ([1, 0, 0])\n11: Node11 ([0, 1, 0])\n13: Node13 ([0, 0, 1])\n\nEdges\n----------------------------\n12: Edge12 10: Node10 ([1, 0, 0]) -> 11: Node11 ([0, 1, 0]) )\n14: Edge14 10: Node10 ([1, 0, 0]) -> 13: Node13 ([0, 0, 1]) )\n\n", rn.to_s
92
92
 
93
93
  end
94
94
 
@@ -102,7 +102,7 @@ Edges
102
102
  @net << PetriNet::Arc.new(source:@net.get_transition('T'), destination:@net.get_place('A'))
103
103
  @net.get_place('A').add_marking
104
104
  rn = @net.generate_reachability_graph
105
- assert_equal "Reachability Graph [SimpleNet3]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n7: Node7 ([1, 0])\n8: Node8 ([1, 1])\n10: Node10 ([Infinity])\n\nEdges\n----------------------------\n9: Edge9 7 -> 8: Node8 ([1, 1]) )\n11: Edge11 8 -> 10: Node10 ([Infinity]) )\n\n", rn.to_s
105
+ assert_equal "Reachability Graph [SimpleNet3]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n7: Node7 ([1, 0])\n8: Node8 ([1, 1])\n10: Node10 ([Infinity])\n\nEdges\n----------------------------\n9: Edge9 7: Node7 ([1, 0]) -> 8: Node8 ([1, 1]) )\n11: Edge11 8: Node8 ([1, 1]) -> 10: Node10 ([Infinity]) )\n\n", rn.to_s
106
106
 
107
107
  end
108
108
 
@@ -119,7 +119,7 @@ Edges
119
119
  @net << PetriNet::Arc.new(source:@net.get_transition('T2'), destination:@net.get_place('A'))
120
120
  @net.get_place('A').add_marking
121
121
  rn = @net.generate_reachability_graph
122
- assert_equal "Reachability Graph [SimpleNet4]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n10: Node10 ([1, 0])\n11: Node11 ([1, 1])\n13: Node13 ([Infinity])\n15: Node15 ([2, 0])\n17: Node17 ([Infinity])\n\nEdges\n----------------------------\n12: Edge12 10 -> 11: Node11 ([1, 1]) )\n14: Edge14 11 -> 13: Node13 ([Infinity]) )\n16: Edge16 10 -> 15: Node15 ([2, 0]) )\n18: Edge18 15 -> 17: Node17 ([Infinity]) )\n\n", rn.to_s
122
+ assert_equal "Reachability Graph [SimpleNet4]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n10: Node10 ([1, 0])\n11: Node11 ([1, 1])\n13: Node13 ([Infinity])\n15: Node15 ([2, 0])\n17: Node17 ([Infinity])\n\nEdges\n----------------------------\n12: Edge12 10: Node10 ([1, 0]) -> 11: Node11 ([1, 1]) )\n14: Edge14 11: Node11 ([1, 1]) -> 13: Node13 ([Infinity]) )\n16: Edge16 10: Node10 ([1, 0]) -> 15: Node15 ([2, 0]) )\n18: Edge18 15: Node15 ([2, 0]) -> 17: Node17 ([Infinity]) )\n\n", rn.to_s
123
123
 
124
124
  end
125
125
 
@@ -136,7 +136,7 @@ Edges
136
136
  @net.get_place('A').add_marking
137
137
  @net.to_gv_new
138
138
  rn = @net.generate_reachability_graph
139
- assert_equal "Reachability Graph [SimpleNet5]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n9: Node9 ([1, 0])\n10: Node10 ([0, 1])\n\nEdges\n----------------------------\n11: Edge11 9 -> 10: Node10 ([0, 1]) )\n\n", rn.to_s
139
+ assert_equal "Reachability Graph [SimpleNet5]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n9: Node9 ([1, 0])\n10: Node10 ([0, 1])\n\nEdges\n----------------------------\n11: Edge11 9: Node9 ([1, 0]) -> 10: Node10 ([0, 1]) )\n13: Edge13 10: Node10 ([0, 1]) -> 9: Node9 ([1, 0]) )\n\n", rn.to_s
140
140
 
141
141
  rn.to_gv
142
142
  end
@@ -182,4 +182,21 @@ Edges
182
182
  @net << PetriNet::Transition.new(name:'T1')
183
183
  @net.generate_reachability_graph # Don't know what to test here, bit this crashed with an Error before...
184
184
  end
185
+
186
+ def test_looped_net1
187
+ @net = PetriNet::Net.new(:name => 'LoopedNet1', :description => 'Should be looped')
188
+ @net << PetriNet::Place.new(name: 'A')
189
+ @net << PetriNet::Place.new(name: 'B')
190
+ @net << PetriNet::Transition.new(name:'T1')
191
+ @net << PetriNet::Transition.new(name:'T2')
192
+ @net << PetriNet::Arc.new(source:@net.get_place('A'), destination:@net.get_transition('T1'))
193
+ @net << PetriNet::Arc.new(source:@net.get_place('B'), destination:@net.get_transition('T2'))
194
+ @net << PetriNet::Arc.new(source:@net.get_transition('T1'), destination:@net.get_place('B'))
195
+ @net << PetriNet::Arc.new(source:@net.get_transition('T2'), destination:@net.get_place('A'))
196
+ @net.get_place('A').add_marking
197
+ @net.to_gv_new
198
+ rg = @net.generate_reachability_graph
199
+ rg.to_gv
200
+ assert_equal "Reachability Graph [LoopedNet1]\n----------------------------\nDescription: \nFilename: \n\nNodes\n----------------------------\n9: Node9 ([1, 0])\n10: Node10 ([0, 1])\n\nEdges\n----------------------------\n11: Edge11 9: Node9 ([1, 0]) -> 10: Node10 ([0, 1]) )\n13: Edge13 10: Node10 ([0, 1]) -> 9: Node9 ([1, 0]) )\n\n", rg.to_s
201
+ end
185
202
  end
@@ -279,6 +279,11 @@ Edges
279
279
  assert_equal weight, @net.generate_weight_function
280
280
  end
281
281
 
282
+ def test_t_invariants
283
+ fill_net
284
+ assert BigDecimal("0.0") == @net.t_invariants.first
285
+ end
286
+
282
287
  def test_get_markings
283
288
  fill_net
284
289
  @net << PetriNet::Place.new(name: 'place2')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: petri_net
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - cclausen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-17 00:00:00.000000000 Z
11
+ date: 2014-05-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-graphviz
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: graphviz
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - '>='
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '>='
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: net-sftp
43
29
  requirement: !ruby/object:Gem::Requirement