Module: Sprockets::Utils

Extended by:
Utils
Included in:
Compressing, Configuration, Engines, Mime, Paths, Processing, Transformers, Utils
Defined in:
lib/sprockets/utils.rb,
lib/sprockets/utils/gzip.rb

Overview

Internal: Utils, we didn’t know where else to put it! Functions may eventually be shuffled into more specific drawers.

Defined Under Namespace

Classes: Gzip

Constant Summary collapse

UNBOUND_METHODS_BIND_TO_ANY_OBJECT =

Internal: Feature detect if UnboundMethods can #bind to any Object or just Objects that share the same super class. Basically if RUBY_VERSION >= 2.

begin
  foo = Module.new { def bar; end }
  foo.instance_method(:bar).bind(Object.new)
  true
rescue TypeError
  false
end

Instance Method Summary collapse

Instance Method Details

#concat_javascript_sources(buf, source) ⇒ Object

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.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/sprockets/utils.rb', line 104

def concat_javascript_sources(buf, source)
  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?

    if !string_end_with_semicolon?(source)
      buf << ";\n"
    elsif source[source.size - 1].ord != 0x0A
      buf << "\n"
    end
  end

  buf
end

#dfs(initial) ⇒ Object

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.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/sprockets/utils.rb', line 190

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

#dfs_paths(path) ⇒ Object

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.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/sprockets/utils.rb', line 217

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

#duplicable?(obj) ⇒ Boolean

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.

Returns:

  • (Boolean)


16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/sprockets/utils.rb', line 16

def duplicable?(obj)
  if RUBY_VERSION >= "2.4.0"
    true
  else
    case obj
    when NilClass, FalseClass, TrueClass, Symbol, Numeric
      false
    else
      true
    end
  end
end

#hash_reassoc(hash, *keys, &block) ⇒ Object

Internal: Duplicate and store key/value on new frozen hash.

Similar to Hash#store for nested frozen hashes.

hash - Hash key - Object keys. 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.



63
64
65
66
67
68
69
70
71
# File 'lib/sprockets/utils.rb', line 63

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

#hash_reassoc1(hash, key) ⇒ Object

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.



37
38
39
40
41
42
43
44
45
# File 'lib/sprockets/utils.rb', line 37

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

#module_include(base, mod) ⇒ Object

Internal: Inject into target module for the duration of the block.

mod - Module

Returns result of block.



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/sprockets/utils.rb', line 155

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

#normalize_extension(extension) ⇒ Object

Internal: Prepends a leading “.” to an extension if its missing.

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

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


130
131
132
133
134
135
136
137
# File 'lib/sprockets/utils.rb', line 130

def normalize_extension(extension)
  extension = extension.to_s
  if extension[/^\./]
    extension
  else
    ".#{extension}"
  end
end

#string_end_with_semicolon?(str) ⇒ Boolean

Internal: Check if string has a trailing semicolon.

str - String

Returns true or false.

Returns:

  • (Boolean)


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/sprockets/utils.rb', line 78

def string_end_with_semicolon?(str)
  i = str.size - 1
  while i >= 0
    c = str[i].ord
    i -= 1

    # Need to compare against the ordinals because the string can be UTF_8 or UTF_32LE encoded
    # 0x0A == "\n"
    # 0x20 == " "
    # 0x09 == "\t"
    # 0x3B == ";"
    unless c == 0x0A || c == 0x20 || c == 0x09
      return c === 0x3B
    end
  end

  true
end