aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/formulary.rb
blob: 3815105c27cd36e67139ccca8f1c9d36e259bc92 (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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# The Formulary is responsible for creating instances of Formula.
# It is not meant to be used directy from formulae.

class Formulary
  module Formulae
    class << self
      if instance_method(:const_defined?).arity == -1
        def formula_const_defined?(name)
          const_defined?(name, false)
        end

        def formula_const_get(name)
          const_get(name, false)
        end
      else
        def formula_const_defined?(name)
          const_defined?(name)
        end

        def formula_const_get(name)
          const_get(name)
        end
      end

      def remove_formula_const(name)
        remove_const(name)
      end

      def formula_const_set(name, value)
        const_set(name, value)
      end
    end
  end

  def self.unload_formula formula_name
    Formulae.remove_formula_const(class_s(formula_name))
  end

  def self.restore_formula formula_name, value
    old_verbose, $VERBOSE = $VERBOSE, nil
    Formulae.formula_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(spec)
      klass.new(name, path, spec)
    end

    def klass
      begin
        have_klass = Formulae.formula_const_defined?(class_name)
      rescue NameError => e
        raise unless e.name.to_s == class_name
        raise FormulaUnavailableError, name, e.backtrace
      end

      load_file unless have_klass

      Formulae.formula_const_get(class_name)
    end

    private

    def load_file
      STDERR.puts "#{$0} (#{self.class.name}): loading #{path}" if ARGV.debug?
      raise FormulaUnavailableError.new(name) unless path.file?
      Formulae.module_eval(path.read, path)
    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(spec)
      formula = super
      formula.local_bottle_path = @bottle_filename
      formula
    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

    def load_file
      HOMEBREW_CACHE_FORMULA.mkpath
      FileUtils.rm_f(path)
      curl url, "-o", path
      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(spec)
      super
    rescue FormulaUnavailableError => e
      raise TapFormulaUnavailableError, tapped_name, e.backtrace
    end
  end

  class NullLoader < FormulaLoader
    def initialize(name)
      @name = name
    end

    def get_formula(spec)
      raise FormulaUnavailableError.new(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, spec=:stable)
    loader_for(ref).get_formula(spec)
  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 FormulaLoader.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 FormulaLoader.new(ref, possible_cached_formula)
    end

    return NullLoader.new(ref)
  end
end