bindata 1.2.2 → 1.3.1

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.

Potentially problematic release.


This version of bindata might be problematic. Click here for more details.

Files changed (52) hide show
  1. data/ChangeLog +12 -0
  2. data/NEWS +53 -0
  3. data/Rakefile +2 -1
  4. data/examples/NBT.txt +149 -0
  5. data/examples/ip_address.rb +1 -2
  6. data/examples/list.rb +124 -0
  7. data/examples/nbt.rb +195 -0
  8. data/lib/bindata.rb +4 -3
  9. data/lib/bindata/alignment.rb +86 -0
  10. data/lib/bindata/array.rb +21 -29
  11. data/lib/bindata/base.rb +82 -81
  12. data/lib/bindata/base_primitive.rb +66 -48
  13. data/lib/bindata/choice.rb +18 -28
  14. data/lib/bindata/deprecated.rb +17 -0
  15. data/lib/bindata/dsl.rb +25 -15
  16. data/lib/bindata/int.rb +2 -2
  17. data/lib/bindata/io.rb +8 -6
  18. data/lib/bindata/offset.rb +91 -0
  19. data/lib/bindata/primitive.rb +22 -11
  20. data/lib/bindata/record.rb +40 -10
  21. data/lib/bindata/sanitize.rb +15 -30
  22. data/lib/bindata/string.rb +16 -17
  23. data/lib/bindata/stringz.rb +0 -1
  24. data/lib/bindata/struct.rb +17 -6
  25. data/lib/bindata/trace.rb +52 -0
  26. data/lib/bindata/wrapper.rb +28 -6
  27. data/manual.haml +56 -10
  28. data/manual.md +318 -113
  29. data/spec/alignment_spec.rb +61 -0
  30. data/spec/array_spec.rb +139 -178
  31. data/spec/base_primitive_spec.rb +86 -111
  32. data/spec/base_spec.rb +200 -172
  33. data/spec/bits_spec.rb +45 -53
  34. data/spec/choice_spec.rb +91 -87
  35. data/spec/deprecated_spec.rb +36 -14
  36. data/spec/float_spec.rb +16 -68
  37. data/spec/int_spec.rb +26 -27
  38. data/spec/io_spec.rb +105 -105
  39. data/spec/lazy_spec.rb +50 -50
  40. data/spec/primitive_spec.rb +36 -36
  41. data/spec/record_spec.rb +134 -134
  42. data/spec/registry_spec.rb +34 -38
  43. data/spec/rest_spec.rb +8 -11
  44. data/spec/skip_spec.rb +9 -17
  45. data/spec/spec_common.rb +4 -0
  46. data/spec/string_spec.rb +92 -115
  47. data/spec/stringz_spec.rb +41 -74
  48. data/spec/struct_spec.rb +132 -153
  49. data/spec/system_spec.rb +115 -60
  50. data/spec/wrapper_spec.rb +63 -31
  51. data/tasks/pkg.rake +1 -1
  52. metadata +15 -7
@@ -1,5 +1,4 @@
1
1
  require 'bindata/base'
2
- require 'bindata/trace'
3
2
 
4
3
  module BinData
5
4
  # A Choice is a collection of data objects of which only one is active
@@ -13,26 +12,29 @@ module BinData
13
12
  #
14
13
  # choices = {5 => type1, 17 => type2}
15
14
  # a = BinData::Choice.new(:choices => choices, :selection => 5)
16
- # a.value # => "Type1"
15
+ # a # => "Type1"
17
16
  #
18
17
  # choices = [ type1, type2 ]
19
18
  # a = BinData::Choice.new(:choices => choices, :selection => 1)
20
- # a.value # => "Type2"
19
+ # a # => "Type2"
21
20
  #
22
21
  # choices = [ nil, nil, nil, type1, nil, type2 ]
23
22
  # a = BinData::Choice.new(:choices => choices, :selection => 3)
24
- # a.value # => "Type1"
23
+ # a # => "Type1"
24
+ #
25
+ #
26
+ # Chooser = Struct.new(:choice)
27
+ # mychoice = Chooser.new
28
+ # mychoice.choice = 'big'
25
29
  #
26
- # mychoice = 'big'
27
30
  # choices = {'big' => :uint16be, 'little' => :uint16le}
28
- # a = BinData::Choice.new(:choices => choices,
29
- # :selection => lambda { mychoice })
30
- # a.value = 256
31
+ # a = BinData::Choice.new(:choices => choices, :copy_on_change => true,
32
+ # :selection => lambda { mychoice.choice })
33
+ # a.assign(256)
31
34
  # a.to_binary_s #=> "\001\000"
32
- # mychoice.replace 'little'
33
- # a.selection #=> 'little'
34
- # a.to_binary_s #=> "\000\001"
35
35
  #
36
+ # mychoice.choice = 'little'
37
+ # a.to_binary_s #=> "\000\001"
36
38
  #
37
39
  # == Parameters
38
40
  #
@@ -98,9 +100,7 @@ module BinData
98
100
  end
99
101
  end
100
102
 
101
- def initialize(parameters = {}, parent = nil)
102
- super
103
-
103
+ def initialize_instance
104
104
  @choices = {}
105
105
  @last_selection = nil
106
106
  end
@@ -162,7 +162,7 @@ module BinData
162
162
  end
163
163
 
164
164
  def do_read(io) #:nodoc:
165
- trace_selection
165
+ hook_before_do_read
166
166
  current_choice.do_read(io)
167
167
  end
168
168
 
@@ -177,12 +177,7 @@ module BinData
177
177
  #---------------
178
178
  private
179
179
 
180
- def trace_selection
181
- BinData::trace_message do |tracer|
182
- selection_string = eval_parameter(:selection).inspect
183
- tracer.trace_obj("#{debug_name}-selection-", selection_string)
184
- end
185
- end
180
+ def hook_before_do_read; end
186
181
 
187
182
  def current_choice
188
183
  selection = eval_parameter(:selection)
@@ -197,12 +192,7 @@ module BinData
197
192
  end
198
193
 
199
194
  def get_or_instantiate_choice(selection)
200
- obj = @choices[selection]
201
- if obj.nil?
202
- obj = instantiate_choice(selection)
203
- @choices[selection] = obj
204
- end
205
- obj
195
+ @choices[selection] ||= instantiate_choice(selection)
206
196
  end
207
197
 
208
198
  def instantiate_choice(selection)
@@ -210,7 +200,7 @@ module BinData
210
200
  if prototype.nil?
211
201
  raise IndexError, "selection '#{selection}' does not exist in :choices for #{debug_name}"
212
202
  end
213
- prototype.instantiate(self)
203
+ prototype.instantiate(nil, self)
214
204
  end
215
205
 
216
206
  def copy_previous_value_if_required(selection, obj)
@@ -17,6 +17,23 @@ end
17
17
 
18
18
  module BinData
19
19
  class Base
20
+
21
+ alias_method :initialize_without_deprecation, :initialize
22
+ def initialize_with_deprecation(*args)
23
+ owner = method(:initialize).owner
24
+ if owner != BinData::Base
25
+ fail "implementing #initialize on #{owner} is not allowed.\nEither downgrade to BinData 1.2.2, or rename #initialize to #initialize_instance."
26
+ end
27
+ initialize_without_deprecation(*args)
28
+ end
29
+ alias_method :initialize, :initialize_with_deprecation
30
+
31
+ def initialize_instance(*args)
32
+ unless args.empty?
33
+ warn "#{caller[0]} remove the call to super in #initialize_instance"
34
+ end
35
+ end
36
+
20
37
  class << self
21
38
  def register(name, class_to_register)
22
39
  if class_to_register == self
@@ -13,6 +13,7 @@ module BinData
13
13
  # [<tt>:sanitize_fields</tt>] Fields are to be sanitized.
14
14
  # [<tt>:mandatory_fieldnames</tt>] Fieldnames are mandatory.
15
15
  # [<tt>:optional_fieldnames</tt>] Fieldnames are optional.
16
+ # [<tt>:fieldnames_for_choices</tt>] Fieldnames are choice keys.
16
17
  # [<tt>:no_fieldnames</tt>] Fieldnames are prohibited.
17
18
  # [<tt>:all_or_none_fieldnames</tt>] All fields must have names, or
18
19
  # none may have names.
@@ -108,7 +109,13 @@ module BinData
108
109
 
109
110
  def hide(*args)
110
111
  if option?(:hidden_fields)
111
- @hide.concat(args.collect { |name| name.to_s })
112
+ hidden = args.collect do |name|
113
+ unless Symbol === name
114
+ warn "Hidden field '#{name}' should be provided as a symbol. Using strings is deprecated"
115
+ end
116
+ name.to_sym
117
+ end
118
+ @hide.concat(hidden.compact)
112
119
  @hide
113
120
  end
114
121
  end
@@ -187,9 +194,10 @@ module BinData
187
194
 
188
195
  def name_from_field_declaration(args)
189
196
  name, params = args
190
- name = nil if name.is_a?(Hash)
197
+ name = "" if name.nil? or name.is_a?(Hash)
198
+ name = name.to_s if name.is_a?(Symbol)
191
199
 
192
- name.to_s
200
+ name
193
201
  end
194
202
 
195
203
  def params_from_field_declaration(type, args, &block)
@@ -234,20 +242,22 @@ module BinData
234
242
  dsl_raise SyntaxError, "field must have a name"
235
243
  end
236
244
 
237
- if malformed_name?(name)
238
- dsl_raise NameError.new("", name), "field '#{name}' is an illegal fieldname"
239
- end
245
+ unless option?(:fieldnames_for_choices)
246
+ if malformed_name?(name)
247
+ dsl_raise NameError.new("", name), "field '#{name}' is an illegal fieldname"
248
+ end
240
249
 
241
- if duplicate_name?(name)
242
- dsl_raise SyntaxError, "duplicate field '#{name}'"
243
- end
250
+ if duplicate_name?(name)
251
+ dsl_raise SyntaxError, "duplicate field '#{name}'"
252
+ end
244
253
 
245
- if name_shadows_method?(name)
246
- dsl_raise NameError.new("", name), "field '#{name}' shadows an existing method"
247
- end
254
+ if name_shadows_method?(name)
255
+ dsl_raise NameError.new("", name), "field '#{name}' shadows an existing method"
256
+ end
248
257
 
249
- if name_is_reserved?(name)
250
- dsl_raise NameError.new("", name), "field '#{name}' is a reserved name"
258
+ if name_is_reserved?(name)
259
+ dsl_raise NameError.new("", name), "field '#{name}' is a reserved name"
260
+ end
251
261
  end
252
262
  end
253
263
 
@@ -317,7 +327,7 @@ module BinData
317
327
 
318
328
  class ChoiceBlockParser
319
329
  def self.extract_params(endian, &block)
320
- parser = DSLParser.new(BinData::Choice, :multiple_fields, :all_or_none_fieldnames)
330
+ parser = DSLParser.new(BinData::Choice, :multiple_fields, :all_or_none_fieldnames, :fieldnames_for_choices)
321
331
  parser.endian endian
322
332
  parser.instance_eval(&block)
323
333
 
@@ -56,6 +56,7 @@ module BinData
56
56
  def read_and_return_value(io)
57
57
  val = #{create_read_code(nbits, endian)}
58
58
  #{create_uint2int_code(nbits) if signed == :signed}
59
+ val
59
60
  end
60
61
  END
61
62
  end
@@ -82,8 +83,7 @@ module BinData
82
83
  def create_uint2int_code(nbits)
83
84
  mask = (1 << (nbits - 1)) - 1
84
85
 
85
- "val = ((val & #{1 << (nbits - 1)}).zero?) ? " +
86
- "val & #{mask} : -(((~val) & #{mask}) + 1)"
86
+ "val = -(((~val) & #{mask}) + 1) if (val >= #{1 << (nbits - 1)})"
87
87
  end
88
88
 
89
89
  def create_read_code(nbits, endian)
@@ -111,6 +111,14 @@ module BinData
111
111
  end
112
112
  end
113
113
 
114
+ # Discards any read bits so the stream becomes aligned at the
115
+ # next byte boundary.
116
+ def reset_read_bits
117
+ raise "Internal state error nbits = #{@rnbits}" if @rnbits >= 8
118
+ @rnbits = 0
119
+ @rval = 0
120
+ end
121
+
114
122
  # Writes the given string of bytes to the io stream.
115
123
  def writebytes(str)
116
124
  flushbits
@@ -160,12 +168,6 @@ module BinData
160
168
  @positioning_supported
161
169
  end
162
170
 
163
- def reset_read_bits
164
- raise "Internal state error nbits = #{@rnbits}" if @rnbits >= 8
165
- @rnbits = 0
166
- @rval = 0
167
- end
168
-
169
171
  def skipbytes(n)
170
172
  # skip over data in 8k blocks
171
173
  while n > 0
@@ -0,0 +1,91 @@
1
+ module BinData
2
+ # == Parameters
3
+ #
4
+ # Parameters may be provided at initialisation to control the behaviour of
5
+ # an object. These parameters are:
6
+ #
7
+ # [<tt>:check_offset</tt>] Raise an error if the current IO offset doesn't
8
+ # meet this criteria. A boolean return indicates
9
+ # success or failure. Any other return is compared
10
+ # to the current offset. The variable +offset+
11
+ # is made available to any lambda assigned to
12
+ # this parameter. This parameter is only checked
13
+ # before reading.
14
+ # [<tt>:adjust_offset</tt>] Ensures that the current IO offset is at this
15
+ # position before reading. This is like
16
+ # <tt>:check_offset</tt>, except that it will
17
+ # adjust the IO offset instead of raising an error.
18
+ module CheckOrAdjustOffsetMixin
19
+
20
+ def self.included(base) #:nodoc:
21
+ base.optional_parameters :check_offset, :adjust_offset
22
+ base.mutually_exclusive_parameters :check_offset, :adjust_offset
23
+ end
24
+
25
+ # Ideally these two methods should be protected,
26
+ # but Ruby 1.9.2 requires them to be public.
27
+ # see http://redmine.ruby-lang.org/issues/show/2375
28
+
29
+ def do_read_with_check_offset(io) #:nodoc:
30
+ check_offset(io)
31
+ do_read_without_check_offset(io)
32
+ end
33
+
34
+ def do_read_with_adjust_offset(io) #:nodoc:
35
+ adjust_offset(io)
36
+ do_read_without_adjust_offset(io)
37
+ end
38
+
39
+ #---------------
40
+ private
41
+
42
+ # To be called from BinData::Base#initialize.
43
+ #
44
+ # Monkey patches #do_read to check or adjust the stream offset
45
+ # as appropriate.
46
+ def add_methods_for_check_or_adjust_offset
47
+ if has_parameter?(:check_offset)
48
+ class << self
49
+ alias_method :do_read_without_check_offset, :do_read
50
+ alias_method :do_read, :do_read_with_check_offset
51
+ end
52
+ end
53
+ if has_parameter?(:adjust_offset)
54
+ class << self
55
+ alias_method :do_read_without_adjust_offset, :do_read
56
+ alias_method :do_read, :do_read_with_adjust_offset
57
+ end
58
+ end
59
+ end
60
+
61
+ def check_offset(io)
62
+ actual_offset = io.offset
63
+ expected = eval_parameter(:check_offset, :offset => actual_offset)
64
+
65
+ if not expected
66
+ raise ValidityError, "offset not as expected for #{debug_name}"
67
+ elsif actual_offset != expected and expected != true
68
+ raise ValidityError,
69
+ "offset is '#{actual_offset}' but " +
70
+ "expected '#{expected}' for #{debug_name}"
71
+ end
72
+ end
73
+
74
+ def adjust_offset(io)
75
+ actual_offset = io.offset
76
+ expected = eval_parameter(:adjust_offset)
77
+ if actual_offset != expected
78
+ begin
79
+ seek = expected - actual_offset
80
+ io.seekbytes(seek)
81
+ warn "adjusting stream position by #{seek} bytes" if $VERBOSE
82
+ rescue
83
+ raise ValidityError,
84
+ "offset is '#{actual_offset}' but couldn't seek to " +
85
+ "expected '#{expected}' for #{debug_name}"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
@@ -29,7 +29,7 @@ module BinData
29
29
  # ps = PascalString.new(:initial_value => "hello")
30
30
  # ps.to_binary_s #=> "\005hello"
31
31
  # ps.read("\003abcde")
32
- # ps.value #=> "abc"
32
+ # ps #=> "abc"
33
33
  #
34
34
  # # Unsigned 24 bit big endian integer
35
35
  # class Uint24be < BinData::Primitive
@@ -53,7 +53,7 @@ module BinData
53
53
  #
54
54
  # u24 = Uint24be.new
55
55
  # u24.read("\x12\x34\x56")
56
- # "0x%x" % u24.value #=> 0x123456
56
+ # "0x%x" % u24 #=> 0x123456
57
57
  #
58
58
  # == Parameters
59
59
  #
@@ -73,20 +73,36 @@ module BinData
73
73
 
74
74
  mandatory_parameter :struct_params
75
75
 
76
- def initialize(parameters = {}, parent = nil)
77
- super
78
-
76
+ def initialize_instance
79
77
  @struct = BinData::Struct.new(get_parameter(:struct_params), self)
80
78
  end
81
79
 
80
+ def respond_to?(symbol, include_private = false) #:nodoc:
81
+ @struct.respond_to?(symbol, include_private) || super
82
+ end
83
+
82
84
  def method_missing(symbol, *args, &block) #:nodoc:
83
- @struct.__send__(symbol, *args, &block)
85
+ if @struct.respond_to?(symbol)
86
+ @struct.__send__(symbol, *args, &block)
87
+ else
88
+ super
89
+ end
84
90
  end
85
91
 
86
92
  def debug_name_of(child) #:nodoc:
87
93
  debug_name + "-internal-"
88
94
  end
89
95
 
96
+ def do_write(io)
97
+ set(_value)
98
+ @struct.do_write(io)
99
+ end
100
+
101
+ def do_num_bytes
102
+ set(_value)
103
+ @struct.do_num_bytes
104
+ end
105
+
90
106
  #---------------
91
107
  private
92
108
 
@@ -99,11 +115,6 @@ module BinData
99
115
  get
100
116
  end
101
117
 
102
- def value_to_binary_string(val)
103
- set(val)
104
- @struct.to_binary_s
105
- end
106
-
107
118
  ###########################################################################
108
119
  # To be implemented by subclasses
109
120
 
@@ -3,6 +3,28 @@ require 'bindata/sanitize'
3
3
  require 'bindata/struct'
4
4
 
5
5
  module BinData
6
+ class RecordArgExtractor
7
+ def self.extract(the_class, the_args)
8
+ value, parameters, parent = BaseArgExtractor.extract(the_class, the_args)
9
+
10
+ if value.nil? and parameters.length > 0
11
+ if field_names_in_parameters(the_class, parameters).length > 0
12
+ value = parameters
13
+ parameters = nil
14
+ end
15
+ end
16
+
17
+ [value, parameters, parent]
18
+ end
19
+
20
+ def self.field_names_in_parameters(the_class, parameters)
21
+ field_names = the_class.fields.field_names.collect { |k| k.to_s }
22
+ param_keys = parameters.keys.collect { |k| k.to_s }
23
+
24
+ (field_names & param_keys)
25
+ end
26
+ end
27
+
6
28
  # A Record is a declarative wrapper around Struct.
7
29
  #
8
30
  # require 'bindata'
@@ -49,6 +71,11 @@ module BinData
49
71
  dsl_parser :multiple_fields, :optional_fieldnames, :sanitize_fields, :hidden_fields
50
72
 
51
73
  class << self
74
+
75
+ def arg_extractor
76
+ RecordArgExtractor
77
+ end
78
+
52
79
  def sanitize_parameters!(params, sanitizer) #:nodoc:
53
80
  params[:fields] = fields
54
81
  params[:endian] = endian unless endian.nil?
@@ -65,22 +92,25 @@ module BinData
65
92
  def define_field_accessors(fields) #:nodoc:
66
93
  unless method_defined?(:bindata_defined_accessors_for_fields?)
67
94
  fields.each_with_index do |field, i|
68
- name = field.name
69
- if name
70
- define_method(name.to_sym) do
71
- instantiate_obj_at(i) unless @field_objs[i]
72
- @field_objs[i]
73
- end
74
- define_method((name + "=").to_sym) do |*vals|
75
- instantiate_obj_at(i) unless @field_objs[i]
76
- @field_objs[i].assign(*vals)
77
- end
95
+ if field.name
96
+ define_field_accessors_for(field.name, i)
78
97
  end
79
98
  end
80
99
 
81
100
  define_method(:bindata_defined_accessors_for_fields?) { true }
82
101
  end
83
102
  end
103
+
104
+ def define_field_accessors_for(name, index)
105
+ define_method(name.to_sym) do
106
+ instantiate_obj_at(index) unless @field_objs[index]
107
+ @field_objs[index]
108
+ end
109
+ define_method((name + "=").to_sym) do |*vals|
110
+ instantiate_obj_at(index) unless @field_objs[index]
111
+ @field_objs[index].assign(*vals)
112
+ end
113
+ end
84
114
  end
85
115
  end
86
116
  end