tb 0.9 → 1.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/README +13 -11
  3. data/lib/tb.rb +14 -6
  4. data/lib/tb/catreader.rb +2 -2
  5. data/lib/tb/cmd_consecutive.rb +6 -2
  6. data/lib/tb/cmd_crop.rb +22 -3
  7. data/lib/tb/cmd_cross.rb +24 -0
  8. data/lib/tb/cmd_cut.rb +20 -10
  9. data/lib/tb/cmd_git.rb +20 -7
  10. data/lib/tb/cmd_group.rb +32 -0
  11. data/lib/tb/cmd_gsub.rb +21 -0
  12. data/lib/tb/cmd_join.rb +28 -0
  13. data/lib/tb/cmd_ls.rb +9 -0
  14. data/lib/tb/cmd_melt.rb +15 -0
  15. data/lib/tb/cmd_mheader.rb +15 -0
  16. data/lib/tb/cmd_nest.rb +27 -6
  17. data/lib/tb/cmd_newfield.rb +19 -2
  18. data/lib/tb/cmd_rename.rb +20 -0
  19. data/lib/tb/{cmd_grep.rb → cmd_search.rb} +37 -23
  20. data/lib/tb/cmd_shape.rb +69 -25
  21. data/lib/tb/cmd_sort.rb +20 -0
  22. data/lib/tb/cmd_tar.rb +38 -0
  23. data/lib/tb/cmd_to_json.rb +2 -2
  24. data/lib/tb/cmd_to_ltsv.rb +3 -3
  25. data/lib/tb/cmd_to_pnm.rb +3 -3
  26. data/lib/tb/cmd_to_tsv.rb +3 -3
  27. data/lib/tb/cmd_to_yaml.rb +3 -3
  28. data/lib/tb/cmd_unmelt.rb +15 -0
  29. data/lib/tb/cmd_unnest.rb +31 -7
  30. data/lib/tb/cmdmain.rb +2 -0
  31. data/lib/tb/cmdtop.rb +1 -1
  32. data/lib/tb/cmdutil.rb +9 -62
  33. data/lib/tb/csv.rb +21 -79
  34. data/lib/tb/enumerable.rb +42 -68
  35. data/lib/tb/enumerator.rb +15 -7
  36. data/lib/tb/{fieldset.rb → hashreader.rb} +37 -56
  37. data/lib/tb/hashwriter.rb +54 -0
  38. data/lib/tb/headerreader.rb +108 -0
  39. data/lib/tb/headerwriter.rb +116 -0
  40. data/lib/tb/json.rb +17 -15
  41. data/lib/tb/ltsv.rb +35 -96
  42. data/lib/tb/ndjson.rb +63 -0
  43. data/lib/tb/numericreader.rb +66 -0
  44. data/lib/tb/numericwriter.rb +61 -0
  45. data/lib/tb/pnm.rb +206 -200
  46. data/lib/tb/ropen.rb +54 -59
  47. data/lib/tb/tsv.rb +39 -71
  48. data/sample/excel2csv +24 -25
  49. data/sample/poi-xls2csv.rb +13 -14
  50. data/tb.gemspec +154 -0
  51. data/test/test_cmd_cat.rb +28 -6
  52. data/test/test_cmd_consecutive.rb +8 -3
  53. data/test/test_cmd_cut.rb +14 -4
  54. data/test/test_cmd_git_log.rb +50 -50
  55. data/test/test_cmd_grep.rb +6 -6
  56. data/test/test_cmd_gsub.rb +7 -2
  57. data/test/test_cmd_ls.rb +70 -62
  58. data/test/test_cmd_shape.rb +43 -6
  59. data/test/test_cmd_svn_log.rb +26 -27
  60. data/test/test_cmd_to_csv.rb +10 -5
  61. data/test/test_cmd_to_json.rb +16 -0
  62. data/test/test_cmd_to_ltsv.rb +2 -2
  63. data/test/test_cmd_to_pp.rb +7 -2
  64. data/test/test_csv.rb +74 -62
  65. data/test/test_ex_enumerable.rb +0 -1
  66. data/test/test_fileenumerator.rb +3 -3
  67. data/test/test_headercsv.rb +43 -0
  68. data/test/test_json.rb +2 -2
  69. data/test/test_ltsv.rb +22 -17
  70. data/test/test_ndjson.rb +62 -0
  71. data/test/test_numericcsv.rb +36 -0
  72. data/test/test_pnm.rb +69 -70
  73. data/test/test_reader.rb +27 -124
  74. data/test/test_tbenum.rb +18 -18
  75. data/test/test_tsv.rb +21 -32
  76. data/test/util_tbtest.rb +12 -0
  77. metadata +41 -19
  78. data/lib/tb/basic.rb +0 -1070
  79. data/lib/tb/reader.rb +0 -106
  80. data/lib/tb/record.rb +0 -158
  81. data/test/test_basic.rb +0 -403
  82. data/test/test_fieldset.rb +0 -42
  83. data/test/test_record.rb +0 -61
@@ -1,1070 +0,0 @@
1
- # lib/tb/basic.rb - basic fetures for table library
2
- #
3
- # Copyright (C) 2010-2012 Tanaka Akira <akr@fsij.org>
4
- #
5
- # Redistribution and use in source and binary forms, with or without
6
- # modification, are permitted provided that the following conditions
7
- # are met:
8
- #
9
- # 1. Redistributions of source code must retain the above copyright
10
- # notice, this list of conditions and the following disclaimer.
11
- # 2. Redistributions in binary form must reproduce the above
12
- # copyright notice, this list of conditions and the following
13
- # disclaimer in the documentation and/or other materials provided
14
- # with the distribution.
15
- # 3. The name of the author may not be used to endorse or promote
16
- # products derived from this software without specific prior
17
- # written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
20
- # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
- # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23
- # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
- # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25
- # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27
- # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
28
- # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
29
- # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
-
31
- # Tb represents a set of records.
32
- # A record contains field values accessed by field names.
33
- #
34
- # A table can be visualized as follows.
35
- #
36
- # _recordid f1 f2 f3
37
- # 0 v01 v02 v03
38
- # 1 v11 v12 v13
39
- # 2 v21 v22 v23
40
- #
41
- # This table has 4 fields and 3 records:
42
- # - fields: _recordid, f1, f2 and f3.
43
- # - records: [0, v01, v02, v03], [1, v11, v12, v13] and [2, v21, v22, v23]
44
- #
45
- # The fields are strings.
46
- # The field names starts with "_" is reserved.
47
- # "_recordid" is a reserved field always defined to identify a record.
48
- #
49
- # Non-reserved fields can be defined by Tb.new and Tb#define_field.
50
- # It is an error to access a field which is not defined.
51
- #
52
- # A value in a record is identified by a recordid and field name.
53
- # A value for non-reserved fields can be any Ruby values.
54
- # A value for _recordid is an non-negative integer and it is automatically allocated when a new record is inserted.
55
- # It is an error to access a record by recordid which is not allocated.
56
- #
57
- class Tb
58
- include Tb::Enumerable
59
-
60
- # :call-seq:
61
- # Tb.new
62
- # Tb.new(fields, values1, values2, ...)
63
- #
64
- # creates an instance of Tb class.
65
- #
66
- # If the first argument, _fields_, is given, it should be an array of strings.
67
- # The strings are used as field names to define fields.
68
- #
69
- # The field names begins with underscore, "_", are reserved.
70
- # Currently, "_recordid" field is defined automatically.
71
- #
72
- # If the second argument and subsequent arguments, valuesN, are given, they should be an array.
73
- # The arrays are used as records to define records.
74
- # A value in the array is used for a value of corresponding field defined by the first argument.
75
- #
76
- # t = Tb.new %w[fruit color],
77
- # %w[apple red],
78
- # %w[banana yellow],
79
- # %w[orange orange]
80
- # pp t
81
- # #=> #<Tb
82
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
83
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}>
84
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
85
- #
86
- def initialize(*args)
87
- @next_recordid = 0
88
- @recordid2index = {}
89
- @free_index = []
90
- @tbl = {"_recordid"=>[]}
91
- @field_list = ["_recordid".freeze]
92
- if !args.empty?
93
- args.first.each {|f|
94
- define_field(f)
95
- }
96
- insert_values(*args)
97
- end
98
- end
99
-
100
- # :call-seq:
101
- # table.replace(table2)
102
- #
103
- # replaces the contents of _table_ same as _table2_.
104
- def replace(tbl2)
105
- raise TypeError, "a Tb expected but #{tbl2.inspect}" unless Tb === tbl2
106
- @next_recordid = tbl2.instance_variable_get(:@next_recordid)
107
- @recordid2index = tbl2.instance_variable_get(:@recordid2index).dup
108
- @free_index = tbl2.instance_variable_get(:@free_index).dup
109
- @tbl = Hash[tbl2.instance_variable_get(:@tbl).map {|k, v| [k, v.dup] }]
110
- @field_list = tbl2.instance_variable_get(:@field_list).dup
111
- end
112
-
113
- def pretty_print(q) # :nodoc:
114
- q.object_group(self) {
115
- each_recordid {|recordid|
116
- q.breakable
117
- fs = @field_list.reject {|f| get_cell(recordid, f).nil? }
118
- q.group(1, '{', '}') {
119
- q.seplist(fs, nil, :each) {|f|
120
- v = get_cell(recordid, f)
121
- q.group {
122
- q.pp f
123
- q.text '=>'
124
- q.group(1) {
125
- q.breakable ''
126
- q.pp v
127
- }
128
- }
129
- }
130
- }
131
- }
132
- }
133
- end
134
- alias inspect pretty_print_inspect # :nodoc:
135
-
136
- def check_recordid_type(recordid)
137
- raise TypeError, "invalid recordid: #{recordid.inspect}" if recordid.kind_of?(Symbol) # Ruby 1.8 has Symbol#to_int.
138
- raise TypeError, "invalid recordid: #{recordid.inspect}" unless recordid.respond_to? :to_int
139
- recordid = recordid.to_int
140
- raise TypeError, "invalid recordid: #{recordid.inspect}" if !recordid.kind_of?(Integer)
141
- recordid
142
- end
143
- private :check_recordid_type
144
-
145
- def check_recordid(recordid)
146
- recordid = check_recordid_type(recordid)
147
- if !@recordid2index.has_key?(recordid)
148
- raise IndexError, "unexpected recordid: #{recordid.inspect}"
149
- end
150
- recordid
151
- end
152
- private :check_recordid
153
-
154
- def check_field_type(field)
155
- raise TypeError, "invalid field name: #{field.inspect}" if field.nil?
156
- field = field.to_s
157
- raise TypeError, "invalid field name: #{field.inspect}" if !field.kind_of?(String)
158
- field
159
- end
160
- private :check_field_type
161
-
162
- def check_field(field)
163
- field = check_field_type(field)
164
- unless @tbl.has_key? field
165
- raise ArgumentError, "field not defined: #{field.inspect}"
166
- end
167
- field
168
- end
169
- private :check_field
170
-
171
- # :call-seq:
172
- # table.define_field(field)
173
- # table.define_field(field) {|record| value_for_the_field }
174
- #
175
- # defines a new field.
176
- #
177
- # If no block is given, the initial value for the field is nil.
178
- #
179
- # If a block is given, the block is called for each record.
180
- # The return value of the block is used for the initial value of the field.
181
- #
182
- # t = Tb.new %w[fruit color],
183
- # %w[apple red],
184
- # %w[banana yellow],
185
- # %w[orange orange]
186
- # pp t
187
- # #=> #<Tb
188
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
189
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
190
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
191
- # t.define_field("namelen") {|record| record["fruit"].length }
192
- # pp t
193
- # #=> #<Tb
194
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red", "namelen"=>5}
195
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow", "namelen"=>6}
196
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange", "namelen"=>6}>
197
- #
198
- def define_field(field)
199
- field = check_field_type(field).dup.freeze
200
- if field.start_with?("_")
201
- raise ArgumentError, "field begins with underscore: #{field.inspect}"
202
- end
203
- if @tbl.has_key? field
204
- raise ArgumentError, "field already defined: #{field.inspect}"
205
- end
206
- @tbl[field] = []
207
- @field_list << field
208
- if block_given?
209
- each_record {|record|
210
- v = yield(record)
211
- if !v.nil?
212
- record[field] = v
213
- end
214
- }
215
- end
216
- end
217
-
218
- # :call-seq:
219
- # table.has_field?(field) -> true or false
220
- #
221
- # returns true if the field specified by the argument is exist.
222
- #
223
- # t = Tb.new %w[fruit color],
224
- # %w[apple red],
225
- # %w[banana yellow],
226
- # %w[orange orange]
227
- # pp t
228
- # #=> #<Tb
229
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
230
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
231
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
232
- # p t.has_field?("fruit") #=> true
233
- # p t.has_field?("foo") #=> false
234
- #
235
- def has_field?(field)
236
- field = check_field_type(field)
237
- @tbl.has_key?(field)
238
- end
239
-
240
- # :call-seq:
241
- # table.list_fields -> [field1, field2, ...]
242
- #
243
- # returns the list of non-reserved field names as an array of strings.
244
- #
245
- # t = Tb.new %w[fruit color],
246
- # %w[apple red],
247
- # %w[banana yellow],
248
- # %w[orange orange]
249
- # pp t
250
- # #=> #<Tb
251
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
252
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
253
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
254
- # p t.list_fields #=> ["fruit", "color"]
255
- #
256
- def list_fields
257
- @field_list.reject {|f| f.start_with?("_") }
258
- end
259
-
260
- # :call-seq:
261
- # table.list_fields_all -> [field1, field2, ...]
262
- #
263
- # returns the list of reserved and non-reserved field names as an array of strings.
264
- #
265
- # t = Tb.new %w[fruit color],
266
- # %w[apple red],
267
- # %w[banana yellow],
268
- # %w[orange orange]
269
- # pp t
270
- # #=> #<Tb
271
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
272
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
273
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
274
- # p t.list_fields_all #=> ["_recordid", "fruit", "color"]
275
- #
276
- def list_fields_all
277
- @field_list.dup
278
- end
279
-
280
- # :call-seq:
281
- # table.reorder_fields!(fields)
282
- #
283
- # reorder the fields.
284
- #
285
- # t = Tb.new %w[fruit color],
286
- # %w[apple red],
287
- # %w[banana yellow],
288
- # %w[orange orange]
289
- # p t.list_fields
290
- # #=> ["fruit", "color"]
291
- # pp t
292
- # #=> #<Tb
293
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
294
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
295
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
296
- # t.reorder_fields! %w[color fruit]
297
- # p t.list_fields
298
- # #=> ["color", "fruit"]
299
- # pp t
300
- # #=> #<Tb
301
- # # {"_recordid"=>0, "color"=>"red", "fruit"=>"apple"}
302
- # # {"_recordid"=>1, "color"=>"yellow", "fruit"=>"banana"}
303
- # # {"_recordid"=>2, "color"=>"orange", "fruit"=>"orange"}>
304
- #
305
- def reorder_fields!(fields)
306
- reserved, non_resreved = @field_list.reject {|f| fields.include? f }.partition {|f| f.start_with?("_") }
307
- fs = reserved + fields + non_resreved
308
- @field_list = @field_list.sort_by {|f| fs.index(f) }
309
- end
310
-
311
- # :call-seq:
312
- # table.list_recordids -> [recordid1, recordid2, ...]
313
- #
314
- # returns the list of recordids as an array of integers.
315
- #
316
- # t = Tb.new %w[fruit color],
317
- # %w[apple red],
318
- # %w[banana yellow],
319
- # %w[orange orange]
320
- # pp t
321
- # #=> #<Tb
322
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
323
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
324
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
325
- # p t.list_recordids #=> [0, 1, 2]
326
- #
327
- def list_recordids
328
- @tbl["_recordid"].compact
329
- end
330
-
331
- # :call-seq:
332
- # table.size
333
- #
334
- # returns the number of records.
335
- #
336
- # t = Tb.new %w[fruit],
337
- # %w[apple],
338
- # %w[banana],
339
- # %w[orange]
340
- # pp t
341
- # #=> #<Tb
342
- # # {"_recordid"=>0, "fruit"=>"apple"}
343
- # # {"_recordid"=>1, "fruit"=>"banana"}
344
- # # {"_recordid"=>2, "fruit"=>"orange"}>
345
- # p t.size
346
- # #=> 3
347
- #
348
- def size
349
- @recordid2index.size
350
- end
351
-
352
- # :call-seq:
353
- # table.empty?
354
- #
355
- # returns true if there is no record.
356
- #
357
- # t0 = Tb.new %w[fruit]
358
- # pp t0
359
- # #=> #<Tb>
360
- # p t0.empty?
361
- # #=> true
362
- #
363
- # t3 = Tb.new %w[fruit],
364
- # %w[apple],
365
- # %w[banana],
366
- # %w[orange]
367
- # pp t3
368
- # #=> #<Tb
369
- # # {"_recordid"=>0, "fruit"=>"apple"}
370
- # # {"_recordid"=>1, "fruit"=>"banana"}
371
- # # {"_recordid"=>2, "fruit"=>"orange"}>
372
- # p t3.empty?
373
- # #=> false
374
- def empty?
375
- self.size == 0
376
- end
377
-
378
- # :call-seq:
379
- # table.allocate_recordid -> fresh_recordid
380
- # table.allocate_recordid(recordid) -> recordid
381
- #
382
- # inserts a record and returns its identifier.
383
- # All fields of the record are initialized to nil.
384
- #
385
- # t = Tb.new %w[fruit color],
386
- # %w[apple red],
387
- # %w[banana yellow],
388
- # %w[orange orange]
389
- # pp t
390
- # #=> #<Tb
391
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
392
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
393
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
394
- # p t.allocate_recoridd #=> 3
395
- # pp t
396
- # #=> #<Tb
397
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
398
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
399
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
400
- # # {"_recordid"=>3}>
401
- #
402
- # If the optional recordid is specified and the recordid is not used in the
403
- # table, a record is allocated with the recordid.
404
- # If the specified recordid is already used, ArgumentError is raised.
405
- #
406
- # t = Tb.new %w[fruit color],
407
- # %w[apple red],
408
- # %w[banana yellow],
409
- # %w[orange orange]
410
- # pp t
411
- # #=> #<Tb
412
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
413
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
414
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
415
- # t.allocate_recordid(100)
416
- # pp t
417
- # #=> #<Tb
418
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
419
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
420
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
421
- # # {"_recordid"=>100}>
422
- #
423
- def allocate_recordid(recordid=nil)
424
- if recordid.nil?
425
- recordid = @next_recordid
426
- @next_recordid += 1
427
- else
428
- recordid = check_recordid_type(recordid)
429
- if @recordid2index.has_key? recordid
430
- raise ArgumentError, "recordid already used: #{recordid.inspect}"
431
- end
432
- @next_recordid = recordid + 1 if @next_recordid <= recordid
433
- end
434
- if @free_index.empty?
435
- index = @tbl["_recordid"].length
436
- else
437
- index = @free_index.pop
438
- end
439
- @recordid2index[recordid] = index
440
- @tbl["_recordid"][index] = recordid
441
- recordid
442
- end
443
-
444
- # :call-seq:
445
- # table.allocate_record(recordid=nil)
446
- #
447
- # allocates a record.
448
- #
449
- # If the optional argument, _recordid_, is specified,
450
- # the allocated record will have the recordid.
451
- # If _recordid_ is already used, ArgumentError is raised.
452
- def allocate_record(recordid=nil)
453
- Tb::Record.new(self, allocate_recordid(recordid))
454
- end
455
-
456
- # :call-seq:
457
- # table.set_cell(recordid, field, value) -> value
458
- #
459
- # sets the value of the cell identified by _recordid_ and _field_.
460
- #
461
- # t = Tb.new %w[fruit color],
462
- # %w[apple red],
463
- # %w[banana yellow],
464
- # %w[orange orange]
465
- # pp t
466
- # #=> #<Tb
467
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
468
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
469
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
470
- # t.set_cell(1, "color", "green")
471
- # pp t
472
- # #=> #<Tb
473
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
474
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"green"}
475
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
476
- def set_cell(recordid, field, value)
477
- recordid = check_recordid(recordid)
478
- field = check_field(field)
479
- raise ArgumentError, "can not set for reserved field: #{field.inspect}" if field.start_with?("_")
480
- ary = @tbl[field]
481
- ary[@recordid2index[recordid]] = value
482
- end
483
-
484
- # :call-seq:
485
- # table.get_cell(recordid, field) -> value
486
- #
487
- # returns the value of the cell identified by _recordid_ and _field_.
488
- #
489
- # t = Tb.new %w[fruit color],
490
- # %w[apple red],
491
- # %w[banana yellow],
492
- # %w[orange orange]
493
- # pp t
494
- # #=> #<Tb
495
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
496
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
497
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
498
- # p t.get_cell(1, "fruit") #=> "banana"
499
- #
500
- def get_cell(recordid, field)
501
- recordid = check_recordid(recordid)
502
- field = check_field(field)
503
- ary = @tbl[field]
504
- ary[@recordid2index[recordid]]
505
- end
506
-
507
- # :call-seq:
508
- # table.delete_cell(recordid, field) -> oldvalue
509
- #
510
- # sets nil to the cell identified by _recordid_ and _field_.
511
- #
512
- # This method returns the old value.
513
- #
514
- # t = Tb.new %w[fruit color],
515
- # %w[apple red],
516
- # %w[banana yellow],
517
- # %w[orange orange]
518
- # pp t
519
- # #=> #<Tb
520
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
521
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
522
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
523
- # p t.delete_cell(1, "color") #=> "yellow"
524
- # pp t
525
- # #=> #<Tb
526
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
527
- # # {"_recordid"=>1, "fruit"=>"banana"}
528
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
529
- # p t.get_cell(1, "color") #=> nil
530
- #
531
- def delete_cell(recordid, field)
532
- recordid = check_recordid(recordid)
533
- field = check_field(field)
534
- raise ArgumentError, "can not delete reserved field: #{field.inspect}" if field.start_with?("_")
535
- ary = @tbl[field]
536
- index = @recordid2index[recordid]
537
- old = ary[index]
538
- ary[index] = nil
539
- old
540
- end
541
-
542
- # :call-seq:
543
- # table.delete_recordid(recordid) -> nil
544
- #
545
- # deletes a record identified by _recordid_.
546
- #
547
- # This method returns nil.
548
- #
549
- # t = Tb.new %w[fruit color],
550
- # %w[apple red],
551
- # %w[banana yellow],
552
- # %w[orange orange]
553
- # pp t
554
- # #=> #<Tb
555
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
556
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
557
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
558
- # p t.delete_recordid(1)
559
- # #=> nil
560
- # pp t
561
- # #=> #<Tb
562
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
563
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
564
- #
565
- def delete_recordid(recordid)
566
- recordid = check_recordid(recordid)
567
- index = @recordid2index.delete(recordid)
568
- @tbl.each {|f, ary|
569
- ary[index] = nil
570
- }
571
- @free_index.push index
572
- nil
573
- end
574
-
575
- # :call-seq:
576
- # table.insert({field1=>value1, ...})
577
- #
578
- # inserts a record.
579
- # The record is represented as a hash which keys are field names.
580
- #
581
- # This method returned the recordid of the inserted record.
582
- #
583
- # t = Tb.new %w[fruit color],
584
- # %w[apple red],
585
- # %w[banana yellow],
586
- # %w[orange orange]
587
- # pp t
588
- # #=> #<Tb
589
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
590
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
591
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
592
- # recordid = t.insert({"fruit"=>"grape", "color"=>"purple"})
593
- # p recordid
594
- # #=> 3
595
- # pp t
596
- # #=> #<Tb
597
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
598
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
599
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
600
- # # {"_recordid"=>3, "fruit"=>"grape", "color"=>"purple"}>
601
- #
602
- def insert(record)
603
- recordid = allocate_recordid
604
- update_record(recordid, record)
605
- recordid
606
- end
607
-
608
- # call-seq
609
- # table.insert_values(fields, values1, values2, ...) -> [recordid1, recordid2, ...]
610
- #
611
- # inserts records.
612
- # The records are represented by fields and values separately.
613
- # The first argument specifies the field names as an array.
614
- # The second argument specifies the first record values as an array.
615
- # The third argument specifies the second record values and so on.
616
- # The third and subsequent arguments are optional.
617
- #
618
- # This method return an array of recordids.
619
- #
620
- # t = Tb.new %w[fruit color],
621
- # %w[apple red],
622
- # %w[banana yellow],
623
- # %w[orange orange]
624
- # pp t
625
- # #=> #<Tb
626
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
627
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
628
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
629
- # p t.insert_values(["fruit", "color"], ["grape", "purple"], ["cherry", "red"])
630
- # #=> [3, 4]
631
- # pp t
632
- # #=> #<Tb
633
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
634
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
635
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
636
- # # {"_recordid"=>3, "fruit"=>"grape", "color"=>"purple"}
637
- # # {"_recordid"=>4, "fruit"=>"cherry", "color"=>"red"}>
638
- #
639
- def insert_values(fields, *values_list)
640
- recordids = []
641
- values_list.each {|values|
642
- if values.length != fields.length
643
- raise ArgumentError, "#{fields.length} fields expected but #{values.length} values given"
644
- end
645
- h = {}
646
- fields.each_with_index {|f, i|
647
- v = values[i]
648
- h[f] = v
649
- }
650
- recordids << insert(h)
651
- }
652
- recordids
653
- end
654
-
655
- # :call-seq:
656
- # table1.concat(table2, table3, ...) -> table1
657
- #
658
- # concatenates argument tables destructively into _table1_.
659
- # The reserved field (_recordid) in the argument tables is ignored.
660
- #
661
- # This method returns _table1_.
662
- #
663
- # t1 = Tb.new %w[fruit color],
664
- # %w[apple red]
665
- # t2 = Tb.new %w[fruit color],
666
- # %w[banana yellow],
667
- # %w[orange orange]
668
- # pp t1
669
- # #=> #<Tb {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}>
670
- # pp t2
671
- # #=> #<Tb
672
- # {"_recordid"=>0, "fruit"=>"banana", "color"=>"yellow"}
673
- # {"_recordid"=>1, "fruit"=>"orange", "color"=>"orange"}>
674
- # t1.concat(t2)
675
- # pp t1
676
- # #=> #<Tb
677
- # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
678
- # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
679
- # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
680
- #
681
- def concat(*tables)
682
- tables.each {|t|
683
- t.each_record {|record|
684
- record = record.to_h
685
- record.delete "_recordid"
686
- self.insert record
687
- }
688
- }
689
- self
690
- end
691
-
692
- # :call-seq:
693
- # table.update_record(recordid, {field1=>value1, ...}) -> nil
694
- #
695
- # updates the record specified by _recordid_.
696
- #
697
- # t = Tb.new %w[fruit color],
698
- # %w[apple red],
699
- # %w[banana yellow],
700
- # %w[orange orange]
701
- # pp t
702
- # #=> #<Tb
703
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
704
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
705
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
706
- # p t.update_record(1, {"color"=>"green"})
707
- # #=> nil
708
- # pp t
709
- # #=> #<Tb
710
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
711
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"green"}
712
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
713
- #
714
- def update_record(recordid, record)
715
- recordid = check_recordid(recordid)
716
- record.each {|f, v|
717
- f = check_field(f)
718
- set_cell(recordid, f, v)
719
- }
720
- nil
721
- end
722
-
723
- # :call-seq:
724
- # table.get_values(recordid, field1, field2, ...) -> [value1, value2, ...]
725
- #
726
- # extracts specified fields of the specified record.
727
- #
728
- # t = Tb.new %w[fruit color],
729
- # %w[apple red],
730
- # %w[banana yellow],
731
- # %w[orange orange]
732
- # pp t
733
- # #=> #<Tb
734
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
735
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
736
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
737
- # p t.get_values(1, "fruit", "color")
738
- # #=> ["banana", "yellow"]
739
- # p t.get_values(0, "fruit")
740
- # #=> ["apple"]
741
- #
742
- def get_values(recordid, *fields)
743
- recordid = check_recordid(recordid)
744
- fields.map {|f|
745
- f = check_field(f)
746
- get_cell(recordid, f)
747
- }
748
- end
749
-
750
- # :call-seq:
751
- # table.get_record(recordid) -> record
752
- #
753
- # get the record specified by _recordid_ as a hash.
754
- #
755
- # t = Tb.new %w[fruit color],
756
- # %w[apple red],
757
- # %w[banana yellow],
758
- # %w[orange orange]
759
- # pp t
760
- # #=> #<Tb
761
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
762
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
763
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
764
- # p t.get_record(1)
765
- # #=> #<Tb::Record: "_recordid"=>1, "fruit"=>"banana", "color"=>"yellow">
766
- #
767
- def get_record(recordid)
768
- recordid = check_recordid(recordid)
769
- Tb::Record.new(self, recordid)
770
- end
771
-
772
- # :call-seq:
773
- # table.each_field {|field| ... }
774
- #
775
- # iterates over the non-reserved field names of the table.
776
- #
777
- # t = Tb.new %w[fruit color],
778
- # %w[apple red],
779
- # %w[banana yellow],
780
- # %w[orange orange]
781
- # pp t
782
- # #=> #<Tb
783
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
784
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
785
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
786
- # t.each_field {|f| p f }
787
- # #=> "fruit"
788
- # # "color"
789
- #
790
- def each_field
791
- @field_list.each {|f|
792
- next if f.start_with?("_")
793
- yield f
794
- }
795
- nil
796
- end
797
-
798
- # :call-seq:
799
- # table.each_field_with_reserved {|field| ... }
800
- #
801
- # iterates over the reserved and non-reserved field names of the table.
802
- #
803
- # t = Tb.new %w[fruit color],
804
- # %w[apple red],
805
- # %w[banana yellow],
806
- # %w[orange orange]
807
- # pp t
808
- # #=> #<Tb
809
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
810
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
811
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
812
- # t.each_field {|f| p f }
813
- # #=> "_recordid"
814
- # # "fruit"
815
- # # "color"
816
- #
817
- def each_field_with_reserved
818
- @field_list.each {|f| yield f }
819
- nil
820
- end
821
-
822
- # :call-seq:
823
- # table.each_recordid {|recordid| ... }
824
- #
825
- # iterates over all records and yield the recordids of them.
826
- #
827
- # This method returns nil.
828
- #
829
- # t = Tb.new %w[fruit color],
830
- # %w[apple red],
831
- # %w[banana yellow],
832
- # %w[orange orange]
833
- # pp t
834
- # #=> #<Tb
835
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
836
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
837
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
838
- # t.each_recordid {|recordid| p recordid }
839
- # #=> 0
840
- # # 1
841
- # # 2
842
- #
843
- def each_recordid
844
- @tbl["_recordid"].each {|recordid|
845
- next if recordid.nil?
846
- yield recordid
847
- }
848
- nil
849
- end
850
-
851
- # :call-seq:
852
- # table.to_a -> [record1, ...]
853
- #
854
- # returns an array containing all records as Tb::Record objects.
855
- #
856
- # t = Tb.new %w[fruit color],
857
- # %w[apple red],
858
- # %w[banana yellow],
859
- # %w[orange orange]
860
- # pp t
861
- # #=> #<Tb
862
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
863
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
864
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
865
- # pp t.to_a
866
- # #=> [#<Tb::Record: "fruit"=>"apple", "color"=>"red">,
867
- # # #<Tb::Record: "fruit"=>"banana", "color"=>"yellow">,
868
- # # #<Tb::Record: "fruit"=>"orange", "color"=>"orange">]
869
- #
870
- def to_a
871
- ary = []
872
- each_recordid {|recordid|
873
- ary << get_record(recordid)
874
- }
875
- ary
876
- end
877
-
878
- # :call-seq:
879
- # table.each {|record| ... }
880
- # table.each_record {|record| ... }
881
- #
882
- # iterates over all records and yields them as Tb::Record object.
883
- #
884
- # This method returns nil.
885
- #
886
- # t = Tb.new %w[fruit color],
887
- # %w[apple red],
888
- # %w[banana yellow],
889
- # %w[orange orange]
890
- # pp t
891
- # #=> #<Tb
892
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
893
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
894
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
895
- # t.each_record {|record| p record }
896
- # #=> #<Tb::Record: "fruit"=>"apple", "color"=>"red">
897
- # # #<Tb::Record: "fruit"=>"banana", "color"=>"yellow">
898
- # # #<Tb::Record: "fruit"=>"orange", "color"=>"orange">
899
- #
900
- def each_record
901
- each_recordid {|recordid|
902
- yield get_record(recordid)
903
- }
904
- nil
905
- end
906
- alias each each_record
907
-
908
- # :call-seq:
909
- # table.each_record_values(field1, ...) {|value1, ...| ... }
910
- def each_record_values(*fields)
911
- each_recordid {|recordid|
912
- vs = get_values(recordid, *fields)
913
- yield vs
914
- }
915
- end
916
-
917
- # :call-seq:
918
- # table.header_and_each(header_proc) {|record| ... }
919
- #
920
- # +header_and_each+ calls _header_proc_ at first.
921
- # The block is called for each record after that.
922
- #
923
- def header_and_each(header_proc, &block)
924
- header_proc.call(list_fields) if header_proc
925
- self.each(&block)
926
- end
927
-
928
- # :call-seq:
929
- # table.filter {|record| ... }
930
- def filter
931
- t = Tb.new list_fields
932
- each_record {|record|
933
- if yield(record)
934
- t.insert record
935
- end
936
- }
937
- t
938
- end
939
-
940
- def natjoin2(table2, missing=nil, retain_left=false, retain_right=false)
941
- t1 = Tb::Enumerator.new {|y|
942
- self.with_header {|h0|
943
- y.set_header h0
944
- }.each {|record|
945
- y.yield record.to_h
946
- }
947
- }
948
- t2 = Tb::Enumerator.new {|y|
949
- table2.with_header {|h0|
950
- y.set_header h0
951
- }.each {|record|
952
- y.yield record.to_h
953
- }
954
- }
955
- er = t1.natjoin2(t2, missing, retain_left, retain_right)
956
- result = nil
957
- er.with_header {|header|
958
- result = Tb.new header
959
- }.each {|pairs|
960
- result.insert pairs
961
- }
962
- result
963
- end
964
-
965
- def natjoin2_outer(table2, missing=nil, retain_left=true, retain_right=true)
966
- natjoin2(table2, missing, retain_left, retain_right)
967
- end
968
-
969
- # :call-seq:
970
- # table.fmap!(field) {|record, value| new_value }
971
- def fmap!(field)
972
- each_recordid {|recordid|
973
- value = yield get_record(recordid), get_cell(recordid, field)
974
- set_cell(recordid, field, value)
975
- }
976
- end
977
-
978
- # :call-seq:
979
- # table.delete_field(field1, ...) -> nil
980
- #
981
- # deletes zero or more fields destructively.
982
- #
983
- # This method returns nil.
984
- def delete_field(*fields)
985
- fields.each {|f|
986
- f = check_field(f)
987
- raise ArgumentError, "can not delete reserved field: #{f.inspect}" if f.start_with?("_")
988
- @tbl.delete(f)
989
- @field_list.delete(f)
990
- }
991
- nil
992
- end
993
-
994
- # :call-seq:
995
- # table.rename_field({old_field1=>new_field1, ...})
996
- #
997
- # creates a new table which field names are renamed.
998
- #
999
- # t = Tb.new %w[fruit color],
1000
- # %w[apple red],
1001
- # %w[banana yellow],
1002
- # %w[orange orange]
1003
- # pp t
1004
- # #=> #<Tb
1005
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
1006
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
1007
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
1008
- # pp t.rename_field("fruit"=>"food")
1009
- # #=> #<Tb
1010
- # # {"_recordid"=>0, "food"=>"apple", "color"=>"red"}
1011
- # # {"_recordid"=>1, "food"=>"banana", "color"=>"yellow"}
1012
- # # {"_recordid"=>2, "food"=>"orange", "color"=>"orange"}>
1013
- #
1014
- def rename_field(rename_hash)
1015
- rh = {}
1016
- rename_hash.each {|of, nf|
1017
- of = check_field(of)
1018
- nf = check_field_type(nf)
1019
- rh[of] = nf
1020
- }
1021
- result = Tb.new
1022
- field_list = self.list_fields
1023
- field_list.each {|of|
1024
- nf = rh.fetch(of, of)
1025
- result.define_field(nf)
1026
- }
1027
- each_recordid {|recordid|
1028
- values = get_values(recordid, *field_list)
1029
- result.allocate_recordid(recordid)
1030
- field_list.each_with_index {|of, i|
1031
- nf = rh.fetch(of, of)
1032
- result.set_cell(recordid, nf, values[i])
1033
- }
1034
- }
1035
- result
1036
- end
1037
-
1038
- # :call-seq:
1039
- # table.reorder_records_by {|rec| ... }
1040
- #
1041
- # creates a new table object which has same records as _table_ but
1042
- # the order of the records are sorted.
1043
- #
1044
- # The sort order is defined as similar manner to Enumerable#sort_by.
1045
- #
1046
- # t = Tb.new %w[fruit color],
1047
- # %w[apple red],
1048
- # %w[banana yellow],
1049
- # %w[orange orange]
1050
- # pp t
1051
- # #=> #<Tb
1052
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
1053
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
1054
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
1055
- #
1056
- # pp t.reorder_records_by {|rec| rec["color"] }
1057
- # #=> #<Tb
1058
- # # {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
1059
- # # {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
1060
- # # {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}>
1061
- #
1062
- def reorder_records_by(&b)
1063
- result = Tb.new self.list_fields
1064
- self.sort_by(&b).each {|rec|
1065
- recordid = result.allocate_recordid(rec["_recordid"])
1066
- result.update_record(recordid, rec)
1067
- }
1068
- result
1069
- end
1070
- end