-
Jack Nagel authoredJack Nagel authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
formulary.rb 5.89 KiB
# The Formulary is responsible for creating instances of Formula.
# It is not meant to be used directy from formulae.
class Formulary
def self.unload_formula formula_name
Object.send(:remove_const, class_s(formula_name))
end
def self.formula_class_defined? class_name
Object.const_defined?(class_name)
end
def self.get_formula_class class_name
Object.const_get(class_name)
end
def self.restore_formula formula_name, value
old_verbose, $VERBOSE = $VERBOSE, nil
Object.const_set(class_s(formula_name), value)
ensure
$VERBOSE = old_verbose
end
def self.class_s name
class_name = name.capitalize
class_name.gsub!(/[-_.\s]([a-zA-Z0-9])/) { $1.upcase }
class_name.gsub!('+', 'x')
class_name
end
# A FormulaLoader returns instances of formulae.
# Subclasses implement loaders for particular sources of formulae.
class FormulaLoader
# The formula's name
attr_reader :name
# The formula's ruby file's path or filename
attr_reader :path
# The ruby constant name of the formula's class
attr_reader :class_name
def initialize(name, path)
@name = name
@path = path.resolved_path
@class_name = Formulary.class_s(name)
end
# Gets the formula instance.
def get_formula
klass.new(name, path)
end
# Return the Class for this formula, `require`-ing it if
# it has not been parsed before.
def klass
begin
have_klass = Formulary.formula_class_defined? class_name
rescue NameError
raise FormulaUnavailableError.new(name)
end
unless have_klass
STDERR.puts "#{$0} (#{self.class.name}): loading #{path}" if ARGV.debug?
begin
require path
rescue NoMethodError
# This is a programming error in an existing formula, and should not
# have a "no such formula" message.
raise
rescue LoadError, NameError
raise if ARGV.debug? # let's see the REAL error
raise FormulaUnavailableError.new(name)
end
end
klass = Formulary.get_formula_class(class_name)
if klass == Formula || !(klass < Formula)
raise FormulaUnavailableError.new(name)
end
klass
end
end
# Loads formulae from bottles.
class BottleLoader < FormulaLoader
def initialize bottle_name
@bottle_filename = Pathname(bottle_name).realpath
name_without_version = bottle_filename_formula_name @bottle_filename
if name_without_version.empty?
if ARGV.homebrew_developer?
opoo "Add a new regex to bottle_version.rb to parse this filename."
end
name = bottle_name
else
name = name_without_version
end
super name, Formula.path(name)
end
def get_formula
formula = super
formula.local_bottle_path = @bottle_filename
formula
end
end
# Loads formulae from Homebrew's provided Library
class StandardLoader < FormulaLoader
def initialize name, path=Formula.path(name)
super
end
end
class AliasLoader < FormulaLoader
def initialize alias_path
path = alias_path.resolved_path
name = path.basename(".rb").to_s
super name, path
end
end
# Loads formulae from disk using a path
class FromPathLoader < FormulaLoader
def initialize path
path = Pathname.new(path).expand_path
super path.basename(".rb").to_s, path
end
end
# Loads formulae from URLs
class FromUrlLoader < FormulaLoader
attr_reader :url
def initialize url
@url = url
uri = URI(url)
formula = File.basename(uri.path, ".rb")
super formula, HOMEBREW_CACHE_FORMULA/File.basename(uri.path)
end
# Downloads the formula's .rb file
def fetch
begin
have_klass = Formulary.formula_class_defined? class_name
rescue NameError
raise FormulaUnavailableError.new(name)
end
unless have_klass
HOMEBREW_CACHE_FORMULA.mkpath
FileUtils.rm path.to_s, :force => true
curl url, '-o', path.to_s
end
end
def get_formula
fetch
super
end
end
# Loads tapped formulae.
class TapLoader < FormulaLoader
attr_reader :tapped_name
def initialize tapped_name
@tapped_name = tapped_name
user, repo, name = tapped_name.split("/", 3).map(&:downcase)
tap = Pathname.new("#{HOMEBREW_LIBRARY}/Taps/#{user}/homebrew-#{repo}")
path = tap.join("#{name}.rb")
if tap.directory?
tap.find_formula do |file|
if file.basename(".rb").to_s == name
path = file
end
end
end
super name, path
end
def get_formula
super
rescue FormulaUnavailableError
raise TapFormulaUnavailableError.new(tapped_name)
end
end
# Return a Formula instance for the given reference.
# `ref` is string containing:
# * a formula name
# * a formula pathname
# * a formula URL
# * a local bottle reference
def self.factory ref
loader_for(ref).get_formula
end
def self.canonical_name(ref)
loader_for(ref).name
end
def self.loader_for(ref)
case ref
when %r[(https?|ftp)://]
return FromUrlLoader.new(ref)
when Pathname::BOTTLE_EXTNAME_RX
return BottleLoader.new(ref)
when HOMEBREW_TAP_FORMULA_REGEX
return TapLoader.new(ref)
end
if File.extname(ref) == ".rb"
return FromPathLoader.new(ref)
end
formula_with_that_name = Formula.path(ref)
if formula_with_that_name.file?
return StandardLoader.new(ref, formula_with_that_name)
end
possible_alias = Pathname.new("#{HOMEBREW_LIBRARY}/Aliases/#{ref}")
if possible_alias.file?
return AliasLoader.new(possible_alias)
end
possible_cached_formula = Pathname.new("#{HOMEBREW_CACHE_FORMULA}/#{ref}.rb")
if possible_cached_formula.file?
return StandardLoader.new(ref, possible_cached_formula)
end
return StandardLoader.new(ref)
end
end