bindata 0.9.3 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bindata might be problematic. Click here for more details.
- data/ChangeLog +18 -0
- data/NEWS +59 -0
- data/README +22 -23
- data/TODO +18 -12
- data/examples/gzip.rb +4 -4
- data/lib/bindata.rb +4 -3
- data/lib/bindata/array.rb +202 -132
- data/lib/bindata/base.rb +147 -166
- data/lib/bindata/{single.rb → base_primitive.rb} +82 -56
- data/lib/bindata/bits.rb +31 -770
- data/lib/bindata/choice.rb +157 -82
- data/lib/bindata/float.rb +25 -27
- data/lib/bindata/int.rb +144 -177
- data/lib/bindata/io.rb +59 -49
- data/lib/bindata/lazy.rb +80 -50
- data/lib/bindata/params.rb +134 -26
- data/lib/bindata/{single_value.rb → primitive.rb} +71 -64
- data/lib/bindata/{multi_value.rb → record.rb} +52 -70
- data/lib/bindata/registry.rb +49 -17
- data/lib/bindata/rest.rb +6 -10
- data/lib/bindata/sanitize.rb +55 -70
- data/lib/bindata/string.rb +60 -42
- data/lib/bindata/stringz.rb +34 -35
- data/lib/bindata/struct.rb +197 -152
- data/lib/bindata/trace.rb +35 -0
- data/spec/array_spec.rb +128 -112
- data/spec/{single_spec.rb → base_primitive_spec.rb} +102 -61
- data/spec/base_spec.rb +190 -185
- data/spec/bits_spec.rb +126 -98
- data/spec/choice_spec.rb +89 -98
- data/spec/example.rb +19 -0
- data/spec/float_spec.rb +28 -44
- data/spec/int_spec.rb +217 -127
- data/spec/io_spec.rb +41 -24
- data/spec/lazy_spec.rb +95 -49
- data/spec/primitive_spec.rb +191 -0
- data/spec/{multi_value_spec.rb → record_spec.rb} +124 -89
- data/spec/registry_spec.rb +53 -12
- data/spec/rest_spec.rb +2 -3
- data/spec/sanitize_spec.rb +47 -73
- data/spec/spec_common.rb +13 -1
- data/spec/string_spec.rb +34 -23
- data/spec/stringz_spec.rb +10 -18
- data/spec/struct_spec.rb +91 -63
- data/spec/system_spec.rb +291 -0
- metadata +12 -8
- data/spec/single_value_spec.rb +0 -131
data/lib/bindata/struct.rb
CHANGED
@@ -6,7 +6,7 @@ module BinData
|
|
6
6
|
#
|
7
7
|
# require 'bindata'
|
8
8
|
#
|
9
|
-
# class Tuple < BinData::
|
9
|
+
# class Tuple < BinData::Record
|
10
10
|
# int8 :x
|
11
11
|
# int8 :y
|
12
12
|
# int8 :z
|
@@ -37,23 +37,34 @@ module BinData
|
|
37
37
|
# <tt>:endian</tt>:: Either :little or :big. This specifies the default
|
38
38
|
# endian of any numerics in this struct, or in any
|
39
39
|
# nested data objects.
|
40
|
+
#
|
41
|
+
# == Field Parameters
|
42
|
+
#
|
43
|
+
# Fields may have have extra parameters as listed below:
|
44
|
+
#
|
45
|
+
# [<tt>:onlyif</tt>] Used to indicate a data object is optional.
|
46
|
+
# if +false+, this object will not be included in any
|
47
|
+
# calls to #read, #write, #num_bytes or #snapshot.
|
40
48
|
class Struct < BinData::Base
|
41
49
|
|
50
|
+
register(self.name, self)
|
51
|
+
|
42
52
|
# These reserved words may not be used as field names
|
43
53
|
RESERVED = (::Hash.instance_methods +
|
44
54
|
%w{alias and begin break case class def defined do else elsif
|
45
55
|
end ensure false for if in module next nil not or redo
|
46
56
|
rescue retry return self super then true undef unless until
|
47
57
|
when while yield} +
|
48
|
-
%w{array element index
|
49
|
-
|
50
|
-
# Register this class
|
51
|
-
register(self.name, self)
|
58
|
+
%w{array element index value} ).uniq
|
52
59
|
|
53
60
|
# A hash that can be accessed via attributes.
|
54
61
|
class Snapshot < Hash #:nodoc:
|
62
|
+
def respond_to?(symbol, include_private = false)
|
63
|
+
has_key?(symbol.to_s) || super(symbol, include_private)
|
64
|
+
end
|
65
|
+
|
55
66
|
def method_missing(symbol, *args)
|
56
|
-
self[symbol.
|
67
|
+
self[symbol.to_s] || super
|
57
68
|
end
|
58
69
|
end
|
59
70
|
|
@@ -61,91 +72,97 @@ module BinData
|
|
61
72
|
#### DEPRECATION HACK to warn about inheriting from BinData::Struct
|
62
73
|
#
|
63
74
|
def inherited(subclass) #:nodoc:
|
64
|
-
if subclass !=
|
75
|
+
if subclass != Record
|
65
76
|
# warn about deprecated method - remove before releasing 1.0
|
66
|
-
fail "error: inheriting from BinData::Struct has been deprecated. Inherit from BinData::
|
77
|
+
fail "error: inheriting from BinData::Struct has been deprecated. Inherit from BinData::Record instead."
|
67
78
|
end
|
68
79
|
end
|
69
80
|
#
|
70
81
|
#### DEPRECATION HACK to allow inheriting from BinData::Struct
|
71
82
|
|
72
83
|
|
73
|
-
# Ensures that +params+ is of the form expected by #initialize.
|
74
84
|
def sanitize_parameters!(sanitizer, params)
|
75
|
-
|
76
|
-
|
77
|
-
if
|
85
|
+
ensure_valid_endian(params)
|
86
|
+
|
87
|
+
if params.has_key?(:fields)
|
88
|
+
sfields = sanitized_fields(sanitizer, params[:fields], params[:endian])
|
89
|
+
field_names = sanitized_field_names(sfields)
|
90
|
+
hfield_names = hidden_field_names(params[:hide])
|
91
|
+
|
92
|
+
ensure_field_names_are_valid(field_names)
|
93
|
+
|
94
|
+
params[:fields] = sfields
|
95
|
+
params[:hide] = (hfield_names & field_names)
|
96
|
+
end
|
97
|
+
|
98
|
+
super(sanitizer, params)
|
99
|
+
end
|
100
|
+
|
101
|
+
#-------------
|
102
|
+
private
|
103
|
+
|
104
|
+
def ensure_valid_endian(params)
|
105
|
+
if params.has_key?(:endian)
|
106
|
+
endian = params[:endian]
|
78
107
|
unless [:little, :big].include?(endian)
|
79
108
|
raise ArgumentError, "unknown value for endian '#{endian}'"
|
80
109
|
end
|
81
|
-
|
82
|
-
params[:endian] = endian
|
83
110
|
end
|
111
|
+
end
|
84
112
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
klass = sanitizer.lookup_klass(ftype)
|
91
|
-
sanitized_fparams = sanitizer.sanitize_params(klass, fparams)
|
92
|
-
[klass, fname, sanitized_fparams]
|
93
|
-
end
|
94
|
-
params[:fields] = all_fields
|
113
|
+
def sanitized_fields(sanitizer, fields, endian)
|
114
|
+
result = nil
|
115
|
+
sanitizer.with_endian(endian) do
|
116
|
+
result = fields.collect do |ftype, fname, fparams|
|
117
|
+
sanitized_field(sanitizer, ftype, fname, fparams)
|
95
118
|
end
|
119
|
+
end
|
120
|
+
result
|
121
|
+
end
|
96
122
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
123
|
+
def sanitized_field(sanitizer, ftype, fname, fparams)
|
124
|
+
fname = fname.to_s
|
125
|
+
fclass = sanitizer.lookup_class(ftype)
|
126
|
+
sanitized_fparams = sanitizer.sanitized_params(fclass, fparams)
|
127
|
+
[fclass, fname, sanitized_fparams]
|
128
|
+
end
|
103
129
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
"as it shadows an existing method.", fname)
|
108
|
-
end
|
130
|
+
def sanitized_field_names(sanitized_fields)
|
131
|
+
sanitized_fields.collect { |fclass, fname, fparams| fname }
|
132
|
+
end
|
109
133
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
"as it is a reserved name.", fname)
|
114
|
-
end
|
134
|
+
def hidden_field_names(hidden)
|
135
|
+
(hidden || []).collect { |h| h.to_s }
|
136
|
+
end
|
115
137
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
"is defined multiple times.", fname)
|
120
|
-
end
|
138
|
+
def ensure_field_names_are_valid(field_names)
|
139
|
+
instance_methods = self.instance_methods
|
140
|
+
reserved_names = RESERVED
|
121
141
|
|
122
|
-
|
142
|
+
field_names.each do |name|
|
143
|
+
if instance_methods.include?(name)
|
144
|
+
raise NameError.new("Rename field '#{name}' in #{self}, " +
|
145
|
+
"as it shadows an existing method.", name)
|
123
146
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
147
|
+
if reserved_names.include?(name)
|
148
|
+
raise NameError.new("Rename field '#{name}' in #{self}, " +
|
149
|
+
"as it is a reserved name.", name)
|
150
|
+
end
|
151
|
+
if field_names.count(name) != 1
|
152
|
+
raise NameError.new("field '#{name}' in #{self}, " +
|
153
|
+
"is defined multiple times.", name)
|
131
154
|
end
|
132
|
-
params[:hide] = hide
|
133
155
|
end
|
134
|
-
|
135
|
-
super(sanitizer, params)
|
136
156
|
end
|
137
157
|
end
|
138
158
|
|
139
|
-
|
140
|
-
|
141
|
-
bindata_optional_parameters :endian, :hide
|
159
|
+
mandatory_parameter :fields
|
160
|
+
optional_parameters :endian, :hide
|
142
161
|
|
143
|
-
# Creates a new Struct.
|
144
162
|
def initialize(params = {}, parent = nil)
|
145
163
|
super(params, parent)
|
146
164
|
|
147
|
-
|
148
|
-
@field_names = no_eval_param(:fields).collect { |k, n, p| n }
|
165
|
+
@field_names = get_parameter(:fields).collect { |c, n, p| n }
|
149
166
|
@field_objs = []
|
150
167
|
end
|
151
168
|
|
@@ -155,7 +172,8 @@ module BinData
|
|
155
172
|
if name.nil?
|
156
173
|
@field_objs.each { |f| f.clear unless f.nil? }
|
157
174
|
else
|
158
|
-
obj
|
175
|
+
warn "'obj.clear(name)' is deprecated. Replacing with 'obj.name.clear'"
|
176
|
+
obj = find_obj_for_name(name)
|
159
177
|
obj.clear unless obj.nil?
|
160
178
|
end
|
161
179
|
end
|
@@ -164,149 +182,176 @@ module BinData
|
|
164
182
|
# is given, returns whether all fields are clear.
|
165
183
|
def clear?(name = nil)
|
166
184
|
if name.nil?
|
167
|
-
@field_objs.
|
168
|
-
return false unless f.nil? or f.clear?
|
169
|
-
end
|
170
|
-
true
|
185
|
+
@field_objs.inject(true) { |all_clear, f| all_clear and (f.nil? or f.clear?) }
|
171
186
|
else
|
172
|
-
obj
|
187
|
+
warn "'obj.clear?(name)' is deprecated. Replacing with 'obj.name.clear?'"
|
188
|
+
obj = find_obj_for_name(name)
|
173
189
|
obj.nil? ? true : obj.clear?
|
174
190
|
end
|
175
191
|
end
|
176
192
|
|
177
|
-
# Returns whether this data object contains a single value. Single
|
178
|
-
# value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
|
179
|
-
def single_value?
|
180
|
-
return false
|
181
|
-
end
|
182
|
-
|
183
193
|
# Returns a list of the names of all fields accessible through this
|
184
194
|
# object. +include_hidden+ specifies whether to include hidden names
|
185
195
|
# in the listing.
|
186
196
|
def field_names(include_hidden = false)
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
names << name
|
193
|
-
end
|
197
|
+
if include_hidden
|
198
|
+
@field_names.dup
|
199
|
+
else
|
200
|
+
hidden = get_parameter(:hide)
|
201
|
+
@field_names - hidden
|
194
202
|
end
|
195
|
-
names
|
196
203
|
end
|
197
204
|
|
198
|
-
|
199
|
-
|
200
|
-
|
205
|
+
def respond_to?(symbol, include_private = false)
|
206
|
+
super(symbol, include_private) ||
|
207
|
+
field_names(true).include?(symbol.to_s.chomp("="))
|
201
208
|
end
|
202
209
|
|
203
|
-
def
|
204
|
-
|
205
|
-
if
|
206
|
-
|
207
|
-
|
208
|
-
offset = 0
|
209
|
-
(0...idx).each do |i|
|
210
|
-
this_offset = @field_objs[i].do_num_bytes
|
211
|
-
if ::Float === offset and ::Integer === this_offset
|
212
|
-
offset = offset.ceil
|
213
|
-
end
|
214
|
-
offset += this_offset
|
215
|
-
end
|
216
|
-
offset
|
210
|
+
def method_missing(symbol, *args, &block)
|
211
|
+
obj = find_obj_for_name(symbol)
|
212
|
+
if obj
|
213
|
+
invoke_field(obj, symbol, args)
|
217
214
|
else
|
218
|
-
|
215
|
+
super
|
219
216
|
end
|
220
217
|
end
|
221
218
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
orig_respond_to?(symbol, include_private) ||
|
226
|
-
field_names(true).include?(symbol.id2name.chomp("="))
|
219
|
+
def debug_name_of(child)
|
220
|
+
field_name = @field_names[find_index_of(child)]
|
221
|
+
"#{debug_name}.#{field_name}"
|
227
222
|
end
|
228
223
|
|
229
|
-
def
|
230
|
-
|
224
|
+
def offset_of(child)
|
225
|
+
if child.class == ::String
|
226
|
+
fail "error: 'offset_of(\"fieldname\")' is deprecated. Use 'fieldname.offset' instead"
|
227
|
+
end
|
228
|
+
|
229
|
+
instantiate_all_objs
|
230
|
+
sum = sum_num_bytes_below_index(find_index_of(child))
|
231
|
+
child_offset = (::Integer === child.do_num_bytes) ? sum.ceil : sum.floor
|
232
|
+
|
233
|
+
offset + child_offset
|
234
|
+
end
|
231
235
|
|
236
|
+
#---------------
|
237
|
+
private
|
238
|
+
|
239
|
+
def invoke_field(obj, symbol, args)
|
240
|
+
name = symbol.to_s
|
232
241
|
is_writer = (name[-1, 1] == "=")
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
if (obj = find_obj_for_name(name))
|
237
|
-
# pass on the request
|
238
|
-
if obj.single_value? and is_writer
|
239
|
-
obj.value = *args
|
240
|
-
elsif obj.single_value?
|
241
|
-
obj.value
|
242
|
-
else
|
243
|
-
obj
|
244
|
-
end
|
242
|
+
|
243
|
+
if is_writer
|
244
|
+
obj.assign(*args)
|
245
245
|
else
|
246
|
-
|
246
|
+
obj
|
247
247
|
end
|
248
248
|
end
|
249
249
|
|
250
|
-
|
251
|
-
|
250
|
+
def find_index_of(obj)
|
251
|
+
@field_objs.find_index { |el| el.equal?(obj) }
|
252
|
+
end
|
252
253
|
|
253
|
-
# Returns the data object that stores values for +name+.
|
254
254
|
def find_obj_for_name(name)
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
255
|
+
field_name = name.to_s.chomp("=")
|
256
|
+
index = @field_names.find_index(field_name)
|
257
|
+
if index
|
258
|
+
instantiate_obj_at(index)
|
259
|
+
@field_objs[index]
|
259
260
|
else
|
260
261
|
nil
|
261
262
|
end
|
262
263
|
end
|
263
264
|
|
264
|
-
|
265
|
-
|
266
|
-
@field_names.each_with_index { |name, i| instantiate_obj(i) }
|
265
|
+
def instantiate_all_objs
|
266
|
+
@field_names.each_index { |i| instantiate_obj_at(i) }
|
267
267
|
end
|
268
268
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
@field_objs[idx] = fklass.new(fparams, self)
|
269
|
+
def instantiate_obj_at(index)
|
270
|
+
if @field_objs[index].nil?
|
271
|
+
fclass, fname, fparams = get_parameter(:fields)[index]
|
272
|
+
@field_objs[index] = fclass.new(fparams, self)
|
274
273
|
end
|
275
274
|
end
|
276
275
|
|
277
|
-
# Reads the values for all fields in this object from +io+.
|
278
276
|
def _do_read(io)
|
279
|
-
|
280
|
-
@field_objs.each { |f| f.do_read(io) }
|
277
|
+
instantiate_all_objs
|
278
|
+
@field_objs.each { |f| f.do_read(io) if include_obj(f) }
|
279
|
+
end
|
280
|
+
|
281
|
+
def _done_read
|
282
|
+
@field_objs.each { |f| f.done_read if include_obj(f) }
|
281
283
|
end
|
282
284
|
|
283
|
-
# Writes the values for all fields in this object to +io+.
|
284
285
|
def _do_write(io)
|
285
|
-
|
286
|
-
@field_objs.each { |f| f.do_write(io) }
|
286
|
+
instantiate_all_objs
|
287
|
+
@field_objs.each { |f| f.do_write(io) if include_obj(f) }
|
287
288
|
end
|
288
289
|
|
289
|
-
# Returns the number of bytes it will take to write the field represented
|
290
|
-
# by +name+. If +name+ is nil then returns the number of bytes required
|
291
|
-
# to write all fields.
|
292
290
|
def _do_num_bytes(name)
|
293
291
|
if name.nil?
|
294
|
-
|
295
|
-
|
292
|
+
instantiate_all_objs
|
293
|
+
sum_num_bytes_for_all_fields.ceil
|
296
294
|
else
|
297
|
-
obj
|
295
|
+
warn "'obj.num_bytes(name)' is deprecated. Replacing with 'obj.name.num_bytes'"
|
296
|
+
obj = find_obj_for_name(name)
|
298
297
|
obj.nil? ? 0 : obj.do_num_bytes
|
299
298
|
end
|
300
299
|
end
|
301
300
|
|
302
|
-
|
301
|
+
def _assign(val)
|
302
|
+
clear
|
303
|
+
assign_fields(as_snapshot(val))
|
304
|
+
end
|
305
|
+
|
306
|
+
def as_snapshot(val)
|
307
|
+
if val.class == Hash
|
308
|
+
snapshot = Snapshot.new
|
309
|
+
val.each_pair { |k,v| snapshot[k.to_s] = v unless v.nil? }
|
310
|
+
snapshot
|
311
|
+
elsif val.nil?
|
312
|
+
Snapshot.new
|
313
|
+
else
|
314
|
+
val
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def assign_fields(snapshot)
|
319
|
+
field_names(true).each do |name|
|
320
|
+
obj = find_obj_for_name(name)
|
321
|
+
if obj and snapshot.respond_to?(name)
|
322
|
+
obj.assign(snapshot.__send__(name))
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
303
327
|
def _snapshot
|
304
|
-
|
328
|
+
snapshot = Snapshot.new
|
305
329
|
field_names.each do |name|
|
306
|
-
|
307
|
-
|
330
|
+
obj = find_obj_for_name(name)
|
331
|
+
snapshot[name] = obj.snapshot if include_obj(obj)
|
332
|
+
end
|
333
|
+
snapshot
|
334
|
+
end
|
335
|
+
|
336
|
+
def sum_num_bytes_for_all_fields
|
337
|
+
sum_num_bytes_below_index(@field_objs.length)
|
338
|
+
end
|
339
|
+
|
340
|
+
def sum_num_bytes_below_index(index)
|
341
|
+
sum = 0
|
342
|
+
(0...index).each do |i|
|
343
|
+
obj = @field_objs[i]
|
344
|
+
if include_obj(obj)
|
345
|
+
nbytes = obj.do_num_bytes
|
346
|
+
sum = ((::Integer === nbytes) ? sum.ceil : sum) + nbytes
|
347
|
+
end
|
308
348
|
end
|
309
|
-
|
349
|
+
|
350
|
+
sum
|
351
|
+
end
|
352
|
+
|
353
|
+
def include_obj(obj)
|
354
|
+
not obj.has_parameter?(:onlyif) or obj.eval_parameter(:onlyif)
|
310
355
|
end
|
311
356
|
end
|
312
357
|
end
|