bindata 1.1.0 → 1.2.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.

Files changed (47) hide show
  1. data/ChangeLog +7 -0
  2. data/README +32 -1167
  3. data/lib/bindata.rb +3 -3
  4. data/lib/bindata/array.rb +5 -6
  5. data/lib/bindata/base.rb +40 -58
  6. data/lib/bindata/base_primitive.rb +7 -11
  7. data/lib/bindata/bits.rb +47 -44
  8. data/lib/bindata/choice.rb +7 -11
  9. data/lib/bindata/deprecated.rb +17 -2
  10. data/lib/bindata/dsl.rb +332 -0
  11. data/lib/bindata/float.rb +48 -50
  12. data/lib/bindata/int.rb +66 -88
  13. data/lib/bindata/params.rb +112 -59
  14. data/lib/bindata/primitive.rb +8 -88
  15. data/lib/bindata/record.rb +11 -99
  16. data/lib/bindata/registry.rb +16 -3
  17. data/lib/bindata/rest.rb +1 -1
  18. data/lib/bindata/sanitize.rb +71 -53
  19. data/lib/bindata/skip.rb +2 -1
  20. data/lib/bindata/string.rb +3 -3
  21. data/lib/bindata/stringz.rb +1 -1
  22. data/lib/bindata/struct.rb +21 -20
  23. data/lib/bindata/trace.rb +8 -0
  24. data/lib/bindata/wrapper.rb +13 -69
  25. data/manual.haml +2 -2
  26. data/spec/array_spec.rb +1 -1
  27. data/spec/base_primitive_spec.rb +4 -4
  28. data/spec/base_spec.rb +19 -6
  29. data/spec/bits_spec.rb +5 -1
  30. data/spec/choice_spec.rb +13 -2
  31. data/spec/deprecated_spec.rb +31 -0
  32. data/spec/example.rb +5 -1
  33. data/spec/io_spec.rb +2 -4
  34. data/spec/lazy_spec.rb +10 -5
  35. data/spec/primitive_spec.rb +13 -5
  36. data/spec/record_spec.rb +149 -45
  37. data/spec/registry_spec.rb +18 -6
  38. data/spec/spec_common.rb +31 -6
  39. data/spec/string_spec.rb +0 -1
  40. data/spec/stringz_spec.rb +4 -4
  41. data/spec/struct_spec.rb +2 -2
  42. data/spec/system_spec.rb +26 -19
  43. data/spec/wrapper_spec.rb +52 -4
  44. data/tasks/manual.rake +1 -1
  45. data/tasks/pkg.rake +13 -0
  46. metadata +121 -46
  47. data/TODO +0 -3
@@ -1,3 +1,4 @@
1
+ require 'bindata/dsl'
1
2
  require 'bindata/sanitize'
2
3
  require 'bindata/struct'
3
4
 
@@ -6,22 +7,21 @@ module BinData
6
7
  #
7
8
  # require 'bindata'
8
9
  #
9
- # class Tuple < BinData::Record
10
- # int8 :x
11
- # int8 :y
12
- # int8 :z
13
- # end
14
- #
15
10
  # class SomeDataType < BinData::Record
16
11
  # hide 'a'
17
12
  #
18
13
  # int32le :a
19
14
  # int16le :b
20
- # tuple :s
15
+ # struct :s do
16
+ # int8 :x
17
+ # int8 :y
18
+ # int8 :z
19
+ # end
21
20
  # end
22
21
  #
23
22
  # obj = SomeDataType.new
24
23
  # obj.field_names =># ["b", "s"]
24
+ # obj.s.field_names =># ["x", "y", "z"]
25
25
  #
26
26
  #
27
27
  # == Parameters
@@ -43,50 +43,12 @@ module BinData
43
43
  # endian of any numerics in this struct, or in any
44
44
  # nested data objects.
45
45
  class Record < BinData::Struct
46
+ include DSLMixin
46
47
 
47
- class << self
48
-
49
- def inherited(subclass) #:nodoc:
50
- # Register the names of all subclasses of this class.
51
- register(subclass.name, subclass)
52
- end
53
-
54
- def endian(endian = nil)
55
- @endian ||= default_endian
56
- if [:little, :big].include?(endian)
57
- @endian = endian
58
- elsif endian != nil
59
- raise ArgumentError,
60
- "unknown value for endian '#{endian}' in #{self}", caller(1)
61
- end
62
- @endian
63
- end
64
-
65
- def hide(*args)
66
- @hide ||= default_hide
67
- @hide.concat(args.collect { |name| name.to_s })
68
- @hide
69
- end
70
-
71
- def fields #:nodoc:
72
- @fields ||= default_fields
73
- end
74
-
75
- def method_missing(symbol, *args) #:nodoc:
76
- name, params = args
77
-
78
- if name.is_a?(Hash)
79
- params = name
80
- name = nil
81
- end
82
-
83
- type = symbol
84
- name = name.to_s
85
- params ||= {}
86
-
87
- append_field(type, name, params)
88
- end
48
+ register_subclasses
49
+ dsl_parser :multiple_fields, :optional_fieldnames, :sanitize_fields, :hidden_fields
89
50
 
51
+ class << self
90
52
  def sanitize_parameters!(params, sanitizer) #:nodoc:
91
53
  params[:fields] = fields
92
54
  params[:endian] = endian unless endian.nil?
@@ -94,56 +56,6 @@ module BinData
94
56
 
95
57
  super(params, sanitizer)
96
58
  end
97
-
98
- #-------------
99
- private
100
-
101
- def parent_record
102
- ancestors[1..-1].find { |cls|
103
- cls.ancestors[1..-1].include?(BinData::Record)
104
- }
105
- end
106
-
107
- def default_endian
108
- rec = parent_record
109
- rec ? rec.endian : nil
110
- end
111
-
112
- def default_hide
113
- rec = parent_record
114
- rec ? rec.hide.dup : []
115
- end
116
-
117
- def default_fields
118
- rec = parent_record
119
- if rec
120
- Sanitizer.new.clone_sanitized_fields(rec.fields)
121
- else
122
- Sanitizer.new.create_sanitized_fields
123
- end
124
- end
125
-
126
- def append_field(type, name, params)
127
- ensure_valid_name(name)
128
-
129
- fields.add_field(type, name, params, endian)
130
- rescue UnknownTypeError => err
131
- raise TypeError, "unknown type '#{err.message}' for #{self}", caller(2)
132
- end
133
-
134
- def ensure_valid_name(name)
135
- if fields.field_names.include?(name)
136
- raise SyntaxError, "duplicate field '#{name}' in #{self}", caller(3)
137
- end
138
- if self.instance_methods.collect { |meth| meth.to_s }.include?(name)
139
- raise NameError.new("", name),
140
- "field '#{name}' shadows an existing method in #{self}", caller(3)
141
- end
142
- if self::RESERVED.include?(name)
143
- raise NameError.new("", name),
144
- "field '#{name}' is a reserved name in #{self}", caller(3)
145
- end
146
- end
147
59
  end
148
60
  end
149
61
  end
@@ -1,6 +1,13 @@
1
1
  module BinData
2
+
3
+ class UnRegisteredTypeError < StandardError ; end
4
+
2
5
  # This registry contains a register of name -> class mappings.
3
6
  #
7
+ # Numerics (integers and floating point numbers) have an endian property as
8
+ # part of their name (e.g. int32be, float_le). The lookup can either be
9
+ # on the full name, or on the shortened name plus endian (e.g. "int32", :big)
10
+ #
4
11
  # Names are stored in under_score_style, not camelCase.
5
12
  class Registry
6
13
 
@@ -21,7 +28,13 @@ module BinData
21
28
  key = lookup_key(name, endian)
22
29
  try_registering_key(key) unless @registry.has_key?(key)
23
30
 
24
- @registry[key]
31
+ @registry[key] || raise(UnRegisteredTypeError, name.to_s)
32
+ end
33
+
34
+ def normalize_name(name, endian = nil)
35
+ if lookup(name, endian)
36
+ lookup_key(name, endian)
37
+ end
25
38
  end
26
39
 
27
40
  def is_registered?(name, endian = nil)
@@ -65,8 +78,8 @@ module BinData
65
78
  end
66
79
 
67
80
  def warn_if_name_is_already_registered(name, class_to_register)
68
- if $VERBOSE and @registry[name] != class_to_register
69
- prev_class = @registry[name]
81
+ prev_class = @registry[name]
82
+ if $VERBOSE and prev_class and prev_class != class_to_register
70
83
  warn "warning: replacing registered class #{prev_class} " +
71
84
  "with #{class_to_register}"
72
85
  end
@@ -17,7 +17,7 @@ module BinData
17
17
  #
18
18
  class Rest < BinData::BasePrimitive
19
19
 
20
- register(self.name, self)
20
+ register_self
21
21
 
22
22
  #---------------
23
23
  private
@@ -2,18 +2,25 @@ require 'bindata/registry'
2
2
 
3
3
  module BinData
4
4
 
5
- class UnknownTypeError < StandardError ; end
6
-
7
- # A BinData object accepts arbitrary parameters. This class sanitizes
8
- # those parameters so they can be used by the BinData object.
5
+ # When a BinData object is instantiated, it can be supplied parameters to
6
+ # determine its behaviour. These parameters must be sanitized to ensure
7
+ # their values are valid. When instantiating many objects, such as an array
8
+ # of records, there is much duplicated validation.
9
+ #
10
+ # The purpose of the sanitizing code is to eliminate the duplicated
11
+ # validation.
12
+ #
13
+ # SanitizedParameters is a hash-like collection of parameters. Its purpose
14
+ # it to recursively sanitize the parameters of an entire BinData object chain
15
+ # at a single time.
9
16
  class SanitizedParameters
10
17
 
11
- def initialize(params, the_class)
18
+ def initialize(parameters, the_class)
12
19
  @all_sanitized = false
13
20
  @the_class = the_class
14
21
 
15
22
  @parameters = {}
16
- params.each { |param, value| @parameters[param.to_sym] = value }
23
+ parameters.each { |key, value| @parameters[key.to_sym] = value }
17
24
 
18
25
  ensure_no_nil_values
19
26
  end
@@ -23,20 +30,22 @@ module BinData
23
30
  end
24
31
  alias_method :size, :length
25
32
 
26
- def [](param)
27
- @parameters[param]
33
+ def [](key)
34
+ @parameters[key]
28
35
  end
29
36
 
30
- def []=(param, value)
31
- @parameters[param] = value unless @all_sanitized
37
+ def []=(key, value)
38
+ @parameters[key] = value unless @all_sanitized
32
39
  end
33
40
 
34
- def has_parameter?(param)
35
- @parameters.has_key?(param)
41
+ def has_parameter?(key)
42
+ @parameters.has_key?(key)
36
43
  end
37
44
 
38
- def needs_sanitizing?(param)
39
- has_parameter?(param) and not self[param].is_a?(SanitizedParameter)
45
+ def needs_sanitizing?(key)
46
+ parameter = @parameters[key]
47
+
48
+ parameter and not parameter.is_a?(SanitizedParameter)
40
49
  end
41
50
 
42
51
  def all_sanitized?
@@ -59,40 +68,42 @@ module BinData
59
68
  def move_unknown_parameters_to(dest)
60
69
  unless @all_sanitized
61
70
  unused_keys = @parameters.keys - @the_class.accepted_parameters.all
62
- unused_keys.each do |param|
63
- next if param == :onlyif
64
- dest[param] = @parameters.delete(param)
71
+ unused_keys.each do |key|
72
+ dest[key] = @parameters.delete(key)
65
73
  end
66
74
  end
67
75
  end
68
76
 
69
- def delete(param)
70
- @parameters.delete(param)
77
+ def warn_replacement_parameter(bad_key, suggested_key)
78
+ if has_parameter?(bad_key)
79
+ warn ":#{bad_key} is not used with #{@the_class}. " +
80
+ "You probably want to change this to :#{suggested_key}"
81
+ end
71
82
  end
72
83
 
73
84
  #---------------
74
85
  private
75
86
 
76
87
  def ensure_no_nil_values
77
- @parameters.each do |param, value|
88
+ @parameters.each do |key, value|
78
89
  if value.nil?
79
90
  raise ArgumentError,
80
- "parameter '#{param}' has nil value in #{@the_class}"
91
+ "parameter '#{key}' has nil value in #{@the_class}"
81
92
  end
82
93
  end
83
94
  end
84
95
 
85
96
  def merge_default_parameters!
86
- @the_class.default_parameters.each do |param, value|
87
- self[param] ||= value
97
+ @the_class.default_parameters.each do |key, value|
98
+ @parameters[key] ||= value
88
99
  end
89
100
  end
90
101
 
91
102
  def ensure_mandatory_parameters_exist
92
- @the_class.mandatory_parameters.each do |param|
93
- unless has_parameter?(param)
103
+ @the_class.mandatory_parameters.each do |key|
104
+ unless has_parameter?(key)
94
105
  raise ArgumentError,
95
- "parameter '#{param}' must be specified in #{@the_class}"
106
+ "parameter '#{key}' must be specified in #{@the_class}"
96
107
  end
97
108
  end
98
109
  end
@@ -100,9 +111,9 @@ module BinData
100
111
  def ensure_mutual_exclusion_of_parameters
101
112
  return if length < 2
102
113
 
103
- @the_class.mutually_exclusive_parameters.each do |param1, param2|
104
- if has_parameter?(param1) and has_parameter?(param2)
105
- raise ArgumentError, "params '#{param1}' and '#{param2}' " +
114
+ @the_class.mutually_exclusive_parameters.each do |key1, key2|
115
+ if has_parameter?(key1) and has_parameter?(key2)
116
+ raise ArgumentError, "params '#{key1}' and '#{key2}' " +
106
117
  "are mutually exclusive in #{@the_class}"
107
118
  end
108
119
  end
@@ -142,20 +153,23 @@ module BinData
142
153
  end
143
154
 
144
155
  def create_sanitized_endian(endian)
145
- SanitizedEndian.new(endian)
156
+ # memoize return value to reduce memory usage
157
+ if endian == :big
158
+ @@sbe ||= SanitizedBigEndian.new
159
+ elsif endian == :little
160
+ @@sle ||= SanitizedLittleEndian.new
161
+ else
162
+ raise ArgumentError, "unknown value for endian '#{endian}'"
163
+ end
146
164
  end
147
165
 
148
166
  def create_sanitized_choices(choices)
149
167
  SanitizedChoices.new(self, choices)
150
168
  end
151
169
 
152
- def create_sanitized_fields
153
- SanitizedFields.new(self)
154
- end
155
-
156
- def clone_sanitized_fields(fields)
170
+ def create_sanitized_fields(fields = nil)
157
171
  new_fields = SanitizedFields.new(self)
158
- new_fields.copy_fields(fields)
172
+ new_fields.copy_fields(fields) if fields
159
173
  new_fields
160
174
  end
161
175
 
@@ -166,7 +180,7 @@ module BinData
166
180
  def with_endian(endian, &block)
167
181
  if endian != nil
168
182
  saved_endian = @endian
169
- @endian = endian.is_a?(SanitizedEndian) ? endian.endian : endian
183
+ @endian = endian.respond_to?(:endian) ? endian.endian : endian
170
184
  yield
171
185
  @endian = saved_endian
172
186
  else
@@ -175,11 +189,7 @@ module BinData
175
189
  end
176
190
 
177
191
  def lookup_class(type)
178
- registered_class = RegisteredClasses.lookup(type, @endian)
179
- if registered_class.nil?
180
- raise UnknownTypeError, type.to_s
181
- end
182
- registered_class
192
+ RegisteredClasses.lookup(type, @endian)
183
193
  end
184
194
 
185
195
  #---------------
@@ -228,9 +238,12 @@ module BinData
228
238
  def initialize(sanitizer)
229
239
  @sanitizer = sanitizer
230
240
  @fields = []
241
+ @field_names = nil
231
242
  end
243
+ attr_reader :fields
232
244
 
233
245
  def add_field(type, name, params, endian)
246
+ @field_names = nil
234
247
  @fields << SanitizedField.new(@sanitizer, name, type, params, endian)
235
248
  end
236
249
 
@@ -238,13 +251,18 @@ module BinData
238
251
  @fields[idx]
239
252
  end
240
253
 
254
+ def empty?
255
+ @fields.empty?
256
+ end
257
+
241
258
  def field_names
242
- @fields.collect { |field| field.name }
259
+ # memoize field names to reduce duplicate copies
260
+ @field_names ||= @fields.collect { |field| field.name }
243
261
  end
244
262
 
245
263
  def copy_fields(other)
246
- other_fields = other.instance_variable_get(:@fields)
247
- @fields.concat(other_fields)
264
+ @field_names = nil
265
+ @fields.concat(other.fields)
248
266
  end
249
267
  end
250
268
  #----------------------------------------------------------------------------
@@ -265,15 +283,15 @@ module BinData
265
283
  end
266
284
  #----------------------------------------------------------------------------
267
285
 
268
- class SanitizedEndian < SanitizedParameter
269
- def initialize(endian)
270
- unless [:little, :big].include?(endian)
271
- raise ArgumentError, "unknown value for endian '#{endian}'"
272
- end
273
-
274
- @endian = endian
286
+ class SanitizedBigEndian < SanitizedParameter
287
+ def endian
288
+ :big
275
289
  end
290
+ end
276
291
 
277
- attr_reader :endian
292
+ class SanitizedLittleEndian < SanitizedParameter
293
+ def endian
294
+ :little
295
+ end
278
296
  end
279
297
  end
@@ -24,7 +24,8 @@ module BinData
24
24
  # <tt>:length</tt>:: The number of bytes to skip.
25
25
  #
26
26
  class Skip < BinData::BasePrimitive
27
- register(self.name, self)
27
+
28
+ register_self
28
29
 
29
30
  mandatory_parameter :length
30
31
 
@@ -20,7 +20,7 @@ module BinData
20
20
  # obj.value = "abcd"
21
21
  # obj.value #=> "abcd\000\000"
22
22
  #
23
- # obj = BinData::String.new(:length => 6, :trim_value => true)
23
+ # obj = BinData::String.new(:length => 6, :trim_padding => true)
24
24
  # obj.value = "abcd"
25
25
  # obj.value #=> "abcd"
26
26
  # obj.to_binary_s #=> "abcd\000\000"
@@ -47,7 +47,7 @@ module BinData
47
47
  # not be trimmed when writing.
48
48
  class String < BinData::BasePrimitive
49
49
 
50
- register(self.name, self)
50
+ register_self
51
51
 
52
52
  optional_parameters :read_length, :length, :trim_padding
53
53
  default_parameters :pad_char => "\0"
@@ -57,7 +57,7 @@ module BinData
57
57
  class << self
58
58
 
59
59
  def sanitize_parameters!(params, sanitizer) #:nodoc:
60
- warn_replacement_parameter(params, :initial_length, :read_length)
60
+ params.warn_replacement_parameter(:initial_length, :read_length)
61
61
 
62
62
  if params.has_parameter?(:pad_char)
63
63
  ch = params[:pad_char]