|
|
|
require 'download_strategy'
|
|
|
|
require 'formula_support'
|
|
|
|
require 'plist/parser'
|
|
|
|
require 'uri'
|
|
|
|
|
|
|
|
HOME_APPS = Pathname.new(File.expand_path("~/Applications"))
|
|
|
|
|
|
|
|
class Cask; end
|
|
|
|
|
|
|
|
require 'cask/cli'
|
|
|
|
require 'cask/cli/edit'
|
|
|
|
require 'cask/cli/install'
|
|
|
|
require 'cask/cli/linkapps'
|
|
|
|
require 'cask/cli/list'
|
|
|
|
require 'cask/cli/search'
|
|
|
|
require 'cask/dsl'
|
|
|
|
require 'cask/exceptions'
|
|
|
|
require 'cask/scopes'
|
|
|
|
require 'plist/parser'
|
|
|
|
|
|
|
|
class Cask
|
|
|
|
include Cask::Scopes
|
|
|
|
include Cask::DSL
|
|
|
|
|
|
|
|
def self.tapspath
|
|
|
|
HOMEBREW_PREFIX.join "Library", "Taps"
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.cellarpath
|
|
|
|
HOMEBREW_CELLAR
|
|
|
|
end
|
Add tap support to `edit` and `list`
Most notably, Cask.all returns an array of strings,
not of Cask instances. This makes things easier, as
well as faster, as there's no need to run map(&:to_s)
everywhere anymore.
self.path is a utility method which returns the path
of the cask from its title. There's something subtle
going in there:
- If `cask_title` is fully qualified, e.g.
"phinze-cask/alfred", it's straightforward.
- If `cask_title` is only the name, e.g.
"firefox-aurora", the name is matched from
the full list (self.all) (which isn't sorted)
and the first result is returned.
Hence, self.path with only the name is not precise.
There might be the possibility to apply heuristics
to do a better match (prefer phinze-cask, or maybe
installed casks?) but that's for another issue :-)
self.nice_listing is another utility method used
in `search` and `list`. It returns a list where
unique casks don't have a prefix, and duplicates
do. The prefix is the tap name. The list is then
sorted. For an example or two, look at the first
comment on phinze/#12.
13 years ago
|
|
|
|
|
|
|
def self.init
|
|
|
|
HOMEBREW_CACHE.mkpath
|
|
|
|
HOME_APPS.mkpath
|
|
|
|
end
|
|
|
|
|
Add tap support to `edit` and `list`
Most notably, Cask.all returns an array of strings,
not of Cask instances. This makes things easier, as
well as faster, as there's no need to run map(&:to_s)
everywhere anymore.
self.path is a utility method which returns the path
of the cask from its title. There's something subtle
going in there:
- If `cask_title` is fully qualified, e.g.
"phinze-cask/alfred", it's straightforward.
- If `cask_title` is only the name, e.g.
"firefox-aurora", the name is matched from
the full list (self.all) (which isn't sorted)
and the first result is returned.
Hence, self.path with only the name is not precise.
There might be the possibility to apply heuristics
to do a better match (prefer phinze-cask, or maybe
installed casks?) but that's for another issue :-)
self.nice_listing is another utility method used
in `search` and `list`. It returns a list where
unique casks don't have a prefix, and duplicates
do. The prefix is the tap name. The list is then
sorted. For an example or two, look at the first
comment on phinze/#12.
13 years ago
|
|
|
def self.path(cask_title)
|
|
|
|
cask_title = all.grep(/#{cask_title}$/).first unless cask_title =~ /\//
|
|
|
|
tapspath.join(cask_title.sub("/", "/Casks/") + ".rb") unless cask_title.nil?
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.load(cask_title)
|
|
|
|
cask_path = path(cask_title)
|
|
|
|
raise CaskUnavailableError, cask_title unless cask_path
|
|
|
|
require cask_path
|
|
|
|
const_get(cask_title.split('/').last.split('-').map(&:capitalize).join).new
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.title
|
|
|
|
self.name.gsub(/([a-z\d])([A-Z])/,'\1-\2').downcase
|
|
|
|
end
|
|
|
|
|
|
|
|
attr_reader :title
|
|
|
|
def initialize(title=self.class.title)
|
|
|
|
@title = title
|
|
|
|
end
|
|
|
|
|
|
|
|
VALID_SUFFIXES = ['dmg', 'pkg', 'app']
|
|
|
|
|
|
|
|
def destination_path
|
|
|
|
HOMEBREW_CELLAR.join(self.title).join(self.version)
|
|
|
|
end
|
|
|
|
|
|
|
|
def install
|
|
|
|
downloader = CurlDownloadStrategy.new(self.title, SoftwareSpec.new(self.url.to_s, self.version))
|
|
|
|
downloaded_path = downloader.fetch
|
|
|
|
|
|
|
|
FileUtils.mkdir_p destination_path
|
|
|
|
|
|
|
|
_with_extracted_mountpoints(downloaded_path) do |mountpoint|
|
|
|
|
puts `ditto '#{mountpoint}' '#{destination_path}'`
|
|
|
|
end
|
|
|
|
|
|
|
|
ohai "Success! #{self} installed to #{destination_path}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def linkapps
|
|
|
|
destination_path.entries.select { |f| f.basename.to_s =~ /.app$/ }.each do |app|
|
|
|
|
symlink_destination = HOME_APPS.join(app.basename)
|
|
|
|
symlink_target = destination_path.join(app)
|
|
|
|
if symlink_destination.symlink?
|
|
|
|
puts "#{symlink_destination} exists but is symlink; removing and relinking"
|
|
|
|
puts "#{symlink_destination} -> #{symlink_target}"
|
|
|
|
symlink_destination.delete
|
|
|
|
symlink_destination.make_symlink(symlink_target)
|
|
|
|
elsif symlink_destination.directory? || symlink_destination.file?
|
|
|
|
puts "#{symlink_destination} already exists and is not a symlink, not linking #{self}"
|
|
|
|
else
|
|
|
|
puts "#{symlink_destination} -> #{symlink_target}"
|
|
|
|
symlink_destination.make_symlink(symlink_target)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def installed?
|
|
|
|
return false unless destination_path.exist?
|
|
|
|
destination_path.entries.any? do |f|
|
|
|
|
f.basename.to_s =~ /.app$/
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def _with_extracted_mountpoints(path)
|
|
|
|
if _dmg?(path)
|
|
|
|
File.open(path) do |dmg|
|
|
|
|
xml_str = `hdiutil mount -plist -nobrowse -readonly -noidme -mountrandom /tmp '#{dmg.path}'`
|
|
|
|
hdiutil_info = Plist::parse_xml(xml_str)
|
|
|
|
raise Exception.new("No disk entities returned by mount at #{dmg.path}") unless hdiutil_info.has_key?("system-entities")
|
|
|
|
mounts = hdiutil_info["system-entities"].collect { |entity|
|
|
|
|
entity["mount-point"]
|
|
|
|
}.compact
|
|
|
|
begin
|
|
|
|
mounts.each do |mountpoint|
|
|
|
|
yield Pathname.new(mountpoint)
|
|
|
|
end
|
|
|
|
ensure
|
|
|
|
mounts.each do |mountpoint|
|
|
|
|
`hdiutil eject '#{mountpoint}'`
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elsif _zip?(path)
|
|
|
|
destdir = "/tmp/brewcask_#{@title}_extracted"
|
|
|
|
`mkdir -p '#{destdir}'`
|
|
|
|
`unzip -d '#{destdir}' '#{path}'`
|
|
|
|
begin
|
|
|
|
yield destdir
|
|
|
|
ensure
|
|
|
|
`rm -rf '#{destdir}'`
|
|
|
|
end
|
|
|
|
elsif _tar_bzip?(path)
|
|
|
|
destdir = "/tmp/brewcask_#{@title}_extracted"
|
|
|
|
`mkdir -p '#{destdir}'`
|
|
|
|
`tar jxf '#{path}' -C '#{destdir}'`
|
|
|
|
begin
|
|
|
|
yield destdir
|
|
|
|
ensure
|
|
|
|
`rm -rf '#{destdir}'`
|
|
|
|
end
|
|
|
|
else
|
|
|
|
raise "uh oh, could not identify type of #{path}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def _dmg?(path)
|
|
|
|
output = `hdiutil imageinfo '#{path}' 2>/dev/null`
|
|
|
|
output != ''
|
|
|
|
end
|
|
|
|
|
|
|
|
def _zip?(path)
|
|
|
|
output = `file -Izb '#{path}'`
|
|
|
|
output.chomp.include? 'compressed-encoding=application/zip; charset=binary; charset=binary'
|
|
|
|
end
|
|
|
|
|
|
|
|
def _tar_bzip?(path)
|
|
|
|
output = `file -Izb '#{path}'`
|
|
|
|
output.chomp == 'application/x-tar; charset=binary compressed-encoding=application/x-bzip2; charset=binary; charset=binary'
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_s
|
|
|
|
@title
|
|
|
|
end
|
|
|
|
end
|