lib/loba/internal/value/value_helper.rb
require 'binding_of_caller'
module Loba
module Internal
module Value
# Internal helper functions for Value.phrases
module ValueHelper
module_function
# Assemble display that shows the method invoking Loba
# @param depth [Integer] depth in call stack to retrieve tag from
# @return [String] tag for where Loba was invoked, wrapped in "[" and "]"
def tag(depth: 0)
delim = { class: '.', instance: '#' }
'[' \
"#{class_name(depth: depth + 1)}" \
"#{delim[method_type(depth: depth + 1)]}" \
"#{method_name(depth: depth + 1)}" \
']'
end
# Identify source code line from where Loba was invoked
# @param depth [Integer] depth in call stack to retrieve source code line from
# @return [Integer] source code line number where Loba was invoked
def line(depth: 0)
caller[depth]
end
# Prepare display of an argument's value.
# @param argument [Symbol, Object] the value or variable for which information is
# to be retrieved
# @param inspect [Boolean] when true, force #inspect to be called against
# `argument` when evaluating
# @param depth [Integer] depth in call stack to start evaluation from
# @return [String] value representation of argument for display
def value(argument:, inspect: true, depth: 0)
val = evaluate(argument: argument, inspect: inspect, depth: depth + 1)
# #inspect adds explicit quotes to strings, so strip back off since #inspect
# always returns a String
val = Loba::Internal.unquote(val) if inspect
# make nils obvious
val.nil? ? '-nil-' : val.to_s
end
# Builds a label for display based on the argument when instantiated.
# If the argument (when instantiated) is not a symbol, it may not be possible
# to infer a label; in that case, '[unknown value]' is returned.
#
# @param argument [Symbol, Object] the value or variable for which information is
# to be retrieved
# * If a symbol, it is assumed to be a reference to a variable
# and a label can be inferred.
# * If any other type, it is assumed to be a literal value to
# and a label should be supplied when instantiated.
# @param explicit_label [String] when provided, an explicit label to use; will
# override any possible inferred label
#
# @return [String] label
def label(argument:, explicit_label: nil)
text = if explicit_label.nil?
argument.is_a?(Symbol) ? "#{argument}:" : nil
elsif explicit_label.respond_to?(:strip)
s = explicit_label.strip
s += ':' unless s[-1] == ':'
s
end
text.nil? ? '[unknown value]:' : text.to_s
end
# Evaluate an arguments value from where it's bound.
# @param argument [Symbol, Object] the value or variable for which information is
# to be retrieved
# @param inspect [Boolean] when true, force #inspect to be called against
# `argument` when evaluating
# @param depth [Integer] depth in call stack to start evaluation from
# @return [Object] value of the argument when Loba was invoked
def evaluate(argument:, inspect: true, depth: 0)
return inspect ? argument.inspect : argument unless argument.is_a?(Symbol)
evaluation = binding.of_caller(depth + 1).eval(argument.to_s)
inspect ? evaluation.inspect : evaluation
end
# Prepare display of class name from where Loba was invoked
# @param depth [Integer] depth in call stack to retrieve class name from
# @return [String] name of class where Loba was invoked
def class_name(depth: 0)
m = binding.of_caller(depth + 1).eval('self.class.name')
if m.nil? || m.empty?
'<anonymous class>'
elsif m == 'Class'
binding.of_caller(depth + 1).eval('self.name')
else
m
end
end
# Prepare display of whether the method from where Loba was invoked is
# for a class or an instance
# @param depth [Integer] depth in call stack
# @return [:class] if method in call stack is a class method
# @return [:instance] if method in call stack is an instance method
def method_type(depth: 0)
if binding.of_caller(depth + 1).eval('self.class.name') == 'Class'
:class
else
:instance
end
end
# Prepare display of method name from where Loba was invoked
# @param depth [Integer] depth in call stack to retrieve method name from
# @return [Symbol, String] name of class where Loba was invoked
def method_name(depth: 0)
m = binding.of_caller(depth + 1).eval('self.send(:__method__)')
m.nil? || m.empty? ? '<anonymous method>' : m
end
end
end
end
end