module JSI
module Util
autoload :Private, 'jsi/util/private'
include Private
extend self
autoload :Arraylike, 'jsi/util/typelike'
autoload :Hashlike, 'jsi/util/typelike'
def modified_copy(object, &block)
if object.respond_to?(:jsi_modified_copy)
object.jsi_modified_copy(&block)
else
yield(object)
end
end
def as_json(object, options = {})
type_err = proc { raise(TypeError, "cannot express object as json: #{object.pretty_inspect.chomp}") }
if object.respond_to?(:as_json)
options.empty? ? object.as_json : object.as_json(**options) elsif object.is_a?(Addressable::URI)
object.to_s
elsif object.respond_to?(:to_hash) && (object_to_hash = object.to_hash).is_a?(Hash)
result = {}
object_to_hash.each_pair do |k, v|
ks = k.is_a?(String) ? k :
k.is_a?(Symbol) ? k.to_s :
k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr :
raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
result[ks] = as_json(v, **options)
end
result
elsif object.respond_to?(:to_ary) && (object_to_ary = object.to_ary).is_a?(Array)
object_to_ary.map { |e| as_json(e, **options) }
elsif [String, Integer, TrueClass, FalseClass, NilClass].any? { |c| object.is_a?(c) }
object
elsif object.is_a?(Float)
type_err.call unless object.finite?
object
elsif object.is_a?(Symbol)
object.to_s
elsif object.is_a?(Set)
as_json(object.to_a, **options)
elsif object.respond_to?(:to_str) && (object_to_str = object.to_str).is_a?(String)
object_to_str
elsif object.respond_to?(:to_int) && (object_to_int = object.to_int).is_a?(Integer)
object_to_int
else
type_err.call
end
end
def to_json(object, options = {})
if USE_TO_JSON_METHOD[object.class]
options.empty? ? object.to_json : object.to_json(**options) else
JSON.generate(as_json(object, **options))
end
end
def stringify_symbol_keys(hashlike)
unless hashlike.respond_to?(:to_hash)
raise(ArgumentError, "expected argument to be a hash; got #{hashlike.class.inspect}: #{hashlike.pretty_inspect.chomp}")
end
JSI::Util.modified_copy(hashlike) do |hash|
out = {}
hash.each do |k, v|
out[k.is_a?(Symbol) ? k.to_s : k] = v
end
out
end
end
def deep_stringify_symbol_keys(object)
if object.respond_to?(:to_hash) && !object.is_a?(Addressable::URI)
JSI::Util.modified_copy(object) do |hash|
out = {}
(hash.respond_to?(:each) ? hash : hash.to_hash).each do |k, v|
out[k.is_a?(Symbol) ? k.to_s.freeze : deep_stringify_symbol_keys(k)] = deep_stringify_symbol_keys(v)
end
out
end
elsif object.respond_to?(:to_ary)
JSI::Util.modified_copy(object) do |ary|
(ary.respond_to?(:each) ? ary : ary.to_ary).map do |e|
deep_stringify_symbol_keys(e)
end
end
else
object
end
end
def deep_to_frozen(object, not_implemented: nil)
dtf = proc { |o| deep_to_frozen(o, not_implemented: not_implemented) }
if object.instance_of?(Hash)
out = {}
identical = object.frozen?
object.each do |k, v|
fk = dtf[k]
fv = dtf[v]
identical &&= fk.__id__ == k.__id__
identical &&= fv.__id__ == v.__id__
out[fk] = fv
end
if !object.default.nil?
out.default = dtf[object.default]
identical &&= out.default.__id__ == object.default.__id__
end
if object.default_proc
raise(ArgumentError, "cannot make immutable copy of a Hash with default_proc")
end
if identical
object
else
out.freeze
end
elsif object.instance_of?(Array)
identical = object.frozen?
out = Array.new(object.size)
object.each_with_index do |e, i|
fe = dtf[e]
identical &&= fe.__id__ == e.__id__
out[i] = fe
end
if identical
object
else
out.freeze
end
elsif object.instance_of?(String)
if object.frozen?
object
else
object.dup.freeze
end
elsif CLASSES_ALWAYS_FROZEN.any? { |c| object.is_a?(c) } object
else
if not_implemented
not_implemented.call(object)
else
raise(NotImplementedError, [
"deep_to_frozen not implemented for class: #{object.class}",
"object: #{object.pretty_inspect.chomp}",
].join("\n"))
end
end
end
def ensure_module_set(modules)
if modules.is_a?(Set) && modules.frozen?
set = modules
elsif modules.is_a?(Enumerable)
set = Set.new(modules).freeze
else
raise(TypeError, "not given an Enumerable of Modules")
end
not_modules = set.reject { |s| s.is_a?(Module) }
if !not_modules.empty?
raise(TypeError, [
"ensure_module_set given non-Module objects:",
*not_modules.map { |ns| ns.pretty_inspect.chomp },
].join("\n"))
end
set
end
end
end