tb 0.9 → 1.0

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