rbbt-util 5.2.4 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
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)