rbbt-util 5.32.30 → 5.33.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rbbt/hpc/orchestrate/batches.rb +7 -0
- data/lib/rbbt/hpc/orchestrate.rb +31 -3
- data/lib/rbbt/persist/tsv/adapter.rb +25 -0
- data/lib/rbbt/persist.rb +6 -4
- data/lib/rbbt/resource/path.rb +4 -0
- data/lib/rbbt/tsv/util.rb +12 -1
- data/lib/rbbt/util/concurrency/processes.rb +2 -1
- data/lib/rbbt/util/misc/exceptions.rb +15 -3
- data/lib/rbbt/workflow/step/accessor.rb +5 -1
- data/lib/rbbt/workflow/step/dependencies.rb +23 -3
- data/lib/rbbt/workflow/step/info.rb +2 -2
- data/lib/rbbt/workflow/step/run.rb +3 -2
- data/lib/rbbt/workflow/step/save_load_inputs.rb +4 -0
- data/lib/rbbt/workflow/util/orchestrator.rb +20 -7
- data/lib/rbbt/workflow.rb +3 -5
- data/share/rbbt_commands/workflow/task +15 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 331adb8d6a45a7cc3ee18b17785ffe03d28585274e9b2d1cab5cd502cdc90821
|
4
|
+
data.tar.gz: 8580df0b38d805739fe3ad80e688c92a5c31305893ef1cf6cfccd88c0aaccc7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6381f886a7282c03c8e8303ae148fe4a4fc3b82dad60d1d039953d83075aa85ccc19bb75f24811e2158a6715b9ed471288b2b23110c981a61ad167bc6593aac0
|
7
|
+
data.tar.gz: 1476c3fdf9fe56928a9dd85fc2384735b71a0a40a45359197fc203608f2933aa6a6f5ec94c1bd5ae4b5782b2cd2bbe752f2d06973f9989307d81e3a1f261eba2
|
@@ -80,6 +80,13 @@ module HPC
|
|
80
80
|
batches.each do |batch|
|
81
81
|
jobs = batch[:jobs]
|
82
82
|
all_deps = jobs.collect{|d| job_dependencies(d) }.flatten.uniq
|
83
|
+
|
84
|
+
minimum = all_deps
|
85
|
+
all_deps.each do |dep|
|
86
|
+
minimum -= job_dependencies(dep)
|
87
|
+
end
|
88
|
+
|
89
|
+
all_deps = minimum
|
83
90
|
deps = all_deps.collect do |d|
|
84
91
|
(batches - [batch]).select{|batch| batch[:jobs].collect(&:path).include? d.path }
|
85
92
|
end.flatten.uniq
|
data/lib/rbbt/hpc/orchestrate.rb
CHANGED
@@ -14,8 +14,14 @@ module HPC
|
|
14
14
|
options.delete "detach"
|
15
15
|
options.delete "jobname"
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
if options[:orchestration_rules]
|
18
|
+
rules = YAML.load(Open.read(options[:orchestration_rules]))
|
19
|
+
elsif Rbbt.etc.slurm["default.yaml"].exists?
|
20
|
+
rules = YAML.load(Open.read(Rbbt.etc.slurm["default.yaml"]))
|
21
|
+
else
|
22
|
+
rules = {}
|
23
|
+
end
|
24
|
+
|
19
25
|
IndiferentHash.setup(rules)
|
20
26
|
|
21
27
|
batches = HPC::Orchestration.job_batches(rules, job)
|
@@ -26,7 +32,29 @@ module HPC
|
|
26
32
|
raise "No batch without unmet dependencies" if top.nil?
|
27
33
|
batches.delete top
|
28
34
|
job_options = options.merge(top[:rules])
|
29
|
-
|
35
|
+
|
36
|
+
if top[:deps].nil?
|
37
|
+
batch_dependencies = []
|
38
|
+
else
|
39
|
+
top_jobs = top[:jobs]
|
40
|
+
|
41
|
+
batch_dependencies = top[:deps].collect{|d|
|
42
|
+
target = d[:top_level]
|
43
|
+
canfail = false
|
44
|
+
|
45
|
+
top_jobs.each do |job|
|
46
|
+
canfail = true if job.canfail_paths.include?(target.path)
|
47
|
+
end
|
48
|
+
|
49
|
+
if canfail
|
50
|
+
'canfail:' + batch_ids[d].to_s
|
51
|
+
else
|
52
|
+
batch_ids[d].to_s
|
53
|
+
end
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
job_options.merge!(:batch_dependencies => batch_dependencies )
|
30
58
|
job_options.merge!(:manifest => top[:jobs].collect{|d| d.task_signature })
|
31
59
|
|
32
60
|
if options[:dry_run]
|
@@ -111,6 +111,31 @@ module Persist
|
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
+
def with_read(&block)
|
115
|
+
if read? || write?
|
116
|
+
return yield
|
117
|
+
else
|
118
|
+
read_and_close &block
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def with_write(&block)
|
123
|
+
if write?
|
124
|
+
return yield
|
125
|
+
else
|
126
|
+
if self.read?
|
127
|
+
self.write_and_read do
|
128
|
+
return yield
|
129
|
+
end
|
130
|
+
else
|
131
|
+
self.write_and_close do
|
132
|
+
return yield
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
114
139
|
def read_and_close
|
115
140
|
if read? || write?
|
116
141
|
begin
|
data/lib/rbbt/persist.rb
CHANGED
@@ -423,6 +423,8 @@ module Persist
|
|
423
423
|
end
|
424
424
|
end
|
425
425
|
|
426
|
+
repo.read
|
427
|
+
|
426
428
|
case
|
427
429
|
when (keys.length == 1 and keys.first == subkey + 'NIL')
|
428
430
|
nil
|
@@ -430,19 +432,19 @@ module Persist
|
|
430
432
|
[]
|
431
433
|
when (keys.length == 1 and keys.first =~ /:SINGLE$/)
|
432
434
|
key = keys.first
|
433
|
-
values = repo.
|
435
|
+
values = repo.with_read do
|
434
436
|
repo[key]
|
435
437
|
end
|
436
438
|
Annotated.load_tsv_values(key, values, "literal", "annotation_types", "JSON")
|
437
439
|
when (keys.any? and not keys.first =~ /ANNOTATED_DOUBLE_ARRAY/)
|
438
|
-
repo.
|
440
|
+
repo.with_read do
|
439
441
|
keys.sort_by{|k| k.split(":").last.to_i}.collect{|key|
|
440
442
|
v = repo[key]
|
441
443
|
Annotated.load_tsv_values(key, v, "literal", "annotation_types", "JSON")
|
442
444
|
}
|
443
445
|
end
|
444
446
|
when (keys.any? and keys.first =~ /ANNOTATED_DOUBLE_ARRAY/)
|
445
|
-
repo.
|
447
|
+
repo.with_read do
|
446
448
|
|
447
449
|
res = keys.sort_by{|k| k.split(":").last.to_i}.collect{|key|
|
448
450
|
v = repo[key]
|
@@ -457,7 +459,7 @@ module Persist
|
|
457
459
|
else
|
458
460
|
entities = yield
|
459
461
|
|
460
|
-
repo.
|
462
|
+
repo.write_and_read do
|
461
463
|
case
|
462
464
|
when entities.nil?
|
463
465
|
repo[subkey + "NIL"] = nil
|
data/lib/rbbt/resource/path.rb
CHANGED
data/lib/rbbt/tsv/util.rb
CHANGED
@@ -297,6 +297,8 @@ module TSV
|
|
297
297
|
when :double
|
298
298
|
if field.nil?
|
299
299
|
through do |k,v| new[k] = v.first end
|
300
|
+
elsif field == :all
|
301
|
+
through do |k,v| new[k] = v.flatten.compact end
|
300
302
|
else
|
301
303
|
pos = identify_field field
|
302
304
|
through do |k,v| new[k] = v[pos] end
|
@@ -313,7 +315,16 @@ module TSV
|
|
313
315
|
end
|
314
316
|
end
|
315
317
|
self.annotate(new)
|
316
|
-
|
318
|
+
if new.fields
|
319
|
+
case field
|
320
|
+
when nil
|
321
|
+
new.fields = new.fields[0..0]
|
322
|
+
when :all
|
323
|
+
new.fields = [new.fields * "+"]
|
324
|
+
else
|
325
|
+
new.fields = [field]
|
326
|
+
end
|
327
|
+
end
|
317
328
|
new.type = :flat
|
318
329
|
new
|
319
330
|
end
|
@@ -205,7 +205,8 @@ class RbbtProcessQueue
|
|
205
205
|
end
|
206
206
|
|
207
207
|
@queue.close_read
|
208
|
-
Log.info "Cpu process
|
208
|
+
Log.info "Cpu process #{@master_pid} with #{num_processes} workers."
|
209
|
+
Log.low "Signal #{@master_pid} USR1/USR2 (#10/#12) to increase/decrease workers."
|
209
210
|
end
|
210
211
|
|
211
212
|
def init(&block)
|
@@ -32,8 +32,6 @@ class DependencyError < Aborted
|
|
32
32
|
def initialize(msg)
|
33
33
|
if defined? Step and Step === msg
|
34
34
|
step = msg
|
35
|
-
workflow = step.path.split("/")[-3]
|
36
|
-
new_msg = [workflow, step.short_path, step.messages.last] * " - "
|
37
35
|
new_msg = [step.path, step.messages.last] * ": "
|
38
36
|
super(new_msg)
|
39
37
|
else
|
@@ -42,6 +40,21 @@ class DependencyError < Aborted
|
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
43
|
+
class DependencyRbbtException < RbbtException
|
44
|
+
def initialize(msg)
|
45
|
+
if defined? Step and Step === msg
|
46
|
+
step = msg
|
47
|
+
|
48
|
+
new_msg = nil
|
49
|
+
new_msg = [step.path, step.messages.last] * ": "
|
50
|
+
|
51
|
+
super(new_msg)
|
52
|
+
else
|
53
|
+
super(msg)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
45
58
|
class DontClose < Exception; end
|
46
59
|
|
47
60
|
class KeepLocked < Exception
|
@@ -64,4 +77,3 @@ class StopInsist < Exception
|
|
64
77
|
@exception = exception
|
65
78
|
end
|
66
79
|
end
|
67
|
-
|
@@ -44,7 +44,7 @@ class Step
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def self.files_dir(path)
|
47
|
-
path.nil? ? nil : path + '.files'
|
47
|
+
path.nil? ? nil : Path.setup(path + '.files')
|
48
48
|
end
|
49
49
|
|
50
50
|
def self.info_file(path)
|
@@ -154,8 +154,12 @@ class Step
|
|
154
154
|
value = Annotated.purge value if defined? Annotated
|
155
155
|
Open.lock(info_file, :lock => info_lock) do
|
156
156
|
i = info(false).dup
|
157
|
+
value = Annotated.purge(value)
|
158
|
+
|
157
159
|
i[key] = value
|
160
|
+
|
158
161
|
dump = Step.serialize_info(i)
|
162
|
+
|
159
163
|
@info_cache = IndiferentHash.setup(i)
|
160
164
|
Misc.sensiblewrite(info_file, dump, :force => true, :lock => false) if Open.exists?(info_file)
|
161
165
|
@info_cache_time = Time.now
|
@@ -107,7 +107,26 @@ class Step
|
|
107
107
|
canfail = ComputeDependency === job && job.canfail?
|
108
108
|
end
|
109
109
|
|
110
|
-
|
110
|
+
raise_dependency_error(job) if job.error? and not canfail
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.raise_dependency_error(job)
|
114
|
+
begin
|
115
|
+
if job.get_exception
|
116
|
+
klass = job.get_exception.class
|
117
|
+
else
|
118
|
+
klass = Kernel.const_get(info[:exception][:class])
|
119
|
+
end
|
120
|
+
rescue
|
121
|
+
Log.exception $!
|
122
|
+
raise DependencyError, job
|
123
|
+
end
|
124
|
+
|
125
|
+
if (klass <= RbbtException)
|
126
|
+
raise DependencyRbbtException, job
|
127
|
+
else
|
128
|
+
raise DependencyError, job
|
129
|
+
end
|
111
130
|
end
|
112
131
|
|
113
132
|
def log_dependency_exec(dependency, action)
|
@@ -169,7 +188,7 @@ class Step
|
|
169
188
|
|
170
189
|
if dependency.error?
|
171
190
|
log_dependency_exec(dependency, :error)
|
172
|
-
|
191
|
+
raise_dependency_error dependency
|
173
192
|
end
|
174
193
|
|
175
194
|
if dependency.streaming?
|
@@ -368,7 +387,7 @@ class Step
|
|
368
387
|
seen_paths << step.path
|
369
388
|
begin
|
370
389
|
Step.prepare_for_execution(step) unless step == self
|
371
|
-
rescue DependencyError
|
390
|
+
rescue DependencyError, DependencyRbbtException
|
372
391
|
raise $! unless canfail_paths.include? step.path
|
373
392
|
end
|
374
393
|
next unless step.dependencies and step.dependencies.any?
|
@@ -493,6 +512,7 @@ class Step
|
|
493
512
|
return true if @overriden
|
494
513
|
return true if dependencies.select{|dep| dep.overriden? }.any?
|
495
514
|
info[:archived_info].each do |f,i|
|
515
|
+
next if Symbol === i
|
496
516
|
return true if i[:overriden] || i["overriden"]
|
497
517
|
end if info[:archived_info]
|
498
518
|
return false
|
@@ -101,7 +101,7 @@ class Step
|
|
101
101
|
seen = []
|
102
102
|
while path = deps.pop
|
103
103
|
dep_info = archived_info[path]
|
104
|
-
if dep_info
|
104
|
+
if Hash === dep_info
|
105
105
|
dep_info[:inputs].each do |k,v|
|
106
106
|
all_inputs[k] = v unless all_inputs.include?(k)
|
107
107
|
end if dep_info[:inputs]
|
@@ -285,7 +285,7 @@ class Step
|
|
285
285
|
begin
|
286
286
|
return true unless info[:exception]
|
287
287
|
klass = Kernel.const_get(info[:exception][:class])
|
288
|
-
! (klass <= RbbtException)
|
288
|
+
! (klass <= RbbtException )
|
289
289
|
rescue Exception
|
290
290
|
true
|
291
291
|
end
|
@@ -437,7 +437,7 @@ class Step
|
|
437
437
|
end
|
438
438
|
end # END SYNC
|
439
439
|
res
|
440
|
-
rescue DependencyError
|
440
|
+
rescue DependencyError, DependencyRbbtException
|
441
441
|
exception $!
|
442
442
|
rescue LockInterrupted
|
443
443
|
raise $!
|
@@ -484,7 +484,7 @@ class Step
|
|
484
484
|
if dofork
|
485
485
|
fork(true) unless started?
|
486
486
|
|
487
|
-
join unless done?
|
487
|
+
join unless done? or dofork == :nowait
|
488
488
|
else
|
489
489
|
run(true) unless started?
|
490
490
|
|
@@ -496,6 +496,7 @@ class Step
|
|
496
496
|
|
497
497
|
def fork(no_load = false, semaphore = nil)
|
498
498
|
raise "Can not fork: Step is waiting for proces #{@pid} to finish" if not @pid.nil? and not Process.pid == @pid and Misc.pid_exists?(@pid) and not done? and info[:forked]
|
499
|
+
Log.debug "Fork to run #{self.path}"
|
499
500
|
sout, sin = Misc.pipe if no_load == :stream
|
500
501
|
@pid = Process.fork do
|
501
502
|
Signal.trap(:TERM) do
|
@@ -26,7 +26,11 @@ module Workflow
|
|
26
26
|
|
27
27
|
type = :path if file.split(".").last == 'as_path'
|
28
28
|
|
29
|
+
type = :nofile if file.split(".").last == 'nofile'
|
30
|
+
|
29
31
|
case type
|
32
|
+
when :nofile
|
33
|
+
inputs[input.to_sym] = Open.realpath(file)
|
30
34
|
when :path
|
31
35
|
inputs[input.to_sym] = Open.realpath(Open.read(file).strip)
|
32
36
|
when :io
|
@@ -65,9 +65,13 @@ module Workflow
|
|
65
65
|
|
66
66
|
IndiferentHash.setup(resources)
|
67
67
|
|
68
|
-
default_resources = rules["default_resources"]
|
68
|
+
default_resources = rules["default_resources"]
|
69
|
+
default_resources ||= rules["defaults"]["resources"] if rules["defaults"]
|
70
|
+
default_resources ||= {}
|
71
|
+
|
69
72
|
default_resources.each{|k,v| resources[k] ||= v } if default_resources
|
70
73
|
|
74
|
+
resources = {:cpus => 1} if resources.empty?
|
71
75
|
resources
|
72
76
|
end
|
73
77
|
|
@@ -80,7 +84,8 @@ module Workflow
|
|
80
84
|
|
81
85
|
def self.candidates(workload, rules)
|
82
86
|
if rules.empty?
|
83
|
-
candidates = workload.
|
87
|
+
candidates = workload.
|
88
|
+
select{|k,v| v.empty? }.
|
84
89
|
collect{|k,v| k }.
|
85
90
|
reject{|k| k.done? }
|
86
91
|
else
|
@@ -98,9 +103,14 @@ module Workflow
|
|
98
103
|
candidates
|
99
104
|
end
|
100
105
|
|
106
|
+
def self.process(*args)
|
107
|
+
self.new.process(*args)
|
108
|
+
end
|
109
|
+
|
101
110
|
attr_accessor :available_resources, :resources_requested, :resources_used, :timer
|
102
111
|
|
103
|
-
def initialize(timer = 5, available_resources =
|
112
|
+
def initialize(timer = 5, available_resources = nil)
|
113
|
+
available_resources = {:cpus => Etc.nprocessors } if available_resources.nil?
|
104
114
|
@timer = timer
|
105
115
|
@available_resources = IndiferentHash.setup(available_resources)
|
106
116
|
@resources_requested = IndiferentHash.setup({})
|
@@ -148,7 +158,7 @@ module Workflow
|
|
148
158
|
log = job_rules[:log] if job_rules
|
149
159
|
log = Log.severity if log.nil?
|
150
160
|
Log.with_severity log do
|
151
|
-
job.produce(false,
|
161
|
+
job.produce(false, :nowait)
|
152
162
|
end
|
153
163
|
end
|
154
164
|
end
|
@@ -176,7 +186,9 @@ module Workflow
|
|
176
186
|
end
|
177
187
|
end
|
178
188
|
|
179
|
-
def process(rules, jobs)
|
189
|
+
def process(rules, jobs = nil)
|
190
|
+
jobs, rules = rules, {} if jobs.nil?
|
191
|
+
jobs = [jobs] if Step === jobs
|
180
192
|
begin
|
181
193
|
|
182
194
|
workload = Orchestrator.workload(jobs)
|
@@ -219,8 +231,9 @@ module Workflow
|
|
219
231
|
|
220
232
|
new_workload = {}
|
221
233
|
workload.each do |k,v|
|
222
|
-
next if k.done?
|
223
|
-
new_workload[k] = v.reject{|d| d.done? || (d.error? && ! d.recoverable_error?)}
|
234
|
+
next if k.done? || k.error? || k.aborted?
|
235
|
+
#new_workload[k] = v.reject{|d| d.done? || ((d.error? || d.aborted?) && ! d.recoverable_error?)}
|
236
|
+
new_workload[k] = v.reject{|d| d.done? || d.error? || d.aborted?}
|
224
237
|
end
|
225
238
|
workload = new_workload
|
226
239
|
sleep timer
|
data/lib/rbbt/workflow.rb
CHANGED
@@ -423,7 +423,7 @@ module Workflow
|
|
423
423
|
|
424
424
|
# jobname => true sets the value of the input to the name of the job
|
425
425
|
if task.input_options
|
426
|
-
jobname_input = task.input_options.select{|i,o| o[:jobname]}.collect{|i,o| i }.first
|
426
|
+
jobname_input = task.input_options.select{|i,o| o[:jobname] }.collect{|i,o| i }.first
|
427
427
|
else
|
428
428
|
jobname_input = nil
|
429
429
|
end
|
@@ -476,14 +476,14 @@ module Workflow
|
|
476
476
|
end
|
477
477
|
end
|
478
478
|
|
479
|
+
input_values = task.take_input_values(inputs)
|
479
480
|
if real_inputs.empty? && Workflow::TAG != :inputs && ! overriden
|
480
481
|
step_path = step_path taskname, jobname, [], [], extension
|
481
|
-
input_values = task.take_input_values(inputs)
|
482
482
|
else
|
483
|
-
input_values = task.take_input_values(inputs)
|
484
483
|
step_path = step_path taskname, jobname, input_values, dependencies, extension
|
485
484
|
end
|
486
485
|
|
486
|
+
|
487
487
|
job = get_job_step step_path, task, input_values, dependencies
|
488
488
|
job.workflow = self
|
489
489
|
job.clean_name = jobname
|
@@ -503,8 +503,6 @@ module Workflow
|
|
503
503
|
|
504
504
|
def _job(taskname, jobname = nil, inputs = {})
|
505
505
|
|
506
|
-
_inputs = IndiferentHash.setup(inputs.dup)
|
507
|
-
|
508
506
|
task_info = task_info(taskname)
|
509
507
|
task_inputs = task_info[:inputs]
|
510
508
|
persist_inputs = inputs.values_at(*task_inputs)
|
@@ -191,6 +191,7 @@ The `recursive_clean` cleans all the job dependency steps recursively.
|
|
191
191
|
-rcl--recursive_clean Clean the last step and its dependencies to recompute the job completely
|
192
192
|
-uaj--update_all_jobs Consider all dependencies when checking for updates, even when they have no info files
|
193
193
|
--fork Run job asyncronously and monitor progress. It monitors detached processes as well
|
194
|
+
--orchestrate* Run the job through the orchestrator
|
194
195
|
--detach Run job asyncronously and detach process
|
195
196
|
--exec Run job with no persistence
|
196
197
|
-O--output* Save job result into file
|
@@ -438,6 +439,19 @@ begin
|
|
438
439
|
end
|
439
440
|
|
440
441
|
job.fork
|
442
|
+
elsif options[:orchestrate]
|
443
|
+
require 'rbbt/workflow/util/orchestrator'
|
444
|
+
rules = case options[:orchestrate]
|
445
|
+
when 'none', 'open', 'default'
|
446
|
+
nil
|
447
|
+
else
|
448
|
+
YAML.parse(Open.read(options[:orchestrate]))
|
449
|
+
end
|
450
|
+
if rules
|
451
|
+
Workflow::Orchestrator.process rules, job
|
452
|
+
else
|
453
|
+
Workflow::Orchestrator.process job
|
454
|
+
end unless job.done?
|
441
455
|
else
|
442
456
|
job.run(:stream)
|
443
457
|
res = job
|
@@ -573,10 +587,10 @@ when Step
|
|
573
587
|
elsif detach
|
574
588
|
exit! 0
|
575
589
|
else
|
590
|
+
res.join
|
576
591
|
if %w(float integer string boolean).include?(res.result_type.to_s)
|
577
592
|
out.puts res.load
|
578
593
|
else
|
579
|
-
res.join
|
580
594
|
Open.open(res.path, :mode => 'rb') do |io|
|
581
595
|
Misc.consume_stream(io, false, out)
|
582
596
|
end if Open.exist?(res.path) || Open.remote?(res.path) || Open.ssh?(res.path)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rbbt-util
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.33.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miguel Vazquez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-02-
|
11
|
+
date: 2022-02-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|