scout-gear 5.1.1 → 6.0.0
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 +4 -4
- data/.vimproject +24 -12
- data/Rakefile +2 -0
- data/VERSION +1 -1
- data/bin/scout +2 -0
- data/lib/scout/exceptions.rb +14 -2
- data/lib/scout/log/color.rb +34 -10
- data/lib/scout/log/progress/report.rb +5 -4
- data/lib/scout/meta_extension.rb +4 -2
- data/lib/scout/misc/format.rb +16 -4
- data/lib/scout/misc/monitor.rb +41 -0
- data/lib/scout/misc.rb +1 -0
- data/lib/scout/open/stream.rb +31 -0
- data/lib/scout/path/find.rb +2 -1
- data/lib/scout/path.rb +1 -1
- data/lib/scout/persist/serialize.rb +15 -4
- data/lib/scout/resource/path.rb +5 -0
- data/lib/scout/resource/util.rb +48 -0
- data/lib/scout/resource.rb +2 -0
- data/lib/scout/semaphore.rb +148 -0
- data/lib/scout/simple_opt/doc.rb +26 -2
- data/lib/scout/work_queue/socket.rb +119 -0
- data/lib/scout/work_queue/worker.rb +54 -0
- data/lib/scout/work_queue.rb +86 -0
- data/lib/scout/workflow/definition.rb +8 -2
- data/lib/scout/workflow/documentation.rb +32 -26
- data/lib/scout/workflow/step/info.rb +13 -13
- data/lib/scout/workflow/step/load.rb +18 -0
- data/lib/scout/workflow/step.rb +40 -4
- data/lib/scout/workflow/task/inputs.rb +4 -2
- data/lib/scout/workflow/task.rb +15 -1
- data/lib/scout/workflow/usage.rb +96 -76
- data/lib/scout/workflow.rb +1 -0
- data/scout-gear.gemspec +25 -3
- data/scout_commands/workflow/info +29 -0
- data/scout_commands/workflow/list +27 -0
- data/scout_commands/workflow/task +32 -681
- data/scout_commands/workflow/task_old +706 -0
- data/share/color/color_names +507 -0
- data/share/color/diverging_colors.hex +12 -0
- data/test/scout/log/test_color.rb +0 -0
- data/test/scout/resource/test_util.rb +27 -0
- data/test/scout/simple_opt/test_doc.rb +16 -0
- data/test/scout/test_meta_extension.rb +9 -0
- data/test/scout/test_semaphore.rb +17 -0
- data/test/scout/test_work_queue.rb +93 -0
- data/test/scout/work_queue/test_socket.rb +46 -0
- data/test/scout/work_queue/test_worker.rb +99 -0
- data/test/scout/workflow/step/test_info.rb +17 -15
- data/test/scout/workflow/step/test_load.rb +65 -0
- data/test/scout/workflow/test_definition.rb +0 -0
- data/test/scout/workflow/test_documentation.rb +30 -0
- data/test/scout/workflow/test_task.rb +1 -0
- data/test/scout/workflow/test_usage.rb +12 -3
- metadata +24 -2
@@ -0,0 +1,148 @@
|
|
1
|
+
begin
|
2
|
+
require 'inline'
|
3
|
+
continue = true
|
4
|
+
rescue Exception
|
5
|
+
Log.warn "The RubyInline gem could not be loaded: semaphore synchronization will not work"
|
6
|
+
continue = false
|
7
|
+
end
|
8
|
+
|
9
|
+
if continue
|
10
|
+
module ScoutSemaphore
|
11
|
+
inline(:C) do |builder|
|
12
|
+
builder.prefix <<-EOF
|
13
|
+
#include <unistd.h>
|
14
|
+
#include <stdio.h>
|
15
|
+
#include <stdlib.h>
|
16
|
+
#include <semaphore.h>
|
17
|
+
#include <time.h>
|
18
|
+
#include <assert.h>
|
19
|
+
#include <errno.h>
|
20
|
+
#include <signal.h>
|
21
|
+
#include <fcntl.h>
|
22
|
+
EOF
|
23
|
+
|
24
|
+
builder.c_singleton <<-EOF
|
25
|
+
void create_semaphore(char* name, int value){
|
26
|
+
sem_open(name, O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO, value);
|
27
|
+
}
|
28
|
+
EOF
|
29
|
+
builder.c_singleton <<-EOF
|
30
|
+
void delete_semaphore(char* name){
|
31
|
+
sem_unlink(name);
|
32
|
+
}
|
33
|
+
EOF
|
34
|
+
|
35
|
+
builder.c_singleton <<-EOF
|
36
|
+
int wait_semaphore(char* name){
|
37
|
+
int ret;
|
38
|
+
sem_t* sem;
|
39
|
+
sem = sem_open(name, 0);
|
40
|
+
ret = sem_wait(sem);
|
41
|
+
sem_close(sem);
|
42
|
+
return(ret);
|
43
|
+
}
|
44
|
+
EOF
|
45
|
+
|
46
|
+
builder.c_singleton <<-EOF
|
47
|
+
void post_semaphore(char* name){
|
48
|
+
sem_t* sem;
|
49
|
+
sem = sem_open(name, 0);
|
50
|
+
sem_post(sem);
|
51
|
+
sem_close(sem);
|
52
|
+
}
|
53
|
+
EOF
|
54
|
+
end
|
55
|
+
|
56
|
+
SEM_MUTEX = Mutex.new
|
57
|
+
def self.synchronize(sem)
|
58
|
+
ret = ScoutSemaphore.wait_semaphore(sem)
|
59
|
+
raise SemaphoreInterrupted if ret == -1
|
60
|
+
begin
|
61
|
+
yield
|
62
|
+
ensure
|
63
|
+
ScoutSemaphore.post_semaphore(sem)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.with_semaphore(size, file = nil)
|
68
|
+
if file.nil?
|
69
|
+
file = "/" << Misc.digest(rand(1000000000000).to_s) if file.nil?
|
70
|
+
else
|
71
|
+
file = file.gsub('/', '_') if file
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
Log.debug "Creating semaphore (#{ size }): #{file}"
|
76
|
+
ScoutSemaphore.create_semaphore(file, size)
|
77
|
+
yield file
|
78
|
+
ensure
|
79
|
+
Log.debug "Removing semaphore #{ file }"
|
80
|
+
ScoutSemaphore.delete_semaphore(file)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.fork_each_on_semaphore(elems, size, file = nil)
|
85
|
+
|
86
|
+
TSV.traverse elems, :cpus => size, :bar => "Fork each on semaphore: #{ Misc.fingerprint elems }", :into => Set.new do |elem|
|
87
|
+
elems.annotate elem if elems.respond_to? :annotate
|
88
|
+
begin
|
89
|
+
yield elem
|
90
|
+
rescue Interrupt
|
91
|
+
Log.warn "Process #{Process.pid} was aborted"
|
92
|
+
end
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.thread_each_on_semaphore(elems, size)
|
99
|
+
mutex = Mutex.new
|
100
|
+
count = 0
|
101
|
+
cv = ConditionVariable.new
|
102
|
+
wait_mutex = Mutex.new
|
103
|
+
|
104
|
+
begin
|
105
|
+
|
106
|
+
threads = []
|
107
|
+
wait_mutex.synchronize do
|
108
|
+
threads = elems.collect do |elem|
|
109
|
+
Thread.new(elem) do |elem|
|
110
|
+
|
111
|
+
continue = false
|
112
|
+
mutex.synchronize do
|
113
|
+
while not continue do
|
114
|
+
if count < size
|
115
|
+
continue = true
|
116
|
+
count += 1
|
117
|
+
end
|
118
|
+
mutex.sleep 1 unless continue
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
begin
|
123
|
+
yield elem
|
124
|
+
rescue Interrupt
|
125
|
+
Log.error "Thread was aborted while processing: #{Misc.fingerprint elem}"
|
126
|
+
raise $!
|
127
|
+
ensure
|
128
|
+
mutex.synchronize do
|
129
|
+
count -= 1
|
130
|
+
cv.signal if mutex.locked?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
threads.each do |thread|
|
138
|
+
thread.join
|
139
|
+
end
|
140
|
+
rescue Exception
|
141
|
+
Log.exception $!
|
142
|
+
Log.info "Ensuring threads are dead: #{threads.length}"
|
143
|
+
threads.each do |thread| thread.kill end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
data/lib/scout/simple_opt/doc.rb
CHANGED
@@ -42,10 +42,33 @@ module SOPT
|
|
42
42
|
"=<#{ type }>"
|
43
43
|
end
|
44
44
|
#extra << " (default: #{Array === default ? (default.length > 3 ? default[0..2]*", " + ', ...' : default*", " ): default})" if default != nil
|
45
|
-
extra << " (default: #{
|
45
|
+
extra << " (default: #{Log.fingerprint(default)})" if default != nil
|
46
46
|
input_str << Log.color(:green, extra)
|
47
47
|
end
|
48
48
|
|
49
|
+
def self.input_array_doc(input_array)
|
50
|
+
input_array.collect do |name,type,description,default,options|
|
51
|
+
type = :string if type.nil?
|
52
|
+
|
53
|
+
name = name.to_s
|
54
|
+
shortcut, options = options, nil if String === options || Symbol === options
|
55
|
+
|
56
|
+
case options && options[:shortcut]
|
57
|
+
when FalseClass
|
58
|
+
shortcut = nil
|
59
|
+
when TrueClass, nil
|
60
|
+
shortcut = fix_shortcut(name[0], name)
|
61
|
+
else
|
62
|
+
shortcut = options[:shortcut]
|
63
|
+
end unless shortcut
|
64
|
+
|
65
|
+
shortcut = fix_shortcut(shortcut, name)
|
66
|
+
register(shortcut, name, type, description) unless self.inputs.include? name
|
67
|
+
name = SOPT.input_format(name, type.to_sym, default, shortcut )
|
68
|
+
Misc.format_definition_list_item(name, description)
|
69
|
+
end * "\n"
|
70
|
+
end
|
71
|
+
|
49
72
|
def self.input_doc(inputs, input_types = nil, input_descriptions = nil, input_defaults = nil, input_shortcuts = nil)
|
50
73
|
type = description = default = nil
|
51
74
|
shortcut = ""
|
@@ -73,10 +96,11 @@ module SOPT
|
|
73
96
|
register(shortcut, name, type, description) unless self.inputs.include? name
|
74
97
|
|
75
98
|
name = SOPT.input_format(name, type.to_sym, default, shortcut)
|
76
|
-
Misc.format_definition_list_item(name, description
|
99
|
+
Misc.format_definition_list_item(name, description)
|
77
100
|
end * "\n"
|
78
101
|
end
|
79
102
|
|
103
|
+
|
80
104
|
def self.doc
|
81
105
|
doc =<<-EOF
|
82
106
|
#{Log.color :magenta}#{command}(1) -- #{summary}
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'scout/open'
|
2
|
+
require 'scout/semaphore'
|
3
|
+
require 'scout/exceptions'
|
4
|
+
class WorkQueue
|
5
|
+
class Socket
|
6
|
+
attr_accessor :sread, :swrite, :write_sem, :read_sem, :cleaned
|
7
|
+
def initialize(serializer = nil)
|
8
|
+
@sread, @swrite = Open.pipe
|
9
|
+
|
10
|
+
@serializer = serializer || Marshal
|
11
|
+
|
12
|
+
@key = "/" << rand(1000000000).to_s << '.' << Process.pid.to_s;
|
13
|
+
@write_sem = @key + '.in'
|
14
|
+
@read_sem = @key + '.out'
|
15
|
+
Log.debug "Creating socket semaphores: #{@key}"
|
16
|
+
ScoutSemaphore.create_semaphore(@write_sem,1)
|
17
|
+
ScoutSemaphore.create_semaphore(@read_sem,1)
|
18
|
+
end
|
19
|
+
|
20
|
+
def clean
|
21
|
+
@cleaned = true
|
22
|
+
@sread.close unless @sread.closed?
|
23
|
+
@swrite.close unless @swrite.closed?
|
24
|
+
Log.low "Destroying socket semaphores: #{[@key] * ", "}"
|
25
|
+
ScoutSemaphore.delete_semaphore(@write_sem)
|
26
|
+
ScoutSemaphore.delete_semaphore(@read_sem)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def dump(obj)
|
31
|
+
stream = @swrite
|
32
|
+
obj.concurrent_stream = nil if obj.respond_to?(:concurrent_stream)
|
33
|
+
case obj
|
34
|
+
when Integer
|
35
|
+
size_head = [obj,"I"].pack 'La'
|
36
|
+
str = size_head
|
37
|
+
when nil
|
38
|
+
size_head = [0,"N"].pack 'La'
|
39
|
+
str = size_head
|
40
|
+
when String
|
41
|
+
payload = obj
|
42
|
+
size_head = [payload.bytesize,"C"].pack 'La'
|
43
|
+
str = size_head << payload
|
44
|
+
else
|
45
|
+
payload = @serializer.dump(obj)
|
46
|
+
size_head = [payload.bytesize,"S"].pack 'La'
|
47
|
+
str = size_head << payload
|
48
|
+
end
|
49
|
+
|
50
|
+
write_length = str.length
|
51
|
+
wrote = stream.write(str)
|
52
|
+
while wrote < write_length
|
53
|
+
wrote += stream.write(str[wrote..-1])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def load
|
58
|
+
stream = @sread
|
59
|
+
size_head = Open.read_stream stream, 5
|
60
|
+
|
61
|
+
size, type = size_head.unpack('La')
|
62
|
+
|
63
|
+
return nil if type == "N"
|
64
|
+
return size.to_i if type == "I"
|
65
|
+
begin
|
66
|
+
payload = Open.read_stream stream, size
|
67
|
+
case type
|
68
|
+
when "S"
|
69
|
+
begin
|
70
|
+
@serializer.load(payload)
|
71
|
+
rescue Exception
|
72
|
+
Log.exception $!
|
73
|
+
raise $!
|
74
|
+
end
|
75
|
+
when "C"
|
76
|
+
payload
|
77
|
+
end
|
78
|
+
rescue TryAgain
|
79
|
+
retry
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def closed_read?
|
84
|
+
@sread.closed?
|
85
|
+
end
|
86
|
+
|
87
|
+
def closed_write?
|
88
|
+
@swrite.closed?
|
89
|
+
end
|
90
|
+
|
91
|
+
def close_write
|
92
|
+
self.dump ClosedStream.new
|
93
|
+
@swrite.close unless closed_write?
|
94
|
+
end
|
95
|
+
|
96
|
+
def close_read
|
97
|
+
@sread.close unless closed_read?
|
98
|
+
end
|
99
|
+
|
100
|
+
#{{{ ACCESSOR
|
101
|
+
def push(obj)
|
102
|
+
ScoutSemaphore.synchronize(@write_sem) do
|
103
|
+
self.dump(obj)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def pop
|
108
|
+
ScoutSemaphore.synchronize(@read_sem) do
|
109
|
+
res = self.load
|
110
|
+
raise res if ClosedStream === res
|
111
|
+
res
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
alias write push
|
116
|
+
|
117
|
+
alias read pop
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class WorkQueue
|
2
|
+
class Worker
|
3
|
+
attr_accessor :pid, :ignore_ouput
|
4
|
+
def initialize
|
5
|
+
end
|
6
|
+
|
7
|
+
def run
|
8
|
+
@pid = Process.fork do
|
9
|
+
yield
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def process(input, output, &block)
|
14
|
+
run do
|
15
|
+
begin
|
16
|
+
while obj = input.read
|
17
|
+
if DoneProcessing === obj
|
18
|
+
output.write DoneProcessing.new
|
19
|
+
raise obj
|
20
|
+
end
|
21
|
+
res = block.call obj
|
22
|
+
output.write res unless ignore_ouput || res == :ignore
|
23
|
+
end
|
24
|
+
rescue DoneProcessing
|
25
|
+
Log.log "Worker #{Process.pid} done"
|
26
|
+
rescue Exception
|
27
|
+
Log.exception $!
|
28
|
+
exit -1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def join
|
34
|
+
Log.log "Joining worker #{@pid}"
|
35
|
+
Process.waitpid @pid
|
36
|
+
end
|
37
|
+
|
38
|
+
def exit(status)
|
39
|
+
Log.log "Worker #{@pid} exited with status #{Log.color(:green, status)}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.join(workers)
|
43
|
+
workers = [workers] unless Array === workers
|
44
|
+
begin
|
45
|
+
while pid = Process.wait
|
46
|
+
status = $?
|
47
|
+
worker = workers.select{|w| w.pid == pid }.first
|
48
|
+
worker.exit status.exitstatus if worker
|
49
|
+
end
|
50
|
+
rescue Errno::ECHILD
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative 'work_queue/socket'
|
2
|
+
require_relative 'work_queue/worker'
|
3
|
+
|
4
|
+
class WorkQueue
|
5
|
+
attr_accessor :workers, :worker_proc, :callback
|
6
|
+
|
7
|
+
def initialize(workers = 0, &block)
|
8
|
+
@input = WorkQueue::Socket.new
|
9
|
+
@output = WorkQueue::Socket.new
|
10
|
+
@workers = workers.times.collect{ Worker.new }
|
11
|
+
@worker_proc = block
|
12
|
+
@worker_mutex = Mutex.new
|
13
|
+
@removed_workers = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_worker(&block)
|
17
|
+
worker = Worker.new
|
18
|
+
@worker_mutex.synchronize do
|
19
|
+
@workers.push(worker)
|
20
|
+
if block_given?
|
21
|
+
worker.process @input, @output, &block
|
22
|
+
else
|
23
|
+
worker.process @input, @output, &@worker_proc
|
24
|
+
end
|
25
|
+
end
|
26
|
+
worker
|
27
|
+
end
|
28
|
+
|
29
|
+
def ignore_ouput
|
30
|
+
@workers.each{|w| w.ignore_ouput = true }
|
31
|
+
end
|
32
|
+
|
33
|
+
def remove_one_worker
|
34
|
+
@input.write DoneProcessing.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_worker(pid)
|
38
|
+
worker = @worker_mutex.synchronize do
|
39
|
+
Log.debug "Remove #{pid}"
|
40
|
+
@removed_workers.concat(@workers.delete_if{|w| w.pid == pid })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def process(&callback)
|
45
|
+
@workers.each do |w|
|
46
|
+
w.process @input, @output, &@worker_proc
|
47
|
+
end
|
48
|
+
@reader = Thread.new do
|
49
|
+
begin
|
50
|
+
while true
|
51
|
+
obj = @output.read
|
52
|
+
if DoneProcessing === obj
|
53
|
+
remove_worker obj.pid if obj.pid
|
54
|
+
else
|
55
|
+
callback.call obj if callback
|
56
|
+
end
|
57
|
+
end
|
58
|
+
rescue Aborted
|
59
|
+
end
|
60
|
+
end if @output
|
61
|
+
end
|
62
|
+
|
63
|
+
def write(obj)
|
64
|
+
@input.write obj
|
65
|
+
end
|
66
|
+
|
67
|
+
def close
|
68
|
+
while @worker_mutex.synchronize{ @workers.length } > 0
|
69
|
+
begin
|
70
|
+
@input.write DoneProcessing.new
|
71
|
+
pid = Process.wait
|
72
|
+
status = $?
|
73
|
+
worker = @worker_mutex.synchronize{ @removed_workers.delete_if{|w| w.pid == pid }.first }
|
74
|
+
worker.exit $?.exitstatus if worker
|
75
|
+
rescue Errno::ECHILD
|
76
|
+
Thread.pass until @workers.length == 0
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@reader.raise Aborted if @reader
|
81
|
+
end
|
82
|
+
|
83
|
+
def join
|
84
|
+
@reader.join if @reader
|
85
|
+
end
|
86
|
+
end
|
@@ -33,6 +33,11 @@ module Workflow
|
|
33
33
|
@annotate_next_task[type] << obj
|
34
34
|
end
|
35
35
|
|
36
|
+
def annotate_next_task_single(type, obj)
|
37
|
+
@annotate_next_task ||= {}
|
38
|
+
@annotate_next_task[type] = obj
|
39
|
+
end
|
40
|
+
|
36
41
|
def dep(*args, &block)
|
37
42
|
case args.length
|
38
43
|
when 3
|
@@ -60,13 +65,14 @@ module Workflow
|
|
60
65
|
@tasks ||= IndiferentHash.setup({})
|
61
66
|
begin
|
62
67
|
@annotate_next_task ||= {}
|
63
|
-
|
68
|
+
task = Task.setup(block, @annotate_next_task.merge(name: name, type: type, directory: directory[name]))
|
69
|
+
@tasks[name] = task
|
64
70
|
ensure
|
65
71
|
@annotate_next_task = {}
|
66
72
|
end
|
67
73
|
end
|
68
74
|
|
69
75
|
def desc(description)
|
70
|
-
|
76
|
+
annotate_next_task_single(:description, description)
|
71
77
|
end
|
72
78
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module Workflow
|
2
|
+
attr_accessor :title, :description
|
2
3
|
|
3
4
|
def self.doc_parse_first_line(str)
|
4
5
|
if str.match(/^([^\n]*)\n\n(.*)/sm)
|
@@ -45,33 +46,38 @@ module Workflow
|
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
|
-
def load_documentation
|
49
|
-
return if @documentation
|
50
|
-
@documentation ||= Workflow.parse_workflow_doc documentation_markdown
|
51
|
-
@documentation[:tasks].each do |task, description|
|
52
|
-
if task.include? "#"
|
53
|
-
workflow, task = task.split("#")
|
54
|
-
workflow = begin
|
55
|
-
Kernel.const_get workflow
|
56
|
-
rescue
|
57
|
-
next
|
58
|
-
end
|
59
|
-
else
|
60
|
-
workflow = self
|
61
|
-
end
|
62
|
-
|
63
|
-
task = task.to_sym
|
64
|
-
if workflow.tasks.include? task
|
65
|
-
workflow.tasks[task].description = description
|
66
|
-
else
|
67
|
-
Log.low "Documentation for #{ task }, but not a #{ workflow.to_s } task"
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
49
|
attr_accessor :documentation
|
73
50
|
def documentation
|
74
|
-
|
75
|
-
|
51
|
+
@documentation ||= begin
|
52
|
+
documentation = Workflow.parse_workflow_doc documentation_markdown
|
53
|
+
|
54
|
+
if @description && (documentation[:description].nil? || documentation[:description].empty?)
|
55
|
+
documentation[:description] = @description
|
56
|
+
end
|
57
|
+
|
58
|
+
if @title && (documentation[:title].nil? || documentation[:title].empty?)
|
59
|
+
documentation[:title] = @title
|
60
|
+
end
|
61
|
+
documentation[:tasks].each do |task, description|
|
62
|
+
if task.include? "#"
|
63
|
+
workflow, task = task.split("#")
|
64
|
+
workflow = begin
|
65
|
+
Kernel.const_get workflow
|
66
|
+
rescue
|
67
|
+
next
|
68
|
+
end
|
69
|
+
else
|
70
|
+
workflow = self
|
71
|
+
end
|
72
|
+
|
73
|
+
task = task.to_sym
|
74
|
+
if workflow.tasks.include? task
|
75
|
+
workflow.tasks[task].description = description
|
76
|
+
else
|
77
|
+
Log.low "Documentation for #{ task }, but not a #{ workflow.to_s } task"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
documentation
|
81
|
+
end
|
76
82
|
end
|
77
83
|
end
|
@@ -1,15 +1,21 @@
|
|
1
1
|
class Step
|
2
|
+
SERIALIZER = :json
|
2
3
|
def info_file
|
3
|
-
@info_file ||=
|
4
|
+
@info_file ||= begin
|
5
|
+
info_file = @path + ".info"
|
6
|
+
@path.annotate info_file if Path === @path
|
7
|
+
info_file
|
8
|
+
end
|
4
9
|
end
|
5
10
|
|
6
11
|
def load_info
|
7
|
-
@info = Persist.load(info_file,
|
12
|
+
@info = Persist.load(info_file, SERIALIZER) || {}
|
13
|
+
IndiferentHash.setup(@info)
|
8
14
|
@info_load_time = Time.now
|
9
15
|
end
|
10
16
|
|
11
|
-
def save_info
|
12
|
-
Persist.save(
|
17
|
+
def save_info(info = nil)
|
18
|
+
Persist.save(info, info_file, SERIALIZER)
|
13
19
|
@info_load_time = Time.now
|
14
20
|
end
|
15
21
|
|
@@ -40,24 +46,18 @@ class Step
|
|
40
46
|
info[key] = value
|
41
47
|
end
|
42
48
|
end
|
43
|
-
save_info
|
49
|
+
save_info(info)
|
44
50
|
end
|
45
51
|
|
46
52
|
def set_info(key, value)
|
47
53
|
merge_info(key => value)
|
48
54
|
end
|
49
55
|
|
50
|
-
def init_info
|
51
|
-
@info = {
|
52
|
-
:status => :waiting
|
53
|
-
}
|
54
|
-
end
|
55
|
-
|
56
56
|
def report_status(status, message = nil)
|
57
57
|
if message.nil?
|
58
|
-
Log.info Log.color(
|
58
|
+
Log.info Log.color(status, status.to_s) + " " + Log.color(:path, path)
|
59
59
|
else
|
60
|
-
Log.info Log.color(
|
60
|
+
Log.info Log.color(status, status.to_s) + " " + Log.color(:path, path) + " " + message
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Step
|
2
|
+
def self.relocate(path)
|
3
|
+
return path if Open.exists?(path)
|
4
|
+
Path.setup(path) unless Path === path
|
5
|
+
relocated = path.relocate
|
6
|
+
return relocated if Open.exists?(relocated)
|
7
|
+
subpath = path.split("/")[-3..-1] * "/"
|
8
|
+
relocated = Path.setup("var/jobs")[subpath]
|
9
|
+
return relocated if Open.exists?(relocated)
|
10
|
+
path
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load(path)
|
14
|
+
path = relocate(path) unless Open.exists?(path)
|
15
|
+
raise "Could not load #{path}" unless Open.exists?(path)
|
16
|
+
s = Step.new path
|
17
|
+
end
|
18
|
+
end
|