bindata 1.0.0 → 1.1.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,5 +1,12 @@
1
1
  = BinData Changelog
2
2
 
3
+ == Version 1.1.0 (2009-11-24)
4
+
5
+ * Allow anonymous fields in Records and Primitives.
6
+ * Add the ability to skip over unused data.
7
+ * Allow Records, Primitives and Wrappers to be derived from.
8
+ * Classes for integers are now defined on demand.
9
+
3
10
  == Version 1.0.0 (2009-09-13)
4
11
 
5
12
  * Is now compatible with Ruby 1.9
data/README CHANGED
@@ -48,6 +48,18 @@ Copyright © 2007 - 2009 [Dion Mendel](mailto:dion@lostrealm.com)
48
48
 
49
49
  ---------------------------------------------------------------------------
50
50
 
51
+ # Installation
52
+
53
+ You can install BinData via rubygems.
54
+
55
+ gem install bindata
56
+
57
+ Alternatively, visit the
58
+ [download](http://rubyforge.org/frs/?group_id=3252) page and download
59
+ BinData as a tar file.
60
+
61
+ ---------------------------------------------------------------------------
62
+
51
63
  # Overview
52
64
 
53
65
  BinData declarations are easy to read. Here's an example.
@@ -248,8 +260,10 @@ one or more fields.
248
260
  converted from `CamelCase` to lowercased `underscore_style`.
249
261
 
250
262
  `field_name`
251
- : is the name by which you can access the data. Use either a
252
- `String` or a `Symbol`.
263
+ : is the name by which you can access the field. Use either a
264
+ `String` or a `Symbol`. If name is nil or the empty string, then
265
+ this particular field is anonymous. An anonymous field is still
266
+ read and written, but will not appear in `#snapshot`.
253
267
 
254
268
  Each field may have optional *parameters* for how to process the data.
255
269
  The parameters are passed as a `Hash` with `Symbols` for keys.
@@ -918,6 +932,34 @@ Examples
918
932
 
919
933
  # Advanced Topics
920
934
 
935
+ ## Skipping over unused data
936
+
937
+ Some binary structures contain data that is irrelevant to your purposes.
938
+
939
+ Say you are interested in 50 bytes of data located 10 megabytes into the
940
+ stream. One way of accessing this useful data is:
941
+
942
+ class MyData < BinData::Record
943
+ string :length => 10 * 1024 * 1024
944
+ string :data, :length => 50
945
+ end
946
+ {:ruby}
947
+
948
+ The advantage of this method is that the irrelevant data is preserved
949
+ when writing the record. The disadvantage is that even if you don't care
950
+ about preserving this irrelevant data, it still occupies memory.
951
+
952
+ If you don't need to preserve this data, an alternative is to use
953
+ `skip` instead of `string`. When reading it will seek over the irrelevant
954
+ data and won't consume space in memory. When writing it will write
955
+ `:length` number of zero bytes.
956
+
957
+ class MyData < BinData::Record
958
+ skip :length => 10 * 1024 * 1024
959
+ string :data, :length => 50
960
+ end
961
+ {:ruby}
962
+
921
963
  ## Wrappers
922
964
 
923
965
  Sometimes you wish to create a new type that is simply an existing type
data/TODO CHANGED
@@ -1,6 +1,3 @@
1
1
  == Pending refactorings
2
2
 
3
- * Refactor registry into registry and numeric registry
4
- update specs accordingly
5
-
6
3
  * Perhaps refactor snapshot -> _snapshot et al
data/lib/bindata.rb CHANGED
@@ -9,6 +9,7 @@ require 'bindata/int'
9
9
  require 'bindata/primitive'
10
10
  require 'bindata/record'
11
11
  require 'bindata/rest'
12
+ require 'bindata/skip'
12
13
  require 'bindata/string'
13
14
  require 'bindata/stringz'
14
15
  require 'bindata/struct'
@@ -29,5 +30,5 @@ require 'bindata/deprecated'
29
30
  #
30
31
  # Copyright (c) 2007 - 2009 Dion Mendel.
31
32
  module BinData
32
- VERSION = "1.0.0"
33
+ VERSION = "1.1.0"
33
34
  end
data/lib/bindata/bits.rb CHANGED
@@ -8,13 +8,15 @@ module BinData
8
8
  def self.define_class(nbits, endian)
9
9
  name = "Bit#{nbits}"
10
10
  name += "le" if endian == :little
11
-
12
- BinData.module_eval <<-END
13
- class #{name} < BinData::BasePrimitive
14
- register(self.name, self)
15
- BitField.create_methods(self, #{nbits}, :#{endian.to_s})
16
- end
17
- END
11
+ unless BinData.const_defined?(name)
12
+ BinData.module_eval <<-END
13
+ class #{name} < BinData::BasePrimitive
14
+ register(self.name, self)
15
+ BitField.create_methods(self, #{nbits}, :#{endian.to_s})
16
+ end
17
+ END
18
+ end
19
+ BinData.const_get(name)
18
20
  end
19
21
 
20
22
  def self.create_methods(bit_class, nbits, endian)
@@ -60,9 +62,25 @@ module BinData
60
62
  end
61
63
  end
62
64
 
63
- # Create commonly used bit based integers
64
- (1 .. 63).each do |nbits|
65
- BitField.define_class(nbits, :little)
66
- BitField.define_class(nbits, :big)
65
+ # Create classes on demand
66
+ class << self
67
+ alias_method :const_missing_without_bits, :const_missing
68
+ def const_missing_with_bits(name)
69
+ name = name.to_s
70
+ mappings = {
71
+ /^Bit(\d+)$/ => :big,
72
+ /^Bit(\d+)le$/ => :little
73
+ }
74
+
75
+ mappings.each_pair do |regex, endian|
76
+ if regex =~ name
77
+ nbits = $1.to_i
78
+ return BitField.define_class(nbits, endian)
79
+ end
80
+ end
81
+
82
+ const_missing_without_bits(name)
83
+ end
84
+ alias_method :const_missing, :const_missing_with_bits
67
85
  end
68
86
  end
data/lib/bindata/int.rb CHANGED
@@ -8,17 +8,18 @@ module BinData
8
8
  class << self
9
9
  def define_class(nbits, endian, signed)
10
10
  name = class_name(nbits, endian, signed)
11
- return if BinData.const_defined?(name)
12
-
13
- int_type = (signed == :signed) ? 'int' : 'uint'
14
- creation_method = "create_#{int_type}_methods"
15
-
16
- BinData.module_eval <<-END
17
- class #{name} < BinData::BasePrimitive
18
- register(self.name, self)
19
- Int.#{creation_method}(self, #{nbits}, :#{endian.to_s})
20
- end
21
- END
11
+ unless BinData.const_defined?(name)
12
+ int_type = (signed == :signed) ? 'int' : 'uint'
13
+ creation_method = "create_#{int_type}_methods"
14
+
15
+ BinData.module_eval <<-END
16
+ class #{name} < BinData::BasePrimitive
17
+ register(self.name, self)
18
+ Int.#{creation_method}(self, #{nbits}, :#{endian.to_s})
19
+ end
20
+ END
21
+ end
22
+ BinData.const_get(name)
22
23
  end
23
24
 
24
25
  def class_name(nbits, endian, signed)
@@ -182,11 +183,29 @@ module BinData
182
183
  Int.create_int_methods(self, 8, :little)
183
184
  end
184
185
 
185
- # Create commonly used integers
186
- [8, 16, 32, 64, 128].each do |nbits|
187
- Int.define_class(nbits, :little, :unsigned)
188
- Int.define_class(nbits, :little, :signed)
189
- Int.define_class(nbits, :big, :unsigned)
190
- Int.define_class(nbits, :big, :signed)
186
+ # Create classes on demand
187
+ class << self
188
+ alias_method :const_missing_without_int, :const_missing
189
+ def const_missing_with_int(name)
190
+ name = name.to_s
191
+ mappings = {
192
+ /^Uint(\d+)be$/ => [:big, :unsigned],
193
+ /^Uint(\d+)le$/ => [:little, :unsigned],
194
+ /^Int(\d+)be$/ => [:big, :signed],
195
+ /^Int(\d+)le$/ => [:little, :signed],
196
+ }
197
+
198
+ mappings.each_pair do |regex, args|
199
+ if regex =~ name
200
+ nbits = $1.to_i
201
+ if (nbits % 8).zero?
202
+ return Int.define_class(nbits, *args)
203
+ end
204
+ end
205
+ end
206
+
207
+ const_missing_without_int(name)
208
+ end
209
+ alias_method :const_missing, :const_missing_with_int
191
210
  end
192
211
  end
data/lib/bindata/io.rb CHANGED
@@ -69,7 +69,10 @@ module BinData
69
69
 
70
70
  # Seek +n+ bytes from the current position in the io stream.
71
71
  def seekbytes(n)
72
+ reset_read_bits
72
73
  @raw_io.seek(n, ::IO::SEEK_CUR)
74
+ rescue Errno::ESPIPE, Errno::EPIPE
75
+ skipbytes(n)
73
76
  end
74
77
 
75
78
  # Reads exactly +n+ bytes from +io+.
@@ -78,9 +81,7 @@ module BinData
78
81
  #
79
82
  # If the data read is too short an IOError is raised.
80
83
  def readbytes(n)
81
- raise "Internal state error nbits = #{@rnbits}" if @rnbits >= 8
82
- @rnbits = 0
83
- @rval = 0
84
+ reset_read_bits
84
85
 
85
86
  str = @raw_io.read(n)
86
87
  raise EOFError, "End of file reached" if str.nil?
@@ -90,10 +91,7 @@ module BinData
90
91
 
91
92
  # Reads all remaining bytes from the stream.
92
93
  def read_all_bytes
93
- raise "Internal state error nbits = #{@rnbits}" if @rnbits >= 8
94
- @rnbits = 0
95
- @rval = 0
96
-
94
+ reset_read_bits
97
95
  @raw_io.read
98
96
  end
99
97
 
@@ -102,8 +100,7 @@ module BinData
102
100
  def readbits(nbits, endian)
103
101
  if @rendian != endian
104
102
  # don't mix bits of differing endian
105
- @rnbits = 0
106
- @rval = 0
103
+ reset_read_bits
107
104
  @rendian = endian
108
105
  end
109
106
 
@@ -126,7 +123,6 @@ module BinData
126
123
  if @wendian != endian
127
124
  # don't mix bits of differing endian
128
125
  flushbits
129
-
130
126
  @wendian = endian
131
127
  end
132
128
 
@@ -164,6 +160,21 @@ module BinData
164
160
  @positioning_supported
165
161
  end
166
162
 
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
+ def skipbytes(n)
170
+ # skip over data in 8k blocks
171
+ while n > 0
172
+ bytes_to_read = [n, 8192].min
173
+ @raw_io.read(bytes_to_read)
174
+ n -= bytes_to_read
175
+ end
176
+ end
177
+
167
178
  def read_big_endian_bits(nbits)
168
179
  while @rnbits < nbits
169
180
  accumulate_big_endian_bits
@@ -68,7 +68,7 @@ module BinData
68
68
  end
69
69
 
70
70
  def endian(endian = nil)
71
- @endian ||= nil
71
+ @endian ||= default_endian
72
72
  if [:little, :big].include?(endian)
73
73
  @endian = endian
74
74
  elsif endian != nil
@@ -78,9 +78,18 @@ module BinData
78
78
  @endian
79
79
  end
80
80
 
81
+ def fields #:nodoc:
82
+ @fields ||= default_fields
83
+ end
84
+
81
85
  def method_missing(symbol, *args) #:nodoc:
82
86
  name, params = args
83
87
 
88
+ if name.is_a?(Hash)
89
+ params = name
90
+ name = nil
91
+ end
92
+
84
93
  type = symbol
85
94
  name = name.to_s
86
95
  params ||= {}
@@ -99,18 +108,30 @@ module BinData
99
108
  #-------------
100
109
  private
101
110
 
102
- def fields
103
- unless defined? @fields
104
- sanitizer = Sanitizer.new
105
- @fields = sanitizer.create_sanitized_fields(endian)
111
+ def parent_primitive
112
+ ancestors[1..-1].find { |cls|
113
+ cls.ancestors[1..-1].include?(BinData::Primitive)
114
+ }
115
+ end
116
+
117
+ def default_endian
118
+ prim = parent_primitive
119
+ prim ? prim.endian : nil
120
+ end
121
+
122
+ def default_fields
123
+ prim = parent_primitive
124
+ if prim
125
+ Sanitizer.new.clone_sanitized_fields(prim.fields)
126
+ else
127
+ Sanitizer.new.create_sanitized_fields
106
128
  end
107
- @fields
108
129
  end
109
130
 
110
131
  def append_field(type, name, params)
111
132
  ensure_valid_name(name)
112
133
 
113
- fields.add_field(type, name, params)
134
+ fields.add_field(type, name, params, endian)
114
135
  rescue UnknownTypeError => err
115
136
  raise TypeError, "unknown type '#{err.message}' for #{self}", caller(2)
116
137
  end
@@ -52,7 +52,7 @@ module BinData
52
52
  end
53
53
 
54
54
  def endian(endian = nil)
55
- @endian ||= nil
55
+ @endian ||= default_endian
56
56
  if [:little, :big].include?(endian)
57
57
  @endian = endian
58
58
  elsif endian != nil
@@ -63,14 +63,23 @@ module BinData
63
63
  end
64
64
 
65
65
  def hide(*args)
66
- @hide ||= []
66
+ @hide ||= default_hide
67
67
  @hide.concat(args.collect { |name| name.to_s })
68
68
  @hide
69
69
  end
70
70
 
71
+ def fields #:nodoc:
72
+ @fields ||= default_fields
73
+ end
74
+
71
75
  def method_missing(symbol, *args) #:nodoc:
72
76
  name, params = args
73
77
 
78
+ if name.is_a?(Hash)
79
+ params = name
80
+ name = nil
81
+ end
82
+
74
83
  type = symbol
75
84
  name = name.to_s
76
85
  params ||= {}
@@ -89,18 +98,35 @@ module BinData
89
98
  #-------------
90
99
  private
91
100
 
92
- def fields
93
- unless defined? @fields
94
- sanitizer = Sanitizer.new
95
- @fields = sanitizer.create_sanitized_fields(endian)
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
96
123
  end
97
- @fields
98
124
  end
99
125
 
100
126
  def append_field(type, name, params)
101
127
  ensure_valid_name(name)
102
128
 
103
- fields.add_field(type, name, params)
129
+ fields.add_field(type, name, params, endian)
104
130
  rescue UnknownTypeError => err
105
131
  raise TypeError, "unknown type '#{err.message}' for #{self}", caller(2)
106
132
  end
@@ -9,6 +9,8 @@ module BinData
9
9
  end
10
10
 
11
11
  def register(name, class_to_register)
12
+ return if class_to_register.nil?
13
+
12
14
  formatted_name = lookup_key(name)
13
15
  warn_if_name_is_already_registered(formatted_name, class_to_register)
14
16
 
@@ -17,15 +19,16 @@ module BinData
17
19
 
18
20
  def lookup(name, endian = nil)
19
21
  key = lookup_key(name, endian)
22
+ try_registering_key(key) unless @registry.has_key?(key)
20
23
 
21
- @registry[key] || lookup_int(key)
24
+ @registry[key]
22
25
  end
23
26
 
24
27
  def is_registered?(name, endian = nil)
25
- @registry.has_key?(lookup_key(name, endian))
28
+ lookup(name, endian) != nil
26
29
  end
27
30
 
28
- # Convert camelCase +name+ to underscore style.
31
+ # Convert CamelCase +name+ to underscore style.
29
32
  def underscore_name(name)
30
33
  name.to_s.sub(/.*::/, "").
31
34
  gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
@@ -51,29 +54,18 @@ module BinData
51
54
  result
52
55
  end
53
56
 
54
- def lookup_int(key)
55
- if /^(u?)int(\d+)(le|be)$/ =~ key
56
- signed = $1 == "u" ? :unsigned : :signed
57
- nbits = $2.to_i
58
- endian = $3 == "le" ? :little : :big
59
- if nbits > 0 and (nbits % 8) == 0
60
- if BinData.const_defined?(:Int)
61
- BinData::Int.define_class(nbits, endian, signed)
62
- end
63
- end
64
- elsif /^bit(\d+)(le)?$/ =~ key
65
- nbits = $1.to_i
66
- endian = $2 == "le" ? :little : :big
67
- if BinData.const_defined?(:BitField)
68
- BinData::BitField.define_class(nbits, endian)
57
+ def try_registering_key(key)
58
+ if /^u?int\d+(le|be)$/ =~ key or /^bit\d+(le)?$/ =~ key
59
+ class_name = key.gsub(/(?:^|_)(.)/) { $1.upcase }
60
+ begin
61
+ register(key, BinData::const_get(class_name))
62
+ rescue NameError
69
63
  end
70
64
  end
71
-
72
- @registry[key]
73
65
  end
74
66
 
75
67
  def warn_if_name_is_already_registered(name, class_to_register)
76
- if $VERBOSE and @registry.has_key?(name)
68
+ if $VERBOSE and @registry[name] != class_to_register
77
69
  prev_class = @registry[name]
78
70
  warn "warning: replacing registered class #{prev_class} " +
79
71
  "with #{class_to_register}"
@@ -149,8 +149,14 @@ module BinData
149
149
  SanitizedChoices.new(self, choices)
150
150
  end
151
151
 
152
- def create_sanitized_fields(endian = nil)
153
- SanitizedFields.new(self, endian)
152
+ def create_sanitized_fields
153
+ SanitizedFields.new(self)
154
+ end
155
+
156
+ def clone_sanitized_fields(fields)
157
+ new_fields = SanitizedFields.new(self)
158
+ new_fields.copy_fields(fields)
159
+ new_fields
154
160
  end
155
161
 
156
162
  def create_sanitized_object_prototype(obj_type, obj_params, endian = nil)
@@ -192,7 +198,7 @@ module BinData
192
198
  class SanitizedParameter; end
193
199
 
194
200
  class SanitizedPrototype < SanitizedParameter
195
- def initialize(sanitizer, obj_type, obj_params, endian = nil)
201
+ def initialize(sanitizer, obj_type, obj_params, endian)
196
202
  sanitizer.with_endian(endian) do
197
203
  @obj_class = sanitizer.lookup_class(obj_type)
198
204
  @obj_params = sanitizer.create_sanitized_params(obj_params, @obj_class)
@@ -206,9 +212,9 @@ module BinData
206
212
  #----------------------------------------------------------------------------
207
213
 
208
214
  class SanitizedField < SanitizedParameter
209
- def initialize(sanitizer, name, field_type, field_params)
210
- @name = name.to_s
211
- @prototype = sanitizer.create_sanitized_object_prototype(field_type, field_params)
215
+ def initialize(sanitizer, name, field_type, field_params, endian)
216
+ @name = (name != nil and name != "") ? name.to_s : nil
217
+ @prototype = sanitizer.create_sanitized_object_prototype(field_type, field_params, endian)
212
218
  end
213
219
  attr_reader :name
214
220
 
@@ -219,16 +225,13 @@ module BinData
219
225
  #----------------------------------------------------------------------------
220
226
 
221
227
  class SanitizedFields < SanitizedParameter
222
- def initialize(sanitizer, endian)
228
+ def initialize(sanitizer)
223
229
  @sanitizer = sanitizer
224
- @endian = endian
225
230
  @fields = []
226
231
  end
227
232
 
228
- def add_field(type, name, params)
229
- @sanitizer.with_endian(@endian) do
230
- @fields << SanitizedField.new(@sanitizer, name, type, params)
231
- end
233
+ def add_field(type, name, params, endian)
234
+ @fields << SanitizedField.new(@sanitizer, name, type, params, endian)
232
235
  end
233
236
 
234
237
  def [](idx)
@@ -238,6 +241,11 @@ module BinData
238
241
  def field_names
239
242
  @fields.collect { |field| field.name }
240
243
  end
244
+
245
+ def copy_fields(other)
246
+ other_fields = other.instance_variable_get(:@fields)
247
+ @fields.concat(other_fields)
248
+ end
241
249
  end
242
250
  #----------------------------------------------------------------------------
243
251
 
@@ -0,0 +1,49 @@
1
+ require "bindata/base_primitive"
2
+
3
+ module BinData
4
+ # Skip will skip over bytes from the input stream. If the stream is not
5
+ # seekable, then the bytes are consumed and discarded.
6
+ #
7
+ # When writing, skip will write <tt>:length</tt> number of zero bytes.
8
+ #
9
+ # require 'bindata'
10
+ #
11
+ # class A < BinData::Record
12
+ # skip :length => 5
13
+ # string :a, :read_length => 5
14
+ # end
15
+ #
16
+ # obj = A.read("abcdefghij")
17
+ # obj.a #=> "fghij"
18
+ #
19
+ # == Parameters
20
+ #
21
+ # Skip objects accept all the params that BinData::BasePrimitive
22
+ # does, as well as the following:
23
+ #
24
+ # <tt>:length</tt>:: The number of bytes to skip.
25
+ #
26
+ class Skip < BinData::BasePrimitive
27
+ register(self.name, self)
28
+
29
+ mandatory_parameter :length
30
+
31
+ #---------------
32
+ private
33
+
34
+ def value_to_binary_string(val)
35
+ len = eval_parameter(:length)
36
+ "\000" * len
37
+ end
38
+
39
+ def read_and_return_value(io)
40
+ len = eval_parameter(:length)
41
+ io.seekbytes(len)
42
+ ""
43
+ end
44
+
45
+ def sensible_default
46
+ ""
47
+ end
48
+ end
49
+ end
@@ -28,7 +28,8 @@ module BinData
28
28
  # params]. Type is a symbol representing a registered
29
29
  # type. Name is the name of this field. Params is an
30
30
  # optional hash of parameters to pass to this field
31
- # when instantiating it.
31
+ # when instantiating it. If name is "" or nil, then
32
+ # that field is anonymous and behaves as a hidden field.
32
33
  # <tt>:hide</tt>:: A list of the names of fields that are to be hidden
33
34
  # from the outside world. Hidden fields don't appear
34
35
  # in #snapshot or #field_names but are still accessible
@@ -88,9 +89,9 @@ module BinData
88
89
  if params.needs_sanitizing?(:fields)
89
90
  fields = params[:fields]
90
91
 
91
- params[:fields] = sanitizer.create_sanitized_fields(params[:endian])
92
+ params[:fields] = sanitizer.create_sanitized_fields
92
93
  fields.each do |ftype, fname, fparams|
93
- params[:fields].add_field(ftype, fname, fparams)
94
+ params[:fields].add_field(ftype, fname, fparams, params[:endian])
94
95
  end
95
96
 
96
97
  field_names = sanitized_field_names(params[:fields])
@@ -108,7 +109,7 @@ module BinData
108
109
  end
109
110
 
110
111
  def sanitized_field_names(sanitized_fields)
111
- sanitized_fields.field_names
112
+ sanitized_fields.field_names.compact
112
113
  end
113
114
 
114
115
  def hidden_field_names(hidden)
@@ -159,10 +160,10 @@ module BinData
159
160
  # in the listing.
160
161
  def field_names(include_hidden = false)
161
162
  if include_hidden
162
- @field_names.dup
163
+ @field_names.compact
163
164
  else
164
165
  hidden = get_parameter(:hide) || []
165
- @field_names - hidden
166
+ @field_names.compact - hidden
166
167
  end
167
168
  end
168
169
 
@@ -28,7 +28,7 @@ module BinData
28
28
  end
29
29
 
30
30
  def endian(endian = nil)
31
- @endian ||= nil
31
+ @endian ||= default_endian
32
32
  if [:little, :big].include?(endian)
33
33
  @endian = endian
34
34
  elsif endian != nil
@@ -38,17 +38,31 @@ module BinData
38
38
  @endian
39
39
  end
40
40
 
41
+ def wrapped(*args)
42
+ @wrapped ||= default_wrapped
43
+ if args.length == 2
44
+ type, params = *args
45
+ ensure_type_exists(type)
46
+
47
+ if wrapped != nil
48
+ raise SyntaxError, "#{self} can only wrap one type", caller(2)
49
+ end
50
+ @wrapped = [type, params]
51
+ end
52
+ @wrapped
53
+ end
54
+
41
55
  def method_missing(symbol, *args) #:nodoc:
42
56
  type = symbol
43
57
  params = args.length == 0 ? {} : args[0]
44
58
 
45
- set_wrapped(type, params)
59
+ wrapped(type, params)
46
60
  end
47
61
 
48
62
  def sanitize_parameters!(params, sanitizer) #:nodoc:
49
- raise "Nothing to wrap" unless defined? @wrapped
63
+ raise "Nothing to wrap" if wrapped.nil?
50
64
 
51
- wrapped_type, wrapped_params = @wrapped
65
+ wrapped_type, wrapped_params = wrapped
52
66
  wrapped_params = wrapped_params.dup
53
67
 
54
68
  params.move_unknown_parameters_to(wrapped_params)
@@ -59,13 +73,20 @@ module BinData
59
73
  #-------------
60
74
  private
61
75
 
62
- def set_wrapped(type, params)
63
- ensure_type_exists(type)
76
+ def parent_wrapper
77
+ ancestors[1..-1].find { |cls|
78
+ cls.ancestors[1..-1].include?(BinData::Wrapper)
79
+ }
80
+ end
64
81
 
65
- if defined? @wrapped
66
- raise SyntaxError, "#{self} can only wrap one type", caller(2)
67
- end
68
- @wrapped = [type, params]
82
+ def default_endian
83
+ wrap = parent_wrapper
84
+ wrap ? wrap.endian : nil
85
+ end
86
+
87
+ def default_wrapped
88
+ wrap = parent_wrapper
89
+ wrap ? wrap.wrapped : nil
69
90
  end
70
91
 
71
92
  def ensure_type_exists(type)
data/manual.haml CHANGED
@@ -125,6 +125,11 @@
125
125
  License
126
126
  .acc-section
127
127
  .acc-content
128
+ %li
129
+ %a{ :href => "#installation" }
130
+ Installation
131
+ .acc-section
132
+ .acc-content
128
133
  %li
129
134
  %a{ :href => "#overview" }
130
135
  Overview
@@ -247,6 +252,11 @@
247
252
  .acc-section
248
253
  .acc-content
249
254
  %ul.level2#menu9
255
+ %li
256
+ %a{ :href => "#skipping_over_unused_data" }
257
+ Skipping over unused data
258
+ .acc-section
259
+ .acc-content
250
260
  %li
251
261
  %a{ :href => "#wrappers" }
252
262
  Wrappers
data/spec/int_spec.rb CHANGED
@@ -149,7 +149,6 @@ share_examples_for "All Integers" do
149
149
  (1 .. 20).each do |nbytes|
150
150
  nbits = nbytes * 8
151
151
  class_name = "#{base}#{nbits}#{endian_str}"
152
- BinData::Int.define_class(nbits, endian, signed_sym)
153
152
  result[BinData.const_get(class_name)] = nbytes
154
153
  end
155
154
 
@@ -200,19 +199,19 @@ end
200
199
  describe "Custom defined integers" do
201
200
  it "should fail unless bits are a multiple of 8" do
202
201
  lambda {
203
- BinData::Int.define_class(7, :little, :unsigned)
202
+ BinData::Uint7le
204
203
  }.should raise_error
205
204
 
206
205
  lambda {
207
- BinData::Int.define_class(7, :big, :unsigned)
206
+ BinData::Uint7be
208
207
  }.should raise_error
209
208
 
210
209
  lambda {
211
- BinData::Int.define_class(7, :little, :signed)
210
+ BinData::Int7le
212
211
  }.should raise_error
213
212
 
214
213
  lambda {
215
- BinData::Int.define_class(7, :big, :signed)
214
+ BinData::Int7be
216
215
  }.should raise_error
217
216
  end
218
217
  end
data/spec/io_spec.rb CHANGED
@@ -3,6 +3,44 @@
3
3
  require File.expand_path(File.join(File.dirname(__FILE__), "spec_common"))
4
4
  require 'bindata/io'
5
5
 
6
+ describe BinData::IO, "reading from non seekable stream" do
7
+ before(:each) do
8
+ @rd, @wr = IO::pipe
9
+ if fork
10
+ # parent
11
+ @wr.close
12
+ @io = BinData::IO.new(@rd)
13
+ else
14
+ # child
15
+ begin
16
+ @rd.close
17
+ @wr.write "a" * 5000
18
+ @wr.write "b" * 5000
19
+ @wr.close
20
+ rescue Exception
21
+ # ignore it
22
+ ensure
23
+ exit!
24
+ end
25
+ end
26
+ end
27
+
28
+ after(:each) do
29
+ @rd.close
30
+ Process.wait
31
+ end
32
+
33
+ it "should always have an offset of 0" do
34
+ @io.readbytes(10)
35
+ @io.offset.should == 0
36
+ end
37
+
38
+ it "should seek" do
39
+ @io.seekbytes(4999)
40
+ @io.readbytes(5).should == "abbbb"
41
+ end
42
+ end
43
+
6
44
  describe BinData::IO do
7
45
  it "should wrap strings in StringIO" do
8
46
  io = BinData::IO.new("abcd")
@@ -152,3 +152,20 @@ describe BinData::Primitive, "with custom default parameters" do
152
152
  obj.value.should == 7
153
153
  end
154
154
  end
155
+
156
+ describe BinData::Primitive, "derived classes" do
157
+ class ParentDerivedPrimitive < BinData::Primitive
158
+ uint16be :a
159
+ def get; self.a; end
160
+ def set(v); self.a = v; end
161
+ end
162
+
163
+ class ChildDerivedPrimitive < ParentDerivedPrimitive
164
+ end
165
+
166
+ it "should derive" do
167
+ a = ChildDerivedPrimitive.new
168
+ a.value = 7
169
+ a.to_binary_s.should == "\000\007"
170
+ end
171
+ end
data/spec/record_spec.rb CHANGED
@@ -71,6 +71,34 @@ describe BinData::Record, "when defining" do
71
71
  end
72
72
  end
73
73
 
74
+ describe BinData::Record, "with anonymous fields" do
75
+ class AnonymousRecord < BinData::Record
76
+ int8 'a', :initial_value => 10
77
+ int8 ''
78
+ int8 :value => :a
79
+ end
80
+
81
+ before(:each) do
82
+ @obj = AnonymousRecord.new
83
+ end
84
+
85
+ it "should only show non anonymous fields" do
86
+ @obj.field_names.should == ["a"]
87
+ end
88
+
89
+ it "should not include anonymous fields in snapshot" do
90
+ @obj.a = 5
91
+ @obj.snapshot.should == {"a" => 5}
92
+ end
93
+
94
+ it "should write anonymous fields" do
95
+ str = "\001\002\003"
96
+ @obj.read(str)
97
+ @obj.a.clear
98
+ @obj.to_binary_s.should == "\012\002\012"
99
+ end
100
+ end
101
+
74
102
  describe BinData::Record, "with hidden fields" do
75
103
  class HiddenRecord < BinData::Record
76
104
  hide :b, 'c'
@@ -350,3 +378,24 @@ describe BinData::Record, "with :onlyif" do
350
378
  @obj.to_binary_s.should == "\x03\x05"
351
379
  end
352
380
  end
381
+
382
+ describe BinData::Record, "derived classes" do
383
+ class ParentDerivedRecord < BinData::Record
384
+ uint8 :a
385
+ end
386
+
387
+ class ChildDerivedRecord < ParentDerivedRecord
388
+ uint8 :b
389
+ end
390
+
391
+ it "should not affect parent" do
392
+ parent = ParentDerivedRecord.new
393
+ parent.field_names.should == ["a"]
394
+ end
395
+
396
+ it "should inherit fields" do
397
+ child = ChildDerivedRecord.new
398
+ child.field_names.should == ["a", "b"]
399
+ end
400
+ end
401
+
@@ -3,6 +3,7 @@
3
3
  require File.expand_path(File.join(File.dirname(__FILE__), "spec_common"))
4
4
  require 'bindata/bits'
5
5
  require 'bindata/int'
6
+ require 'bindata/float'
6
7
  require 'bindata/registry'
7
8
 
8
9
  describe BinData::Registry do
@@ -55,54 +56,46 @@ describe BinData::Registry do
55
56
  it "should ignore the outer nestings of classes" do
56
57
  @r.underscore_name('A::B::C').should == 'c'
57
58
  end
59
+ end
58
60
 
59
- =begin
60
- it "should lookup integers with endian" do
61
- @r.register("Int24be", A)
62
- @r.register("Int24le", B)
63
- @r.register("Uint24be", C)
64
- @r.register("Uint24le", D)
61
+ describe BinData::Registry, "with numerics" do
62
+ before(:each) do
63
+ @r = BinData::RegisteredClasses
64
+ end
65
65
 
66
- @r.lookup("int24", :big).should == A
67
- @r.lookup("int24", :little).should == B
68
- @r.lookup("uint24", :big).should == C
69
- @r.lookup("uint24", :little).should == D
66
+ it "should lookup integers with endian" do
67
+ @r.lookup("int24", :big).to_s.should == "BinData::Int24be"
68
+ @r.lookup("int24", :little).to_s.should == "BinData::Int24le"
69
+ @r.lookup("uint24", :big).to_s.should == "BinData::Uint24be"
70
+ @r.lookup("uint24", :little).to_s.should == "BinData::Uint24le"
70
71
  end
71
72
 
72
73
  it "should not lookup integers without endian" do
73
- @r.register("Int24be", A)
74
-
75
74
  @r.lookup("int24").should be_nil
76
75
  end
77
76
 
78
- it "should lookup floats with endian" do
79
- @r.register("FloatBe", A)
80
- @r.register("FloatLe", B)
81
- @r.register("DoubleBe", C)
82
- @r.register("DoubleLe", D)
83
-
84
- @r.lookup("float", :big).should == A
85
- @r.lookup("float", :little).should == B
86
- @r.lookup("double", :big).should == C
87
- @r.lookup("double", :little).should == D
77
+ it "should not lookup non byte based integers" do
78
+ @r.lookup("int3").should be_nil
79
+ @r.lookup("int3", :big).should be_nil
80
+ @r.lookup("int3", :little).should be_nil
88
81
  end
89
82
 
90
- it "should automatically create classes for integers" do
91
- BinData.const_defined?(:Uint40be).should be_false
92
- @r.lookup("uint40be")
93
- BinData.const_defined?(:Uint40be).should be_true
83
+ it "should lookup floats with endian" do
84
+ @r.lookup("float", :big).to_s.should == "BinData::FloatBe"
85
+ @r.lookup("float", :little).to_s.should == "BinData::FloatLe"
86
+ @r.lookup("double", :big).to_s.should == "BinData::DoubleBe"
87
+ @r.lookup("double", :little).to_s.should == "BinData::DoubleLe"
94
88
  end
95
89
 
96
- it "should automatically create classes for big endian bits" do
97
- BinData.const_defined?(:Bit801).should be_false
98
- @r.lookup("bit801")
99
- BinData.const_defined?(:Bit801).should be_true
90
+ it "should lookup bits" do
91
+ @r.lookup("bit5").to_s.should == "BinData::Bit5"
92
+ @r.lookup("bit6le").to_s.should == "BinData::Bit6le"
100
93
  end
101
94
 
102
- it "should automatically create classes for little endian bits" do
103
- BinData.const_defined?(:Bit802le).should be_false
104
- @r.lookup("bit802le")
105
- BinData.const_defined?(:Bit802le).should be_true
95
+ it "should lookup bits by ignoring endian" do
96
+ @r.lookup("bit2", :big).to_s.should == "BinData::Bit2"
97
+ @r.lookup("bit3le", :big).to_s.should == "BinData::Bit3le"
98
+ @r.lookup("bit2", :little).to_s.should == "BinData::Bit2"
99
+ @r.lookup("bit3le", :little).to_s.should == "BinData::Bit3le"
106
100
  end
107
- =end
108
101
  end
data/spec/skip_spec.rb ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "spec_common"))
4
+ require 'bindata/skip'
5
+
6
+ describe BinData::Skip do
7
+ before(:each) do
8
+ @skip = BinData::Skip.new(:length => 5)
9
+ end
10
+
11
+ it "should default to the empty string" do
12
+ @skip.should == ""
13
+ end
14
+
15
+ it "should skip bytes" do
16
+ io = StringIO.new("abcdefghij")
17
+ @skip.read(io)
18
+ io.pos.should == 5
19
+ end
20
+
21
+ it "should have expected binary representation" do
22
+ @skip.to_binary_s.should == "\000" * 5
23
+ end
24
+
25
+ it "should have expected binary representation after setting value" do
26
+ @skip.value = "123"
27
+ @skip.to_binary_s.should == "\000" * 5
28
+ end
29
+
30
+ it "should have expected binary representation after reading" do
31
+ io = StringIO.new("abcdefghij")
32
+ @skip.read(io)
33
+ @skip.to_binary_s.should == "\000" * 5
34
+ end
35
+ end
data/spec/struct_spec.rb CHANGED
@@ -41,6 +41,32 @@ describe BinData::Struct, "when initializing" do
41
41
  end
42
42
  end
43
43
 
44
+ describe BinData::Struct, "with anonymous fields" do
45
+ before(:each) do
46
+ @params = { :fields => [
47
+ [:int8, :a, {:initial_value => 10}],
48
+ [:int8, nil],
49
+ [:int8, '', {:value => :a}]] }
50
+ @obj = BinData::Struct.new(@params)
51
+ end
52
+
53
+ it "should only show non anonymous fields" do
54
+ @obj.field_names.should == ["a"]
55
+ end
56
+
57
+ it "should not include anonymous fields in snapshot" do
58
+ @obj.a = 5
59
+ @obj.snapshot.should == {"a" => 5}
60
+ end
61
+
62
+ it "should write anonymous fields" do
63
+ str = "\001\002\003"
64
+ @obj.read(str)
65
+ @obj.a.clear
66
+ @obj.to_binary_s.should == "\012\002\012"
67
+ end
68
+ end
69
+
44
70
  describe BinData::Struct, "with hidden fields" do
45
71
  before(:each) do
46
72
  @params = { :hide => [:b, 'c'],
data/spec/wrapper_spec.rb CHANGED
@@ -81,3 +81,17 @@ describe BinData::Wrapper, "inside a struct" do
81
81
  obj.should == {'b' => 2}
82
82
  end
83
83
  end
84
+
85
+ describe BinData::Wrapper, "derived classes" do
86
+ class ParentDerivedWrapper < BinData::Wrapper
87
+ uint32le
88
+ end
89
+
90
+ class ChildDerivedWrapper < ParentDerivedWrapper
91
+ end
92
+
93
+ it "should wrap" do
94
+ a = ChildDerivedWrapper.new
95
+ a.num_bytes.should == 4
96
+ end
97
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bindata
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dion Mendel
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-13 00:00:00 +08:00
12
+ date: 2009-11-24 00:00:00 +08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -22,67 +22,71 @@ extensions: []
22
22
  extra_rdoc_files:
23
23
  - NEWS
24
24
  files:
25
- - INSTALL
26
- - TODO
27
- - GPL
28
- - Rakefile
29
25
  - COPYING
30
- - NEWS
26
+ - INSTALL
31
27
  - README
28
+ - NEWS
29
+ - GPL
32
30
  - ChangeLog
33
- - examples/gzip.rb
31
+ - Rakefile
32
+ - TODO
34
33
  - examples/ip_address.rb
35
- - spec/primitive_spec.rb
36
- - spec/record_spec.rb
37
- - spec/float_spec.rb
38
- - spec/int_spec.rb
39
- - spec/registry_spec.rb
40
- - spec/lazy_spec.rb
41
- - spec/deprecated_spec.rb
42
- - spec/array_spec.rb
43
- - spec/bits_spec.rb
34
+ - examples/gzip.rb
44
35
  - spec/io_spec.rb
45
- - spec/struct_spec.rb
46
- - spec/stringz_spec.rb
47
36
  - spec/rest_spec.rb
48
- - spec/base_spec.rb
37
+ - spec/lazy_spec.rb
38
+ - spec/primitive_spec.rb
49
39
  - spec/wrapper_spec.rb
40
+ - spec/spec_common.rb
41
+ - spec/registry_spec.rb
42
+ - spec/string_spec.rb
43
+ - spec/stringz_spec.rb
50
44
  - spec/choice_spec.rb
51
45
  - spec/example.rb
52
- - spec/system_spec.rb
53
- - spec/string_spec.rb
54
- - spec/spec_common.rb
55
46
  - spec/base_primitive_spec.rb
56
- - lib/bindata/array.rb
57
- - lib/bindata/base_primitive.rb
58
- - lib/bindata/int.rb
59
- - lib/bindata/rest.rb
60
- - lib/bindata/struct.rb
61
- - lib/bindata/lazy.rb
62
- - lib/bindata/choice.rb
63
- - lib/bindata/string.rb
47
+ - spec/bits_spec.rb
48
+ - spec/deprecated_spec.rb
49
+ - spec/base_spec.rb
50
+ - spec/int_spec.rb
51
+ - spec/system_spec.rb
52
+ - spec/record_spec.rb
53
+ - spec/float_spec.rb
54
+ - spec/skip_spec.rb
55
+ - spec/struct_spec.rb
56
+ - spec/array_spec.rb
57
+ - lib/bindata/deprecated.rb
58
+ - lib/bindata/record.rb
64
59
  - lib/bindata/float.rb
65
- - lib/bindata/base.rb
66
- - lib/bindata/primitive.rb
67
60
  - lib/bindata/registry.rb
68
- - lib/bindata/bits.rb
69
- - lib/bindata/deprecated.rb
61
+ - lib/bindata/lazy.rb
62
+ - lib/bindata/choice.rb
63
+ - lib/bindata/struct.rb
70
64
  - lib/bindata/params.rb
71
- - lib/bindata/sanitize.rb
65
+ - lib/bindata/base_primitive.rb
66
+ - lib/bindata/bits.rb
72
67
  - lib/bindata/trace.rb
68
+ - lib/bindata/wrapper.rb
69
+ - lib/bindata/sanitize.rb
70
+ - lib/bindata/base.rb
71
+ - lib/bindata/skip.rb
72
+ - lib/bindata/rest.rb
73
+ - lib/bindata/string.rb
74
+ - lib/bindata/array.rb
73
75
  - lib/bindata/stringz.rb
76
+ - lib/bindata/primitive.rb
77
+ - lib/bindata/int.rb
74
78
  - lib/bindata/io.rb
75
- - lib/bindata/record.rb
76
- - lib/bindata/wrapper.rb
77
79
  - lib/bindata.rb
78
80
  - tasks/rdoc.rake
79
81
  - tasks/manual.rake
80
- - tasks/pkg.rake
81
82
  - tasks/rspec.rake
83
+ - tasks/pkg.rake
82
84
  - setup.rb
83
85
  - manual.haml
84
86
  has_rdoc: true
85
87
  homepage: http://bindata.rubyforge.org
88
+ licenses: []
89
+
86
90
  post_install_message:
87
91
  rdoc_options:
88
92
  - --main
@@ -104,9 +108,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
108
  requirements: []
105
109
 
106
110
  rubyforge_project: bindata
107
- rubygems_version: 1.3.1
111
+ rubygems_version: 1.3.5
108
112
  signing_key:
109
- specification_version: 2
113
+ specification_version: 3
110
114
  summary: A declarative way to read and write binary file formats
111
115
  test_files: []
112
116