2015-08-09 14:39:46 +03:00
|
|
|
require "utils/json"
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# a {Tap} is used to extend the formulae provided by Homebrew core.
|
|
|
|
# Usually, it's synced with a remote git repository. And it's likely
|
|
|
|
# a Github repository with the name of `user/homebrew-repo`. In such
|
|
|
|
# case, `user/repo` will be used as the {#name} of this {Tap}, where
|
|
|
|
# {#user} represents Github username and {#repo} represents repository
|
|
|
|
# name without leading `homebrew-`.
|
2015-06-10 15:39:47 +08:00
|
|
|
class Tap
|
|
|
|
TAP_DIRECTORY = HOMEBREW_LIBRARY/"Taps"
|
|
|
|
|
2015-09-27 16:52:14 +08:00
|
|
|
CACHE = {}
|
|
|
|
|
|
|
|
def self.clear_cache
|
|
|
|
CACHE.clear
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.fetch(user, repo)
|
|
|
|
cache_key = "#{user}/#{repo}".downcase
|
|
|
|
CACHE.fetch(cache_key) { |key| CACHE[key] = Tap.new(user, repo) }
|
|
|
|
end
|
|
|
|
|
2015-06-10 15:39:47 +08:00
|
|
|
extend Enumerable
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# The user name of this {Tap}. Usually, it's the Github username of
|
|
|
|
# this #{Tap}'s remote repository.
|
2015-06-10 15:39:47 +08:00
|
|
|
attr_reader :user
|
2015-06-13 01:53:55 +08:00
|
|
|
|
|
|
|
# The repository name of this {Tap} without leading `homebrew-`.
|
2015-06-10 15:39:47 +08:00
|
|
|
attr_reader :repo
|
2015-06-13 01:53:55 +08:00
|
|
|
|
|
|
|
# The name of this {Tap}. It combines {#user} and {#repo} with a slash.
|
|
|
|
# {#name} is always in lowercase.
|
|
|
|
# e.g. `user/repo`
|
2015-06-10 15:39:47 +08:00
|
|
|
attr_reader :name
|
2015-06-13 01:53:55 +08:00
|
|
|
|
|
|
|
# The local path to this {Tap}.
|
|
|
|
# e.g. `/usr/local/Library/Taps/user/homebrew-repo`
|
2015-06-10 15:39:47 +08:00
|
|
|
attr_reader :path
|
|
|
|
|
2015-06-13 01:57:00 +08:00
|
|
|
def initialize(user, repo)
|
2015-06-10 15:39:47 +08:00
|
|
|
# we special case homebrew so users don't have to shift in a terminal
|
|
|
|
@user = user == "homebrew" ? "Homebrew" : user
|
|
|
|
@repo = repo
|
|
|
|
@name = "#{@user}/#{@repo}".downcase
|
|
|
|
@path = TAP_DIRECTORY/"#{@user}/homebrew-#{@repo}".downcase
|
2015-06-13 01:57:00 +08:00
|
|
|
end
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# The remote path to this {Tap}.
|
|
|
|
# e.g. `https://github.com/user/homebrew-repo`
|
2015-06-13 01:57:00 +08:00
|
|
|
def remote
|
|
|
|
@remote ||= if installed?
|
2015-07-30 16:51:00 +08:00
|
|
|
if git?
|
2015-06-13 01:57:00 +08:00
|
|
|
@path.cd do
|
|
|
|
Utils.popen_read("git", "config", "--get", "remote.origin.url").chomp
|
|
|
|
end
|
2015-06-10 15:39:47 +08:00
|
|
|
end
|
|
|
|
else
|
2015-06-13 01:57:00 +08:00
|
|
|
raise TapUnavailableError, name
|
2015-06-10 15:39:47 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-30 16:51:00 +08:00
|
|
|
# True if this {Tap} is a git repository.
|
|
|
|
def git?
|
|
|
|
(@path/".git").exist?
|
|
|
|
end
|
|
|
|
|
2015-06-10 15:39:47 +08:00
|
|
|
def to_s
|
|
|
|
name
|
|
|
|
end
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# True if this {Tap} is an official Homebrew tap.
|
2015-06-10 15:39:47 +08:00
|
|
|
def official?
|
|
|
|
@user == "Homebrew"
|
|
|
|
end
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# True if the remote of this {Tap} is a private repository.
|
2015-06-10 15:39:47 +08:00
|
|
|
def private?
|
|
|
|
return true if custom_remote?
|
|
|
|
GitHub.private_repo?(@user, "homebrew-#{@repo}")
|
|
|
|
rescue GitHub::HTTPNotFoundError
|
|
|
|
true
|
|
|
|
rescue GitHub::Error
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# True if this {Tap} has been installed.
|
2015-06-10 15:39:47 +08:00
|
|
|
def installed?
|
2015-06-11 21:54:00 +01:00
|
|
|
@path.directory?
|
2015-06-10 15:39:47 +08:00
|
|
|
end
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# True if the {#remote} of {Tap} is customized.
|
2015-06-10 15:39:47 +08:00
|
|
|
def custom_remote?
|
2015-06-13 01:57:00 +08:00
|
|
|
return true unless remote
|
|
|
|
remote.casecmp("https://github.com/#{@user}/homebrew-#{@repo}") != 0
|
2015-06-10 15:39:47 +08:00
|
|
|
end
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# an array of all {Formula} files of this {Tap}.
|
2015-06-10 15:39:47 +08:00
|
|
|
def formula_files
|
2015-09-27 16:52:14 +08:00
|
|
|
@formula_files ||= if dir = [@path/"Formula", @path/"HomebrewFormula", @path].detect(&:directory?)
|
|
|
|
dir.children.select { |p| p.extname == ".rb" }
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
2015-06-10 15:39:47 +08:00
|
|
|
end
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# an array of all {Formula} names of this {Tap}.
|
2015-06-10 15:39:47 +08:00
|
|
|
def formula_names
|
2015-09-27 16:52:14 +08:00
|
|
|
@formula_names ||= formula_files.map { |f| "#{name}/#{f.basename(".rb")}" }
|
2015-06-10 15:39:47 +08:00
|
|
|
end
|
|
|
|
|
2015-09-12 18:21:22 +08:00
|
|
|
# an array of all alias files of this {Tap}.
|
|
|
|
# @private
|
|
|
|
def alias_files
|
2015-09-27 16:52:14 +08:00
|
|
|
@alias_files ||= Pathname.glob("#{path}/Aliases/*").select(&:file?)
|
2015-09-12 18:21:22 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# an array of all aliases of this {Tap}.
|
|
|
|
# @private
|
|
|
|
def aliases
|
2015-09-27 16:52:14 +08:00
|
|
|
@aliases ||= alias_files.map { |f| "#{name}/#{f.basename}" }
|
2015-09-12 18:21:22 +08:00
|
|
|
end
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# an array of all commands files of this {Tap}.
|
2015-06-10 15:39:47 +08:00
|
|
|
def command_files
|
2015-09-27 16:52:14 +08:00
|
|
|
@command_files ||= Pathname.glob("#{path}/cmd/brew-*").select(&:executable?)
|
2015-06-10 15:39:47 +08:00
|
|
|
end
|
|
|
|
|
2015-08-09 22:42:46 +08:00
|
|
|
def pinned_symlink_path
|
|
|
|
HOMEBREW_LIBRARY/"PinnedTaps/#{@name}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def pinned?
|
2015-09-27 16:52:14 +08:00
|
|
|
return @pinned if instance_variable_defined?(:@pinned)
|
|
|
|
@pinned = pinned_symlink_path.directory?
|
2015-08-09 22:42:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def pin
|
|
|
|
raise TapUnavailableError, name unless installed?
|
|
|
|
raise TapPinStatusError.new(name, true) if pinned?
|
|
|
|
pinned_symlink_path.make_relative_symlink(@path)
|
2015-09-27 16:52:14 +08:00
|
|
|
@pinned = true
|
2015-08-09 22:42:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def unpin
|
|
|
|
raise TapUnavailableError, name unless installed?
|
|
|
|
raise TapPinStatusError.new(name, false) unless pinned?
|
|
|
|
pinned_symlink_path.delete
|
|
|
|
pinned_symlink_path.dirname.rmdir_if_possible
|
2015-09-27 16:52:14 +08:00
|
|
|
@pinned = false
|
2015-08-09 22:42:46 +08:00
|
|
|
end
|
|
|
|
|
2015-06-10 15:39:47 +08:00
|
|
|
def to_hash
|
2015-08-13 14:56:14 +08:00
|
|
|
hash = {
|
2015-06-10 15:39:47 +08:00
|
|
|
"name" => @name,
|
|
|
|
"user" => @user,
|
|
|
|
"repo" => @repo,
|
|
|
|
"path" => @path.to_s,
|
|
|
|
"installed" => installed?,
|
|
|
|
"official" => official?,
|
|
|
|
"formula_names" => formula_names,
|
|
|
|
"formula_files" => formula_files.map(&:to_s),
|
2015-08-09 22:42:46 +08:00
|
|
|
"command_files" => command_files.map(&:to_s),
|
|
|
|
"pinned" => pinned?
|
2015-06-10 15:39:47 +08:00
|
|
|
}
|
2015-08-13 14:56:14 +08:00
|
|
|
|
|
|
|
if installed?
|
|
|
|
hash["remote"] = remote
|
|
|
|
hash["custom_remote"] = custom_remote?
|
|
|
|
end
|
|
|
|
|
|
|
|
hash
|
2015-06-10 15:39:47 +08:00
|
|
|
end
|
|
|
|
|
2015-08-09 14:39:46 +03:00
|
|
|
# Hash with tap formula renames
|
|
|
|
def formula_renames
|
|
|
|
@formula_renames ||= if (rename_file = path/"formula_renames.json").file?
|
|
|
|
Utils::JSON.load(rename_file.read)
|
|
|
|
else
|
|
|
|
{}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-06-10 15:39:47 +08:00
|
|
|
def self.each
|
|
|
|
return unless TAP_DIRECTORY.directory?
|
|
|
|
|
|
|
|
TAP_DIRECTORY.subdirs.each do |user|
|
|
|
|
user.subdirs.each do |repo|
|
2015-09-27 16:52:14 +08:00
|
|
|
yield fetch(user.basename.to_s, repo.basename.to_s.sub("homebrew-", ""))
|
2015-06-10 15:39:47 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-06-13 01:53:55 +08:00
|
|
|
# an array of all installed {Tap} names.
|
2015-06-10 15:39:47 +08:00
|
|
|
def self.names
|
|
|
|
map(&:name)
|
|
|
|
end
|
|
|
|
end
|