bindata 0.9.3 → 0.10.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 +18 -0
  2. data/NEWS +59 -0
  3. data/README +22 -23
  4. data/TODO +18 -12
  5. data/examples/gzip.rb +4 -4
  6. data/lib/bindata.rb +4 -3
  7. data/lib/bindata/array.rb +202 -132
  8. data/lib/bindata/base.rb +147 -166
  9. data/lib/bindata/{single.rb → base_primitive.rb} +82 -56
  10. data/lib/bindata/bits.rb +31 -770
  11. data/lib/bindata/choice.rb +157 -82
  12. data/lib/bindata/float.rb +25 -27
  13. data/lib/bindata/int.rb +144 -177
  14. data/lib/bindata/io.rb +59 -49
  15. data/lib/bindata/lazy.rb +80 -50
  16. data/lib/bindata/params.rb +134 -26
  17. data/lib/bindata/{single_value.rb → primitive.rb} +71 -64
  18. data/lib/bindata/{multi_value.rb → record.rb} +52 -70
  19. data/lib/bindata/registry.rb +49 -17
  20. data/lib/bindata/rest.rb +6 -10
  21. data/lib/bindata/sanitize.rb +55 -70
  22. data/lib/bindata/string.rb +60 -42
  23. data/lib/bindata/stringz.rb +34 -35
  24. data/lib/bindata/struct.rb +197 -152
  25. data/lib/bindata/trace.rb +35 -0
  26. data/spec/array_spec.rb +128 -112
  27. data/spec/{single_spec.rb → base_primitive_spec.rb} +102 -61
  28. data/spec/base_spec.rb +190 -185
  29. data/spec/bits_spec.rb +126 -98
  30. data/spec/choice_spec.rb +89 -98
  31. data/spec/example.rb +19 -0
  32. data/spec/float_spec.rb +28 -44
  33. data/spec/int_spec.rb +217 -127
  34. data/spec/io_spec.rb +41 -24
  35. data/spec/lazy_spec.rb +95 -49
  36. data/spec/primitive_spec.rb +191 -0
  37. data/spec/{multi_value_spec.rb → record_spec.rb} +124 -89
  38. data/spec/registry_spec.rb +53 -12
  39. data/spec/rest_spec.rb +2 -3
  40. data/spec/sanitize_spec.rb +47 -73
  41. data/spec/spec_common.rb +13 -1
  42. data/spec/string_spec.rb +34 -23
  43. data/spec/stringz_spec.rb +10 -18
  44. data/spec/struct_spec.rb +91 -63
  45. data/spec/system_spec.rb +291 -0
  46. metadata +12 -8
  47. data/spec/single_value_spec.rb +0 -131
data/lib/bindata/rest.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "bindata/single"
1
+ require "bindata/base_primitive"
2
2
 
3
3
  module BinData
4
4
  # Rest will consume the input stream from the current position to the end of
@@ -6,7 +6,7 @@ module BinData
6
6
  #
7
7
  # require 'bindata'
8
8
  #
9
- # class A < BinData::MultiValue
9
+ # class A < BinData::Record
10
10
  # string :a, :read_length => 5
11
11
  # rest :rest
12
12
  # end
@@ -15,25 +15,21 @@ module BinData
15
15
  # obj.a #=> "abcde"
16
16
  # obj.rest #=" "fghij"
17
17
  #
18
- class Rest < BinData::Single
18
+ class Rest < BinData::BasePrimitive
19
19
 
20
- # Register this class
21
20
  register(self.name, self)
22
21
 
23
22
  #---------------
24
23
  private
25
24
 
26
- # Return the string representation that +val+ will take when written.
27
- def val_to_str(val)
25
+ def value_to_binary_string(val)
28
26
  val
29
27
  end
30
28
 
31
- # Read a number of bytes from +io+ and return the value they represent.
32
- def read_val(io)
33
- io.raw_io.read
29
+ def read_and_return_value(io)
30
+ io.read_all_bytes
34
31
  end
35
32
 
36
- # Returns an empty string as default.
37
33
  def sensible_default
38
34
  ""
39
35
  end
@@ -1,37 +1,29 @@
1
+ require 'bindata/registry'
1
2
  require 'forwardable'
2
3
 
3
4
  module BinData
4
5
 
5
6
  # A BinData object accepts arbitrary parameters. This class only contains
6
- # parameters that have been sanitized, and categorizes them according to
7
- # whether they are BinData::Base.internal_parameters or are extra.
7
+ # parameters that have been sanitized.
8
8
  class SanitizedParameters
9
9
  extend Forwardable
10
10
 
11
- # Sanitize the given parameters.
12
- def initialize(klass, params)
13
- @hash = params
14
- @internal_parameters = {}
15
- @extra_parameters = {}
11
+ def initialize(the_class, params)
12
+ @parameters = {}
16
13
 
17
- # partition parameters into known and extra parameters
18
- @hash.each do |k,v|
14
+ params.each do |k,v|
19
15
  k = k.to_sym
20
16
  if v.nil?
21
- raise ArgumentError, "parameter :#{k} has nil value in #{klass}"
17
+ raise ArgumentError, "parameter :#{k} has nil value in #{the_class}"
22
18
  end
23
19
 
24
- if klass.internal_parameters.include?(k)
25
- @internal_parameters[k] = v
26
- else
27
- @extra_parameters[k] = v
28
- end
20
+ @parameters[k] = v
29
21
  end
30
22
  end
31
23
 
32
- attr_reader :internal_parameters, :extra_parameters
24
+ attr_reader :parameters
33
25
 
34
- def_delegators :@hash, :[], :has_key?, :include?, :keys
26
+ def_delegators :@parameters, :[], :has_key?, :include?, :keys
35
27
  end
36
28
 
37
29
  # The Sanitizer sanitizes the parameters that are passed when creating a
@@ -41,45 +33,21 @@ module BinData
41
33
  class Sanitizer
42
34
 
43
35
  class << self
44
-
45
- # Sanitize +params+ for +obj+.
36
+ # Sanitize +params+ for +the_class+.
46
37
  # Returns sanitized parameters.
47
- def sanitize(klass, params)
38
+ def sanitize(the_class, params)
48
39
  if SanitizedParameters === params
49
40
  params
50
41
  else
51
42
  sanitizer = self.new
52
- sanitizer.sanitize_params(klass, params)
43
+ sanitizer.sanitized_params(the_class, params)
53
44
  end
54
45
  end
55
-
56
- # Returns true if +type+ is registered.
57
- def type_exists?(type, endian = nil)
58
- lookup(type, endian) != nil
59
- end
60
-
61
- # Returns the class matching a previously registered +name+.
62
- def lookup(name, endian)
63
- name = name.to_s
64
- klass = Registry.instance.lookup(name)
65
- if klass.nil? and endian != nil
66
- # lookup failed so attempt endian lookup
67
- if /^u?int\d{1,3}$/ =~ name
68
- new_name = name + ((endian == :little) ? "le" : "be")
69
- klass = Registry.instance.lookup(new_name)
70
- elsif ["float", "double"].include?(name)
71
- new_name = name + ((endian == :little) ? "_le" : "_be")
72
- klass = Registry.instance.lookup(new_name)
73
- end
74
- end
75
- klass
76
- end
77
46
  end
78
47
 
79
- # Create a new Sanitizer.
80
48
  def initialize
81
- @seen = []
82
49
  @endian = nil
50
+ @seen = []
83
51
  end
84
52
 
85
53
  # Executes the given block with +endian+ set as the current endian.
@@ -94,44 +62,61 @@ module BinData
94
62
  end
95
63
  end
96
64
 
97
- # Converts +type+ into the appropriate class.
98
- def lookup_klass(type)
99
- klass = self.class.lookup(type, @endian)
100
- raise TypeError, "unknown type '#{type}'" if klass.nil?
101
- klass
65
+ def lookup_class(type)
66
+ registered_class = RegisteredClasses.lookup(type, @endian)
67
+ if registered_class.nil?
68
+ raise TypeError, "unknown type '#{type}'"
69
+ end
70
+ registered_class
102
71
  end
103
72
 
104
- # Sanitizes +params+ for +klass+.
105
- # Returns +sanitized_params+.
106
- def sanitize_params(klass, params)
73
+ def sanitized_params(the_class, params)
107
74
  new_params = params.nil? ? {} : params.dup
108
75
 
109
- if klass.recursive? and @seen.include?(klass)
110
- # This klass is defined recursively. Remember the current endian
111
- # and delay sanitizing the parameters until later.
112
- new_params[:endian] = @endian if can_store_endian?(klass, new_params)
113
- ret_val = new_params
76
+ if can_sanitize_parameters?(the_class)
77
+ get_sanitized_params(the_class, new_params)
114
78
  else
115
- @seen.push(klass)
79
+ store_current_endian!(the_class, new_params)
80
+ new_params
81
+ end
82
+ end
116
83
 
117
- # Sanitize new_params. This may recursively call this method again.
118
- klass.sanitize_parameters!(self, new_params)
119
- ret_val = SanitizedParameters.new(klass, new_params)
84
+ #---------------
85
+ private
86
+
87
+ def can_sanitize_parameters?(the_class)
88
+ not need_to_delay_sanitizing?(the_class)
89
+ end
90
+
91
+ def need_to_delay_sanitizing?(the_class)
92
+ the_class.recursive? and @seen.include?(the_class)
93
+ end
120
94
 
121
- @seen.pop
95
+ def get_sanitized_params(the_class, params)
96
+ result = nil
97
+ with_class_to_sanitize(the_class) do
98
+ the_class.sanitize_parameters!(self, params)
99
+ result = SanitizedParameters.new(the_class, params)
122
100
  end
101
+ result
102
+ end
123
103
 
124
- ret_val
104
+ def with_class_to_sanitize(the_class, &block)
105
+ @seen.push(the_class)
106
+ yield
107
+ @seen.pop
125
108
  end
126
109
 
127
- #---------------
128
- private
110
+ def store_current_endian!(the_class, params)
111
+ if can_store_endian?(the_class, params)
112
+ params[:endian] = @endian
113
+ end
114
+ end
129
115
 
130
- # Can we store the current endian for later?
131
- def can_store_endian?(klass, params)
132
- (@endian != nil and klass.internal_parameters.include?(:endian) and
116
+ def can_store_endian?(the_class, params)
117
+ (@endian != nil and
118
+ the_class.accepted_internal_parameters.include?(:endian) and
133
119
  not params.has_key?(:endian))
134
120
  end
135
121
  end
136
122
  end
137
-
@@ -1,4 +1,4 @@
1
- require "bindata/single"
1
+ require "bindata/base_primitive"
2
2
 
3
3
  module BinData
4
4
  # A String is a sequence of bytes. This is the same as strings in Ruby.
@@ -23,16 +23,16 @@ module BinData
23
23
  # obj = BinData::String.new(:length => 6, :trim_value => true)
24
24
  # obj.value = "abcd"
25
25
  # obj.value #=> "abcd"
26
- # obj.to_s #=> "abcd\000\000"
26
+ # obj.to_binary_s #=> "abcd\000\000"
27
27
  #
28
28
  # obj = BinData::String.new(:length => 6, :pad_char => 'A')
29
29
  # obj.value = "abcd"
30
30
  # obj.value #=> "abcdAA"
31
- # obj.to_s #=> "abcdAA"
31
+ # obj.to_binary_s #=> "abcdAA"
32
32
  #
33
33
  # == Parameters
34
34
  #
35
- # String objects accept all the params that BinData::Single
35
+ # String objects accept all the params that BinData::BasePrimitive
36
36
  # does, as well as the following:
37
37
  #
38
38
  # <tt>:read_length</tt>:: The length to use when reading a value.
@@ -41,75 +41,93 @@ module BinData
41
41
  # <tt>:pad_char</tt>:: The character to use when padding a string to a
42
42
  # set length. Valid values are Integers and
43
43
  # Strings of length 1. "\0" is the default.
44
- # <tt>:trim_value</tt>:: Boolean, default false. If set, #value will
44
+ # <tt>:trim_padding</tt>:: Boolean, default false. If set, #value will
45
45
  # return the value with all pad_chars trimmed
46
46
  # from the end of the string. The value will
47
47
  # not be trimmed when writing.
48
- class String < BinData::Single
48
+ class String < BinData::BasePrimitive
49
49
 
50
- # Register this class
51
50
  register(self.name, self)
52
51
 
53
- # These are the parameters used by this class.
54
- bindata_optional_parameters :read_length, :length, :trim_value
55
- bindata_default_parameters :pad_char => "\0"
56
- bindata_mutually_exclusive_parameters :read_length, :length
57
- bindata_mutually_exclusive_parameters :length, :value
52
+ optional_parameters :read_length, :length, :trim_padding
53
+ default_parameters :pad_char => "\0"
54
+ mutually_exclusive_parameters :read_length, :length
55
+ mutually_exclusive_parameters :length, :value
58
56
 
59
57
  class << self
60
58
 
61
- # Ensures that +params+ is of the form expected by #initialize.
59
+ def deprecate!(params, old_key, new_key)
60
+ if params.has_key?(old_key)
61
+ warn ":#{old_key} is deprecated. Replacing with :#{new_key}"
62
+ params[new_key] = params.delete(old_key)
63
+ end
64
+ end
65
+
62
66
  def sanitize_parameters!(sanitizer, params)
63
67
  # warn about deprecated param - remove before releasing 1.0
64
- if params[:initial_length]
65
- warn ":initial_length is deprecated. Replacing with :read_length"
66
- params[:read_length] = params.delete(:initial_length)
67
- end
68
+ deprecate!(params, :trim_value, :trim_padding)
69
+
70
+ warn_replacement_parameter(params, :initial_length, :read_length)
68
71
 
69
- # set :pad_char to be a single length character string
70
72
  if params.has_key?(:pad_char)
71
73
  ch = params[:pad_char]
72
- ch = ch.respond_to?(:chr) ? ch.chr : ch.to_s
73
- if ch.length > 1
74
- raise ArgumentError, ":pad_char must not contain more than 1 char"
75
- end
76
- params[:pad_char] = ch
74
+ params[:pad_char] = sanitized_pad_char(ch)
77
75
  end
78
76
 
79
77
  super(sanitizer, params)
80
78
  end
81
- end
82
79
 
83
- # Overrides value to return the value padded to the desired length or
84
- # trimmed as required.
85
- def value
86
- v = val_to_str(_value)
87
- if no_eval_param(:trim_value) == true
88
- v.sub!(/#{eval_param(:pad_char)}*$/, "")
80
+ #-------------
81
+ private
82
+
83
+ def sanitized_pad_char(ch)
84
+ result = ch.respond_to?(:chr) ? ch.chr : ch.to_s
85
+ if result.length > 1
86
+ raise ArgumentError, ":pad_char must not contain more than 1 char"
87
+ end
88
+ result
89
89
  end
90
- v
91
90
  end
92
91
 
93
92
  #---------------
94
93
  private
95
94
 
96
- # Returns +val+ ensuring that it is padded to the desired length.
97
- def val_to_str(val)
98
- # trim val if necessary
99
- len = eval_param(:length) || val.length
100
- str = val.slice(0, len)
95
+ def _snapshot
96
+ # override to ensure length and optionally trim padding
97
+ result = super
98
+ if has_parameter?(:length)
99
+ result = truncate_or_pad_to_length(result)
100
+ end
101
+ if get_parameter(:trim_padding) == true
102
+ result = trim_padding(result)
103
+ end
104
+ result
105
+ end
106
+
107
+ def truncate_or_pad_to_length(str)
108
+ len = eval_parameter(:length) || str.length
109
+ if str.length == len
110
+ str
111
+ elsif str.length > len
112
+ str.slice(0, len)
113
+ else
114
+ str + (eval_parameter(:pad_char) * (len - str.length))
115
+ end
116
+ end
117
+
118
+ def trim_padding(str)
119
+ str.sub(/#{eval_parameter(:pad_char)}*$/, "")
120
+ end
101
121
 
102
- # then pad to length if str is short
103
- str << (eval_param(:pad_char) * (len - str.length))
122
+ def value_to_binary_string(val)
123
+ truncate_or_pad_to_length(val)
104
124
  end
105
125
 
106
- # Read a number of bytes from +io+ and return the value they represent.
107
- def read_val(io)
108
- len = eval_param(:read_length) || eval_param(:length) || 0
126
+ def read_and_return_value(io)
127
+ len = eval_parameter(:read_length) || eval_parameter(:length) || 0
109
128
  io.readbytes(len)
110
129
  end
111
130
 
112
- # Returns an empty string as default.
113
131
  def sensible_default
114
132
  ""
115
133
  end
@@ -1,4 +1,4 @@
1
- require "bindata/single"
1
+ require "bindata/base_primitive"
2
2
 
3
3
  module BinData
4
4
  # A BinData::Stringz object is a container for a zero ("\0") terminated
@@ -16,42 +16,36 @@ module BinData
16
16
  # obj.snapshot #=> "abcd"
17
17
  # obj.value #=> "abcd"
18
18
  # obj.num_bytes #=> 5
19
- # obj.to_s #=> "abcd\000"
19
+ # obj.to_binary_s #=> "abcd\000"
20
20
  #
21
21
  # == Parameters
22
22
  #
23
- # Stringz objects accept all the params that BinData::Single
23
+ # Stringz objects accept all the params that BinData::BasePrimitive
24
24
  # does, as well as the following:
25
25
  #
26
26
  # <tt>:max_length</tt>:: The maximum length of the string including the zero
27
27
  # byte.
28
- class Stringz < BinData::Single
28
+ class Stringz < BinData::BasePrimitive
29
29
 
30
- # Register this class
31
30
  register(self.name, self)
32
31
 
33
- # These are the parameters used by this class.
34
- bindata_optional_parameters :max_length
35
-
36
- # Overrides value to return the value of this data excluding the trailing
37
- # zero byte.
38
- def value
39
- v = super
40
- val_to_str(v).chomp("\0")
41
- end
32
+ optional_parameters :max_length
42
33
 
43
34
  #---------------
44
35
  private
45
36
 
46
- # Returns +val+ ensuring it is zero terminated and no longer
47
- # than <tt>:max_length</tt> bytes.
48
- def val_to_str(val)
49
- zero_terminate(val, eval_param(:max_length))
37
+ def _snapshot
38
+ # override to always remove trailing zero bytes
39
+ result = super
40
+ trim_and_zero_terminate(result).chomp("\0")
41
+ end
42
+
43
+ def value_to_binary_string(val)
44
+ trim_and_zero_terminate(val)
50
45
  end
51
46
 
52
- # Read a number of bytes from +io+ and return the value they represent.
53
- def read_val(io)
54
- max_length = eval_param(:max_length)
47
+ def read_and_return_value(io)
48
+ max_length = eval_parameter(:max_length)
55
49
  str = ""
56
50
  i = 0
57
51
  ch = nil
@@ -63,24 +57,25 @@ module BinData
63
57
  i += 1
64
58
  end
65
59
 
66
- zero_terminate(str, max_length)
60
+ trim_and_zero_terminate(str)
67
61
  end
68
62
 
69
- # Returns an empty string as default.
70
63
  def sensible_default
71
64
  ""
72
65
  end
73
66
 
74
- # Returns +str+ after it has been zero terminated. The returned string
75
- # will not be longer than +max_length+.
76
- def zero_terminate(str, max_length = nil)
77
- # str must not be empty
78
- result = (str == "") ? "\0" : str
67
+ def trim_and_zero_terminate(str)
68
+ str = truncate_at_first_zero_byte(str)
69
+ str = trim_to(str, eval_parameter(:max_length))
70
+ append_zero_byte_if_needed(str)
71
+ end
79
72
 
80
- # remove anything after the first \0
81
- result = result.sub(/([^\0]*\0).*/, '\1')
73
+ def truncate_at_first_zero_byte(str)
74
+ str.sub(/([^\0]*\0).*/, '\1')
75
+ end
82
76
 
83
- # trim string to be no longer than max_length including zero byte
77
+ def trim_to(str, max_length = nil)
78
+ result = str
84
79
  if max_length
85
80
  max_length = 1 if max_length < 1
86
81
  result = result[0, max_length]
@@ -88,11 +83,15 @@ module BinData
88
83
  result[-1, 1] = "\0"
89
84
  end
90
85
  end
91
-
92
- # ensure last byte in the string is a zero byte
93
- result << "\0" if result[-1, 1] != "\0"
94
-
95
86
  result
96
87
  end
88
+
89
+ def append_zero_byte_if_needed(str)
90
+ if str.length == 0 or str[-1, 1] != "\0"
91
+ str + "\0"
92
+ else
93
+ str
94
+ end
95
+ end
97
96
  end
98
97
  end