jbangert-bindata 1.5.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.
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