gxg-framework/lib/gxg/gxg_elements.rb
2021-11-16 08:41:53 -08:00

2263 lines
63 KiB
Ruby

# ---------------------------------------------------------------------------------------------------------------------
# Various data element classes:
module GxG
#
class Version
def initialize(params={})
unless params.is_a?(::Hash)
params = {:interpret => (params.to_s)}
end
@encoded_value = encode(params)
end
def value()
decode({:value => @encoded_value})
end
def encoded_value()
@encoded_value
end
def <=>(other)
return @encoded_value <=> other.encoded_value()
end
def to_d()
::BigDecimal.new(@encoded_value)
end
def to_s()
value.inspect
end
def inspect()
value.inspect
end
def increment(params={:revision => 1})
current = decode({:value => @encoded_value})
if current[:phase].is_a?(Hash)
numeric_phase = current[:phase][:candidate].to_i + 2
else
case current[:phase]
when :alpha
numeric_phase = 0
when :beta
numeric_phase = 1
when :release
numeric_phase = 99
end
end
if params[:phase].is_a?(Numeric)
phase_increment = params[:phase]
else
phase_increment = 0
end
new_value = {}
#
if params[:revision].is_a?(Numeric)
if (params[:revision].to_i + current[:revision]) > 9999
params[:patch_level] = params[:patch_level].to_i + 1
new_value[:revision] = 0
else
new_value[:revision] = (params[:revision].to_i + current[:revision])
end
else
new_value[:revision] = current[:revision]
end
#
if params[:patch_level].is_a?(Numeric)
if (params[:patch_level].to_i + current[:patch_level]) > 9999
phase_increment = phase_increment + 1
new_value[:patch_level] = 0
else
new_value[:patch_level] = (params[:patch_level].to_i + current[:patch_level])
end
else
new_value[:patch_level] = current[:patch_level]
end
#
if phase_increment > 0
if (phase_increment + numeric_phase) > 99
params[:tiny] = params[:tiny].to_i + 1
new_value[:phase] = 99
else
new_value[:phase] = (phase_increment + numeric_phase)
end
else
new_value[:phase] = numeric_phase
end
#
if params[:tiny].is_a?(Numeric)
if (params[:tiny].to_i + current[:tiny]) > 999
params[:minor] = params[:minor].to_i + 1
new_value[:tiny] = 0
else
new_value[:tiny] = (params[:tiny].to_i + current[:tiny])
end
else
new_value[:tiny] = current[:tiny]
end
#
if params[:minor].is_a?(Numeric)
if (params[:minor].to_i + current[:minor]) > 999
params[:major] = params[:major].to_i + 1
new_value[:minor] = 0
else
new_value[:minor] = (params[:minor].to_i + current[:minor])
end
else
new_value[:minor] = current[:minor]
end
#
if params[:major].is_a?(Numeric)
new_value[:major] = current[:major] + params[:major].to_i
else
new_value[:major] = current[:major]
end
# convert phase numeric value to Symbol / Hash
if (2..98).include?(new_value[:phase])
new_value[:phase] = {:candidate => (new_value[:phase] - 2)}
else
case new_value[:phase]
when 0
new_value[:phase] = :alpha
when 1
new_value[:phase] = :beta
when 99
new_value[:phase] = :release
end
end
#
@encoded_value = encode(new_value)
self
end
def decode(params={:value => 0.0})
# numeric value format: MmmmtttPPpppp.rrrrr
string_value = params[:value].to_s
major = string_value.split(".")[0][0..-13].to_i
minor = string_value.split(".")[0][-12..-10].to_i
teeny = string_value.split(".")[0][-9..-7].to_i
phase = string_value.split(".")[0][-6..-5].to_i
if (2..98).include?(phase)
phase = {:candidate => (phase - 2)}
else
case phase
when 0
phase = :alpha
when 1
phase = :beta
when 99
phase = :release
end
end
patch_level = string_value.split(".")[0][-4..-1].to_i
revision = string_value.split(".")[1].to_i
#
{:major => major, :minor => minor, :teeny => teeny, :phase => phase, :patch_level => patch_level, :revision => revision}
end
def interpret_version(params={})
result = {:phase => :release}
# params[:interpret]
sections = params[:interpret].split(".")
major_string = ""
minor_string = ""
teeny_string = ""
phase_string = ""
patch_level = ""
revision = ""
# *very* simple at this point
result[:major] = sections[0].to_i
result[:minor] = sections[1].to_i
result[:teeny] = sections[2].to_i
if sections.last.to_s.include?("alpha")
result[:phase] = :alpha
end
if sections.last.to_s.include?("beta")
result[:phase] = :beta
end
# TODO: Version.intereperet_version : add patch-level & revision detect (need a numeric string scrubber)
#
result
end
#
def encode(params={})
# numeric value format: MmmmtttPPpppp.rrrrr
#
if params[:interpret].is_a?(String)
params = params.merge(interpret_version(params))
end
#
major = 0
minor = 0
teeny = 0
phase = :release
patch_level = 0
revision = 0
#
if params[:major].is_a?(Numeric)
major = params[:major].to_i
end
if params[:minor].is_a?(Numeric)
minor = params[:minor].to_i
end
if params[:teeny].is_a?(Numeric)
teeny = params[:teeny].to_i
end
if params[:phase].is_a?(Hash)
# :phase => :alpha
# :phase => :beta
# :phase => {:candidate => 12} (0 to 96)
# :phase => :release
if params[:phase][:candidate].is_a?(Numeric)
phase = params[:phase]
phase[:candidate] = phase[:candidate].to_i
unless (0..96).include?(phase[:candidate])
raise ArgumentError, ":phase[:candidate] must be between 0 and 96, consider incrementing teeny version beyond that"
end
else
raise ArgumentError, ":phase[:candidate] must be an Numeric, you provided a #{params[:phase][:candidate].class}"
end
else
if params[:phase].is_a?(Symbol)
if [:alpha, :beta, :release].include?(params[:phase])
phase = params[:phase]
else
#
end
end
end
if params[:patch_level].is_a?(Numeric)
patch_level = params[:patch_level].to_i
end
if params[:revision].is_a?(Numeric)
revision = params[:revision].to_i
end
# numeric value format: MmmmtttPPpppp.rrrrr
#
string_value = "#{major}"
(3 - minor.to_s.size).times do
string_value << "0"
end
string_value << minor.to_s
#
(3 - teeny.to_s.size).times do
string_value << "0"
end
string_value << teeny.to_s
#
if phase.is_a?(Hash)
if phase[:candidate]
phase[:candidate] += 2
(2 - phase[:candidate].to_s.size).times do
string_value << "0"
end
string_value << "#{phase[:candidate]}"
end
else
case phase
when :alpha
string_value << "00"
when :beta
string_value << "01"
when :release
string_value << "99"
end
end
#
(4 - patch_level.to_s.size).times do
string_value << "0"
end
string_value << patch_level.to_s
string_value << "."
#
(4 - revision.to_s.size).times do
string_value << "0"
end
string_value << revision.to_s
#
string_value
end
private :decode, :encode, :interpret_version
end
#
class Enumerator < ::Enumerator
# this is a bit inefficient, but it gets the job done. It is essentially an each wrapper to do a cooperative pauses for non-blocking goodness. :)
# TODO: inspiration: what about just leveraging 'super' after a pause operation on all overrides. (thx)
#
def initialize(object=nil,method=:each,*args, &block)
# Without the call to super() it works, but will be uninitialized
# super(object,method,*args)
@collection = object.stock_to_enum(method,*args, &block)
# @collection = ::Enumerator.new(object,method,*args,&block)
self
end
#
def each(&block)
# reference = {:object => self, :method => __callee__, :fiber => Fiber.current, :thread => Thread.current}
if block.respond_to?(:call)
if ::GxG::EventManager::manager_running?
@collection.each do |*args|
block.call(*args)
pause
end
self
else
@collection.each(&block)
self
end
else
self
end
end
#
def next()
pause
@collection.next()
end
#
def next_values()
pause
@collection.next_values()
end
#
def peek()
pause
@collection.peek()
end
#
def peek_values()
pause
@collection.peek_values()
end
#
def rewind()
if @collection.respond_to?(:rewind)
@collection.rewind
end
self
end
#
def with_index(offset=0,&block)
enumerator = GxG::Enumerator.new(@collection.with_index(offset))
if block.respond_to?(:call)
enumerator.each do |entry,index|
block.call(entry,index)
end
else
enumerator
end
end
alias :each_with_index :with_index
#
def with_object(an_object=nil,&block)
enumerator = GxG::Enumerator.new(@collection.with_object(an_object))
if block.respond_to?(:call)
enumerator.each do |entry,the_object|
block.call(entry,the_object)
end
an_object
else
enumerator
end
end
#
end
#
class ReturnResponse
# this class allows for characterized muli-part return values for Procs and Methods
# this was created for a variety of reasons. Hope this isn't a dead end.
def initialize(response={})
unless response.is_a?(::Hash)
raise ArgumentError.new("Response must be passed as a Hash")
end
@details = response
end
def new_selector()
@details[:new_selector]
end
def result()
@details[:result]
end
def error()
@details[:error]
end
def warning()
@details[:warning]
end
def info()
@details[:info]
end
def debug()
@details[:debug]
end
def inspect()
@details.inspect
end
end
#
class Tuple
# Inspired by Python's Tuple class, in this case: allows any object to be a key for any other object
# Monkey-patch notes: http://stackoverflow.com/questions/4470108/when-monkey-patching-a-method-can-you-call-the-overridden-method-from-the-new-i
# (Array integration)
def initialize(key=nil,value=nil)
@thread_safety = ::Mutex.new
@key = key
@value = value
end
#
def key()
@thread_safety.synchronize { @key }
end
def key= (new_key=nil)
@thread_safety.synchronize { @key = new_key }
end
#
def value()
@thread_safety.synchronize { @value }
end
def value= (new_value=nil)
@thread_safety.synchronize { @value = new_value }
end
#
def <=>(other_value=nil)
if other_value.respone_to?(:<=>)
if other_value.is_a?(GxG::Tuple)
@thread_safety.synchronize { @value <=> other_value.value }
else
@thread_safety.synchronize { @value <=> other_value }
end
else
# taking liberties here, if other object does not support compare, then this obj is construed as greater than. (bias)
1
end
end
def to_s()
@thread_safety.synchronize { @value.to_s }
end
def inspect()
@thread_safety.synchronize { "#{@key.inspect} => #{@value.inspect}" }
end
#
end
#
class ByteArray
# make as close to array as possible with one exception: only works-with/accepts Integer between 0 and 255 as elements.
# Also: ByteArray is one-dimensional/flat --> cannot encapsulate other arrays, just Integers between 0 and 255.
# TODO: ByteArray : fill in missing methods compared to standard Array class.
# GxG::ByteArray::missing_methods(Array)
protected
#
def filter_data(the_data="")
#
error_message = ("ByteArray.new expects a String, a Integer or Float between 0 and 255, a nil ( counted as padvalue() ), a ByteArray, or an Array of any of those; you provided #{the_data.inspect}")
#
unless the_data.is_any?(::NilClass,::String,::Numeric, ::Array, ::GxG::ByteArray)
raise Exception.new(error_message)
end
if the_data.is_a?(::String)
the_data = the_data.slice_bytes((0..-1))
end
if the_data.is_a?(::Numeric)
the_data = the_data.to_i
if (0..255).include?(the_data)
the_data = the_data.chr
the_data.force_encoding(Encoding::ASCII_8BIT)
else
raise Exception.new(error_message)
end
end
if the_data.is_a?(::GxG::ByteArray)
the_data = the_data.data().clone
end
if the_data.is_a?(::Array)
temp_string = ""
temp_string.force_encoding(Encoding::ASCII_8BIT)
the_data.flatten.to_enum.each do |element|
temp_string << self.filter_data(element)
end
if temp_string.size > 0
the_data = temp_string
else
the_data = ""
the_data.force_encoding(Encoding::ASCII_8BIT)
end
end
unless the_data
the_data = @pad_value.to_i.chr
the_data.force_encoding(Encoding::ASCII_8BIT)
end
#
the_data
end
#
def filter_parameters(*args)
# puts "Got: #{args.inspect}"
the_range = nil
if args[0].is_any?(::Numeric, ::Integer, ::Float, ::BigDecimal)
args[0] = args[0].to_i
if args[0] < 0
args[0] = @data.bytesize + args[0]
end
the_range = ((args[0])..(args[0]))
if args[1].is_a?(Numeric)
args[1] = args[1].to_i
if args[1] < 1
raise ArgumentError.new("Second parameter needs to be a Numeric greater than 0, you provided #{args[1].inspect}")
else
the_range = ((args[0])..(args[0] + (args[1] - 1)))
end
end
else
if args[0].is_a?(Range)
if args[0].first < 0
args[1] = @data.bytesize + args[0].first
else
args[1] = args[0].first
end
if args[0].last < 0
args[2] = @data.bytesize + args[0].last
else
args[2] = args[0].last
end
if args[1] <= args[2]
the_range = ((args[1])..(args[2]))
else
the_range = ((args[2])..(args[1]))
end
else
unless the_range
raise ArgumentError.new("First parameter needs to be a Numeric or a Range, you provided #{args[0].inspect}")
end
end
end
the_range
end
#
public
#
def self.try_convert(*args)
begin
GxG::ByteArray.new(1,[(args)])
rescue Exception
nil
end
end
#
def self.[](*args)
GxG::ByteArray.new(*args)
end
#
def self.process(the_array=GxG::ByteArray.new,&block)
new_array = GxG::ByteArray.new
if block.respond_to?(:call)
if the_array.is_any?(::Array, ::Hash, ::GxG::ByteArray, ::Set, ::Struct)
new_array = the_array.process(&block)
else
raise ArgumentError, "you must pass an Array, a ByteArray, or a Hash"
end
end
new_array
end
#
def self.search(the_array=GxG::ByteArray.new,&block)
new_array = GxG::ByteArray.new
if block.respond_to?(:call)
if the_array.is_any?(::Array, ::Hash, ::GxG::ByteArray, ::Set, ::Struct)
new_array = the_array.search(&block)
else
raise ArgumentError, "you must pass an Array, a ByteArray, or a Hash"
end
end
new_array
end
#
def data()
@data
end
#
# instance methods:
def initialize(*args,&block)
# Integer(how_many), <Acceptable-data-type>, <Max-size>, <Pad-value>, <Outer-value>
# --OR--
# Integer(how_many), <Max-size>, <Pad-value>, <Outer-value> &block
@data = ""
@data.force_encoding(Encoding::ASCII_8BIT)
#
memory_limits = GxG::SYSTEM.memory_limits()
@max_size = nil
@pad_value = 0
@outer = nil
# *should* create a 'self' to call upon.
super()
#
if args.size > 0
if block.respond_to?(:call)
if args[0].is_a?(Numeric)
args[0] = args[0].to_i
if args[0] >= 0
if args[0] > memory_limits[:process].first
args[0] = memory_limits[:process].first
end
if args[1].is_a?(Numeric)
self.maxsize = args[1].to_i
end
if args[2].is_a?(Numeric)
self.padvalue = args[1].to_i
end
if args[3].is_a?(Numeric)
self.outervalue = args[1].to_i
end
#
(0..(args[0] - 1)).to_enum.each do |index|
raw_data_array = [(block.call(index))].flatten
raw_data_array.to_enum.each do |raw_data|
#
raw_data = (self.filter_data(raw_data) || "")
#
if raw_data.bytesize > 0
if (args[0] * raw_data.bytesize) > memory_limits[:process].first
# Attempts to project whether or not maximum memory limits will be exceeded.
# LATER: ByteArray.new: Might get false positives on variable return data from block (re-think this)
raise NoMemoryError.new("This operation will exceed available per-process memory limits")
end
@data << raw_data
end
end
#
end
#
if @max_size
if @data.bytesize > @max_size
@max_size = @data.bytesize
end
end
#
end
end
else
#
if args[0].is_a?(Numeric)
how_many = args[0].to_i
if args[1]
raw_data_array = [(args[1])].flatten
else
raw_data_array = [(@pad_value.to_i.chr)]
end
if args[2].is_a?(Numeric)
self.maxsize = args[2].to_i
end
if args[3].is_a?(Numeric)
self.padvalue = args[3].to_i
end
if args[4].is_a?(Numeric)
self.outervalue = args[4].to_i
end
else
how_many = 1
raw_data_array = [(args[0])].flatten
if args[1].is_a?(Numeric)
self.maxsize = args[1].to_i
end
if args[2].is_a?(Numeric)
self.padvalue = args[2].to_i
end
if args[3].is_a?(Numeric)
self.outervalue = args[3].to_i
end
end
#
new_data = ""
new_data.force_encoding(Encoding::ASCII_8BIT)
raw_data_array.to_enum.each do |raw_data|
new_data << (self.filter_data(raw_data) || "")
end
if new_data.bytesize > 0
if (how_many * new_data.bytesize) > memory_limits[:process].first
raise NoMemoryError.new("This operation will exceed available per-process memory limits")
end
how_many.times do
@data << new_data
end
end
#
if @max_size
if @data.bytesize > @max_size
@max_size = @data.bytesize
end
end
#
end
#
end
self
end
#
def mime_type()
@data.mime_type()
end
#
def process(&block)
result = self.clone
if block.respond_to?(:call)
result.each_with_index do |entry,index|
result[(index)] = block.call(entry, index, self)
end
end
result
end
#
def process!(&block)
if block.respond_to?(:call)
self.replace(self.process(&block).data())
end
self
end
#
def search(&block)
result = []
if block.respond_to?(:call)
self.each_with_index do |entry,index|
raw_result = block.call(entry,index,self)
if raw_result
result << raw_result
end
end
end
result
end
#
def pattern_range(the_pattern="", initial_offset=0)
result = nil
if the_pattern.is_a?(::ByteArray)
the_pattern = the_pattern.to_s
end
if the_pattern.is_a?(::String)
if initial_offset.is_a?(::Integer)
if @max_size
if initial_offset > @max_size
raise ArgumentError, "Offset exceeds the maximum size."
end
end
if the_pattern.encoding != ::Encoding::ASCII_8BIT
the_pattern = the_pattern.clone
the_pattern.force_encoding(::Encoding::ASCII_8BIT)
end
offset = @data.index(the_pattern, initial_offset)
if offset.is_a?(::Integer)
result = ((offset)..(offset + (the_pattern.size - 1)))
end
else
raise ArgumentError, "You MUST provide the offset as an Integer."
end
else
raise ArgumentError, "You MUST provide a String to find."
end
result
end
#
def pattern_ranges(the_pattern="")
result = []
offset = 0
if the_pattern.is_a?(::ByteArray)
the_pattern = the_pattern.to_s
end
last_found = self.pattern_range(the_pattern,offset)
while last_found != nil do
if last_found.is_a?(::Range)
result << last_found
if (last_found.last() + 1) <= @data.bytesize
offset = (last_found.last() + 1)
else
break
end
end
last_found = self.pattern_range(the_pattern,offset)
end
result
end
#
def padvalue()
@pad_value.clone
end
#
def padvalue=(*args)
if args.size > 0
if args[0].is_a?(Float)
args[0] = args[0].to_i
end
if args[0].is_a?(Integer)
if (0..255).include?(args[0])
@pad_value = args[0]
end
else
raise TypeError.new("Value type: expected Float or Integer. You provided #{args[0].class}")
end
end
@pad_value.clone
end
#
def match(regular_expression)
@data.match(regular_expression)
end
#
def maxsize()
@max_size.clone
end
#
def maxsize=(*args)
# Set @maxsize: Warning: setting @maxsize lower than the @data.bytesize will truncate the data
if args.size > 0
if args[0]
if args[0].is_a?(Numeric)
args[0] = args[0].to_i
end
#
if args[0].is_a?(Integer)
if args[0] > GxG::SYSTEM.memory_limits()[:process].first
raise NoMemoryError.new("Setting maxsize this high exceeds per-process memory limits")
end
if args[0] > @max_size.to_i
@max_size = args[0]
else
if args[0] < @max_size.to_i
# truncate data if @max_size lowered below @data.bytesize
@max_size = args[0]
if @data.bytesize > args[0]
@data.replace(@data[0..(args[0] - 1)])
end
end
end
else
raise ArgumentError.new("Expected NilClass or Numeric class. You provided #{args[0].class}")
end
else
@max_size = nil
end
else
@max_size = nil
end
@max_size.clone
end
#
def outervalue()
@outer.clone
end
#
def outervalue=(*args)
if args.size > 0
if args[0]
if args[0].is_a?(Float)
args[0] = args[0].to_i
end
if args[0].is_a?(Integer)
if (0..255).include?(args[0])
@outer = args[0]
end
else
raise TypeError.new("Expected a NilClass, a Float, or a Integer. You provided #{args[0].class}")
end
else
@outer = nil
end
else
@outer = nil
end
self.outervalue()
end
#
def dclone()
result = GxG::ByteArray.new(@data.clone)
result.padvalue = (self.padvalue())
result.maxsize = (self.maxsize())
result.outervalue = (self.outervalue())
result
end
#
def initialize_clone()
result = self.dclone
if self.frozen?
result.freeze
end
result
end
#
def initialize_dup()
# ByteArry is one-dimensional anyhow
self.dclone
end
alias :dup :initialize_dup
alias :initialize_copy :initialize_dup
def clone()
initialize_dup()
end
#
def [](indexer=0, length=nil)
self.slice(indexer,length)
end
#
def []=(indexer=0, length=nil, value=nil)
#
if length
unless value
value = length
length = nil
end
end
value = self.filter_data(value)
error = nil
result = nil
#
if value
if value.bytesize > 0
#
the_range = self.filter_parameters(indexer,length)
#
unless the_range
if length
error = ArgumentError.new("#{indexer.inspect},#{length.inspect} could not be fashioned into a valid Indexer")
else
error = ArgumentError.new("#{indexer.inspect} could not be fashioned into a valid Indexer")
end
end
if error
raise error
end
# check overall compliance with maxsize
if @max_size
if (the_range.count > @max_size || the_range.first > (@max_size - 1) || the_range.last > (@max_size - 1))
error = IndexError.new("Index exceeds #{@max_size} limit. You might need to adjust maxsize.")
end
if (the_range.first + (value.bytesize - 1)) > (@max_size - 1)
error = RangeError.new("Data size at specified Index exceeds #{@max_size} limit. You might need to adjust maxsize.")
end
#
if indexer.is_a?(Integer)
if length
# replace operation
if (the_range.first + (the_range.count - 1)) > (@max_size - 1)
error = RangeError.new("Range exceeds #{@max_size} limit. You might need to adjust maxsize.")
else
if the_range.count < value.bytesize and (the_range.first + (the_range.count - 1) + ((value.bytesize - 1) - (the_range.count - 1))) > (@max_size - 1)
error = RangeError.new("Value size exceeds #{@max_size} limit. You might need to adjust maxsize.")
end
end
else
# insert operation
if (@data.size - 1 + value.bytesize) > @max_size
error = RangeError.new("Operation exceeds #{@max_size} limit. You might need to adjust maxsize.")
end
if (the_range.first + (value.bytesize - 1)) > (@max_size - 1)
error = RangeError.new("Value size exceeds #{@max_size} limit. You might need to adjust maxsize.")
end
end
else
if the_range.count > @max_size
error = RangeError.new("Range exceeds #{@max_size} limit. You might need to adjust maxsize.")
else
if the_range.count < value.bytesize and (the_range.first + (the_range.count - 1) + ((value.bytesize - 1) - (the_range.count - 1))) > (@max_size - 1)
error = RangeError.new("Value size exceeds #{@max_size} limit. You might need to adjust maxsize.")
end
end
end
if error
raise error
end
end
#
if the_range.first > (@data.bytesize - 1)
(the_range.first - (@data.bytesize - 1)).times do
@data << @pad_value.chr
end
end
#
if length
@data[(the_range.first),(the_range.count)] = value
else
if indexer.is_a?(Numeric)
@data[(the_range.first)] = value
else
@data[(the_range)] = value
end
end
#
end
result = GxG::ByteArray.new(value)
end
result
end
#
def |(*args)
scrap = GxG::ByteArray::try_convert(args)
result = self.dclone
if scrap
scrap.each do |byte|
unless result.include?(byte)
result << byte
end
end
end
result
end
#
def &(*args)
result = GxG::ByteArray.new
if args.size > 0
other = GxG::ByteArray::try_convert(args)
if other
result_scrap = ""
result_scrap.force_encoding(Encoding::ASCII_8BIT)
self.each do |the_byte|
if other.include?(the_byte)
result_scrap << the_byte.chr
end
end
result = GxG::ByteArray.new(result_scrap)
end
end
result
end
#
def *(*args)
result = GxG::ByteArray.new
if self.size > 0
if args.size > 0
if args[0].is_a?(::String)
result = self.join(args[0])
else
if args[0].is_a?(Numeric)
scrap_size = ((args[0].to_f % args[0].to_i.to_f) * self.size.to_f).to_i
args[0] = args[0].to_i
if (args[0] * self.size) <= GxG::SYSTEM.memory_limits()[:process].first
args[0].times do
result << self.data().clone
end
if scrap_size > 0
result << self.data()[0..(scrap_size)]
end
else
raise NoMemoryError.new("This operation will exceed per-process memory limits")
end
else
raise ArgumentError.new("a String or Numeric is required, you provied #{args[0].inspect}")
end
end
else
raise TypeError.new("Can't convert nil to ByteArray")
end
end
result
end
#
def +(*args)
result = GxG::ByteArray.new
if args.size > 0
other = GxG::ByteArray::try_convert(args)
if other
if other.size > 0
result = GxG::ByteArray.new(1,[(self.data().clone),(other.data())])
end
end
else
raise TypeError.new("Can't convert nil to ByteArray")
end
result
end
#
def -(*args)
result = GxG::ByteArray.new
if args.size > 0
scrap = GxG::ByteArray::try_convert(args)
if scrap
result_scrap = ""
result_scrap.force_encoding(Encoding::ASCII_8BIT)
self.each do |the_byte|
unless scrap.include?(the_byte)
result_scrap << the_byte.chr
end
end
result = GxG::ByteArray.new(result_scrap)
end
end
result
end
#
def <<(*args)
if args.size > 0
value = GxG::ByteArray::try_convert(args)
if value
if @max_size
if (@data.bytesize + value.size) > @max_size
raise RangeError.new("Operation exceeds #{@max_size} limit. You might need to adjust maxsize.")
end
end
@data << value.data()
end
end
self
end
#
def <=>(*args)
result = 1
if args.size > 0
if args[0].is_a?(::GxG::ByteArray)
result = (self.data() <=> args[0].data())
else
# Allows sorting against std. Array or String that *would* sort normal if it were a ByteArray (byte comparison)
other = GxG::ByteArray::try_convert(args)
if other
result = (self.data() <=> other.data())
end
end
end
result
end
#
def ==(*args)
result = false
if args.size > 0
if args[0].is_a?(::GxG::ByteArray)
result = (self.data() == args[0].data())
else
# kinda cool, but might be a bridge too far for strict equivalence.
# other = GxG::ByteArray::try_convert(args)
# if other
# result = (self.data() == other.data())
#end
end
end
result
end
#
def at(*args)
if args[0].is_a?(Float)
args[0] = args[0].to_i
end
if args[0].is_a?(Integer)
self[(args[0])]
else
raise TypeError.new("Expected Integer or Float. You provided #{args[0].class}")
end
end
#
def clear()
@data.clear
self
end
#
def collect(&block)
result = GxG::ByteArray.new
if block.respond_to?(:call)
result_scrap = ""
result_scrap.force_encoding(Encoding::ASCII_8BIT)
self.each do |the_byte|
result_scrap << self.filter_data(block.call(the_byte))
end
result = GxG::ByteArray.new(result_scrap)
else
result = self.to_enum(:each)
end
result
end
#
def collect!(&block)
if block.respond_to?(:call)
self.replace(self.collect(&block).data())
self
else
self.to_enum(:each)
end
end
#
def combination(*args,&block)
# TODO: port c code to ruby (true-enumeration-as-bytearray support)
enumerator = @data.to_enum(:bytes).to_a.to_enum(:combination,*args)
if block.respond_to?(:call)
enumerator.each do |the_combination|
block.call(GxG::ByteArray.new(the_combination))
end
self
else
# Note: this form does not put bytearrays of each combination into the block, just arrays (please fix)
GxG::Enumerator.new(enumerator)
end
end
#
def repeated_combination(*args,&block)
# TODO: port c code to ruby (true-enumeration-as-bytearray support)
enumerator = @data.to_enum(:bytes).to_a.to_enum(:repeated_combination,*args)
if block.respond_to?(:call)
enumerator.each do |the_combination|
block.call(GxG::ByteArray.new(the_combination))
end
self
else
GxG::Enumerator.new(enumerator)
end
end
#
def compact()
self
end
#
def concat(*args)
result = self
if args.size > 0
other = GxG::ByteArray::try_convert(args)
if other
result = (self + other)
else
raise Exception.new("Could not convert #{args.inspect} to ByteArray")
end
end
result
end
#
def count(match_value=nil,&block)
result = 0
if match_value.is_a?(Numeric)
self.each do |the_byte|
if the_byte == match_value.to_i
result += 1
end
end
else
if block.respond_to?(:call)
self.each do |the_byte|
if block.call(the_byte) == true
result += 1
end
end
else
result = @data.bytesize
end
end
result
end
#
def cycle(iterations=nil,&block)
if @data.bytesize > 0
unless iterations.to_i < 0
if block.respond_to?(:call)
if iterations
iterations.to_i.times do
self.each do |the_byte|
block.call(the_byte)
end
end
nil
else
while true do
self.each do |the_byte|
block.call(the_byte)
end
end
end
else
@data.to_enum(:bytes).to_a.to_enum(:cycle,iterations)
end
end
end
end
#
def delete(*args,&block)
result = nil
if args[0].is_a?(Numeric)
comparitor = self.filter_data(args[0].to_i)
if comparitor.bytesize > 1
comparitor = comparitor[0]
end
scrap = @data.delete(comparitor)
if scrap == @data
if block.respond_to?(:call)
result = block.call()
end
else
@data.replace(scrap)
result = args[0]
end
end
result
end
#
def delete_at(*args)
result = nil
if args.size > 0
the_range = self.filter_parameters(*args)
if the_range.last < self.size
result = @data.slice!(the_range)
end
end
result
end
#
def delete_if(&block)
self.reject!(&block)
end
#
def drop(length)
self.slice((length.to_i - 1)..-1)
end
#
def drop_while(&block)
accumulator = GxG::ByteArray.new
if block.respond_to?(:call)
self.each do |the_byte|
result = block.call(the_byte)
unless result
accumulator << the_byte
end
end
accumulator
else
# TODO: 5xMem efficiency fix for various engine run conditions
GxG::Enumeratior.new(@data.to_enum(:bytes).to_a.to_enum(:drop_while))
end
end
#
# TODO: GxG::ByteArray.each_index/each_with_index: improve memory efficiency and speed significantly (!)
#
def each(&block)
if block.respond_to?(:call)
if @data.bytesize > 0
@data.to_enum(:bytes).each {|the_byte| block.call(the_byte)}
end
self
else
self.to_enum(:each)
end
end
#
def each_index(&block)
if block.respond_to?(:call)
if @data.bytesize > 0
(0..(@data.bytesize - 1)).to_enum.each {|index| block.call(index)}
end
self
else
self.to_enum(:each_index)
end
end
#
def each_with_index(offset=0,&block)
if block.respond_to?(:call)
if @data.bytesize > 0
# bulky, but required to support self.reject!, etc :
@data.to_enum(:bytes).with_index(offset).each {|value,index| block.call(value,index)}
#old : (0..(@data.bytesize - 1)).to_enum.each {|index| block.call(@data[(index)].ord,index)}
end
self
else
self.to_enum(:each_with_index,offset)
end
end
#
def empty?()
@data.bytesize == 0
end
#
def eql?(*args)
if args.size > 0
self == args[0]
else
false
end
end
#
def fetch(*args,&block)
#fetch(index) → obj click to toggle source
#fetch(index, default ) → obj
#fetch(index) {|index| block } → obj
#
#Tries to return the element at position index. If the index lies outside the array, the first form throws an IndexError exception,
# the second form returns default, and the third form returns the value of invoking the block, passing in the index.
# Negative values of index count from the end of the array.
# LATER: ByteArray.fetch: check how to incorporate 'padded-edges' feature
if args.size > 0
if args[0].is_a?(Numeric)
args[0] = self.filter_parameters(args[0].to_i).first
if args[0] >= self.size
if args[1]
if block.respond_to?(:call)
block.call(args[0])
else
args[1]
end
else
raise IndexError.new("Index specified lies outside the ByteArray")
end
else
self.at(args[0])
end
end
end
end
#
def fill(*args,&block)
#fill(obj) → ary click to toggle source
#fill(obj, start [, length]) → ary
#fill(obj, range ) → ary
#fill {|index| block } → ary
#fill(start [, length] ) {|index| block } → ary
#fill(range) {|index| block } → ary
#
#The first three forms set the selected elements of self (which may be the entire array) to obj. A start of nil is equivalent to zero.
#A length of nil is equivalent to self.length. The last three forms fill the array with the value of the block.
#The block is passed the absolute index of each element to be filled. Negative values of start count from the end of the array.
#a = [ "a", "b", "c", "d" ]
#a.fill("x") #=> ["x", "x", "x", "x"]
#a.fill("z", 2, 2) #=> ["x", "x", "z", "z"]
#a.fill("y", 0..1) #=> ["y", "y", "z", "z"]
#a.fill {|i| i*i} #=> [0, 1, 4, 9]
#a.fill(-2) {|i| i*i*i} #=> [0, 1, 8, 27]
# Note: if ByteArray is empty, it will be filled to @max_size, otherwise only the used portion or specified range.
result = nil
result_scrap = ""
result_scrap.force_encoding(Encoding::ASCII_8BIT)
#
if block.respond_to?(:call)
if args.size > 0
if args[0].is_a?(Numeric)
args[0] = args[0].to_i
if args[0] < 0
if args[1].is_a?(Numeric)
if args[1].to_i > 0
args = [((@data.bytesize + args[0])..(@data.bytesize + args[0] + (args[1].to_i - 1)))]
else
raise ArgumentError.new("length specified should be a positive Integer for Float")
end
else
args = [((@data.bytesize + args[0])..(@data.bytesize - 1))]
end
end
end
fillrange = self.filter_parameters(*args)
else
fillrange = (0..((@max_size || @data.bytesize) - 1))
end
(0..(@data.bytesize - 1)).to_enum.each do |index|
if fillrange.include?(index)
raw_value = self.filter_data(block.call(index))
if raw_value.size > 0
# Allow only single byte results to merge into fill
# since the concept is 'per-item' which in this case is a single byte only.
result_scrap << raw_value[0]
else
result_scrap << @pad_value.chr
end
else
result_scrap << @data[(index)]
end
end
else
if args.size > 0
value = nil
case args.size
when 1
fillrange = (0..((@max_size || @data.bytesize) - 1))
value = self.filter_data(args[0])
when 2
fillrange = self.filter_parameters(args[1])
value = self.filter_data(args[0])
when 3
fillrange = self.filter_parameters(args[1],args[2])
value = self.filter_data(args[0])
else
fillrange = self.filter_parameters(args[1],args[2])
value = self.filter_data(args[0])
end
value = (value || @pad_value.chr)
#
(0..(@data.bytesize - 1)).to_enum.each do |index|
if fillrange.include?(index)
result_scrap << value
else
result_scrap << @data[(index)]
end
end
else
raise ArgumentError.new("wrong number of arguments (0 for 1..3)")
end
end
if result_scrap.size > 0
self.replace(result_scrap)
end
self
end
#
def first(*args)
if args.size > 0
if args[0].is_a?(Float)
args[0] = args[0].to_i
end
if args[0].is_a?(Integer)
if args[0] > 0
if @max_size
unless args[0] <= @max_size
raise RangeError.new("#{args[0]} is out of range (1 - #{@max_size}).")
end
end
self[(0..(args[0] - 1))]
end
else
raise TypeError.new("Expected Integer. You provided #{args[0].class}")
end
else
self[0]
end
end
#
def flatten()
# ByteArray is always flat anyhow
self.dclone
end
#
def flatten!()
# ByteArray is always flat anyhow
self
end
#
def freeze()
@data.freeze
@max_size.freeze
@pad_value.freeze
@outer.freeze
super()
end
#
def hash()
@data.hash
end
#
def include?(*args)
if args.size > 0
if args[0].is_a?(Float)
args[0] = args[0].to_i
end
if args[0].is_a?(Integer)
if (0..255).include?(args[0])
@data.include?(args[0].chr)
else
raise RangeError.new("Value out of range: range is 0 to 255. You provided #{args[0]}")
end
else
raise TypeError.new("Expected Integer or Float. You provided #{args[0].class}")
end
else
raise ArgumentError.new("Wrong number of arguments (#{args.size} for 1).")
end
end
#
def find_index(*args, &block)
result = nil
if block.respond_to?(:call)
found_at = 0
@data.to_enum(:bytes).each do |the_byte|
if block.call(the_byte) == true
result = found_at
break
end
found_at += 1
end
else
if args.size > 0
if args[0].is_a?(Float)
args[0] = args[0].to_i
end
if args[0].is_a?(Integer)
if (0..255).include?(args[0])
if args[1]
# offset specified (addtional option vs standard Array)
result = @data.index(args[0].chr,args[1])
else
result = @data.index(args[0].chr)
end
end
else
raise TypeError.new("Expected Integer or Float. You provided #{args[0].class}")
end
else
result = @data.to_enum(:bytes)
end
end
result
end
#
def insert(*args)
if args.size > 1
if args[0].is_a?(Float)
args[0] = args[0].to_i
end
if args[0].is_a?(Integer)
value = self.filter_data(args[(1..(args.size - 1))])
if @max_size
unless (@data.bytesize + value.bytesize) <= @max_size
raise RangeError.new("Operation exceeds #{@max_size} limit. You need to adjust maxsize.")
end
end
begin
@data.insert(args[0],value)
rescue Exception => error
raise error.exception(error.message.gsub("string",(self.class.to_s)))
end
else
raise TypeError.new("Expected Integer or Float. You provided #{args[0].class}")
end
else
raise ArgumentError.new("Wrong number of arguments (#{args.size} for 2).")
end
self
end
#
def inspect()
result = "["
@data.to_enum(:bytes).each do |the_byte|
if result.size > 1
result << ", "
end
if the_byte > 10
result << "0x#{the_byte.to_s(16).upcase}"
else
result << "0x0#{the_byte.to_s(16).upcase}"
end
end
result << "]"
result
end
#
def join(*args)
result = ""
if args[1].is_a?(Encoding)
# Extra optional encoding vs standard Array
result.force_encoding(args[1])
else
result.force_encoding(Encoding::ASCII_8BIT)
end
@data.to_enum(:bytes).each do |the_byte|
if (args[0].is_a?(String) && result.size > 0)
result << (args[0] + the_byte.chr)
else
result << the_byte.chr
end
end
result
end
#
def last(*args)
if args.size > 0
if args[0].is_a?(Float)
args[0] = args[0].to_i
end
if args[0].is_a?(Integer)
if args[0] > 0
the_range = ((@data.bytesize - 1 - (args[0] - 1))..(@data.bytesize - 1))
if @max_size
unless args[0] <= @max_size
raise RangeError.new("#{args[0]} is out of range (1 - #{@max_size}).")
end
end
self[(the_range)]
else
raise RangeError.new("#{args[0]} is out of range (1 - #{@max_size}).")
end
else
raise TypeError.new("Expected Integer. You provided #{args[0].class}")
end
else
self[(@data.bytesize - 1)]
end
end
#
def pack(*args)
result = ""
if args.size >= 1
args[0] = (args[0] || "")
if args[0].is_a?(String)
if args[1].is_a?(Encoding)
# Extra optional encoding vs standard Array
result.force_encoding(args[1])
end
# LATER: ByteArray.pack: a more memory-efficient method is needed here long term. For now: leverage Array.pack.
# Each byte will take up 4 to 8 bytes depending just to get packed into a string ... gotta fix this.
result << self.to_a.pack(args[0])
else
raise TypeError.new("Expected String. You provided #{args[0].class}")
end
else
raise ArgumentError.new("Wrong number of arguments (#{args.size} for 1+).")
end
result
end
#
def permutation(*args,&block)
# TODO: port c code to ruby (true-enumeration-as-bytearray support)
enumerator = @data.to_enum(:bytes).to_a.to_enum(:permutation,*args)
if block.respond_to?(:call)
enumerator.each do |the_permutation|
block.call(GxG::ByteArray.new(the_permutation))
end
self
else
GxG::Enumerator.new(enumerator)
end
end
#
def repeated_permutation(*args,&block)
# TODO: port c code to ruby (true-enumeration-as-bytearray support)
enumerator = @data.to_enum(:bytes).to_a.to_enum(:repeated_permutation,*args)
if block.respond_to?(:call)
enumerator.each do |the_permutation|
block.call(GxG::ByteArray.new(the_permutation))
end
self
else
GxG::Enumerator.new(enumerator)
end
end
#
def pop(how_many=nil)
# LIFO style stack operation
result = nil
if how_many.is_a?(Numeric)
unless how_many.to_i > 0
raise ArgumentError.new("#{how_many} needs to be a positive Numeric")
end
result = ::GxG::ByteArray.new
how_many.times do
result << self.delete_at(-1)
end
else
result = self.delete_at(-1)
end
result
end
#
def product(*others,&block)
if @data.bytesize > 0
others = GxG::ByteArray.new(others)
if block.respond_to?(:call)
self.each do |the_byte|
others.each do |other_byte|
block.call(GxG::ByteArray.new([(the_byte),(other_byte)]))
end
end
self
else
accumulator = GxG::ByteArray.new
self.each do |the_byte|
others.each do |other_byte|
accumulator << the_byte
accumulator << other_byte
end
end
accumulator
end
else
self
end
end
#
def push(*args)
# LIFO style stack operation
self << GxG::ByteArray.new(args)
end
#
def rassoc(key=nil)
nil
end
#
def reject(&block)
result = nil
if block.respond_to?(:call)
result = GxG::ByteArray.new
result.padvalue = (@pad_value)
if @max_size
result.maxsize = (@max_size)
end
self.to_enum.each do |the_byte|
result << the_byte
end
else
result = self.to_enum
end
result
end
#
def reject!(&block)
result = nil
if block.respond_to?(:call)
purgelist = []
self.each_with_index do |value, index|
if block.call(value) == true
@data[(index)] = ""
purgelist << index
end
end
result = self
else
result = self.to_a.to_enum(:reject!)
end
result
end
#
def replace(*args)
# replace data payload while leaving max_size and pad_value untouched, fitting replacement data into in-place constraints if any.
if args.size > 0
replacement = nil
if args[0].is_a?(::GxG::ByteArray)
replacement = args[0].data()
end
unless replacement.is_a?(::String)
replacement = GxG::ByteArray::try_convert(args)
if replacement
replacement = replacement.data()
end
end
if replacement
replacement.force_encoding(Encoding::ASCII_8BIT)
if @max_size
if replacement.bytesize <= @max_size
@data.replace(replacement)
else
@data.replace(replacement[(0..(@max_size - 1))])
end
else
@data.replace(replacement)
end
else
raise Exception.new("Could not convert #{args[0].class} to ByteArray")
end
end
self
end
#
def reverse()
result = GxG::ByteArray.new(@data.reverse)
result.padvalue = (@pad_value.clone)
if @max_size
result.maxsize = (@max_size.clone)
end
result
end
#
def reverse!()
@data.reverse!
self
end
#
def reverse_each(&block)
result = nil
if block.respond_to?(:call)
# use duplicate of self, do not preserve frozen state.
result = self.dup
result.clear
@data.reverse.to_enum(:bytes).each {|the_byte| result << block.call(the_byte)}
else
# ballons mem footprint x2: *ouch* - don't know a better way yet.
result = self.clone.reverse!.to_enum
end
result
end
#
def rotate(vector=1)
unless vector.is_a?(Numeric)
raise ArgumentError.new("Expected a Numeric, you provided #{vector.class}")
end
# from array.c : rotate_count
if vector < 0
vector = (self.size - (~vector % self.size) - 1).to_i
else
vector = (vector % self.size).to_i
end
if GxG::Engine.memory_load_high?
# trade speed for (2x) space
scrap = []
unless vector == 0
the_range = self.filter_parameters(((vector)..-1))
scrap << self[(the_range)]
if the_range.first > 0
scrap << self[(0..(the_range.first - 1))]
end
end
if scrap.size > 0
result = GxG::ByteArray.new(scrap)
else
result = self.dclone
end
else
# trade (5x) space for speed
result = GxG::ByteArray.new(self.to_a.rotate(vector))
end
result
end
#
def rotate!(vector=1)
if vector.is_a?(Numeric)
if vector.to_i != 0
new_data = self.rotate(vector).data()
unless @data.hash == new_data.hash
self.replace(new_data)
end
end
else
raise ArgumentError.new("Expected a Numeric, you provided #{vector.class}")
end
self
end
#
def sample(*args)
GxG::ByteArray.new(@data.to_enum(:bytes).to_a.sample(*args))
end
#
def select(&block)
result = GxG::ByteArray.new
result.maxsize = (@max_size)
result.padvalue = (@pad_value)
result.outervalue = (@outer)
if block.respond_to?(:call)
self.each do |value|
unless block.call(value) == false
result << value
end
end
else
self.to_enum(:each)
end
result
end
#
def select!(&block)
if block.respond_to?(:call)
state = @data.hash
self.replace(self.select(&block).data())
if state == @data.hash
nil
else
self
end
else
self.to_enum(:each)
end
end
#
def shift()
# FIFO style pop
self.delete_at(0)
end
#
def shuffle(*args)
GxG::ByteArray.new(@data.to_enum(:bytes).to_a.shuffle(*args))
end
#
def shuffle!(*args)
self.replace(self.shuffle(*args))
self
end
#
def size()
@data.bytesize
end
#
def slice(indexer=0, length=nil)
the_range = self.filter_parameters(indexer,length)
unless the_range
if length
raise ArgumentError.new("#{indexer.inspect},#{length.inspect} could not be fashioned into a valid Indexer")
else
raise ArgumentError.new("#{indexer.inspect} could not be fashioned into a valid Indexer")
end
end
# check overall compliance with maxsize
if @max_size
if ((the_range.count > @max_size) || (the_range.first > (@max_size - 1)) || (the_range.last > (@max_size - 1)))
raise IndexError.new("Index exceeds #{@max_size} limit. You need to adjust maxsize.")
end
end
#
result = nil
#
if the_range
if the_range.first == the_range.last
result = @data.byte_at(the_range.first)
else
result = @data.bytes_at(the_range)
end
end
#
result
end
#
def slice!(indexer=0, length=nil)
replacement = self.slice(indexer,length)
if replacement.is_a?(Numeric)
self.replace(replacement.chr)
else
self.replace(replacement)
end
if self.size == 1
self.slice(0)
else
self
end
end
#
# BETA: ByteArray : see about self.to_a replacement (memory efficiency) in sort methods.
#
def sort(&block)
if block.respond_to?(:call)
GxG::ByteArray.new(self.to_a.sort {|a,b| pause;block.call(a,b)})
else
GxG::ByteArray.new(self.to_a.sort)
end
end
#
def sort!(&block)
if block.respond_to?(:call)
self.replace(self.sort(&block).data())
end
self
end
#
def sort_by!(&block)
if block.respond_to?(:call)
self.replace(GxG::ByteArray.new(self.to_a.sort_by! {|a,b| pause;block.call(a,b)}).data())
self
else
self.to_enum(:sort_by!)
end
end
#
def take(length)
self.slice(0..(length.to_i - 1))
end
#
def take_while(&block)
if block.respond_to?(:call)
accumulator = GxG::ByteArray.new
self.each do |the_byte|
result = block.call(the_byte)
if result
accumulator << the_byte
else
break
end
end
accumulator
else
self.to_enum(:take_while)
end
end
#
def to_a()
result = []
if @data.bytesize > 0
(0..(@data.bytesize - 1)).to_enum.each {|index| result << self[(index)]}
end
result
end
#
def to_s()
@data.dup
end
#
def to_json()
(("binary:" + self.to_s).encode64)
end
#
def transpose()
self.dclone
end
#
def uniq(&block)
if block.respond_to?(:call)
harness = Proc.new do |element|
pause
block.call(element)
end
GxG::ByteArray.new(self.to_a.uniq(&harness))
else
self
end
end
#
def uniq!(&block)
scrap = self.uniq(&block)
if scrap.size == self.size
nil
else
self.replace(scrap)
self
end
end
#
def unshift(*args)
# FIFO style push
if args.size > 0
self.insert(0,args)
end
self
end
#
def values_at(*args)
result = GxG::ByteArray.new
if args.size > 0
result_scrap = ""
result_scrap.force_encoding(Encoding::ASCII_8BIT)
args.to_enum.each do |selector|
if selector.is_a?(Numeric) or selector.is_a?(Range)
unless selector.is_a?(Range)
selector = selector.to_i
end
scrap = self[(selector)]
if scrap.is_a?(Integer)
result_scrap << scrap.chr
else
result_scrap << scrap.data()
end
else
raise TypeError.new("Expected Integer, Float or Range. You provided #{selector.class}")
end
end
result = GxG::ByteArray.new(result_scrap)
end
result
end
#
def zip(*args,&block)
# Converts any valid argument values to array, then merges elements of self with corresponding elements from each argument
# This generates a sequence of self.size n-element ByteArrays as step buffers, where n is one more that the count of arguments
# * Then flattened output as ByteArray *
# If the size of any argument is less than enumObj.size, @pad_value is supplied
# If a block given, it is invoked for each output array.
# If block returns an array or ByteArray, it replaces the provided buffer and is appended to output.
#
result = nil
if args.size > 0
result_scrap = ""
result_scrap.force_encoding(Encoding::ASCII_8BIT)
args.to_enum(:each_index).each do |index|
unless args[(index)].is_a?(GxG::ByteArray)
args[(index)] = GxG::ByteArray.new(args[(index)])
end
end
self.each_with_index do |byte,index|
#
buffer = ""
buffer.force_encoding(Encoding::ASCII_8BIT)
buffer << byte.chr
args.to_enum(:each_index).each do |arg_index|
if index >= args[(arg_index)].size
buffer << @pad_value.chr
else
buffer << args[(arg_index)].data()[(index)]
end
end
#
if block.respond_to?(:call)
# I want to differ from stock array.zip behavior:
# I want to have it possible to return the processed buffer back -
# to a result ByteArray if result of yield = array/ByteArray.
raw_new = block.call(GxG::ByteArray.new(buffer))
if raw_new
buffer = GxG::ByteArray.new(raw_new).data()
end
end
result_scrap << buffer
end
result = GxG::ByteArray.new(result_scrap)
else
result = self.dclone
end
result
end
# Method Aliases
alias :assoc :rassoc
alias :map :collect
alias :map! :collect!
alias :compact! :compact
alias :indices :values_at
alias :indexes :values_at
alias :rindex :find_index
alias :index :find_index
alias :length :size
alias :nitems :size
alias :to_ary :to_a
alias :keep_if :select!
# Byte Operations
# GxG Convenience Methods:
def paths_to(object=nil,base_path="")
# new idea here:
search_results = []
# TODO: if paths can use ranges, expand to use string or byte or int byte or byte-pattern matching to define returned valid path for get/set.
if object.is_a?(Numeric)
self.to_enum.each_with_index do |entry,index|
if entry == object.to_i
search_results << (base_path + "/" + index.to_s)
end
end
end
search_results
end
#
def get_at_path(the_path="/")
# TODO: question: would it serve much to allow ranges as structure-path specifier? What would that involve?
# /^(?:[0-9])*[0-9](?:[0-9])*$/ = nil if an alpha present there, else 0 only numeric
# Attribution : http://stackoverflow.com/questions/1240674/regex-match-a-string-containing-numbers-and-letters-but-not-a-string-of-just-nu
#
# if ":" detected do: (str.gsub("%2f","/").to_sym) as key else (str.gsub("%2f","/"))
result = nil
if the_path == "/"
result = self
else
object_stack = [(self)]
path_stack = the_path.split("/")
path_stack.to_enum.each do |path_element|
element = nil
if path_element.size > 0
if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
element = path_element.to_i
end
end
if element
result = object_stack.first[(element)]
if result.is_a?(NilClass)
break
else
object_stack.unshift(result)
end
else
# ignore double slashes? '//'
# break
end
end
end
result
end
#
def set_at_path(the_path="/",the_value=nil)
result = nil
if the_path != "/"
container = self
if container
raw_selector = ::File::basename(the_path)
selector = nil
if raw_selector.size > 0
if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
selector = raw_selector.to_i
end
end
if selector
container[(selector)] = the_value
if container[(selector)] == the_value
result = true
end
else
# ignore double slashes? '//'
# break
end
#
end
end
result
end
end
#
end
#