rbbt-util 5.13.37 → 5.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rbbt +6 -1
  3. data/lib/rbbt/fix_width_table.rb +21 -9
  4. data/lib/rbbt/monitor.rb +1 -1
  5. data/lib/rbbt/packed_index.rb +19 -5
  6. data/lib/rbbt/persist/tsv.rb +9 -1
  7. data/lib/rbbt/persist/tsv/fix_width_table.rb +1 -1
  8. data/lib/rbbt/persist/tsv/packed_index.rb +101 -0
  9. data/lib/rbbt/persist/tsv/sharder.rb +11 -3
  10. data/lib/rbbt/resource/path.rb +1 -1
  11. data/lib/rbbt/resource/rake.rb +1 -0
  12. data/lib/rbbt/tsv/accessor.rb +18 -13
  13. data/lib/rbbt/tsv/dumper.rb +2 -6
  14. data/lib/rbbt/tsv/manipulate.rb +6 -4
  15. data/lib/rbbt/tsv/parallel/traverse.rb +7 -6
  16. data/lib/rbbt/tsv/parser.rb +20 -16
  17. data/lib/rbbt/tsv/stream.rb +87 -76
  18. data/lib/rbbt/tsv/util.rb +8 -3
  19. data/lib/rbbt/util/R.rb +1 -1
  20. data/lib/rbbt/util/cmd.rb +0 -3
  21. data/lib/rbbt/util/concurrency/processes.rb +3 -0
  22. data/lib/rbbt/util/concurrency/processes/worker.rb +0 -1
  23. data/lib/rbbt/util/log.rb +45 -18
  24. data/lib/rbbt/util/log/progress/report.rb +3 -2
  25. data/lib/rbbt/util/log/progress/util.rb +1 -1
  26. data/lib/rbbt/util/misc/concurrent_stream.rb +12 -6
  27. data/lib/rbbt/util/misc/development.rb +10 -4
  28. data/lib/rbbt/util/misc/lock.rb +1 -1
  29. data/lib/rbbt/util/misc/omics.rb +2 -0
  30. data/lib/rbbt/util/misc/pipes.rb +90 -87
  31. data/lib/rbbt/workflow.rb +6 -2
  32. data/lib/rbbt/workflow/accessor.rb +70 -40
  33. data/lib/rbbt/workflow/definition.rb +23 -0
  34. data/lib/rbbt/workflow/step.rb +15 -3
  35. data/lib/rbbt/workflow/step/run.rb +18 -13
  36. data/lib/rbbt/workflow/usage.rb +3 -0
  37. data/share/Rlib/util.R +1 -1
  38. data/share/rbbt_commands/tsv/get +0 -2
  39. data/share/rbbt_commands/tsv/info +13 -5
  40. data/share/rbbt_commands/tsv/subset +1 -1
  41. data/share/rbbt_commands/workflow/info +32 -0
  42. data/share/rbbt_commands/workflow/task +0 -2
  43. data/test/rbbt/persist/tsv/test_sharder.rb +44 -0
  44. data/test/rbbt/test_fix_width_table.rb +1 -0
  45. data/test/rbbt/test_packed_index.rb +3 -0
  46. data/test/rbbt/tsv/test_stream.rb +55 -2
  47. data/test/rbbt/util/misc/test_pipes.rb +8 -6
  48. data/test/rbbt/workflow/test_step.rb +7 -6
  49. metadata +3 -2
data/lib/rbbt/workflow.rb CHANGED
@@ -275,14 +275,18 @@ module Workflow
275
275
  Workflow.resolve_locals(inputs)
276
276
 
277
277
  task_inputs = task_info(taskname)[:inputs]
278
- defaults = task_info(taskname)[:input_defaults]
278
+ defaults = IndiferentHash.setup(task_info(taskname)[:input_defaults])
279
279
 
280
280
  dependencies = real_dependencies(task, jobname, defaults.merge(inputs), task_dependencies[taskname] || [])
281
281
 
282
282
  real_inputs = {}
283
283
 
284
284
  inputs.each do |k,v|
285
- real_inputs[k] = v if (task_inputs.include?(k.to_sym) or task_inputs.include?(k.to_s)) and (defaults[k].to_s != v.to_s and not (FalseClass === v and defaults[k].nil?))
285
+ default = defaults[k]
286
+ if (task_inputs.include?(k.to_sym) or task_inputs.include?(k.to_s)) and
287
+ (defaults[k].to_s != v.to_s and not (FalseClass === v and defaults[k].nil?))
288
+ real_inputs[k] = v
289
+ end
286
290
  end
287
291
 
288
292
  if real_inputs.empty?
@@ -2,7 +2,8 @@ require 'rbbt/util/open'
2
2
  require 'yaml'
3
3
 
4
4
  class Step
5
-
5
+
6
+
6
7
  INFO_SERIALIAZER = Marshal
7
8
 
8
9
  def self.started?
@@ -12,7 +13,7 @@ class Step
12
13
  def self.wait_for_jobs(jobs)
13
14
  begin
14
15
  threads = []
15
- jobs.each do |j| threads << Thread.new{j.grace.join} end
16
+ jobs.each do |j| threads << Thread.new{j.join} end
16
17
  threads.each{|t| t.join }
17
18
  rescue Exception
18
19
  threads.each{|t| t.exit }
@@ -66,33 +67,40 @@ class Step
66
67
  @info_file ||= Step.info_file(path)
67
68
  end
68
69
 
69
- def info
70
+ def info_lock
71
+ @info_lock ||= begin
72
+ path = Persist.persistence_path(info_file + '.lock', {:dir => Step.lock_dir})
73
+ Lockfile.new path
74
+ end
75
+ end
76
+
77
+ def info(check_lock = true)
70
78
  return {} if info_file.nil? or not Open.exists? info_file
71
79
  begin
72
- @info_mutex.synchronize do
73
- begin
74
- return @info_cache if @info_cache and File.mtime(info_file) < @info_cache_time
75
- rescue Exception
76
- raise $!
77
- end
80
+ begin
81
+ return @info_cache if @info_cache and File.ctime(info_file) < @info_cache_time
82
+ rescue Exception
83
+ raise $!
84
+ end
78
85
 
79
- begin
80
- @info_cache = Misc.insist(2, 3, info_file) do
81
- Misc.insist(2, 1, info_file) do
82
- Misc.insist(3, 0.2, info_file) do
83
- Open.open(info_file) do |file|
84
- INFO_SERIALIAZER.load(file) || {}
85
- end
86
+ begin
87
+ @info_cache = Misc.insist(2, 3, info_file) do
88
+ Misc.insist(2, 1, info_file) do
89
+ Misc.insist(3, 0.2, info_file) do
90
+ raise TryAgain, "Info locked" if check_lock and info_lock.locked?
91
+ Open.open(info_file) do |file|
92
+ INFO_SERIALIAZER.load(file) #|| {}
86
93
  end
87
94
  end
88
95
  end
89
- @info_cache_time = Time.now
90
- @info_cache
91
96
  end
97
+ @info_cache_time = Time.now
98
+ @info_cache
92
99
  end
93
100
  rescue Exception
94
101
  Log.debug{"Error loading info file: " + info_file}
95
- Open.write(info_file, INFO_SERIALIAZER.dump({:status => :error, :messages => ["Info file lost"]}))
102
+ Log.exception $!
103
+ Open.sensiblewrite(info_file, INFO_SERIALIAZER.dump({:status => :error, :messages => ["Info file lost"]}))
96
104
  raise $!
97
105
  end
98
106
  end
@@ -100,12 +108,11 @@ class Step
100
108
  def set_info(key, value)
101
109
  return nil if @exec or info_file.nil?
102
110
  value = Annotated.purge value if defined? Annotated
103
- lock_filename = Persist.persistence_path(info_file, {:dir => Step.lock_dir})
104
- Open.lock(info_file, :refresh => false) do
105
- i = info
111
+ Open.lock(info_file, :lock => info_lock) do
112
+ i = info(false)
106
113
  i[key] = value
107
114
  @info_cache = i
108
- Open.write(info_file, INFO_SERIALIAZER.dump(i))
115
+ Misc.sensiblewrite(info_file, INFO_SERIALIAZER.dump(i), :force => true)
109
116
  @info_cache_time = Time.now
110
117
  value
111
118
  end
@@ -114,12 +121,11 @@ class Step
114
121
  def merge_info(hash)
115
122
  return nil if @exec or info_file.nil?
116
123
  value = Annotated.purge value if defined? Annotated
117
- lock_filename = Persist.persistence_path(info_file, {:dir => Step.lock_dir})
118
- Open.lock(info_file, :refresh => false) do
119
- i = info
124
+ Open.lock(info_file, :lock => info_lock) do
125
+ i = info(false)
120
126
  i.merge! hash
121
127
  @info_cache = i
122
- Open.write(info_file, INFO_SERIALIAZER.dump(i))
128
+ Misc.sensiblewrite(info_file, INFO_SERIALIAZER.dump(i), :force => true)
123
129
  @info_cache_time = Time.now
124
130
  value
125
131
  end
@@ -416,10 +422,11 @@ module Workflow
416
422
 
417
423
  def rec_dependencies(taskname)
418
424
  if task_dependencies.include? taskname
419
- deps = task_dependencies[taskname].select{|dep| String === dep or Symbol === dep or Array === dep}
420
- all_deps = deps.dup
425
+ deps = task_dependencies[taskname]
426
+ all_deps = deps.select{|dep| String === dep or Symbol === dep or Array === dep}
421
427
  deps.each do |dep|
422
- if Array === dep
428
+ case dep
429
+ when Array
423
430
  dep.first.rec_dependencies(dep.last).each do |d|
424
431
  if Array === d
425
432
  all_deps << d
@@ -427,8 +434,10 @@ module Workflow
427
434
  all_deps << [dep.first, d]
428
435
  end
429
436
  end
430
- else
437
+ when String, Symbol
431
438
  all_deps.concat rec_dependencies(dep.to_sym)
439
+ when DependencyBlock
440
+ all_deps << dep.dependency
432
441
  end
433
442
  end
434
443
  all_deps.uniq
@@ -437,28 +446,49 @@ module Workflow
437
446
  end
438
447
  end
439
448
 
449
+ def task_from_dep(dep)
450
+ case dep
451
+ when Array
452
+ dep.first.tasks[dep.last]
453
+ when String
454
+ tasks[dep.to_sym]
455
+ when Symbol
456
+ tasks[dep.to_sym]
457
+ else
458
+ raise "Unknown dependency: #{Misc.fingerprint dep}"
459
+ end
460
+ end
461
+
440
462
  def rec_inputs(taskname)
441
- [taskname].concat(rec_dependencies(taskname)).inject([]){|acc, tn| acc.concat((Array === tn ? tn.first.tasks[tn.last] : tasks[tn.to_sym]).inputs) }.uniq
463
+ [taskname].concat(rec_dependencies(taskname)).inject([]){|acc, tn| acc.concat(task_from_dep(tn).inputs) }.uniq
442
464
  end
443
465
 
444
466
  def rec_input_defaults(taskname)
445
- [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge((Array === tn ? tn.first.tasks[tn.last.to_sym] : tasks[tn.to_sym]).input_defaults) }.
446
- tap{|h| IndiferentHash.setup(h)}
467
+ [taskname].concat(rec_dependencies(taskname)).inject(IndiferentHash.setup({})){|acc, tn|
468
+ new = (Array === tn ? tn.first.tasks[tn.last.to_sym] : tasks[tn.to_sym]).input_defaults
469
+ acc = new.merge(acc)
470
+ }.tap{|h| IndiferentHash.setup(h)}
447
471
  end
448
472
 
449
473
  def rec_input_types(taskname)
450
- [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge((Array === tn ? tn.first.tasks[tn.last.to_sym] : tasks[tn.to_sym]).input_types) }.
451
- tap{|h| IndiferentHash.setup(h) }
474
+ [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn|
475
+ new = (Array === tn ? tn.first.tasks[tn.last.to_sym] : tasks[tn.to_sym]).input_types
476
+ acc = new.merge(acc)
477
+ }.tap{|h| IndiferentHash.setup(h)}
452
478
  end
453
479
 
454
480
  def rec_input_descriptions(taskname)
455
- [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge((Array === tn ? tn.first.tasks[tn.last.to_sym] : tasks[tn.to_sym]).input_descriptions) }.
456
- tap{|h| IndiferentHash.setup(h)}
481
+ [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn|
482
+ new = (Array === tn ? tn.first.tasks[tn.last.to_sym] : tasks[tn.to_sym]).input_descriptions
483
+ acc = new.merge(acc)
484
+ }.tap{|h| IndiferentHash.setup(h)}
457
485
  end
458
486
 
459
487
  def rec_input_options(taskname)
460
- [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge((Array === tn ? tn.first.tasks[tn.last.to_sym] : tasks[tn.to_sym]).input_options)}.
461
- tap{|h| IndiferentHash.setup(h)}
488
+ [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn|
489
+ new = (Array === tn ? tn.first.tasks[tn.last.to_sym] : tasks[tn.to_sym]).input_options
490
+ acc = new.merge(acc)
491
+ }.tap{|h| IndiferentHash.setup(h)}
462
492
  end
463
493
 
464
494
  def real_dependencies(task, jobname, inputs, dependencies)
@@ -4,6 +4,15 @@ require 'rbbt/workflow/annotate'
4
4
  module Workflow
5
5
  include AnnotatedModule
6
6
 
7
+ module DependencyBlock
8
+ attr_accessor :dependency
9
+ def self.setup(block, dependency)
10
+ block.extend DependencyBlock
11
+ block.dependency = dependency
12
+ block
13
+ end
14
+ end
15
+
7
16
  AnnotatedModule.add_consummable_annotation(self,
8
17
  :result_description => "",
9
18
  :result_type => nil,
@@ -36,6 +45,20 @@ module Workflow
36
45
  end
37
46
  end
38
47
 
48
+ def dep(*dependency, &block)
49
+ @dependencies ||= []
50
+ if block_given?
51
+ DependencyBlock.setup block, dependency if dependency.any?
52
+ @dependencies << block
53
+ else
54
+ if Module === dependency.first
55
+ @dependencies << dependency
56
+ else
57
+ @dependencies.concat dependency
58
+ end
59
+ end
60
+ end
61
+
39
62
  def task(name, &block)
40
63
  if Hash === name
41
64
  type = name.first.last
@@ -14,7 +14,11 @@ class Step
14
14
  attr_accessor :lock_dir
15
15
 
16
16
  def lock_dir
17
- @lock_dir ||= Rbbt.tmp.step_info_locks.find
17
+ @lock_dir ||= begin
18
+ dir = Rbbt.tmp.step_info_locks.find
19
+ FileUtils.mkdir_p dir unless Open.exists? dir
20
+ dir
21
+ end
18
22
  end
19
23
  end
20
24
 
@@ -35,11 +39,18 @@ class Step
35
39
  @mutex = Mutex.new
36
40
  @info_mutex = Mutex.new
37
41
  @inputs = inputs || []
38
- NamedArray.setup @inputs, task.inputs if task
42
+ NamedArray.setup @inputs, task.inputs.collect{|s| s.to_s} if task and task.inputs
39
43
  end
40
44
 
41
45
  def inputs
42
- NamedArray.setup @inputs, task.inputs if task.inputs and not NamedArray === @inputs
46
+ if @inputs.nil? and task and task.respond_to? :inputs
47
+ @inputs = info[:inputs].values_at *task.inputs.collect{|name| name.to_s}
48
+ end
49
+
50
+ if task.inputs and not NamedArray === @inputs
51
+ NamedArray.setup @inputs, task.inputs
52
+ end
53
+
43
54
  @inputs
44
55
  end
45
56
 
@@ -188,6 +199,7 @@ class Step
188
199
  else
189
200
  end
190
201
  end
202
+ self
191
203
  end
192
204
 
193
205
  def step(name)
@@ -100,17 +100,21 @@ class Step
100
100
  end
101
101
 
102
102
  def kill_children
103
- children_pids = info[:children_pids]
104
- if children_pids and children_pids.any?
105
- Log.medium("Killing children: #{ children_pids * ", " }")
106
- children_pids.each do |pid|
107
- Log.medium("Killing child #{ pid }")
108
- begin
109
- Process.kill "INT", pid
110
- rescue Exception
111
- Log.medium("Exception killing child #{ pid }: #{$!.message}")
103
+ begin
104
+ children_pids = info[:children_pids]
105
+ if children_pids and children_pids.any?
106
+ Log.medium("Killing children: #{ children_pids * ", " }")
107
+ children_pids.each do |pid|
108
+ Log.medium("Killing child #{ pid }")
109
+ begin
110
+ Process.kill "INT", pid
111
+ rescue Exception
112
+ Log.medium("Exception killing child #{ pid }: #{$!.message}")
113
+ end
112
114
  end
113
115
  end
116
+ rescue
117
+ Log.medium("Exception finding children")
114
118
  end
115
119
  end
116
120
 
@@ -276,9 +280,10 @@ class Step
276
280
  Misc.pre_fork
277
281
  begin
278
282
  RbbtSemaphore.wait_semaphore(semaphore) if semaphore
279
- FileUtils.mkdir_p File.dirname(path) unless Open.exists? File.dirname(path)
283
+ FileUtils.mkdir_p File.dirname(path) unless File.exists? File.dirname(path)
280
284
  begin
281
285
  res = run true
286
+ set_info :forked, true
282
287
  rescue Aborted
283
288
  Log.debug{"Forked process aborted: #{path}"}
284
289
  log :aborted, "Job aborted (#{Process.pid})"
@@ -306,15 +311,15 @@ class Step
306
311
  end
307
312
  rescue Exception
308
313
  Log.debug("Exception waiting for children: #{$!.message}")
309
- exit -1
314
+ RbbtSemaphore.post_semaphore(semaphore) if semaphore
315
+ Kernel.exit! -1
310
316
  end
311
317
  set_info :pid, nil
312
- exit 0
313
318
  ensure
314
319
  RbbtSemaphore.post_semaphore(semaphore) if semaphore
315
320
  end
321
+ Kernel.exit! 0
316
322
  end
317
- set_info :forked, true
318
323
  Process.detach(@pid)
319
324
  self
320
325
  end
@@ -27,7 +27,10 @@ module Task
27
27
  if deps and deps.any?
28
28
  puts Log.color(:magenta, "Inputs from dependencies:")
29
29
  puts
30
+ seen = []
30
31
  deps.each do |dep|
32
+ next if seen.include? dep.name
33
+ seen << dep.name
31
34
  puts " #{Log.color :yellow, dep.name.to_s}:"
32
35
  puts
33
36
  puts SOPT.input_doc((dep.inputs - self.inputs), dep.input_types, dep.input_descriptions, dep.input_defaults, true)
data/share/Rlib/util.R CHANGED
@@ -77,7 +77,7 @@ rbbt.tsv <- function(filename, sep = "\t", comment.char ="#", row.names=1, check
77
77
  data=read.table(file=filename, sep=sep, fill=fill, as.is=as.is, quote=quote, row.names= row.names, comment.char = comment.char, ...);
78
78
  f = file(filename, 'r');
79
79
  headers = readLines(f, 1);
80
- if (length(grep("^#: ", headers)) > 0){
80
+ if (length(grep("^#:", headers)) > 0){
81
81
  headers = readLines(f, 1);
82
82
  }
83
83
  if (length(grep("^#", headers)) > 0){
@@ -17,12 +17,10 @@ else
17
17
  tsv = TSV.open(file, :grep => value )
18
18
  end
19
19
 
20
-
21
20
  res = tsv[value]
22
21
 
23
22
  if res.nil?
24
23
  raise "RECORD NOT FOUND"
25
24
  else
26
25
  puts tsv[value].report
27
- end
28
26
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ #"!/usr/bin/env ruby
2
2
 
3
3
  require 'rbbt-util'
4
4
  require 'rbbt/util/simpleopt'
@@ -18,6 +18,7 @@ Display summary information. Works with Tokyocabinet HDB and BDB as well.
18
18
  -hh--header_hash* Change the character used to mark the header line (defaults to #)
19
19
  -k--key_field* Change the key field
20
20
  -f--fields* Change the fields to load
21
+ -s--sep* Change the fields separator (default TAB)
21
22
  -h--help Help
22
23
  EOF
23
24
 
@@ -31,6 +32,7 @@ raise ParameterException, "Please specify the tsv file as argument" if file.nil?
31
32
 
32
33
  options[:fields] = options[:fields].split(/,\|/) if options[:fields]
33
34
  options[:header_hash] = options["header_hash"]
35
+ options[:sep] = options["sep"]
34
36
 
35
37
  case
36
38
  when options[:tokyocabinet]
@@ -53,10 +55,16 @@ else
53
55
  puts " - #{Log.color :cyan, i + 1}: " << Log.color(:yellow, f)
54
56
  end
55
57
  end
56
- puts "Rows: #{Log.color :blue, `wc -l '#{ file }'|cut -f 1 -d' '`}" unless Open.remote? file
57
- parts = []
58
- header.first_line.split(header.sep).each_with_index{|p,i| parts << (Log.color(:cyan, "(#{i}) ") << p.strip) }
59
- puts parts * "\t"
58
+
59
+ if String === file and not Open.remote? file and File.exists? file
60
+ rows = `wc -l '#{ file }' 2>/dev/null|cut -f 1 -d' '`
61
+ else
62
+ rows = "Could not get rows of #{Misc.fingerprint file}"
63
+ end
64
+ puts "Rows: #{Log.color :blue, rows}"
65
+ parts = []
66
+ header.first_line.split(header.sep).each_with_index{|p,i| parts << (Log.color(:cyan, "(#{i}) ") << p.strip) }
67
+ puts parts * "\t"
60
68
  puts
61
69
  end
62
70
 
@@ -15,7 +15,7 @@ Subsets entries from a TSV file from a given list. Works with Tokyocabinet HDB a
15
15
  -tch--tokyocabinet File is a TC HDB
16
16
  -tcb--tokyocabinet_bd File is a TC BDB
17
17
  -hh--header_hash* Change the character used to mark the header line (defaults to #)
18
- -s--subset* Subset of samples (Comma-separated of file)
18
+ -s--subset* Subset of keys (Comma-separated or file)
19
19
  -h--help Help
20
20
  EOF
21
21
 
@@ -14,11 +14,13 @@ Examine the info of a job result
14
14
  $ rbbt workflow info <job-result>
15
15
 
16
16
  -h--help Help
17
+ -a--all Print all info entries
17
18
  EOF
18
19
 
19
20
  SOPT.usage if options[:help]
20
21
 
21
22
  file = ARGV.shift
23
+ all = options.delete :all
22
24
 
23
25
  def get_step(file)
24
26
  file = file.sub(/\.(info|files)/,'')
@@ -39,6 +41,19 @@ def status_msg(status)
39
41
  Log.color(color, status)
40
42
  end
41
43
 
44
+ def pid_msg(pid, done = false)
45
+ color = if pid and (done or Misc.pid_exists? pid)
46
+ :green
47
+ else
48
+ :red
49
+ end
50
+ if pid.nil?
51
+ ""
52
+ else
53
+ Log.color(color, pid)
54
+ end
55
+ end
56
+
42
57
  step = get_step file
43
58
 
44
59
  info = step.info
@@ -47,9 +62,14 @@ inputs = info[:inputs]
47
62
  status = info[:status]
48
63
  time = info[:time_elapsed]
49
64
  messages = info[:messages]
65
+ backtrace = info[:backtrace]
66
+ pid = info[:pid]
67
+ exception = info[:exception]
68
+ rest = info.keys - [:inputs, :dependencies, :status, :time_elapsed, :messages, :backtrace, :exception, :pid]
50
69
 
51
70
  puts Log.color(:magenta, "File") << ": " << step.path
52
71
  puts Log.color(:magenta, "Status") << ": " << status_msg(status)
72
+ puts Log.color(:magenta, "Pid") << ": " << pid_msg(pid, status.to_s == "done")
53
73
  puts Log.color(:magenta, "Time") << ": " << time.to_i.to_s << " sec." if time
54
74
  if inputs and inputs.any?
55
75
  puts Log.color(:magenta, "Inputs")
@@ -81,3 +101,15 @@ if messages and messages.any?
81
101
  puts " " << msg
82
102
  end
83
103
  end
104
+
105
+ if status == :error
106
+ puts Log.color(:magenta, "Backtrace") << ": "
107
+ puts Log.color_stack backtrace
108
+ end
109
+
110
+ if all
111
+ puts Log.color(:magenta, "Other entries") << ": "
112
+ rest.each do |key|
113
+ puts Misc.format_definition_list_item(key, info[key].to_s)
114
+ end
115
+ end