bindata 0.8.1 → 0.9.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,13 +1,22 @@
1
1
  = BinData Changelog
2
2
 
3
- -== Version 0.8.1 (2008-01-14)
3
+ == Version 0.9.0 (2008-06-02)
4
+
5
+ * Added :adjust_offset option to automatically seek to a given offset.
6
+ * Modified #read to accept strings as well as IO streams.
7
+ * Choice now accepts sparse arrays and hashes as :choice.
8
+ * Added BinData::Rest to help with debugging.
9
+ * Major internal restructuring - memory usage is much better.
10
+ * Improved documentation
11
+
12
+ == Version 0.8.1 (2008-01-14)
4
13
 
5
14
  * Reduced memory consumption.
6
15
  * Increased execution speed.
7
16
  * Deprecated BinData::Base.parameters
8
17
  * Fixed spec syntax (thanks to David Goodlad)
9
18
 
10
- -== Version 0.8.0 (2007-10-14)
19
+ == Version 0.8.0 (2007-10-14)
11
20
 
12
21
  * Add reserved field names to Struct.
13
22
  * Prevent warnings about method redefinition.
data/README CHANGED
@@ -15,7 +15,7 @@ Do you ever find yourself writing code like this?
15
15
  It's ugly, violates DRY and feels like you're writing Perl, not Ruby.
16
16
  There is a better way.
17
17
 
18
- class Rectangle < BinData::Struct
18
+ class Rectangle < BinData::MultiValue
19
19
  uint16le :len
20
20
  string :name, :read_length => :len
21
21
  uint32le :width
@@ -36,7 +36,7 @@ download[http://rubyforge.org/frs/?group_id=3252] page.
36
36
 
37
37
  BinData declarations are easy to read. Here's an example.
38
38
 
39
- class MyFancyFormat < BinData::Struct
39
+ class MyFancyFormat < BinData::MultiValue
40
40
  stringz :comment
41
41
  uint8 :count, :check_value => lambda { (value % 2) == 0 }
42
42
  array :some_ints, :type => :int32be, :initial_length => :count
@@ -62,7 +62,7 @@ the writing code, have a go at the reading code.
62
62
  The general format of a BinData declaration is a class containing one or more
63
63
  fields.
64
64
 
65
- class MyName < BinData::Struct
65
+ class MyName < BinData::MultiValue
66
66
  type field_name, :param1 => "foo", :param2 => bar, ...
67
67
  ...
68
68
  end
@@ -100,7 +100,7 @@ the string contains the string's length.
100
100
 
101
101
  Here's how we'd implement the same example with BinData.
102
102
 
103
- class PascalString < BinData::Struct
103
+ class PascalString < BinData::MultiValue
104
104
  uint8 :len, :value => lambda { data.length }
105
105
  string :data, :read_length => :len
106
106
  end
@@ -120,7 +120,7 @@ Here's how we'd implement the same example with BinData.
120
120
  This syntax needs explaining. Let's simplify by examining reading and
121
121
  writing separately.
122
122
 
123
- class PascalStringReader < BinData::Struct
123
+ class PascalStringReader < BinData::MultiValue
124
124
  uint8 :len
125
125
  string :data, :read_length => :len
126
126
  end
@@ -132,7 +132,7 @@ This states that when reading the string, the initial length of the string
132
132
  Note that <tt>:read_length => :len</tt> is syntactic sugar for
133
133
  <tt>:read_length => lambda { len }</tt>, but more on that later.
134
134
 
135
- class PascalStringWriter < BinData::Struct
135
+ class PascalStringWriter < BinData::MultiValue
136
136
  uint8 :len, :value => lambda { data.length }
137
137
  string :data
138
138
  end
@@ -152,6 +152,13 @@ length afterwards.
152
152
  These are the predefined types. Custom types can be created by composing
153
153
  these types.
154
154
 
155
+ BinData::String:: A sequence of bytes.
156
+ BinData::Stringz:: A zero terminated sequence of bytes.
157
+
158
+ BinData::Array:: A list of objects of the same type.
159
+ BinData::Choice:: A choice between several objects.
160
+ BinData::Struct:: An ordered collection of named objects.
161
+
155
162
  BinData::Int8:: Signed 8 bit integer.
156
163
  BinData::Int16le:: Signed 16 bit integer (little endian).
157
164
  BinData::Int16be:: Signed 16 bit integer (big endian).
@@ -173,16 +180,11 @@ BinData::FloatBe:: Single precision floating point number (big endian).
173
180
  BinData::DoubleLe:: Double precision floating point number (little endian).
174
181
  BinData::DoubleBe:: Double precision floating point number (big endian).
175
182
 
176
- BinData::String:: A sequence of bytes.
177
- BinData::Stringz:: A zero terminated sequence of bytes.
178
-
179
- BinData::Array:: A list of objects of the same type.
180
- BinData::Choice:: A choice between several objects.
181
- BinData::Struct:: An ordered collection of named objects.
183
+ BinData::Rest:: Consumes the rest of the input stream.
182
184
 
183
185
  == Parameters
184
186
 
185
- class PascalStringWriter < BinData::Struct
187
+ class PascalStringWriter < BinData::MultiValue
186
188
  uint8 :len, :value => lambda { data.length }
187
189
  string :data
188
190
  end
@@ -217,7 +219,7 @@ produced is independent of architecture. Explicitly specifying the
217
219
  endianess of each numeric type can become tedious, so the following
218
220
  shortcut is provided.
219
221
 
220
- class A < BinData::Struct
222
+ class A < BinData::MultiValue
221
223
  endian :little
222
224
 
223
225
  uint16 :a
@@ -229,7 +231,7 @@ shortcut is provided.
229
231
 
230
232
  is equivalent to:
231
233
 
232
- class A < BinData::Struct
234
+ class A < BinData::MultiValue
233
235
  uint16le :a
234
236
  uint32le :b
235
237
  double_le :c
@@ -243,13 +245,47 @@ cascade to nested types, as illustrated with the array in the above example.
243
245
 
244
246
  == Creating custom types
245
247
 
246
- Custom types should be created by subclassing BinData::Struct.
247
- Ocassionally it may be useful to subclass BinData::Single. Subclassing
248
- other classes may have unexpected results and is unsupported.
248
+ Custom types should be created by subclassing BinData::MultiValue or
249
+ BinData::SingleValue. Ocassionally it may be useful to subclass
250
+ BinData::Single. Subclassing other classes may have unexpected results
251
+ and is unsupported.
252
+
253
+ Let us revisit the Pascal String example.
254
+
255
+ class PascalString < BinData::MultiValue
256
+ uint8 :len, :value => lambda { data.length }
257
+ string :data, :read_length => :len
258
+ end
259
+
260
+ We'd like to make PascalString a custom type that behaves like a
261
+ BinData::Single object so we can use :initial_value etc. Here's an
262
+ example usage of what we'd like:
263
+
264
+ class Favourites < BinData::MultiValue
265
+ pascal_string :language, :initial_value => "ruby"
266
+ pascal_string :os, :initial_value => "unix"
267
+ end
268
+
269
+ f = Favourites.new
270
+ f.os = "freebsd"
271
+ f.to_s #=> "\004ruby\007freebsd"
272
+
273
+ We create this type of custom string by inheriting from BinData::SingleValue
274
+ and implementing the #get and #set methods.
275
+
276
+ class PascalString < BinData::SingleValue
277
+ uint8 :len, :value => lambda { data.length }
278
+ string :data, :read_length => :len
279
+
280
+ def get; self.data; end
281
+ def set(v) self.data = v; end
282
+ end
249
283
 
284
+ If the type we are creating represents a single value then inherit from
285
+ BinData::SingleValue, otherwise inherit from BinData::MultiValue.
250
286
 
251
287
  == License
252
288
 
253
289
  BinData is released under the same license as Ruby.
254
290
 
255
- Copyright (c) 2007 Dion Mendel
291
+ Copyright (c) 2007, 2008 Dion Mendel
data/TODO CHANGED
@@ -3,3 +3,7 @@
3
3
  * Need optional parameter for fields
4
4
 
5
5
  * Should I add seek capability?
6
+
7
+ * Add (Multi|Single)Value.custom_parameters
8
+
9
+ * BitFields
@@ -9,12 +9,12 @@ class Gzip
9
9
  # Known compression methods
10
10
  DEFLATE = 8
11
11
 
12
- class Extra < BinData::Struct
12
+ class Extra < BinData::MultiValue
13
13
  uint16le :len, :length => lambda { data.length }
14
14
  string :data, :read_length => :len
15
15
  end
16
16
 
17
- class Header < BinData::Struct
17
+ class Header < BinData::MultiValue
18
18
  uint16le :ident, :value => 0x8b1f, :check_value => 0x8b1f
19
19
  uint8 :compression_method, :initial_value => DEFLATE
20
20
  uint8 :flags, :value => :calculate_flags_val,
@@ -60,7 +60,7 @@ class Gzip
60
60
  end
61
61
  end
62
62
 
63
- class Footer < BinData::Struct
63
+ class Footer < BinData::MultiValue
64
64
  uint32le :crc32
65
65
  uint32le :uncompressed_size
66
66
  end
@@ -5,10 +5,17 @@ require 'bindata/array'
5
5
  require 'bindata/choice'
6
6
  require 'bindata/float'
7
7
  require 'bindata/int'
8
+ require 'bindata/multi_value'
9
+ require 'bindata/rest'
10
+ require 'bindata/single_value'
8
11
  require 'bindata/string'
9
12
  require 'bindata/stringz'
10
13
  require 'bindata/struct'
11
14
 
15
+ # = BinData
16
+ #
17
+ # A declarative way to read and write structured binary data.
18
+ #
12
19
  module BinData
13
- VERSION = "0.8.1"
20
+ VERSION = "0.9.0"
14
21
  end
@@ -1,15 +1,31 @@
1
1
  require 'bindata/base'
2
+ require 'bindata/sanitize'
2
3
 
3
4
  module BinData
4
5
  # An Array is a list of data objects of the same type.
5
6
  #
6
7
  # require 'bindata'
7
- # require 'stringio'
8
8
  #
9
- # a = BinData::Array.new(:type => :int8, :initial_length => 5)
10
- # io = StringIO.new("\x03\x04\x05\x06\x07")
11
- # a.read(io)
12
- # a.snapshot #=> [3, 4, 5, 6, 7]
9
+ # data = "\x03\x04\x05\x06\x07\x08\x09"
10
+ #
11
+ # obj = BinData::Array.new(:type => :int8, :initial_length => 6)
12
+ # obj.read(data)
13
+ # obj.snapshot #=> [3, 4, 5, 6, 7, 8]
14
+ #
15
+ # obj = BinData::Array.new(:type => :int8,
16
+ # :read_until => lambda { index == 1 })
17
+ # obj.read(data)
18
+ # obj.snapshot #=> [3, 4]
19
+ #
20
+ # obj = BinData::Array.new(:type => :int8,
21
+ # :read_until => lambda { element >= 6 })
22
+ # obj.read(data)
23
+ # obj.snapshot #=> [3, 4, 5, 6]
24
+ #
25
+ # obj = BinData::Array.new(:type => :int8,
26
+ # :read_until => lambda { array[index] + array[index - 1] == 13 })
27
+ # obj.read(data)
28
+ # obj.snapshot #=> [3, 4, 5, 6, 7]
13
29
  #
14
30
  # == Parameters
15
31
  #
@@ -30,7 +46,7 @@ module BinData
30
46
  #
31
47
  # Each data object in an array has the variable +index+ made available
32
48
  # to any lambda evaluated as a parameter of that data object.
33
- class Array < Base
49
+ class Array < BinData::Base
34
50
  include Enumerable
35
51
 
36
52
  # Register this class
@@ -39,22 +55,44 @@ module BinData
39
55
  # These are the parameters used by this class.
40
56
  mandatory_parameter :type
41
57
  optional_parameters :initial_length, :read_until
58
+ mutually_exclusive_parameters :initial_length, :read_until
59
+
60
+ class << self
61
+ # Returns a sanitized +params+ that is of the form expected
62
+ # by #initialize.
63
+ def sanitize_parameters(params, endian = nil)
64
+ params = params.dup
65
+
66
+ unless params.has_key?(:initial_length) or params.has_key?(:read_until)
67
+ # ensure one of :initial_length and :read_until exists
68
+ params[:initial_length] = 0
69
+ end
70
+
71
+ if params.has_key?(:type)
72
+ type, el_params = params[:type]
73
+ klass = lookup(type, endian)
74
+ raise TypeError, "unknown type '#{type}' for #{self}" if klass.nil?
75
+ params[:type] = [klass, SanitizedParameters.new(klass, el_params, endian)]
76
+ end
42
77
 
43
- # An empty hash shared by all instances
44
- @@empty_hash = Hash.new.freeze
78
+ super(params, endian)
79
+ end
80
+
81
+ # An array has no fields.
82
+ def all_possible_field_names(sanitized_params)
83
+ []
84
+ end
85
+ end
45
86
 
46
87
  # Creates a new Array
47
88
  def initialize(params = {}, env = nil)
48
- super(cleaned_params(params), env)
49
- ensure_mutual_exclusion(:initial_length, :read_until)
89
+ super(params, env)
50
90
 
51
- type, el_params = param(:type)
52
- klass = klass_lookup(type)
53
- raise TypeError, "unknown type '#{type}' for #{self}" if klass.nil?
91
+ klass, el_params = param(:type)
54
92
 
55
93
  @element_list = nil
56
94
  @element_klass = klass
57
- @element_params = el_params || @@empty_hash
95
+ @element_params = el_params
58
96
  end
59
97
 
60
98
  # Clears the element at position +index+. If +index+ is not given, then
@@ -126,6 +164,12 @@ module BinData
126
164
  elements.collect { |e| e.snapshot }
127
165
  end
128
166
 
167
+ # Returns whether this data object contains a single value. Single
168
+ # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
169
+ def single_value?
170
+ return false
171
+ end
172
+
129
173
  # An array has no fields.
130
174
  def field_names
131
175
  []
@@ -239,17 +283,5 @@ module BinData
239
283
  @element_list << element
240
284
  element
241
285
  end
242
-
243
- # Returns a hash of cleaned +params+. Cleaning means that param
244
- # values are converted to a desired format.
245
- def cleaned_params(params)
246
- unless params.has_key?(:initial_length) or params.has_key?(:read_until)
247
- # ensure one of :initial_length and :read_until exists
248
- new_params = params.dup
249
- new_params[:initial_length] = 0
250
- params = new_params
251
- end
252
- params
253
- end
254
286
  end
255
287
  end
@@ -1,5 +1,7 @@
1
1
  require 'bindata/lazy'
2
+ require 'bindata/sanitize'
2
3
  require 'bindata/registry'
4
+ require 'stringio'
3
5
 
4
6
  module BinData
5
7
  # Error raised when unexpected results occur when reading data from IO.
@@ -21,6 +23,10 @@ module BinData
21
23
  # is made available to any lambda assigned to
22
24
  # this parameter. This parameter is only checked
23
25
  # before reading.
26
+ # [<tt>:adjust_offset</tt>] Ensures that the current IO offset is at this
27
+ # position before reading. This is like
28
+ # <tt>:check_offset</tt>, except that it will
29
+ # adjust the IO offset instead of raising an error.
24
30
  class Base
25
31
  class << self
26
32
  # Returns the mandatory parameters used by this class. Any given args
@@ -63,11 +69,75 @@ module BinData
63
69
  end
64
70
  alias_method :optional_parameter, :optional_parameters
65
71
 
66
- # Returns both the mandatory and optional parameters used by this class.
67
- def parameters
68
- # warn about deprecated method - remove before releasing 1.0
69
- warn "warning: #parameters is deprecated."
70
- (mandatory_parameters + optional_parameters).uniq
72
+ # Returns the default parameters used by this class. Any given args
73
+ # are appended to the parameters list. The parameters for a class will
74
+ # include the parameters of its ancestors.
75
+ def default_parameters(params = {})
76
+ unless defined? @default_parameters
77
+ @default_parameters = {}
78
+ ancestors[1..-1].each do |parent|
79
+ if parent.respond_to?(:default_parameters)
80
+ @default_parameters = @default_parameters.merge(parent.default_parameters)
81
+ end
82
+ end
83
+ end
84
+ if not params.empty?
85
+ @default_parameters = @default_parameters.merge(params)
86
+ end
87
+ @default_parameters
88
+ end
89
+ alias_method :default_parameter, :default_parameters
90
+
91
+ # Returns the pairs of mutually exclusive parameters used by this class.
92
+ # Any given args are appended to the parameters list. The parameters for
93
+ # a class will include the parameters of its ancestors.
94
+ def mutually_exclusive_parameters(*args)
95
+ unless defined? @mutually_exclusive_parameters
96
+ @mutually_exclusive_parameters = []
97
+ ancestors[1..-1].each do |parent|
98
+ if parent.respond_to?(:mutually_exclusive_parameters)
99
+ @mutually_exclusive_parameters.concat(parent.mutually_exclusive_parameters)
100
+ end
101
+ end
102
+ end
103
+ if not args.empty?
104
+ @mutually_exclusive_parameters << [args[0].to_sym, args[1].to_sym]
105
+ end
106
+ @mutually_exclusive_parameters
107
+ end
108
+
109
+ # Returns a list of parameters that are accepted by this object
110
+ def accepted_parameters
111
+ (mandatory_parameters + optional_parameters + default_parameters.keys).uniq
112
+ end
113
+
114
+ # Returns a sanitized +params+ that is of the form expected
115
+ # by #initialize.
116
+ def sanitize_parameters(params, *args)
117
+ params = params.dup
118
+
119
+ # add default parameters
120
+ default_parameters.each do |k,v|
121
+ params[k] = v unless params.has_key?(k)
122
+ end
123
+
124
+ # ensure mandatory parameters exist
125
+ mandatory_parameters.each do |prm|
126
+ if not params.has_key?(prm)
127
+ raise ArgumentError, "parameter ':#{prm}' must be specified " +
128
+ "in #{self}"
129
+ end
130
+ end
131
+
132
+ # ensure mutual exclusion
133
+ mutually_exclusive_parameters.each do |param1, param2|
134
+ if params.has_key?(param1) and params.has_key?(param2)
135
+ raise ArgumentError, "params #{param1} and #{param2} " +
136
+ "are mutually exclusive"
137
+ end
138
+ end
139
+
140
+ params
71
141
  end
72
142
 
73
143
  # Instantiates this class and reads from +io+. For single value objects
@@ -86,19 +156,17 @@ module BinData
86
156
  private :register
87
157
 
88
158
  # Returns the class matching a previously registered +name+.
89
- def lookup(name)
159
+ def lookup(name, endian = nil)
160
+ name = name.to_s
90
161
  klass = Registry.instance.lookup(name)
91
- if klass.nil?
162
+ if klass.nil? and endian != nil
92
163
  # lookup failed so attempt endian lookup
93
- if self.respond_to?(:endian) and self.endian != nil
94
- name = name.to_s
95
- if /^u?int\d\d?$/ =~ name
96
- new_name = name + ((self.endian == :little) ? "le" : "be")
97
- klass = Registry.instance.lookup(new_name)
98
- elsif ["float", "double"].include?(name)
99
- new_name = name + ((self.endian == :little) ? "_le" : "_be")
100
- klass = Registry.instance.lookup(new_name)
101
- end
164
+ if /^u?int\d{1,3}$/ =~ name
165
+ new_name = name + ((endian == :little) ? "le" : "be")
166
+ klass = Registry.instance.lookup(new_name)
167
+ elsif ["float", "double"].include?(name)
168
+ new_name = name + ((endian == :little) ? "_le" : "_be")
169
+ klass = Registry.instance.lookup(new_name)
102
170
  end
103
171
  end
104
172
  klass
@@ -106,7 +174,9 @@ module BinData
106
174
  end
107
175
 
108
176
  # Define the parameters we use in this class.
109
- optional_parameters :check_offset, :readwrite
177
+ optional_parameters :check_offset, :adjust_offset
178
+ default_parameters :readwrite => true
179
+ mutually_exclusive_parameters :check_offset, :adjust_offset
110
180
 
111
181
  # Creates a new data object.
112
182
  #
@@ -114,68 +184,26 @@ module BinData
114
184
  # reference callable objects (methods or procs). +env+ is the
115
185
  # environment that these callable objects are evaluated in.
116
186
  def initialize(params = {}, env = nil)
117
- # all known parameters
118
- mandatory = self.class.mandatory_parameters
119
- optional = self.class.optional_parameters
120
-
121
- # default :readwrite param to true if unspecified
122
- if not params.has_key?(:readwrite)
123
- params = params.dup
124
- params[:readwrite] = true
187
+ unless SanitizedParameters === params
188
+ params = SanitizedParameters.new(self.class, params)
125
189
  end
126
190
 
127
- # ensure mandatory parameters exist
128
- mandatory.each do |prm|
129
- if not params.has_key?(prm)
130
- raise ArgumentError, "parameter ':#{prm}' must be specified " +
131
- "in #{self}"
132
- end
133
- end
134
-
135
- # partition parameters into known and extra parameters
136
- @params = {}
137
- extra = {}
138
- params.each do |k,v|
139
- k = k.to_sym
140
- raise ArgumentError, "parameter :#{k} is nil in #{self}" if v.nil?
141
- if mandatory.include?(k) or optional.include?(k)
142
- @params[k] = v.freeze
143
- else
144
- extra[k] = v.freeze
145
- end
146
- end
191
+ @params = params.accepted_parameters
147
192
 
148
193
  # set up the environment
149
194
  @env = env || LazyEvalEnv.new
150
- @env.params = extra
195
+ @env.params = params.extra_parameters
151
196
  @env.data_object = self
152
197
  end
153
198
 
154
- # Returns the class matching a previously registered +name+.
155
- def klass_lookup(name)
156
- @cache ||= {}
157
- klass = @cache[name]
158
- if klass.nil?
159
- klass = self.class.lookup(name)
160
- if klass.nil? and @env.parent_data_object != nil
161
- # lookup failed so retry in the context of the parent data object
162
- klass = @env.parent_data_object.klass_lookup(name)
163
- end
164
- @cache[name] = klass
165
- end
166
- klass
167
- end
168
-
169
- # Returns a list of parameters that are accepted by this object
170
- def accepted_parameters
171
- (self.class.mandatory_parameters + self.class.optional_parameters).uniq
172
- end
173
-
174
- # Reads data into this bin object by calling #do_read then #done_read.
199
+ # Reads data into this data object by calling #do_read then #done_read.
175
200
  def read(io)
201
+ # wrap strings in a StringIO
202
+ io = StringIO.new(io) if io.respond_to?(:to_str)
203
+
176
204
  # remove previous method to prevent warnings
177
205
  class << io
178
- undef_method(:bindata_mark) if method_defined?(:bindata_mark)
206
+ remove_method(:bindata_mark) if method_defined?(:bindata_mark)
179
207
  end
180
208
 
181
209
  # remember the current position in the IO object
@@ -198,17 +226,19 @@ module BinData
198
226
  _write(io) if eval_param(:readwrite) != false
199
227
  end
200
228
 
229
+ # Returns the string representation of this data object.
230
+ def to_s
231
+ io = StringIO.new
232
+ write(io)
233
+ io.rewind
234
+ io.read
235
+ end
236
+
201
237
  # Returns the number of bytes it will take to write this data.
202
238
  def num_bytes(what = nil)
203
239
  (eval_param(:readwrite) != false) ? _num_bytes(what) : 0
204
240
  end
205
241
 
206
- # Returns whether this data object contains a single value. Single
207
- # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
208
- def single_value?
209
- respond_to? :value
210
- end
211
-
212
242
  # Return a human readable representation of this object.
213
243
  def inspect
214
244
  snapshot.inspect
@@ -243,14 +273,6 @@ module BinData
243
273
  @params.has_key?(key.to_sym)
244
274
  end
245
275
 
246
- # Raise an error if +param1+ and +param2+ are both given as params.
247
- def ensure_mutual_exclusion(param1, param2)
248
- if has_param?(param1) and has_param?(param2)
249
- raise ArgumentError, "params #{param1} and #{param2} " +
250
- "are mutually exclusive"
251
- end
252
- end
253
-
254
276
  # Checks that the current offset of +io+ is as expected. This should
255
277
  # be called from #do_read before performing the reading.
256
278
  def check_offset(io)
@@ -264,11 +286,32 @@ module BinData
264
286
  raise ValidityError, "offset is '#{actual_offset}' but " +
265
287
  "expected '#{expected}'"
266
288
  end
289
+ elsif has_param?(:adjust_offset)
290
+ actual_offset = io.pos - io.bindata_mark
291
+ expected = eval_param(:adjust_offset)
292
+ if actual_offset != expected
293
+ begin
294
+ seek = expected - actual_offset
295
+ io.seek(seek, IO::SEEK_CUR)
296
+ warn "adjusting stream position by #{seek} bytes" if $VERBOSE
297
+ rescue
298
+ # could not seek so raise an error
299
+ raise ValidityError, "offset is '#{actual_offset}' but " +
300
+ "couldn't seek to expected '#{expected}'"
301
+ end
302
+ end
267
303
  end
268
304
  end
269
305
 
306
+ ###########################################################################
270
307
  # To be implemented by subclasses
271
308
 
309
+ # Returns a list of the names of all possible field names for an object
310
+ # created with +sanitized_params+.
311
+ def self.all_possible_field_names(sanitized_params)
312
+ raise NotImplementedError
313
+ end
314
+
272
315
  # Resets the internal state to that of a newly created object.
273
316
  def clear
274
317
  raise NotImplementedError
@@ -299,6 +342,12 @@ module BinData
299
342
  raise NotImplementedError
300
343
  end
301
344
 
345
+ # Returns whether this data object contains a single value. Single
346
+ # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
347
+ def single_value?
348
+ raise NotImplementedError
349
+ end
350
+
302
351
  # Returns a list of the names of all fields accessible through this
303
352
  # object.
304
353
  def field_names
@@ -306,9 +355,10 @@ module BinData
306
355
  end
307
356
 
308
357
  # Set visibility requirements of methods to implement
309
- public :clear, :done_read, :snapshot, :field_names
358
+ public :clear, :done_read, :snapshot, :single_value?, :field_names
310
359
  private :_do_read, :_write, :_num_bytes
311
360
 
312
361
  # End To be implemented by subclasses
362
+ ###########################################################################
313
363
  end
314
364
  end