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 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, :initial_length => :len
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, :initial_length => :len
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, :initial_length => :len
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>:initial_length => :len</tt> is syntactic sugar for
133
- <tt>:initial_length => lambda { len }</tt>, but more on that later.
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 }
@@ -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, :initial_length => :len
14
+ string :data, :read_length => :len
15
15
  end
16
16
 
17
17
  class Header < BinData::Struct
18
- uint16le :id, :value => 0x8b1f, :check_value => 0x8b1f
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
@@ -10,5 +10,5 @@ require 'bindata/stringz'
10
10
  require 'bindata/struct'
11
11
 
12
12
  module BinData
13
- VERSION = "0.7.0"
13
+ VERSION = "0.8.0"
14
14
  end
@@ -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
- obj = elements[index]
175
- obj.single_value? ? obj.value : obj
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
 
@@ -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 mark; #{io.pos}; end"
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.mark
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
- # To be implemented by subclasses
285
- =end
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
@@ -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>:initial_length</tt>:: The initial length to use before a value is
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 :initial_length, :length, :trim_value
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
- # :initial_length and :value
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(:initial_length, :length)
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 = val_num_bytes(val)
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
- readbytes(io, val_num_bytes(""))
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
@@ -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>:: An array specifying the fields for this struct. Each
31
- # element of the array is of the form
32
- # [type, name, params]. Type is a symbol representing
33
- # a registered type. Name is the name of this field.
34
- # Name may be nil as in the example above. Params is an
35
- # optional hash of parameters to pass to this field when
36
- # instantiating it.
37
- # <tt>:hide</tt>:: A list of the names of fields that are to be hidden
38
- # from the outside world. Hidden fields don't appear
39
- # in #snapshot or #field_names but are still accessible
40
- # by name.
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
- optional_parameter :endian, :hide
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 methods.include?(name)
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
- # allow structs to fake single value
189
- return value if single_value?
190
-
191
- hash = Snapshot.new
192
- field_names.each do |name|
193
- hash[name] = find_obj_for_name(name).snapshot
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
- # single values don't have any fields
203
- return [] if single_value?
204
-
205
- names = []
206
- @fields.each do |name, obj|
207
- if name != ""
208
- names << name unless (param(:hide).include?(name) and !include_hidden)
209
- else
210
- names.concat(obj.field_names)
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
- # need to use original respond_to? to prevent infinite recursion
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
@@ -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
@@ -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
@@ -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 :initial_length" do
13
- params = {:length => 5, :initial_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 :initial_length" do
29
- before(:each) do
30
- @str = BinData::String.new(:initial_length => 5)
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
- it "should set num_bytes" do
34
- @str.num_bytes.should eql(5)
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 fill value with pad_char" do
38
- @str.value.should eql("\0\0\0\0\0")
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 :initial_length bytes" do
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 forget :initial_length after value is set" do
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
- @str.num_bytes.should eql(5)
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 :initial_length and :value" do
98
+ describe "A String with :read_length and :initial_value" do
101
99
  before(:each) do
102
- @str = BinData::String.new(:initial_length => 5, :value => "abcdefghij")
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 :initial_length before value is read" do
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("abcde")
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 :initial_length for reading" do
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 forget :initial_length after reading" do
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)
@@ -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 TestStruct < BinData::Struct
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 = TestStruct.new
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('veCCVvN')
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.0
7
- date: 2007-08-27 00:00:00 +08:00
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