1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
# frozen_string_literal: true
require 'sprockets/asset'
require 'sprockets/bower'
require 'sprockets/cache'
require 'sprockets/configuration'
require 'sprockets/digest_utils'
require 'sprockets/errors'
require 'sprockets/loader'
require 'sprockets/npm'
require 'sprockets/path_dependency_utils'
require 'sprockets/path_digest_utils'
require 'sprockets/path_utils'
require 'sprockets/resolve'
require 'sprockets/server'
require 'sprockets/source_map_utils'
require 'sprockets/uri_tar'
module Sprockets
class DoubleLinkError < Sprockets::Error
def initialize(parent_filename:, logical_path:, last_filename:, filename:)
super <<~MSG
Multiple files with the same output path cannot be linked (#{logical_path.inspect})
In #{parent_filename.inspect} these files were linked:
- #{last_filename}
- #{filename}
MSG
end
end
# `Base` class for `Environment` and `CachedEnvironment`.
class Base
include PathUtils, PathDependencyUtils, PathDigestUtils, DigestUtils, SourceMapUtils
include Configuration
include Server
include Resolve, Loader
include Bower
include Npm
# Get persistent cache store
attr_reader :cache
# Set persistent cache store
#
# The cache store must implement a pair of getters and
# setters. Either `get(key)`/`set(key, value)`,
# `[key]`/`[key]=value`, `read(key)`/`write(key, value)`.
def cache=(cache)
@cache = Cache.new(cache, logger)
end
# Return an `CachedEnvironment`. Must be implemented by the subclass.
def cached
raise NotImplementedError
end
alias_method :index, :cached
# Internal: Compute digest for path.
#
# path - String filename or directory path.
#
# Returns a String digest or nil.
def file_digest(path)
if stat = self.stat(path)
# Caveat: Digests are cached by the path's current mtime. Its possible
# for a files contents to have changed and its mtime to have been
# negligently reset thus appearing as if the file hasn't changed on
# disk. Also, the mtime is only read to the nearest second. It's
# also possible the file was updated more than once in a given second.
key = UnloadedAsset.new(path, self).file_digest_key(stat.mtime.to_i)
cache.fetch(key) do
self.stat_digest(path, stat)
end
end
end
# Find asset by logical path or expanded path.
def find_asset(*args, **options)
uri, _ = resolve(*args, **options)
if uri
load(uri)
end
end
def find_all_linked_assets(*args)
return to_enum(__method__, *args) unless block_given?
parent_asset = asset = find_asset(*args)
return unless asset
yield asset
stack = asset.links.to_a
linked_paths = {}
while uri = stack.shift
yield asset = load(uri)
last_filename = linked_paths[asset.logical_path]
if last_filename && last_filename != asset.filename
raise DoubleLinkError.new(
parent_filename: parent_asset.filename,
last_filename: last_filename,
logical_path: asset.logical_path,
filename: asset.filename
)
end
linked_paths[asset.logical_path] = asset.filename
stack = asset.links.to_a + stack
end
nil
end
# Preferred `find_asset` shorthand.
#
# environment['application.js']
#
def [](*args, **options)
find_asset(*args, **options)
end
# Find asset by logical path or expanded path.
#
# If the asset is not found an error will be raised.
def find_asset!(*args)
uri, _ = resolve!(*args)
if uri
load(uri)
end
end
# Pretty inspect
def inspect
"#<#{self.class}:0x#{object_id.to_s(16)} " +
"root=#{root.to_s.inspect}, " +
"paths=#{paths.inspect}>"
end
def compress_from_root(uri)
URITar.new(uri, self).compress
end
def expand_from_root(uri)
URITar.new(uri, self).expand
end
end
end
|