mongodb-mongo 0.1.3
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.rdoc +216 -0
- data/Rakefile +54 -0
- data/bin/mongo_console +21 -0
- data/bin/validate +51 -0
- data/examples/benchmarks.rb +38 -0
- data/examples/blog.rb +76 -0
- data/examples/index_test.rb +128 -0
- data/examples/simple.rb +17 -0
- data/lib/mongo/admin.rb +86 -0
- data/lib/mongo/collection.rb +161 -0
- data/lib/mongo/cursor.rb +230 -0
- data/lib/mongo/db.rb +399 -0
- data/lib/mongo/message/get_more_message.rb +21 -0
- data/lib/mongo/message/insert_message.rb +19 -0
- data/lib/mongo/message/kill_cursors_message.rb +20 -0
- data/lib/mongo/message/message.rb +68 -0
- data/lib/mongo/message/message_header.rb +34 -0
- data/lib/mongo/message/msg_message.rb +17 -0
- data/lib/mongo/message/opcodes.rb +16 -0
- data/lib/mongo/message/query_message.rb +67 -0
- data/lib/mongo/message/remove_message.rb +20 -0
- data/lib/mongo/message/update_message.rb +21 -0
- data/lib/mongo/message.rb +4 -0
- data/lib/mongo/mongo.rb +98 -0
- data/lib/mongo/query.rb +110 -0
- data/lib/mongo/types/binary.rb +34 -0
- data/lib/mongo/types/dbref.rb +37 -0
- data/lib/mongo/types/objectid.rb +137 -0
- data/lib/mongo/types/regexp_of_holding.rb +44 -0
- data/lib/mongo/types/undefined.rb +31 -0
- data/lib/mongo/util/bson.rb +431 -0
- data/lib/mongo/util/byte_buffer.rb +163 -0
- data/lib/mongo/util/ordered_hash.rb +68 -0
- data/lib/mongo/util/xml_to_ruby.rb +102 -0
- data/lib/mongo.rb +12 -0
- data/mongo-ruby-driver.gemspec +62 -0
- data/tests/test_admin.rb +60 -0
- data/tests/test_bson.rb +135 -0
- data/tests/test_byte_buffer.rb +69 -0
- data/tests/test_cursor.rb +66 -0
- data/tests/test_db.rb +85 -0
- data/tests/test_db_api.rb +354 -0
- data/tests/test_db_connection.rb +17 -0
- data/tests/test_message.rb +35 -0
- data/tests/test_objectid.rb +98 -0
- data/tests/test_ordered_hash.rb +85 -0
- data/tests/test_round_trip.rb +116 -0
- metadata +100 -0
@@ -0,0 +1,431 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 10gen Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it
|
5
|
+
# under the terms of the GNU Affero General Public License, version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
require 'base64'
|
18
|
+
require 'mongo/util/byte_buffer'
|
19
|
+
require 'mongo/util/ordered_hash'
|
20
|
+
require 'mongo/types/binary'
|
21
|
+
require 'mongo/types/dbref'
|
22
|
+
require 'mongo/types/objectid'
|
23
|
+
require 'mongo/types/regexp_of_holding'
|
24
|
+
require 'mongo/types/undefined'
|
25
|
+
|
26
|
+
# A BSON seralizer/deserializer.
|
27
|
+
class BSON
|
28
|
+
|
29
|
+
MINKEY = -1
|
30
|
+
EOO = 0
|
31
|
+
NUMBER = 1
|
32
|
+
STRING = 2
|
33
|
+
OBJECT = 3
|
34
|
+
ARRAY = 4
|
35
|
+
BINARY = 5
|
36
|
+
UNDEFINED = 6
|
37
|
+
OID = 7
|
38
|
+
BOOLEAN = 8
|
39
|
+
DATE = 9
|
40
|
+
NULL = 10
|
41
|
+
REGEX = 11
|
42
|
+
REF = 12
|
43
|
+
CODE = 13
|
44
|
+
SYMBOL = 14
|
45
|
+
CODE_W_SCOPE = 15
|
46
|
+
NUMBER_INT = 16
|
47
|
+
MAXKEY = 127
|
48
|
+
|
49
|
+
if RUBY_VERSION >= '1.9'
|
50
|
+
def self.to_utf8(str)
|
51
|
+
str.encode("utf-8")
|
52
|
+
end
|
53
|
+
else
|
54
|
+
def self.to_utf8(str)
|
55
|
+
str # TODO punt for now
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.serialize_cstr(buf, val)
|
60
|
+
buf.put_array(to_utf8(val.to_s).unpack("C*") + [0])
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize(db=nil)
|
64
|
+
# db is only needed during deserialization when the data contains a DBRef
|
65
|
+
@db = db
|
66
|
+
@buf = ByteBuffer.new
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_a
|
70
|
+
@buf.to_a
|
71
|
+
end
|
72
|
+
|
73
|
+
def serialize(obj)
|
74
|
+
raise "Document is null" unless obj
|
75
|
+
|
76
|
+
@buf.rewind
|
77
|
+
# put in a placeholder for the total size
|
78
|
+
@buf.put_int(0)
|
79
|
+
|
80
|
+
obj.each {|k, v|
|
81
|
+
type = bson_type(v, k)
|
82
|
+
case type
|
83
|
+
when STRING, CODE, SYMBOL
|
84
|
+
serialize_string_element(@buf, k, v, type)
|
85
|
+
when NUMBER, NUMBER_INT
|
86
|
+
serialize_number_element(@buf, k, v, type)
|
87
|
+
when OBJECT
|
88
|
+
serialize_object_element(@buf, k, v)
|
89
|
+
when OID
|
90
|
+
serialize_oid_element(@buf, k, v)
|
91
|
+
when ARRAY
|
92
|
+
serialize_array_element(@buf, k, v)
|
93
|
+
when REGEX
|
94
|
+
serialize_regex_element(@buf, k, v)
|
95
|
+
when BOOLEAN
|
96
|
+
serialize_boolean_element(@buf, k, v)
|
97
|
+
when DATE
|
98
|
+
serialize_date_element(@buf, k, v)
|
99
|
+
when NULL
|
100
|
+
serialize_null_element(@buf, k)
|
101
|
+
when REF
|
102
|
+
serialize_dbref_element(@buf, k, v)
|
103
|
+
when BINARY
|
104
|
+
serialize_binary_element(@buf, k, v)
|
105
|
+
when UNDEFINED
|
106
|
+
serialize_undefined_element(@buf, k)
|
107
|
+
when CODE_W_SCOPE
|
108
|
+
# TODO
|
109
|
+
raise "unimplemented type #{type}"
|
110
|
+
else
|
111
|
+
raise "unhandled type #{type}"
|
112
|
+
end
|
113
|
+
}
|
114
|
+
serialize_eoo_element(@buf)
|
115
|
+
@buf.put_int(@buf.size, 0)
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
def deserialize(buf=nil, parent=nil)
|
120
|
+
# If buf is nil, use @buf, assumed to contain already-serialized BSON.
|
121
|
+
# This is only true during testing.
|
122
|
+
@buf = ByteBuffer.new(buf.to_a) if buf
|
123
|
+
@buf.rewind
|
124
|
+
@buf.get_int # eat message size
|
125
|
+
doc = OrderedHash.new
|
126
|
+
while @buf.more?
|
127
|
+
type = @buf.get
|
128
|
+
case type
|
129
|
+
when STRING, CODE
|
130
|
+
key = deserialize_cstr(@buf)
|
131
|
+
doc[key] = deserialize_string_data(@buf)
|
132
|
+
when SYMBOL
|
133
|
+
key = deserialize_cstr(@buf)
|
134
|
+
doc[key] = deserialize_string_data(@buf).intern
|
135
|
+
when NUMBER
|
136
|
+
key = deserialize_cstr(@buf)
|
137
|
+
doc[key] = deserialize_number_data(@buf)
|
138
|
+
when NUMBER_INT
|
139
|
+
key = deserialize_cstr(@buf)
|
140
|
+
doc[key] = deserialize_number_int_data(@buf)
|
141
|
+
when OID
|
142
|
+
key = deserialize_cstr(@buf)
|
143
|
+
doc[key] = deserialize_oid_data(@buf)
|
144
|
+
when ARRAY
|
145
|
+
key = deserialize_cstr(@buf)
|
146
|
+
doc[key] = deserialize_array_data(@buf, doc)
|
147
|
+
when REGEX
|
148
|
+
key = deserialize_cstr(@buf)
|
149
|
+
doc[key] = deserialize_regex_data(@buf)
|
150
|
+
when OBJECT
|
151
|
+
key = deserialize_cstr(@buf)
|
152
|
+
doc[key] = deserialize_object_data(@buf, doc)
|
153
|
+
when BOOLEAN
|
154
|
+
key = deserialize_cstr(@buf)
|
155
|
+
doc[key] = deserialize_boolean_data(@buf)
|
156
|
+
when DATE
|
157
|
+
key = deserialize_cstr(@buf)
|
158
|
+
doc[key] = deserialize_date_data(@buf)
|
159
|
+
when NULL
|
160
|
+
key = deserialize_cstr(@buf)
|
161
|
+
doc[key] = nil
|
162
|
+
when UNDEFINED
|
163
|
+
key = deserialize_cstr(@buf)
|
164
|
+
doc[key] = XGen::Mongo::Driver::Undefined.new
|
165
|
+
when REF
|
166
|
+
key = deserialize_cstr(@buf)
|
167
|
+
doc[key] = deserialize_dbref_data(@buf, key, parent)
|
168
|
+
when BINARY
|
169
|
+
key = deserialize_cstr(@buf)
|
170
|
+
doc[key] = deserialize_binary_data(@buf)
|
171
|
+
when CODE_W_SCOPE
|
172
|
+
# TODO
|
173
|
+
raise "unimplemented type #{type}"
|
174
|
+
when EOO
|
175
|
+
break
|
176
|
+
else
|
177
|
+
raise "Unknown type #{type}, key = #{key}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
@buf.rewind
|
181
|
+
doc
|
182
|
+
end
|
183
|
+
|
184
|
+
# For debugging.
|
185
|
+
def hex_dump
|
186
|
+
str = ''
|
187
|
+
@buf.to_a.each_with_index { |b,i|
|
188
|
+
if (i % 8) == 0
|
189
|
+
str << "\n" if i > 0
|
190
|
+
str << '%4d: ' % i
|
191
|
+
else
|
192
|
+
str << ' '
|
193
|
+
end
|
194
|
+
str << '%02X' % b
|
195
|
+
}
|
196
|
+
str
|
197
|
+
end
|
198
|
+
|
199
|
+
def deserialize_date_data(buf)
|
200
|
+
millisecs = buf.get_long()
|
201
|
+
Time.at(millisecs.to_f / 1000.0) # at() takes fractional seconds
|
202
|
+
end
|
203
|
+
|
204
|
+
def deserialize_boolean_data(buf)
|
205
|
+
buf.get == 1
|
206
|
+
end
|
207
|
+
|
208
|
+
def deserialize_number_data(buf)
|
209
|
+
buf.get_double
|
210
|
+
end
|
211
|
+
|
212
|
+
def deserialize_number_int_data(buf)
|
213
|
+
buf.get_int
|
214
|
+
end
|
215
|
+
|
216
|
+
def deserialize_object_data(buf, parent)
|
217
|
+
size = buf.get_int
|
218
|
+
buf.position -= 4
|
219
|
+
BSON.new(@db).deserialize(buf.get(size), parent)
|
220
|
+
end
|
221
|
+
|
222
|
+
def deserialize_array_data(buf, parent)
|
223
|
+
h = deserialize_object_data(buf, parent)
|
224
|
+
a = []
|
225
|
+
h.each { |k, v| a[k.to_i] = v }
|
226
|
+
a
|
227
|
+
end
|
228
|
+
|
229
|
+
def deserialize_regex_data(buf)
|
230
|
+
str = deserialize_cstr(buf)
|
231
|
+
options_str = deserialize_cstr(buf)
|
232
|
+
options = 0
|
233
|
+
options |= Regexp::IGNORECASE if options_str.include?('i')
|
234
|
+
options |= Regexp::MULTILINE if options_str.include?('m')
|
235
|
+
options |= Regexp::EXTENDED if options_str.include?('x')
|
236
|
+
options_str.gsub!(/[imx]/, '') # Now remove the three we understand
|
237
|
+
XGen::Mongo::Driver::RegexpOfHolding.new(str, options, options_str)
|
238
|
+
end
|
239
|
+
|
240
|
+
def deserialize_string_data(buf)
|
241
|
+
len = buf.get_int
|
242
|
+
bytes = buf.get(len)
|
243
|
+
str = bytes[0..-2].pack("C*")
|
244
|
+
if RUBY_VERSION >= '1.9'
|
245
|
+
str.force_encoding("utf-8")
|
246
|
+
end
|
247
|
+
str
|
248
|
+
end
|
249
|
+
|
250
|
+
def deserialize_oid_data(buf)
|
251
|
+
XGen::Mongo::Driver::ObjectID.new(buf.get(12))
|
252
|
+
end
|
253
|
+
|
254
|
+
def deserialize_dbref_data(buf, key, parent)
|
255
|
+
ns = deserialize_string_data(buf)
|
256
|
+
oid = deserialize_oid_data(buf)
|
257
|
+
XGen::Mongo::Driver::DBRef.new(parent, key, @db, ns, oid)
|
258
|
+
end
|
259
|
+
|
260
|
+
def deserialize_binary_data(buf)
|
261
|
+
len = buf.get_int
|
262
|
+
bytes = buf.get(len)
|
263
|
+
str = ''
|
264
|
+
bytes.each { |c| str << c.chr }
|
265
|
+
str.to_mongo_binary
|
266
|
+
end
|
267
|
+
|
268
|
+
def serialize_eoo_element(buf)
|
269
|
+
buf.put(EOO)
|
270
|
+
end
|
271
|
+
|
272
|
+
def serialize_null_element(buf, key)
|
273
|
+
buf.put(NULL)
|
274
|
+
self.class.serialize_cstr(buf, key)
|
275
|
+
end
|
276
|
+
|
277
|
+
def serialize_dbref_element(buf, key, val)
|
278
|
+
serialize_string_element(buf, key, val.namespace, REF)
|
279
|
+
buf.put_array(val.object_id.to_a)
|
280
|
+
end
|
281
|
+
|
282
|
+
def serialize_binary_element(buf, key, val)
|
283
|
+
buf.put(BINARY)
|
284
|
+
self.class.serialize_cstr(buf, key)
|
285
|
+
buf.put_int(val.length)
|
286
|
+
bytes = if RUBY_VERSION >= '1.9'
|
287
|
+
val.bytes.to_a
|
288
|
+
else
|
289
|
+
a = []
|
290
|
+
val.each_byte { |byte| a << byte }
|
291
|
+
a
|
292
|
+
end
|
293
|
+
buf.put_array(bytes)
|
294
|
+
end
|
295
|
+
|
296
|
+
def serialize_undefined_element(buf, key)
|
297
|
+
buf.put(UNDEFINED)
|
298
|
+
self.class.serialize_cstr(buf, key)
|
299
|
+
end
|
300
|
+
|
301
|
+
def serialize_boolean_element(buf, key, val)
|
302
|
+
buf.put(BOOLEAN)
|
303
|
+
self.class.serialize_cstr(buf, key)
|
304
|
+
buf.put(val ? 1 : 0)
|
305
|
+
end
|
306
|
+
|
307
|
+
def serialize_date_element(buf, key, val)
|
308
|
+
buf.put(DATE)
|
309
|
+
self.class.serialize_cstr(buf, key)
|
310
|
+
millisecs = (val.to_f * 1000).to_i
|
311
|
+
buf.put_long(millisecs)
|
312
|
+
end
|
313
|
+
|
314
|
+
def serialize_number_element(buf, key, val, type)
|
315
|
+
buf.put(type)
|
316
|
+
self.class.serialize_cstr(buf, key)
|
317
|
+
if type == NUMBER
|
318
|
+
buf.put_double(val)
|
319
|
+
else
|
320
|
+
buf.put_int(val)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def serialize_object_element(buf, key, val, opcode=OBJECT)
|
325
|
+
buf.put(opcode)
|
326
|
+
self.class.serialize_cstr(buf, key)
|
327
|
+
buf.put_array(BSON.new.serialize(val).to_a)
|
328
|
+
end
|
329
|
+
|
330
|
+
def serialize_array_element(buf, key, val)
|
331
|
+
# Turn array into hash with integer indices as keys
|
332
|
+
h = OrderedHash.new
|
333
|
+
i = 0
|
334
|
+
val.each { |v| h[i] = v; i += 1 }
|
335
|
+
serialize_object_element(buf, key, h, ARRAY)
|
336
|
+
end
|
337
|
+
|
338
|
+
def serialize_regex_element(buf, key, val)
|
339
|
+
buf.put(REGEX)
|
340
|
+
self.class.serialize_cstr(buf, key)
|
341
|
+
|
342
|
+
str = val.to_s.sub(/.*?:/, '')[0..-2] # Turn "(?xxx:yyy)" into "yyy"
|
343
|
+
self.class.serialize_cstr(buf, str)
|
344
|
+
|
345
|
+
options = val.options
|
346
|
+
options_str = ''
|
347
|
+
options_str << 'i' if ((options & Regexp::IGNORECASE) != 0)
|
348
|
+
options_str << 'm' if ((options & Regexp::MULTILINE) != 0)
|
349
|
+
options_str << 'x' if ((options & Regexp::EXTENDED) != 0)
|
350
|
+
options_str << val.extra_options_str if val.respond_to?(:extra_options_str)
|
351
|
+
# Must store option chars in alphabetical order
|
352
|
+
self.class.serialize_cstr(buf, options_str.split(//).sort.uniq.join)
|
353
|
+
end
|
354
|
+
|
355
|
+
def serialize_oid_element(buf, key, val)
|
356
|
+
buf.put(OID)
|
357
|
+
self.class.serialize_cstr(buf, key)
|
358
|
+
|
359
|
+
buf.put_array(val.to_a)
|
360
|
+
end
|
361
|
+
|
362
|
+
def serialize_string_element(buf, key, val, type)
|
363
|
+
buf.put(type)
|
364
|
+
self.class.serialize_cstr(buf, key)
|
365
|
+
|
366
|
+
# Make a hole for the length
|
367
|
+
len_pos = buf.position
|
368
|
+
buf.put_int(0)
|
369
|
+
|
370
|
+
# Save the string
|
371
|
+
start_pos = buf.position
|
372
|
+
self.class.serialize_cstr(buf, val)
|
373
|
+
end_pos = buf.position
|
374
|
+
|
375
|
+
# Put the string size in front
|
376
|
+
buf.put_int(end_pos - start_pos, len_pos)
|
377
|
+
|
378
|
+
# Go back to where we were
|
379
|
+
buf.position = end_pos
|
380
|
+
end
|
381
|
+
|
382
|
+
def deserialize_cstr(buf)
|
383
|
+
chars = ""
|
384
|
+
while 1
|
385
|
+
b = buf.get
|
386
|
+
break if b == 0
|
387
|
+
chars << b.chr
|
388
|
+
end
|
389
|
+
if RUBY_VERSION >= '1.9'
|
390
|
+
chars.force_encoding("utf-8") # Mongo stores UTF-8
|
391
|
+
end
|
392
|
+
chars
|
393
|
+
end
|
394
|
+
|
395
|
+
def bson_type(o, key)
|
396
|
+
case o
|
397
|
+
when nil
|
398
|
+
NULL
|
399
|
+
when Integer
|
400
|
+
NUMBER_INT
|
401
|
+
when Numeric
|
402
|
+
NUMBER
|
403
|
+
when XGen::Mongo::Driver::Binary # must be before String
|
404
|
+
BINARY
|
405
|
+
when String
|
406
|
+
# magic awful stuff - the DB requires that a where clause is sent as CODE
|
407
|
+
key == "$where" ? CODE : STRING
|
408
|
+
when Array
|
409
|
+
ARRAY
|
410
|
+
when Regexp
|
411
|
+
REGEX
|
412
|
+
when XGen::Mongo::Driver::ObjectID
|
413
|
+
OID
|
414
|
+
when XGen::Mongo::Driver::DBRef
|
415
|
+
REF
|
416
|
+
when true, false
|
417
|
+
BOOLEAN
|
418
|
+
when Time
|
419
|
+
DATE
|
420
|
+
when Hash
|
421
|
+
OBJECT
|
422
|
+
when Symbol
|
423
|
+
SYMBOL
|
424
|
+
when XGen::Mongo::Driver::Undefined
|
425
|
+
UNDEFINED
|
426
|
+
else
|
427
|
+
raise "Unknown type of object: #{o.class.name}"
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 10gen Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it
|
5
|
+
# under the terms of the GNU Affero General Public License, version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
# A byte buffer.
|
18
|
+
class ByteBuffer
|
19
|
+
|
20
|
+
attr_reader :order
|
21
|
+
|
22
|
+
def initialize(initial_data=[])
|
23
|
+
@buf = initial_data
|
24
|
+
@cursor = 0
|
25
|
+
self.order = :little_endian
|
26
|
+
end
|
27
|
+
|
28
|
+
# +endianness+ should be :little_endian or :big_endian. Default is :little_endian
|
29
|
+
def order=(endianness)
|
30
|
+
@order = endianness
|
31
|
+
@int_pack_order = endianness == :little_endian ? 'V' : 'N'
|
32
|
+
@double_pack_order = endianness == :little_endian ? 'E' : 'G'
|
33
|
+
end
|
34
|
+
|
35
|
+
def rewind
|
36
|
+
@cursor = 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def position
|
40
|
+
@cursor
|
41
|
+
end
|
42
|
+
|
43
|
+
def position=(val)
|
44
|
+
@cursor = val
|
45
|
+
end
|
46
|
+
|
47
|
+
def clear
|
48
|
+
@buf = []
|
49
|
+
rewind
|
50
|
+
end
|
51
|
+
|
52
|
+
def size
|
53
|
+
@buf.size
|
54
|
+
end
|
55
|
+
alias_method :length, :size
|
56
|
+
|
57
|
+
def put(byte, offset=nil)
|
58
|
+
@cursor = offset if offset
|
59
|
+
@buf[@cursor] = byte
|
60
|
+
@cursor += 1
|
61
|
+
end
|
62
|
+
|
63
|
+
def put_array(array, offset=nil)
|
64
|
+
@cursor = offset if offset
|
65
|
+
@buf[@cursor, array.length] = array
|
66
|
+
@cursor += array.length
|
67
|
+
end
|
68
|
+
|
69
|
+
if RUBY_VERSION >= '1.9'
|
70
|
+
def put_int(i, offset=nil)
|
71
|
+
put_array([i].pack(@int_pack_order).split(//).collect{|c| c.bytes.first}, offset)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
def put_int(i, offset=nil)
|
75
|
+
put_array([i].pack(@int_pack_order).split(//).collect{|c| c[0]}, offset)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def put_long(i, offset=nil)
|
80
|
+
offset = @cursor unless offset
|
81
|
+
if @int_pack_order == 'N'
|
82
|
+
put_int(i >> 32, offset)
|
83
|
+
put_int(i & 0xffffffff, offset + 4)
|
84
|
+
else
|
85
|
+
put_int(i & 0xffffffff, offset)
|
86
|
+
put_int(i >> 32, offset + 4)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
if RUBY_VERSION >= '1.9'
|
91
|
+
def put_double(d, offset=nil)
|
92
|
+
put_array([d].pack(@double_pack_order).split(//).collect{|c| c.bytes.first}, offset)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
def put_double(d, offset=nil)
|
96
|
+
put_array([d].pack(@double_pack_order).split(//).collect{|c| c[0]}, offset)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# If +size+ == nil, returns one byte. Else returns array of bytes of length
|
101
|
+
# # +size+.
|
102
|
+
def get(len=nil)
|
103
|
+
one_byte = len.nil?
|
104
|
+
len ||= 1
|
105
|
+
check_read_length(len)
|
106
|
+
start = @cursor
|
107
|
+
@cursor += len
|
108
|
+
if one_byte
|
109
|
+
@buf[start]
|
110
|
+
else
|
111
|
+
@buf[start, len]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_int
|
116
|
+
check_read_length(4)
|
117
|
+
vals = ""
|
118
|
+
(@cursor..@cursor+3).each { |i| vals << @buf[i].chr }
|
119
|
+
@cursor += 4
|
120
|
+
vals.unpack(@int_pack_order)[0]
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_long
|
124
|
+
i1 = get_int
|
125
|
+
i2 = get_int
|
126
|
+
if @int_pack_order == 'N'
|
127
|
+
(i1 << 32) + i2
|
128
|
+
else
|
129
|
+
(i2 << 32) + i1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def get_double
|
134
|
+
check_read_length(8)
|
135
|
+
vals = ""
|
136
|
+
(@cursor..@cursor+7).each { |i| vals << @buf[i].chr }
|
137
|
+
@cursor += 8
|
138
|
+
vals.unpack(@double_pack_order)[0]
|
139
|
+
end
|
140
|
+
|
141
|
+
def more?
|
142
|
+
@cursor < @buf.size
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_a
|
146
|
+
@buf
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_s
|
150
|
+
@buf.pack("C*")
|
151
|
+
end
|
152
|
+
|
153
|
+
def dump
|
154
|
+
@buf.each_with_index { |c, i| $stderr.puts "#{'%04d' % i}: #{'%02x' % c} #{'%03o' % c} #{'%s' % c.chr} #{'%3d' % c}" }
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def check_read_length(len)
|
160
|
+
raise "attempt to read past end of buffer" if @cursor + len > @buf.length
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 10gen Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it
|
5
|
+
# under the terms of the GNU Affero General Public License, version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
# A hash in which the order of keys are preserved.
|
18
|
+
#
|
19
|
+
# Under Ruby 1.9 and greater, this class has no added methods because Ruby's
|
20
|
+
# Hash already keeps its keys ordered by order of insertion.
|
21
|
+
class OrderedHash < Hash
|
22
|
+
|
23
|
+
# We only need the body of this class if the RUBY_VERSION is before 1.9
|
24
|
+
if RUBY_VERSION < '1.9'
|
25
|
+
|
26
|
+
attr_accessor :ordered_keys
|
27
|
+
|
28
|
+
def keys
|
29
|
+
@ordered_keys || []
|
30
|
+
end
|
31
|
+
|
32
|
+
def []=(key, value)
|
33
|
+
@ordered_keys ||= []
|
34
|
+
@ordered_keys << key unless @ordered_keys.include?(key)
|
35
|
+
super(key, value)
|
36
|
+
end
|
37
|
+
|
38
|
+
def each
|
39
|
+
@ordered_keys ||= []
|
40
|
+
@ordered_keys.each { |k| yield k, self[k] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def values
|
44
|
+
collect { |k, v| v }
|
45
|
+
end
|
46
|
+
|
47
|
+
def merge(other)
|
48
|
+
oh = self.dup
|
49
|
+
oh.merge!(other)
|
50
|
+
oh
|
51
|
+
end
|
52
|
+
|
53
|
+
def merge!(other)
|
54
|
+
@ordered_keys ||= []
|
55
|
+
@ordered_keys += other.keys # unordered if not an OrderedHash
|
56
|
+
@ordered_keys.uniq!
|
57
|
+
super(other)
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
str = '{'
|
62
|
+
str << (@ordered_keys || []).collect { |k| "\"#{k}\"=>#{self.[](k).inspect}" }.join(", ")
|
63
|
+
str << '}'
|
64
|
+
end
|
65
|
+
|
66
|
+
end # Ruby before 1.9
|
67
|
+
|
68
|
+
end
|