jbangert-bindata 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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,105 @@
1
+ module BinData
2
+ # A LazyEvaluator is bound to a data object. The evaluator will evaluate
3
+ # lambdas in the context of this data object. These lambdas
4
+ # are those that are passed to data objects as parameters, e.g.:
5
+ #
6
+ # BinData::String.new(:value => lambda { %w{a test message}.join(" ") })
7
+ #
8
+ # As a shortcut, :foo is the equivalent of lambda { foo }.
9
+ #
10
+ # When evaluating lambdas, unknown methods are resolved in the context of the
11
+ # parent of the bound data object. Resolution is attempted firstly as keys
12
+ # in #parameters, and secondly as methods in this parent. This
13
+ # resolution propagates up the chain of parent data objects.
14
+ #
15
+ # An evaluation will recurse until it returns a result that is not
16
+ # a lambda or a symbol.
17
+ #
18
+ # This resolution process makes the lambda easier to read as we just write
19
+ # <tt>field</tt> instead of <tt>obj.field</tt>.
20
+ class LazyEvaluator
21
+
22
+ # Creates a new evaluator. All lazy evaluation is performed in the
23
+ # context of +obj+.
24
+ def initialize(obj)
25
+ @obj = obj
26
+ end
27
+
28
+ def lazy_eval(val, overrides = nil)
29
+ @overrides = overrides if overrides
30
+ if val.is_a? Symbol
31
+ __send__(val)
32
+ elsif val.respond_to? :arity
33
+ instance_exec(&val)
34
+ else
35
+ val
36
+ end
37
+ end
38
+
39
+ # Returns a LazyEvaluator for the parent of this data object.
40
+ def parent
41
+ if @obj.parent
42
+ @obj.parent.lazy_evaluator
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ # Returns the index of this data object inside it's nearest container
49
+ # array.
50
+ def index
51
+ return @overrides[:index] if @overrides and @overrides.has_key?(:index)
52
+
53
+ child = @obj
54
+ parent = @obj.parent
55
+ while parent
56
+ if parent.respond_to?(:find_index_of)
57
+ return parent.find_index_of(child)
58
+ end
59
+ child = parent
60
+ parent = parent.parent
61
+ end
62
+ raise NoMethodError, "no index found"
63
+ end
64
+
65
+ def method_missing(symbol, *args)
66
+ return @overrides[symbol] if defined? @overrides and @overrides.has_key?(symbol)
67
+
68
+ if @obj.parent
69
+ eval_symbol_in_parent_context(symbol, args)
70
+ else
71
+ super
72
+ end
73
+ end
74
+
75
+ #---------------
76
+ private
77
+
78
+ def eval_symbol_in_parent_context(symbol, args)
79
+ result = resolve_symbol_in_parent_context(symbol, args)
80
+ recursively_eval(result, args)
81
+ end
82
+
83
+ def resolve_symbol_in_parent_context(symbol, args)
84
+ obj_parent = @obj.parent
85
+
86
+ if obj_parent.has_parameter?(symbol)
87
+ obj_parent.get_parameter(symbol)
88
+ elsif obj_parent.safe_respond_to?(symbol)
89
+ obj_parent.__send__(symbol, *args)
90
+ else
91
+ symbol
92
+ end
93
+ end
94
+
95
+ def recursively_eval(val, args)
96
+ if val.is_a?(Symbol)
97
+ parent.__send__(val, *args)
98
+ elsif val.respond_to?(:arity)
99
+ parent.instance_exec(&val)
100
+ else
101
+ val
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,91 @@
1
+ module BinData
2
+ # == Parameters
3
+ #
4
+ # Parameters may be provided at initialisation to control the behaviour of
5
+ # an object. These parameters are:
6
+ #
7
+ # [<tt>:check_offset</tt>] Raise an error if the current IO offset doesn't
8
+ # meet this criteria. A boolean return indicates
9
+ # success or failure. Any other return is compared
10
+ # to the current offset. The variable +offset+
11
+ # is made available to any lambda assigned to
12
+ # this parameter. This parameter is only checked
13
+ # before reading.
14
+ # [<tt>:adjust_offset</tt>] Ensures that the current IO offset is at this
15
+ # position before reading. This is like
16
+ # <tt>:check_offset</tt>, except that it will
17
+ # adjust the IO offset instead of raising an error.
18
+ module CheckOrAdjustOffsetMixin
19
+
20
+ def self.included(base) #:nodoc:
21
+ base.optional_parameters :check_offset, :adjust_offset
22
+ base.mutually_exclusive_parameters :check_offset, :adjust_offset
23
+ end
24
+
25
+ # Ideally these two methods should be protected,
26
+ # but Ruby 1.9.2 requires them to be public.
27
+ # see http://redmine.ruby-lang.org/issues/show/2375
28
+
29
+ def do_read_with_check_offset(io) #:nodoc:
30
+ check_offset(io)
31
+ do_read_without_check_offset(io)
32
+ end
33
+
34
+ def do_read_with_adjust_offset(io) #:nodoc:
35
+ adjust_offset(io)
36
+ do_read_without_adjust_offset(io)
37
+ end
38
+
39
+ #---------------
40
+ private
41
+
42
+ # To be called from BinData::Base#initialize.
43
+ #
44
+ # Monkey patches #do_read to check or adjust the stream offset
45
+ # as appropriate.
46
+ def add_methods_for_check_or_adjust_offset
47
+ if has_parameter?(:check_offset)
48
+ class << self
49
+ alias_method :do_read_without_check_offset, :do_read
50
+ alias_method :do_read, :do_read_with_check_offset
51
+ end
52
+ end
53
+ if has_parameter?(:adjust_offset)
54
+ class << self
55
+ alias_method :do_read_without_adjust_offset, :do_read
56
+ alias_method :do_read, :do_read_with_adjust_offset
57
+ end
58
+ end
59
+ end
60
+
61
+ def check_offset(io)
62
+ actual_offset = io.offset
63
+ expected = eval_parameter(:check_offset, :offset => actual_offset)
64
+
65
+ if not expected
66
+ raise ValidityError, "offset not as expected for #{debug_name}"
67
+ elsif actual_offset != expected and expected != true
68
+ raise ValidityError,
69
+ "offset is '#{actual_offset}' but " +
70
+ "expected '#{expected}' for #{debug_name}"
71
+ end
72
+ end
73
+
74
+ def adjust_offset(io)
75
+ actual_offset = io.offset
76
+ expected = eval_parameter(:adjust_offset)
77
+ if actual_offset != expected
78
+ begin
79
+ seek = expected - actual_offset
80
+ io.seekbytes(seek)
81
+ warn "adjusting stream position by #{seek} bytes" if $VERBOSE
82
+ rescue
83
+ raise ValidityError,
84
+ "offset is '#{actual_offset}' but couldn't seek to " +
85
+ "expected '#{expected}' for #{debug_name}"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
@@ -0,0 +1,135 @@
1
+ require 'bindata/lazy'
2
+
3
+ module BinData
4
+ module AcceptedParametersMixin
5
+ def self.included(base) #:nodoc:
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ # Class methods to mix in to BinData::Base
10
+ module ClassMethods
11
+ # Mandatory parameters must be present when instantiating a data object.
12
+ def mandatory_parameters(*args)
13
+ accepted_parameters.mandatory(*args)
14
+ end
15
+
16
+ # Optional parameters may be present when instantiating a data object.
17
+ def optional_parameters(*args)
18
+ accepted_parameters.optional(*args)
19
+ end
20
+
21
+ # Default parameters can be overridden when instantiating a data object.
22
+ def default_parameters(*args)
23
+ accepted_parameters.default(*args)
24
+ end
25
+
26
+ # Mutually exclusive parameters may not all be present when
27
+ # instantiating a data object.
28
+ def mutually_exclusive_parameters(*args)
29
+ accepted_parameters.mutually_exclusive(*args)
30
+ end
31
+
32
+ alias_method :mandatory_parameter, :mandatory_parameters
33
+ alias_method :optional_parameter, :optional_parameters
34
+ alias_method :default_parameter, :default_parameters
35
+
36
+ def accepted_parameters #:nodoc:
37
+ unless defined? @accepted_parameters
38
+ ancestor_params = superclass.respond_to?(:accepted_parameters) ?
39
+ superclass.accepted_parameters : nil
40
+ @accepted_parameters = AcceptedParameters.new(ancestor_params)
41
+ end
42
+ @accepted_parameters
43
+ end
44
+ end
45
+
46
+ # BinData objects accept parameters when initializing. AcceptedParameters
47
+ # allow a BinData class to declaratively identify accepted parameters as
48
+ # mandatory, optional, default or mutually exclusive.
49
+ class AcceptedParameters
50
+
51
+ def initialize(ancestor_parameters = nil)
52
+ if ancestor_parameters
53
+ @mandatory = ancestor_parameters.mandatory.dup
54
+ @optional = ancestor_parameters.optional.dup
55
+ @default = ancestor_parameters.default.dup
56
+ @mutually_exclusive = ancestor_parameters.mutually_exclusive.dup
57
+ else
58
+ @mandatory = []
59
+ @optional = []
60
+ @default = Hash.new
61
+ @mutually_exclusive = []
62
+ end
63
+ end
64
+
65
+ def mandatory(*args)
66
+ if not args.empty?
67
+ @mandatory.concat(to_syms(args))
68
+ @mandatory.uniq!
69
+ end
70
+ @mandatory
71
+ end
72
+
73
+ def optional(*args)
74
+ if not args.empty?
75
+ @optional.concat(to_syms(args))
76
+ @optional.uniq!
77
+ end
78
+ @optional
79
+ end
80
+
81
+ def default(args = nil)
82
+ if args
83
+ to_syms(args.keys) # call for side effect of validating names
84
+ args.each_pair do |param, value|
85
+ @default[param.to_sym] = value
86
+ end
87
+ end
88
+ @default
89
+ end
90
+
91
+ def mutually_exclusive(*args)
92
+ arg1, arg2 = args
93
+ if arg1 != nil && arg2 != nil
94
+ @mutually_exclusive.push([arg1.to_sym, arg2.to_sym])
95
+ @mutually_exclusive.uniq!
96
+ end
97
+ @mutually_exclusive
98
+ end
99
+
100
+ def all
101
+ (@mandatory + @optional + @default.keys).uniq
102
+ end
103
+
104
+ #---------------
105
+ private
106
+
107
+ def to_syms(args)
108
+ syms = args.collect { |el| el.to_sym }
109
+ ensure_valid_names(syms)
110
+ syms
111
+ end
112
+
113
+ def ensure_valid_names(names)
114
+ invalid_names = self.class.invalid_parameter_names
115
+ names.each do |name|
116
+ if invalid_names.include?(name)
117
+ raise NameError.new("Rename parameter '#{name}' " +
118
+ "as it shadows an existing method.", name)
119
+ end
120
+ end
121
+ end
122
+
123
+ def self.invalid_parameter_names
124
+ unless defined? @invalid_names
125
+ all_names = LazyEvaluator.instance_methods(true) + Kernel.methods
126
+ allowed_names = ["name", "type", :name, :type] # ruby 1.8 vs 1.9
127
+ invalid_names = (all_names - allowed_names).uniq
128
+ @invalid_names = Hash[*invalid_names.collect { |key| [key.to_sym, true] }.flatten]
129
+ end
130
+ @invalid_names
131
+ end
132
+ end
133
+ end
134
+ end
135
+
@@ -0,0 +1,135 @@
1
+ require 'bindata/base_primitive'
2
+ require 'bindata/dsl'
3
+ require 'bindata/struct'
4
+
5
+ module BinData
6
+ # A Primitive is a declarative way to define a new BinData data type.
7
+ # The data type must contain a primitive value only, i.e numbers or strings.
8
+ # For new data types that contain multiple values see BinData::Record.
9
+ #
10
+ # To define a new data type, set fields as if for Record and add a
11
+ # #get and #set method to extract / convert the data between the fields
12
+ # and the #value of the object.
13
+ #
14
+ # require 'bindata'
15
+ #
16
+ # class PascalString < BinData::Primitive
17
+ # uint8 :len, :value => lambda { data.length }
18
+ # string :data, :read_length => :len
19
+ #
20
+ # def get
21
+ # self.data
22
+ # end
23
+ #
24
+ # def set(v)
25
+ # self.data = v
26
+ # end
27
+ # end
28
+ #
29
+ # ps = PascalString.new(:initial_value => "hello")
30
+ # ps.to_binary_s #=> "\005hello"
31
+ # ps.read("\003abcde")
32
+ # ps #=> "abc"
33
+ #
34
+ # # Unsigned 24 bit big endian integer
35
+ # class Uint24be < BinData::Primitive
36
+ # uint8 :byte1
37
+ # uint8 :byte2
38
+ # uint8 :byte3
39
+ #
40
+ # def get
41
+ # (self.byte1 << 16) | (self.byte2 << 8) | self.byte3
42
+ # end
43
+ #
44
+ # def set(v)
45
+ # v = 0 if v < 0
46
+ # v = 0xffffff if v > 0xffffff
47
+ #
48
+ # self.byte1 = (v >> 16) & 0xff
49
+ # self.byte2 = (v >> 8) & 0xff
50
+ # self.byte3 = v & 0xff
51
+ # end
52
+ # end
53
+ #
54
+ # u24 = Uint24be.new
55
+ # u24.read("\x12\x34\x56")
56
+ # "0x%x" % u24 #=> 0x123456
57
+ #
58
+ # == Parameters
59
+ #
60
+ # Primitive objects accept all the parameters that BinData::BasePrimitive do.
61
+ #
62
+ class Primitive < BasePrimitive
63
+ include DSLMixin
64
+
65
+ unregister_self
66
+ dsl_parser :primitive
67
+
68
+ class << self
69
+ def sanitize_parameters!(params) #:nodoc:
70
+ params[:struct_params] = params.create_sanitized_params(dsl_params, BinData::Struct)
71
+ end
72
+ end
73
+
74
+ mandatory_parameter :struct_params
75
+
76
+ def initialize_instance
77
+ @struct = BinData::Struct.new(get_parameter(:struct_params), self)
78
+ end
79
+
80
+ def respond_to?(symbol, include_private = false) #:nodoc:
81
+ @struct.respond_to?(symbol, include_private) || super
82
+ end
83
+
84
+ def method_missing(symbol, *args, &block) #:nodoc:
85
+ if @struct.respond_to?(symbol)
86
+ @struct.__send__(symbol, *args, &block)
87
+ else
88
+ super
89
+ end
90
+ end
91
+
92
+ def debug_name_of(child) #:nodoc:
93
+ debug_name + "-internal-"
94
+ end
95
+
96
+ def do_write(io)
97
+ set(_value)
98
+ @struct.do_write(io)
99
+ end
100
+
101
+ def do_num_bytes
102
+ set(_value)
103
+ @struct.do_num_bytes
104
+ end
105
+
106
+ #---------------
107
+ private
108
+
109
+ def sensible_default
110
+ get
111
+ end
112
+
113
+ def read_and_return_value(io)
114
+ @struct.do_read(io)
115
+ get
116
+ end
117
+
118
+ ###########################################################################
119
+ # To be implemented by subclasses
120
+
121
+ # Extracts the value for this data object from the fields of the
122
+ # internal struct.
123
+ def get
124
+ raise NotImplementedError
125
+ end
126
+
127
+ # Sets the fields of the internal struct to represent +v+.
128
+ def set(v)
129
+ raise NotImplementedError
130
+ end
131
+
132
+ # To be implemented by subclasses
133
+ ###########################################################################
134
+ end
135
+ end