require "bindata/base_primitive"
module BinData
# A String is a sequence of bytes. This is the same as strings in Ruby 1.8.
# The issue of character encoding is ignored by this class.
#
# require 'bindata'
#
# data = "abcdefghij"
#
# obj = BinData::String.new(:read_length => 5)
# obj.read(data)
# obj #=> "abcde"
#
# obj = BinData::String.new(:length => 6)
# obj.read(data)
# obj #=> "abcdef"
# obj.assign("abcdefghij")
# obj #=> "abcdef"
# obj.assign("abcd")
# obj #=> "abcd\000\000"
#
# obj = BinData::String.new(:length => 6, :trim_padding => true)
# obj.assign("abcd")
# obj #=> "abcd"
# obj.to_binary_s #=> "abcd\000\000"
#
# obj = BinData::String.new(:length => 6, :pad_byte => 'A')
# obj.assign("abcd")
# obj #=> "abcdAA"
# obj.to_binary_s #=> "abcdAA"
#
# == Parameters
#
# String objects accept all the params that BinData::BasePrimitive
# does, as well as the following:
#
# :read_length:: The length in bytes to use when reading a value.
# :length:: The fixed length of the string. If a shorter
# string is set, it will be padded to this length.
# :pad_byte:: The byte to use when padding a string to a
# set length. Valid values are Integers and
# Strings of length 1. "\0" is the default.
# :pad_front:: Signifies that the padding occurs at the front
# of the string rather than the end. Default
# is false.
# :trim_padding:: Boolean, default false. If set, #value will
# return the value with all pad_bytes trimmed
# from the end of the string. The value will
# not be trimmed when writing.
class String < BinData::BasePrimitive
arg_processor :string
optional_parameters :read_length, :length, :trim_padding, :pad_front, :pad_left
default_parameters :pad_byte => "\0"
mutually_exclusive_parameters :read_length, :length
mutually_exclusive_parameters :length, :value
def initialize_shared_instance
if (has_parameter?(:value) || has_parameter?(:asserted_value)) &&
! has_parameter?(:read_length)
extend WarnNoReadLengthPlugin
end
super
end
def assign(val)
super(binary_string(val))
end
def snapshot
# override to trim padding
snap = super
snap = clamp_to_length(snap)
if get_parameter(:trim_padding)
trim_padding(snap)
else
snap
end
end
#---------------
private
def clamp_to_length(str)
str = binary_string(str)
len = eval_parameter(:length) || str.length
if str.length == len
str
elsif str.length > len
str.slice(0, len)
else
padding = (eval_parameter(:pad_byte) * (len - str.length))
if get_parameter(:pad_front)
padding + str
else
str + padding
end
end
end
def trim_padding(str)
if get_parameter(:pad_front)
str.sub(/\A#{eval_parameter(:pad_byte)}*/, "")
else
str.sub(/#{eval_parameter(:pad_byte)}*\z/, "")
end
end
def value_to_binary_string(val)
clamp_to_length(val)
end
def read_and_return_value(io)
len = eval_parameter(:read_length) || eval_parameter(:length) || 0
io.readbytes(len)
end
def sensible_default
""
end
end
class StringArgProcessor < BaseArgProcessor
def sanitize_parameters!(obj_class, params)
params.warn_replacement_parameter(:initial_length, :read_length)
params.must_be_integer(:read_length, :length)
if params.has_parameter?(:pad_left)
params[:pad_front] = params.delete(:pad_left)
end
if params.has_parameter?(:pad_byte)
byte = params[:pad_byte]
params[:pad_byte] = sanitized_pad_byte(byte)
end
end
#-------------
private
def sanitized_pad_byte(byte)
pad_byte = byte.is_a?(Integer) ? byte.chr : byte.to_s
if pad_byte.bytesize > 1
raise ArgumentError, ":pad_byte must not contain more than 1 byte"
end
pad_byte
end
end
# Warns when reading if :value && no :read_length
module WarnNoReadLengthPlugin
def read_and_return_value(io)
warn "#{debug_name} does not have a :read_length parameter - returning empty string"
""
end
end
end