rbbt-util 5.2.4 → 5.3.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 (47) hide show
  1. checksums.yaml +8 -8
  2. data/bin/rbbt +23 -10
  3. data/bin/rbbt_monitor.rb +8 -8
  4. data/lib/rbbt/annotations.rb +22 -1
  5. data/lib/rbbt/annotations/util.rb +1 -1
  6. data/lib/rbbt/entity.rb +162 -0
  7. data/lib/rbbt/fix_width_table.rb +7 -0
  8. data/lib/rbbt/persist.rb +16 -9
  9. data/lib/rbbt/persist/tsv.rb +14 -8
  10. data/lib/rbbt/resource.rb +1 -6
  11. data/lib/rbbt/resource/path.rb +23 -27
  12. data/lib/rbbt/tsv.rb +33 -4
  13. data/lib/rbbt/tsv/accessor.rb +100 -57
  14. data/lib/rbbt/tsv/attach.rb +3 -1
  15. data/lib/rbbt/tsv/attach/util.rb +34 -10
  16. data/lib/rbbt/tsv/index.rb +12 -3
  17. data/lib/rbbt/tsv/manipulate.rb +25 -1
  18. data/lib/rbbt/tsv/parser.rb +1 -0
  19. data/lib/rbbt/util/R.rb +36 -6
  20. data/lib/rbbt/util/cmd.rb +2 -1
  21. data/lib/rbbt/util/color.rb +250 -0
  22. data/lib/rbbt/util/colorize.rb +57 -0
  23. data/lib/rbbt/util/misc.rb +57 -19
  24. data/lib/rbbt/util/named_array.rb +66 -14
  25. data/lib/rbbt/util/open.rb +134 -10
  26. data/lib/rbbt/util/semaphore.rb +71 -0
  27. data/lib/rbbt/workflow.rb +34 -7
  28. data/lib/rbbt/workflow/accessor.rb +12 -8
  29. data/lib/rbbt/workflow/step.rb +44 -28
  30. data/lib/rbbt/workflow/usage.rb +3 -0
  31. data/share/lib/R/util.R +31 -0
  32. data/share/rbbt_commands/app/start +5 -4
  33. data/share/rbbt_commands/study/task +222 -0
  34. data/share/rbbt_commands/tsv/attach +13 -0
  35. data/share/rbbt_commands/tsv/change_id +15 -0
  36. data/share/rbbt_commands/tsv/info +3 -1
  37. data/share/rbbt_commands/workflow/task +14 -15
  38. data/test/rbbt/test_entity.rb +221 -0
  39. data/test/rbbt/test_tsv.rb +2 -1
  40. data/test/rbbt/test_workflow.rb +0 -2
  41. data/test/rbbt/tsv/test_accessor.rb +2 -2
  42. data/test/rbbt/util/test_R.rb +9 -2
  43. data/test/rbbt/util/test_colorize.rb +12 -0
  44. data/test/rbbt/util/test_misc.rb +0 -5
  45. data/test/rbbt/util/test_open.rb +31 -0
  46. data/test/rbbt/workflow/test_step.rb +32 -0
  47. metadata +13 -2
data/lib/rbbt/resource.rb CHANGED
@@ -4,11 +4,7 @@ require 'rbbt/util/chain_methods'
4
4
  require 'rbbt/resource/path'
5
5
 
6
6
  module Resource
7
- extend ChainMethods
8
- self.chain_prefix = :resource
9
-
10
7
  def self.extended(base)
11
- setup_chains(base)
12
8
  if not base.respond_to? :pkgdir
13
9
  class << base
14
10
  attr_accessor :pkgdir, :subdir, :resources, :rake_dirs
@@ -26,8 +22,7 @@ module Resource
26
22
  Path.setup @subdir || "", @pkgdir, self
27
23
  end
28
24
 
29
- def resource_method_missing(name, prev = nil, *args)
30
- # Fix problem with ruby 1.9 calling methods by its own initiative. ARG
25
+ def method_missing(name, prev = nil, *args)
31
26
  if prev.nil?
32
27
  root.send(name, *args)
33
28
  else
@@ -1,29 +1,15 @@
1
- require 'rbbt/util/chain_methods'
2
1
  require 'rbbt/resource/util'
3
2
  require 'rbbt/tsv'
4
3
 
5
4
  module Path
6
5
  attr_accessor :resource, :pkgdir
7
6
 
8
- extend ChainMethods
9
- self.chain_prefix = :path
10
-
11
7
  def self.setup(string, pkgdir = nil, resource = nil)
12
8
  string.extend Path
13
9
  string.pkgdir = pkgdir || 'rbbt'
14
10
  string.resource = resource
15
11
  string
16
12
  end
17
-
18
- def self.extended(string)
19
- setup_chains(string)
20
- if not string.respond_to? :byte
21
- class << string
22
- alias byte path_clean_get_brackets
23
- end
24
- end
25
- end
26
-
27
13
  def join(name)
28
14
  if self.empty?
29
15
  Path.setup name.to_s, @pkgdir, @resource
@@ -37,24 +23,29 @@ module Path
37
23
  end
38
24
 
39
25
  def glob(pattern = '*')
40
- Dir.glob(File.join(self, pattern)).collect{|f| Path.setup(f, self.resource, self.pkgdir)}
26
+ Dir.glob(File.join(Regexp.quote(self), pattern)).collect{|f| Path.setup(f, self.resource, self.pkgdir)}
41
27
  end
42
28
 
43
- def path_get_brackets(name)
29
+ def [](name, orig = false)
30
+ return super(name) if orig
44
31
  join name
45
32
  end
46
33
 
47
- def path_method_missing(name, prev = nil, *args, &block)
34
+ def byte(pos)
35
+ send(:[], pos, true)
36
+ end
37
+
38
+ def method_missing(name, prev = nil, *args, &block)
48
39
  if block_given?
49
- path_clean_method_missing name, prev, *args, &block
40
+ super name, prev, *args, &block
50
41
  else
51
42
  # Fix problem with ruby 1.9 calling methods by its own initiative. ARG
52
- path_clean_method_missing(name, prev, *args) if name.to_s =~ /^to_/
53
- if prev.nil?
54
- join name
55
- else
56
- join(prev).join(name)
57
- end
43
+ super(name, prev, *args) if name.to_s =~ /^to_/
44
+ if prev.nil?
45
+ join name
46
+ else
47
+ join(prev).join(name)
48
+ end
58
49
  end
59
50
  end
60
51
 
@@ -135,7 +126,8 @@ module Path
135
126
 
136
127
  def produce(force = false)
137
128
  path = self.find
138
- return self if File.exists?(path) and not force
129
+
130
+ return self if Open.exists?(path.to_s) and not force
139
131
 
140
132
  raise "No resource defined to produce file: #{ self }" if resource.nil?
141
133
 
@@ -153,8 +145,8 @@ module Path
153
145
  end
154
146
 
155
147
 
156
- def open(options = {})
157
- Open.open(self.produce.find, options)
148
+ def open(options = {}, &block)
149
+ Open.open(self.produce.find, options, &block)
158
150
  end
159
151
 
160
152
  def to_s
@@ -181,6 +173,10 @@ module Path
181
173
  YAML.load self.open
182
174
  end
183
175
 
176
+ def pipe_to(cmd, options = {})
177
+ CMD.cmd(cmd, {:in => self.open, :pipe => true}.merge(options))
178
+ end
179
+
184
180
  def index(options = {})
185
181
  TSV.index(self.produce, options)
186
182
  end
data/lib/rbbt/tsv.rb CHANGED
@@ -16,6 +16,14 @@ require 'rbbt/tsv/attach'
16
16
  require 'rbbt/tsv/filter'
17
17
 
18
18
  module TSV
19
+ class << self
20
+ attr_accessor :lock_dir
21
+
22
+ def lock_dir
23
+ @lock_dir ||= Rbbt.tmp.tsv_open_locks.find
24
+ end
25
+ end
26
+
19
27
  def self.setup(hash, options = {})
20
28
  options = Misc.add_defaults options, :default_value => []
21
29
  default_value = Misc.process_options options, :default_value
@@ -48,7 +56,7 @@ module TSV
48
56
 
49
57
  data = nil
50
58
 
51
- lock_filename = filename.nil? ? nil : Persist.persistence_path(filename, {:dir => Rbbt.tmp.tsv_open_locks.find})
59
+ lock_filename = filename.nil? ? nil : Persist.persistence_path(filename, {:dir => TSV.lock_dir})
52
60
  Misc.lock lock_filename do
53
61
  data = Persist.persist_tsv source, filename, options, persist_options do |data|
54
62
  if serializer
@@ -63,7 +71,7 @@ module TSV
63
71
 
64
72
  data.filename = filename.to_s unless filename.nil?
65
73
  if data.identifiers.nil? and Path === filename and filename.identifier_file_path
66
- data.identifiers = filename.identifier_file_path.to_s
74
+ data.identifiers = filename.identifier_file_path.to_s
67
75
  end
68
76
 
69
77
  data
@@ -74,11 +82,26 @@ module TSV
74
82
 
75
83
  data.entity_options = entity_options
76
84
 
85
+ if Path === source and data.identifiers
86
+ data.identifiers = Path.setup(data.identifiers, source.pkgdir, source.resource)
87
+ end
88
+
77
89
  data
78
90
  end
79
91
 
80
92
  def self.parse_header(stream, options = {})
81
- Parser.new stream, options
93
+ case
94
+ when Path === stream
95
+ stream.open do |f|
96
+ Parser.new f, options
97
+ end
98
+ when (String === stream and stream.length < 300 and Open.exists? stream)
99
+ stream.open do |f|
100
+ Parser.new f, options
101
+ end
102
+ else
103
+ Parser.new stream, options
104
+ end
82
105
  end
83
106
 
84
107
  def self.parse(stream, data, options = {})
@@ -96,11 +119,17 @@ module TSV
96
119
 
97
120
  if TokyoCabinet::HDB === data and parser.straight
98
121
  data.close
122
+ pos = stream.pos if stream.respond_to? :pos
99
123
  begin
100
- CMD.cmd("tchmgr importtsv '#{data.persistence_path}'", :in => stream, :log => false)
124
+ CMD.cmd("tchmgr importtsv '#{data.persistence_path}'", :in => stream, :log => false, :dont_close_in => true)
101
125
  rescue
102
126
  Log.debug("tchmgr importtsv failed for: #{data.persistence_path}")
103
127
  Log.debug($!.message)
128
+ if stream.respond_to? :seek
129
+ stream.seek pos
130
+ else
131
+ #raise "tchmgr import failed and cannot restore stream"
132
+ end
104
133
  end
105
134
  data.write
106
135
  end
@@ -1,13 +1,18 @@
1
- require 'rbbt/util/chain_methods'
1
+ #require 'rbbt/util/chain_methods'
2
2
  require 'yaml'
3
3
  module TSV
4
- extend ChainMethods
5
- self.chain_prefix = :tsv
4
+ #extend ChainMethods
5
+ #self.chain_prefix = :tsv
6
6
 
7
- NIL_YAML = "--- \n"
7
+ TSV_SERIALIZER = YAML
8
+ SERIALIZED_NIL = TSV_SERIALIZER.dump nil
8
9
 
9
10
  attr_accessor :unnamed, :serializer_module, :entity_options, :entity_templates
10
11
 
12
+ def annotate(tsv)
13
+ TSV.setup(tsv, :key_field => key_field, :fields => fields, :namespace => namespace, :entity_options => entity_options, :type => type, :filename => filename, :identifiers => identifiers)
14
+ end
15
+
11
16
  def entity_options
12
17
  if @entity_options.nil?
13
18
  @entity_options = namespace ? {:namespace => namespace, :organism => namespace} : {}
@@ -16,6 +21,12 @@ module TSV
16
21
  @entity_options
17
22
  end
18
23
 
24
+ def entity_options=(options)
25
+ @entity_options = options
26
+ @entity_templates = nil
27
+ end
28
+
29
+
19
30
  def entity_templates
20
31
  @entity_templates ||= {}
21
32
  end
@@ -70,7 +81,7 @@ module TSV
70
81
  end
71
82
 
72
83
  def self.extended(data)
73
- setup_chains(data)
84
+ #setup_chains(data)
74
85
 
75
86
  if not data.respond_to? :write
76
87
  class << data
@@ -96,10 +107,10 @@ module TSV
96
107
  end
97
108
 
98
109
  if not data.respond_to? :serialized_get
99
- class << data
100
- alias serialized_get tsv_clean_get_brackets
101
- alias serialized_set tsv_clean_set_brackets
102
- end
110
+ #class << data
111
+ # alias serialized_get []
112
+ # alias serialized_set []=
113
+ #end
103
114
  end
104
115
  end
105
116
 
@@ -109,13 +120,13 @@ module TSV
109
120
  ENTRY_KEYS = []
110
121
 
111
122
  #{{{ Chained Methods
112
- def tsv_empty?
123
+ def empty?
113
124
  length == 0
114
125
  end
115
126
 
116
- def tsv_get_brackets(key)
117
- value = serialized_get(key)
118
- return value if value.nil? or @unnamed or fields.nil?
127
+ def [](key, clean = false)
128
+ value = (self.respond_to?(:serialized_get) and not clean) ? serialized_get(key) : super(key)
129
+ return value if value.nil? or @unnamed or clean == :entry_key or fields.nil?
119
130
 
120
131
  case type
121
132
  when :double, :list
@@ -128,41 +139,44 @@ module TSV
128
139
  value
129
140
  end
130
141
 
131
- def tsv_set_brackets(key,value)
142
+ def []=(key, value, clean = false)
143
+ return super(key, value) if clean or not self.respond_to?(:serialized_set)
132
144
  serialized_set(key, value)
133
145
  end
134
146
 
135
- def tsv_keys
136
- keys = tsv_clean_keys - ENTRY_KEYS
147
+ def keys
148
+ keys = super - ENTRY_KEYS
137
149
  return keys if @unnamed or key_field.nil?
138
150
 
139
151
  prepare_entity(keys, key_field, entity_options.merge(:dup_array => true))
140
152
  end
141
153
 
142
- def tsv_values
154
+ def values
143
155
  values = chunked_values_at(keys)
144
156
  return values if @unnamed or fields.nil?
145
157
 
146
158
  case type
147
159
  when :double, :list
148
160
  values.each{|value| setup_array value, fields, nil, entity_options}
149
- when :flat, :single
161
+ when :single
162
+ values = prepare_entity(values, fields.first, entity_options)
163
+ when :flat
150
164
  values = values.collect{|v| prepare_entity(v, fields.first, entity_options)}
151
165
  end
152
166
 
153
167
  values
154
168
  end
155
169
 
156
- def tsv_each
170
+ def each
157
171
  fields = self.fields
158
172
 
159
173
  serializer = self.serializer
160
174
  serializer_module = SERIALIZER_ALIAS[serializer] unless serializer.nil?
161
- tsv_clean_each do |key, value|
175
+ super do |key, value|
162
176
  next if ENTRY_KEYS.include? key
163
177
 
164
178
  # TODO Update this to be more efficient
165
- value = serializer_module.load(value) unless serializer.nil?
179
+ value = serializer_module.load(value) unless serializer.nil? or FalseClass === serializer
166
180
 
167
181
  # Annotated with Entity and NamedArray
168
182
  if not @unnamed
@@ -182,10 +196,10 @@ module TSV
182
196
  end
183
197
  end
184
198
 
185
- def tsv_collect
199
+ def collect
186
200
  serializer = self.serializer
187
201
  serializer_module = SERIALIZER_ALIAS[serializer] unless serializer.nil?
188
- tsv_clean_collect do |key, value|
202
+ super do |key, value|
189
203
  next if ENTRY_KEYS.include? key
190
204
 
191
205
  # TODO Update this to be more efficient
@@ -212,15 +226,15 @@ module TSV
212
226
  end
213
227
  end
214
228
 
215
- def tsv_size
229
+ def size
216
230
  keys.length
217
231
  end
218
232
 
219
- def tsv_length
233
+ def length
220
234
  keys.length
221
235
  end
222
236
 
223
- def tsv_values_at(*keys)
237
+ def values_at(*keys)
224
238
  keys.collect do |key|
225
239
  self[key]
226
240
  end
@@ -236,7 +250,7 @@ module TSV
236
250
 
237
251
  #{{{ Sorting
238
252
 
239
- def tsv_sort_by(field = nil, just_keys = false, &block)
253
+ def sort_by(field = nil, just_keys = false, &block)
240
254
  field = :all if field.nil?
241
255
  if field == :all
242
256
  elems = collect
@@ -249,7 +263,7 @@ module TSV
249
263
  end
250
264
  when :list, :flat
251
265
  through :key, field do |key, fields|
252
- elems << [key, fields.first]
266
+ elems << [key, fields]
253
267
  end
254
268
  when :double
255
269
  through :key, field do |key, fields|
@@ -267,12 +281,33 @@ module TSV
267
281
  elems.sort_by{|key, value| key }
268
282
  end
269
283
  else
284
+ sorted = elems.sort do |a, b|
285
+ a_value = a.last
286
+ b_value = b.last
287
+ case
288
+ when ((a_value.nil? or (a_value.respond_to?(:empty?) and a_value.empty?)) and (b_value.nil? or (b_value.respond_to?(:empty?) and b_value.empty?)))
289
+ 0
290
+ when (a_value.nil? or (a_value.respond_to?(:empty?) and a_value.empty?))
291
+ -1
292
+ when (b_value.nil? or (b_value.respond_to?(:empty?) and b_value.empty?))
293
+ 1
294
+ when Array === a_value
295
+ if a_value.length == 1 and b_value.length == 1
296
+ a_value.first <=> b_value.first
297
+ else
298
+ a_value.length <=> b_value.length
299
+ end
300
+ else
301
+ a_value <=> b_value
302
+ end
303
+ end
270
304
  if just_keys
271
- keys = elems.sort_by{|key, value| value }.collect{|key, value| key}
305
+ #keys = elems.sort_by{|key, value| value }.collect{|key, value| key}
306
+ keys = sorted.collect{|key, value| key}
272
307
  keys = prepare_entity(keys, key_field, entity_options.merge(:dup_array => true))
273
308
  keys
274
309
  else
275
- elems.sort_by{|key, value| value }.collect{|key, value| [key, self[key]]}
310
+ sorted.collect{|key, value| [key, self[key]]}
276
311
  end
277
312
  end
278
313
  else
@@ -310,12 +345,13 @@ module TSV
310
345
  entries.each do |entry|
311
346
  key = KEY_PREFIX + entry
312
347
  ENTRY_KEYS << key
313
- self.module_eval "
348
+ line = __LINE__; self.module_eval "
314
349
  attr_accessor :#{entry}
315
350
 
316
351
  def #{ entry }
317
352
  if not defined? @#{entry}
318
- @#{entry} = (value = self.tsv_clean_get_brackets('#{key}')).nil? ? nil : YAML.load(value)
353
+ # @#{entry} = (value = self.clean_get_brackets('#{key}')).nil? ? nil : TSV_SERIALIZER.load(value)
354
+ @#{entry} = (value = self.send(:[], '#{key}', :entry_key)).nil? ? nil : TSV_SERIALIZER.load(value)
319
355
  end
320
356
  @#{entry}
321
357
  end
@@ -325,33 +361,36 @@ if '#{entry}' == 'serializer'
325
361
 
326
362
  def #{ entry }=(value)
327
363
  @#{entry} = value
328
- self.tsv_clean_set_brackets '#{key}', value.nil? ? NIL_YAML : value.to_yaml
364
+ #self.tsv_clean_set_brackets '#{key}', value.nil? ? SERIALIZED_NIL : value.to_yaml
365
+ self.send(:[]=, '#{key}', value.nil? ? SERIALIZED_NIL : value.to_yaml, true)
329
366
 
330
367
  return if value.nil?
331
368
 
332
369
  self.serializer_module = SERIALIZER_ALIAS[value.to_sym]
333
370
 
334
371
  if serializer_module.nil?
335
- class << self
336
- alias serialized_get tsv_clean_get_brackets
337
- alias serialized_set tsv_clean_set_brackets
338
- end
372
+ #class << self
373
+ # alias serialized_get tsv_clean_get_brackets
374
+ # alias serialized_set tsv_clean_set_brackets
375
+ #end
339
376
 
340
377
  else
341
378
  class << self
342
379
 
343
380
  define_method :serialized_get do |key|
344
381
  return nil unless self.include? key
345
- res = tsv_clean_get_brackets(key)
382
+ res = self.send(:[], key, true)
346
383
  return res if res.nil?
347
384
  self.serializer_module.load(res)
348
385
  end
349
386
 
350
387
  define_method :serialized_set do |key, value|
351
388
  if value.nil?
352
- tsv_clean_set_brackets key, value
389
+ self.send(:[]=, key, value, true)
390
+ #tsv_clean_set_brackets key, value
353
391
  else
354
- tsv_clean_set_brackets key, self.serializer_module.dump(value)
392
+ self.send(:[]=, key, self.serializer_module.dump(value), true)
393
+ #tsv_clean_set_brackets key, self.serializer_module.dump(value)
355
394
  end
356
395
  end
357
396
  end
@@ -361,10 +400,11 @@ if '#{entry}' == 'serializer'
361
400
  else
362
401
  def #{ entry }=(value)
363
402
  @#{entry} = value
364
- self.tsv_clean_set_brackets '#{key}', value.nil? ? NIL_YAML : value.to_yaml
403
+ self.send(:[]=, '#{key}', value.nil? ? SERIALIZED_NIL : value.to_yaml, true)
404
+ #self.tsv_clean_set_brackets '#{key}', value.nil? ? SERIALIZED_NIL : value.to_yaml
365
405
  end
366
406
  end
367
- "
407
+ ", __FILE__, line
368
408
  end
369
409
  end
370
410
 
@@ -378,8 +418,9 @@ end
378
418
  :serializer
379
419
 
380
420
  def fields
381
- @fields ||= YAML.load(self.tsv_clean_get_brackets("__tsv_hash_fields") || "--- \n")
382
- if @fields.nil? or @unnamed
421
+ #@fields ||= TSV_SERIALIZER.load(self.tsv_clean_get_brackets("__tsv_hash_fields") || SERIALIZED_NIL)
422
+ @fields ||= TSV_SERIALIZER.load(self.send(:[], "__tsv_hash_fields", :entry_key) || SERIALIZED_NIL)
423
+ if true or @fields.nil? or @unnamed
383
424
  @fields
384
425
  else
385
426
  @named_fields ||= NamedArray.setup @fields, @fields, nil, entity_options, entity_templates
@@ -387,13 +428,15 @@ end
387
428
  end
388
429
 
389
430
  def namespace=(value)
390
- self.tsv_clean_set_brackets "__tsv_hash_namespace", value.nil? ? NIL_YAML : value.to_yaml
431
+ #self.tsv_clean_set_brackets "__tsv_hash_namespace", value.nil? ? SERIALIZED_NIL : value.to_yaml
432
+ self.send(:[]=, "__tsv_hash_namespace", value.nil? ? SERIALIZED_NIL : value.to_yaml, true)
391
433
  @namespace = value
392
434
  @entity_options = nil
393
435
  end
394
436
 
395
437
  def fields=(value)
396
- self.tsv_clean_set_brackets "__tsv_hash_fields", value.nil? ? NIL_YAML : value.to_yaml
438
+ #self.tsv_clean_set_brackets "__tsv_hash_fields", value.nil? ? SERIALIZED_NIL : value.to_yaml
439
+ self.send(:[]=, "__tsv_hash_fields", value.nil? ? SERIALIZED_NIL : value.to_yaml, true)
397
440
  @fields = value
398
441
  @named_fields = nil
399
442
  end
@@ -422,7 +465,7 @@ end
422
465
  when Path === filename
423
466
  filename.identifier_files
424
467
  when filename
425
- Path.setup(filename).identifier_files
468
+ Path.setup(filename.dup).identifier_files
426
469
  else
427
470
  []
428
471
  end
@@ -442,16 +485,16 @@ end
442
485
  end
443
486
 
444
487
  def values_to_s(values)
445
- case
446
- when (values.nil? and fields.nil?)
447
- "\n"
448
- when (values.nil? and not fields.nil?)
449
- "\t" << ([""] * fields.length) * "\t" << "\n"
450
- when (not Array === values)
451
- "\t" << values.to_s << "\n"
452
- else
453
- "\t" << values.collect{|v| Array === v ? v * "|" : v} * "\t" << "\n"
454
- end
488
+ case
489
+ when (values.nil? and fields.nil?)
490
+ "\n"
491
+ when (values.nil? and not fields.nil?)
492
+ "\t" << ([""] * fields.length) * "\t" << "\n"
493
+ when (not Array === values)
494
+ "\t" << values.to_s << "\n"
495
+ else
496
+ "\t" << values.collect{|v| Array === v ? v * "|" : v} * "\t" << "\n"
497
+ end
455
498
  end
456
499
 
457
500
  def to_s(keys = nil, no_options = false)