diff --git a/Library/Homebrew/cask/cask_loader.rb b/Library/Homebrew/cask/cask_loader.rb index a3a288f620..d9e8fd452a 100644 --- a/Library/Homebrew/cask/cask_loader.rb +++ b/Library/Homebrew/cask/cask_loader.rb @@ -207,9 +207,9 @@ module Cask def self.try_new(ref, warn: false) ref = ref.to_s - return unless ref.match?(HOMEBREW_TAP_CASK_REGEX) + return unless (token_tap_type = CaskLoader.tap_cask_token_type(ref, warn: warn)) - token, tap, = CaskLoader.tap_cask_token_type(ref, warn: warn) + token, tap, = token_tap_type new("#{tap}/#{token}") end @@ -529,9 +529,12 @@ module Cask self.for(ref, warn: warn).load(config: config) end + sig { params(tapped_token: String, warn: T::Boolean).returns(T.nilable([String, Tap, T.nilable(Symbol)])) } def self.tap_cask_token_type(tapped_token, warn:) - user, repo, token = tapped_token.split("/", 3).map(&:downcase) - tap = Tap.fetch(user, repo) + return unless (tap_with_token = Tap.with_cask_token(tapped_token)) + + tap, token = tap_with_token + type = nil if (new_token = tap.cask_renames[token].presence) @@ -550,7 +553,9 @@ module Cask opoo "Tap migration for #{tapped_token} points to itself, stopping recursion." else old_token = tap.core_cask_tap? ? token : tapped_token - token, tap, = tap_cask_token_type(new_tapped_token, warn: false) + return unless (token_tap_type = tap_cask_token_type(new_tapped_token, warn: false)) + + token, tap, = token_tap_type new_token = new_tap.core_cask_tap? ? token : "#{tap}/#{token}" type = :migration end diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index f7a2ce0ed7..8786d187b0 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -142,8 +142,8 @@ on_request: true) return unless @cask.conflicts_with @cask.conflicts_with[:cask].each do |conflicting_cask| - if (match = conflicting_cask.match(HOMEBREW_TAP_CASK_REGEX)) - conflicting_cask_tap = Tap.fetch(match[1], match[2]) + if (conflicting_cask_tap_with_token = Tap.with_cask_token(conflicting_cask)) + conflicting_cask_tap, = conflicting_cask_tap_with_token next unless conflicting_cask_tap.installed? end diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index 16b5d80034..ca9293f15f 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -175,11 +175,13 @@ module Homebrew end args.named.each do |name| - next if File.exist?(name) - next unless name =~ HOMEBREW_TAP_FORMULA_REGEX + if (tap_with_name = Tap.with_formula_name(name)) + tap, = tap_with_name + elsif (tap_with_token = Tap.with_cask_token(name)) + tap, = tap_with_token + end - tap = Tap.fetch(Regexp.last_match(1), Regexp.last_match(2)) - tap.ensure_installed! + tap&.ensure_installed! end if args.ignore_dependencies? diff --git a/Library/Homebrew/dependency.rb b/Library/Homebrew/dependency.rb index 8896435b67..d4ea978768 100644 --- a/Library/Homebrew/dependency.rb +++ b/Library/Homebrew/dependency.rb @@ -19,7 +19,9 @@ class Dependency @name = name @tags = tags - @tap = Tap.fetch(Regexp.last_match(1), Regexp.last_match(2)) if name =~ HOMEBREW_TAP_FORMULA_REGEX + return unless (tap_with_name = Tap.with_formula_name(name)) + + @tap, = tap_with_name end def to_s diff --git a/Library/Homebrew/dev-cmd/extract.rb b/Library/Homebrew/dev-cmd/extract.rb index 257cd30450..1f8ed18374 100644 --- a/Library/Homebrew/dev-cmd/extract.rb +++ b/Library/Homebrew/dev-cmd/extract.rb @@ -34,9 +34,8 @@ module Homebrew def self.extract args = extract_args.parse - if (match = args.named.first.match(HOMEBREW_TAP_FORMULA_REGEX)) - name = match[3].downcase - source_tap = Tap.fetch(match[1], match[2]) + if (tap_with_name = args.named.first&.then { Tap.with_formula_name(_1) }) + source_tap, name = tap_with_name else name = args.named.first.downcase source_tap = CoreTap.instance diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 90fb41557b..9a43cef169 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -721,15 +721,15 @@ module Formulary } def self.try_new(ref, from: T.unsafe(nil), warn: false) ref = ref.to_s - return unless (name = ref[HOMEBREW_TAP_FORMULA_REGEX, :name]) - alias_name = name + return unless (name_tap_type = Formulary.tap_formula_name_type(ref, warn: warn)) - name, tap, type = Formulary.tap_formula_name_type(ref, warn: warn) + name, tap, type = name_tap_type path = Formulary.find_formula_in_tap(name, tap) options = if type == :alias - { alias_name: alias_name.downcase } + # TODO: Simplify this by making `tap_formula_name_type` return the alias name. + { alias_name: T.must(ref[HOMEBREW_TAP_FORMULA_REGEX, :name]).downcase } else {} end @@ -893,7 +893,9 @@ module Formulary ref = "#{CoreTap.instance}/#{name}" - name, tap, type = Formulary.tap_formula_name_type(ref, warn: warn) + return unless (name_tap_type = Formulary.tap_formula_name_type(ref, warn: warn)) + + name, tap, type = name_tap_type options = if type == :alias { alias_name: alias_name.downcase } @@ -1127,9 +1129,12 @@ module Formulary loader_for(ref).path end + sig { params(tapped_name: String, warn: T::Boolean).returns(T.nilable([String, Tap, T.nilable(Symbol)])) } def self.tap_formula_name_type(tapped_name, warn:) - user, repo, name = tapped_name.split("/", 3).map(&:downcase) - tap = Tap.fetch(user, repo) + return unless (tap_with_name = Tap.with_formula_name(tapped_name)) + + tap, name = tap_with_name + type = nil # FIXME: Remove the need to do this here. @@ -1138,7 +1143,7 @@ module Formulary if (possible_alias = tap.alias_table[alias_table_key].presence) # FIXME: Remove the need to split the name and instead make # the alias table only contain short names. - name = possible_alias.split("/").last + name = T.must(possible_alias.split("/").last) type = :alias elsif (new_name = tap.formula_renames[name].presence) old_name = tap.core_tap? ? name : tapped_name @@ -1156,7 +1161,10 @@ module Formulary opoo "Tap migration for #{tapped_name} points to itself, stopping recursion." else old_name = tap.core_tap? ? name : tapped_name - name, tap, = tap_formula_name_type(new_tapped_name, warn: false) + return unless (name_tap_type = tap_formula_name_type(new_tapped_name, warn: false)) + + name, tap, = name_tap_type + new_name = new_tap.core_tap? ? name : "#{tap}/#{name}" type = :migration end diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 256816d647..5bad532b3b 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -65,6 +65,38 @@ class Tap fetch(match[:user], match[:repo]) end + # @private + sig { params(name: String).returns(T.nilable([T.attached_class, String])) } + def self.with_formula_name(name) + return unless (match = name.match(HOMEBREW_TAP_FORMULA_REGEX)) + + user = T.must(match[:user]) + repo = T.must(match[:repo]) + name = T.must(match[:name]) + + # Relative paths are not taps. + return if [user, repo].intersect?([".", ".."]) + + tap = fetch(user, repo) + [tap, name.downcase] + end + + # @private + sig { params(token: String).returns(T.nilable([T.attached_class, String])) } + def self.with_cask_token(token) + return unless (match = token.match(HOMEBREW_TAP_CASK_REGEX)) + + user = T.must(match[:user]) + repo = T.must(match[:repo]) + token = T.must(match[:token]) + + # Relative paths are not taps. + return if [user, repo].intersect?([".", ".."]) + + tap = fetch(user, repo) + [tap, token.downcase] + end + sig { returns(CoreCaskTap) } def self.default_cask_tap odisabled "`Tap.default_cask_tap`", "`CoreCaskTap.instance`" diff --git a/Library/Homebrew/test/formulary_spec.rb b/Library/Homebrew/test/formulary_spec.rb index cd4dec18c3..92e1343225 100644 --- a/Library/Homebrew/test/formulary_spec.rb +++ b/Library/Homebrew/test/formulary_spec.rb @@ -535,6 +535,22 @@ RSpec.describe Formulary do end describe "::loader_for" do + context "when given a relative path with two slashes" do + it "returns a `FromPathLoader`" do + mktmpdir.cd do + FileUtils.mkdir "Formula" + FileUtils.touch "Formula/gcc.rb" + expect(described_class.loader_for("./Formula/gcc.rb")).to be_a Formulary::FromPathLoader + end + end + end + + context "when given a tapped name" do + it "returns a `FromTapLoader`" do + expect(described_class.loader_for("homebrew/core/gcc")).to be_a Formulary::FromTapLoader + end + end + context "when not using the API" do before do ENV["HOMEBREW_NO_INSTALL_FROM_API"] = "1" diff --git a/Library/Homebrew/test/tap_spec.rb b/Library/Homebrew/test/tap_spec.rb index 2b735c9d44..0fd9fb11d9 100644 --- a/Library/Homebrew/test/tap_spec.rb +++ b/Library/Homebrew/test/tap_spec.rb @@ -599,4 +599,24 @@ RSpec.describe Tap do expect(described_class.fetch("my", "tap-with-@-symbol").repo_var_suffix).to eq "_MY_HOMEBREW_TAP_WITH___SYMBOL" end end + + describe "::with_formula_name" do + it "returns the tap and formula name when given a full name" do + expect(described_class.with_formula_name("homebrew/core/gcc")).to eq [CoreTap.instance, "gcc"] + end + + it "returns nil when given a relative path" do + expect(described_class.with_formula_name("./Formula/gcc.rb")).to be_nil + end + end + + describe "::with_cask_token" do + it "returns the tap and cask token when given a full token" do + expect(described_class.with_cask_token("homebrew/cask/alfred")).to eq [CoreCaskTap.instance, "alfred"] + end + + it "returns nil when given a relative path" do + expect(described_class.with_cask_token("./Casks/alfred.rb")).to be_nil + end + end end