File: //usr/local/rvm/gems/ruby-2.5.9/gems/sprockets-4.0.2/lib/sprockets/utils.rb
# frozen_string_literal: true
require 'set'
module Sprockets
# Internal: Utils, we didn't know where else to put it! Functions may
# eventually be shuffled into more specific drawers.
module Utils
extend self
# Internal: Check if object can safely be .dup'd.
#
# Similar to ActiveSupport #duplicable? check.
#
# obj - Any Object
#
# Returns false if .dup would raise a TypeError, otherwise true.
def duplicable?(obj)
case obj
when NilClass, FalseClass, TrueClass, Symbol, Numeric
false
else
true
end
end
# Internal: Duplicate and store key/value on new frozen hash.
#
# Seperated for recursive calls, always use hash_reassoc(hash, *keys).
#
# hash - Hash
# key - Object key
#
# Returns Hash.
def hash_reassoc1(hash, key)
hash = hash.dup if hash.frozen?
old_value = hash[key]
old_value = old_value.dup if duplicable?(old_value)
new_value = yield old_value
new_value.freeze if duplicable?(new_value)
hash.store(key, new_value)
hash.freeze
end
# Internal: Duplicate and store key/value on new frozen hash.
#
# Similar to Hash#store for nested frozen hashes.
#
# hash - Hash
# key_a - Object key. Use multiple keys for nested hashes.
# key_b - Object key. Use multiple keys for nested hashes.
# block - Receives current value at key.
#
# Examples
#
# config = {paths: ["/bin", "/sbin"]}.freeze
# new_config = hash_reassoc(config, :paths) do |paths|
# paths << "/usr/local/bin"
# end
#
# Returns duplicated frozen Hash.
def hash_reassoc(hash, key_a, key_b = nil, &block)
if key_b
hash_reassoc1(hash, key_a) do |value|
hash_reassoc(value, key_b, &block)
end
else
hash_reassoc1(hash, key_a, &block)
end
end
WHITESPACE_ORDINALS = {0x0A => "\n", 0x20 => " ", 0x09 => "\t"}
private_constant :WHITESPACE_ORDINALS
# Internal: Check if string has a trailing semicolon.
#
# str - String
#
# Returns true or false.
def string_end_with_semicolon?(str)
i = str.size - 1
while i >= 0
c = str[i].ord
i -= 1
next if WHITESPACE_ORDINALS[c]
return c === 0x3B
end
true
end
# Internal: Accumulate asset source to buffer and append a trailing
# semicolon if necessary.
#
# buf - String buffer to append to
# source - String source to append
#
# Returns buf String.
def concat_javascript_sources(buf, source)
return buf if source.bytesize <= 0
buf << source
# If the source contains non-ASCII characters, indexing on it becomes O(N).
# This will lead to O(N^2) performance in string_end_with_semicolon?, so we should use 32 bit encoding to make sure indexing stays O(1)
source = source.encode(Encoding::UTF_32LE) unless source.ascii_only?
return buf if string_end_with_semicolon?(source)
# If the last character in the string was whitespace,
# such as a newline, then we want to put the semicolon
# before the whitespace. Otherwise append a semicolon.
if whitespace = WHITESPACE_ORDINALS[source[-1].ord]
buf[-1] = ";#{whitespace}"
else
buf << ";"
end
buf
end
# Internal: Inject into target module for the duration of the block.
#
# mod - Module
#
# Returns result of block.
def module_include(base, mod)
old_methods = {}
mod.instance_methods.each do |sym|
old_methods[sym] = base.instance_method(sym) if base.method_defined?(sym)
end
mod.instance_methods.each do |sym|
method = mod.instance_method(sym)
base.send(:define_method, sym, method)
end
yield
ensure
mod.instance_methods.each do |sym|
base.send(:undef_method, sym) if base.method_defined?(sym)
end
old_methods.each do |sym, method|
base.send(:define_method, sym, method)
end
end
# Internal: Post-order Depth-First search algorithm.
#
# Used for resolving asset dependencies.
#
# initial - Initial Array of nodes to traverse.
# block -
# node - Current node to get children of
#
# Returns a Set of nodes.
def dfs(initial)
nodes, seen = Set.new, Set.new
stack = Array(initial).reverse
while node = stack.pop
if seen.include?(node)
nodes.add(node)
else
seen.add(node)
stack.push(node)
stack.concat(Array(yield node).reverse)
end
end
nodes
end
# Internal: Post-order Depth-First search algorithm that gathers all paths
# along the way.
#
# TODO: Rename function.
#
# path - Initial Array node path
# block -
# node - Current node to get children of
#
# Returns an Array of node Arrays.
def dfs_paths(path)
paths = []
stack = [path]
seen = Set.new
while path = stack.pop
seen.add(path.last)
paths << path
children = yield path.last
children.reverse_each do |node|
stack.push(path + [node]) unless seen.include?(node)
end
end
paths
end
end
end