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.
Files changed (71) hide show
  1. data/.gitignore +1 -0
  2. data/BSDL +22 -0
  3. data/COPYING +52 -0
  4. data/ChangeLog.rdoc +204 -0
  5. data/Gemfile +2 -0
  6. data/INSTALL +11 -0
  7. data/NEWS.rdoc +164 -0
  8. data/README.md +54 -0
  9. data/Rakefile +13 -0
  10. data/bindata.gemspec +31 -0
  11. data/doc/manual.haml +407 -0
  12. data/doc/manual.md +1649 -0
  13. data/examples/NBT.txt +149 -0
  14. data/examples/gzip.rb +161 -0
  15. data/examples/ip_address.rb +22 -0
  16. data/examples/list.rb +124 -0
  17. data/examples/nbt.rb +178 -0
  18. data/lib/bindata.rb +33 -0
  19. data/lib/bindata/alignment.rb +83 -0
  20. data/lib/bindata/array.rb +335 -0
  21. data/lib/bindata/base.rb +388 -0
  22. data/lib/bindata/base_primitive.rb +214 -0
  23. data/lib/bindata/bits.rb +87 -0
  24. data/lib/bindata/choice.rb +216 -0
  25. data/lib/bindata/count_bytes_remaining.rb +35 -0
  26. data/lib/bindata/deprecated.rb +50 -0
  27. data/lib/bindata/dsl.rb +312 -0
  28. data/lib/bindata/float.rb +80 -0
  29. data/lib/bindata/int.rb +184 -0
  30. data/lib/bindata/io.rb +274 -0
  31. data/lib/bindata/lazy.rb +105 -0
  32. data/lib/bindata/offset.rb +91 -0
  33. data/lib/bindata/params.rb +135 -0
  34. data/lib/bindata/primitive.rb +135 -0
  35. data/lib/bindata/record.rb +110 -0
  36. data/lib/bindata/registry.rb +92 -0
  37. data/lib/bindata/rest.rb +35 -0
  38. data/lib/bindata/sanitize.rb +290 -0
  39. data/lib/bindata/skip.rb +48 -0
  40. data/lib/bindata/string.rb +145 -0
  41. data/lib/bindata/stringz.rb +96 -0
  42. data/lib/bindata/struct.rb +388 -0
  43. data/lib/bindata/trace.rb +94 -0
  44. data/lib/bindata/version.rb +3 -0
  45. data/setup.rb +1585 -0
  46. data/spec/alignment_spec.rb +61 -0
  47. data/spec/array_spec.rb +331 -0
  48. data/spec/base_primitive_spec.rb +238 -0
  49. data/spec/base_spec.rb +376 -0
  50. data/spec/bits_spec.rb +163 -0
  51. data/spec/choice_spec.rb +263 -0
  52. data/spec/count_bytes_remaining_spec.rb +38 -0
  53. data/spec/deprecated_spec.rb +31 -0
  54. data/spec/example.rb +21 -0
  55. data/spec/float_spec.rb +37 -0
  56. data/spec/int_spec.rb +216 -0
  57. data/spec/io_spec.rb +352 -0
  58. data/spec/lazy_spec.rb +217 -0
  59. data/spec/primitive_spec.rb +202 -0
  60. data/spec/record_spec.rb +530 -0
  61. data/spec/registry_spec.rb +108 -0
  62. data/spec/rest_spec.rb +26 -0
  63. data/spec/skip_spec.rb +27 -0
  64. data/spec/spec_common.rb +58 -0
  65. data/spec/string_spec.rb +300 -0
  66. data/spec/stringz_spec.rb +118 -0
  67. data/spec/struct_spec.rb +350 -0
  68. data/spec/system_spec.rb +380 -0
  69. data/tasks/manual.rake +36 -0
  70. data/tasks/rspec.rake +17 -0
  71. metadata +208 -0
@@ -0,0 +1,110 @@
1
+ require 'bindata/dsl'
2
+ require 'bindata/sanitize'
3
+ require 'bindata/struct'
4
+
5
+ module BinData
6
+ # Extracts args for Records.
7
+ #
8
+ # Foo.new(:bar => "baz) is ambiguous as to whether :bar is a value or parameter.
9
+ #
10
+ # BaseArgExtractor always assumes :bar is parameter. This extractor correctly
11
+ # identifies it as value or parameter.
12
+ class RecordArgExtractor
13
+ class << self
14
+ def extract(the_class, the_args)
15
+ value, parameters, parent = BaseArgExtractor.extract(the_class, the_args)
16
+
17
+ if parameters_is_value?(the_class, value, parameters)
18
+ value = parameters
19
+ parameters = {}
20
+ end
21
+
22
+ [value, parameters, parent]
23
+ end
24
+
25
+ def parameters_is_value?(the_class, value, parameters)
26
+ if value.nil? and parameters.length > 0
27
+ field_names_in_parameters?(the_class, parameters)
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ def field_names_in_parameters?(the_class, parameters)
34
+ field_names = the_class.fields.field_names
35
+ param_keys = parameters.keys
36
+
37
+ (field_names & param_keys).length > 0
38
+ end
39
+ end
40
+ end
41
+
42
+ # A Record is a declarative wrapper around Struct.
43
+ #
44
+ # require 'bindata'
45
+ #
46
+ # class SomeDataType < BinData::Record
47
+ # hide :a
48
+ #
49
+ # int32le :a
50
+ # int16le :b
51
+ # struct :s do
52
+ # int8 :x
53
+ # int8 :y
54
+ # int8 :z
55
+ # end
56
+ # end
57
+ #
58
+ # obj = SomeDataType.new
59
+ # obj.field_names =># ["b", "s"]
60
+ # obj.s.field_names =># ["x", "y", "z"]
61
+ #
62
+ class Record < BinData::Struct
63
+ include DSLMixin
64
+
65
+ unregister_self
66
+ dsl_parser :struct
67
+
68
+ class << self
69
+
70
+ def arg_extractor
71
+ RecordArgExtractor
72
+ end
73
+
74
+ def sanitize_parameters!(params) #:nodoc:
75
+ params.merge!(dsl_params)
76
+
77
+ super(params)
78
+
79
+ define_field_accessors(params[:fields].fields)
80
+ end
81
+
82
+ # Defines accessor methods to avoid the overhead of going through
83
+ # Struct#method_missing. This is purely a speed optimisation.
84
+ # Removing this method will not have any effect on correctness.
85
+ def define_field_accessors(fields) #:nodoc:
86
+ unless method_defined?(:bindata_defined_accessors_for_fields?)
87
+ fields.each_with_index do |field, i|
88
+ name = field.name_as_sym
89
+ if name
90
+ define_field_accessors_for(name, i)
91
+ end
92
+ end
93
+
94
+ define_method(:bindata_defined_accessors_for_fields?) { true }
95
+ end
96
+ end
97
+
98
+ def define_field_accessors_for(name, index)
99
+ define_method(name) do
100
+ instantiate_obj_at(index) unless @field_objs[index]
101
+ @field_objs[index]
102
+ end
103
+ define_method(name.to_s + "=") do |*vals|
104
+ instantiate_obj_at(index) unless @field_objs[index]
105
+ @field_objs[index].assign(*vals)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,92 @@
1
+ module BinData
2
+
3
+ class UnRegisteredTypeError < StandardError ; end
4
+
5
+ # This registry contains a register of name -> class mappings.
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
+ #
11
+ # Names are stored in under_score_style, not camelCase.
12
+ class Registry
13
+
14
+ def initialize
15
+ @registry = {}
16
+ end
17
+
18
+ def register(name, class_to_register)
19
+ return if class_to_register.nil?
20
+
21
+ formatted_name = lookup_key(name)
22
+ warn_if_name_is_already_registered(formatted_name, class_to_register)
23
+
24
+ @registry[formatted_name] = class_to_register
25
+ end
26
+
27
+ def unregister(name)
28
+ formatted_name = lookup_key(name)
29
+ @registry.delete(formatted_name)
30
+ end
31
+
32
+ def lookup(name, endian = nil)
33
+ key = lookup_key(name, endian)
34
+ try_registering_key(key) unless @registry.has_key?(key)
35
+
36
+ @registry[key] || raise(UnRegisteredTypeError, name.to_s)
37
+ end
38
+
39
+ def normalize_name(name, endian = nil)
40
+ if lookup(name, endian)
41
+ lookup_key(name, endian)
42
+ end
43
+ end
44
+
45
+ # Convert CamelCase +name+ to underscore style.
46
+ def underscore_name(name)
47
+ name.to_s.sub(/.*::/, "").
48
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
49
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
50
+ tr("-", "_").
51
+ downcase
52
+ end
53
+
54
+ #---------------
55
+ private
56
+
57
+ def lookup_key(name, endian = nil)
58
+ name = underscore_name(name)
59
+
60
+ result = name
61
+ if endian != nil
62
+ if /^u?int\d+$/ =~ name
63
+ result = name + ((endian == :little) ? "le" : "be")
64
+ elsif /^(float|double)$/ =~ name
65
+ result = name + ((endian == :little) ? "_le" : "_be")
66
+ end
67
+ end
68
+ result
69
+ end
70
+
71
+ def try_registering_key(key)
72
+ if /^u?int\d+(le|be)$/ =~ key or /^bit\d+(le)?$/ =~ key
73
+ class_name = key.gsub(/(?:^|_)(.)/) { $1.upcase }
74
+ begin
75
+ register(key, BinData::const_get(class_name))
76
+ rescue NameError
77
+ end
78
+ end
79
+ end
80
+
81
+ def warn_if_name_is_already_registered(name, class_to_register)
82
+ prev_class = @registry[name]
83
+ if $VERBOSE and prev_class and prev_class != class_to_register
84
+ warn "warning: replacing registered class #{prev_class} " +
85
+ "with #{class_to_register}"
86
+ end
87
+ end
88
+ end
89
+
90
+ # A singleton registry of all registered classes.
91
+ RegisteredClasses = Registry.new
92
+ end
@@ -0,0 +1,35 @@
1
+ require "bindata/base_primitive"
2
+
3
+ module BinData
4
+ # Rest will consume the input stream from the current position to the end of
5
+ # the stream. This will mainly be useful for debugging and developing.
6
+ #
7
+ # require 'bindata'
8
+ #
9
+ # class A < BinData::Record
10
+ # string :a, :read_length => 5
11
+ # rest :rest
12
+ # end
13
+ #
14
+ # obj = A.read("abcdefghij")
15
+ # obj.a #=> "abcde"
16
+ # obj.rest #=" "fghij"
17
+ #
18
+ class Rest < BinData::BasePrimitive
19
+
20
+ #---------------
21
+ private
22
+
23
+ def value_to_binary_string(val)
24
+ val
25
+ end
26
+
27
+ def read_and_return_value(io)
28
+ io.read_all_bytes
29
+ end
30
+
31
+ def sensible_default
32
+ ""
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,290 @@
1
+ require 'bindata/registry'
2
+
3
+ module BinData
4
+
5
+ # Subclasses of this are sanitized
6
+ class SanitizedParameter; end
7
+
8
+ class SanitizedPrototype < SanitizedParameter
9
+ def initialize(obj_type, obj_params, endian)
10
+ endian = endian.endian if endian.respond_to? :endian
11
+ obj_params ||= {}
12
+ if BinData::Base === obj_type or obj_type.is_a?(Class)
13
+ obj_class = obj_type
14
+ else
15
+ obj_class = RegisteredClasses.lookup(obj_type, endian)
16
+ end
17
+
18
+ if BinData::Base === obj_class
19
+ @factory = obj_class
20
+ else
21
+ @obj_class = obj_class
22
+ @obj_params = SanitizedParameters.new(obj_params, @obj_class, endian)
23
+ end
24
+ end
25
+
26
+ def instantiate(value = nil, parent = nil)
27
+ @factory ||= @obj_class.new(@obj_params)
28
+
29
+ @factory.new(value, parent)
30
+ end
31
+ end
32
+ #----------------------------------------------------------------------------
33
+
34
+ class SanitizedField < SanitizedParameter
35
+ def initialize(name, field_type, field_params, endian)
36
+ @name = name
37
+ @prototype = SanitizedPrototype.new(field_type, field_params, endian)
38
+ end
39
+
40
+ attr_reader :prototype
41
+
42
+ def name_as_sym
43
+ @name.nil? ? nil : @name.to_sym
44
+ end
45
+
46
+ def name
47
+ @name
48
+ end
49
+
50
+ def instantiate(value = nil, parent = nil)
51
+ @prototype.instantiate(value, parent)
52
+ end
53
+ end
54
+ #----------------------------------------------------------------------------
55
+
56
+ class SanitizedFields < SanitizedParameter
57
+ def initialize(endian)
58
+ @fields = []
59
+ @endian = endian
60
+ end
61
+ attr_reader :fields
62
+
63
+ def add_field(type, name, params)
64
+ name = nil if name == ""
65
+
66
+ @fields << SanitizedField.new(name, type, params, @endian)
67
+ end
68
+
69
+ def [](idx)
70
+ @fields[idx]
71
+ end
72
+
73
+ def empty?
74
+ @fields.empty?
75
+ end
76
+
77
+ def length
78
+ @fields.length
79
+ end
80
+
81
+ def each(&block)
82
+ @fields.each(&block)
83
+ end
84
+
85
+ def collect(&block)
86
+ @fields.collect(&block)
87
+ end
88
+
89
+ def field_names
90
+ @fields.collect { |field| field.name_as_sym }
91
+ end
92
+
93
+ def has_field_name?(name)
94
+ @fields.detect { |f| f.name_as_sym == name.to_sym }
95
+ end
96
+
97
+ def all_field_names_blank?
98
+ @fields.all? { |f| f.name == nil }
99
+ end
100
+
101
+ def no_field_names_blank?
102
+ @fields.all? { |f| f.name != nil }
103
+ end
104
+
105
+ def copy_fields(other)
106
+ @fields.concat(other.fields)
107
+ end
108
+ end
109
+ #----------------------------------------------------------------------------
110
+
111
+ class SanitizedChoices < SanitizedParameter
112
+ def initialize(choices, endian)
113
+ @choices = {}
114
+ choices.each_pair do |key, val|
115
+ if SanitizedParameter === val
116
+ prototype = val
117
+ else
118
+ type, param = val
119
+ prototype = SanitizedPrototype.new(type, param, endian)
120
+ end
121
+
122
+ if key == :default
123
+ @choices.default = prototype
124
+ else
125
+ @choices[key] = prototype
126
+ end
127
+ end
128
+ end
129
+
130
+ def [](key)
131
+ @choices[key]
132
+ end
133
+ end
134
+ #----------------------------------------------------------------------------
135
+
136
+ class SanitizedBigEndian < SanitizedParameter
137
+ def endian
138
+ :big
139
+ end
140
+ end
141
+
142
+ class SanitizedLittleEndian < SanitizedParameter
143
+ def endian
144
+ :little
145
+ end
146
+ end
147
+ #----------------------------------------------------------------------------
148
+
149
+ # BinData objects are instantiated with parameters to determine their
150
+ # behaviour. These parameters must be sanitized to ensure their values
151
+ # are valid. When instantiating many objects with identical parameters,
152
+ # such as an array of records, there is much duplicated sanitizing.
153
+ #
154
+ # The purpose of the sanitizing code is to eliminate the duplicated
155
+ # validation.
156
+ #
157
+ # SanitizedParameters is a hash-like collection of parameters. Its purpose
158
+ # is to recursively sanitize the parameters of an entire BinData object chain
159
+ # at a single time.
160
+ class SanitizedParameters < Hash
161
+
162
+ # Memoized constants
163
+ BIG_ENDIAN = SanitizedBigEndian.new
164
+ LITTLE_ENDIAN = SanitizedLittleEndian.new
165
+
166
+ class << self
167
+ def sanitize(parameters, the_class)
168
+ if SanitizedParameters === parameters
169
+ parameters
170
+ else
171
+ SanitizedParameters.new(parameters, the_class, nil)
172
+ end
173
+ end
174
+ end
175
+
176
+ def initialize(parameters, the_class, endian)
177
+ parameters.each_pair { |key, value| self[key.to_sym] = value }
178
+
179
+ @the_class = the_class
180
+ @endian = endian
181
+
182
+ sanitize!
183
+ end
184
+
185
+ alias_method :has_parameter?, :has_key?
186
+
187
+ def needs_sanitizing?(key)
188
+ parameter = self[key]
189
+
190
+ parameter and not parameter.is_a?(SanitizedParameter)
191
+ end
192
+
193
+ def warn_replacement_parameter(bad_key, suggested_key)
194
+ if has_parameter?(bad_key)
195
+ warn ":#{bad_key} is not used with #{@the_class}. " +
196
+ "You probably want to change this to :#{suggested_key}"
197
+ end
198
+ end
199
+
200
+ def warn_renamed_parameter(old_key, new_key)
201
+ val = delete(old_key)
202
+ if val
203
+ self[new_key] = val
204
+ warn ":#{old_key} has been renamed to :#{new_key} in #{@the_class}. " +
205
+ "Using :#{old_key} is now deprecated and will be removed in the future"
206
+ end
207
+ end
208
+
209
+ def endian
210
+ @endian || self[:endian]
211
+ end
212
+ attr_writer :endian
213
+
214
+ def create_sanitized_endian(endian)
215
+ if endian == :big
216
+ BIG_ENDIAN
217
+ elsif endian == :little
218
+ LITTLE_ENDIAN
219
+ else
220
+ raise ArgumentError, "unknown value for endian '#{endian}'"
221
+ end
222
+ end
223
+
224
+ def create_sanitized_params(params, the_class)
225
+ SanitizedParameters.new(params, the_class, self.endian)
226
+ end
227
+
228
+ def create_sanitized_choices(choices)
229
+ SanitizedChoices.new(choices, self.endian)
230
+ end
231
+
232
+ def create_sanitized_fields
233
+ SanitizedFields.new(self.endian)
234
+ end
235
+
236
+ def create_sanitized_object_prototype(obj_type, obj_params)
237
+ SanitizedPrototype.new(obj_type, obj_params, self.endian)
238
+ end
239
+
240
+ #---------------
241
+ private
242
+
243
+ def sanitize!
244
+ ensure_no_nil_values
245
+ merge_default_parameters!
246
+
247
+ @the_class.sanitize_parameters!(self)
248
+
249
+ ensure_mandatory_parameters_exist
250
+ ensure_mutual_exclusion_of_parameters
251
+ end
252
+
253
+ def ensure_no_nil_values
254
+ each do |key, value|
255
+ if value.nil?
256
+ raise ArgumentError,
257
+ "parameter '#{key}' has nil value in #{@the_class}"
258
+ end
259
+ end
260
+ end
261
+
262
+ def merge_default_parameters!
263
+ @the_class.default_parameters.each do |key, value|
264
+ self[key] ||= value
265
+ end
266
+ end
267
+
268
+ def ensure_mandatory_parameters_exist
269
+ @the_class.mandatory_parameters.each do |key|
270
+ unless has_parameter?(key)
271
+ raise ArgumentError,
272
+ "parameter '#{key}' must be specified in #{@the_class}"
273
+ end
274
+ end
275
+ end
276
+
277
+ def ensure_mutual_exclusion_of_parameters
278
+ return if length < 2
279
+
280
+ @the_class.mutually_exclusive_parameters.each do |key1, key2|
281
+ if has_parameter?(key1) and has_parameter?(key2)
282
+ raise ArgumentError, "params '#{key1}' and '#{key2}' " +
283
+ "are mutually exclusive in #{@the_class}"
284
+ end
285
+ end
286
+ end
287
+ end
288
+ #----------------------------------------------------------------------------
289
+
290
+ end