bindata 0.8.1 → 0.9.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.
@@ -9,24 +9,29 @@ module BinData
9
9
  @registry = {}
10
10
  end
11
11
 
12
+ # Convert camelCase +name+ to underscore style.
13
+ def underscore_name(name)
14
+ name.to_s.sub(/.*::/, "").
15
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
16
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
17
+ tr("-", "_").
18
+ downcase
19
+ end
20
+
12
21
  # Registers the mapping of +name+ to +klass+. +name+ is converted
13
22
  # from camelCase to underscore style.
14
23
  # Returns the converted name
15
24
  def register(name, klass)
16
25
  # convert camelCase name to underscore style
17
- underscore_name = name.to_s.sub(/.*::/, "").
18
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
19
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
20
- tr("-", "_").
21
- downcase
26
+ name = underscore_name(name)
22
27
 
23
28
  # warn if replacing an existing class
24
- if $VERBOSE and (existing = @registry[underscore_name])
29
+ if $VERBOSE and (existing = @registry[name])
25
30
  warn "warning: replacing registered class #{existing} with #{klass}"
26
31
  end
27
32
 
28
- @registry[underscore_name] = klass
29
- underscore_name.dup
33
+ @registry[name] = klass
34
+ name.dup
30
35
  end
31
36
 
32
37
  # Returns the class matching a previously registered +name+.
@@ -0,0 +1,41 @@
1
+ require "bindata/single"
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::MultiValue
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 < Single
19
+
20
+ # Register this class
21
+ register(self.name, self)
22
+
23
+ #---------------
24
+ private
25
+
26
+ # Return the string representation that +val+ will take when written.
27
+ def val_to_str(val)
28
+ val
29
+ end
30
+
31
+ # Read a number of bytes from +io+ and return the value they represent.
32
+ def read_val(io)
33
+ io.read
34
+ end
35
+
36
+ # Returns an empty string as default.
37
+ def sensible_default
38
+ ""
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,36 @@
1
+ require 'forwardable'
2
+
3
+ module BinData
4
+ # A BinData object accepts arbitrary parameters. This class ensures that
5
+ # the parameters have been sanitized, and categorizes them according to
6
+ # whether they are BinData::Base.accepted_parameters or are extra.
7
+ class SanitizedParameters
8
+ extend Forwardable
9
+
10
+ # Sanitize the given parameters.
11
+ def initialize(klass, params, *args)
12
+ params ||= {}
13
+ @hash = klass.sanitize_parameters(params, *args)
14
+ @accepted_parameters = {}
15
+ @extra_parameters = {}
16
+
17
+ # partition parameters into known and extra parameters
18
+ @hash.each do |k,v|
19
+ k = k.to_sym
20
+ if v.nil?
21
+ raise ArgumentError, "parameter :#{k} has nil value in #{klass}"
22
+ end
23
+
24
+ if klass.accepted_parameters.include?(k)
25
+ @accepted_parameters[k] = v
26
+ else
27
+ @extra_parameters[k] = v
28
+ end
29
+ end
30
+ end
31
+
32
+ attr_reader :accepted_parameters, :extra_parameters
33
+
34
+ def_delegators :@hash, :[], :has_key?, :include?, :keys
35
+ end
36
+ end
@@ -6,6 +6,26 @@ module BinData
6
6
  # as integer, float or string. Only one value can be contained by this
7
7
  # object. This value can be read from or written to an IO stream.
8
8
  #
9
+ # require 'bindata'
10
+ #
11
+ # obj = BinData::Uint8.new(:initial_value => 42)
12
+ # obj.value #=> 42
13
+ # obj.value = 5
14
+ # obj.value #=> 5
15
+ # obj.clear
16
+ # obj.value #=> 42
17
+ #
18
+ # obj = BinData::Uint8.new(:value => 42)
19
+ # obj.value #=> 42
20
+ # obj.value = 5
21
+ # obj.value #=> 42
22
+ #
23
+ # obj = BinData::Uint8.new(:check_value => 3)
24
+ # obj.read("\005") #=> BinData::ValidityError: value is '5' but expected '3'
25
+ #
26
+ # obj = BinData::Uint8.new(:check_value => lambda { value < 5 })
27
+ # obj.read("\007") #=> BinData::ValidityError: value not as expected
28
+ #
9
29
  # == Parameters
10
30
  #
11
31
  # Parameters may be provided at initialisation to control the behaviour of
@@ -28,15 +48,15 @@ module BinData
28
48
  class Single < Base
29
49
  # These are the parameters used by this class.
30
50
  optional_parameters :initial_value, :value, :check_value
51
+ mutually_exclusive_parameters :initial_value, :value
31
52
 
32
- # Register the names of all subclasses of this class.
33
- def self.inherited(subclass) #:nodoc:
34
- register(subclass.name, subclass)
53
+ # Single objects don't contain fields so this returns an empty list.
54
+ def self.all_possible_field_names(sanitized_params)
55
+ []
35
56
  end
36
57
 
37
58
  def initialize(params = {}, env = nil)
38
59
  super(params, env)
39
- ensure_mutual_exclusion(:initial_value, :value)
40
60
  clear
41
61
  end
42
62
 
@@ -90,6 +110,11 @@ module BinData
90
110
  value
91
111
  end
92
112
 
113
+ # Single objects are single_values
114
+ def single_value?
115
+ true
116
+ end
117
+
93
118
  # Single objects don't contain fields so this returns an empty list.
94
119
  def field_names
95
120
  []
@@ -149,7 +174,7 @@ module BinData
149
174
  str
150
175
  end
151
176
 
152
- =begin
177
+ ###########################################################################
153
178
  # To be implemented by subclasses
154
179
 
155
180
  # Return the string representation that +val+ will take when written.
@@ -168,6 +193,6 @@ module BinData
168
193
  end
169
194
 
170
195
  # To be implemented by subclasses
171
- =end
196
+ ###########################################################################
172
197
  end
173
198
  end
@@ -0,0 +1,203 @@
1
+ require 'bindata/single'
2
+ require 'bindata/struct'
3
+
4
+ module BinData
5
+ # A SingleValue is a declarative way to define a new BinData data type.
6
+ # The data type must contain a single value only. For new data types
7
+ # that contain multiple values see BinData::MultiValue.
8
+ #
9
+ # To define a new data type, set fields as if for MultiValue and add a
10
+ # #get and #set method to extract / convert the data between the fields
11
+ # and the #value of the object.
12
+ #
13
+ # require 'bindata'
14
+ #
15
+ # class PascalString < BinData::SingleValue
16
+ # uint8 :len, :value => lambda { data.length }
17
+ # string :data, :read_length => :len
18
+ #
19
+ # def get
20
+ # self.data
21
+ # end
22
+ #
23
+ # def set(v)
24
+ # self.data = v
25
+ # end
26
+ # end
27
+ #
28
+ # ps = PascalString.new(:initial_value => "hello")
29
+ # ps.to_s #=> "\005hello"
30
+ # ps.read("\003abcde")
31
+ # ps.value #=> "abc"
32
+ #
33
+ # # Unsigned 24 bit big endian integer
34
+ # class Uint24be < BinData::SingleValue
35
+ # uint8 :byte1
36
+ # uint8 :byte2
37
+ # uint8 :byte3
38
+ #
39
+ # def get
40
+ # (self.byte1 << 16) | (self.byte2 << 8) | self.byte3
41
+ # end
42
+ #
43
+ # def set(v)
44
+ # v = 0 if v < 0
45
+ # v = 0xffffff if v > 0xffffff
46
+ #
47
+ # self.byte1 = (v >> 16) & 0xff
48
+ # self.byte2 = (v >> 8) & 0xff
49
+ # self.byte3 = v & 0xff
50
+ # end
51
+ # end
52
+ #
53
+ # u24 = Uint24be.new
54
+ # u24.read("\x12\x34\x56")
55
+ # "0x%x" % u24.value #=> 0x123456
56
+ #
57
+ # == Parameters
58
+ #
59
+ # SingleValue objects accept all the parameters that BinData::Single do.
60
+ #
61
+ class SingleValue < Single
62
+
63
+ class << self
64
+
65
+ # Register the names of all subclasses of this class.
66
+ def inherited(subclass) #:nodoc:
67
+ register(subclass.name, subclass)
68
+ end
69
+
70
+ # Returns or sets the endianess of numerics used in this stucture.
71
+ # Endianess is applied to the fields of this structure.
72
+ # Valid values are :little and :big.
73
+ def endian(endian = nil)
74
+ @endian ||= nil
75
+ if [:little, :big].include?(endian)
76
+ @endian = endian
77
+ elsif endian != nil
78
+ raise ArgumentError, "unknown value for endian '#{endian}'"
79
+ end
80
+ @endian
81
+ end
82
+
83
+ # Returns all stored fields. Should only be called by
84
+ # #sanitize_parameters
85
+ def fields
86
+ @fields || []
87
+ end
88
+
89
+ # Used to define fields for the internal structure.
90
+ def method_missing(symbol, *args)
91
+ name, params = args
92
+
93
+ type = symbol
94
+ name = (name.nil? or name == "") ? nil : name.to_s
95
+ params ||= {}
96
+
97
+ # note that fields are stored in an instance variable not a class var
98
+ @fields ||= []
99
+
100
+ # check that type is known
101
+ if lookup(type, endian).nil?
102
+ raise TypeError, "unknown type '#{type}' for #{self}", caller
103
+ end
104
+
105
+ # check that name is okay
106
+ if name != nil
107
+ # check for duplicate names
108
+ @fields.each do |t, n, p|
109
+ if n == name
110
+ raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
111
+ end
112
+ end
113
+
114
+ # check that name doesn't shadow an existing method
115
+ if self.instance_methods.include?(name)
116
+ raise NameError.new("", name),
117
+ "field '#{name}' shadows an existing method", caller
118
+ end
119
+ end
120
+
121
+ # remember this field. These fields will be recalled upon creating
122
+ # an instance of this class
123
+ @fields.push([type, name, params])
124
+ end
125
+
126
+ # Returns a sanitized +params+ that is of the form expected
127
+ # by #initialize.
128
+ def sanitize_parameters(params, endian = nil)
129
+ params = params.dup
130
+
131
+ # possibly override endian
132
+ endian = self.endian || endian
133
+
134
+ hash = {}
135
+ hash[:fields] = self.fields
136
+
137
+ unless endian.nil?
138
+ hash[:endian] = endian
139
+ end
140
+
141
+ params[:struct_params] = hash
142
+
143
+ super(params, endian)
144
+ end
145
+ end
146
+
147
+ # These are the parameters used by this class.
148
+ mandatory_parameter :struct_params
149
+
150
+ def initialize(params = {}, env = nil)
151
+ super(params, env)
152
+
153
+ @struct = BinData::Struct.new(param(:struct_params), create_env)
154
+ end
155
+
156
+ # Forward method calls to the internal struct.
157
+ def method_missing(symbol, *args, &block)
158
+ if @struct.respond_to?(symbol)
159
+ @struct.__send__(symbol, *args, &block)
160
+ else
161
+ super
162
+ end
163
+ end
164
+
165
+ #---------------
166
+ private
167
+
168
+ # Retrieve a sensible default from the internal struct.
169
+ def sensible_default
170
+ get
171
+ end
172
+
173
+ # Read data into the fields of the internal struct then return the value.
174
+ def read_val(io)
175
+ @struct.read(io)
176
+ get
177
+ end
178
+
179
+ # Sets +val+ into the fields of the internal struct then returns the
180
+ # string representation.
181
+ def val_to_str(val)
182
+ set(val)
183
+ @struct.to_s
184
+ end
185
+
186
+ ###########################################################################
187
+ # To be implemented by subclasses
188
+
189
+ # Extracts the value for this data object from the fields of the
190
+ # internal struct.
191
+ def get
192
+ raise NotImplementedError
193
+ end
194
+
195
+ # Sets the fields of the internal struct to represent +v+.
196
+ def set(v)
197
+ raise NotImplementedError
198
+ end
199
+
200
+ # To be implemented by subclasses
201
+ ###########################################################################
202
+ end
203
+ end
@@ -4,6 +4,32 @@ module BinData
4
4
  # A String is a sequence of bytes. This is the same as strings in Ruby.
5
5
  # The issue of character encoding is ignored by this class.
6
6
  #
7
+ # require 'bindata'
8
+ #
9
+ # data = "abcdefghij"
10
+ #
11
+ # obj = BinData::String.new(:read_length => 5)
12
+ # obj.read(data)
13
+ # obj.value #=> "abcde"
14
+ #
15
+ # obj = BinData::String.new(:length => 6)
16
+ # obj.read(data)
17
+ # obj.value #=> "abcdef"
18
+ # obj.value = "abcdefghij"
19
+ # obj.value #=> "abcdef"
20
+ # obj.value = "abcd"
21
+ # obj.value #=> "abcd\000\000"
22
+ #
23
+ # obj = BinData::String.new(:length => 6, :trim_value => true)
24
+ # obj.value = "abcd"
25
+ # obj.value #=> "abcd"
26
+ # obj.to_s #=> "abcd\000\000"
27
+ #
28
+ # obj = BinData::String.new(:length => 6, :pad_char => 'A')
29
+ # obj.value = "abcd"
30
+ # obj.value #=> "abcdAA"
31
+ # obj.to_s #=> "abcdAA"
32
+ #
7
33
  # == Parameters
8
34
  #
9
35
  # String objects accept all the params that BinData::Single
@@ -20,20 +46,41 @@ module BinData
20
46
  # from the end of the string. The value will
21
47
  # not be trimmed when writing.
22
48
  class String < Single
49
+
50
+ # Register this class
51
+ register(self.name, self)
52
+
23
53
  # These are the parameters used by this class.
24
- mandatory_parameters :pad_char
25
54
  optional_parameters :read_length, :length, :trim_value
55
+ default_parameters :pad_char => "\0"
56
+ mutually_exclusive_parameters :read_length, :length
57
+ mutually_exclusive_parameters :length, :value
58
+
59
+ class << self
60
+
61
+ # Returns a sanitized +params+ that is of the form expected
62
+ # by #initialize.
63
+ def sanitize_parameters(params, *args)
64
+ params = params.dup
26
65
 
27
- def initialize(params = {}, env = nil)
28
- super(cleaned_params(params), env)
66
+ # warn about deprecated param - remove before releasing 1.0
67
+ if params[:initial_length]
68
+ warn ":initial_length is deprecated. Replacing with :read_length"
69
+ params[:read_length] = params.delete(:initial_length)
70
+ end
29
71
 
30
- # the only valid param combinations of length and value are:
31
- # :read_length and :value
32
- # :read_length and :initial_value
33
- # :length and :initial_value
34
- ensure_mutual_exclusion(:initial_value, :value)
35
- ensure_mutual_exclusion(:read_length, :length)
36
- ensure_mutual_exclusion(:length, :value)
72
+ # set :pad_char to be a single length character string
73
+ if params.has_key?(:pad_char)
74
+ ch = params[:pad_char]
75
+ ch = ch.respond_to?(:chr) ? ch.chr : ch.to_s
76
+ if ch.length > 1
77
+ raise ArgumentError, ":pad_char must not contain more than 1 char"
78
+ end
79
+ params[:pad_char] = ch
80
+ end
81
+
82
+ super(params, *args)
83
+ end
37
84
  end
38
85
 
39
86
  # Overrides value to return the value padded to the desired length or
@@ -67,27 +114,5 @@ module BinData
67
114
  def sensible_default
68
115
  ""
69
116
  end
70
-
71
- # Returns a hash of cleaned +params+. Cleaning means that param
72
- # values are converted to a desired format.
73
- def cleaned_params(params)
74
- new_params = params.dup
75
-
76
- # warn about deprecated param - remove before releasing 1.0
77
- if params[:initial_length]
78
- warn ":initial_length is deprecated. Replacing with :read_length"
79
- new_params[:read_length] = params[:initial_length]
80
- end
81
-
82
- # set :pad_char to be a single length character string
83
- ch = new_params[:pad_char] || 0
84
- ch = ch.respond_to?(:chr) ? ch.chr : ch.to_s
85
- if ch.length > 1
86
- raise ArgumentError, ":pad_char must not contain more than 1 char"
87
- end
88
- new_params[:pad_char] = ch
89
-
90
- new_params
91
- end
92
117
  end
93
118
  end