module Sprockets::Utils

def concat_javascript_sources(buf, source)

Returns buf String.

source - String source to append
buf - String buffer to append to

semicolon if necessary.
Internal: Accumulate asset source to buffer and append a trailing
def concat_javascript_sources(buf, source)
  if buf.bytesize > 0
    buf << ";" unless string_end_with_semicolon?(buf)
    buf << "\n" unless buf.end_with?("\n")
  end
  buf << source
end

def dfs(initial)

Returns a Set of nodes.

node - Current node to get children of
block -
initial - Initial Array of nodes to traverse.

Used for resolving asset dependencies.

Internal: Post-order Depth-First search algorithm.
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

def dfs_paths(path)

Returns an Array of node Arrays.

node - Current node to get children of
block -
path - Initial Array node path

TODO: Rename function.

along the way.
Internal: Post-order Depth-First search algorithm that gathers all paths
def dfs_paths(path)
  paths = []
  stack, seen = [path], Set.new
  while path = stack.pop
    if !seen.include?(path.last)
      seen.add(path.last)
      paths << path if path.size > 1
      Array(yield path.last).reverse_each do |node|
        stack.push(path + [node])
      end
    end
  end
  paths
end

def duplicable?(obj)

Returns false if .dup would raise a TypeError, otherwise true.

obj - Any Object

Similar to ActiveSupport #duplicable? check.

Internal: Check if object can safely be .dup'd.
def duplicable?(obj)
  case obj
  when NilClass, FalseClass, TrueClass, Symbol, Numeric
    false
  else
    true
  end
end

def hash_reassoc(hash, *keys, &block)

Returns duplicated frozen Hash.

end
paths << "/usr/local/bin"
new_config = hash_reassoc(config, :paths) do |paths|
config = {paths: ["/bin", "/sbin"]}.freeze

Examples

block - Receives current value at key.
key - Object keys. Use multiple keys for nested hashes.
hash - Hash

Similar to Hash#store for nested frozen hashes.

Internal: Duplicate and store key/value on new frozen hash.
def hash_reassoc(hash, *keys, &block)
  if keys.size == 1
    hash_reassoc1(hash, keys[0], &block)
  else
    hash_reassoc1(hash, keys[0]) do |value|
      hash_reassoc(value, *keys[1..-1], &block)
    end
  end
end

def hash_reassoc1(hash, key)

Returns Hash.

key - Object key
hash - Hash

Seperated for recursive calls, always use hash_reassoc(hash, *keys).

Internal: Duplicate and store key/value on new frozen 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

def module_include(base, mod)

Returns result of block.

mod - Module

Internal: Inject into target module for the duration of the 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
  unless UNBOUND_METHODS_BIND_TO_ANY_OBJECT
    base.send(:include, mod) unless base < mod
  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

def normalize_extension(extension)


# => ".css"
normalize_extension(".css")

# => ".js"
normalize_extension("js")

Internal: Prepends a leading "." to an extension if its missing.
def normalize_extension(extension)
  extension = extension.to_s
  if extension[/^\./]
    extension
  else
    ".#{extension}"
  end
end

def string_end_with_semicolon?(str)

Returns true or false.

str - String

Internal: Check if string has a trailing semicolon.
def string_end_with_semicolon?(str)
  i = str.size - 1
  while i >= 0
    c = str[i]
    i -= 1
    if c == "\n" || c == " " || c == "\t"
      next
    elsif c != ";"
      return false
    else
      return true
    end
  end
  true
end