rbbt-util 5.13.37 → 5.14.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.
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