bindata 0.7.0 → 0.8.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 +8 -0
- data/README +5 -5
- data/examples/gzip.rb +2 -2
- data/lib/bindata.rb +1 -1
- data/lib/bindata/array.rb +49 -34
- data/lib/bindata/base.rb +24 -5
- data/lib/bindata/string.rb +14 -19
- data/lib/bindata/struct.rb +132 -34
- data/spec/array_spec.rb +24 -0
- data/spec/base_spec.rb +29 -0
- data/spec/string_spec.rb +51 -28
- data/spec/struct_spec.rb +124 -31
- metadata +2 -2
data/ChangeLog
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
= BinData Changelog
|
2
2
|
|
3
|
+
-== Version 0.8.0 (2007-10-14)
|
4
|
+
|
5
|
+
* Add reserved field names to Struct.
|
6
|
+
* Prevent warnings about method redefinition.
|
7
|
+
* Allow Struct to masquerade as one of its fields.
|
8
|
+
* Renamed String param :initial_length to :read_length.
|
9
|
+
* BinData::Array now behaves more like the internal ruby array.
|
10
|
+
|
3
11
|
== Version 0.7.0 (2007-08-26)
|
4
12
|
|
5
13
|
* Arrays now support terminating conditions as well as fixed length reads.
|
data/README
CHANGED
@@ -17,7 +17,7 @@ There is a better way.
|
|
17
17
|
|
18
18
|
class Rectangle < BinData::Struct
|
19
19
|
uint16le :len
|
20
|
-
string :name, :
|
20
|
+
string :name, :read_length => :len
|
21
21
|
uint32le :width
|
22
22
|
uint32le :height
|
23
23
|
end
|
@@ -102,7 +102,7 @@ Here's how we'd implement the same example with BinData.
|
|
102
102
|
|
103
103
|
class PascalString < BinData::Struct
|
104
104
|
uint8 :len, :value => lambda { data.length }
|
105
|
-
string :data, :
|
105
|
+
string :data, :read_length => :len
|
106
106
|
end
|
107
107
|
|
108
108
|
# reading
|
@@ -122,15 +122,15 @@ writing separately.
|
|
122
122
|
|
123
123
|
class PascalStringReader < BinData::Struct
|
124
124
|
uint8 :len
|
125
|
-
string :data, :
|
125
|
+
string :data, :read_length => :len
|
126
126
|
end
|
127
127
|
|
128
128
|
This states that when reading the string, the initial length of the string
|
129
129
|
(and hence the number of bytes to read) is determined by the value of the
|
130
130
|
+len+ field.
|
131
131
|
|
132
|
-
Note that <tt>:
|
133
|
-
<tt>:
|
132
|
+
Note that <tt>:read_length => :len</tt> is syntactic sugar for
|
133
|
+
<tt>:read_length => lambda { len }</tt>, but more on that later.
|
134
134
|
|
135
135
|
class PascalStringWriter < BinData::Struct
|
136
136
|
uint8 :len, :value => lambda { data.length }
|
data/examples/gzip.rb
CHANGED
@@ -11,11 +11,11 @@ class Gzip
|
|
11
11
|
|
12
12
|
class Extra < BinData::Struct
|
13
13
|
uint16le :len, :length => lambda { data.length }
|
14
|
-
string :data, :
|
14
|
+
string :data, :read_length => :len
|
15
15
|
end
|
16
16
|
|
17
17
|
class Header < BinData::Struct
|
18
|
-
uint16le :
|
18
|
+
uint16le :ident, :value => 0x8b1f, :check_value => 0x8b1f
|
19
19
|
uint8 :compression_method, :initial_value => DEFLATE
|
20
20
|
uint8 :flags, :value => :calculate_flags_val,
|
21
21
|
# Upper 3 bits must be zero
|
data/lib/bindata.rb
CHANGED
data/lib/bindata/array.rb
CHANGED
@@ -128,37 +128,6 @@ module BinData
|
|
128
128
|
[]
|
129
129
|
end
|
130
130
|
|
131
|
-
# Returns the first element, or the first +n+ elements, of the array.
|
132
|
-
# If the array is empty, the first form returns nil, and the second
|
133
|
-
# form returns an empty array.
|
134
|
-
def first(n = nil)
|
135
|
-
if n.nil?
|
136
|
-
self.length.zero? ? nil : self[0]
|
137
|
-
else
|
138
|
-
array = []
|
139
|
-
[n, self.length].min.times do |i|
|
140
|
-
array.push(self[i])
|
141
|
-
end
|
142
|
-
array
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
# Returns the last element, or the last +n+ elements, of the array.
|
147
|
-
# If the array is empty, the first form returns nil, and the second
|
148
|
-
# form returns an empty array.
|
149
|
-
def last(n = nil)
|
150
|
-
if n.nil?
|
151
|
-
self.length.zero? ? nil : self[self.length - 1]
|
152
|
-
else
|
153
|
-
array = []
|
154
|
-
start = self.length - [n, self.length].min
|
155
|
-
start.upto(self.length - 1) do |i|
|
156
|
-
array.push(self[i])
|
157
|
-
end
|
158
|
-
array
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
131
|
# Appends a new element to the end of the array. If the array contains
|
163
132
|
# single_values then the +value+ may be provided to the call.
|
164
133
|
# Returns the appended object, or value in the case of single_values.
|
@@ -170,10 +139,15 @@ module BinData
|
|
170
139
|
|
171
140
|
# Returns the element at +index+. If the element is a single_value
|
172
141
|
# then the value of the element is returned instead.
|
173
|
-
def [](index)
|
174
|
-
|
175
|
-
|
142
|
+
def [](*index)
|
143
|
+
data = elements[*index]
|
144
|
+
if data.respond_to?(:each)
|
145
|
+
data.collect { |el| el.single_value? ? el.value : el }
|
146
|
+
else
|
147
|
+
data.single_value? ? data.value : data
|
148
|
+
end
|
176
149
|
end
|
150
|
+
alias_method :slice, :[]
|
177
151
|
|
178
152
|
# Sets the element at +index+. If the element is a single_value
|
179
153
|
# then the value of the element is set instead.
|
@@ -193,12 +167,53 @@ module BinData
|
|
193
167
|
end
|
194
168
|
end
|
195
169
|
|
170
|
+
# Returns the first element, or the first +n+ elements, of the array.
|
171
|
+
# If the array is empty, the first form returns nil, and the second
|
172
|
+
# form returns an empty array.
|
173
|
+
def first(n = nil)
|
174
|
+
if n.nil?
|
175
|
+
self.length.zero? ? nil : self[0]
|
176
|
+
else
|
177
|
+
array = []
|
178
|
+
[n, self.length].min.times do |i|
|
179
|
+
array.push(self[i])
|
180
|
+
end
|
181
|
+
array
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Returns the last element, or the last +n+ elements, of the array.
|
186
|
+
# If the array is empty, the first form returns nil, and the second
|
187
|
+
# form returns an empty array.
|
188
|
+
def last(n = nil)
|
189
|
+
if n.nil?
|
190
|
+
self.length.zero? ? nil : self[self.length - 1]
|
191
|
+
else
|
192
|
+
array = []
|
193
|
+
start = self.length - [n, self.length].min
|
194
|
+
start.upto(self.length - 1) do |i|
|
195
|
+
array.push(self[i])
|
196
|
+
end
|
197
|
+
array
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
196
201
|
# The number of elements in this array.
|
197
202
|
def length
|
198
203
|
elements.length
|
199
204
|
end
|
200
205
|
alias_method :size, :length
|
201
206
|
|
207
|
+
# Returns true if self array contains no elements.
|
208
|
+
def empty?
|
209
|
+
length.zero?
|
210
|
+
end
|
211
|
+
|
212
|
+
# Allow this object to be used in array context.
|
213
|
+
def to_ary
|
214
|
+
snapshot
|
215
|
+
end
|
216
|
+
|
202
217
|
#---------------
|
203
218
|
private
|
204
219
|
|
data/lib/bindata/base.rb
CHANGED
@@ -157,13 +157,25 @@ module BinData
|
|
157
157
|
klass
|
158
158
|
end
|
159
159
|
|
160
|
+
# Returns a list of parameters that *weren't* provided to this object.
|
161
|
+
def unsupplied_parameters
|
162
|
+
supplied = @params.keys + @env.params.keys
|
163
|
+
self.class.parameters - supplied
|
164
|
+
end
|
165
|
+
|
160
166
|
# Reads data into this bin object by calling #do_read then #done_read.
|
161
167
|
def read(io)
|
168
|
+
# remove previous method to prevent warnings
|
169
|
+
class << io
|
170
|
+
undef_method(:bindata_mark) if method_defined?(:bindata_mark)
|
171
|
+
end
|
172
|
+
|
162
173
|
# remember the current position in the IO object
|
163
|
-
io.instance_eval "def
|
174
|
+
io.instance_eval "def bindata_mark; #{io.pos}; end"
|
164
175
|
|
165
176
|
do_read(io)
|
166
177
|
done_read
|
178
|
+
self
|
167
179
|
end
|
168
180
|
|
169
181
|
# Reads the value for this data from +io+.
|
@@ -189,6 +201,11 @@ module BinData
|
|
189
201
|
respond_to? :value
|
190
202
|
end
|
191
203
|
|
204
|
+
# Return a human readable representation of this object.
|
205
|
+
def inspect
|
206
|
+
snapshot.inspect
|
207
|
+
end
|
208
|
+
|
192
209
|
#---------------
|
193
210
|
private
|
194
211
|
|
@@ -230,7 +247,7 @@ module BinData
|
|
230
247
|
# be called from #do_read before performing the reading.
|
231
248
|
def check_offset(io)
|
232
249
|
if has_param?(:check_offset)
|
233
|
-
actual_offset = io.pos - io.
|
250
|
+
actual_offset = io.pos - io.bindata_mark
|
234
251
|
expected = eval_param(:check_offset, :offset => actual_offset)
|
235
252
|
|
236
253
|
if not expected
|
@@ -242,7 +259,6 @@ module BinData
|
|
242
259
|
end
|
243
260
|
end
|
244
261
|
|
245
|
-
=begin
|
246
262
|
# To be implemented by subclasses
|
247
263
|
|
248
264
|
# Resets the internal state to that of a newly created object.
|
@@ -281,7 +297,10 @@ module BinData
|
|
281
297
|
raise NotImplementedError
|
282
298
|
end
|
283
299
|
|
284
|
-
#
|
285
|
-
|
300
|
+
# Set visibility requirements of methods to implement
|
301
|
+
public :clear, :done_read, :snapshot, :field_names
|
302
|
+
private :_do_read, :_write, :_num_bytes
|
303
|
+
|
304
|
+
# End To be implemented by subclasses
|
286
305
|
end
|
287
306
|
end
|
data/lib/bindata/string.rb
CHANGED
@@ -9,8 +9,7 @@ module BinData
|
|
9
9
|
# String objects accept all the params that BinData::Single
|
10
10
|
# does, as well as the following:
|
11
11
|
#
|
12
|
-
# <tt>:
|
13
|
-
# either read or set.
|
12
|
+
# <tt>:read_length</tt>:: The length to use when reading a value.
|
14
13
|
# <tt>:length</tt>:: The fixed length of the string. If a shorter
|
15
14
|
# string is set, it will be padded to this length.
|
16
15
|
# <tt>:pad_char</tt>:: The character to use when padding a string to a
|
@@ -23,17 +22,17 @@ module BinData
|
|
23
22
|
class String < Single
|
24
23
|
# These are the parameters used by this class.
|
25
24
|
mandatory_parameters :pad_char
|
26
|
-
optional_parameters :
|
25
|
+
optional_parameters :read_length, :length, :trim_value
|
27
26
|
|
28
27
|
def initialize(params = {}, env = nil)
|
29
28
|
super(cleaned_params(params), env)
|
30
29
|
|
31
30
|
# the only valid param combinations of length and value are:
|
32
|
-
# :
|
31
|
+
# :read_length and :value
|
32
|
+
# :read_length and :initial_value
|
33
33
|
# :length and :initial_value
|
34
34
|
ensure_mutual_exclusion(:initial_value, :value)
|
35
|
-
ensure_mutual_exclusion(:
|
36
|
-
ensure_mutual_exclusion(:initial_length, :initial_value)
|
35
|
+
ensure_mutual_exclusion(:read_length, :length)
|
37
36
|
ensure_mutual_exclusion(:length, :value)
|
38
37
|
end
|
39
38
|
|
@@ -51,7 +50,7 @@ module BinData
|
|
51
50
|
# Returns +val+ ensuring that it is padded to the desired length.
|
52
51
|
def val_to_str(val)
|
53
52
|
# trim val if necessary
|
54
|
-
len =
|
53
|
+
len = eval_param(:length) || val.length
|
55
54
|
str = val.slice(0, len)
|
56
55
|
|
57
56
|
# then pad to length if str is short
|
@@ -60,7 +59,8 @@ module BinData
|
|
60
59
|
|
61
60
|
# Read a number of bytes from +io+ and return the value they represent.
|
62
61
|
def read_val(io)
|
63
|
-
|
62
|
+
len = eval_param(:read_length) || eval_param(:length) || 0
|
63
|
+
readbytes(io, len)
|
64
64
|
end
|
65
65
|
|
66
66
|
# Returns an empty string as default.
|
@@ -68,22 +68,17 @@ module BinData
|
|
68
68
|
""
|
69
69
|
end
|
70
70
|
|
71
|
-
# Return the number of bytes that +val+ will occupy when written.
|
72
|
-
def val_num_bytes(val)
|
73
|
-
if clear? and (evaluated = eval_param(:initial_length))
|
74
|
-
evaluated
|
75
|
-
elsif (evaluated = eval_param(:length))
|
76
|
-
evaluated
|
77
|
-
else
|
78
|
-
val.length
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
71
|
# Returns a hash of cleaned +params+. Cleaning means that param
|
83
72
|
# values are converted to a desired format.
|
84
73
|
def cleaned_params(params)
|
85
74
|
new_params = params.dup
|
86
75
|
|
76
|
+
# warn about deprecated param - remove before releasing 1.0
|
77
|
+
if params[:initial_length]
|
78
|
+
warn ":initial_length is deprecated. Replacing with :read_length"
|
79
|
+
new_params[:read_length] = params[:initial_length]
|
80
|
+
end
|
81
|
+
|
87
82
|
# set :pad_char to be a single length character string
|
88
83
|
ch = new_params[:pad_char] || 0
|
89
84
|
ch = ch.respond_to?(:chr) ? ch.chr : ch.to_s
|
data/lib/bindata/struct.rb
CHANGED
@@ -22,22 +22,38 @@ module BinData
|
|
22
22
|
# obj = SomeStruct.new
|
23
23
|
# obj.field_names =># ["b", "x", "y", "z"]
|
24
24
|
#
|
25
|
+
#
|
26
|
+
# class PascalString < BinData::Struct
|
27
|
+
# delegate :data
|
28
|
+
#
|
29
|
+
# int32le :len, :value => lambda { data.length }
|
30
|
+
# string :data, :read_length => :len
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# str = PascalString.new
|
34
|
+
# str.value = "a test string"
|
35
|
+
# str.single_value? =># true
|
36
|
+
# str.len =># 13
|
37
|
+
# str.num_bytes =># 17
|
38
|
+
#
|
25
39
|
# == Parameters
|
26
40
|
#
|
27
41
|
# Parameters may be provided at initialisation to control the behaviour of
|
28
42
|
# an object. These params are:
|
29
43
|
#
|
30
|
-
# <tt>:fields</tt>::
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# <tt>:hide</tt>::
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
44
|
+
# <tt>:fields</tt>:: An array specifying the fields for this struct.
|
45
|
+
# Each element of the array is of the form [type, name,
|
46
|
+
# params]. Type is a symbol representing a registered
|
47
|
+
# type. Name is the name of this field. Name may be
|
48
|
+
# nil as in the example above. Params is an optional
|
49
|
+
# hash of parameters to pass to this field when
|
50
|
+
# instantiating it.
|
51
|
+
# <tt>:hide</tt>:: A list of the names of fields that are to be hidden
|
52
|
+
# from the outside world. Hidden fields don't appear
|
53
|
+
# in #snapshot or #field_names but are still accessible
|
54
|
+
# by name.
|
55
|
+
# <tt>:delegate</tt>:: Forwards unknown methods calls and unknown params
|
56
|
+
# to this field.
|
41
57
|
class Struct < Base
|
42
58
|
# A hash that can be accessed via attributes.
|
43
59
|
class Snapshot < Hash #:nodoc:
|
@@ -68,6 +84,16 @@ module BinData
|
|
68
84
|
@endian
|
69
85
|
end
|
70
86
|
|
87
|
+
# Returns the name of the delegate field for this struct. The delegate
|
88
|
+
# is set to +name+ if given.
|
89
|
+
def delegate(name=nil)
|
90
|
+
@delegate ||= nil
|
91
|
+
if name != nil
|
92
|
+
@delegate = name.to_s
|
93
|
+
end
|
94
|
+
@delegate
|
95
|
+
end
|
96
|
+
|
71
97
|
# Returns the names of any hidden fields in this struct. Any given args
|
72
98
|
# are appended to the hidden list.
|
73
99
|
def hide(*args)
|
@@ -106,6 +132,12 @@ module BinData
|
|
106
132
|
"field '#{name}' shadows an existing method", caller
|
107
133
|
end
|
108
134
|
|
135
|
+
# check that name isn't reserved
|
136
|
+
if Hash.instance_methods.include?(name) and delegate.nil?
|
137
|
+
raise NameError.new("", name),
|
138
|
+
"field '#{name}' is a reserved name", caller
|
139
|
+
end
|
140
|
+
|
109
141
|
# remember this field. These fields will be recalled upon creating
|
110
142
|
# an instance of this class
|
111
143
|
@fields.push([type, name, params])
|
@@ -119,23 +151,65 @@ module BinData
|
|
119
151
|
|
120
152
|
# These are the parameters used by this class.
|
121
153
|
mandatory_parameter :fields
|
122
|
-
|
154
|
+
optional_parameters :endian, :hide, :delegate
|
123
155
|
|
124
156
|
# Creates a new Struct.
|
125
157
|
def initialize(params = {}, env = nil)
|
126
158
|
super(cleaned_params(params), env)
|
127
159
|
|
160
|
+
all_methods = methods
|
161
|
+
all_reserved_methods = nil
|
162
|
+
delegate_name = param(:delegate)
|
163
|
+
|
164
|
+
# get all reserved field names
|
165
|
+
res = param(:fields).find { |type, name, params| name == delegate_name }
|
166
|
+
if res
|
167
|
+
# all methods and field_names of the delegate are reserved.
|
168
|
+
klass_name = res[0]
|
169
|
+
delegate_klass = klass_lookup(klass_name)
|
170
|
+
if delegate_klass.nil?
|
171
|
+
raise TypeError, "unknown type '#{klass_name} for #{self}"
|
172
|
+
end
|
173
|
+
|
174
|
+
delegate_params = res[2]
|
175
|
+
delegate = delegate_klass.new(delegate_params, create_env)
|
176
|
+
all_reserved_methods = delegate.methods + delegate.field_names -
|
177
|
+
all_methods
|
178
|
+
|
179
|
+
# move unsupplied params from this object to the delegate object
|
180
|
+
delegate.unsupplied_parameters.each do |p|
|
181
|
+
if (v = @env.params.delete(p))
|
182
|
+
delegate_params[p] = v
|
183
|
+
end
|
184
|
+
end
|
185
|
+
else
|
186
|
+
# no delegate so all instance methods of Hash are reserved
|
187
|
+
all_reserved_methods = Hash.instance_methods - all_methods
|
188
|
+
end
|
189
|
+
|
128
190
|
# create instances of the fields
|
129
191
|
@fields = param(:fields).collect do |type, name, params|
|
130
192
|
klass = klass_lookup(type)
|
131
193
|
raise TypeError, "unknown type '#{type}' for #{self}" if klass.nil?
|
132
|
-
if
|
194
|
+
if all_methods.include?(name)
|
133
195
|
raise NameError.new("field '#{name}' shadows an existing method",name)
|
134
196
|
end
|
197
|
+
if all_reserved_methods.include?(name)
|
198
|
+
raise NameError.new("field '#{name}' is a reserved name",name)
|
199
|
+
end
|
135
200
|
[name, klass.new(params, create_env)]
|
136
201
|
end
|
137
202
|
end
|
138
203
|
|
204
|
+
# Returns a list of parameters that *weren't* provided to this object.
|
205
|
+
def unsupplied_parameters
|
206
|
+
if delegate_object != nil
|
207
|
+
delegate_object.unsupplied_parameters
|
208
|
+
else
|
209
|
+
super
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
139
213
|
# Clears the field represented by +name+. If no +name+
|
140
214
|
# is given, clears all fields in the struct.
|
141
215
|
def clear(name = nil)
|
@@ -185,32 +259,39 @@ module BinData
|
|
185
259
|
|
186
260
|
# Returns a snapshot of this struct as a hash.
|
187
261
|
def snapshot
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
262
|
+
if delegate_object != nil
|
263
|
+
delegate_object.snapshot
|
264
|
+
else
|
265
|
+
hash = Snapshot.new
|
266
|
+
field_names.each do |name|
|
267
|
+
hash[name] = find_obj_for_name(name).snapshot
|
268
|
+
end
|
269
|
+
hash
|
194
270
|
end
|
195
|
-
hash
|
196
271
|
end
|
197
272
|
|
198
273
|
# Returns a list of the names of all fields accessible through this
|
199
274
|
# object. +include_hidden+ specifies whether to include hidden names
|
200
275
|
# in the listing.
|
201
276
|
def field_names(include_hidden = false)
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
277
|
+
if delegate_object != nil and !include_hidden
|
278
|
+
# delegate if possible
|
279
|
+
delegate_object.field_names
|
280
|
+
else
|
281
|
+
# collect field names
|
282
|
+
names = []
|
283
|
+
hidden = param(:hide)
|
284
|
+
@fields.each do |name, obj|
|
285
|
+
if name != ""
|
286
|
+
if include_hidden or not hidden.include?(name)
|
287
|
+
names << name
|
288
|
+
end
|
289
|
+
else
|
290
|
+
names.concat(obj.field_names)
|
291
|
+
end
|
211
292
|
end
|
293
|
+
names
|
212
294
|
end
|
213
|
-
names
|
214
295
|
end
|
215
296
|
|
216
297
|
# Returns the data object that stores values for +name+.
|
@@ -240,18 +321,18 @@ module BinData
|
|
240
321
|
offset
|
241
322
|
end
|
242
323
|
|
243
|
-
# Override to include field names.
|
324
|
+
# Override to include field names and delegate methods.
|
244
325
|
alias_method :orig_respond_to?, :respond_to?
|
245
326
|
def respond_to?(symbol, include_private = false)
|
246
327
|
orig_respond_to?(symbol, include_private) ||
|
247
|
-
field_names(true).include?(symbol.id2name.chomp("="))
|
328
|
+
field_names(true).include?(symbol.id2name.chomp("=")) ||
|
329
|
+
delegate_object.respond_to?(symbol, include_private)
|
248
330
|
end
|
249
331
|
|
250
332
|
# Returns whether this data object contains a single value. Single
|
251
333
|
# value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
|
252
334
|
def single_value?
|
253
|
-
|
254
|
-
orig_respond_to?(:value)
|
335
|
+
delegate_object ? delegate_object.single_value? : false
|
255
336
|
end
|
256
337
|
|
257
338
|
def method_missing(symbol, *args)
|
@@ -270,6 +351,8 @@ module BinData
|
|
270
351
|
else
|
271
352
|
obj
|
272
353
|
end
|
354
|
+
elsif delegate_object.respond_to?(symbol)
|
355
|
+
delegate_object.__send__(symbol, *args)
|
273
356
|
else
|
274
357
|
super
|
275
358
|
end
|
@@ -278,6 +361,15 @@ module BinData
|
|
278
361
|
#---------------
|
279
362
|
private
|
280
363
|
|
364
|
+
# Returns the delegate object if any.
|
365
|
+
def delegate_object
|
366
|
+
if (name = param(:delegate))
|
367
|
+
find_obj_for_name(name)
|
368
|
+
else
|
369
|
+
nil
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
281
373
|
# Returns a list of all the bindata objects for this struct.
|
282
374
|
def bindata_objects
|
283
375
|
@fields.collect { |f| f[1] }
|
@@ -308,6 +400,12 @@ module BinData
|
|
308
400
|
end
|
309
401
|
new_params[:hide] = hide
|
310
402
|
|
403
|
+
# collect delegate name if it corresponds to a field name
|
404
|
+
if (delegate = (new_params[:delegate] || self.class.delegate))
|
405
|
+
delegate = delegate.to_s
|
406
|
+
new_params[:delegate] = delegate if field_names.include?(delegate)
|
407
|
+
end
|
408
|
+
|
311
409
|
new_params
|
312
410
|
end
|
313
411
|
end
|
data/spec/array_spec.rb
CHANGED
@@ -33,6 +33,10 @@ describe "An Array with no elements" do
|
|
33
33
|
@data.length.should be_zero
|
34
34
|
end
|
35
35
|
|
36
|
+
it "should be empty" do
|
37
|
+
@data.should be_empty
|
38
|
+
end
|
39
|
+
|
36
40
|
it "should return nil for the first element" do
|
37
41
|
@data.first.should be_nil
|
38
42
|
end
|
@@ -66,22 +70,30 @@ describe "An Array with several elements" do
|
|
66
70
|
@data.snapshot.should eql([1, 2, 3, 4, 5])
|
67
71
|
end
|
68
72
|
|
73
|
+
it "should coerce to ::Array if required" do
|
74
|
+
((1..7).to_a - @data).should eql([6, 7])
|
75
|
+
end
|
76
|
+
|
69
77
|
it "should return the first element" do
|
70
78
|
@data.first.should eql(1)
|
71
79
|
end
|
72
80
|
|
73
81
|
it "should return the first n elements" do
|
82
|
+
@data[0...3].should eql([1, 2, 3])
|
74
83
|
@data.first(3).should eql([1, 2, 3])
|
75
84
|
@data.first(99).should eql([1, 2, 3, 4, 5])
|
76
85
|
end
|
77
86
|
|
78
87
|
it "should return the last element" do
|
79
88
|
@data.last.should eql(5)
|
89
|
+
@data[-1].should eql(5)
|
80
90
|
end
|
81
91
|
|
82
92
|
it "should return the last n elements" do
|
83
93
|
@data.last(3).should eql([3, 4, 5])
|
84
94
|
@data.last(99).should eql([1, 2, 3, 4, 5])
|
95
|
+
|
96
|
+
@data[-3, 100].should eql([3, 4, 5])
|
85
97
|
end
|
86
98
|
|
87
99
|
it "should have correct num elements" do
|
@@ -106,6 +118,14 @@ describe "An Array with several elements" do
|
|
106
118
|
@data[1].should eql(8)
|
107
119
|
end
|
108
120
|
|
121
|
+
it "should not be empty" do
|
122
|
+
@data.should_not be_empty
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should return a nicely formatted array for inspect" do
|
126
|
+
@data.inspect.should eql("[1, 2, 3, 4, 5]")
|
127
|
+
end
|
128
|
+
|
109
129
|
it "should be able to use methods from Enumerable" do
|
110
130
|
@data.select { |x| (x % 2) == 0 }.should eql([2, 4])
|
111
131
|
end
|
@@ -170,6 +190,10 @@ describe "An Array containing structs" do
|
|
170
190
|
@data[3].a.should eql(3)
|
171
191
|
end
|
172
192
|
|
193
|
+
it "should access multiple elements with slice" do
|
194
|
+
@data.slice(2, 3).collect { |x| x.a }.should eql([2, 3, 4])
|
195
|
+
end
|
196
|
+
|
173
197
|
it "should not be able to modify elements" do
|
174
198
|
lambda { @data[1] = 3 }.should raise_error(NoMethodError)
|
175
199
|
end
|
data/spec/base_spec.rb
CHANGED
@@ -93,6 +93,14 @@ describe "A data object with parameters" do
|
|
93
93
|
obj.param(:p2).should be_nil
|
94
94
|
obj.param(:p3).should respond_to(:arity)
|
95
95
|
end
|
96
|
+
|
97
|
+
it "should identify unsupplied parameters" do
|
98
|
+
obj = WithParam.new(:p1 => 1, :p3 => 3, :p4 => 4, :p5 => 5)
|
99
|
+
obj.unsupplied_parameters.should include(:p2)
|
100
|
+
obj.unsupplied_parameters.should_not include(:p1)
|
101
|
+
obj.unsupplied_parameters.should_not include(:p3)
|
102
|
+
obj.unsupplied_parameters.should_not include(:p4)
|
103
|
+
end
|
96
104
|
end
|
97
105
|
|
98
106
|
describe "A data object with :check_offset" do
|
@@ -192,3 +200,24 @@ describe "A data object defining a value method" do
|
|
192
200
|
obj.should be_a_single_value
|
193
201
|
end
|
194
202
|
end
|
203
|
+
|
204
|
+
describe "A subclass of Base" do
|
205
|
+
before(:all) do
|
206
|
+
eval <<-END
|
207
|
+
class SubClassOfBase < BinData::Base
|
208
|
+
public :_do_read, :_write, :_num_bytes
|
209
|
+
end
|
210
|
+
END
|
211
|
+
@obj = SubClassOfBase.new
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should raise errors on unimplemented methods" do
|
215
|
+
lambda { @obj.clear }.should raise_error(NotImplementedError)
|
216
|
+
lambda { @obj.done_read }.should raise_error(NotImplementedError)
|
217
|
+
lambda { @obj.snapshot }.should raise_error(NotImplementedError)
|
218
|
+
lambda { @obj.field_names }.should raise_error(NotImplementedError)
|
219
|
+
lambda { @obj._do_read(nil) }.should raise_error(NotImplementedError)
|
220
|
+
lambda { @obj._write(nil) }.should raise_error(NotImplementedError)
|
221
|
+
lambda { @obj._num_bytes }.should raise_error(NotImplementedError)
|
222
|
+
end
|
223
|
+
end
|
data/spec/string_spec.rb
CHANGED
@@ -9,13 +9,8 @@ describe "Test mutual exclusion of parameters" do
|
|
9
9
|
lambda { BinData::String.new(params) }.should raise_error(ArgumentError)
|
10
10
|
end
|
11
11
|
|
12
|
-
it ":length and :
|
13
|
-
params = {:length => 5, :
|
14
|
-
lambda { BinData::String.new(params) }.should raise_error(ArgumentError)
|
15
|
-
end
|
16
|
-
|
17
|
-
it ":initial_value and :initial_length" do
|
18
|
-
params = {:initial_value => "", :initial_length => 5}
|
12
|
+
it ":length and :read_length" do
|
13
|
+
params = {:length => 5, :read_length => 5}
|
19
14
|
lambda { BinData::String.new(params) }.should raise_error(ArgumentError)
|
20
15
|
end
|
21
16
|
|
@@ -25,35 +20,38 @@ describe "Test mutual exclusion of parameters" do
|
|
25
20
|
end
|
26
21
|
end
|
27
22
|
|
28
|
-
describe "A String with
|
29
|
-
|
30
|
-
|
23
|
+
describe "A String with deprecated parameters" do
|
24
|
+
it "should substitude :read_length for :initial_length" do
|
25
|
+
obj = BinData::String.new(:initial_length => 3)
|
26
|
+
io = StringIO.new("abcdefghij")
|
27
|
+
obj.read(io)
|
28
|
+
obj.value.should eql("abc")
|
31
29
|
end
|
30
|
+
end
|
32
31
|
|
33
|
-
|
34
|
-
|
32
|
+
describe "A String with :read_length" do
|
33
|
+
before(:each) do
|
34
|
+
@str = BinData::String.new(:read_length => 5)
|
35
35
|
end
|
36
36
|
|
37
|
-
it "should
|
38
|
-
@str.
|
37
|
+
it "should have default value" do
|
38
|
+
@str.num_bytes.should eql(0)
|
39
|
+
@str.value.should eql("")
|
39
40
|
end
|
40
41
|
|
41
|
-
it "should read :
|
42
|
+
it "should read :read_length bytes" do
|
42
43
|
io = StringIO.new("abcdefghij")
|
43
44
|
@str.read(io)
|
44
45
|
@str.value.should eql("abcde")
|
45
46
|
end
|
46
47
|
|
47
|
-
it "should
|
48
|
-
@str.value = "abc"
|
49
|
-
@str.num_bytes.should eql(3)
|
50
|
-
end
|
51
|
-
|
52
|
-
it "should remember :initial_length after value is cleared" do
|
48
|
+
it "should remember :read_length after value is cleared" do
|
53
49
|
@str.value = "abc"
|
54
50
|
@str.num_bytes.should eql(3)
|
55
51
|
@str.clear
|
56
|
-
|
52
|
+
io = StringIO.new("abcdefghij")
|
53
|
+
@str.read(io)
|
54
|
+
@str.value.should eql("abcde")
|
57
55
|
end
|
58
56
|
end
|
59
57
|
|
@@ -97,23 +95,48 @@ describe "A String with :length" do
|
|
97
95
|
end
|
98
96
|
end
|
99
97
|
|
100
|
-
describe "A String with :
|
98
|
+
describe "A String with :read_length and :initial_value" do
|
101
99
|
before(:each) do
|
102
|
-
@str = BinData::String.new(:
|
100
|
+
@str = BinData::String.new(:read_length => 5, :initial_value => "abcdefghij")
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should use :initial_value before value is read" do
|
104
|
+
@str.num_bytes.should eql(10)
|
105
|
+
@str.value.should eql("abcdefghij")
|
103
106
|
end
|
104
107
|
|
105
|
-
it "should use :
|
108
|
+
it "should use :read_length for reading" do
|
109
|
+
io = StringIO.new("ABCDEFGHIJKLMNOPQRST")
|
110
|
+
@str.read(io)
|
111
|
+
io.pos.should eql(5)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should forget :initial_value after reading" do
|
115
|
+
io = StringIO.new("ABCDEFGHIJKLMNOPQRST")
|
116
|
+
@str.read(io)
|
106
117
|
@str.num_bytes.should eql(5)
|
107
|
-
@str.value.should eql("
|
118
|
+
@str.value.should eql("ABCDE")
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "A String with :read_length and :value" do
|
124
|
+
before(:each) do
|
125
|
+
@str = BinData::String.new(:read_length => 5, :value => "abcdefghij")
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should not be affected by :read_length before value is read" do
|
129
|
+
@str.num_bytes.should eql(10)
|
130
|
+
@str.value.should eql("abcdefghij")
|
108
131
|
end
|
109
132
|
|
110
|
-
it "should use :
|
133
|
+
it "should use :read_length for reading" do
|
111
134
|
io = StringIO.new("ABCDEFGHIJKLMNOPQRST")
|
112
135
|
@str.read(io)
|
113
136
|
io.pos.should eql(5)
|
114
137
|
end
|
115
138
|
|
116
|
-
it "should
|
139
|
+
it "should not be affected by :read_length after reading" do
|
117
140
|
io = StringIO.new("ABCDEFGHIJKLMNOPQRST")
|
118
141
|
@str.read(io)
|
119
142
|
@str.num_bytes.should eql(10)
|
data/spec/struct_spec.rb
CHANGED
@@ -6,7 +6,7 @@ require 'bindata'
|
|
6
6
|
describe "A Struct with hidden fields" do
|
7
7
|
before(:all) do
|
8
8
|
eval <<-END
|
9
|
-
class
|
9
|
+
class HiddenStruct < BinData::Struct
|
10
10
|
hide :b, 'c'
|
11
11
|
int8 :a
|
12
12
|
int8 'b', :initial_value => 10
|
@@ -14,7 +14,7 @@ describe "A Struct with hidden fields" do
|
|
14
14
|
int8 :d, :value => :b
|
15
15
|
end
|
16
16
|
END
|
17
|
-
@obj =
|
17
|
+
@obj = HiddenStruct.new
|
18
18
|
end
|
19
19
|
|
20
20
|
it "should only show fields that aren't hidden" do
|
@@ -35,7 +35,88 @@ describe "A Struct with hidden fields" do
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
describe "A Struct that delegates" do
|
39
|
+
before(:all) do
|
40
|
+
eval <<-END
|
41
|
+
class DelegateStruct < BinData::Struct
|
42
|
+
delegate :b
|
43
|
+
int8 :a, :initial_value => :num
|
44
|
+
int8 'b'
|
45
|
+
int8 :c, :value => :b
|
46
|
+
end
|
47
|
+
END
|
48
|
+
@obj = DelegateStruct.new(:num => 5)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should access custom parameters" do
|
52
|
+
@obj.a.should eql(5)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should have correct num_bytes" do
|
56
|
+
@obj.num_bytes.should eql(3)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should delegate snapshot" do
|
60
|
+
@obj.value = 6
|
61
|
+
@obj.snapshot.should eql(6)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should delegate single_value?" do
|
65
|
+
@obj.should be_a_single_value
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should delegate methods" do
|
69
|
+
@obj.should respond_to?(:value)
|
70
|
+
@obj.value = 7
|
71
|
+
@obj.c.should eql(7)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should identify unsupplied parameters" do
|
75
|
+
@obj.unsupplied_parameters.should include(:check_value)
|
76
|
+
@obj.unsupplied_parameters.should include(:initial_value)
|
77
|
+
@obj.unsupplied_parameters.should include(:value)
|
78
|
+
@obj.unsupplied_parameters.should_not include(:endian)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should pass params when creating" do
|
82
|
+
obj = DelegateStruct.new(:initial_value => :val, :val => 14)
|
83
|
+
obj.value.should eql(14)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "A Struct with nested delegation" do
|
88
|
+
before(:all) do
|
89
|
+
eval <<-END
|
90
|
+
class DelegateOuterStruct < BinData::Struct
|
91
|
+
endian :little
|
92
|
+
delegate :b
|
93
|
+
int8 :a
|
94
|
+
struct :b, :delegate => :y,
|
95
|
+
:fields => [[:int8, :x], [:int32, :y], [:int8, :z]]
|
96
|
+
end
|
97
|
+
END
|
98
|
+
@obj = DelegateOuterStruct.new(:initial_value => 7)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should followed nested delegation" do
|
102
|
+
@obj.should be_a_single_value
|
103
|
+
@obj.field_names.should eql([])
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should forward parameters" do
|
107
|
+
@obj.should respond_to?(:value)
|
108
|
+
@obj.value.should eql(7)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should identify unsupplied parameters" do
|
112
|
+
@obj.unsupplied_parameters.should include(:check_value)
|
113
|
+
@obj.unsupplied_parameters.should include(:value)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
38
117
|
describe "Defining a Struct" do
|
118
|
+
before(:all) do
|
119
|
+
end
|
39
120
|
it "should fail on non registered types" do
|
40
121
|
lambda {
|
41
122
|
eval <<-END
|
@@ -44,6 +125,15 @@ describe "Defining a Struct" do
|
|
44
125
|
end
|
45
126
|
END
|
46
127
|
}.should raise_error(TypeError)
|
128
|
+
|
129
|
+
lambda {
|
130
|
+
BinData::Struct.new(:fields => [[:non_registered_type, :a]])
|
131
|
+
}.should raise_error(TypeError)
|
132
|
+
|
133
|
+
lambda {
|
134
|
+
BinData::Struct.new(:delegate => :a,
|
135
|
+
:fields => [[:non_registered_type, :a]])
|
136
|
+
}.should raise_error(TypeError)
|
47
137
|
end
|
48
138
|
|
49
139
|
it "should fail on duplicate names" do
|
@@ -58,6 +148,30 @@ describe "Defining a Struct" do
|
|
58
148
|
}.should raise_error(SyntaxError)
|
59
149
|
end
|
60
150
|
|
151
|
+
it "should fail on reserved names" do
|
152
|
+
lambda {
|
153
|
+
eval <<-END
|
154
|
+
class ReservedName < BinData::Struct
|
155
|
+
int8 :a
|
156
|
+
int8 :invert # from Hash.instance_methods
|
157
|
+
end
|
158
|
+
END
|
159
|
+
}.should raise_error(NameError)
|
160
|
+
|
161
|
+
lambda {
|
162
|
+
# :invert is from Hash.instance_methods
|
163
|
+
BinData::Struct.new(:fields => [[:int8, :a], [:int8, :invert]])
|
164
|
+
}.should raise_error(NameError)
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should fail on reserved names of delegated fields" do
|
168
|
+
lambda {
|
169
|
+
# :value is from Int8.instance_methods
|
170
|
+
BinData::Struct.new(:delegate => :a,
|
171
|
+
:fields => [[:int8, :a], [:int8, :value]])
|
172
|
+
}.should raise_error(NameError)
|
173
|
+
end
|
174
|
+
|
61
175
|
it "should fail when field name shadows an existing method" do
|
62
176
|
lambda {
|
63
177
|
eval <<-END
|
@@ -97,6 +211,11 @@ describe "A Struct with multiple fields" do
|
|
97
211
|
@obj.num_bytes.should eql(2)
|
98
212
|
end
|
99
213
|
|
214
|
+
it "should identify unsupplied parameters" do
|
215
|
+
@obj.unsupplied_parameters.should include(:delegate)
|
216
|
+
@obj.unsupplied_parameters.should include(:endian)
|
217
|
+
end
|
218
|
+
|
100
219
|
it "should clear" do
|
101
220
|
@obj.a = 6
|
102
221
|
@obj.clear
|
@@ -143,34 +262,6 @@ describe "A Struct with multiple fields" do
|
|
143
262
|
end
|
144
263
|
end
|
145
264
|
|
146
|
-
describe "A Struct with a value method" do
|
147
|
-
before(:all) do
|
148
|
-
eval <<-END
|
149
|
-
class StructWithValue < BinData::Struct
|
150
|
-
int8 :a
|
151
|
-
int8 :b
|
152
|
-
|
153
|
-
def value
|
154
|
-
a
|
155
|
-
end
|
156
|
-
end
|
157
|
-
END
|
158
|
-
@obj = StructWithValue.new
|
159
|
-
end
|
160
|
-
|
161
|
-
it "should be single value object" do
|
162
|
-
@obj.should be_a_single_value
|
163
|
-
end
|
164
|
-
|
165
|
-
it "should have no field names" do
|
166
|
-
@obj.field_names.should be_empty
|
167
|
-
end
|
168
|
-
|
169
|
-
it "should not respond to field accesses" do
|
170
|
-
@obj.should_not respond_to?(:a)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
265
|
describe "A Struct with nested structs" do
|
175
266
|
before(:all) do
|
176
267
|
eval <<-END
|
@@ -222,6 +313,7 @@ describe "A Struct with an endian defined" do
|
|
222
313
|
array :c, :type => :int8, :initial_length => 2
|
223
314
|
choice :d, :choices => [ [:uint16], [:uint32] ], :selection => 1
|
224
315
|
struct :e, :fields => [ [:uint16, :f], [:uint32be, :g] ]
|
316
|
+
struct :h, :fields => [ [:struct, :i, {:fields => [[:uint16, :j]]}] ]
|
225
317
|
end
|
226
318
|
END
|
227
319
|
@obj = StructWithEndian.new
|
@@ -235,8 +327,9 @@ describe "A Struct with an endian defined" do
|
|
235
327
|
@obj.d = 5
|
236
328
|
@obj.e.f = 6
|
237
329
|
@obj.e.g = 7
|
330
|
+
@obj.h.i.j = 8
|
238
331
|
|
239
|
-
expected = [1, 2.0, 3, 4, 5, 6, 7].pack('
|
332
|
+
expected = [1, 2.0, 3, 4, 5, 6, 7, 8].pack('veCCVvNv')
|
240
333
|
|
241
334
|
io = StringIO.new
|
242
335
|
@obj.write(io)
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: bindata
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.8.0
|
7
|
+
date: 2007-10-14 00:00:00 +08:00
|
8
8
|
summary: A declarative way to read and write binary file formats
|
9
9
|
require_paths:
|
10
10
|
- lib
|