bindata 0.9.0 → 0.9.1

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,11 @@
1
1
  = BinData Changelog
2
2
 
3
+ == Version 0.9.1 (2008-06-15)
4
+
5
+ * Implemented bit fields.
6
+ * Added :onlyif parameter to Base for specifying optional fields.
7
+ * Fixed IO offset bug with SingleValues.
8
+
3
9
  == Version 0.9.0 (2008-06-02)
4
10
 
5
11
  * Added :adjust_offset option to automatically seek to a given offset.
@@ -7,14 +13,14 @@
7
13
  * Choice now accepts sparse arrays and hashes as :choice.
8
14
  * Added BinData::Rest to help with debugging.
9
15
  * Major internal restructuring - memory usage is much better.
10
- * Improved documentation
16
+ * Improved documentation.
11
17
 
12
18
  == Version 0.8.1 (2008-01-14)
13
19
 
14
20
  * Reduced memory consumption.
15
21
  * Increased execution speed.
16
- * Deprecated BinData::Base.parameters
17
- * Fixed spec syntax (thanks to David Goodlad)
22
+ * Deprecated BinData::Base.parameters.
23
+ * Fixed spec syntax (thanks to David Goodlad).
18
24
 
19
25
  == Version 0.8.0 (2007-10-14)
20
26
 
data/README CHANGED
@@ -175,6 +175,16 @@ BinData::Uint32be:: Unsigned 32 bit integer (big endian).
175
175
  BinData::Uint64le:: Unsigned 64 bit integer (little endian).
176
176
  BinData::Uint64be:: Unsigned 64 bit integer (big endian).
177
177
 
178
+ BinData::Bit1:: 1 bit unsigned integer (big endian).
179
+ BinData::Bit2:: 2 bit unsigned integer (big endian).
180
+ ...
181
+ BinData::Bit63:: 63 bit unsigned integer (big endian).
182
+
183
+ BinData::Bit1le:: 1 bit unsigned integer (little endian).
184
+ BinData::Bit2le:: 2 bit unsigned integer (little endian).
185
+ ...
186
+ BinData::Bit63le:: 63 bit unsigned integer (little endian).
187
+
178
188
  BinData::FloatLe:: Single precision floating point number (little endian).
179
189
  BinData::FloatBe:: Single precision floating point number (big endian).
180
190
  BinData::DoubleLe:: Double precision floating point number (little endian).
data/examples/gzip.rb CHANGED
@@ -10,59 +10,46 @@ class Gzip
10
10
  DEFLATE = 8
11
11
 
12
12
  class Extra < BinData::MultiValue
13
- uint16le :len, :length => lambda { data.length }
14
- string :data, :read_length => :len
13
+ endian :little
14
+
15
+ uint16 :len, :length => lambda { data.length }
16
+ string :data, :read_length => :len
15
17
  end
16
18
 
17
19
  class Header < BinData::MultiValue
18
- uint16le :ident, :value => 0x8b1f, :check_value => 0x8b1f
19
- uint8 :compression_method, :initial_value => DEFLATE
20
- uint8 :flags, :value => :calculate_flags_val,
21
- # Upper 3 bits must be zero
22
- :check_value => lambda { (value & 0xe0) == 0 }
23
- uint32le :mtime
24
- uint8 :extra_flags
25
- uint8 :os, :initial_value => 255 # unknown OS
26
-
27
- # These fields are optional depending on the bits in flags
28
- extra :extra, :readwrite => :extra?
29
- stringz :file_name, :readwrite => :file_name?
30
- stringz :comment, :readwrite => :comment?
31
- uint16le :crc16, :readwrite => :crc16?
32
-
33
-
34
- ## Convenience methods for accessing and manipulating flags
20
+ endian :little
35
21
 
36
- attr_writer :text
22
+ uint16 :ident, :value => 0x8b1f, :check_value => 0x8b1f
23
+ uint8 :compression_method, :initial_value => DEFLATE
37
24
 
38
- # Access bits of flags
39
- def text?; flag_val(0) end
40
- def crc16?; flag_val(1) end
41
- def extra?; flag_val(2) end
42
- def file_name?; flag_val(3) end
43
- def comment?; flag_val(4) end
25
+ bit3 :freserved, :value => 0, :check_value => 0
26
+ bit1 :fcomment, :value => lambda { comment.length > 0 ? 1 : 0 }
27
+ bit1 :ffile_name, :value => lambda { file_name.length > 0 ? 1 : 0 }
28
+ bit1 :fextra, :value => lambda { extra.len > 0 ? 1 : 0 }
29
+ bit1 :fcrc16, :value => 0 # see comment below
30
+ bit1 :ftext
44
31
 
45
- def flag_val(bit) (flags & (1 << bit)) != 0 end
32
+ # Never include header crc. This is because the current versions of the
33
+ # command-line version of gzip (up through version 1.3.x) do not
34
+ # support header crc's, and will report that it is a "multi-part gzip
35
+ # file" and give up.
46
36
 
47
- # Calculate the value of flags based on current state.
48
- def calculate_flags_val
49
- ((@text ? 1 : 0) << 0) |
37
+ uint32 :mtime
38
+ uint8 :extra_flags
39
+ uint8 :os, :initial_value => 255 # unknown OS
50
40
 
51
- # Never include header crc. This is because the current versions of the
52
- # command-line version of gzip (up through version 1.3.x) do not
53
- # support header crc's, and will report that it is a "multi-part gzip
54
- # file" and give up.
55
- ((!clear?(:crc16) ? 0 : 0) << 1) |
56
-
57
- ((!clear?(:extra) ? 1 : 0) << 2) |
58
- ((!clear?(:file_name) ? 1 : 0) << 3) |
59
- ((!clear?(:comment) ? 1 : 0) << 4)
60
- end
41
+ # These fields are optional depending on the bits in flags
42
+ extra :extra, :onlyif => lambda { fextra.nonzero? }
43
+ stringz :file_name, :onlyif => lambda { ffile_name.nonzero? }
44
+ stringz :comment, :onlyif => lambda { fcomment.nonzero? }
45
+ uint16 :crc16, :onlyif => lambda { fcrc16.nonzero? }
61
46
  end
62
47
 
63
48
  class Footer < BinData::MultiValue
64
- uint32le :crc32
65
- uint32le :uncompressed_size
49
+ endian :little
50
+
51
+ uint32 :crc32
52
+ uint32 :uncompressed_size
66
53
  end
67
54
 
68
55
  def initialize
@@ -71,8 +58,8 @@ class Gzip
71
58
  end
72
59
 
73
60
  attr_accessor :compressed
74
- def_delegators :@header, :file_name=, :file_name, :file_name?
75
- def_delegators :@header, :comment=, :comment, :comment?
61
+ def_delegators :@header, :file_name=, :file_name
62
+ def_delegators :@header, :comment=, :comment
76
63
  def_delegators :@header, :compression_method
77
64
  def_delegators :@footer, :crc32, :uncompressed_size
78
65
 
@@ -166,7 +153,7 @@ if __FILE__ == $0
166
153
  g.uncompressed_size,
167
154
  ratio,
168
155
  g.file_name]
169
- puts "Comment: #{g.comment}" if g.comment?
156
+ puts "Comment: #{g.comment}" if g.comment != ""
170
157
  puts
171
158
 
172
159
  puts "Executing gzip -l -v"
data/lib/bindata.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  # Copyright (c) 2007,2008 Dion Mendel.
3
3
 
4
4
  require 'bindata/array'
5
+ require 'bindata/bits'
5
6
  require 'bindata/choice'
6
7
  require 'bindata/float'
7
8
  require 'bindata/int'
@@ -17,5 +18,5 @@ require 'bindata/struct'
17
18
  # A declarative way to read and write structured binary data.
18
19
  #
19
20
  module BinData
20
- VERSION = "0.9.0"
21
+ VERSION = "0.9.1"
21
22
  end
data/lib/bindata/array.rb CHANGED
@@ -95,19 +95,6 @@ module BinData
95
95
  @element_params = el_params
96
96
  end
97
97
 
98
- # Clears the element at position +index+. If +index+ is not given, then
99
- # the internal state of the array is reset to that of a newly created
100
- # object.
101
- def clear(index = nil)
102
- if @element_list.nil?
103
- # do nothing as the array is already clear
104
- elsif index.nil?
105
- @element_list = nil
106
- else
107
- elements[index].clear
108
- end
109
- end
110
-
111
98
  # Returns if the element at position +index+ is clear?. If +index+
112
99
  # is not given, then returns whether all fields are clear.
113
100
  def clear?(index = nil)
@@ -121,49 +108,19 @@ module BinData
121
108
  end
122
109
  end
123
110
 
124
- # Reads the values for all fields in this object from +io+.
125
- def _do_read(io)
126
- if has_param?(:initial_length)
127
- elements.each { |f| f.do_read(io) }
128
- else # :read_until
111
+ # Clears the element at position +index+. If +index+ is not given, then
112
+ # the internal state of the array is reset to that of a newly created
113
+ # object.
114
+ def clear(index = nil)
115
+ if @element_list.nil?
116
+ # do nothing as the array is already clear
117
+ elsif index.nil?
129
118
  @element_list = nil
130
- loop do
131
- element = append_new_element
132
- element.do_read(io)
133
- variables = { :index => self.length - 1, :element => self.last,
134
- :array => self }
135
- finished = eval_param(:read_until, variables)
136
- break if finished
137
- end
138
- end
139
- end
140
-
141
- # To be called after calling #do_read.
142
- def done_read
143
- elements.each { |f| f.done_read }
144
- end
145
-
146
- # Writes the values for all fields in this object to +io+.
147
- def _write(io)
148
- elements.each { |f| f.write(io) }
149
- end
150
-
151
- # Returns the number of bytes it will take to write the element at
152
- # +index+. If +index+, then returns the number of bytes required
153
- # to write all fields.
154
- def _num_bytes(index)
155
- if index.nil?
156
- elements.inject(0) { |sum, f| sum + f.num_bytes }
157
119
  else
158
- elements[index].num_bytes
120
+ elements[index].clear
159
121
  end
160
122
  end
161
123
 
162
- # Returns a snapshot of the data in this array.
163
- def snapshot
164
- elements.collect { |e| e.snapshot }
165
- end
166
-
167
124
  # Returns whether this data object contains a single value. Single
168
125
  # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
169
126
  def single_value?
@@ -175,6 +132,16 @@ module BinData
175
132
  []
176
133
  end
177
134
 
135
+ # Returns a snapshot of the data in this array.
136
+ def snapshot
137
+ elements.collect { |e| e.snapshot }
138
+ end
139
+
140
+ # To be called after calling #do_read.
141
+ def done_read
142
+ elements.each { |f| f.done_read }
143
+ end
144
+
178
145
  # Appends a new element to the end of the array. If the array contains
179
146
  # single_values then the +value+ may be provided to the call.
180
147
  # Returns the appended object, or value in the case of single_values.
@@ -256,6 +223,39 @@ module BinData
256
223
  #---------------
257
224
  private
258
225
 
226
+ # Reads the values for all fields in this object from +io+.
227
+ def _do_read(io)
228
+ if has_param?(:initial_length)
229
+ elements.each { |f| f.do_read(io) }
230
+ else # :read_until
231
+ @element_list = nil
232
+ loop do
233
+ element = append_new_element
234
+ element.do_read(io)
235
+ variables = { :index => self.length - 1, :element => self.last,
236
+ :array => self }
237
+ finished = eval_param(:read_until, variables)
238
+ break if finished
239
+ end
240
+ end
241
+ end
242
+
243
+ # Writes the values for all fields in this object to +io+.
244
+ def _do_write(io)
245
+ elements.each { |f| f.do_write(io) }
246
+ end
247
+
248
+ # Returns the number of bytes it will take to write the element at
249
+ # +index+. If +index+, then returns the number of bytes required
250
+ # to write all fields.
251
+ def _do_num_bytes(index)
252
+ if index.nil?
253
+ (elements.inject(0) { |sum, f| sum + f.do_num_bytes }).ceil
254
+ else
255
+ elements[index].do_num_bytes
256
+ end
257
+ end
258
+
259
259
  # Returns the list of all elements in the array. The elements
260
260
  # will be instantiated on the first call to this method.
261
261
  def elements
data/lib/bindata/base.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'bindata/io'
1
2
  require 'bindata/lazy'
2
3
  require 'bindata/sanitize'
3
4
  require 'bindata/registry'
@@ -16,6 +17,9 @@ module BinData
16
17
  #
17
18
  # [<tt>:readwrite</tt>] If false, calls to #read or #write will
18
19
  # not perform any I/O. Default is true.
20
+ # [<tt>:onlyif</tt>] This is an alias for :readwrite. It is generally
21
+ # used to indicate a data object is optional.
22
+ # not perform any I/O. Default is true.
19
23
  # [<tt>:check_offset</tt>] Raise an error if the current IO offset doesn't
20
24
  # meet this criteria. A boolean return indicates
21
25
  # success or failure. Any other return is compared
@@ -116,6 +120,11 @@ module BinData
116
120
  def sanitize_parameters(params, *args)
117
121
  params = params.dup
118
122
 
123
+ # replace :onlyif with :readwrite
124
+ if params.has_key?(:onlyif)
125
+ params[:readwrite] = params.delete(:onlyif)
126
+ end
127
+
119
128
  # add default parameters
120
129
  default_parameters.each do |k,v|
121
130
  params[k] = v unless params.has_key?(k)
@@ -198,16 +207,7 @@ module BinData
198
207
 
199
208
  # Reads data into this data object by calling #do_read then #done_read.
200
209
  def read(io)
201
- # wrap strings in a StringIO
202
- io = StringIO.new(io) if io.respond_to?(:to_str)
203
-
204
- # remove previous method to prevent warnings
205
- class << io
206
- remove_method(:bindata_mark) if method_defined?(:bindata_mark)
207
- end
208
-
209
- # remember the current position in the IO object
210
- io.instance_eval "def bindata_mark; #{io.pos}; end"
210
+ io = BinData::IO.new(io) unless BinData::IO === io
211
211
 
212
212
  do_read(io)
213
213
  done_read
@@ -216,14 +216,49 @@ module BinData
216
216
 
217
217
  # Reads the value for this data from +io+.
218
218
  def do_read(io)
219
+ raise ArgumentError, "io must be a BinData::IO" unless BinData::IO === io
220
+
219
221
  clear
220
222
  check_offset(io)
221
- _do_read(io) if eval_param(:readwrite) != false
223
+
224
+ if eval_param(:readwrite)
225
+ _do_read(io)
226
+ end
222
227
  end
223
228
 
224
- # Writes the value for this data to +io+.
229
+ # Writes the value for this data to +io+ by calling #do_write.
225
230
  def write(io)
226
- _write(io) if eval_param(:readwrite) != false
231
+ io = BinData::IO.new(io) unless BinData::IO === io
232
+
233
+ do_write(io)
234
+ io.flush
235
+ self
236
+ end
237
+
238
+
239
+ # Writes the value for this data to +io+.
240
+ def do_write(io)
241
+ raise ArgumentError, "io must be a BinData::IO" unless BinData::IO === io
242
+
243
+ if eval_param(:readwrite)
244
+ _do_write(io)
245
+ end
246
+ end
247
+
248
+ # Returns the number of bytes it will take to write this data by calling
249
+ # #do_num_bytes.
250
+ def num_bytes(what = nil)
251
+ num = do_num_bytes(what)
252
+ num.ceil
253
+ end
254
+
255
+ # Returns the number of bytes it will take to write this data.
256
+ def do_num_bytes(what = nil)
257
+ if eval_param(:readwrite)
258
+ _do_num_bytes(what)
259
+ else
260
+ 0
261
+ end
227
262
  end
228
263
 
229
264
  # Returns the string representation of this data object.
@@ -234,11 +269,6 @@ module BinData
234
269
  io.read
235
270
  end
236
271
 
237
- # Returns the number of bytes it will take to write this data.
238
- def num_bytes(what = nil)
239
- (eval_param(:readwrite) != false) ? _num_bytes(what) : 0
240
- end
241
-
242
272
  # Return a human readable representation of this object.
243
273
  def inspect
244
274
  snapshot.inspect
@@ -277,7 +307,7 @@ module BinData
277
307
  # be called from #do_read before performing the reading.
278
308
  def check_offset(io)
279
309
  if has_param?(:check_offset)
280
- actual_offset = io.pos - io.bindata_mark
310
+ actual_offset = io.offset
281
311
  expected = eval_param(:check_offset, :offset => actual_offset)
282
312
 
283
313
  if not expected
@@ -287,12 +317,12 @@ module BinData
287
317
  "expected '#{expected}'"
288
318
  end
289
319
  elsif has_param?(:adjust_offset)
290
- actual_offset = io.pos - io.bindata_mark
320
+ actual_offset = io.offset
291
321
  expected = eval_param(:adjust_offset)
292
322
  if actual_offset != expected
293
323
  begin
294
324
  seek = expected - actual_offset
295
- io.seek(seek, IO::SEEK_CUR)
325
+ io.seekbytes(seek)
296
326
  warn "adjusting stream position by #{seek} bytes" if $VERBOSE
297
327
  rescue
298
328
  # could not seek so raise an error
@@ -317,46 +347,46 @@ module BinData
317
347
  raise NotImplementedError
318
348
  end
319
349
 
320
- # Reads the data for this data object from +io+.
321
- def _do_read(io)
350
+ # Returns whether this data object contains a single value. Single
351
+ # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
352
+ def single_value?
322
353
  raise NotImplementedError
323
354
  end
324
355
 
325
- # To be called after calling #do_read.
326
- def done_read
356
+ # Returns a list of the names of all fields accessible through this
357
+ # object.
358
+ def field_names
327
359
  raise NotImplementedError
328
360
  end
329
361
 
330
- # Writes the value for this data to +io+.
331
- def _write(io)
362
+ # Returns a snapshot of this data object.
363
+ def snapshot
332
364
  raise NotImplementedError
333
365
  end
334
366
 
335
- # Returns the number of bytes it will take to write this data.
336
- def _num_bytes
367
+ # To be called after calling #do_read.
368
+ def done_read
337
369
  raise NotImplementedError
338
370
  end
339
371
 
340
- # Returns a snapshot of this data object.
341
- def snapshot
372
+ # Reads the data for this data object from +io+.
373
+ def _do_read(io)
342
374
  raise NotImplementedError
343
375
  end
344
376
 
345
- # Returns whether this data object contains a single value. Single
346
- # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
347
- def single_value?
377
+ # Writes the value for this data to +io+.
378
+ def _do_write(io)
348
379
  raise NotImplementedError
349
380
  end
350
381
 
351
- # Returns a list of the names of all fields accessible through this
352
- # object.
353
- def field_names
382
+ # Returns the number of bytes it will take to write this data.
383
+ def _do_num_bytes
354
384
  raise NotImplementedError
355
385
  end
356
386
 
357
387
  # Set visibility requirements of methods to implement
358
- public :clear, :done_read, :snapshot, :single_value?, :field_names
359
- private :_do_read, :_write, :_num_bytes
388
+ public :clear, :single_value?, :field_names, :snapshot, :done_read
389
+ private :_do_read, :_do_write, :_do_num_bytes
360
390
 
361
391
  # End To be implemented by subclasses
362
392
  ###########################################################################