bindata 0.6.0 → 0.7.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,12 @@
1
1
  = BinData Changelog
2
2
 
3
+ == Version 0.7.0 (2007-08-26)
4
+
5
+ * Arrays now support terminating conditions as well as fixed length reads.
6
+ * Updated specs to new rspec syntax (0.9).
7
+ * Added scoped resolution of variables in lambdas.
8
+ * Added ability to append elements to arrays.
9
+
3
10
  == Version 0.6.0 (2007-03-28)
4
11
 
5
12
  * Added 64 bit integers.
data/README CHANGED
@@ -157,12 +157,16 @@ BinData::Int16le:: Signed 16 bit integer (little endian).
157
157
  BinData::Int16be:: Signed 16 bit integer (big endian).
158
158
  BinData::Int32le:: Signed 32 bit integer (little endian).
159
159
  BinData::Int32be:: Signed 32 bit integer (big endian).
160
+ BinData::Int64le:: Signed 64 bit integer (little endian).
161
+ BinData::Int64be:: Signed 64 bit integer (big endian).
160
162
 
161
163
  BinData::Uint8:: Unsigned 8 bit integer.
162
164
  BinData::Uint16le:: Unsigned 16 bit integer (little endian).
163
165
  BinData::Uint16be:: Unsigned 16 bit integer (big endian).
164
166
  BinData::Uint32le:: Unsigned 32 bit integer (little endian).
165
167
  BinData::Uint32be:: Unsigned 32 bit integer (big endian).
168
+ BinData::Uint64le:: Unsigned 64 bit integer (little endian).
169
+ BinData::Uint64be:: Unsigned 64 bit integer (big endian).
166
170
 
167
171
  BinData::FloatLe:: Single precision floating point number (little endian).
168
172
  BinData::FloatBe:: Single precision floating point number (big endian).
data/TODO CHANGED
@@ -1,8 +1,3 @@
1
- * Add a way to do something like: read a bunch of integers and stop reading
2
- after reading an integer with a value of 0.
3
-
4
- * Add scoping so the value of a param doesn't need to call parent
5
-
6
1
  * Think how offset_of should work.
7
2
 
8
3
  * Need optional parameter for fields
@@ -10,5 +10,5 @@ require 'bindata/stringz'
10
10
  require 'bindata/struct'
11
11
 
12
12
  module BinData
13
- VERSION = "0.6.0"
13
+ VERSION = "0.7.0"
14
14
  end
@@ -21,6 +21,15 @@ module BinData
21
21
  # passed to it, then it should be provided as
22
22
  # <tt>[type_symbol, hash_params]</tt>.
23
23
  # <tt>:initial_length</tt>:: The initial length of the array.
24
+ # <tt>:read_until</tt>:: While reading, elements are read until this
25
+ # condition is true. This is typically used to
26
+ # read an array until a sentinel value is found.
27
+ # The variables +index+, +element+ and +array+
28
+ # are made available to any lambda assigned to
29
+ # this parameter.
30
+ #
31
+ # Each data object in an array has the variable +index+ made available
32
+ # to any lambda evaluated as a parameter of that data object.
24
33
  class Array < Base
25
34
  include Enumerable
26
35
 
@@ -28,11 +37,13 @@ module BinData
28
37
  register(self.name, self)
29
38
 
30
39
  # These are the parameters used by this class.
31
- mandatory_parameters :type, :initial_length
40
+ mandatory_parameter :type
41
+ optional_parameters :initial_length, :read_until
32
42
 
33
43
  # Creates a new Array
34
44
  def initialize(params = {}, env = nil)
35
- super(params, env)
45
+ super(cleaned_params(params), env)
46
+ ensure_mutual_exclusion(:initial_length, :read_until)
36
47
 
37
48
  type, el_params = param(:type)
38
49
  klass = klass_lookup(type)
@@ -41,8 +52,6 @@ module BinData
41
52
  @element_list = nil
42
53
  @element_klass = klass
43
54
  @element_params = el_params || {}
44
-
45
- # TODO: how to increase the size of the array?
46
55
  end
47
56
 
48
57
  # Clears the element at position +index+. If +index+ is not given, then
@@ -73,7 +82,19 @@ module BinData
73
82
 
74
83
  # Reads the values for all fields in this object from +io+.
75
84
  def _do_read(io)
76
- elements.each { |f| f.do_read(io) }
85
+ if has_param?(:initial_length)
86
+ elements.each { |f| f.do_read(io) }
87
+ else # :read_until
88
+ @element_list = nil
89
+ loop do
90
+ element = append_new_element
91
+ element.do_read(io)
92
+ variables = { :index => self.length - 1, :element => self.last,
93
+ :array => self }
94
+ finished = eval_param(:read_until, variables)
95
+ break if finished
96
+ end
97
+ end
77
98
  end
78
99
 
79
100
  # To be called after calling #do_read.
@@ -107,6 +128,46 @@ module BinData
107
128
  []
108
129
  end
109
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
+ # Appends a new element to the end of the array. If the array contains
163
+ # single_values then the +value+ may be provided to the call.
164
+ # Returns the appended object, or value in the case of single_values.
165
+ def append(value = nil)
166
+ append_new_element
167
+ self[self.length - 1] = value unless value.nil?
168
+ self.last
169
+ end
170
+
110
171
  # Returns the element at +index+. If the element is a single_value
111
172
  # then the value of the element is returned instead.
112
173
  def [](index)
@@ -146,15 +207,39 @@ module BinData
146
207
  def elements
147
208
  if @element_list.nil?
148
209
  @element_list = []
149
-
150
- # create the desired number of instances
151
- eval_param(:initial_length).times do |i|
152
- env = create_env
153
- env.index = i
154
- @element_list << @element_klass.new(@element_params, env)
210
+ if has_param?(:initial_length)
211
+ # create the desired number of instances
212
+ eval_param(:initial_length).times do
213
+ append_new_element
214
+ end
155
215
  end
156
216
  end
157
217
  @element_list
158
218
  end
219
+
220
+ # Creates a new element and appends it to the end of @element_list.
221
+ # Returns the newly created element
222
+ def append_new_element
223
+ # ensure @element_list is initialised
224
+ elements()
225
+
226
+ env = create_env
227
+ env.add_variable(:index, @element_list.length)
228
+ element = @element_klass.new(@element_params, env)
229
+ @element_list << element
230
+ element
231
+ end
232
+
233
+ # Returns a hash of cleaned +params+. Cleaning means that param
234
+ # values are converted to a desired format.
235
+ def cleaned_params(params)
236
+ unless params.has_key?(:initial_length) or params.has_key?(:read_until)
237
+ # ensure one of :initial_length and :read_until exists
238
+ new_params = params.dup
239
+ new_params[:initial_length] = 0
240
+ params = new_params
241
+ end
242
+ params
243
+ end
159
244
  end
160
245
  end
@@ -14,11 +14,13 @@ module BinData
14
14
  #
15
15
  # [<tt>:readwrite</tt>] If false, calls to #read or #write will
16
16
  # not perform any I/O. Default is true.
17
- # [<tt>:check_offset</tt>] Raise an error if the current IO offest doesn't
17
+ # [<tt>:check_offset</tt>] Raise an error if the current IO offset doesn't
18
18
  # meet this criteria. A boolean return indicates
19
19
  # success or failure. Any other return is compared
20
- # to the current offset. This parameter is
21
- # only checked before reading.
20
+ # to the current offset. The variable +offset+
21
+ # is made available to any lambda assigned to
22
+ # this parameter. This parameter is only checked
23
+ # before reading.
22
24
  class Base
23
25
  class << self
24
26
  # Returns the mandatory parameters used by this class. Any given args
@@ -33,7 +35,7 @@ module BinData
33
35
  end
34
36
  end
35
37
  end
36
- unless (args.empty?)
38
+ if not args.empty?
37
39
  args.each { |arg| @mandatory_parameters << arg.to_sym }
38
40
  @mandatory_parameters.uniq!
39
41
  end
@@ -53,7 +55,7 @@ module BinData
53
55
  end
54
56
  end
55
57
  end
56
- unless (args.empty?)
58
+ if not args.empty?
57
59
  args.each { |arg| @optional_parameters << arg.to_sym }
58
60
  @optional_parameters.uniq!
59
61
  end
@@ -111,14 +113,14 @@ module BinData
111
113
  # environment that these callable objects are evaluated in.
112
114
  def initialize(params = {}, env = nil)
113
115
  # default :readwrite param to true if unspecified
114
- unless params.has_key?(:readwrite)
116
+ if not params.has_key?(:readwrite)
115
117
  params = params.dup
116
118
  params[:readwrite] = true
117
119
  end
118
120
 
119
121
  # ensure mandatory parameters exist
120
122
  self.class.mandatory_parameters.each do |prm|
121
- unless params.has_key?(prm)
123
+ if not params.has_key?(prm)
122
124
  raise ArgumentError, "parameter ':#{prm}' must be specified " +
123
125
  "in #{self}"
124
126
  end
@@ -197,9 +199,10 @@ module BinData
197
199
 
198
200
  # Returns the value of the evaluated parameter. +key+ references a
199
201
  # parameter from the +params+ hash used when creating the data object.
202
+ # +values+ contains data that may be accessed when evaluating +key+.
200
203
  # Returns nil if +key+ does not refer to any parameter.
201
- def eval_param(key)
202
- @env.lazy_eval(@params[key])
204
+ def eval_param(key, values = {})
205
+ @env.lazy_eval(@params[key], values)
203
206
  end
204
207
 
205
208
  # Returns the parameter from the +params+ hash referenced by +key+.
@@ -227,13 +230,13 @@ module BinData
227
230
  # be called from #do_read before performing the reading.
228
231
  def check_offset(io)
229
232
  if has_param?(:check_offset)
230
- @env.offset = io.pos - io.mark
231
- expected = eval_param(:check_offset)
233
+ actual_offset = io.pos - io.mark
234
+ expected = eval_param(:check_offset, :offset => actual_offset)
232
235
 
233
236
  if not expected
234
237
  raise ValidityError, "offset not as expected"
235
- elsif @env.offset != expected and expected != true
236
- raise ValidityError, "offset is '#{@env.offset}' but " +
238
+ elsif actual_offset != expected and expected != true
239
+ raise ValidityError, "offset is '#{actual_offset}' but " +
237
240
  "expected '#{expected}'"
238
241
  end
239
242
  end
@@ -7,11 +7,8 @@ module BinData
7
7
  # params:: any extra parameters that have been passed to the data object.
8
8
  # The value of a parameter is either a lambda, a symbol or a
9
9
  # literal value (such as a Fixnum).
10
- # value:: the value of the data object if it is single
11
- # index:: the index of the data object if it is in an array
12
- # offset:: the current offset of the IO object when reading
13
10
  #
14
- # Unknown methods are resolved in the context of the parent data object,
11
+ # Unknown methods are resolved in the context of the parent environment,
15
12
  # first as keys in the extra parameters, and secondly as methods in the
16
13
  # parent data object. This makes the lambda easier to read as we just write
17
14
  # <tt>field</tt> instead of <tt>obj.field</tt>.
@@ -20,21 +17,27 @@ module BinData
20
17
  # parent data object.
21
18
  def initialize(parent = nil)
22
19
  @parent = parent
20
+ @variables = {}
21
+ @overrides = {}
23
22
  end
24
23
  attr_reader :parent
25
- attr_accessor :data_object, :params, :index, :offset
24
+ attr_accessor :data_object, :params
26
25
 
27
26
  # only accessible by another LazyEvalEnv
28
27
  protected :data_object
29
28
 
29
+ # Add a variable with a pre-assigned value to this environment. +sym+
30
+ # will be accessible as a variable for any lambda evaluated
31
+ # with #lazy_eval.
32
+ def add_variable(sym, value)
33
+ @variables[sym.to_sym] = value
34
+ end
35
+
30
36
  # TODO: offset_of needs to be better thought out
31
37
  def offset_of(sym)
32
- if @parent and @parent.data_object and
33
- @parent.data_object.respond_to?(:offset_of)
34
- @parent.data_object.offset_of(sym)
35
- else
36
- nil
37
- end
38
+ @parent.data_object.offset_of(sym)
39
+ rescue
40
+ nil
38
41
  end
39
42
 
40
43
  # Returns the data_object for the parent environment.
@@ -42,32 +45,33 @@ module BinData
42
45
  @parent.nil? ? nil : @parent.data_object
43
46
  end
44
47
 
45
- # Returns the value of the data object wrapped by this environment.
46
- def value
47
- @data_object.respond_to?(:value) ? @data_object.value : nil
48
- end
49
-
50
48
  # Evaluates +obj+ in the context of this environment. Evaluation
51
49
  # recurses until it yields a value that is not a symbol or lambda.
52
- def lazy_eval(obj)
50
+ def lazy_eval(obj, overrides = {})
51
+ @overrides = overrides
53
52
  if obj.is_a? Symbol
54
53
  # treat :foo as lambda { foo }
55
- lazy_eval(__send__(obj))
54
+ obj = __send__(obj)
56
55
  elsif obj.respond_to? :arity
57
- instance_eval(&obj)
58
- else
59
- obj
56
+ obj = instance_eval(&obj)
60
57
  end
58
+ @overrides = {}
59
+ obj
61
60
  end
62
61
 
63
62
  def method_missing(symbol, *args)
64
- if @parent and @parent.params and @parent.params.has_key?(symbol)
65
- # is there a param with this name?
66
- @parent.lazy_eval(@parent.params[symbol])
67
- elsif @parent and @parent.data_object and
68
- @parent.data_object.respond_to?(symbol)
69
- # how about a field or method in the parent?
70
- @parent.data_object.__send__(symbol, *args)
63
+ if @overrides.include?(symbol)
64
+ @overrides[symbol]
65
+ elsif @variables.include?(symbol)
66
+ @variables[symbol]
67
+ elsif @parent
68
+ obj = symbol
69
+ if @parent.params and @parent.params.has_key?(symbol)
70
+ obj = @parent.params[symbol]
71
+ elsif @parent.data_object and @parent.data_object.respond_to?(symbol)
72
+ obj = @parent.data_object.__send__(symbol, *args)
73
+ end
74
+ @parent.lazy_eval(obj)
71
75
  else
72
76
  super
73
77
  end
@@ -20,9 +20,11 @@ module BinData
20
20
  # will return the value of the data read from the
21
21
  # IO, not the result of the <tt>:value</tt> param.
22
22
  # [<tt>:check_value</tt>] Raise an error unless the value read in meets
23
- # this criteria. A boolean return indicates
24
- # success or failure. Any other return is compared
25
- # to the value just read in.
23
+ # this criteria. The variable +value+ is made
24
+ # available to any lambda assigned to this
25
+ # parameter. A boolean return indicates success
26
+ # or failure. Any other return is compared to
27
+ # the value just read in.
26
28
  class Single < Base
27
29
  # These are the parameters used by this class.
28
30
  optional_parameters :initial_value, :value, :check_value
@@ -56,11 +58,12 @@ module BinData
56
58
 
57
59
  # does the value meet expectations?
58
60
  if has_param?(:check_value)
59
- expected = eval_param(:check_value)
61
+ current_value = self.value
62
+ expected = eval_param(:check_value, :value => current_value)
60
63
  if not expected
61
64
  raise ValidityError, "value not as expected"
62
- elsif @value != expected and expected != true
63
- raise ValidityError, "value is '#{@value}' but " +
65
+ elsif current_value != expected and expected != true
66
+ raise ValidityError, "value is '#{current_value}' but " +
64
67
  "expected '#{expected}'"
65
68
  end
66
69
  end
@@ -56,8 +56,7 @@ module BinData
56
56
  end
57
57
 
58
58
  # Returns or sets the endianess of numerics used in this stucture.
59
- # Endianess is propagated to nested data objects unless overridden
60
- # in a nested Struct.
59
+ # Endianess is applied to the fields of this structure.
61
60
  # Valid values are :little and :big.
62
61
  def endian(endian = nil)
63
62
  @endian ||= nil
@@ -5,87 +5,140 @@ require 'bindata/array'
5
5
  require 'bindata/int'
6
6
  require 'bindata/struct'
7
7
 
8
- context "Instantiating an Array" do
9
- specify "should ensure mandatory parameters are supplied" do
8
+ describe "Instantiating an Array" do
9
+ it "should ensure mandatory parameters are supplied" do
10
10
  args = {}
11
11
  lambda { BinData::Array.new(args) }.should raise_error(ArgumentError)
12
- args = {:type => :int8}
13
- lambda { BinData::Array.new(args) }.should raise_error(ArgumentError)
14
12
  args = {:initial_length => 3}
15
13
  lambda { BinData::Array.new(args) }.should raise_error(ArgumentError)
16
14
  end
17
15
 
18
- specify "should fail if a given type is unknown" do
16
+ it "should fail if a given type is unknown" do
19
17
  args = {:type => :does_not_exist, :initial_length => 3}
20
18
  lambda { BinData::Array.new(args) }.should raise_error(TypeError)
21
19
  end
20
+
21
+ it "should not allow both :initial_length and :read_until" do
22
+ args = {:initial_length => 3, :read_until => lambda { false } }
23
+ lambda { BinData::Array.new(args) }.should raise_error(ArgumentError)
24
+ end
25
+ end
26
+
27
+ describe "An Array with no elements" do
28
+ before(:each) do
29
+ @data = BinData::Array.new(:type => :int8)
30
+ end
31
+
32
+ it "should return correct length" do
33
+ @data.length.should be_zero
34
+ end
35
+
36
+ it "should return nil for the first element" do
37
+ @data.first.should be_nil
38
+ end
39
+
40
+ it "should return [] for the first n elements" do
41
+ @data.first(3).should eql([])
42
+ end
43
+
44
+ it "should return nil for the last element" do
45
+ @data.last.should be_nil
46
+ end
47
+
48
+ it "should return [] for the last n elements" do
49
+ @data.last(3).should eql([])
50
+ end
51
+
52
+ it "should append an element" do
53
+ @data.append(99)
54
+ @data.length.should eql(1)
55
+ @data.last.should eql(99)
56
+ end
22
57
  end
23
58
 
24
- context "An Array with several elements" do
25
- setup do
59
+ describe "An Array with several elements" do
60
+ before(:each) do
26
61
  type = [:int16le, {:initial_value => lambda { index + 1 }}]
27
62
  @data = BinData::Array.new(:type => type, :initial_length => 5)
28
63
  end
29
64
 
30
- specify "should return a correct snapshot" do
65
+ it "should return a correct snapshot" do
31
66
  @data.snapshot.should eql([1, 2, 3, 4, 5])
32
67
  end
33
68
 
34
- specify "should have correct num elements" do
69
+ it "should return the first element" do
70
+ @data.first.should eql(1)
71
+ end
72
+
73
+ it "should return the first n elements" do
74
+ @data.first(3).should eql([1, 2, 3])
75
+ @data.first(99).should eql([1, 2, 3, 4, 5])
76
+ end
77
+
78
+ it "should return the last element" do
79
+ @data.last.should eql(5)
80
+ end
81
+
82
+ it "should return the last n elements" do
83
+ @data.last(3).should eql([3, 4, 5])
84
+ @data.last(99).should eql([1, 2, 3, 4, 5])
85
+ end
86
+
87
+ it "should have correct num elements" do
35
88
  @data.length.should eql(5)
36
89
  @data.size.should eql(5)
37
90
  end
38
91
 
39
- specify "should have correct num_bytes" do
92
+ it "should have correct num_bytes" do
40
93
  @data.num_bytes.should eql(10)
41
94
  end
42
95
 
43
- specify "should have correct num_bytes for individual elements" do
96
+ it "should have correct num_bytes for individual elements" do
44
97
  @data.num_bytes(0).should eql(2)
45
98
  end
46
99
 
47
- specify "should have no field_names" do
100
+ it "should have no field_names" do
48
101
  @data.field_names.should be_empty
49
102
  end
50
103
 
51
- specify "should be able to directly access elements" do
104
+ it "should be able to directly access elements" do
52
105
  @data[1] = 8
53
106
  @data[1].should eql(8)
54
107
  end
55
108
 
56
- specify "should be able to use methods from Enumerable" do
109
+ it "should be able to use methods from Enumerable" do
57
110
  @data.select { |x| (x % 2) == 0 }.should eql([2, 4])
58
111
  end
59
112
 
60
- specify "should clear" do
113
+ it "should clear" do
61
114
  @data[1] = 8
62
115
  @data.clear
63
116
  @data.collect.should eql([1, 2, 3, 4, 5])
64
117
  end
65
118
 
66
- specify "should clear a single element" do
119
+ it "should clear a single element" do
67
120
  @data[1] = 8
68
121
  @data.clear(1)
69
122
  @data[1].should eql(2)
70
123
  end
71
124
 
72
- specify "should be clear upon creation" do
125
+ it "should be clear upon creation" do
73
126
  @data.clear?.should be_true
74
127
  end
75
128
 
76
- specify "should be clear if all elements are clear" do
129
+ it "should be clear if all elements are clear" do
77
130
  @data[1] = 8
78
131
  @data.clear(1)
79
132
  @data.clear?.should be_true
80
133
  end
81
134
 
82
- specify "should test clear status of individual elements" do
135
+ it "should test clear status of individual elements" do
83
136
  @data[1] = 8
84
137
  @data.clear?(0).should be_true
85
138
  @data.clear?(1).should be_false
86
139
  end
87
140
 
88
- specify "should read and write correctly" do
141
+ it "should read and write correctly" do
89
142
  io = StringIO.new
90
143
  @data[1] = 8
91
144
  @data.write(io)
@@ -97,25 +150,71 @@ context "An Array with several elements" do
97
150
  @data.read(io)
98
151
  @data[1].should eql(8)
99
152
  end
153
+
154
+ it "should append an element" do
155
+ @data.append(99)
156
+ @data.length.should eql(6)
157
+ @data.last.should eql(99)
158
+ end
100
159
  end
101
160
 
102
- context "An Array containing structs" do
103
- setup do
161
+ describe "An Array containing structs" do
162
+ before(:each) do
104
163
  type = [:struct, {:fields => [[:int8, :a,
105
164
  {:initial_value => lambda { parent.index }}],
106
165
  [:int8, :b]]}]
107
166
  @data = BinData::Array.new(:type => type, :initial_length => 5)
108
167
  end
109
168
 
110
- specify "should access elements, not values" do
169
+ it "should access elements, not values" do
111
170
  @data[3].a.should eql(3)
112
171
  end
113
172
 
114
- specify "should not be able to modify elements" do
173
+ it "should not be able to modify elements" do
115
174
  lambda { @data[1] = 3 }.should raise_error(NoMethodError)
116
175
  end
117
176
 
118
- specify "should interate over each element" do
177
+ it "should interate over each element" do
119
178
  @data.collect { |s| s.a }.should eql([0, 1, 2, 3, 4])
120
179
  end
180
+
181
+ it "should be able to append elements" do
182
+ obj = @data.append
183
+ obj.a = 3
184
+ obj.b = 5
185
+
186
+ @data.last.a.should eql(3)
187
+ @data.last.b.should eql(5)
188
+ end
189
+ end
190
+
191
+ describe "An Array with :read_until containing +element+" do
192
+ before(:each) do
193
+ read_until = lambda { element == 5 }
194
+ @data = BinData::Array.new(:type => :int8, :read_until => read_until)
195
+ end
196
+
197
+ it "should append to an empty array" do
198
+ @data.append(3)
199
+ @data.first.should eql(3)
200
+ end
201
+
202
+ it "should read until the sentinel is reached" do
203
+ io = StringIO.new("\x01\x02\x03\x04\x05\x06\x07")
204
+ @data.read(io)
205
+ @data.length.should eql(5)
206
+ end
207
+ end
208
+
209
+ describe "An Array with :read_until containing +array+ and +index+" do
210
+ before(:each) do
211
+ read_until = lambda { index >=2 and array[index - 2] == 5 }
212
+ @data = BinData::Array.new(:type => :int8, :read_until => read_until)
213
+ end
214
+
215
+ it "should read until the sentinel is reached" do
216
+ io = StringIO.new("\x01\x02\x03\x04\x05\x06\x07\x08")
217
+ @data.read(io)
218
+ @data.length.should eql(7)
219
+ end
121
220
  end