require "bindata/base_primitive"
module BinData
# Skip will skip over bytes from the input stream. If the stream is not
# seekable, then the bytes are consumed and discarded.
#
# When writing, skip will write the appropriate number of zero bytes.
#
# require 'bindata'
#
# class A < BinData::Record
# skip length: 5
# string :a, read_length: 5
# end
#
# obj = A.read("abcdefghij")
# obj.a #=> "fghij"
#
#
# class B < BinData::Record
# skip until_valid: [:string, {read_length: 2, assert: "ef"} ]
# string :b, read_length: 5
# end
#
# obj = B.read("abcdefghij")
# obj.b #=> "efghi"
#
#
# == Parameters
#
# Skip objects accept all the params that BinData::BasePrimitive
# does, as well as the following:
#
# :length:: The number of bytes to skip.
# :to_abs_offset:: Skips to the given absolute offset.
# :until_valid:: Skips untils a given byte pattern is matched.
# This parameter contains a type that will raise
# a BinData::ValidityError unless an acceptable byte
# sequence is found. The type is represented by a
# Symbol, or if the type is to have params #
# passed to it, then it should be provided as #
# [type_symbol, hash_params].
#
class Skip < BinData::BasePrimitive
arg_processor :skip
optional_parameters :length, :to_abs_offset, :until_valid
mutually_exclusive_parameters :length, :to_abs_offset, :until_valid
def initialize_shared_instance
extend SkipLengthPlugin if has_parameter?(:length)
extend SkipToAbsOffsetPlugin if has_parameter?(:to_abs_offset)
extend SkipUntilValidPlugin if has_parameter?(:until_valid)
super
end
#---------------
private
def value_to_binary_string(val)
len = skip_length
if len < 0
raise ValidityError, "#{debug_name} attempted to seek backwards by #{len.abs} bytes"
end
"\000" * skip_length
end
def read_and_return_value(io)
len = skip_length
if len < 0
raise ValidityError, "#{debug_name} attempted to seek backwards by #{len.abs} bytes"
end
io.seekbytes(len)
""
end
def sensible_default
""
end
end
class SkipArgProcessor < BaseArgProcessor
def sanitize_parameters!(obj_class, params)
unless params.has_parameter?(:length) ||
params.has_parameter?(:to_abs_offset) ||
params.has_parameter?(:until_valid)
raise ArgumentError, "#{obj_class} requires either :length, :to_abs_offset or :until_valid"
end
params.must_be_integer(:to_abs_offset, :length)
if params.needs_sanitizing?(:until_valid)
el_type, el_params = params[:until_valid]
params[:until_valid] = params.create_sanitized_object_prototype(el_type, el_params)
end
end
end
# Logic for the :length parameter
module SkipLengthPlugin
def skip_length
eval_parameter(:length)
end
end
# Logic for the :to_abs_offset parameter
module SkipToAbsOffsetPlugin
def skip_length
eval_parameter(:to_abs_offset) - abs_offset
end
end
# Logic for the :until_valid parameter
module SkipUntilValidPlugin
def skip_length
# no skipping when writing
0
end
def read_and_return_value(io)
prototype = get_parameter(:until_valid)
validator = prototype.instantiate(nil, self)
valid = false
until valid
begin
io.with_readahead do
validator.read(io)
valid = true
end
rescue ValidityError
io.readbytes(1)
end
end
end
end
end