Remove unsafe references from CLI and Formulary code

This commit is contained in:
Douglas Eichelberger 2024-12-06 11:06:27 -08:00
parent 710b4e794c
commit 9a2b386a52
4 changed files with 90 additions and 146 deletions

View File

@ -73,7 +73,7 @@ module Homebrew
end end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def no_named? = named.blank? def no_named? = named.empty?
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def build_from_source_formulae def build_from_source_formulae

View File

@ -18,7 +18,7 @@ module Homebrew
params( params(
args: String, args: String,
parent: Args, parent: Args,
override_spec: Symbol, override_spec: T.nilable(Symbol),
force_bottle: T::Boolean, force_bottle: T::Boolean,
flags: T::Array[String], flags: T::Array[String],
cask_options: T::Boolean, cask_options: T::Boolean,
@ -28,9 +28,9 @@ module Homebrew
def initialize( def initialize(
*args, *args,
parent: Args.new, parent: Args.new,
override_spec: T.unsafe(nil), override_spec: nil,
force_bottle: T.unsafe(nil), force_bottle: false,
flags: T.unsafe(nil), flags: [],
cask_options: false, cask_options: false,
without_api: false without_api: false
) )
@ -73,11 +73,7 @@ module Homebrew
).returns(T::Array[T.any(Formula, Keg, Cask::Cask)]) ).returns(T::Array[T.any(Formula, Keg, Cask::Cask)])
} }
def to_formulae_and_casks( def to_formulae_and_casks(
only: parent.only_formula_or_cask, only: parent.only_formula_or_cask, ignore_unavailable: false, method: nil, uniq: true, warn: false
ignore_unavailable: false,
method: T.unsafe(nil),
uniq: true,
warn: T.unsafe(nil)
) )
@to_formulae_and_casks ||= T.let( @to_formulae_and_casks ||= T.let(
{}, T.nilable(T::Hash[T.nilable(Symbol), T::Array[T.any(Formula, Keg, Cask::Cask)]]) {}, T.nilable(T::Hash[T.nilable(Symbol), T::Array[T.any(Formula, Keg, Cask::Cask)]])
@ -127,7 +123,7 @@ module Homebrew
T.cast(formula_or_cask, T.any(Formula, Cask::Cask)).tap&.installed? T.cast(formula_or_cask, T.any(Formula, Cask::Cask)).tap&.installed?
end end
return formulae_and_casks_with_taps if formulae_and_casks_without_taps.blank? return formulae_and_casks_with_taps if formulae_and_casks_without_taps.empty?
types = [] types = []
types << "formulae" if formulae_and_casks_without_taps.any?(Formula) types << "formulae" if formulae_and_casks_without_taps.any?(Formula)
@ -369,7 +365,7 @@ module Homebrew
options = { warn: }.compact options = { warn: }.compact
candidate_cask = Cask::CaskLoader.load(name, config:, **options) candidate_cask = Cask::CaskLoader.load(name, config:, **options)
if unreadable_error.present? if unreadable_error
onoe <<~EOS onoe <<~EOS
Failed to load formula: #{name} Failed to load formula: #{name}
#{unreadable_error} #{unreadable_error}
@ -435,7 +431,7 @@ module Homebrew
end end
end end
raise unreadable_error if unreadable_error.present? raise unreadable_error if unreadable_error
user, repo, short_name = name.downcase.split("/", 3) user, repo, short_name = name.downcase.split("/", 3)
if repo.present? && short_name.present? if repo.present? && short_name.present?

View File

@ -458,11 +458,9 @@ module Homebrew
).void ).void
} }
def named_args(type = nil, number: nil, min: nil, max: nil, without_api: false) def named_args(type = nil, number: nil, min: nil, max: nil, without_api: false)
if number.present? && (min.present? || max.present?) raise ArgumentError, "Do not specify both `number` and `min` or `max`" if number && (min || max)
raise ArgumentError, "Do not specify both `number` and `min` or `max`"
end
if type == :none && (number.present? || min.present? || max.present?) if type == :none && (number || min || max)
raise ArgumentError, "Do not specify both `number`, `min` or `max` with `named_args :none`" raise ArgumentError, "Do not specify both `number`, `min` or `max` with `named_args :none`"
end end
@ -527,9 +525,9 @@ module Homebrew
"<#{@named_args_type}>" "<#{@named_args_type}>"
end end
named_args = if @min_named_args.blank? && @max_named_args == 1 named_args = if @min_named_args.nil? && @max_named_args == 1
" [#{arg_type}]" " [#{arg_type}]"
elsif @min_named_args.blank? elsif @min_named_args.nil?
" [#{arg_type} ...]" " [#{arg_type} ...]"
elsif @min_named_args == 1 && @max_named_args == 1 elsif @min_named_args == 1 && @max_named_args == 1
" #{arg_type}" " #{arg_type}"

View File

@ -472,20 +472,17 @@ module Formulary
platform_cache[:api][name] = klass platform_cache[:api][name] = klass
end end
sig { params(name: String, spec: Symbol, force_bottle: T::Boolean, flags: T::Array[String]).returns(Formula) } sig {
params(name: String, spec: T.nilable(Symbol), force_bottle: T::Boolean, flags: T::Array[String]).returns(Formula)
}
def self.resolve( def self.resolve(
name, name,
spec: T.unsafe(nil), spec: nil,
force_bottle: T.unsafe(nil), force_bottle: false,
flags: T.unsafe(nil) flags: []
) )
options = {
force_bottle:,
flags:,
}.compact
if name.include?("/") || File.exist?(name) if name.include?("/") || File.exist?(name)
f = factory(name, *spec, **options) f = factory(name, *spec, force_bottle:, flags:)
if f.any_version_installed? if f.any_version_installed?
tab = Tab.for_formula(f) tab = Tab.for_formula(f)
resolved_spec = spec || tab.spec resolved_spec = spec || tab.spec
@ -498,10 +495,8 @@ module Formulary
end end
else else
rack = to_rack(name) rack = to_rack(name)
if (alias_path = factory(name, **options).alias_path) alias_path = factory(name, force_bottle:, flags:).alias_path
options[:alias_path] = alias_path f = from_rack(rack, *spec, alias_path:, force_bottle:, flags:)
end
f = from_rack(rack, *spec, **options)
end end
# If this formula was installed with an alias that has since changed, # If this formula was installed with an alias that has since changed,
@ -556,8 +551,8 @@ module Formulary
sig { returns(T.nilable(Tap)) } sig { returns(T.nilable(Tap)) }
attr_reader :tap attr_reader :tap
sig { params(name: String, path: Pathname, alias_path: Pathname, tap: Tap).void } sig { params(name: String, path: Pathname, alias_path: T.nilable(Pathname), tap: T.nilable(Tap)).void }
def initialize(name, path, alias_path: T.unsafe(nil), tap: T.unsafe(nil)) def initialize(name, path, alias_path: nil, tap: nil)
@name = name @name = name
@path = path @path = path
@alias_path = alias_path @alias_path = alias_path
@ -590,10 +585,10 @@ module Formulary
# Loads a formula from a bottle. # Loads a formula from a bottle.
class FromBottleLoader < FormulaLoader class FromBottleLoader < FormulaLoader
sig { sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(T.attached_class)) .returns(T.nilable(T.attached_class))
} }
def self.try_new(ref, from: T.unsafe(nil), warn: false) def self.try_new(ref, from: nil, warn: false)
return if Homebrew::EnvConfig.forbid_packages_from_paths? return if Homebrew::EnvConfig.forbid_packages_from_paths?
ref = ref.to_s ref = ref.to_s
@ -633,10 +628,10 @@ module Formulary
# Loads formulae from disk using a path. # Loads formulae from disk using a path.
class FromPathLoader < FormulaLoader class FromPathLoader < FormulaLoader
sig { sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(T.attached_class)) .returns(T.nilable(T.attached_class))
} }
def self.try_new(ref, from: T.unsafe(nil), warn: false) def self.try_new(ref, from: nil, warn: false)
path = case ref path = case ref
when String when String
Pathname(ref) Pathname(ref)
@ -651,58 +646,42 @@ module Formulary
return if Homebrew::EnvConfig.forbid_packages_from_paths? && return if Homebrew::EnvConfig.forbid_packages_from_paths? &&
!path.realpath.to_s.start_with?("#{HOMEBREW_CELLAR}/", "#{HOMEBREW_LIBRARY}/Taps/") !path.realpath.to_s.start_with?("#{HOMEBREW_CELLAR}/", "#{HOMEBREW_LIBRARY}/Taps/")
options = if (tap = Tap.from_path(path)) if (tap = Tap.from_path(path))
# Only treat symlinks in taps as aliases. # Only treat symlinks in taps as aliases.
if path.symlink? if path.symlink?
alias_path = path alias_path = path
path = alias_path.resolved_path path = alias_path.resolved_path
{
alias_path:,
tap:,
}
else
{
tap:,
}
end end
elsif (tap = Homebrew::API.tap_from_source_download(path))
# Don't treat cache symlinks as aliases.
{
tap:,
}
else else
{} # Don't treat cache symlinks as aliases.
tap = Homebrew::API.tap_from_source_download(path)
end end
return if path.extname != ".rb" return if path.extname != ".rb"
new(path, **options) new(path, alias_path:, tap:)
end end
sig { params(path: T.any(Pathname, String), alias_path: Pathname, tap: Tap).void } sig { params(path: T.any(Pathname, String), alias_path: T.nilable(Pathname), tap: T.nilable(Tap)).void }
def initialize(path, alias_path: T.unsafe(nil), tap: T.unsafe(nil)) def initialize(path, alias_path: nil, tap: nil)
path = Pathname(path).expand_path path = Pathname(path).expand_path
name = path.basename(".rb").to_s name = path.basename(".rb").to_s
alias_path = alias_path&.expand_path alias_path = alias_path&.expand_path
alias_dir = alias_path&.dirname alias_dir = alias_path&.dirname
options = { alias_path = nil if alias_dir != tap&.alias_dir
alias_path: (alias_path if alias_dir == tap&.alias_dir),
tap:,
}.compact
super(name, path, **options) super(name, path, alias_path:, tap: tap)
end end
end end
# Loads formula from a URI. # Loads formula from a URI.
class FromURILoader < FormulaLoader class FromURILoader < FormulaLoader
sig { sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(T.attached_class)) .returns(T.nilable(T.attached_class))
} }
def self.try_new(ref, from: T.unsafe(nil), warn: false) def self.try_new(ref, from: nil, warn: false)
return if Homebrew::EnvConfig.forbid_packages_from_paths? return if Homebrew::EnvConfig.forbid_packages_from_paths?
# Cache compiled regex # Cache compiled regex
@ -763,10 +742,10 @@ module Formulary
attr_reader :path attr_reader :path
sig { sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(FormulaLoader)) .returns(T.nilable(FormulaLoader))
} }
def self.try_new(ref, from: T.unsafe(nil), warn: false) def self.try_new(ref, from: nil, warn: false)
ref = ref.to_s ref = ref.to_s
return unless (name_tap_type = Formulary.tap_formula_name_type(ref, warn:)) return unless (name_tap_type = Formulary.tap_formula_name_type(ref, warn:))
@ -774,28 +753,24 @@ module Formulary
name, tap, type = name_tap_type name, tap, type = name_tap_type
path = Formulary.find_formula_in_tap(name, tap) path = Formulary.find_formula_in_tap(name, tap)
options = if type == :alias if type == :alias
# TODO: Simplify this by making `tap_formula_name_type` return the alias name. # TODO: Simplify this by making `tap_formula_name_type` return the alias name.
{ alias_name: T.must(ref[HOMEBREW_TAP_FORMULA_REGEX, :name]).downcase } alias_name = T.must(ref[HOMEBREW_TAP_FORMULA_REGEX, :name]).downcase
else
{}
end end
if type == :migration && tap.core_tap? && (loader = FromAPILoader.try_new(name)) if type == :migration && tap.core_tap? && (loader = FromAPILoader.try_new(name))
loader loader
else else
new(name, path, tap:, **options) new(name, path, tap:, alias_name:)
end end
end end
sig { params(name: String, path: Pathname, tap: Tap, alias_name: String).void } sig { params(name: String, path: Pathname, tap: Tap, alias_name: T.nilable(String)).void }
def initialize(name, path, tap:, alias_name: T.unsafe(nil)) def initialize(name, path, tap:, alias_name: nil)
options = { alias_path = tap.alias_dir/alias_name if alias_name
alias_path: (tap.alias_dir/alias_name if alias_name),
tap:,
}.compact
super(name, path, **options) super(name, path, alias_path:, tap:)
@tap = tap
end end
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false) def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false)
@ -819,10 +794,10 @@ module Formulary
# Loads a formula from a name, as long as it exists only in a single tap. # Loads a formula from a name, as long as it exists only in a single tap.
class FromNameLoader < FromTapLoader class FromNameLoader < FromTapLoader
sig { sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(FormulaLoader)) .returns(T.nilable(FormulaLoader))
} }
def self.try_new(ref, from: T.unsafe(nil), warn: false) def self.try_new(ref, from: nil, warn: false)
return unless ref.is_a?(String) return unless ref.is_a?(String)
return unless ref.match?(/\A#{HOMEBREW_TAP_FORMULA_NAME_REGEX}\Z/o) return unless ref.match?(/\A#{HOMEBREW_TAP_FORMULA_NAME_REGEX}\Z/o)
@ -851,10 +826,10 @@ module Formulary
# Loads a formula from a formula file in a keg. # Loads a formula from a formula file in a keg.
class FromKegLoader < FormulaLoader class FromKegLoader < FormulaLoader
sig { sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(T.attached_class)) .returns(T.nilable(T.attached_class))
} }
def self.try_new(ref, from: T.unsafe(nil), warn: false) def self.try_new(ref, from: nil, warn: false)
ref = ref.to_s ref = ref.to_s
return unless (keg_formula = HOMEBREW_PREFIX/"opt/#{ref}/.brew/#{ref}.rb").file? return unless (keg_formula = HOMEBREW_PREFIX/"opt/#{ref}/.brew/#{ref}.rb").file?
@ -866,10 +841,10 @@ module Formulary
# Loads a formula from a cached formula file. # Loads a formula from a cached formula file.
class FromCacheLoader < FormulaLoader class FromCacheLoader < FormulaLoader
sig { sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(T.attached_class)) .returns(T.nilable(T.attached_class))
} }
def self.try_new(ref, from: T.unsafe(nil), warn: false) def self.try_new(ref, from: nil, warn: false)
ref = ref.to_s ref = ref.to_s
return unless (cached_formula = HOMEBREW_CACHE_FORMULA/"#{ref}.rb").file? return unless (cached_formula = HOMEBREW_CACHE_FORMULA/"#{ref}.rb").file?
@ -881,10 +856,10 @@ module Formulary
# Pseudo-loader which will raise a {FormulaUnavailableError} when trying to load the corresponding formula. # Pseudo-loader which will raise a {FormulaUnavailableError} when trying to load the corresponding formula.
class NullLoader < FormulaLoader class NullLoader < FormulaLoader
sig { sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(T.attached_class)) .returns(T.nilable(T.attached_class))
} }
def self.try_new(ref, from: T.unsafe(nil), warn: false) def self.try_new(ref, from: nil, warn: false)
return if ref.is_a?(URI::Generic) return if ref.is_a?(URI::Generic)
new(ref) new(ref)
@ -920,10 +895,10 @@ module Formulary
# Load a formula from the API. # Load a formula from the API.
class FromAPILoader < FormulaLoader class FromAPILoader < FormulaLoader
sig { sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) params(ref: T.any(String, Pathname, URI::Generic), from: T.nilable(Symbol), warn: T::Boolean)
.returns(T.nilable(T.attached_class)) .returns(T.nilable(T.attached_class))
} }
def self.try_new(ref, from: T.unsafe(nil), warn: false) def self.try_new(ref, from: nil, warn: false)
return if Homebrew::EnvConfig.no_install_from_api? return if Homebrew::EnvConfig.no_install_from_api?
return unless ref.is_a?(String) return unless ref.is_a?(String)
return unless (name = ref[HOMEBREW_DEFAULT_TAP_FORMULA_REGEX, :name]) return unless (name = ref[HOMEBREW_DEFAULT_TAP_FORMULA_REGEX, :name])
@ -941,23 +916,16 @@ module Formulary
name, tap, type = name_tap_type name, tap, type = name_tap_type
options = if type == :alias alias_name = alias_name.downcase if type == :alias
{ alias_name: alias_name.downcase }
else
{}
end
new(name, tap:, **options) new(name, tap:, alias_name:)
end end
sig { params(name: String, tap: Tap, alias_name: String).void } sig { params(name: String, tap: T.nilable(Tap), alias_name: T.nilable(String)).void }
def initialize(name, tap: T.unsafe(nil), alias_name: T.unsafe(nil)) def initialize(name, tap: nil, alias_name: nil)
options = { alias_path = CoreTap.instance.alias_dir/alias_name if alias_name
alias_path: (CoreTap.instance.alias_dir/alias_name if alias_name),
tap:,
}.compact
super(name, Formulary.core_path(name), **options) super(name, Formulary.core_path(name), alias_path:, tap:)
end end
def klass(flags:, ignore_errors:) def klass(flags:, ignore_errors:)
@ -986,7 +954,7 @@ module Formulary
ref: T.any(Pathname, String), ref: T.any(Pathname, String),
spec: Symbol, spec: Symbol,
alias_path: T.any(NilClass, Pathname, String), alias_path: T.any(NilClass, Pathname, String),
from: Symbol, from: T.nilable(Symbol),
warn: T::Boolean, warn: T::Boolean,
force_bottle: T::Boolean, force_bottle: T::Boolean,
flags: T::Array[String], flags: T::Array[String],
@ -997,25 +965,19 @@ module Formulary
ref, ref,
spec = :stable, spec = :stable,
alias_path: nil, alias_path: nil,
from: T.unsafe(nil), from: nil,
warn: T.unsafe(nil), warn: false,
force_bottle: T.unsafe(nil), force_bottle: false,
flags: T.unsafe(nil), flags: [],
ignore_errors: T.unsafe(nil) ignore_errors: false
) )
cache_key = "#{ref}-#{spec}-#{alias_path}-#{from}" cache_key = "#{ref}-#{spec}-#{alias_path}-#{from}"
if factory_cached? && platform_cache[:formulary_factory]&.key?(cache_key) if factory_cached? && platform_cache[:formulary_factory]&.key?(cache_key)
return platform_cache[:formulary_factory][cache_key] return platform_cache[:formulary_factory][cache_key]
end end
loader_options = { from:, warn: }.compact formula = loader_for(ref, from:, warn:)
formula_options = { alias_path:, .get_formula(spec, alias_path:, force_bottle:, flags:, ignore_errors:)
force_bottle:,
flags:,
ignore_errors: }.compact
formula = loader_for(ref, **loader_options)
.get_formula(spec, **formula_options)
if factory_cached? if factory_cached?
platform_cache[:formulary_factory] ||= {} platform_cache[:formulary_factory] ||= {}
@ -1035,18 +997,13 @@ module Formulary
params( params(
rack: Pathname, rack: Pathname,
# Automatically resolves the formula's spec if not specified. # Automatically resolves the formula's spec if not specified.
spec: Symbol, spec: T.nilable(Symbol),
alias_path: T.any(Pathname, String), alias_path: T.any(NilClass, Pathname, String),
force_bottle: T::Boolean, force_bottle: T::Boolean,
flags: T::Array[String], flags: T::Array[String],
).returns(Formula) ).returns(Formula)
} }
def self.from_rack( def self.from_rack(rack, spec = nil, alias_path: nil, force_bottle: false, flags: [])
rack, spec = T.unsafe(nil),
alias_path: T.unsafe(nil),
force_bottle: T.unsafe(nil),
flags: T.unsafe(nil)
)
kegs = rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : [] kegs = rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : []
keg = kegs.find(&:linked?) || kegs.find(&:optlinked?) || kegs.max_by(&:scheme_and_version) keg = kegs.find(&:linked?) || kegs.find(&:optlinked?) || kegs.max_by(&:scheme_and_version)
@ -1075,18 +1032,18 @@ module Formulary
params( params(
keg: Keg, keg: Keg,
# Automatically resolves the formula's spec if not specified. # Automatically resolves the formula's spec if not specified.
spec: Symbol, spec: T.nilable(Symbol),
alias_path: T.any(Pathname, String), alias_path: T.any(NilClass, Pathname, String),
force_bottle: T::Boolean, force_bottle: T::Boolean,
flags: T::Array[String], flags: T::Array[String],
).returns(Formula) ).returns(Formula)
} }
def self.from_keg( def self.from_keg(
keg, keg,
spec = T.unsafe(nil), spec = nil,
alias_path: T.unsafe(nil), alias_path: nil,
force_bottle: T.unsafe(nil), force_bottle: false,
flags: T.unsafe(nil) flags: []
) )
tab = keg.tab tab = keg.tab
tap = tab.tap tap = tab.tap
@ -1125,7 +1082,7 @@ module Formulary
path: Pathname, path: Pathname,
contents: String, contents: String,
spec: Symbol, spec: Symbol,
alias_path: Pathname, alias_path: T.nilable(Pathname),
force_bottle: T::Boolean, force_bottle: T::Boolean,
flags: T::Array[String], flags: T::Array[String],
ignore_errors: T::Boolean, ignore_errors: T::Boolean,
@ -1136,18 +1093,13 @@ module Formulary
path, path,
contents, contents,
spec = :stable, spec = :stable,
alias_path: T.unsafe(nil), alias_path: nil,
force_bottle: T.unsafe(nil), force_bottle: false,
flags: T.unsafe(nil), flags: [],
ignore_errors: T.unsafe(nil) ignore_errors: false
) )
options = { FormulaContentsLoader.new(name, path, contents)
alias_path:, .get_formula(spec, alias_path:, force_bottle:, flags:, ignore_errors:)
force_bottle:,
flags:,
ignore_errors:,
}.compact
FormulaContentsLoader.new(name, path, contents).get_formula(spec, **options)
end end
def self.to_rack(ref) def self.to_rack(ref)
@ -1219,9 +1171,7 @@ module Formulary
[name, tap, type] [name, tap, type]
end end
def self.loader_for(ref, from: T.unsafe(nil), warn: true) def self.loader_for(ref, from: nil, warn: true)
options = { from:, warn: }.compact
[ [
FromBottleLoader, FromBottleLoader,
FromURILoader, FromURILoader,
@ -1233,7 +1183,7 @@ module Formulary
FromCacheLoader, FromCacheLoader,
NullLoader, NullLoader,
].each do |loader_class| ].each do |loader_class|
if (loader = loader_class.try_new(ref, **options)) if (loader = loader_class.try_new(ref, from:, warn:))
$stderr.puts "#{$PROGRAM_NAME} (#{loader_class}): loading #{ref}" if debug? $stderr.puts "#{$PROGRAM_NAME} (#{loader_class}): loading #{ref}" if debug?
return loader return loader
end end