jbangert-bindata 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/BSDL +22 -0
- data/COPYING +52 -0
- data/ChangeLog.rdoc +204 -0
- data/Gemfile +2 -0
- data/INSTALL +11 -0
- data/NEWS.rdoc +164 -0
- data/README.md +54 -0
- data/Rakefile +13 -0
- data/bindata.gemspec +31 -0
- data/doc/manual.haml +407 -0
- data/doc/manual.md +1649 -0
- data/examples/NBT.txt +149 -0
- data/examples/gzip.rb +161 -0
- data/examples/ip_address.rb +22 -0
- data/examples/list.rb +124 -0
- data/examples/nbt.rb +178 -0
- data/lib/bindata.rb +33 -0
- data/lib/bindata/alignment.rb +83 -0
- data/lib/bindata/array.rb +335 -0
- data/lib/bindata/base.rb +388 -0
- data/lib/bindata/base_primitive.rb +214 -0
- data/lib/bindata/bits.rb +87 -0
- data/lib/bindata/choice.rb +216 -0
- data/lib/bindata/count_bytes_remaining.rb +35 -0
- data/lib/bindata/deprecated.rb +50 -0
- data/lib/bindata/dsl.rb +312 -0
- data/lib/bindata/float.rb +80 -0
- data/lib/bindata/int.rb +184 -0
- data/lib/bindata/io.rb +274 -0
- data/lib/bindata/lazy.rb +105 -0
- data/lib/bindata/offset.rb +91 -0
- data/lib/bindata/params.rb +135 -0
- data/lib/bindata/primitive.rb +135 -0
- data/lib/bindata/record.rb +110 -0
- data/lib/bindata/registry.rb +92 -0
- data/lib/bindata/rest.rb +35 -0
- data/lib/bindata/sanitize.rb +290 -0
- data/lib/bindata/skip.rb +48 -0
- data/lib/bindata/string.rb +145 -0
- data/lib/bindata/stringz.rb +96 -0
- data/lib/bindata/struct.rb +388 -0
- data/lib/bindata/trace.rb +94 -0
- data/lib/bindata/version.rb +3 -0
- data/setup.rb +1585 -0
- data/spec/alignment_spec.rb +61 -0
- data/spec/array_spec.rb +331 -0
- data/spec/base_primitive_spec.rb +238 -0
- data/spec/base_spec.rb +376 -0
- data/spec/bits_spec.rb +163 -0
- data/spec/choice_spec.rb +263 -0
- data/spec/count_bytes_remaining_spec.rb +38 -0
- data/spec/deprecated_spec.rb +31 -0
- data/spec/example.rb +21 -0
- data/spec/float_spec.rb +37 -0
- data/spec/int_spec.rb +216 -0
- data/spec/io_spec.rb +352 -0
- data/spec/lazy_spec.rb +217 -0
- data/spec/primitive_spec.rb +202 -0
- data/spec/record_spec.rb +530 -0
- data/spec/registry_spec.rb +108 -0
- data/spec/rest_spec.rb +26 -0
- data/spec/skip_spec.rb +27 -0
- data/spec/spec_common.rb +58 -0
- data/spec/string_spec.rb +300 -0
- data/spec/stringz_spec.rb +118 -0
- data/spec/struct_spec.rb +350 -0
- data/spec/system_spec.rb +380 -0
- data/tasks/manual.rake +36 -0
- data/tasks/rspec.rake +17 -0
- metadata +208 -0
data/lib/bindata/base.rb
ADDED
@@ -0,0 +1,388 @@
|
|
1
|
+
require 'bindata/io'
|
2
|
+
require 'bindata/lazy'
|
3
|
+
require 'bindata/offset'
|
4
|
+
require 'bindata/params'
|
5
|
+
require 'bindata/registry'
|
6
|
+
require 'bindata/sanitize'
|
7
|
+
|
8
|
+
module BinData
|
9
|
+
# Error raised when unexpected results occur when reading data from IO.
|
10
|
+
class ValidityError < StandardError ; end
|
11
|
+
|
12
|
+
# ArgExtractors take the arguments passed to BinData::Base.new and
|
13
|
+
# separates them into [value, parameters, parent].
|
14
|
+
class BaseArgExtractor
|
15
|
+
@@empty_hash = Hash.new.freeze
|
16
|
+
|
17
|
+
def self.extract(the_class, the_args)
|
18
|
+
args = the_args.dup
|
19
|
+
value = parameters = parent = nil
|
20
|
+
|
21
|
+
if args.length > 1 and args.last.is_a? BinData::Base
|
22
|
+
parent = args.pop
|
23
|
+
end
|
24
|
+
|
25
|
+
if args.length > 0 and args.last.is_a? Hash
|
26
|
+
parameters = args.pop
|
27
|
+
end
|
28
|
+
|
29
|
+
if args.length > 0
|
30
|
+
value = args.pop
|
31
|
+
end
|
32
|
+
|
33
|
+
parameters ||= @@empty_hash
|
34
|
+
|
35
|
+
return [value, parameters, parent]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# This is the abstract base class for all data objects.
|
40
|
+
class Base
|
41
|
+
include AcceptedParametersMixin
|
42
|
+
include CheckOrAdjustOffsetMixin
|
43
|
+
|
44
|
+
class << self
|
45
|
+
|
46
|
+
# Instantiates this class and reads from +io+, returning the newly
|
47
|
+
# created data object.
|
48
|
+
def read(io)
|
49
|
+
obj = self.new
|
50
|
+
obj.read(io)
|
51
|
+
obj
|
52
|
+
end
|
53
|
+
|
54
|
+
# The arg extractor for this class.
|
55
|
+
def arg_extractor
|
56
|
+
BaseArgExtractor
|
57
|
+
end
|
58
|
+
|
59
|
+
# The name of this class as used by Records, Arrays etc.
|
60
|
+
def bindata_name
|
61
|
+
RegisteredClasses.underscore_name(self.name)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Call this method if this class is abstract and not to be used.
|
65
|
+
def unregister_self
|
66
|
+
RegisteredClasses.unregister(name)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Registers all subclasses of this class for use
|
70
|
+
def register_subclasses #:nodoc:
|
71
|
+
class << self
|
72
|
+
define_method(:inherited) do |subclass|
|
73
|
+
RegisteredClasses.register(subclass.name, subclass)
|
74
|
+
register_subclasses
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private :unregister_self, :register_subclasses
|
80
|
+
end
|
81
|
+
|
82
|
+
# Register all subclasses of this class.
|
83
|
+
register_subclasses
|
84
|
+
|
85
|
+
# The registered name may be provided explicitly.
|
86
|
+
optional_parameter :name
|
87
|
+
|
88
|
+
# Creates a new data object.
|
89
|
+
#
|
90
|
+
# Args are optional, but if present, must be in the following order.
|
91
|
+
#
|
92
|
+
# +value+ is a value that is +assign+ed immediately after initialization.
|
93
|
+
#
|
94
|
+
# +parameters+ is a hash containing symbol keys. Some parameters may
|
95
|
+
# reference callable objects (methods or procs).
|
96
|
+
#
|
97
|
+
# +parent+ is the parent data object (e.g. struct, array, choice) this
|
98
|
+
# object resides under.
|
99
|
+
#
|
100
|
+
# == Parameters
|
101
|
+
#
|
102
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
103
|
+
# an object. These params are:
|
104
|
+
#
|
105
|
+
# <tt>:name</tt>:: The name that this object can be referred to may be
|
106
|
+
# set explicitly. This is only useful when dynamically
|
107
|
+
# generating types.
|
108
|
+
# <code><pre>
|
109
|
+
# BinData::Struct.new(:name => :my_struct, :fields => ...)
|
110
|
+
# array = BinData::Array.new(:type => :my_struct)
|
111
|
+
# </pre></code>
|
112
|
+
#
|
113
|
+
def initialize(*args)
|
114
|
+
value, parameters, parent = extract_args(args)
|
115
|
+
|
116
|
+
@params = SanitizedParameters.sanitize(parameters, self.class)
|
117
|
+
@parent = parent
|
118
|
+
|
119
|
+
register_prototype
|
120
|
+
add_methods_for_check_or_adjust_offset
|
121
|
+
|
122
|
+
initialize_shared_instance
|
123
|
+
initialize_instance
|
124
|
+
assign(value) if value
|
125
|
+
end
|
126
|
+
|
127
|
+
attr_accessor :parent
|
128
|
+
protected :parent=
|
129
|
+
|
130
|
+
# Creates a new data object based on this instance.
|
131
|
+
#
|
132
|
+
# All parameters will be be duplicated. Use this method
|
133
|
+
# when creating multiple objects with the same parameters.
|
134
|
+
def new(value = nil, parent = nil)
|
135
|
+
obj = clone
|
136
|
+
obj.parent = parent if parent
|
137
|
+
obj.initialize_instance
|
138
|
+
obj.assign(value) if value
|
139
|
+
|
140
|
+
obj
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the result of evaluating the parameter identified by +key+.
|
144
|
+
#
|
145
|
+
# +overrides+ is an optional +parameters+ like hash that allow the
|
146
|
+
# parameters given at object construction to be overridden.
|
147
|
+
#
|
148
|
+
# Returns nil if +key+ does not refer to any parameter.
|
149
|
+
def eval_parameter(key, overrides = nil)
|
150
|
+
value = get_parameter(key)
|
151
|
+
if value.is_a?(Symbol) or value.respond_to?(:arity)
|
152
|
+
lazy_evaluator.lazy_eval(value, overrides)
|
153
|
+
else
|
154
|
+
value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns a lazy evaluator for this object.
|
159
|
+
def lazy_evaluator #:nodoc:
|
160
|
+
@lazy ||= LazyEvaluator.new(self)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns the parameter referenced by +key+.
|
164
|
+
# Use this method if you are sure the parameter is not to be evaluated.
|
165
|
+
# You most likely want #eval_parameter.
|
166
|
+
def get_parameter(key)
|
167
|
+
@params[key]
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns whether +key+ exists in the +parameters+ hash.
|
171
|
+
def has_parameter?(key)
|
172
|
+
@params.has_parameter?(key)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Reads data into this data object.
|
176
|
+
def read(io)
|
177
|
+
io = BinData::IO.new(io) unless BinData::IO === io
|
178
|
+
|
179
|
+
@in_read = true
|
180
|
+
clear
|
181
|
+
do_read(io)
|
182
|
+
@in_read = false
|
183
|
+
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
#:nodoc:
|
188
|
+
attr_reader :in_read
|
189
|
+
protected :in_read
|
190
|
+
|
191
|
+
# Returns if this object is currently being read. This is used
|
192
|
+
# internally by BasePrimitive.
|
193
|
+
def reading? #:nodoc:
|
194
|
+
furthest_ancestor.in_read
|
195
|
+
end
|
196
|
+
protected :reading?
|
197
|
+
|
198
|
+
# Writes the value for this data object to +io+.
|
199
|
+
def write(io)
|
200
|
+
io = BinData::IO.new(io) unless BinData::IO === io
|
201
|
+
|
202
|
+
do_write(io)
|
203
|
+
io.flush
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns the number of bytes it will take to write this data object.
|
208
|
+
def num_bytes
|
209
|
+
do_num_bytes.ceil
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns the string representation of this data object.
|
213
|
+
def to_binary_s
|
214
|
+
io = BinData::IO.create_string_io
|
215
|
+
write(io)
|
216
|
+
io.rewind
|
217
|
+
io.read
|
218
|
+
end
|
219
|
+
|
220
|
+
# Return a human readable representation of this data object.
|
221
|
+
def inspect
|
222
|
+
snapshot.inspect
|
223
|
+
end
|
224
|
+
|
225
|
+
# Return a string representing this data object.
|
226
|
+
def to_s
|
227
|
+
snapshot.to_s
|
228
|
+
end
|
229
|
+
|
230
|
+
# Work with Ruby's pretty-printer library.
|
231
|
+
def pretty_print(pp) #:nodoc:
|
232
|
+
pp.pp(snapshot)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Override and delegate =~ as it is defined in Object.
|
236
|
+
def =~(other)
|
237
|
+
snapshot =~ other
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns a user friendly name of this object for debugging purposes.
|
241
|
+
def debug_name
|
242
|
+
if @parent
|
243
|
+
@parent.debug_name_of(self)
|
244
|
+
else
|
245
|
+
"obj"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Returns the offset of this object wrt to its most distant ancestor.
|
250
|
+
def offset
|
251
|
+
if @parent
|
252
|
+
@parent.offset + @parent.offset_of(self)
|
253
|
+
else
|
254
|
+
0
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Returns the offset of this object wrt to its parent.
|
259
|
+
def rel_offset
|
260
|
+
if @parent
|
261
|
+
@parent.offset_of(self)
|
262
|
+
else
|
263
|
+
0
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def ==(other) #:nodoc:
|
268
|
+
# double dispatch
|
269
|
+
other == snapshot
|
270
|
+
end
|
271
|
+
|
272
|
+
# A version of +respond_to?+ used by the lazy evaluator. It doesn't
|
273
|
+
# reinvoke the evaluator so as to avoid infinite evaluation loops.
|
274
|
+
def safe_respond_to?(symbol, include_private = false) #:nodoc:
|
275
|
+
respond_to?(symbol, include_private)
|
276
|
+
end
|
277
|
+
alias_method :orig_respond_to?, :respond_to? #:nodoc:
|
278
|
+
|
279
|
+
#---------------
|
280
|
+
private
|
281
|
+
|
282
|
+
def extract_args(the_args)
|
283
|
+
self.class.arg_extractor.extract(self.class, the_args)
|
284
|
+
end
|
285
|
+
|
286
|
+
def register_prototype
|
287
|
+
if has_parameter?(:name)
|
288
|
+
RegisteredClasses.register(get_parameter(:name), self)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def furthest_ancestor
|
293
|
+
if parent.nil?
|
294
|
+
self
|
295
|
+
else
|
296
|
+
an = parent
|
297
|
+
an = an.parent while an.parent
|
298
|
+
an
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def binary_string(str)
|
303
|
+
if str.respond_to?(:force_encoding)
|
304
|
+
str.dup.force_encoding(Encoding::BINARY)
|
305
|
+
else
|
306
|
+
str.dup
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
###########################################################################
|
311
|
+
# To be implemented by subclasses
|
312
|
+
|
313
|
+
# Performs sanity checks on the given parameters. This method converts
|
314
|
+
# the parameters to the form expected by this data object.
|
315
|
+
def self.sanitize_parameters!(parameters) #:nodoc:
|
316
|
+
end
|
317
|
+
|
318
|
+
# Initializes the state of the object. All instance variables that
|
319
|
+
# are used by the object must be initialized here.
|
320
|
+
def initialize_instance
|
321
|
+
end
|
322
|
+
|
323
|
+
# Initialises state that is shared by objects with the same parameters.
|
324
|
+
#
|
325
|
+
# This should only be used when optimising for performance. Instance
|
326
|
+
# variables set here, and changes to the singleton class will be shared
|
327
|
+
# between all objects that are initialized with the same parameters.
|
328
|
+
# This method is called only once for a particular set of parameters.
|
329
|
+
def initialize_shared_instance
|
330
|
+
end
|
331
|
+
|
332
|
+
# Resets the internal state to that of a newly created object.
|
333
|
+
def clear
|
334
|
+
raise NotImplementedError
|
335
|
+
end
|
336
|
+
|
337
|
+
# Returns true if the object has not been changed since creation.
|
338
|
+
def clear?
|
339
|
+
raise NotImplementedError
|
340
|
+
end
|
341
|
+
|
342
|
+
# Assigns the value of +val+ to this data object. Note that +val+ must
|
343
|
+
# always be deep copied to ensure no aliasing problems can occur.
|
344
|
+
def assign(val)
|
345
|
+
raise NotImplementedError
|
346
|
+
end
|
347
|
+
|
348
|
+
# Returns a snapshot of this data object.
|
349
|
+
def snapshot
|
350
|
+
raise NotImplementedError
|
351
|
+
end
|
352
|
+
|
353
|
+
# Returns the debug name of +child+. This only needs to be implemented
|
354
|
+
# by objects that contain child objects.
|
355
|
+
def debug_name_of(child) #:nodoc:
|
356
|
+
debug_name
|
357
|
+
end
|
358
|
+
|
359
|
+
# Returns the offset of +child+. This only needs to be implemented
|
360
|
+
# by objects that contain child objects.
|
361
|
+
def offset_of(child) #:nodoc:
|
362
|
+
0
|
363
|
+
end
|
364
|
+
|
365
|
+
# Reads the data for this data object from +io+.
|
366
|
+
def do_read(io) #:nodoc:
|
367
|
+
raise NotImplementedError
|
368
|
+
end
|
369
|
+
|
370
|
+
# Writes the value for this data to +io+.
|
371
|
+
def do_write(io) #:nodoc:
|
372
|
+
raise NotImplementedError
|
373
|
+
end
|
374
|
+
|
375
|
+
# Returns the number of bytes it will take to write this data.
|
376
|
+
def do_num_bytes #:nodoc:
|
377
|
+
raise NotImplementedError
|
378
|
+
end
|
379
|
+
|
380
|
+
# Set visibility requirements of methods to implement
|
381
|
+
public :clear, :clear?, :assign, :snapshot, :debug_name_of, :offset_of
|
382
|
+
protected :initialize_instance, :initialize_shared_instance
|
383
|
+
protected :do_read, :do_write, :do_num_bytes
|
384
|
+
|
385
|
+
# End To be implemented by subclasses
|
386
|
+
###########################################################################
|
387
|
+
end
|
388
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'bindata/base'
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# A BinData::BasePrimitive object is a container for a value that has a
|
5
|
+
# particular binary representation. A value corresponds to a primitive type
|
6
|
+
# such as as integer, float or string. Only one value can be contained by
|
7
|
+
# this object. This value can be read from or written to an IO stream.
|
8
|
+
#
|
9
|
+
# require 'bindata'
|
10
|
+
#
|
11
|
+
# obj = BinData::Uint8.new(:initial_value => 42)
|
12
|
+
# obj #=> 42
|
13
|
+
# obj.assign(5)
|
14
|
+
# obj #=> 5
|
15
|
+
# obj.clear
|
16
|
+
# obj #=> 42
|
17
|
+
#
|
18
|
+
# obj = BinData::Uint8.new(:value => 42)
|
19
|
+
# obj #=> 42
|
20
|
+
# obj.assign(5)
|
21
|
+
# obj #=> 42
|
22
|
+
#
|
23
|
+
# obj = BinData::Uint8.new(:check_value => 3)
|
24
|
+
# obj.read("\005") #=> BinData::ValidityError: value is '5' but expected '3'
|
25
|
+
#
|
26
|
+
# obj = BinData::Uint8.new(:check_value => lambda { value < 5 })
|
27
|
+
# obj.read("\007") #=> BinData::ValidityError: value not as expected
|
28
|
+
#
|
29
|
+
# == Parameters
|
30
|
+
#
|
31
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
32
|
+
# an object. These params include those for BinData::Base as well as:
|
33
|
+
#
|
34
|
+
# [<tt>:initial_value</tt>] This is the initial value to use before one is
|
35
|
+
# either #read or explicitly set with #value=.
|
36
|
+
# [<tt>:value</tt>] The object will always have this value.
|
37
|
+
# Calls to #value= are ignored when
|
38
|
+
# using this param. While reading, #value
|
39
|
+
# will return the value of the data read from the
|
40
|
+
# IO, not the result of the <tt>:value</tt> param.
|
41
|
+
# [<tt>:check_value</tt>] Raise an error unless the value read in meets
|
42
|
+
# this criteria. The variable +value+ is made
|
43
|
+
# available to any lambda assigned to this
|
44
|
+
# parameter. A boolean return indicates success
|
45
|
+
# or failure. Any other return is compared to
|
46
|
+
# the value just read in.
|
47
|
+
class BasePrimitive < BinData::Base
|
48
|
+
unregister_self
|
49
|
+
|
50
|
+
optional_parameters :initial_value, :value, :check_value
|
51
|
+
mutually_exclusive_parameters :initial_value, :value
|
52
|
+
|
53
|
+
def initialize_shared_instance
|
54
|
+
if has_parameter?(:check_value)
|
55
|
+
class << self
|
56
|
+
alias_method :do_read_without_check_value, :do_read
|
57
|
+
alias_method :do_read, :do_read_with_check_value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
if has_parameter?(:value)
|
61
|
+
class << self
|
62
|
+
alias_method :_value, :_value_with_value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
if has_parameter?(:initial_value)
|
66
|
+
class << self
|
67
|
+
alias_method :_value, :_value_with_initial_value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize_instance
|
73
|
+
@value = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def clear #:nodoc:
|
77
|
+
@value = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def clear? #:nodoc:
|
81
|
+
@value.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
def assign(val)
|
85
|
+
raise ArgumentError, "can't set a nil value for #{debug_name}" if val.nil?
|
86
|
+
|
87
|
+
unless has_parameter?(:value)
|
88
|
+
raw_val = val.respond_to?(:snapshot) ? val.snapshot : val
|
89
|
+
@value = begin
|
90
|
+
raw_val.dup
|
91
|
+
rescue TypeError
|
92
|
+
# can't dup Fixnums
|
93
|
+
raw_val
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def snapshot
|
99
|
+
_value
|
100
|
+
end
|
101
|
+
|
102
|
+
def value
|
103
|
+
snapshot
|
104
|
+
end
|
105
|
+
alias_method :value=, :assign
|
106
|
+
|
107
|
+
def respond_to?(symbol, include_private = false) #:nodoc:
|
108
|
+
child = snapshot
|
109
|
+
child.respond_to?(symbol, include_private) || super
|
110
|
+
end
|
111
|
+
|
112
|
+
def method_missing(symbol, *args, &block) #:nodoc:
|
113
|
+
child = snapshot
|
114
|
+
if child.respond_to?(symbol)
|
115
|
+
child.__send__(symbol, *args, &block)
|
116
|
+
else
|
117
|
+
super
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def <=>(other)
|
122
|
+
snapshot <=> other
|
123
|
+
end
|
124
|
+
|
125
|
+
def eql?(other)
|
126
|
+
# double dispatch
|
127
|
+
other.eql?(snapshot)
|
128
|
+
end
|
129
|
+
|
130
|
+
def hash
|
131
|
+
snapshot.hash
|
132
|
+
end
|
133
|
+
|
134
|
+
def do_read(io) #:nodoc:
|
135
|
+
@value = read_and_return_value(io)
|
136
|
+
hook_after_do_read
|
137
|
+
end
|
138
|
+
|
139
|
+
def do_read_with_check_value(io) #:nodoc:
|
140
|
+
do_read_without_check_value(io)
|
141
|
+
check_value(snapshot)
|
142
|
+
end
|
143
|
+
|
144
|
+
def do_write(io) #:nodoc:
|
145
|
+
io.writebytes(value_to_binary_string(_value))
|
146
|
+
end
|
147
|
+
|
148
|
+
def do_num_bytes #:nodoc:
|
149
|
+
value_to_binary_string(_value).length
|
150
|
+
end
|
151
|
+
|
152
|
+
#---------------
|
153
|
+
private
|
154
|
+
|
155
|
+
def hook_after_do_read; end
|
156
|
+
|
157
|
+
def check_value(current_value)
|
158
|
+
expected = eval_parameter(:check_value, :value => current_value)
|
159
|
+
if not expected
|
160
|
+
raise ValidityError,
|
161
|
+
"value '#{current_value}' not as expected for #{debug_name}"
|
162
|
+
elsif current_value != expected and expected != true
|
163
|
+
raise ValidityError,
|
164
|
+
"value is '#{current_value}' but " +
|
165
|
+
"expected '#{expected}' for #{debug_name}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# The unmodified value of this data object. Note that #snapshot calls this
|
170
|
+
# method. This indirection is so that #snapshot can be overridden in
|
171
|
+
# subclasses to modify the presentation value.
|
172
|
+
#
|
173
|
+
# Table of possible preconditions and expected outcome
|
174
|
+
# 1. :value and !reading? -> :value
|
175
|
+
# 2. :value and reading? -> @value
|
176
|
+
# 3. :initial_value and clear? -> :initial_value
|
177
|
+
# 4. :initial_value and !clear? -> @value
|
178
|
+
# 5. clear? -> sensible_default
|
179
|
+
# 6. !clear? -> @value
|
180
|
+
|
181
|
+
def _value
|
182
|
+
@value != nil ? @value : sensible_default()
|
183
|
+
end
|
184
|
+
|
185
|
+
def _value_with_value #:nodoc:
|
186
|
+
reading? ? @value : eval_parameter(:value)
|
187
|
+
end
|
188
|
+
|
189
|
+
def _value_with_initial_value #:nodoc:
|
190
|
+
@value != nil ? @value : eval_parameter(:initial_value)
|
191
|
+
end
|
192
|
+
|
193
|
+
###########################################################################
|
194
|
+
# To be implemented by subclasses
|
195
|
+
|
196
|
+
# Return the string representation that +val+ will take when written.
|
197
|
+
def value_to_binary_string(val)
|
198
|
+
raise NotImplementedError
|
199
|
+
end
|
200
|
+
|
201
|
+
# Read a number of bytes from +io+ and return the value they represent.
|
202
|
+
def read_and_return_value(io)
|
203
|
+
raise NotImplementedError
|
204
|
+
end
|
205
|
+
|
206
|
+
# Return a sensible default for this data.
|
207
|
+
def sensible_default
|
208
|
+
raise NotImplementedError
|
209
|
+
end
|
210
|
+
|
211
|
+
# To be implemented by subclasses
|
212
|
+
###########################################################################
|
213
|
+
end
|
214
|
+
end
|