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