aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/formulary.rb
blob: c4e1be1c73d953335850b3db7c537c2d6e3dca66 (plain)
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
148
149
150
151
152
153
154
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
180
181
182
183
184
# The Formulary is responsible for creating instances of Formula.
class Formulary

  def self.unload_formula formula_name
    Object.send(:remove_const, Formula.class_s(formula_name))
  end

  def self.formula_class_defined? formula_name
    Object.const_defined?(Formula.class_s(formula_name))
  end

  def self.get_formula_class formula_name
    Object.const_get(Formula.class_s(formula_name))
  end

  def self.restore_formula formula_name, value
    old_verbose, $VERBOSE = $VERBOSE, nil
    Object.const_set(Formula.class_s(formula_name), value)
  ensure
    $VERBOSE = old_verbose
  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

    # Gets the formula instance.
    # Subclasses must define this.
    def get_formula; 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? name
      rescue NameError
        raise FormulaUnavailableError.new(name)
      end

      unless have_klass
        puts "#{$0}: loading #{path}" if ARGV.debug?
        begin
          require path.to_s
        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(name)
      if (klass == Formula) || !klass.ancestors.include?(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
      @path = Formula.path(@name)
    end

    def get_formula
      formula = klass.new(name)
      formula.local_bottle_path = @bottle_filename
      return formula
    end
  end

  # Loads formulae from Homebrew's provided Library
  class StandardLoader < FormulaLoader
    def initialize name
      @name = name
      @path = Formula.path(name)
    end

    def get_formula
      return klass.new(name)
    end
  end

  # Loads formulae from disk using a path
  class FromPathLoader < FormulaLoader
    def initialize path
      # require allows filenames to drop the .rb extension, but everything else
      # in our codebase will require an exact and fullpath.
      path = "#{path}.rb" unless path =~ /\.rb$/

      @path = Pathname.new(path)
      @name = @path.stem
    end

    def get_formula
      klass.new(name, path.to_s)
    end
  end

  # Loads formulae from URLs
  class FromUrlLoader < FormulaLoader
    attr_reader :url

    def initialize url
      @url = url
      @path = HOMEBREW_CACHE_FORMULA/File.basename(url)
      @name = File.basename(url, '.rb')
    end

    # Downloads the formula's .rb file
    def fetch
      unless Formulary.formula_class_defined? name
        HOMEBREW_CACHE_FORMULA.mkpath
        FileUtils.rm path.to_s, :force => true
        curl url, '-o', path.to_s
      end
    end

    def get_formula
      return klass.new(name, path.to_s)
    end
  end

  # Loads tapped formulae.
  class TapLoader < FormulaLoader
    def initialize tapped_name
      @name = tapped_name
      @path = Pathname.new(tapped_name)
    end

    def get_formula
      klass.new(tapped_name, path.to_s)
    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
    # If a URL is passed, download to the cache and install
    if ref =~ %r[(https?|ftp)://]
      f = FromUrlLoader.new(ref)
      f.fetch
    elsif ref =~ Pathname::BOTTLE_EXTNAME_RX
      f = BottleLoader.new(ref)
    else
      name_or_path = Formula.canonical_name(ref)
      if name_or_path =~ %r{^(\w+)/(\w+)/([^/])+$}
        # name appears to be a tapped formula, so we don't munge it
        # in order to provide a useful error message when require fails.
        f = TapLoader.new(name_or_path)
      elsif name_or_path.include? "/"
        # If name was a path or mapped to a cached formula
        f = FromPathLoader.new(name_or_path)
      elsif name_or_path =~ /\.rb$/
        f = FromPathLoader.new("./#{name_or_path}")
      else
        f = StandardLoader.new(name_or_path)
      end
    end

    f.get_formula
  end
end