scout-gear 7.1.0 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +65 -2
  3. data/VERSION +1 -1
  4. data/bin/scout +5 -1
  5. data/lib/rbbt-scout.rb +5 -0
  6. data/lib/scout/concurrent_stream.rb +13 -8
  7. data/lib/scout/config.rb +168 -0
  8. data/lib/scout/exceptions.rb +5 -3
  9. data/lib/scout/indiferent_hash/options.rb +1 -0
  10. data/lib/scout/indiferent_hash.rb +4 -2
  11. data/lib/scout/log/color.rb +3 -2
  12. data/lib/scout/log/progress/report.rb +1 -0
  13. data/lib/scout/log/progress/util.rb +66 -1
  14. data/lib/scout/log/progress.rb +5 -3
  15. data/lib/scout/log.rb +3 -2
  16. data/lib/scout/misc/helper.rb +31 -0
  17. data/lib/scout/misc/monitor.rb +4 -1
  18. data/lib/scout/misc/system.rb +15 -0
  19. data/lib/scout/misc.rb +2 -0
  20. data/lib/scout/named_array.rb +68 -0
  21. data/lib/scout/open/stream.rb +58 -33
  22. data/lib/scout/path/find.rb +27 -3
  23. data/lib/scout/path/util.rb +7 -4
  24. data/lib/scout/persist/serialize.rb +7 -14
  25. data/lib/scout/persist.rb +46 -12
  26. data/lib/scout/resource/produce.rb +7 -94
  27. data/lib/scout/resource/software.rb +176 -0
  28. data/lib/scout/semaphore.rb +8 -1
  29. data/lib/scout/tsv/dumper.rb +112 -0
  30. data/lib/scout/tsv/index.rb +161 -0
  31. data/lib/scout/tsv/open.rb +128 -0
  32. data/lib/scout/tsv/parser.rb +230 -30
  33. data/lib/scout/tsv/path.rb +13 -0
  34. data/lib/scout/tsv/persist/adapter.rb +367 -0
  35. data/lib/scout/tsv/persist/fix_width_table.rb +324 -0
  36. data/lib/scout/tsv/persist/serialize.rb +117 -0
  37. data/lib/scout/tsv/persist/tokyocabinet.rb +113 -0
  38. data/lib/scout/tsv/persist.rb +13 -0
  39. data/lib/scout/tsv/traverse.rb +143 -0
  40. data/lib/scout/tsv/util/filter.rb +303 -0
  41. data/lib/scout/tsv/util/process.rb +73 -0
  42. data/lib/scout/tsv/util/select.rb +220 -0
  43. data/lib/scout/tsv/util.rb +82 -0
  44. data/lib/scout/tsv.rb +16 -3
  45. data/lib/scout/work_queue/worker.rb +4 -4
  46. data/lib/scout/work_queue.rb +22 -7
  47. data/lib/scout/workflow/definition.rb +101 -4
  48. data/lib/scout/workflow/step/config.rb +18 -0
  49. data/lib/scout/workflow/step/dependencies.rb +40 -0
  50. data/lib/scout/workflow/step/file.rb +15 -0
  51. data/lib/scout/workflow/step/info.rb +35 -4
  52. data/lib/scout/workflow/step/progress.rb +14 -0
  53. data/lib/scout/workflow/step/provenance.rb +148 -0
  54. data/lib/scout/workflow/step.rb +71 -17
  55. data/lib/scout/workflow/task.rb +10 -5
  56. data/lib/scout/workflow/usage.rb +3 -1
  57. data/lib/scout/workflow.rb +11 -3
  58. data/lib/scout-gear.rb +1 -0
  59. data/lib/scout.rb +1 -0
  60. data/scout-gear.gemspec +64 -10
  61. data/scout_commands/find +1 -1
  62. data/scout_commands/workflow/task +16 -9
  63. data/scout_commands/workflow/task_old +2 -2
  64. data/share/software/install_helpers +523 -0
  65. data/test/scout/log/test_progress.rb +0 -2
  66. data/test/scout/misc/test_system.rb +21 -0
  67. data/test/scout/open/test_stream.rb +160 -1
  68. data/test/scout/path/test_find.rb +14 -7
  69. data/test/scout/resource/test_software.rb +24 -0
  70. data/test/scout/test_config.rb +66 -0
  71. data/test/scout/test_meta_extension.rb +10 -0
  72. data/test/scout/test_named_array.rb +19 -0
  73. data/test/scout/test_persist.rb +96 -0
  74. data/test/scout/test_tmpfile.rb +1 -1
  75. data/test/scout/test_tsv.rb +50 -1
  76. data/test/scout/test_work_queue.rb +41 -13
  77. data/test/scout/tsv/persist/test_adapter.rb +44 -0
  78. data/test/scout/tsv/persist/test_fix_width_table.rb +134 -0
  79. data/test/scout/tsv/persist/test_tokyocabinet.rb +92 -0
  80. data/test/scout/tsv/test_dumper.rb +44 -0
  81. data/test/scout/tsv/test_index.rb +156 -0
  82. data/test/scout/tsv/test_open.rb +9 -0
  83. data/test/scout/tsv/test_parser.rb +114 -3
  84. data/test/scout/tsv/test_persist.rb +43 -0
  85. data/test/scout/tsv/test_traverse.rb +116 -0
  86. data/test/scout/tsv/test_util.rb +23 -0
  87. data/test/scout/tsv/util/test_filter.rb +188 -0
  88. data/test/scout/tsv/util/test_process.rb +47 -0
  89. data/test/scout/tsv/util/test_select.rb +44 -0
  90. data/test/scout/work_queue/test_worker.rb +66 -9
  91. data/test/scout/workflow/step/test_dependencies.rb +25 -0
  92. data/test/scout/workflow/step/test_info.rb +15 -17
  93. data/test/scout/workflow/step/test_load.rb +19 -21
  94. data/test/scout/workflow/step/test_provenance.rb +25 -0
  95. data/test/scout/workflow/test_step.rb +206 -10
  96. data/test/scout/workflow/test_task.rb +0 -3
  97. data/test/test_helper.rb +9 -1
  98. metadata +50 -6
@@ -21,27 +21,27 @@ class WorkQueue
21
21
  raise obj
22
22
  end
23
23
  res = block.call obj
24
- output.write res unless output.nil? || ignore_ouput || res == :ignore
24
+ output.write res unless ignore_ouput || res == :ignore
25
25
  end
26
26
  rescue DoneProcessing
27
27
  rescue Interrupt
28
28
  rescue Exception
29
29
  output.write WorkerException.new($!, Process.pid)
30
- exit -1
30
+ Kernel.exit -1
31
31
  end
32
32
  end
33
33
  end
34
34
 
35
35
  def abort
36
36
  begin
37
- Log.log "Aborting worker #{@pid}"
37
+ Log.debug "Aborting worker #{@pid}"
38
38
  Process.kill "INT", @pid
39
39
  rescue Errno::ECHILD
40
40
  end
41
41
  end
42
42
 
43
43
  def join
44
- Log.log "Joining worker #{@pid}"
44
+ Log.debug "Joining worker #{@pid}"
45
45
  Process.waitpid @pid
46
46
  end
47
47
 
@@ -36,8 +36,12 @@ class WorkQueue
36
36
 
37
37
  def remove_worker(pid)
38
38
  @worker_mutex.synchronize do
39
- @workers.delete_if{|w| w.pid == pid }
40
- @removed_workers << pid
39
+ worker = @workers.index{|w| w.pid == pid}
40
+ if worker
41
+ Log.debug "Removed worker #{pid}"
42
+ @workers.delete_at(worker)
43
+ @removed_workers << pid
44
+ end
41
45
  end
42
46
  end
43
47
 
@@ -50,11 +54,13 @@ class WorkQueue
50
54
  while true
51
55
  obj = @output.read
52
56
  if DoneProcessing === obj
57
+
53
58
  done = @worker_mutex.synchronize do
54
59
  Log.low "Worker #{obj.pid} done"
55
60
  @done_workers << obj.pid
56
- @done_workers.length == @removed_workers.length + @workers.length
61
+ @closed && @done_workers.length == @removed_workers.length + @workers.length
57
62
  end
63
+
58
64
  break if done
59
65
  elsif Exception === obj
60
66
  raise obj
@@ -65,9 +71,13 @@ class WorkQueue
65
71
  rescue DoneProcessing
66
72
  rescue Aborted
67
73
  rescue WorkerException
68
- Log.error "Exception in worker #{obj.pid} #{Log.fingerprint obj.exception}"
74
+ Log.error "Exception in worker #{obj.pid} in queue #{Process.pid}: #{obj.message}"
69
75
  self.abort
70
- raise obj.exception
76
+ raise obj.worker_exception
77
+ rescue
78
+ Log.error "Exception processing output in queue #{Process.pid}: #{$!.message}"
79
+ self.abort
80
+ raise $!
71
81
  end
72
82
  end
73
83
 
@@ -84,11 +94,12 @@ class WorkQueue
84
94
  while true
85
95
  pid = Process.wait
86
96
  remove_worker(pid)
87
- break if workers.empty?
97
+ break if @worker_mutex.synchronize{ @workers.empty? }
88
98
  end
89
99
  end
90
100
  end
91
101
 
102
+ Thread.pass until @worker_mutex.synchronize{ @workers.select{|w| w.pid.nil? }.empty? }
92
103
  Thread.pass until @waiter["name"]
93
104
  end
94
105
 
@@ -97,10 +108,14 @@ class WorkQueue
97
108
  end
98
109
 
99
110
  def abort
100
- workers.each{|w| w.abort }
111
+ Log.low "Aborting #{@workers.length} workers in queue #{Process.pid}"
112
+ @worker_mutex.synchronize do
113
+ @workers.each{|w| w.abort }
114
+ end
101
115
  end
102
116
 
103
117
  def close
118
+ @closed = true
104
119
  @worker_mutex.synchronize{ @workers.length }.times do
105
120
  @input.write DoneProcessing.new()
106
121
  end
@@ -2,7 +2,7 @@ require_relative '../meta_extension'
2
2
 
3
3
  module Workflow
4
4
  extend MetaExtension
5
- extension_attr :name, :tasks
5
+ extension_attr :name, :tasks, :helpers
6
6
 
7
7
  class << self
8
8
  attr_accessor :directory
@@ -17,6 +17,32 @@ module Workflow
17
17
  @name ||= self.to_s
18
18
  end
19
19
 
20
+ def helpers
21
+ @helpers ||= {}
22
+ end
23
+
24
+ def helper(name, *args, &block)
25
+ if block_given?
26
+ helpers[name] = block
27
+ else
28
+ raise RbbtException, "helper #{name} unkown in #{self} workflow" unless helpers[name]
29
+ helpers[name].call(*args)
30
+ end
31
+ end
32
+
33
+ def step_module
34
+ @_m ||= begin
35
+ m = Module.new
36
+
37
+ helpers.each do |name,block|
38
+ m.send(:define_method, name, &block)
39
+ end
40
+
41
+ m
42
+ end
43
+ @_m
44
+ end
45
+
20
46
  attr_accessor :directory
21
47
  def directory
22
48
  @directory ||= Workflow.directory[name]
@@ -60,19 +86,90 @@ module Workflow
60
86
  annotate_next_task(:inputs, args)
61
87
  end
62
88
 
89
+ def desc(description)
90
+ annotate_next_task_single(:description, description)
91
+ end
92
+
93
+ def returns(type)
94
+ annotate_next_task_single(:returns, type)
95
+ end
96
+
97
+ def extension(extension)
98
+ annotate_next_task_single(:extension, extension)
99
+ end
100
+
63
101
  def task(name_and_type, &block)
64
102
  name, type = name_and_type.collect.first
65
103
  @tasks ||= IndiferentHash.setup({})
66
104
  begin
67
105
  @annotate_next_task ||= {}
68
- task = Task.setup(block, @annotate_next_task.merge(name: name, type: type, directory: directory[name]))
106
+ task = Task.setup(block, @annotate_next_task.merge(name: name, type: type, directory: directory[name], workflow: self))
69
107
  @tasks[name] = task
70
108
  ensure
71
109
  @annotate_next_task = {}
72
110
  end
73
111
  end
74
112
 
75
- def desc(description)
76
- annotate_next_task_single(:description, description)
113
+ def task_alias(name, workflow, oname, *rest, &block)
114
+ dep(workflow, oname, *rest, &block)
115
+ extension :dep_task unless @extension
116
+ returns workflow.tasks[oname].returns if workflow.tasks.include?(oname) unless @returns
117
+ task name => nil do
118
+ raise RbbtException, "dep_task does not have any dependencies" if dependencies.empty?
119
+ Step.wait_for_jobs dependencies.select{|d| d.streaming? }
120
+ dep = dependencies.last
121
+ dep.join
122
+ raise dep.get_exception if dep.error?
123
+ raise Aborted, "Aborted dependency #{dep.path}" if dep.aborted?
124
+ set_info :result_type, dep.info[:result_type]
125
+ forget = config :forget_dep_tasks, "forget_dep_tasks", :default => FORGET_DEP_TASKS
126
+ if forget
127
+ remove = config :remove_dep_tasks, "remove_dep_tasks", :default => REMOVE_DEP_TASKS
128
+
129
+ self.archive_deps
130
+ self.copy_files_dir
131
+ self.dependencies = self.dependencies - [dep]
132
+ Open.rm_rf self.files_dir if Open.exist? self.files_dir
133
+ FileUtils.cp_r dep.files_dir, self.files_dir if Open.exist?(dep.files_dir)
134
+
135
+ if dep.overriden || ! Workflow.job_path?(dep.path)
136
+ Open.link dep.path, self.tmp_path
137
+ else
138
+ Open.ln_h dep.path, self.tmp_path
139
+
140
+ case remove.to_s
141
+ when 'true'
142
+ dep.clean
143
+ when 'recursive'
144
+ (dep.dependencies + dep.rec_dependencies).uniq.each do |d|
145
+ next if d.overriden
146
+ d.clean unless config(:remove_dep, d.task_signature, d.task_name, d.workflow.to_s, :default => true).to_s == 'false'
147
+ end
148
+ dep.clean unless config(:remove_dep, dep.task_signature, dep.task_name, dep.workflow.to_s, :default => true).to_s == 'false'
149
+ end
150
+ end
151
+ else
152
+ if Open.exists?(dep.files_dir)
153
+ Open.rm_rf self.files_dir
154
+ Open.link dep.files_dir, self.files_dir
155
+ end
156
+ if defined?(RemoteStep) && RemoteStep === dep
157
+ Open.write(self.tmp_path, Open.read(dep.path))
158
+ else
159
+ Open.link dep.path, self.path
160
+ end
161
+ end
162
+ nil
163
+ end
77
164
  end
165
+
166
+ alias dep_task task_alias
167
+
168
+ def export(*args)
169
+ end
170
+
171
+ alias export_synchronous export
172
+ alias export_asynchronous export
173
+ alias export_exec export
174
+ alias export_stream export
78
175
  end
@@ -0,0 +1,18 @@
1
+ require_relative '../../config'
2
+
3
+ class Step
4
+ def config(key, *tokens)
5
+ options = tokens.pop if Hash === tokens.last
6
+ options ||= {}
7
+
8
+ new_tokens = []
9
+ if workflow
10
+ workflow_name = workflow.to_s
11
+ new_tokens << ("workflow:" << workflow_name)
12
+ new_tokens << ("task:" << workflow_name << "#" << task_name.to_s)
13
+ end
14
+ new_tokens << ("task:" << task_name.to_s)
15
+
16
+ Scout::Config.get(key, tokens + new_tokens, options)
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ class Step
2
+ def recursive_inputs
3
+ dependencies.inject(@inputs.annotate(@inputs.dup)) do |acc,dep|
4
+ acc.concat(dep.inputs) if dep.inputs
5
+ acc
6
+ end
7
+ end
8
+
9
+ def input_dependencies
10
+ return [] unless inputs
11
+ inputs.select do |d|
12
+ Step === d
13
+ end
14
+ end
15
+
16
+ def prepare_dependencies
17
+ inverse_dep = {}
18
+ dependencies.each{|dep|
19
+ next if dep.done?
20
+ if dep.dependencies
21
+ dep.dependencies.each do |d|
22
+ inverse_dep[d] ||= []
23
+ inverse_dep[d] << dep
24
+ end
25
+ end
26
+ input_dependencies.each do |d|
27
+ inverse_dep[d] ||= []
28
+ inverse_dep[d] << dep
29
+ end
30
+ }
31
+ inverse_dep.each do |dep,list|
32
+ dep.tee_copies = list.length
33
+ end
34
+ end
35
+
36
+ def run_dependencies
37
+ dependencies.each{|dep| dep.run unless dep.running? || dep.done? }
38
+ end
39
+
40
+ end
@@ -0,0 +1,15 @@
1
+ class Step
2
+ def files_dir
3
+ @files_dir ||= begin
4
+ dir = @path + ".files"
5
+ @path.annotate(dir) if Path === @path
6
+ dir
7
+ end
8
+ end
9
+
10
+ def file(file)
11
+ dir = files_dir
12
+ Path.setup(dir) unless Path === dir
13
+ dir[file]
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  class Step
2
- SERIALIZER = :json
2
+ SERIALIZER = :marshal
3
3
  def info_file
4
4
  @info_file ||= begin
5
5
  info_file = @path + ".info"
@@ -20,7 +20,11 @@ class Step
20
20
  end
21
21
 
22
22
  def info
23
- outdated = @info && Open.exists?(info_file) && @info_load_time && Open.mtime(info_file) > @info_load_time
23
+ outdated = begin
24
+ @info_load_time && (mtime = Open.mtime(info_file)) && mtime > @info_load_time
25
+ rescue
26
+ true
27
+ end
24
28
 
25
29
  if @info.nil? || outdated
26
30
  load_info
@@ -33,6 +37,19 @@ class Step
33
37
  info = self.info
34
38
  new_info.each do |key,value|
35
39
  report_status new_info[:status], new_info[:message] if key == :status
40
+ if Exception === value
41
+ begin
42
+ Marshal.dump(value)
43
+ rescue TypeError
44
+ if ScoutException === value
45
+ new = ScoutException.new value.message
46
+ else
47
+ new = Exception.new value.message
48
+ end
49
+ new.set_backtrace(value.backtrace)
50
+ value = new
51
+ end
52
+ end
36
53
  if info.include?(key)
37
54
  case info[key]
38
55
  when Array
@@ -62,7 +79,6 @@ class Step
62
79
  end
63
80
 
64
81
  def log(status, message = nil)
65
- report_status status, message
66
82
  if message
67
83
  merge_info :status => status, :messages => [message]
68
84
  else
@@ -71,7 +87,22 @@ class Step
71
87
  end
72
88
 
73
89
  def status
74
- info[:status]
90
+ info[:status].tap{|s| s.nil? ? s : s.to_sym }
75
91
  end
76
92
 
93
+ def error?
94
+ status == :error
95
+ end
96
+
97
+ def aborted?
98
+ status == :aborted
99
+ end
100
+
101
+ def running?
102
+ ! done? && (info[:pid] && Misc.pid_alive?(info[:pid]))
103
+ end
104
+
105
+ def exception
106
+ info[:exception]
107
+ end
77
108
  end
@@ -0,0 +1,14 @@
1
+ class Step
2
+ def progress_bar(msg = "Progress", options = nil)
3
+ if Hash === msg and options.nil?
4
+ options = msg
5
+ msg = nil
6
+ end
7
+ options = {} if options.nil?
8
+
9
+ max = options[:max]
10
+ Open.mkdir files_dir
11
+ Log::ProgressBar.new_bar(max, {:desc => msg, :file => (@exec ? nil : file(:progress))}.merge(options))
12
+ end
13
+ end
14
+
@@ -0,0 +1,148 @@
1
+ class Step
2
+ def self.job_path?(path)
3
+ path.split("/")[-4] == "jobs"
4
+ end
5
+
6
+ def self.status_color(status)
7
+ case status.to_sym
8
+ when :error, :aborted, :missing, :dead, :unsync
9
+ :red
10
+ when :streaming, :started
11
+ :cyan
12
+ when :done, :noinfo
13
+ :green
14
+ when :dependencies, :waiting, :setup
15
+ :yellow
16
+ when :notfound, :cleaned
17
+ :blue
18
+ else
19
+ if status.to_s.index ">"
20
+ :cyan
21
+ else
22
+ :cyan
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.prov_status_msg(status)
28
+ color = status_color(status)
29
+ Log.color(color, status.to_s)
30
+ end
31
+
32
+ def self.prov_report_msg(status, name, path, info, input = nil)
33
+ parts = path.sub(/\{.*/,'').split "/"
34
+
35
+ parts.pop
36
+
37
+ task = Log.color(:yellow, parts.pop)
38
+ workflow = Log.color(:magenta, parts.pop)
39
+
40
+ if ! Step.job_path?(path)
41
+ task, status, workflow = Log.color(:yellow, info[:task_name]), Log.color(:green, "file"), Log.color(:magenta, "-")
42
+ end
43
+
44
+ path_mtime = begin
45
+ Open.mtime(path)
46
+ rescue Exception
47
+ nil
48
+ end
49
+
50
+ if input.nil? || input.empty?
51
+ input_str = nil
52
+ else
53
+ input = input.reject{|dep,name| (input & dep.dependencies.collect{|d| [d,name]}).any? }
54
+ input = input.reject{|dep,name| (input & dep.input_dependencies.collect{|d| [d,name]}).any? }
55
+ input_str = Log.color(:magenta, "-> ") + input.collect{|dep,name| Log.color(:yellow, dep.task_name.to_s) + ":" + Log.color(:yellow, name) }.uniq * " "
56
+ end
57
+
58
+ str = if ! (Open.remote?(path) || Open.ssh?(path)) && (Open.exists?(path) && $main_mtime && path_mtime && ($main_mtime - path_mtime) < -2)
59
+ prov_status_msg(status.to_s) << " " << [workflow, task, path, input_str].compact * " " << " (#{Log.color(:red, "Mtime out of sync") })"
60
+ else
61
+ prov_status_msg(status.to_s) << " " << [workflow, task, path, input_str].compact * " "
62
+ end
63
+
64
+ if $inputs and $inputs.any?
65
+ job_inputs = Workflow.load_step(path).recursive_inputs.to_hash
66
+ IndiferentHash.setup(job_inputs)
67
+
68
+ $inputs.each do |input|
69
+ value = job_inputs[input]
70
+ next if value.nil?
71
+ value_str = Misc.fingerprint(value)
72
+ str << "\t#{Log.color :magenta, input}=#{value_str}"
73
+ end
74
+ end
75
+
76
+ if $info_fields and $info_fields.any?
77
+ $info_fields.each do |field|
78
+ IndiferentHash.setup(info)
79
+ value = info[field]
80
+ next if value.nil?
81
+ value_str = Misc.fingerprint(value)
82
+ str << "\t#{Log.color :magenta, field}=#{value_str}"
83
+ end
84
+ end
85
+
86
+ str << "\n"
87
+ end
88
+
89
+ def self.prov_report(step, offset = 0, task = nil, seen = [], expand_repeats = false, input = nil)
90
+ info = step.info || {}
91
+ info[:task_name] = task
92
+ path = step.path
93
+ status = info[:status] || :missing
94
+ status = "remote" if Open.remote?(path) || Open.ssh?(path)
95
+ name = info[:name] || File.basename(path)
96
+ status = :unsync if status == :done and not Open.exist?(path)
97
+ status = :notfound if status == :noinfo and not Open.exist?(path)
98
+
99
+
100
+ this_step_msg = prov_report_msg(status, name, path, info, input)
101
+
102
+ input_dependencies = {}
103
+ step.dependencies.each do |dep|
104
+ if dep.input_dependencies.any?
105
+ dep.input_dependencies.each do |id|
106
+ input_name, _dep = dep.recursive_inputs.fields.zip(dep.recursive_inputs).select{|f,d|
107
+ d == id || (String === d && d.start_with?(id.files_dir)) || (Array === d && d.include?(id))
108
+ }.last
109
+ if input_name
110
+ input_dependencies[id] ||= []
111
+ input_dependencies[id] << [dep, input_name]
112
+ end
113
+ end
114
+ end
115
+ end if step.dependencies
116
+
117
+ str = ""
118
+ str = " " * offset + this_step_msg if ENV["RBBT_ORIGINAL_STACK"] == 'true'
119
+
120
+ step.dependencies.dup.tap{|l|
121
+ l.reverse! if ENV["RBBT_ORIGINAL_STACK"] == 'true'
122
+ }.each do |dep|
123
+ path = dep.path
124
+ new = ! seen.include?(path)
125
+ if new
126
+ seen << path
127
+ str << prov_report(dep, offset + 1, task, seen, expand_repeats, input_dependencies[dep])
128
+ else
129
+ if expand_repeats
130
+ str << Log.color(Step.status_color(dep.status), Log.uncolor(prov_report(dep, offset+1, task)))
131
+ else
132
+ info = dep.info || {}
133
+ status = info[:status] || :missing
134
+ status = "remote" if Open.remote?(path) || Open.ssh?(path)
135
+ name = info[:name] || File.basename(path)
136
+ status = :unsync if status == :done and not Open.exist?(path)
137
+ status = :notfound if status == :noinfo and not Open.exist?(path)
138
+
139
+ str << Log.color(Step.status_color(status), " " * (offset + 1) + Log.uncolor(prov_report_msg(status, name, path, info, input_dependencies[dep])))
140
+ end
141
+ end
142
+ end if step.dependencies
143
+
144
+ str += (" " * offset) + this_step_msg unless ENV["RBBT_ORIGINAL_STACK"] == 'true'
145
+
146
+ str
147
+ end
148
+ end