Cask: Use nested classes and modules.

This commit is contained in:
Markus Reiter 2016-09-24 13:52:43 +02:00
parent 687f0fcf72
commit b86c8efb79
146 changed files with 6464 additions and 5987 deletions

View File

@ -86,7 +86,7 @@ Style/BlockDelimiters:
- proc
Style/ClassAndModuleChildren:
EnforcedStyle: compact
EnforcedStyle: nested
Style/Documentation:
Enabled: false

View File

@ -40,22 +40,22 @@ require "utils"
require "vendor/plist/plist"
module Hbc
include Hbc::Locations
include Hbc::Scopes
include Hbc::Options
include Hbc::Utils
include Locations
include Scopes
include Options
include Utils
def self.init
Hbc::Cache.ensure_cache_exists
Hbc::Cache.migrate_legacy_cache
Cache.ensure_cache_exists
Cache.migrate_legacy_cache
Hbc::Caskroom.migrate_caskroom_from_repo_to_prefix
Hbc::Caskroom.ensure_caskroom_exists
Caskroom.migrate_caskroom_from_repo_to_prefix
Caskroom.ensure_caskroom_exists
end
def self.load(query)
odebug "Loading Cask definitions"
cask = Hbc::Source.for_query(query).load
cask = Source.for_query(query).load
cask.dumpcask
cask
end

View File

@ -1,5 +1,3 @@
module Hbc::Artifact; end
require "hbc/artifact/app"
require "hbc/artifact/artifact" # generic 'artifact' stanza
require "hbc/artifact/binary"
@ -24,42 +22,44 @@ require "hbc/artifact/suite"
require "hbc/artifact/uninstall"
require "hbc/artifact/zap"
module Hbc::Artifact
# NOTE: order is important here, since we want to extract nested containers
# before we handle any other artifacts
def self.artifacts
[
Hbc::Artifact::PreflightBlock,
Hbc::Artifact::NestedContainer,
Hbc::Artifact::Installer,
Hbc::Artifact::App,
Hbc::Artifact::Suite,
Hbc::Artifact::Artifact, # generic 'artifact' stanza
Hbc::Artifact::Colorpicker,
Hbc::Artifact::Pkg,
Hbc::Artifact::Prefpane,
Hbc::Artifact::Qlplugin,
Hbc::Artifact::Font,
Hbc::Artifact::Service,
Hbc::Artifact::StageOnly,
Hbc::Artifact::Binary,
Hbc::Artifact::InputMethod,
Hbc::Artifact::InternetPlugin,
Hbc::Artifact::AudioUnitPlugin,
Hbc::Artifact::VstPlugin,
Hbc::Artifact::Vst3Plugin,
Hbc::Artifact::ScreenSaver,
Hbc::Artifact::Uninstall,
Hbc::Artifact::PostflightBlock,
Hbc::Artifact::Zap,
]
end
module Hbc
module Artifact
# NOTE: order is important here, since we want to extract nested containers
# before we handle any other artifacts
def self.artifacts
[
PreflightBlock,
NestedContainer,
Installer,
App,
Suite,
Artifact, # generic 'artifact' stanza
Colorpicker,
Pkg,
Prefpane,
Qlplugin,
Font,
Service,
StageOnly,
Binary,
InputMethod,
InternetPlugin,
AudioUnitPlugin,
VstPlugin,
Vst3Plugin,
ScreenSaver,
Uninstall,
PostflightBlock,
Zap,
]
end
def self.for_cask(cask)
odebug "Determining which artifacts are present in Cask #{cask}"
artifacts.select do |artifact|
odebug "Checking for artifact class #{artifact}"
artifact.me?(cask)
def self.for_cask(cask)
odebug "Determining which artifacts are present in Cask #{cask}"
artifacts.select do |artifact|
odebug "Checking for artifact class #{artifact}"
artifact.me?(cask)
end
end
end
end

View File

@ -1,36 +1,40 @@
require "hbc/artifact/base"
class Hbc::Artifact::AbstractFlightBlock < Hbc::Artifact::Base
def self.artifact_dsl_key
super.to_s.sub(%r{_block$}, "").to_sym
end
module Hbc
module Artifact
class AbstractFlightBlock < Base
def self.artifact_dsl_key
super.to_s.sub(%r{_block$}, "").to_sym
end
def self.uninstall_artifact_dsl_key
artifact_dsl_key.to_s.prepend("uninstall_").to_sym
end
def self.uninstall_artifact_dsl_key
artifact_dsl_key.to_s.prepend("uninstall_").to_sym
end
def self.class_for_dsl_key(dsl_key)
Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}")
end
def self.class_for_dsl_key(dsl_key)
Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}")
end
def self.me?(cask)
cask.artifacts[artifact_dsl_key].any? ||
cask.artifacts[uninstall_artifact_dsl_key].any?
end
def self.me?(cask)
cask.artifacts[artifact_dsl_key].any? ||
cask.artifacts[uninstall_artifact_dsl_key].any?
end
def install_phase
abstract_phase(self.class.artifact_dsl_key)
end
def install_phase
abstract_phase(self.class.artifact_dsl_key)
end
def uninstall_phase
abstract_phase(self.class.uninstall_artifact_dsl_key)
end
def uninstall_phase
abstract_phase(self.class.uninstall_artifact_dsl_key)
end
private
private
def abstract_phase(dsl_key)
@cask.artifacts[dsl_key].each do |block|
self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block)
def abstract_phase(dsl_key)
@cask.artifacts[dsl_key].each do |block|
self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block)
end
end
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::App < Hbc::Artifact::Moved
module Hbc
module Artifact
class App < Moved
end
end
end

View File

@ -1,20 +1,24 @@
require "hbc/artifact/moved"
class Hbc::Artifact::Artifact < Hbc::Artifact::Moved
def self.artifact_english_name
"Generic Artifact"
end
module Hbc
module Artifact
class Artifact < Moved
def self.artifact_english_name
"Generic Artifact"
end
def self.artifact_dirmethod
:appdir
end
def self.artifact_dirmethod
:appdir
end
def load_specification(artifact_spec)
source_string, target_hash = artifact_spec
raise Hbc::CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil?
@source = @cask.staged_path.join(source_string)
raise Hbc::CaskInvalidError.new(@cask.token, "target required for generic artifact #{source_string}") unless target_hash.is_a?(Hash)
target_hash.assert_valid_keys(:target)
@target = Pathname.new(target_hash[:target])
def load_specification(artifact_spec)
source_string, target_hash = artifact_spec
raise CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil?
@source = @cask.staged_path.join(source_string)
raise CaskInvalidError.new(@cask.token, "target required for generic artifact #{source_string}") unless target_hash.is_a?(Hash)
target_hash.assert_valid_keys(:target)
@target = Pathname.new(target_hash[:target])
end
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::AudioUnitPlugin < Hbc::Artifact::Moved
module Hbc
module Artifact
class AudioUnitPlugin < Moved
end
end
end

View File

@ -1,79 +1,83 @@
class Hbc::Artifact::Base
def self.artifact_name
@artifact_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase
end
module Hbc
module Artifact
class Base
def self.artifact_name
@artifact_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase
end
def self.artifact_english_name
@artifact_english_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1 \2')
end
def self.artifact_english_name
@artifact_english_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1 \2')
end
def self.artifact_english_article
@artifact_english_article ||= artifact_english_name =~ %r{^[aeiou]}i ? "an" : "a"
end
def self.artifact_english_article
@artifact_english_article ||= artifact_english_name =~ %r{^[aeiou]}i ? "an" : "a"
end
def self.artifact_dsl_key
@artifact_dsl_key ||= artifact_name.to_sym
end
def self.artifact_dsl_key
@artifact_dsl_key ||= artifact_name.to_sym
end
def self.artifact_dirmethod
@artifact_dirmethod ||= "#{artifact_name}dir".to_sym
end
def self.artifact_dirmethod
@artifact_dirmethod ||= "#{artifact_name}dir".to_sym
end
def self.me?(cask)
cask.artifacts[artifact_dsl_key].any?
end
def self.me?(cask)
cask.artifacts[artifact_dsl_key].any?
end
attr_reader :force
attr_reader :force
def zap_phase
odebug "Nothing to do. The #{self.class.artifact_name} artifact has no zap phase."
end
def zap_phase
odebug "Nothing to do. The #{self.class.artifact_name} artifact has no zap phase."
end
# TODO: this sort of logic would make more sense in dsl.rb, or a
# constructor called from dsl.rb, so long as that isn't slow.
def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil)
# TODO: when stanza names are harmonized with class names,
# stanza may not be needed as an explicit argument
description = stanza.to_s
if key
arguments = arguments[key]
description.concat(" #{key.inspect}")
# TODO: this sort of logic would make more sense in dsl.rb, or a
# constructor called from dsl.rb, so long as that isn't slow.
def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil)
# TODO: when stanza names are harmonized with class names,
# stanza may not be needed as an explicit argument
description = stanza.to_s
if key
arguments = arguments[key]
description.concat(" #{key.inspect}")
end
# backward-compatible string value
arguments = { executable: arguments } if arguments.is_a?(String)
# key sanity
permitted_keys = [:args, :input, :executable, :must_succeed, :sudo, :bsexec, :print_stdout, :print_stderr]
unknown_keys = arguments.keys - permitted_keys
unless unknown_keys.empty?
opoo %Q{Unknown arguments to #{description} -- #{unknown_keys.inspect} (ignored). Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.}
end
arguments.reject! { |k| !permitted_keys.include?(k) }
# key warnings
override_keys = override_arguments.keys
ignored_keys = arguments.keys & override_keys
unless ignored_keys.empty?
onoe "Some arguments to #{description} will be ignored -- :#{unknown_keys.inspect} (overridden)."
end
# extract executable
executable = arguments.key?(:executable) ? arguments.delete(:executable) : nil
arguments = default_arguments.merge arguments
arguments.merge! override_arguments
[executable, arguments]
end
def summary
{}
end
def initialize(cask, command: SystemCommand, force: false)
@cask = cask
@command = command
@force = force
end
end
# backward-compatible string value
arguments = { executable: arguments } if arguments.is_a?(String)
# key sanity
permitted_keys = [:args, :input, :executable, :must_succeed, :sudo, :bsexec, :print_stdout, :print_stderr]
unknown_keys = arguments.keys - permitted_keys
unless unknown_keys.empty?
opoo %Q{Unknown arguments to #{description} -- #{unknown_keys.inspect} (ignored). Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.}
end
arguments.reject! { |k| !permitted_keys.include?(k) }
# key warnings
override_keys = override_arguments.keys
ignored_keys = arguments.keys & override_keys
unless ignored_keys.empty?
onoe "Some arguments to #{description} will be ignored -- :#{unknown_keys.inspect} (overridden)."
end
# extract executable
executable = arguments.key?(:executable) ? arguments.delete(:executable) : nil
arguments = default_arguments.merge arguments
arguments.merge! override_arguments
[executable, arguments]
end
def summary
{}
end
def initialize(cask, command: Hbc::SystemCommand, force: false)
@cask = cask
@command = command
@force = force
end
end

View File

@ -1,7 +1,11 @@
require "hbc/artifact/symlinked"
class Hbc::Artifact::Binary < Hbc::Artifact::Symlinked
def install_phase
super unless Hbc.no_binaries
module Hbc
module Artifact
class Binary < Symlinked
def install_phase
super unless Hbc.no_binaries
end
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::Colorpicker < Hbc::Artifact::Moved
module Hbc
module Artifact
class Colorpicker < Moved
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::Font < Hbc::Artifact::Moved
module Hbc
module Artifact
class Font < Moved
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::InputMethod < Hbc::Artifact::Moved
module Hbc
module Artifact
class InputMethod < Moved
end
end
end

View File

@ -1,41 +1,45 @@
require "hbc/artifact/base"
class Hbc::Artifact::Installer < Hbc::Artifact::Base
# TODO: for backward compatibility, removeme
def install
install_phase
end
module Hbc
module Artifact
class Installer < Base
# TODO: for backward compatibility, removeme
def install
install_phase
end
# TODO: for backward compatibility, removeme
def uninstall
uninstall_phase
end
# TODO: for backward compatibility, removeme
def uninstall
uninstall_phase
end
def install_phase
@cask.artifacts[self.class.artifact_dsl_key].each do |artifact|
if artifact.manual
puts <<-EOS.undent
To complete the installation of Cask #{@cask}, you must also
run the installer at
def install_phase
@cask.artifacts[self.class.artifact_dsl_key].each do |artifact|
if artifact.manual
puts <<-EOS.undent
To complete the installation of Cask #{@cask}, you must also
run the installer at
'#{@cask.staged_path.join(artifact.manual)}'
'#{@cask.staged_path.join(artifact.manual)}'
EOS
else
executable, script_arguments = self.class.read_script_arguments(artifact.script,
self.class.artifact_dsl_key.to_s,
{ must_succeed: true, sudo: true },
print_stdout: true)
ohai "Running #{self.class.artifact_dsl_key} script #{executable}"
raise Hbc::CaskInvalidError.new(@cask, "#{self.class.artifact_dsl_key} missing executable") if executable.nil?
executable_path = @cask.staged_path.join(executable)
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path)
@command.run(executable_path, script_arguments)
EOS
else
executable, script_arguments = self.class.read_script_arguments(artifact.script,
self.class.artifact_dsl_key.to_s,
{ must_succeed: true, sudo: true },
print_stdout: true)
ohai "Running #{self.class.artifact_dsl_key} script #{executable}"
raise CaskInvalidError.new(@cask, "#{self.class.artifact_dsl_key} missing executable") if executable.nil?
executable_path = @cask.staged_path.join(executable)
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path)
@command.run(executable_path, script_arguments)
end
end
end
def uninstall_phase
odebug "Nothing to do. The #{self.class.artifact_dsl_key} artifact has no uninstall phase."
end
end
end
def uninstall_phase
odebug "Nothing to do. The #{self.class.artifact_dsl_key} artifact has no uninstall phase."
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::InternetPlugin < Hbc::Artifact::Moved
module Hbc
module Artifact
class InternetPlugin < Moved
end
end
end

View File

@ -1,88 +1,92 @@
require "hbc/artifact/relocated"
class Hbc::Artifact::Moved < Hbc::Artifact::Relocated
def self.english_description
"#{artifact_english_name}s"
end
module Hbc
module Artifact
class Moved < Relocated
def self.english_description
"#{artifact_english_name}s"
end
def install_phase
each_artifact do |artifact|
load_specification(artifact)
next unless preflight_checks
delete if Hbc::Utils.path_occupied?(target) && force
move
end
end
def install_phase
each_artifact do |artifact|
load_specification(artifact)
next unless preflight_checks
delete if Utils.path_occupied?(target) && force
move
end
end
def uninstall_phase
each_artifact do |artifact|
load_specification(artifact)
next unless File.exist?(target)
delete
end
end
def uninstall_phase
each_artifact do |artifact|
load_specification(artifact)
next unless File.exist?(target)
delete
end
end
private
private
def each_artifact
# the sort is for predictability between Ruby versions
@cask.artifacts[self.class.artifact_dsl_key].sort.each do |artifact|
yield artifact
end
end
def each_artifact
# the sort is for predictability between Ruby versions
@cask.artifacts[self.class.artifact_dsl_key].sort.each do |artifact|
yield artifact
end
end
def move
ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'"
target.dirname.mkpath
FileUtils.move(source, target)
add_altname_metadata target, source.basename.to_s
end
def move
ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'"
target.dirname.mkpath
FileUtils.move(source, target)
add_altname_metadata target, source.basename.to_s
end
def preflight_checks
if Hbc::Utils.path_occupied?(target)
if force
ohai(warning_target_exists { |s| s << "overwriting." })
else
ohai(warning_target_exists { |s| s << "not moving." })
return false
def preflight_checks
if Utils.path_occupied?(target)
if force
ohai(warning_target_exists { |s| s << "overwriting." })
else
ohai(warning_target_exists { |s| s << "not moving." })
return false
end
end
unless source.exist?
message = "It seems the #{self.class.artifact_english_name} source is not there: '#{source}'"
raise CaskError, message
end
true
end
def warning_target_exists
message_parts = [
"It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'",
]
yield(message_parts) if block_given?
message_parts.join("; ")
end
def delete
ohai "Removing #{self.class.artifact_english_name}: '#{target}'"
raise CaskError, "Cannot remove undeletable #{self.class.artifact_english_name}" if MacOS.undeletable?(target)
if force
Utils.gain_permissions_remove(target, command: @command)
else
target.rmtree
end
end
def summarize_artifact(artifact_spec)
load_specification artifact_spec
if target.exist?
target_abv = " (#{target.abv})"
else
warning = "Missing #{self.class.artifact_english_name}"
warning = "#{Tty.red}#{warning}#{Tty.reset}: "
end
"#{warning}#{printable_target}#{target_abv}"
end
end
unless source.exist?
message = "It seems the #{self.class.artifact_english_name} source is not there: '#{source}'"
raise Hbc::CaskError, message
end
true
end
def warning_target_exists
message_parts = [
"It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'",
]
yield(message_parts) if block_given?
message_parts.join("; ")
end
def delete
ohai "Removing #{self.class.artifact_english_name}: '#{target}'"
raise Hbc::CaskError, "Cannot remove undeletable #{self.class.artifact_english_name}" if MacOS.undeletable?(target)
if force
Hbc::Utils.gain_permissions_remove(target, command: @command)
else
target.rmtree
end
end
def summarize_artifact(artifact_spec)
load_specification artifact_spec
if target.exist?
target_abv = " (#{target.abv})"
else
warning = "Missing #{self.class.artifact_english_name}"
warning = "#{Tty.red}#{warning}#{Tty.reset}: "
end
"#{warning}#{printable_target}#{target_abv}"
end
end

View File

@ -1,24 +1,28 @@
require "hbc/artifact/base"
class Hbc::Artifact::NestedContainer < Hbc::Artifact::Base
def install_phase
@cask.artifacts[:nested_container].each { |container| extract(container) }
end
module Hbc
module Artifact
class NestedContainer < Base
def install_phase
@cask.artifacts[:nested_container].each { |container| extract(container) }
end
def uninstall_phase
# no need to take action; is removed after extraction
end
def uninstall_phase
# no need to take action; is removed after extraction
end
def extract(container_relative_path)
source = @cask.staged_path.join(container_relative_path)
container = Hbc::Container.for_path(source, @command)
def extract(container_relative_path)
source = @cask.staged_path.join(container_relative_path)
container = Container.for_path(source, @command)
unless container
raise Hbc::CaskError, "Aw dang, could not identify nested container at '#{source}'"
unless container
raise CaskError, "Aw dang, could not identify nested container at '#{source}'"
end
ohai "Extracting nested container #{source.basename}"
container.new(@cask, source, @command).extract
FileUtils.remove_entry_secure(source)
end
end
ohai "Extracting nested container #{source.basename}"
container.new(@cask, source, @command).extract
FileUtils.remove_entry_secure(source)
end
end

View File

@ -1,53 +1,57 @@
require "hbc/artifact/base"
class Hbc::Artifact::Pkg < Hbc::Artifact::Base
attr_reader :pkg_relative_path
module Hbc
module Artifact
class Pkg < Base
attr_reader :pkg_relative_path
def self.artifact_dsl_key
:pkg
end
def load_pkg_description(pkg_description)
@pkg_relative_path = pkg_description.shift
@pkg_install_opts = pkg_description.shift
begin
if @pkg_install_opts.respond_to?(:keys)
@pkg_install_opts.assert_valid_keys(:allow_untrusted)
elsif @pkg_install_opts
raise
def self.artifact_dsl_key
:pkg
end
def load_pkg_description(pkg_description)
@pkg_relative_path = pkg_description.shift
@pkg_install_opts = pkg_description.shift
begin
if @pkg_install_opts.respond_to?(:keys)
@pkg_install_opts.assert_valid_keys(:allow_untrusted)
elsif @pkg_install_opts
raise
end
raise if pkg_description.nil?
rescue StandardError
raise CaskInvalidError.new(@cask, "Bad pkg stanza")
end
end
def pkg_install_opts(opt)
@pkg_install_opts[opt] if @pkg_install_opts.respond_to?(:keys)
end
def install_phase
@cask.artifacts[:pkg].each { |pkg_description| run_installer(pkg_description) }
end
def uninstall_phase
# Do nothing. Must be handled explicitly by a separate :uninstall stanza.
end
def run_installer(pkg_description)
load_pkg_description pkg_description
ohai "Running installer for #{@cask}; your password may be necessary."
ohai "Package installers may write to any location; options such as --appdir are ignored."
source = @cask.staged_path.join(pkg_relative_path)
unless source.exist?
raise CaskError, "pkg source file not found: '#{source}'"
end
args = [
"-pkg", source,
"-target", "/"
]
args << "-verboseR" if Hbc.verbose
args << "-allowUntrusted" if pkg_install_opts :allow_untrusted
@command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true)
end
raise if pkg_description.nil?
rescue StandardError
raise Hbc::CaskInvalidError.new(@cask, "Bad pkg stanza")
end
end
def pkg_install_opts(opt)
@pkg_install_opts[opt] if @pkg_install_opts.respond_to?(:keys)
end
def install_phase
@cask.artifacts[:pkg].each { |pkg_description| run_installer(pkg_description) }
end
def uninstall_phase
# Do nothing. Must be handled explicitly by a separate :uninstall stanza.
end
def run_installer(pkg_description)
load_pkg_description pkg_description
ohai "Running installer for #{@cask}; your password may be necessary."
ohai "Package installers may write to any location; options such as --appdir are ignored."
source = @cask.staged_path.join(pkg_relative_path)
unless source.exist?
raise Hbc::CaskError, "pkg source file not found: '#{source}'"
end
args = [
"-pkg", source,
"-target", "/"
]
args << "-verboseR" if Hbc.verbose
args << "-allowUntrusted" if pkg_install_opts :allow_untrusted
@command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true)
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/abstract_flight_block"
class Hbc::Artifact::PostflightBlock < Hbc::Artifact::AbstractFlightBlock
module Hbc
module Artifact
class PostflightBlock < AbstractFlightBlock
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/abstract_flight_block"
class Hbc::Artifact::PreflightBlock < Hbc::Artifact::AbstractFlightBlock
module Hbc
module Artifact
class PreflightBlock < AbstractFlightBlock
end
end
end

View File

@ -1,7 +1,11 @@
require "hbc/artifact/moved"
class Hbc::Artifact::Prefpane < Hbc::Artifact::Moved
def self.artifact_english_name
"Preference Pane"
module Hbc
module Artifact
class Prefpane < Moved
def self.artifact_english_name
"Preference Pane"
end
end
end
end

View File

@ -1,21 +1,25 @@
require "hbc/artifact/moved"
class Hbc::Artifact::Qlplugin < Hbc::Artifact::Moved
def self.artifact_english_name
"QuickLook Plugin"
end
module Hbc
module Artifact
class Qlplugin < Moved
def self.artifact_english_name
"QuickLook Plugin"
end
def install_phase
super
reload_quicklook
end
def install_phase
super
reload_quicklook
end
def uninstall_phase
super
reload_quicklook
end
def uninstall_phase
super
reload_quicklook
end
def reload_quicklook
@command.run!("/usr/bin/qlmanage", args: ["-r"])
def reload_quicklook
@command.run!("/usr/bin/qlmanage", args: ["-r"])
end
end
end
end

View File

@ -1,53 +1,57 @@
require "hbc/artifact/base"
class Hbc::Artifact::Relocated < Hbc::Artifact::Base
def summary
{
english_description: self.class.english_description,
contents: @cask.artifacts[self.class.artifact_dsl_key].map(&method(:summarize_artifact)).compact,
}
end
module Hbc
module Artifact
class Relocated < Base
def summary
{
english_description: self.class.english_description,
contents: @cask.artifacts[self.class.artifact_dsl_key].map(&method(:summarize_artifact)).compact,
}
end
attr_reader :source, :target
attr_reader :source, :target
def printable_target
target.to_s.sub(%r{^#{ENV['HOME']}(#{File::SEPARATOR}|$)}, "~/")
end
def printable_target
target.to_s.sub(%r{^#{ENV['HOME']}(#{File::SEPARATOR}|$)}, "~/")
end
ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames".freeze
ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames".freeze
# Try to make the asset searchable under the target name. Spotlight
# respects this attribute for many filetypes, but ignores it for App
# bundles. Alfred 2.2 respects it even for App bundles.
def add_altname_metadata(file, altname)
return if altname.casecmp(file.basename).zero?
odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata"
altnames = @command.run("/usr/bin/xattr",
args: ["-p", ALT_NAME_ATTRIBUTE, file.to_s],
print_stderr: false).stdout.sub(%r{\A\((.*)\)\Z}, '\1')
odebug "Existing metadata is: '#{altnames}'"
altnames.concat(", ") unless altnames.empty?
altnames.concat(%Q{"#{altname}"})
altnames = "(#{altnames})"
# Try to make the asset searchable under the target name. Spotlight
# respects this attribute for many filetypes, but ignores it for App
# bundles. Alfred 2.2 respects it even for App bundles.
def add_altname_metadata(file, altname)
return if altname.casecmp(file.basename).zero?
odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata"
altnames = @command.run("/usr/bin/xattr",
args: ["-p", ALT_NAME_ATTRIBUTE, file.to_s],
print_stderr: false).stdout.sub(%r{\A\((.*)\)\Z}, '\1')
odebug "Existing metadata is: '#{altnames}'"
altnames.concat(", ") unless altnames.empty?
altnames.concat(%Q{"#{altname}"})
altnames = "(#{altnames})"
# Some packges are shipped as u=rx (e.g. Bitcoin Core)
@command.run!("/bin/chmod", args: ["--", "u=rwx", file.to_s, file.realpath.to_s])
# Some packges are shipped as u=rx (e.g. Bitcoin Core)
@command.run!("/bin/chmod", args: ["--", "u=rwx", file.to_s, file.realpath.to_s])
@command.run!("/usr/bin/xattr",
args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file.to_s],
print_stderr: false)
end
@command.run!("/usr/bin/xattr",
args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file.to_s],
print_stderr: false)
end
def load_specification(artifact_spec)
source_string, target_hash = artifact_spec
raise Hbc::CaskInvalidError if source_string.nil?
@source = @cask.staged_path.join(source_string)
if target_hash
raise Hbc::CaskInvalidError unless target_hash.respond_to?(:keys)
target_hash.assert_valid_keys(:target)
@target = Hbc.send(self.class.artifact_dirmethod).join(target_hash[:target])
else
@target = Hbc.send(self.class.artifact_dirmethod).join(source.basename)
def load_specification(artifact_spec)
source_string, target_hash = artifact_spec
raise CaskInvalidError if source_string.nil?
@source = @cask.staged_path.join(source_string)
if target_hash
raise CaskInvalidError unless target_hash.respond_to?(:keys)
target_hash.assert_valid_keys(:target)
@target = Hbc.send(self.class.artifact_dirmethod).join(target_hash[:target])
else
@target = Hbc.send(self.class.artifact_dirmethod).join(source.basename)
end
end
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::ScreenSaver < Hbc::Artifact::Moved
module Hbc
module Artifact
class ScreenSaver < Moved
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::Service < Hbc::Artifact::Moved
module Hbc
module Artifact
class Service < Moved
end
end
end

View File

@ -1,15 +1,19 @@
require "hbc/artifact/base"
class Hbc::Artifact::StageOnly < Hbc::Artifact::Base
def self.artifact_dsl_key
:stage_only
end
module Hbc
module Artifact
class StageOnly < Base
def self.artifact_dsl_key
:stage_only
end
def install_phase
# do nothing
end
def install_phase
# do nothing
end
def uninstall_phase
# do nothing
def uninstall_phase
# do nothing
end
end
end
end

View File

@ -1,11 +1,15 @@
require "hbc/artifact/moved"
class Hbc::Artifact::Suite < Hbc::Artifact::Moved
def self.artifact_english_name
"App Suite"
end
module Hbc
module Artifact
class Suite < Moved
def self.artifact_english_name
"App Suite"
end
def self.artifact_dirmethod
:appdir
def self.artifact_dirmethod
:appdir
end
end
end
end

View File

@ -1,65 +1,69 @@
require "hbc/artifact/relocated"
class Hbc::Artifact::Symlinked < Hbc::Artifact::Relocated
def self.link_type_english_name
"Symlink"
end
module Hbc
module Artifact
class Symlinked < Relocated
def self.link_type_english_name
"Symlink"
end
def self.english_description
"#{artifact_english_name} #{link_type_english_name}s"
end
def self.english_description
"#{artifact_english_name} #{link_type_english_name}s"
end
def self.islink?(path)
path.symlink?
end
def self.islink?(path)
path.symlink?
end
def link(artifact_spec)
load_specification artifact_spec
return unless preflight_checks(source, target)
ohai "#{self.class.link_type_english_name}ing #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'"
create_filesystem_link(source, target)
end
def link(artifact_spec)
load_specification artifact_spec
return unless preflight_checks(source, target)
ohai "#{self.class.link_type_english_name}ing #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'"
create_filesystem_link(source, target)
end
def unlink(artifact_spec)
load_specification artifact_spec
return unless self.class.islink?(target)
ohai "Removing #{self.class.artifact_english_name} #{self.class.link_type_english_name.downcase}: '#{target}'"
target.delete
end
def unlink(artifact_spec)
load_specification artifact_spec
return unless self.class.islink?(target)
ohai "Removing #{self.class.artifact_english_name} #{self.class.link_type_english_name.downcase}: '#{target}'"
target.delete
end
def install_phase
@cask.artifacts[self.class.artifact_dsl_key].each(&method(:link))
end
def install_phase
@cask.artifacts[self.class.artifact_dsl_key].each(&method(:link))
end
def uninstall_phase
@cask.artifacts[self.class.artifact_dsl_key].each(&method(:unlink))
end
def uninstall_phase
@cask.artifacts[self.class.artifact_dsl_key].each(&method(:unlink))
end
def preflight_checks(source, target)
if target.exist? && !self.class.islink?(target)
ohai "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'; not linking."
return false
def preflight_checks(source, target)
if target.exist? && !self.class.islink?(target)
ohai "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'; not linking."
return false
end
unless source.exist?
raise CaskError, "It seems the #{self.class.link_type_english_name.downcase} source is not there: '#{source}'"
end
true
end
def create_filesystem_link(source, target)
Pathname.new(target).dirname.mkpath
@command.run!("/bin/ln", args: ["-hfs", "--", source, target])
add_altname_metadata source, target.basename.to_s
end
def summarize_artifact(artifact_spec)
load_specification artifact_spec
return unless self.class.islink?(target)
link_description = "#{Tty.red}Broken Link#{Tty.reset}: " unless target.exist?
target_readlink_abv = " (#{target.readlink.abv})" if target.readlink.exist?
"#{link_description}#{printable_target} -> #{target.readlink}#{target_readlink_abv}"
end
end
unless source.exist?
raise Hbc::CaskError, "It seems the #{self.class.link_type_english_name.downcase} source is not there: '#{source}'"
end
true
end
def create_filesystem_link(source, target)
Pathname.new(target).dirname.mkpath
@command.run!("/bin/ln", args: ["-hfs", "--", source, target])
add_altname_metadata source, target.basename.to_s
end
def summarize_artifact(artifact_spec)
load_specification artifact_spec
return unless self.class.islink?(target)
link_description = "#{Tty.red}Broken Link#{Tty.reset}: " unless target.exist?
target_readlink_abv = " (#{target.readlink.abv})" if target.readlink.exist?
"#{link_description}#{printable_target} -> #{target.readlink}#{target_readlink_abv}"
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/uninstall_base"
class Hbc::Artifact::Uninstall < Hbc::Artifact::UninstallBase
module Hbc
module Artifact
class Uninstall < UninstallBase
end
end
end

View File

@ -2,248 +2,252 @@ require "pathname"
require "hbc/artifact/base"
class Hbc::Artifact::UninstallBase < Hbc::Artifact::Base
# TODO: 500 is also hardcoded in cask/pkg.rb, but much of
# that logic is probably in the wrong location
module Hbc
module Artifact
class UninstallBase < Base
# TODO: 500 is also hardcoded in cask/pkg.rb, but much of
# that logic is probably in the wrong location
PATH_ARG_SLICE_SIZE = 500
PATH_ARG_SLICE_SIZE = 500
ORDERED_DIRECTIVES = [
:early_script,
:launchctl,
:quit,
:signal,
:login_item,
:kext,
:script,
:pkgutil,
:delete,
:trash,
:rmdir,
].freeze
ORDERED_DIRECTIVES = [
:early_script,
:launchctl,
:quit,
:signal,
:login_item,
:kext,
:script,
:pkgutil,
:delete,
:trash,
:rmdir,
].freeze
# TODO: these methods were consolidated here from separate
# sources and should be refactored for consistency
# TODO: these methods were consolidated here from separate
# sources and should be refactored for consistency
def self.expand_path_strings(path_strings)
path_strings.map { |path_string|
path_string.start_with?("~") ? Pathname.new(path_string).expand_path : Pathname.new(path_string)
}
end
def self.remove_relative_path_strings(action, path_strings)
relative = path_strings.map { |path_string|
path_string if %r{/\.\.(?:/|\Z)}.match(path_string) || !%r{\A/}.match(path_string)
}.compact
relative.each do |path_string|
opoo "Skipping #{action} for relative path #{path_string}"
end
path_strings - relative
end
def self.remove_undeletable_path_strings(action, path_strings)
undeletable = path_strings.map { |path_string|
path_string if MacOS.undeletable?(Pathname.new(path_string))
}.compact
undeletable.each do |path_string|
opoo "Skipping #{action} for undeletable path #{path_string}"
end
path_strings - undeletable
end
def install_phase
odebug "Nothing to do. The uninstall artifact has no install phase."
end
def uninstall_phase
dispatch_uninstall_directives
end
def dispatch_uninstall_directives(expand_tilde = true)
directives_set = @cask.artifacts[stanza]
ohai "Running #{stanza} process for #{@cask}; your password may be necessary"
directives_set.each do |directives|
warn_for_unknown_directives(directives)
end
ORDERED_DIRECTIVES.each do |directive_sym|
directives_set.select { |h| h.key?(directive_sym) }.each do |directives|
args = [directives]
args << expand_tilde if [:delete, :trash, :rmdir].include?(directive_sym)
send("uninstall_#{directive_sym}", *args)
def self.expand_path_strings(path_strings)
path_strings.map { |path_string|
path_string.start_with?("~") ? Pathname.new(path_string).expand_path : Pathname.new(path_string)
}
end
end
end
private
def self.remove_relative_path_strings(action, path_strings)
relative = path_strings.map { |path_string|
path_string if %r{/\.\.(?:/|\Z)}.match(path_string) || !%r{\A/}.match(path_string)
}.compact
relative.each do |path_string|
opoo "Skipping #{action} for relative path #{path_string}"
end
path_strings - relative
end
def stanza
self.class.artifact_dsl_key
end
def self.remove_undeletable_path_strings(action, path_strings)
undeletable = path_strings.map { |path_string|
path_string if MacOS.undeletable?(Pathname.new(path_string))
}.compact
undeletable.each do |path_string|
opoo "Skipping #{action} for undeletable path #{path_string}"
end
path_strings - undeletable
end
def warn_for_unknown_directives(directives)
unknown_keys = directives.keys - ORDERED_DIRECTIVES
return if unknown_keys.empty?
opoo %Q{Unknown arguments to #{stanza} -- #{unknown_keys.inspect}. Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.}
end
def install_phase
odebug "Nothing to do. The uninstall artifact has no install phase."
end
# Preserve prior functionality of script which runs first. Should rarely be needed.
# :early_script should not delete files, better defer that to :script.
# If Cask writers never need :early_script it may be removed in the future.
def uninstall_early_script(directives)
uninstall_script(directives, directive_name: :early_script)
end
def uninstall_phase
dispatch_uninstall_directives
end
# :launchctl must come before :quit/:signal for cases where app would instantly re-launch
def uninstall_launchctl(directives)
Array(directives[:launchctl]).each do |service|
ohai "Removing launchctl service #{service}"
[false, true].each do |with_sudo|
plist_status = @command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout
if plist_status =~ %r{^\{}
@command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo)
def dispatch_uninstall_directives(expand_tilde = true)
directives_set = @cask.artifacts[stanza]
ohai "Running #{stanza} process for #{@cask}; your password may be necessary"
directives_set.each do |directives|
warn_for_unknown_directives(directives)
end
ORDERED_DIRECTIVES.each do |directive_sym|
directives_set.select { |h| h.key?(directive_sym) }.each do |directives|
args = [directives]
args << expand_tilde if [:delete, :trash, :rmdir].include?(directive_sym)
send("uninstall_#{directive_sym}", *args)
end
end
end
private
def stanza
self.class.artifact_dsl_key
end
def warn_for_unknown_directives(directives)
unknown_keys = directives.keys - ORDERED_DIRECTIVES
return if unknown_keys.empty?
opoo %Q{Unknown arguments to #{stanza} -- #{unknown_keys.inspect}. Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.}
end
# Preserve prior functionality of script which runs first. Should rarely be needed.
# :early_script should not delete files, better defer that to :script.
# If Cask writers never need :early_script it may be removed in the future.
def uninstall_early_script(directives)
uninstall_script(directives, directive_name: :early_script)
end
# :launchctl must come before :quit/:signal for cases where app would instantly re-launch
def uninstall_launchctl(directives)
Array(directives[:launchctl]).each do |service|
ohai "Removing launchctl service #{service}"
[false, true].each do |with_sudo|
plist_status = @command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout
if plist_status =~ %r{^\{}
@command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo)
sleep 1
end
paths = ["/Library/LaunchAgents/#{service}.plist",
"/Library/LaunchDaemons/#{service}.plist"]
paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo
paths = paths.map { |elt| Pathname(elt) }.select(&:exist?)
paths.each do |path|
@command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo)
end
# undocumented and untested: pass a path to uninstall :launchctl
next unless Pathname(service).exist?
@command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo)
@command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo)
sleep 1
end
end
end
# :quit/:signal must come before :kext so the kext will not be in use by a running process
def uninstall_quit(directives)
Array(directives[:quit]).each do |id|
ohai "Quitting application ID #{id}"
num_running = count_running_processes(id)
next unless num_running > 0
@command.run!("/usr/bin/osascript", args: ["-e", %Q{tell application id "#{id}" to quit}], sudo: true)
sleep 3
end
end
# :signal should come after :quit so it can be used as a backup when :quit fails
def uninstall_signal(directives)
Array(directives[:signal]).flatten.each_slice(2) do |pair|
raise CaskInvalidError.new(@cask, "Each #{stanza} :signal must have 2 elements.") unless pair.length == 2
signal, id = pair
ohai "Signalling '#{signal}' to application ID '#{id}'"
pids = get_unix_pids(id)
next unless pids.any?
# Note that unlike :quit, signals are sent from the current user (not
# upgraded to the superuser). This is a todo item for the future, but
# there should be some additional thought/safety checks about that, as a
# misapplied "kill" by root could bring down the system. The fact that we
# learned the pid from AppleScript is already some degree of protection,
# though indirect.
odebug "Unix ids are #{pids.inspect} for processes with bundle identifier #{id}"
Process.kill(signal, *pids)
sleep 3
end
end
def count_running_processes(bundle_id)
@command.run!("/usr/bin/osascript",
args: ["-e", %Q{tell application "System Events" to count processes whose bundle identifier is "#{bundle_id}"}],
sudo: true).stdout.to_i
end
def get_unix_pids(bundle_id)
pid_string = @command.run!("/usr/bin/osascript",
args: ["-e", %Q{tell application "System Events" to get the unix id of every process whose bundle identifier is "#{bundle_id}"}],
sudo: true).stdout.chomp
return [] unless pid_string =~ %r{\A\d+(?:\s*,\s*\d+)*\Z} # sanity check
pid_string.split(%r{\s*,\s*}).map(&:strip).map(&:to_i)
end
def uninstall_login_item(directives)
Array(directives[:login_item]).each do |name|
ohai "Removing login item #{name}"
@command.run!("/usr/bin/osascript",
args: ["-e", %Q{tell application "System Events" to delete every login item whose name is "#{name}"}],
sudo: false)
sleep 1
end
paths = ["/Library/LaunchAgents/#{service}.plist",
"/Library/LaunchDaemons/#{service}.plist"]
paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo
paths = paths.map { |elt| Pathname(elt) }.select(&:exist?)
paths.each do |path|
@command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo)
end
# :kext should be unloaded before attempting to delete the relevant file
def uninstall_kext(directives)
Array(directives[:kext]).each do |kext|
ohai "Unloading kernel extension #{kext}"
is_loaded = @command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout
if is_loaded.length > 1
@command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true)
sleep 1
end
end
# undocumented and untested: pass a path to uninstall :launchctl
next unless Pathname(service).exist?
@command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo)
@command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo)
end
# :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted
def uninstall_script(directives, directive_name: :script)
executable, script_arguments = self.class.read_script_arguments(directives,
"uninstall",
{ must_succeed: true, sudo: true },
{ print_stdout: true },
directive_name)
ohai "Running uninstall script #{executable}"
raise CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil?
executable_path = @cask.staged_path.join(executable)
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path)
@command.run(executable_path, script_arguments)
sleep 1
end
end
end
# :quit/:signal must come before :kext so the kext will not be in use by a running process
def uninstall_quit(directives)
Array(directives[:quit]).each do |id|
ohai "Quitting application ID #{id}"
num_running = count_running_processes(id)
next unless num_running > 0
@command.run!("/usr/bin/osascript", args: ["-e", %Q{tell application id "#{id}" to quit}], sudo: true)
sleep 3
end
end
# :signal should come after :quit so it can be used as a backup when :quit fails
def uninstall_signal(directives)
Array(directives[:signal]).flatten.each_slice(2) do |pair|
raise Hbc::CaskInvalidError.new(@cask, "Each #{stanza} :signal must have 2 elements.") unless pair.length == 2
signal, id = pair
ohai "Signalling '#{signal}' to application ID '#{id}'"
pids = get_unix_pids(id)
next unless pids.any?
# Note that unlike :quit, signals are sent from the current user (not
# upgraded to the superuser). This is a todo item for the future, but
# there should be some additional thought/safety checks about that, as a
# misapplied "kill" by root could bring down the system. The fact that we
# learned the pid from AppleScript is already some degree of protection,
# though indirect.
odebug "Unix ids are #{pids.inspect} for processes with bundle identifier #{id}"
Process.kill(signal, *pids)
sleep 3
end
end
def count_running_processes(bundle_id)
@command.run!("/usr/bin/osascript",
args: ["-e", %Q{tell application "System Events" to count processes whose bundle identifier is "#{bundle_id}"}],
sudo: true).stdout.to_i
end
def get_unix_pids(bundle_id)
pid_string = @command.run!("/usr/bin/osascript",
args: ["-e", %Q{tell application "System Events" to get the unix id of every process whose bundle identifier is "#{bundle_id}"}],
sudo: true).stdout.chomp
return [] unless pid_string =~ %r{\A\d+(?:\s*,\s*\d+)*\Z} # sanity check
pid_string.split(%r{\s*,\s*}).map(&:strip).map(&:to_i)
end
def uninstall_login_item(directives)
Array(directives[:login_item]).each do |name|
ohai "Removing login item #{name}"
@command.run!("/usr/bin/osascript",
args: ["-e", %Q{tell application "System Events" to delete every login item whose name is "#{name}"}],
sudo: false)
sleep 1
end
end
# :kext should be unloaded before attempting to delete the relevant file
def uninstall_kext(directives)
Array(directives[:kext]).each do |kext|
ohai "Unloading kernel extension #{kext}"
is_loaded = @command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout
if is_loaded.length > 1
@command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true)
sleep 1
def uninstall_pkgutil(directives)
ohai "Removing files from pkgutil Bill-of-Materials"
Array(directives[:pkgutil]).each do |regexp|
pkgs = Hbc::Pkg.all_matching(regexp, @command)
pkgs.each(&:uninstall)
end
end
end
end
# :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted
def uninstall_script(directives, directive_name: :script)
executable, script_arguments = self.class.read_script_arguments(directives,
"uninstall",
{ must_succeed: true, sudo: true },
{ print_stdout: true },
directive_name)
ohai "Running uninstall script #{executable}"
raise Hbc::CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil?
executable_path = @cask.staged_path.join(executable)
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path)
@command.run(executable_path, script_arguments)
sleep 1
end
def uninstall_delete(directives, expand_tilde = true)
Array(directives[:delete]).concat(Array(directives[:trash])).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice|
ohai "Removing files: #{path_slice.utf8_inspect}"
path_slice = self.class.expand_path_strings(path_slice) if expand_tilde
path_slice = self.class.remove_relative_path_strings(:delete, path_slice)
path_slice = self.class.remove_undeletable_path_strings(:delete, path_slice)
@command.run!("/bin/rm", args: path_slice.unshift("-rf", "--"), sudo: true)
end
end
def uninstall_pkgutil(directives)
ohai "Removing files from pkgutil Bill-of-Materials"
Array(directives[:pkgutil]).each do |regexp|
pkgs = Hbc::Pkg.all_matching(regexp, @command)
pkgs.each(&:uninstall)
end
end
# :trash functionality is stubbed as a synonym for :delete
# TODO: make :trash work differently, moving files to the Trash
def uninstall_trash(directives, expand_tilde = true)
uninstall_delete(directives, expand_tilde)
end
def uninstall_delete(directives, expand_tilde = true)
Array(directives[:delete]).concat(Array(directives[:trash])).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice|
ohai "Removing files: #{path_slice.utf8_inspect}"
path_slice = self.class.expand_path_strings(path_slice) if expand_tilde
path_slice = self.class.remove_relative_path_strings(:delete, path_slice)
path_slice = self.class.remove_undeletable_path_strings(:delete, path_slice)
@command.run!("/bin/rm", args: path_slice.unshift("-rf", "--"), sudo: true)
end
end
# :trash functionality is stubbed as a synonym for :delete
# TODO: make :trash work differently, moving files to the Trash
def uninstall_trash(directives, expand_tilde = true)
uninstall_delete(directives, expand_tilde)
end
def uninstall_rmdir(directives, expand_tilde = true)
Array(directives[:rmdir]).flatten.each do |directory|
directory = self.class.expand_path_strings([directory]).first if expand_tilde
directory = self.class.remove_relative_path_strings(:rmdir, [directory]).first
directory = self.class.remove_undeletable_path_strings(:rmdir, [directory]).first
next if directory.to_s.empty?
ohai "Removing directory if empty: #{directory.to_s.utf8_inspect}"
directory = Pathname.new(directory)
next unless directory.exist?
@command.run!("/bin/rm",
args: ["-f", "--", directory.join(".DS_Store")],
sudo: true,
print_stderr: false)
@command.run("/bin/rmdir",
args: ["--", directory],
sudo: true,
print_stderr: false)
def uninstall_rmdir(directives, expand_tilde = true)
Array(directives[:rmdir]).flatten.each do |directory|
directory = self.class.expand_path_strings([directory]).first if expand_tilde
directory = self.class.remove_relative_path_strings(:rmdir, [directory]).first
directory = self.class.remove_undeletable_path_strings(:rmdir, [directory]).first
next if directory.to_s.empty?
ohai "Removing directory if empty: #{directory.to_s.utf8_inspect}"
directory = Pathname.new(directory)
next unless directory.exist?
@command.run!("/bin/rm",
args: ["-f", "--", directory.join(".DS_Store")],
sudo: true,
print_stderr: false)
@command.run("/bin/rmdir",
args: ["--", directory],
sudo: true,
print_stderr: false)
end
end
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::Vst3Plugin < Hbc::Artifact::Moved
module Hbc
module Artifact
class Vst3Plugin < Moved
end
end
end

View File

@ -1,4 +1,8 @@
require "hbc/artifact/moved"
class Hbc::Artifact::VstPlugin < Hbc::Artifact::Moved
module Hbc
module Artifact
class VstPlugin < Moved
end
end
end

View File

@ -1,16 +1,20 @@
require "hbc/artifact/uninstall_base"
class Hbc::Artifact::Zap < Hbc::Artifact::UninstallBase
def install_phase
odebug "Nothing to do. The zap artifact has no install phase."
end
module Hbc
module Artifact
class Zap < UninstallBase
def install_phase
odebug "Nothing to do. The zap artifact has no install phase."
end
def uninstall_phase
odebug "Nothing to do. The zap artifact has no uninstall phase."
end
def uninstall_phase
odebug "Nothing to do. The zap artifact has no uninstall phase."
end
def zap_phase
expand_tilde = true
dispatch_uninstall_directives(expand_tilde)
def zap_phase
expand_tilde = true
dispatch_uninstall_directives(expand_tilde)
end
end
end
end

View File

@ -2,215 +2,217 @@ require "hbc/checkable"
require "hbc/download"
require "digest"
class Hbc::Audit
include Hbc::Checkable
module Hbc
class Audit
include Checkable
attr_reader :cask, :download
attr_reader :cask, :download
def initialize(cask, download: false, check_token_conflicts: false, command: Hbc::SystemCommand)
@cask = cask
@download = download
@check_token_conflicts = check_token_conflicts
@command = command
end
def check_token_conflicts?
@check_token_conflicts
end
def run!
check_required_stanzas
check_version
check_sha256
check_appcast
check_url
check_generic_artifacts
check_token_conflicts
check_download
self
rescue StandardError => e
odebug "#{e.message}\n#{e.backtrace.join("\n")}"
add_error "exception while auditing #{cask}: #{e.message}"
self
end
def success?
!(errors? || warnings?)
end
def summary_header
"audit for #{cask}"
end
private
def check_required_stanzas
odebug "Auditing required stanzas"
%i{version sha256 url homepage}.each do |sym|
add_error "a #{sym} stanza is required" unless cask.send(sym)
def initialize(cask, download: false, check_token_conflicts: false, command: SystemCommand)
@cask = cask
@download = download
@check_token_conflicts = check_token_conflicts
@command = command
end
add_error "a license stanza is required (:unknown is OK)" unless cask.license
add_error "at least one name stanza is required" if cask.name.empty?
# TODO: specific DSL knowledge should not be spread around in various files like this
# TODO: nested_container should not still be a pseudo-artifact at this point
installable_artifacts = cask.artifacts.reject { |k| [:uninstall, :zap, :nested_container].include?(k) }
add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
end
def check_version
return unless cask.version
check_no_string_version_latest
end
def check_no_string_version_latest
odebug "Verifying version :latest does not appear as a string ('latest')"
return unless cask.version.raw_version == "latest"
add_error "you should use version :latest instead of version 'latest'"
end
def check_sha256
return unless cask.sha256
check_sha256_no_check_if_latest
check_sha256_actually_256
check_sha256_invalid
end
def check_sha256_no_check_if_latest
odebug "Verifying sha256 :no_check with version :latest"
return unless cask.version.latest? && cask.sha256 != :no_check
add_error "you should use sha256 :no_check when version is :latest"
end
def check_sha256_actually_256(sha256: cask.sha256, stanza: "sha256")
odebug "Verifying #{stanza} string is a legal SHA-256 digest"
return unless sha256.is_a?(String)
return if sha256.length == 64 && sha256[%r{^[0-9a-f]+$}i]
add_error "#{stanza} string must be of 64 hexadecimal characters"
end
def check_sha256_invalid(sha256: cask.sha256, stanza: "sha256")
odebug "Verifying #{stanza} is not a known invalid value"
empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
return unless sha256 == empty_sha256
add_error "cannot use the sha256 for an empty string in #{stanza}: #{empty_sha256}"
end
def check_appcast
return unless cask.appcast
odebug "Auditing appcast"
check_appcast_has_checkpoint
return unless cask.appcast.checkpoint
check_sha256_actually_256(sha256: cask.appcast.checkpoint, stanza: "appcast :checkpoint")
check_sha256_invalid(sha256: cask.appcast.checkpoint, stanza: "appcast :checkpoint")
return unless download
check_appcast_http_code
check_appcast_checkpoint_accuracy
end
def check_appcast_has_checkpoint
odebug "Verifying appcast has :checkpoint key"
add_error "a checkpoint sha256 is required for appcast" unless cask.appcast.checkpoint
end
def check_appcast_http_code
odebug "Verifying appcast returns 200 HTTP response code"
result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", Hbc::URL::FAKE_USER_AGENT, "--output", "/dev/null", "--write-out", "%{http_code}", cask.appcast], print_stderr: false)
if result.success?
http_code = result.stdout.chomp
add_warning "unexpected HTTP response code retrieving appcast: #{http_code}" unless http_code == "200"
else
add_warning "error retrieving appcast: #{result.stderr}"
def check_token_conflicts?
@check_token_conflicts
end
end
def check_appcast_checkpoint_accuracy
odebug "Verifying appcast checkpoint is accurate"
result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", Hbc::URL::FAKE_USER_AGENT, cask.appcast], print_stderr: false)
if result.success?
processed_appcast_text = result.stdout.gsub(%r{<pubDate>[^<]*</pubDate>}, "")
# This step is necessary to replicate running `sed` from the command line
processed_appcast_text << "\n" unless processed_appcast_text.end_with?("\n")
expected = cask.appcast.checkpoint
actual = Digest::SHA2.hexdigest(processed_appcast_text)
add_warning <<-EOS.undent unless expected == actual
appcast checkpoint mismatch
Expected: #{expected}
Actual: #{actual}
EOS
else
add_warning "error retrieving appcast: #{result.stderr}"
def run!
check_required_stanzas
check_version
check_sha256
check_appcast
check_url
check_generic_artifacts
check_token_conflicts
check_download
self
rescue StandardError => e
odebug "#{e.message}\n#{e.backtrace.join("\n")}"
add_error "exception while auditing #{cask}: #{e.message}"
self
end
end
def check_url
return unless cask.url
check_download_url_format
end
def check_download_url_format
odebug "Auditing URL format"
if bad_sourceforge_url?
add_warning "SourceForge URL format incorrect. See https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/url.md#sourceforgeosdn-urls"
elsif bad_osdn_url?
add_warning "OSDN URL format incorrect. See https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/url.md#sourceforgeosdn-urls"
def success?
!(errors? || warnings?)
end
end
def bad_url_format?(regex, valid_formats_array)
return false unless cask.url.to_s =~ regex
valid_formats_array.none? { |format| cask.url.to_s =~ format }
end
def summary_header
"audit for #{cask}"
end
def bad_sourceforge_url?
bad_url_format?(%r{sourceforge},
[
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z},
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)\/)},
# special cases: cannot find canonical format URL
%r{\Ahttps?://brushviewer\.sourceforge\.net/brushviewql\.zip\Z},
%r{\Ahttps?://doublecommand\.sourceforge\.net/files/},
%r{\Ahttps?://excalibur\.sourceforge\.net/get\.php\?id=},
])
end
private
def bad_osdn_url?
bad_url_format?(%r{osd}, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
end
def check_generic_artifacts
cask.artifacts[:artifact].each do |source, target_hash|
unless target_hash.is_a?(Hash) && target_hash[:target]
add_error "target required for generic artifact #{source}"
next
def check_required_stanzas
odebug "Auditing required stanzas"
%i{version sha256 url homepage}.each do |sym|
add_error "a #{sym} stanza is required" unless cask.send(sym)
end
add_error "target must be absolute path for generic artifact #{source}" unless Pathname.new(target_hash[:target]).absolute?
add_error "a license stanza is required (:unknown is OK)" unless cask.license
add_error "at least one name stanza is required" if cask.name.empty?
# TODO: specific DSL knowledge should not be spread around in various files like this
# TODO: nested_container should not still be a pseudo-artifact at this point
installable_artifacts = cask.artifacts.reject { |k| [:uninstall, :zap, :nested_container].include?(k) }
add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
end
end
def check_token_conflicts
return unless check_token_conflicts?
return unless core_formula_names.include?(cask.token)
add_warning "possible duplicate, cask token conflicts with Homebrew core formula: #{core_formula_url}"
end
def check_version
return unless cask.version
check_no_string_version_latest
end
def core_tap
@core_tap ||= CoreTap.instance
end
def check_no_string_version_latest
odebug "Verifying version :latest does not appear as a string ('latest')"
return unless cask.version.raw_version == "latest"
add_error "you should use version :latest instead of version 'latest'"
end
def core_formula_names
core_tap.formula_names
end
def check_sha256
return unless cask.sha256
check_sha256_no_check_if_latest
check_sha256_actually_256
check_sha256_invalid
end
def core_formula_url
"#{core_tap.default_remote}/blob/master/Formula/#{cask.token}.rb"
end
def check_sha256_no_check_if_latest
odebug "Verifying sha256 :no_check with version :latest"
return unless cask.version.latest? && cask.sha256 != :no_check
add_error "you should use sha256 :no_check when version is :latest"
end
def check_download
return unless download && cask.url
odebug "Auditing download"
downloaded_path = download.perform
Hbc::Verify.all(cask, downloaded_path)
rescue => e
add_error "download not possible: #{e.message}"
def check_sha256_actually_256(sha256: cask.sha256, stanza: "sha256")
odebug "Verifying #{stanza} string is a legal SHA-256 digest"
return unless sha256.is_a?(String)
return if sha256.length == 64 && sha256[%r{^[0-9a-f]+$}i]
add_error "#{stanza} string must be of 64 hexadecimal characters"
end
def check_sha256_invalid(sha256: cask.sha256, stanza: "sha256")
odebug "Verifying #{stanza} is not a known invalid value"
empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
return unless sha256 == empty_sha256
add_error "cannot use the sha256 for an empty string in #{stanza}: #{empty_sha256}"
end
def check_appcast
return unless cask.appcast
odebug "Auditing appcast"
check_appcast_has_checkpoint
return unless cask.appcast.checkpoint
check_sha256_actually_256(sha256: cask.appcast.checkpoint, stanza: "appcast :checkpoint")
check_sha256_invalid(sha256: cask.appcast.checkpoint, stanza: "appcast :checkpoint")
return unless download
check_appcast_http_code
check_appcast_checkpoint_accuracy
end
def check_appcast_has_checkpoint
odebug "Verifying appcast has :checkpoint key"
add_error "a checkpoint sha256 is required for appcast" unless cask.appcast.checkpoint
end
def check_appcast_http_code
odebug "Verifying appcast returns 200 HTTP response code"
result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", URL::FAKE_USER_AGENT, "--output", "/dev/null", "--write-out", "%{http_code}", cask.appcast], print_stderr: false)
if result.success?
http_code = result.stdout.chomp
add_warning "unexpected HTTP response code retrieving appcast: #{http_code}" unless http_code == "200"
else
add_warning "error retrieving appcast: #{result.stderr}"
end
end
def check_appcast_checkpoint_accuracy
odebug "Verifying appcast checkpoint is accurate"
result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", URL::FAKE_USER_AGENT, cask.appcast], print_stderr: false)
if result.success?
processed_appcast_text = result.stdout.gsub(%r{<pubDate>[^<]*</pubDate>}, "")
# This step is necessary to replicate running `sed` from the command line
processed_appcast_text << "\n" unless processed_appcast_text.end_with?("\n")
expected = cask.appcast.checkpoint
actual = Digest::SHA2.hexdigest(processed_appcast_text)
add_warning <<-EOS.undent unless expected == actual
appcast checkpoint mismatch
Expected: #{expected}
Actual: #{actual}
EOS
else
add_warning "error retrieving appcast: #{result.stderr}"
end
end
def check_url
return unless cask.url
check_download_url_format
end
def check_download_url_format
odebug "Auditing URL format"
if bad_sourceforge_url?
add_warning "SourceForge URL format incorrect. See https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/url.md#sourceforgeosdn-urls"
elsif bad_osdn_url?
add_warning "OSDN URL format incorrect. See https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/url.md#sourceforgeosdn-urls"
end
end
def bad_url_format?(regex, valid_formats_array)
return false unless cask.url.to_s =~ regex
valid_formats_array.none? { |format| cask.url.to_s =~ format }
end
def bad_sourceforge_url?
bad_url_format?(%r{sourceforge},
[
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z},
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)\/)},
# special cases: cannot find canonical format URL
%r{\Ahttps?://brushviewer\.sourceforge\.net/brushviewql\.zip\Z},
%r{\Ahttps?://doublecommand\.sourceforge\.net/files/},
%r{\Ahttps?://excalibur\.sourceforge\.net/get\.php\?id=},
])
end
def bad_osdn_url?
bad_url_format?(%r{osd}, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
end
def check_generic_artifacts
cask.artifacts[:artifact].each do |source, target_hash|
unless target_hash.is_a?(Hash) && target_hash[:target]
add_error "target required for generic artifact #{source}"
next
end
add_error "target must be absolute path for generic artifact #{source}" unless Pathname.new(target_hash[:target]).absolute?
end
end
def check_token_conflicts
return unless check_token_conflicts?
return unless core_formula_names.include?(cask.token)
add_warning "possible duplicate, cask token conflicts with Homebrew core formula: #{core_formula_url}"
end
def core_tap
@core_tap ||= CoreTap.instance
end
def core_formula_names
core_tap.formula_names
end
def core_formula_url
"#{core_tap.default_remote}/blob/master/Formula/#{cask.token}.rb"
end
def check_download
return unless download && cask.url
odebug "Auditing download"
downloaded_path = download.perform
Verify.all(cask, downloaded_path)
rescue => e
add_error "download not possible: #{e.message}"
end
end
end

View File

@ -1,10 +1,12 @@
class Hbc::Auditor
def self.audit(cask, audit_download: false, check_token_conflicts: false)
download = audit_download && Hbc::Download.new(cask)
audit = Hbc::Audit.new(cask, download: download,
check_token_conflicts: check_token_conflicts)
audit.run!
puts audit.summary
audit.success?
module Hbc
class Auditor
def self.audit(cask, audit_download: false, check_token_conflicts: false)
download = audit_download && Download.new(cask)
audit = Audit.new(cask, download: download,
check_token_conflicts: check_token_conflicts)
audit.run!
puts audit.summary
audit.success?
end
end
end

View File

@ -1,34 +1,36 @@
module Hbc::Cache
module_function
module Hbc
module Cache
module_function
def ensure_cache_exists
return if Hbc.cache.exist?
def ensure_cache_exists
return if Hbc.cache.exist?
odebug "Creating Cache at #{Hbc.cache}"
Hbc.cache.mkpath
end
def migrate_legacy_cache
return unless Hbc.legacy_cache.exist?
ohai "Migrating cached files to #{Hbc.cache}..."
Hbc.legacy_cache.children.select(&:symlink?).each do |symlink|
file = symlink.readlink
new_name = file.basename
.sub(%r{\-((?:(\d|#{Hbc::DSL::Version::DIVIDER_REGEX})*\-\2*)*[^\-]+)$}x,
'--\1')
renamed_file = Hbc.cache.join(new_name)
if file.exist?
puts "#{file} -> #{renamed_file}"
FileUtils.mv(file, renamed_file)
end
FileUtils.rm(symlink)
odebug "Creating Cache at #{Hbc.cache}"
Hbc.cache.mkpath
end
FileUtils.remove_entry_secure(Hbc.legacy_cache)
def migrate_legacy_cache
return unless Hbc.legacy_cache.exist?
ohai "Migrating cached files to #{Hbc.cache}..."
Hbc.legacy_cache.children.select(&:symlink?).each do |symlink|
file = symlink.readlink
new_name = file.basename
.sub(%r{\-((?:(\d|#{DSL::Version::DIVIDER_REGEX})*\-\2*)*[^\-]+)$}x,
'--\1')
renamed_file = Hbc.cache.join(new_name)
if file.exist?
puts "#{file} -> #{renamed_file}"
FileUtils.mv(file, renamed_file)
end
FileUtils.rm(symlink)
end
FileUtils.remove_entry_secure(Hbc.legacy_cache)
end
end
end

View File

@ -2,114 +2,116 @@ require "forwardable"
require "hbc/dsl"
class Hbc::Cask
extend Forwardable
module Hbc
class Cask
extend Forwardable
attr_reader :token, :sourcefile_path
def initialize(token, sourcefile_path: nil, dsl: nil, &block)
@token = token
@sourcefile_path = sourcefile_path
@dsl = dsl || Hbc::DSL.new(@token)
@dsl.instance_eval(&block) if block_given?
end
Hbc::DSL::DSL_METHODS.each do |method_name|
define_method(method_name) { @dsl.send(method_name) }
end
METADATA_SUBDIR = ".metadata".freeze
def metadata_master_container_path
@metadata_master_container_path ||= caskroom_path.join(METADATA_SUBDIR)
end
def metadata_versioned_container_path
cask_version = version ? version : :unknown
metadata_master_container_path.join(cask_version.to_s)
end
def metadata_path(timestamp = :latest, create = false)
return nil unless metadata_versioned_container_path.respond_to?(:join)
if create && timestamp == :latest
raise Hbc::CaskError, "Cannot create metadata path when timestamp is :latest"
attr_reader :token, :sourcefile_path
def initialize(token, sourcefile_path: nil, dsl: nil, &block)
@token = token
@sourcefile_path = sourcefile_path
@dsl = dsl || DSL.new(@token)
@dsl.instance_eval(&block) if block_given?
end
path = if timestamp == :latest
Pathname.glob(metadata_versioned_container_path.join("*")).sort.last
elsif timestamp == :now
Hbc::Utils.nowstamp_metadata_path(metadata_versioned_container_path)
else
metadata_versioned_container_path.join(timestamp)
end
if create
odebug "Creating metadata directory #{path}"
FileUtils.mkdir_p path
DSL::DSL_METHODS.each do |method_name|
define_method(method_name) { @dsl.send(method_name) }
end
path
end
def metadata_subdir(leaf, timestamp = :latest, create = false)
if create && timestamp == :latest
raise Hbc::CaskError, "Cannot create metadata subdir when timestamp is :latest"
METADATA_SUBDIR = ".metadata".freeze
def metadata_master_container_path
@metadata_master_container_path ||= caskroom_path.join(METADATA_SUBDIR)
end
unless leaf.respond_to?(:length) && !leaf.empty?
raise Hbc::CaskError, "Cannot create metadata subdir for empty leaf"
def metadata_versioned_container_path
cask_version = version ? version : :unknown
metadata_master_container_path.join(cask_version.to_s)
end
parent = metadata_path(timestamp, create)
return nil unless parent.respond_to?(:join)
subdir = parent.join(leaf)
if create
odebug "Creating metadata subdirectory #{subdir}"
FileUtils.mkdir_p subdir
def metadata_path(timestamp = :latest, create = false)
return nil unless metadata_versioned_container_path.respond_to?(:join)
if create && timestamp == :latest
raise CaskError, "Cannot create metadata path when timestamp is :latest"
end
path = if timestamp == :latest
Pathname.glob(metadata_versioned_container_path.join("*")).sort.last
elsif timestamp == :now
Utils.nowstamp_metadata_path(metadata_versioned_container_path)
else
metadata_versioned_container_path.join(timestamp)
end
if create
odebug "Creating metadata directory #{path}"
FileUtils.mkdir_p path
end
path
end
subdir
end
def timestamped_versions
Pathname.glob(metadata_master_container_path.join("*", "*"))
.map { |p| p.relative_path_from(metadata_master_container_path) }
.sort_by(&:basename) # sort by timestamp
.map(&:split)
end
def metadata_subdir(leaf, timestamp = :latest, create = false)
if create && timestamp == :latest
raise CaskError, "Cannot create metadata subdir when timestamp is :latest"
end
unless leaf.respond_to?(:length) && !leaf.empty?
raise CaskError, "Cannot create metadata subdir for empty leaf"
end
parent = metadata_path(timestamp, create)
return nil unless parent.respond_to?(:join)
subdir = parent.join(leaf)
if create
odebug "Creating metadata subdirectory #{subdir}"
FileUtils.mkdir_p subdir
end
subdir
end
def versions
timestamped_versions.map(&:first)
.reverse
.uniq
.reverse
end
def timestamped_versions
Pathname.glob(metadata_master_container_path.join("*", "*"))
.map { |p| p.relative_path_from(metadata_master_container_path) }
.sort_by(&:basename) # sort by timestamp
.map(&:split)
end
def installed?
!versions.empty?
end
def versions
timestamped_versions.map(&:first)
.reverse
.uniq
.reverse
end
def to_s
@token
end
def installed?
!versions.empty?
end
def dumpcask
return unless Hbc.respond_to?(:debug)
return unless Hbc.debug
def to_s
@token
end
odebug "Cask instance dumps in YAML:"
odebug "Cask instance toplevel:", to_yaml
[
:name,
:homepage,
:url,
:appcast,
:version,
:license,
:sha256,
:artifacts,
:caveats,
:depends_on,
:conflicts_with,
:container,
:gpg,
:accessibility_access,
:auto_updates,
].each do |method|
odebug "Cask instance method '#{method}':", send(method).to_yaml
def dumpcask
return unless Hbc.respond_to?(:debug)
return unless Hbc.debug
odebug "Cask instance dumps in YAML:"
odebug "Cask instance toplevel:", to_yaml
[
:name,
:homepage,
:url,
:appcast,
:version,
:license,
:sha256,
:artifacts,
:caveats,
:depends_on,
:conflicts_with,
:container,
:gpg,
:accessibility_access,
:auto_updates,
].each do |method|
odebug "Cask instance method '#{method}':", send(method).to_yaml
end
end
end
end

View File

@ -1,33 +1,35 @@
require "hbc/topological_hash"
class Hbc::CaskDependencies
attr_reader :cask, :graph, :sorted
module Hbc
class CaskDependencies
attr_reader :cask, :graph, :sorted
def initialize(cask)
@cask = cask
@graph = graph_dependencies
@sorted = sort
end
def initialize(cask)
@cask = cask
@graph = graph_dependencies
@sorted = sort
end
def graph_dependencies
deps_in = ->(csk) { csk.depends_on ? csk.depends_on.cask || [] : [] }
walk = lambda { |acc, deps|
deps.each do |dep|
next if acc.key?(dep)
succs = deps_in.call Hbc.load(dep)
acc[dep] = succs
walk.call(acc, succs)
end
acc
}
def graph_dependencies
deps_in = ->(csk) { csk.depends_on ? csk.depends_on.cask || [] : [] }
walk = lambda { |acc, deps|
deps.each do |dep|
next if acc.key?(dep)
succs = deps_in.call Hbc.load(dep)
acc[dep] = succs
walk.call(acc, succs)
end
acc
}
graphed = walk.call({}, @cask.depends_on.cask)
Hbc::TopologicalHash[graphed]
end
graphed = walk.call({}, @cask.depends_on.cask)
TopologicalHash[graphed]
end
def sort
@graph.tsort
rescue TSort::Cyclic
raise Hbc::CaskCyclicCaskDependencyError, @cask.token
def sort
@graph.tsort
rescue TSort::Cyclic
raise CaskCyclicCaskDependencyError, @cask.token
end
end
end

View File

@ -1,41 +1,43 @@
module Hbc::Caskroom
module_function
module Hbc
module Caskroom
module_function
def migrate_caskroom_from_repo_to_prefix
repo_caskroom = Hbc.homebrew_repository.join("Caskroom")
return if Hbc.caskroom.exist?
return unless repo_caskroom.directory?
def migrate_caskroom_from_repo_to_prefix
repo_caskroom = Hbc.homebrew_repository.join("Caskroom")
return if Hbc.caskroom.exist?
return unless repo_caskroom.directory?
ohai "Moving Caskroom from HOMEBREW_REPOSITORY to HOMEBREW_PREFIX"
ohai "Moving Caskroom from HOMEBREW_REPOSITORY to HOMEBREW_PREFIX"
if Hbc.caskroom.parent.writable?
FileUtils.mv repo_caskroom, Hbc.caskroom
else
opoo "#{Hbc.caskroom.parent} is not writable, sudo is needed to move the Caskroom."
system "/usr/bin/sudo", "--", "/bin/mv", "--", repo_caskroom.to_s, Hbc.caskroom.parent.to_s
end
end
def ensure_caskroom_exists
return if Hbc.caskroom.exist?
ohai "Creating Caskroom at #{Hbc.caskroom}"
if Hbc.caskroom.parent.writable?
Hbc.caskroom.mkpath
else
ohai "We'll set permissions properly so we won't need sudo in the future"
toplevel_dir = Hbc.caskroom
toplevel_dir = toplevel_dir.parent until toplevel_dir.parent.root?
unless toplevel_dir.directory?
# If a toplevel dir such as '/opt' must be created, enforce standard permissions.
# sudo in system is rude.
system "/usr/bin/sudo", "--", "/bin/mkdir", "--", toplevel_dir
system "/usr/bin/sudo", "--", "/bin/chmod", "--", "0775", toplevel_dir
if Hbc.caskroom.parent.writable?
FileUtils.mv repo_caskroom, Hbc.caskroom
else
opoo "#{Hbc.caskroom.parent} is not writable, sudo is needed to move the Caskroom."
system "/usr/bin/sudo", "--", "/bin/mv", "--", repo_caskroom.to_s, Hbc.caskroom.parent.to_s
end
# sudo in system is rude.
system "/usr/bin/sudo", "--", "/bin/mkdir", "-p", "--", Hbc.caskroom
unless Hbc.caskroom.parent == toplevel_dir
system "/usr/bin/sudo", "--", "/usr/sbin/chown", "-R", "--", "#{Hbc::Utils.current_user}:staff", Hbc.caskroom.parent.to_s
end
def ensure_caskroom_exists
return if Hbc.caskroom.exist?
ohai "Creating Caskroom at #{Hbc.caskroom}"
if Hbc.caskroom.parent.writable?
Hbc.caskroom.mkpath
else
ohai "We'll set permissions properly so we won't need sudo in the future"
toplevel_dir = Hbc.caskroom
toplevel_dir = toplevel_dir.parent until toplevel_dir.parent.root?
unless toplevel_dir.directory?
# If a toplevel dir such as '/opt' must be created, enforce standard permissions.
# sudo in system is rude.
system "/usr/bin/sudo", "--", "/bin/mkdir", "--", toplevel_dir
system "/usr/bin/sudo", "--", "/bin/chmod", "--", "0775", toplevel_dir
end
# sudo in system is rude.
system "/usr/bin/sudo", "--", "/bin/mkdir", "-p", "--", Hbc.caskroom
unless Hbc.caskroom.parent == toplevel_dir
system "/usr/bin/sudo", "--", "/usr/sbin/chown", "-R", "--", "#{Utils.current_user}:staff", Hbc.caskroom.parent.to_s
end
end
end
end

View File

@ -1,12 +1,14 @@
class Hbc::Caveats
def initialize(block)
@block = block
end
module Hbc
class Caveats
def initialize(block)
@block = block
end
def eval_and_print(cask)
dsl = Hbc::DSL::Caveats.new(cask)
retval = dsl.instance_eval(&@block)
return if retval.nil?
puts retval.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n")
def eval_and_print(cask)
dsl = DSL::Caveats.new(cask)
retval = dsl.instance_eval(&@block)
return if retval.nil?
puts retval.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n")
end
end
end

View File

@ -1,51 +1,53 @@
module Hbc::Checkable
def errors
Array(@errors)
end
def warnings
Array(@warnings)
end
def add_error(message)
@errors ||= []
@errors << message
end
def add_warning(message)
@warnings ||= []
@warnings << message
end
def errors?
Array(@errors).any?
end
def warnings?
Array(@warnings).any?
end
def result
if errors?
"#{Tty.red}failed#{Tty.reset}"
elsif warnings?
"#{Tty.yellow}warning#{Tty.reset}"
else
"#{Tty.green}passed#{Tty.reset}"
end
end
def summary
summary = ["#{summary_header}: #{result}"]
errors.each do |error|
summary << " #{Tty.red}-#{Tty.reset} #{error}"
module Hbc
module Checkable
def errors
Array(@errors)
end
warnings.each do |warning|
summary << " #{Tty.yellow}-#{Tty.reset} #{warning}"
def warnings
Array(@warnings)
end
summary.join("\n")
def add_error(message)
@errors ||= []
@errors << message
end
def add_warning(message)
@warnings ||= []
@warnings << message
end
def errors?
Array(@errors).any?
end
def warnings?
Array(@warnings).any?
end
def result
if errors?
"#{Tty.red}failed#{Tty.reset}"
elsif warnings?
"#{Tty.yellow}warning#{Tty.reset}"
else
"#{Tty.green}passed#{Tty.reset}"
end
end
def summary
summary = ["#{summary_header}: #{result}"]
errors.each do |error|
summary << " #{Tty.red}-#{Tty.reset} #{error}"
end
warnings.each do |warning|
summary << " #{Tty.yellow}-#{Tty.reset} #{warning}"
end
summary.join("\n")
end
end
end

View File

@ -1,5 +1,3 @@
class Hbc::CLI; end
require "optparse"
require "shellwords"
@ -28,248 +26,250 @@ require "hbc/cli/internal_dump"
require "hbc/cli/internal_help"
require "hbc/cli/internal_stanza"
class Hbc::CLI
ALIASES = {
"ls" => "list",
"homepage" => "home",
"-S" => "search", # verb starting with "-" is questionable
"up" => "update",
"instal" => "install", # gem does the same
"rm" => "uninstall",
"remove" => "uninstall",
"abv" => "info",
"dr" => "doctor",
# aliases from Homebrew that we don't (yet) support
# 'ln' => 'link',
# 'configure' => 'diy',
# '--repo' => '--repository',
# 'environment' => '--env',
# '-c1' => '--config',
module Hbc
class CLI
ALIASES = {
"ls" => "list",
"homepage" => "home",
"-S" => "search", # verb starting with "-" is questionable
"up" => "update",
"instal" => "install", # gem does the same
"rm" => "uninstall",
"remove" => "uninstall",
"abv" => "info",
"dr" => "doctor",
# aliases from Homebrew that we don't (yet) support
# 'ln' => 'link',
# 'configure' => 'diy',
# '--repo' => '--repository',
# 'environment' => '--env',
# '-c1' => '--config',
}.freeze
OPTIONS = {
"--caskroom=" => :caskroom=,
"--appdir=" => :appdir=,
"--colorpickerdir=" => :colorpickerdir=,
"--prefpanedir=" => :prefpanedir=,
"--qlplugindir=" => :qlplugindir=,
"--fontdir=" => :fontdir=,
"--servicedir=" => :servicedir=,
"--input_methoddir=" => :input_methoddir=,
"--internet_plugindir=" => :internet_plugindir=,
"--audio_unit_plugindir=" => :audio_unit_plugindir=,
"--vst_plugindir=" => :vst_plugindir=,
"--vst3_plugindir=" => :vst3_plugindir=,
"--screen_saverdir=" => :screen_saverdir=,
}.freeze
FLAGS = {
"--no-binaries" => :no_binaries=,
"--debug" => :debug=,
"--verbose" => :verbose=,
"--outdated" => :cleanup_outdated=,
"--help" => :help=,
}.freeze
OPTIONS = {
"--caskroom=" => :caskroom=,
"--appdir=" => :appdir=,
"--colorpickerdir=" => :colorpickerdir=,
"--prefpanedir=" => :prefpanedir=,
"--qlplugindir=" => :qlplugindir=,
"--fontdir=" => :fontdir=,
"--servicedir=" => :servicedir=,
"--input_methoddir=" => :input_methoddir=,
"--internet_plugindir=" => :internet_plugindir=,
"--audio_unit_plugindir=" => :audio_unit_plugindir=,
"--vst_plugindir=" => :vst_plugindir=,
"--vst3_plugindir=" => :vst3_plugindir=,
"--screen_saverdir=" => :screen_saverdir=,
}.freeze
FLAGS = {
"--no-binaries" => :no_binaries=,
"--debug" => :debug=,
"--verbose" => :verbose=,
"--outdated" => :cleanup_outdated=,
"--help" => :help=,
}.freeze
def self.command_classes
@command_classes ||= Hbc::CLI.constants
.map(&Hbc::CLI.method(:const_get))
.select { |sym| sym.respond_to?(:run) }
end
def self.commands
@commands ||= command_classes.map(&:command_name)
end
def self.lookup_command(command_string)
@lookup ||= Hash[commands.zip(command_classes)]
command_string = ALIASES.fetch(command_string, command_string)
@lookup.fetch(command_string, command_string)
end
# modified from Homebrew
def self.require?(path)
require path
true # OK if already loaded
rescue LoadError => e
# HACK: :( because we should raise on syntax errors
# but not if the file doesn't exist.
# TODO: make robust!
raise unless e.to_s.include? path
end
def self.should_init?(command)
(command.is_a? Class) && (command < Hbc::CLI::Base) && command.needs_init?
end
def self.run_command(command, *rest)
if command.respond_to?(:run)
# usual case: built-in command verb
command.run(*rest)
elsif require? Hbc::Utils.which("brewcask-#{command}.rb").to_s
# external command as Ruby library on PATH, Homebrew-style
elsif command.to_s.include?("/") && require?(command.to_s)
# external command as Ruby library with literal path, useful
# for development and troubleshooting
sym = Pathname.new(command.to_s).basename(".rb").to_s.capitalize
klass = begin
Hbc::CLI.const_get(sym)
rescue NameError
nil
end
if klass.respond_to?(:run)
# invoke "run" on a Ruby library which follows our coding conventions
# other Ruby libraries must do everything via "require"
klass.run(*rest)
end
elsif Hbc::Utils.which "brewcask-#{command}"
# arbitrary external executable on PATH, Homebrew-style
exec "brewcask-#{command}", *ARGV[1..-1]
elsif Pathname.new(command.to_s).executable? &&
command.to_s.include?("/") &&
!command.to_s.match(%r{\.rb$})
# arbitrary external executable with literal path, useful
# for development and troubleshooting
exec command, *ARGV[1..-1]
else
# failure
Hbc::CLI::NullCommand.new(command).run
def self.command_classes
@command_classes ||= self.constants
.map(&method(:const_get))
.select { |sym| sym.respond_to?(:run) }
end
end
def self.process(arguments)
command_string, *rest = *arguments
rest = process_options(rest)
command = Hbc.help ? "help" : lookup_command(command_string)
Hbc.default_tap.install unless Hbc.default_tap.installed?
Hbc.init if should_init?(command)
run_command(command, *rest)
rescue Hbc::CaskError, Hbc::CaskSha256MismatchError => e
msg = e.message
msg << e.backtrace.join("\n") if Hbc.debug
onoe msg
exit 1
rescue StandardError, ScriptError, NoMemoryError => e
msg = e.message
msg << Hbc::Utils.error_message_with_suggestions
msg << e.backtrace.join("\n")
onoe msg
exit 1
end
def self.nice_listing(cask_list)
cask_taps = {}
cask_list.each do |c|
user, repo, token = c.split "/"
repo.sub!(%r{^homebrew-}i, "")
cask_taps[token] ||= []
cask_taps[token].push "#{user}/#{repo}"
def self.commands
@commands ||= command_classes.map(&:command_name)
end
list = []
cask_taps.each do |token, taps|
if taps.length == 1
list.push token
def self.lookup_command(command_string)
@lookup ||= Hash[commands.zip(command_classes)]
command_string = ALIASES.fetch(command_string, command_string)
@lookup.fetch(command_string, command_string)
end
# modified from Homebrew
def self.require?(path)
require path
true # OK if already loaded
rescue LoadError => e
# HACK: :( because we should raise on syntax errors
# but not if the file doesn't exist.
# TODO: make robust!
raise unless e.to_s.include? path
end
def self.should_init?(command)
(command.is_a? Class) && (command < CLI::Base) && command.needs_init?
end
def self.run_command(command, *rest)
if command.respond_to?(:run)
# usual case: built-in command verb
command.run(*rest)
elsif require? Utils.which("brewcask-#{command}.rb").to_s
# external command as Ruby library on PATH, Homebrew-style
elsif command.to_s.include?("/") && require?(command.to_s)
# external command as Ruby library with literal path, useful
# for development and troubleshooting
sym = Pathname.new(command.to_s).basename(".rb").to_s.capitalize
klass = begin
self.const_get(sym)
rescue NameError
nil
end
if klass.respond_to?(:run)
# invoke "run" on a Ruby library which follows our coding conventions
# other Ruby libraries must do everything via "require"
klass.run(*rest)
end
elsif Utils.which "brewcask-#{command}"
# arbitrary external executable on PATH, Homebrew-style
exec "brewcask-#{command}", *ARGV[1..-1]
elsif Pathname.new(command.to_s).executable? &&
command.to_s.include?("/") &&
!command.to_s.match(%r{\.rb$})
# arbitrary external executable with literal path, useful
# for development and troubleshooting
exec command, *ARGV[1..-1]
else
taps.each { |r| list.push [r, token].join "/" }
# failure
NullCommand.new(command).run
end
end
list.sort
end
def self.parser
# If you modify these arguments, please update USAGE.md
@parser ||= OptionParser.new do |opts|
OPTIONS.each do |option, method|
opts.on("#{option}" "PATH", Pathname) do |path|
Hbc.public_send(method, path)
def self.process(arguments)
command_string, *rest = *arguments
rest = process_options(rest)
command = Hbc.help ? "help" : lookup_command(command_string)
Hbc.default_tap.install unless Hbc.default_tap.installed?
Hbc.init if should_init?(command)
run_command(command, *rest)
rescue CaskError, CaskSha256MismatchError => e
msg = e.message
msg << e.backtrace.join("\n") if Hbc.debug
onoe msg
exit 1
rescue StandardError, ScriptError, NoMemoryError => e
msg = e.message
msg << Utils.error_message_with_suggestions
msg << e.backtrace.join("\n")
onoe msg
exit 1
end
def self.nice_listing(cask_list)
cask_taps = {}
cask_list.each do |c|
user, repo, token = c.split "/"
repo.sub!(%r{^homebrew-}i, "")
cask_taps[token] ||= []
cask_taps[token].push "#{user}/#{repo}"
end
list = []
cask_taps.each do |token, taps|
if taps.length == 1
list.push token
else
taps.each { |r| list.push [r, token].join "/" }
end
end
list.sort
end
def self.parser
# If you modify these arguments, please update USAGE.md
@parser ||= OptionParser.new do |opts|
OPTIONS.each do |option, method|
opts.on("#{option}" "PATH", Pathname) do |path|
Hbc.public_send(method, path)
end
end
opts.on("--binarydir=PATH") do
opoo <<-EOS.undent
Option --binarydir is obsolete!
Homebrew-Cask now uses the same location as your Homebrew installation for executable links.
EOS
end
FLAGS.each do |flag, method|
opts.on(flag) do
Hbc.public_send(method, true)
end
end
opts.on("--version") do
raise OptionParser::InvalidOption # override default handling of --version
end
end
end
def self.process_options(args)
all_args = Shellwords.shellsplit(ENV["HOMEBREW_CASK_OPTS"] || "") + args
remaining = []
until all_args.empty?
begin
head = all_args.shift
remaining.concat(parser.parse([head]))
rescue OptionParser::InvalidOption
remaining << head
retry
rescue OptionParser::MissingArgument
raise CaskError, "The option '#{head}' requires an argument"
rescue OptionParser::AmbiguousOption
raise CaskError, "There is more than one possible option that starts with '#{head}'"
end
end
opts.on("--binarydir=PATH") do
opoo <<-EOS.undent
Option --binarydir is obsolete!
Homebrew-Cask now uses the same location as your Homebrew installation for executable links.
# for compat with Homebrew, not certain if this is desirable
Hbc.verbose = true if !ENV["VERBOSE"].nil? || !ENV["HOMEBREW_VERBOSE"].nil?
remaining
end
class NullCommand
def initialize(attempted_verb)
@attempted_verb = attempted_verb
end
def run(*args)
if args.include?("--version") || @attempted_verb == "--version"
puts Hbc.full_version
else
purpose
usage
unless @attempted_verb.to_s.strip.empty? || @attempted_verb == "help"
raise CaskError, "Unknown command: #{@attempted_verb}"
end
end
end
def purpose
puts <<-EOS.undent
brew-cask provides a friendly homebrew-style CLI workflow for the
administration of macOS applications distributed as binaries.
EOS
end
FLAGS.each do |flag, method|
opts.on(flag) do
Hbc.public_send(method, true)
def usage
max_command_len = CLI.commands.map(&:length).max
puts "Commands:\n\n"
CLI.command_classes.each do |klass|
next unless klass.visible
puts " #{klass.command_name.ljust(max_command_len)} #{_help_for(klass)}"
end
puts %Q{\nSee also "man brew-cask"}
end
opts.on("--version") do
raise OptionParser::InvalidOption # override default handling of --version
def help
""
end
end
end
def self.process_options(args)
all_args = Shellwords.shellsplit(ENV["HOMEBREW_CASK_OPTS"] || "") + args
remaining = []
until all_args.empty?
begin
head = all_args.shift
remaining.concat(parser.parse([head]))
rescue OptionParser::InvalidOption
remaining << head
retry
rescue OptionParser::MissingArgument
raise Hbc::CaskError, "The option '#{head}' requires an argument"
rescue OptionParser::AmbiguousOption
raise Hbc::CaskError, "There is more than one possible option that starts with '#{head}'"
def _help_for(klass)
klass.respond_to?(:help) ? klass.help : nil
end
end
# for compat with Homebrew, not certain if this is desirable
Hbc.verbose = true if !ENV["VERBOSE"].nil? || !ENV["HOMEBREW_VERBOSE"].nil?
remaining
end
class NullCommand
def initialize(attempted_verb)
@attempted_verb = attempted_verb
end
def run(*args)
if args.include?("--version") || @attempted_verb == "--version"
puts Hbc.full_version
else
purpose
usage
unless @attempted_verb.to_s.strip.empty? || @attempted_verb == "help"
raise Hbc::CaskError, "Unknown command: #{@attempted_verb}"
end
end
end
def purpose
puts <<-EOS.undent
brew-cask provides a friendly homebrew-style CLI workflow for the
administration of macOS applications distributed as binaries.
EOS
end
def usage
max_command_len = Hbc::CLI.commands.map(&:length).max
puts "Commands:\n\n"
Hbc::CLI.command_classes.each do |klass|
next unless klass.visible
puts " #{klass.command_name.ljust(max_command_len)} #{_help_for(klass)}"
end
puts %Q{\nSee also "man brew-cask"}
end
def help
""
end
def _help_for(klass)
klass.respond_to?(:help) ? klass.help : nil
end
end
end

View File

@ -1,52 +1,56 @@
class Hbc::CLI::Audit < Hbc::CLI::Base
def self.help
"verifies installability of Casks"
end
module Hbc
class CLI
class Audit < Base
def self.help
"verifies installability of Casks"
end
def self.run(*args)
failed_casks = new(args, Hbc::Auditor).run
return if failed_casks.empty?
raise Hbc::CaskError, "audit failed for casks: #{failed_casks.join(" ")}"
end
def self.run(*args)
failed_casks = new(args, Auditor).run
return if failed_casks.empty?
raise CaskError, "audit failed for casks: #{failed_casks.join(" ")}"
end
def initialize(args, auditor)
@args = args
@auditor = auditor
end
def initialize(args, auditor)
@args = args
@auditor = auditor
end
def run
casks_to_audit.each_with_object([]) do |cask, failed|
failed << cask unless audit(cask)
def run
casks_to_audit.each_with_object([]) do |cask, failed|
failed << cask unless audit(cask)
end
end
def audit(cask)
odebug "Auditing Cask #{cask}"
@auditor.audit(cask, audit_download: audit_download?,
check_token_conflicts: check_token_conflicts?)
end
def audit_download?
@args.include?("--download")
end
def check_token_conflicts?
@args.include?("--token-conflicts")
end
def casks_to_audit
if cask_tokens.empty?
Hbc.all
else
cask_tokens.map { |token| Hbc.load(token) }
end
end
def cask_tokens
@cask_tokens ||= self.class.cask_tokens_from(@args)
end
def self.needs_init?
true
end
end
end
def audit(cask)
odebug "Auditing Cask #{cask}"
@auditor.audit(cask, audit_download: audit_download?,
check_token_conflicts: check_token_conflicts?)
end
def audit_download?
@args.include?("--download")
end
def check_token_conflicts?
@args.include?("--token-conflicts")
end
def casks_to_audit
if cask_tokens.empty?
Hbc.all
else
cask_tokens.map { |token| Hbc.load(token) }
end
end
def cask_tokens
@cask_tokens ||= self.class.cask_tokens_from(@args)
end
def self.needs_init?
true
end
end

View File

@ -1,21 +1,25 @@
class Hbc::CLI::Base
def self.command_name
@command_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase
end
module Hbc
class CLI
class Base
def self.command_name
@command_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase
end
def self.visible
true
end
def self.visible
true
end
def self.cask_tokens_from(args)
args.reject { |a| a.empty? || a.chars.first == "-" }
end
def self.cask_tokens_from(args)
args.reject { |a| a.empty? || a.chars.first == "-" }
end
def self.help
"No help available for the #{command_name} command"
end
def self.help
"No help available for the #{command_name} command"
end
def self.needs_init?
false
def self.needs_init?
false
end
end
end
end

View File

@ -1,15 +1,19 @@
class Hbc::CLI::Cat < Hbc::CLI::Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
# only respects the first argument
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
cask_path = Hbc.path(cask_token)
raise Hbc::CaskUnavailableError, cask_token.to_s unless cask_path.exist?
puts File.open(cask_path, &:read)
end
module Hbc
class CLI
class Cat < Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise CaskUnspecifiedError if cask_tokens.empty?
# only respects the first argument
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
cask_path = Hbc.path(cask_token)
raise CaskUnavailableError, cask_token.to_s unless cask_path.exist?
puts File.open(cask_path, &:read)
end
def self.help
"dump raw source of the given Cask to the standard output"
def self.help
"dump raw source of the given Cask to the standard output"
end
end
end
end

View File

@ -1,108 +1,112 @@
class Hbc::CLI::Cleanup < Hbc::CLI::Base
OUTDATED_DAYS = 10
OUTDATED_TIMESTAMP = Time.now - (60 * 60 * 24 * OUTDATED_DAYS)
module Hbc
class CLI
class Cleanup < Base
OUTDATED_DAYS = 10
OUTDATED_TIMESTAMP = Time.now - (60 * 60 * 24 * OUTDATED_DAYS)
def self.help
"cleans up cached downloads and tracker symlinks"
end
def self.needs_init?
true
end
def self.run(*args)
if args.empty?
default.cleanup!
else
default.cleanup(args)
end
end
def self.default
@default ||= new(Hbc.cache, Hbc.cleanup_outdated)
end
attr_reader :cache_location, :outdated_only
def initialize(cache_location, outdated_only)
@cache_location = Pathname.new(cache_location)
@outdated_only = outdated_only
end
def cleanup!
remove_cache_files
end
def cleanup(tokens)
remove_cache_files(*tokens)
end
def cache_files
return [] unless cache_location.exist?
cache_location.children
.map(&method(:Pathname))
.reject(&method(:outdated?))
end
def outdated?(file)
outdated_only && file && file.stat.mtime > OUTDATED_TIMESTAMP
end
def incomplete?(file)
file.extname == ".incomplete"
end
def cache_incompletes
cache_files.select(&method(:incomplete?))
end
def cache_completes
cache_files.reject(&method(:incomplete?))
end
def disk_cleanup_size
Hbc::Utils.size_in_bytes(cache_files)
end
def remove_cache_files(*tokens)
message = "Removing cached downloads"
message.concat " for #{tokens.join(", ")}" unless tokens.empty?
message.concat " older than #{OUTDATED_DAYS} days old" if outdated_only
ohai message
deletable_cache_files = if tokens.empty?
cache_files
else
start_withs = tokens.map { |token| "#{token}--" }
cache_files.select { |path|
path.basename.to_s.start_with?(*start_withs)
}
end
delete_paths(deletable_cache_files)
end
def delete_paths(paths)
cleanup_size = 0
processed_files = 0
paths.each do |item|
next unless item.exist?
processed_files += 1
if Hbc::Utils.file_locked?(item)
puts "skipping: #{item} is locked"
next
def self.help
"cleans up cached downloads and tracker symlinks"
end
puts item
item_size = File.size?(item)
cleanup_size += item_size unless item_size.nil?
item.unlink
end
if processed_files.zero?
puts "Nothing to do"
else
disk_space = disk_usage_readable(cleanup_size)
ohai "This operation has freed approximately #{disk_space} of disk space."
def self.needs_init?
true
end
def self.run(*args)
if args.empty?
default.cleanup!
else
default.cleanup(args)
end
end
def self.default
@default ||= new(Hbc.cache, Hbc.cleanup_outdated)
end
attr_reader :cache_location, :outdated_only
def initialize(cache_location, outdated_only)
@cache_location = Pathname.new(cache_location)
@outdated_only = outdated_only
end
def cleanup!
remove_cache_files
end
def cleanup(tokens)
remove_cache_files(*tokens)
end
def cache_files
return [] unless cache_location.exist?
cache_location.children
.map(&method(:Pathname))
.reject(&method(:outdated?))
end
def outdated?(file)
outdated_only && file && file.stat.mtime > OUTDATED_TIMESTAMP
end
def incomplete?(file)
file.extname == ".incomplete"
end
def cache_incompletes
cache_files.select(&method(:incomplete?))
end
def cache_completes
cache_files.reject(&method(:incomplete?))
end
def disk_cleanup_size
Utils.size_in_bytes(cache_files)
end
def remove_cache_files(*tokens)
message = "Removing cached downloads"
message.concat " for #{tokens.join(", ")}" unless tokens.empty?
message.concat " older than #{OUTDATED_DAYS} days old" if outdated_only
ohai message
deletable_cache_files = if tokens.empty?
cache_files
else
start_withs = tokens.map { |token| "#{token}--" }
cache_files.select { |path|
path.basename.to_s.start_with?(*start_withs)
}
end
delete_paths(deletable_cache_files)
end
def delete_paths(paths)
cleanup_size = 0
processed_files = 0
paths.each do |item|
next unless item.exist?
processed_files += 1
if Utils.file_locked?(item)
puts "skipping: #{item} is locked"
next
end
puts item
item_size = File.size?(item)
cleanup_size += item_size unless item_size.nil?
item.unlink
end
if processed_files.zero?
puts "Nothing to do"
else
disk_space = disk_usage_readable(cleanup_size)
ohai "This operation has freed approximately #{disk_space} of disk space."
end
end
end
end
end

View File

@ -1,37 +1,41 @@
class Hbc::CLI::Create < Hbc::CLI::Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
cask_path = Hbc.path(cask_token)
odebug "Creating Cask #{cask_token}"
module Hbc
class CLI
class Create < Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise CaskUnspecifiedError if cask_tokens.empty?
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
cask_path = Hbc.path(cask_token)
odebug "Creating Cask #{cask_token}"
raise Hbc::CaskAlreadyCreatedError, cask_token if cask_path.exist?
raise CaskAlreadyCreatedError, cask_token if cask_path.exist?
File.open(cask_path, "w") do |f|
f.write template(cask_token)
end
File.open(cask_path, "w") do |f|
f.write template(cask_token)
end
exec_editor cask_path
end
def self.template(cask_token)
<<-EOS.undent
cask '#{cask_token}' do
version ''
sha256 ''
url 'https://'
name ''
homepage ''
license :unknown # TODO: change license and remove this comment; ':unknown' is a machine-generated placeholder
app ''
exec_editor cask_path
end
EOS
end
def self.help
"creates the given Cask and opens it in an editor"
def self.template(cask_token)
<<-EOS.undent
cask '#{cask_token}' do
version ''
sha256 ''
url 'https://'
name ''
homepage ''
license :unknown # TODO: change license and remove this comment; ':unknown' is a machine-generated placeholder
app ''
end
EOS
end
def self.help
"creates the given Cask and opens it in an editor"
end
end
end
end

View File

@ -1,205 +1,209 @@
class Hbc::CLI::Doctor < Hbc::CLI::Base
def self.run
ohai "macOS Release:", render_with_none_as_error(MacOS.full_version)
ohai "Hardware Architecture:", render_with_none_as_error("#{Hardware::CPU.type}-#{Hardware::CPU.bits}")
ohai "Ruby Version:", render_with_none_as_error("#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}")
ohai "Ruby Path:", render_with_none_as_error(RbConfig.ruby)
# TODO: consider removing most Homebrew constants from doctor output
ohai "Homebrew Version:", render_with_none_as_error(homebrew_version)
ohai "Homebrew Executable Path:", render_with_none_as_error(Hbc.homebrew_executable)
ohai "Homebrew Cellar Path:", render_with_none_as_error(homebrew_cellar)
ohai "Homebrew Repository Path:", render_with_none_as_error(homebrew_repository)
ohai "Homebrew Origin:", render_with_none_as_error(homebrew_origin)
ohai "Homebrew-Cask Version:", render_with_none_as_error(Hbc.full_version)
ohai "Homebrew-Cask Install Location:", render_install_location
ohai "Homebrew-Cask Staging Location:", render_staging_location(Hbc.caskroom)
ohai "Homebrew-Cask Cached Downloads:", render_cached_downloads
ohai "Homebrew-Cask Default Tap Path:", render_tap_paths(Hbc.default_tap.path)
ohai "Homebrew-Cask Alternate Cask Taps:", render_tap_paths(alt_taps)
ohai "Homebrew-Cask Default Tap Cask Count:", render_with_none_as_error(default_cask_count)
ohai "Contents of $LOAD_PATH:", render_load_path($LOAD_PATH)
ohai "Contents of $RUBYLIB Environment Variable:", render_env_var("RUBYLIB")
ohai "Contents of $RUBYOPT Environment Variable:", render_env_var("RUBYOPT")
ohai "Contents of $RUBYPATH Environment Variable:", render_env_var("RUBYPATH")
ohai "Contents of $RBENV_VERSION Environment Variable:", render_env_var("RBENV_VERSION")
ohai "Contents of $CHRUBY_VERSION Environment Variable:", render_env_var("CHRUBY_VERSION")
ohai "Contents of $GEM_HOME Environment Variable:", render_env_var("GEM_HOME")
ohai "Contents of $GEM_PATH Environment Variable:", render_env_var("GEM_PATH")
ohai "Contents of $BUNDLE_PATH Environment Variable:", render_env_var("BUNDLE_PATH")
ohai "Contents of $PATH Environment Variable:", render_env_var("PATH")
ohai "Contents of $SHELL Environment Variable:", render_env_var("SHELL")
ohai "Contents of Locale Environment Variables:", render_with_none(locale_variables)
ohai "Running As Privileged User:", render_with_none_as_error(privileged_uid)
end
def self.alt_taps
Tap.select { |t| t.cask_dir && t != Hbc.default_tap }
.map(&:path)
end
def self.default_cask_count
Hbc.default_tap.cask_files.count
rescue StandardError
"0 #{error_string "Error reading #{Hbc.default_tap.path}"}"
end
def self.homebrew_origin
homebrew_origin = notfound_string
begin
Dir.chdir(homebrew_repository) do
homebrew_origin = Hbc::SystemCommand.run("/usr/bin/git",
args: %w[config --get remote.origin.url],
print_stderr: false).stdout.strip
module Hbc
class CLI
class Doctor < Base
def self.run
ohai "macOS Release:", render_with_none_as_error(MacOS.full_version)
ohai "Hardware Architecture:", render_with_none_as_error("#{Hardware::CPU.type}-#{Hardware::CPU.bits}")
ohai "Ruby Version:", render_with_none_as_error("#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}")
ohai "Ruby Path:", render_with_none_as_error(RbConfig.ruby)
# TODO: consider removing most Homebrew constants from doctor output
ohai "Homebrew Version:", render_with_none_as_error(homebrew_version)
ohai "Homebrew Executable Path:", render_with_none_as_error(Hbc.homebrew_executable)
ohai "Homebrew Cellar Path:", render_with_none_as_error(homebrew_cellar)
ohai "Homebrew Repository Path:", render_with_none_as_error(homebrew_repository)
ohai "Homebrew Origin:", render_with_none_as_error(homebrew_origin)
ohai "Homebrew-Cask Version:", render_with_none_as_error(Hbc.full_version)
ohai "Homebrew-Cask Install Location:", render_install_location
ohai "Homebrew-Cask Staging Location:", render_staging_location(Hbc.caskroom)
ohai "Homebrew-Cask Cached Downloads:", render_cached_downloads
ohai "Homebrew-Cask Default Tap Path:", render_tap_paths(Hbc.default_tap.path)
ohai "Homebrew-Cask Alternate Cask Taps:", render_tap_paths(alt_taps)
ohai "Homebrew-Cask Default Tap Cask Count:", render_with_none_as_error(default_cask_count)
ohai "Contents of $LOAD_PATH:", render_load_path($LOAD_PATH)
ohai "Contents of $RUBYLIB Environment Variable:", render_env_var("RUBYLIB")
ohai "Contents of $RUBYOPT Environment Variable:", render_env_var("RUBYOPT")
ohai "Contents of $RUBYPATH Environment Variable:", render_env_var("RUBYPATH")
ohai "Contents of $RBENV_VERSION Environment Variable:", render_env_var("RBENV_VERSION")
ohai "Contents of $CHRUBY_VERSION Environment Variable:", render_env_var("CHRUBY_VERSION")
ohai "Contents of $GEM_HOME Environment Variable:", render_env_var("GEM_HOME")
ohai "Contents of $GEM_PATH Environment Variable:", render_env_var("GEM_PATH")
ohai "Contents of $BUNDLE_PATH Environment Variable:", render_env_var("BUNDLE_PATH")
ohai "Contents of $PATH Environment Variable:", render_env_var("PATH")
ohai "Contents of $SHELL Environment Variable:", render_env_var("SHELL")
ohai "Contents of Locale Environment Variables:", render_with_none(locale_variables)
ohai "Running As Privileged User:", render_with_none_as_error(privileged_uid)
end
if homebrew_origin !~ %r{\S}
homebrew_origin = "#{none_string} #{error_string}"
elsif homebrew_origin !~ %r{(mxcl|Homebrew)/(home)?brew(\.git)?\Z}
homebrew_origin.concat " #{error_string "warning: nonstandard origin"}"
def self.alt_taps
Tap.select { |t| t.cask_dir && t != Hbc.default_tap }
.map(&:path)
end
rescue StandardError
homebrew_origin = error_string "Not Found - Error running git"
end
homebrew_origin
end
def self.homebrew_repository
homebrew_constants("repository")
end
def self.homebrew_cellar
homebrew_constants("cellar")
end
def self.homebrew_version
homebrew_constants("version")
end
def self.homebrew_taps
@homebrew_taps ||= if homebrew_repository.respond_to?(:join)
homebrew_repository.join("Library", "Taps")
end
end
def self.homebrew_constants(name)
@homebrew_constants ||= {}
return @homebrew_constants[name] if @homebrew_constants.key?(name)
@homebrew_constants[name] = notfound_string
begin
@homebrew_constants[name] = Hbc::SystemCommand.run!(Hbc.homebrew_executable,
args: ["--#{name}"],
print_stderr: false)
.stdout
.strip
if @homebrew_constants[name] !~ %r{\S}
@homebrew_constants[name] = "#{none_string} #{error_string}"
def self.default_cask_count
Hbc.default_tap.cask_files.count
rescue StandardError
"0 #{error_string "Error reading #{Hbc.default_tap.path}"}"
end
path = Pathname.new(@homebrew_constants[name])
@homebrew_constants[name] = path if path.exist?
rescue StandardError
@homebrew_constants[name] = error_string "Not Found - Error running brew"
end
@homebrew_constants[name]
end
def self.locale_variables
ENV.keys.grep(%r{^(?:LC_\S+|LANG|LANGUAGE)\Z}).collect { |v| %Q{#{v}="#{ENV[v]}"} }.sort.join("\n")
end
def self.homebrew_origin
homebrew_origin = notfound_string
begin
Dir.chdir(homebrew_repository) do
homebrew_origin = SystemCommand.run("/usr/bin/git",
args: %w[config --get remote.origin.url],
print_stderr: false).stdout.strip
end
if homebrew_origin !~ %r{\S}
homebrew_origin = "#{none_string} #{error_string}"
elsif homebrew_origin !~ %r{(mxcl|Homebrew)/(home)?brew(\.git)?\Z}
homebrew_origin.concat " #{error_string "warning: nonstandard origin"}"
end
rescue StandardError
homebrew_origin = error_string "Not Found - Error running git"
end
homebrew_origin
end
def self.privileged_uid
Process.euid.zero? ? "Yes #{error_string "warning: not recommended"}" : "No"
rescue StandardError
notfound_string
end
def self.homebrew_repository
homebrew_constants("repository")
end
def self.none_string
"<NONE>"
end
def self.homebrew_cellar
homebrew_constants("cellar")
end
def self.legacy_tap_pattern
%r{phinze}
end
def self.homebrew_version
homebrew_constants("version")
end
def self.notfound_string
"#{Tty.red}Not Found - Unknown Error#{Tty.reset}"
end
def self.homebrew_taps
@homebrew_taps ||= if homebrew_repository.respond_to?(:join)
homebrew_repository.join("Library", "Taps")
end
end
def self.error_string(string = "Error")
"#{Tty.red}(#{string})#{Tty.reset}"
end
def self.homebrew_constants(name)
@homebrew_constants ||= {}
return @homebrew_constants[name] if @homebrew_constants.key?(name)
@homebrew_constants[name] = notfound_string
begin
@homebrew_constants[name] = SystemCommand.run!(Hbc.homebrew_executable,
args: ["--#{name}"],
print_stderr: false)
.stdout
.strip
if @homebrew_constants[name] !~ %r{\S}
@homebrew_constants[name] = "#{none_string} #{error_string}"
end
path = Pathname.new(@homebrew_constants[name])
@homebrew_constants[name] = path if path.exist?
rescue StandardError
@homebrew_constants[name] = error_string "Not Found - Error running brew"
end
@homebrew_constants[name]
end
def self.render_with_none(string)
return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty?
none_string
end
def self.locale_variables
ENV.keys.grep(%r{^(?:LC_\S+|LANG|LANGUAGE)\Z}).collect { |v| %Q{#{v}="#{ENV[v]}"} }.sort.join("\n")
end
def self.render_with_none_as_error(string)
return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty?
"#{none_string} #{error_string}"
end
def self.privileged_uid
Process.euid.zero? ? "Yes #{error_string "warning: not recommended"}" : "No"
rescue StandardError
notfound_string
end
def self.render_tap_paths(paths)
paths = [paths] unless paths.respond_to?(:each)
paths.collect do |dir|
if dir.nil? || dir.to_s.empty?
def self.none_string
"<NONE>"
end
def self.legacy_tap_pattern
%r{phinze}
end
def self.notfound_string
"#{Tty.red}Not Found - Unknown Error#{Tty.reset}"
end
def self.error_string(string = "Error")
"#{Tty.red}(#{string})#{Tty.reset}"
end
def self.render_with_none(string)
return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty?
none_string
elsif dir.to_s.match(legacy_tap_pattern)
dir.to_s.concat(" #{error_string "Warning: legacy tap path"}")
else
dir.to_s
end
def self.render_with_none_as_error(string)
return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty?
"#{none_string} #{error_string}"
end
def self.render_tap_paths(paths)
paths = [paths] unless paths.respond_to?(:each)
paths.collect do |dir|
if dir.nil? || dir.to_s.empty?
none_string
elsif dir.to_s.match(legacy_tap_pattern)
dir.to_s.concat(" #{error_string "Warning: legacy tap path"}")
else
dir.to_s
end
end
end
def self.render_env_var(var)
if ENV.key?(var)
%Q{#{var}="#{ENV[var]}"}
else
none_string
end
end
# This could be done by calling into Homebrew, but the situation
# where "doctor" is needed is precisely the situation where such
# things are less dependable.
def self.render_install_location
locations = Dir.glob(Pathname.new(homebrew_cellar).join("brew-cask", "*")).reverse
if locations.empty?
none_string
else
locations.collect do |l|
"#{l} #{error_string 'error: legacy install. Run "brew uninstall --force brew-cask".'}"
end
end
end
def self.render_staging_location(path)
path = Pathname.new(path)
if !path.exist?
"#{path} #{error_string "error: path does not exist"}}"
elsif !path.writable?
"#{path} #{error_string "error: not writable by current user"}"
else
path
end
end
def self.render_load_path(paths)
return "#{none_string} #{error_string}" if [*paths].empty?
paths
end
def self.render_cached_downloads
cleanup = CLI::Cleanup.default
files = cleanup.cache_files
count = files.count
size = cleanup.disk_cleanup_size
size_msg = "#{number_readable(count)} files, #{disk_usage_readable(size)}"
warn_msg = error_string('warning: run "brew cask cleanup"')
size_msg << " #{warn_msg}" if count > 0
[Hbc.cache, size_msg]
end
def self.help
"checks for configuration issues"
end
end
end
def self.render_env_var(var)
if ENV.key?(var)
%Q{#{var}="#{ENV[var]}"}
else
none_string
end
end
# This could be done by calling into Homebrew, but the situation
# where "doctor" is needed is precisely the situation where such
# things are less dependable.
def self.render_install_location
locations = Dir.glob(Pathname.new(homebrew_cellar).join("brew-cask", "*")).reverse
if locations.empty?
none_string
else
locations.collect do |l|
"#{l} #{error_string 'error: legacy install. Run "brew uninstall --force brew-cask".'}"
end
end
end
def self.render_staging_location(path)
path = Pathname.new(path)
if !path.exist?
"#{path} #{error_string "error: path does not exist"}}"
elsif !path.writable?
"#{path} #{error_string "error: not writable by current user"}"
else
path
end
end
def self.render_load_path(paths)
return "#{none_string} #{error_string}" if [*paths].empty?
paths
end
def self.render_cached_downloads
cleanup = Hbc::CLI::Cleanup.default
files = cleanup.cache_files
count = files.count
size = cleanup.disk_cleanup_size
size_msg = "#{number_readable(count)} files, #{disk_usage_readable(size)}"
warn_msg = error_string('warning: run "brew cask cleanup"')
size_msg << " #{warn_msg}" if count > 0
[Hbc.cache, size_msg]
end
def self.help
"checks for configuration issues"
end
end

View File

@ -1,18 +1,22 @@
class Hbc::CLI::Edit < Hbc::CLI::Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
# only respects the first argument
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
cask_path = Hbc.path(cask_token)
odebug "Opening editor for Cask #{cask_token}"
unless cask_path.exist?
raise Hbc::CaskUnavailableError, %Q{#{cask_token}, run "brew cask create #{cask_token}" to create a new Cask}
end
exec_editor cask_path
end
module Hbc
class CLI
class Edit < Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise CaskUnspecifiedError if cask_tokens.empty?
# only respects the first argument
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
cask_path = Hbc.path(cask_token)
odebug "Opening editor for Cask #{cask_token}"
unless cask_path.exist?
raise CaskUnavailableError, %Q{#{cask_token}, run "brew cask create #{cask_token}" to create a new Cask}
end
exec_editor cask_path
end
def self.help
"edits the given Cask"
def self.help
"edits the given Cask"
end
end
end
end

View File

@ -1,19 +1,23 @@
class Hbc::CLI::Fetch < Hbc::CLI::Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
force = args.include? "--force"
module Hbc
class CLI
class Fetch < Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise CaskUnspecifiedError if cask_tokens.empty?
force = args.include? "--force"
cask_tokens.each do |cask_token|
ohai "Downloading external files for Cask #{cask_token}"
cask = Hbc.load(cask_token)
downloaded_path = Hbc::Download.new(cask, force: force).perform
Hbc::Verify.all(cask, downloaded_path)
ohai "Success! Downloaded to -> #{downloaded_path}"
cask_tokens.each do |cask_token|
ohai "Downloading external files for Cask #{cask_token}"
cask = Hbc.load(cask_token)
downloaded_path = Download.new(cask, force: force).perform
Verify.all(cask, downloaded_path)
ohai "Success! Downloaded to -> #{downloaded_path}"
end
end
def self.help
"downloads remote application files to local cache"
end
end
end
def self.help
"downloads remote application files to local cache"
end
end

View File

@ -1,18 +1,22 @@
class Hbc::CLI::Home < Hbc::CLI::Base
def self.run(*cask_tokens)
if cask_tokens.empty?
odebug "Opening project homepage"
system "/usr/bin/open", "--", "http://caskroom.io/"
else
cask_tokens.each do |cask_token|
odebug "Opening homepage for Cask #{cask_token}"
cask = Hbc.load(cask_token)
system "/usr/bin/open", "--", cask.homepage
module Hbc
class CLI
class Home < Base
def self.run(*cask_tokens)
if cask_tokens.empty?
odebug "Opening project homepage"
system "/usr/bin/open", "--", "http://caskroom.io/"
else
cask_tokens.each do |cask_token|
odebug "Opening homepage for Cask #{cask_token}"
cask = Hbc.load(cask_token)
system "/usr/bin/open", "--", cask.homepage
end
end
end
def self.help
"opens the homepage of the given Cask"
end
end
end
def self.help
"opens the homepage of the given Cask"
end
end

View File

@ -1,65 +1,69 @@
class Hbc::CLI::Info < Hbc::CLI::Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
cask_tokens.each do |cask_token|
odebug "Getting info for Cask #{cask_token}"
cask = Hbc.load(cask_token)
module Hbc
class CLI
class Info < Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise CaskUnspecifiedError if cask_tokens.empty?
cask_tokens.each do |cask_token|
odebug "Getting info for Cask #{cask_token}"
cask = Hbc.load(cask_token)
info(cask)
end
end
def self.help
"displays information about the given Cask"
end
def self.info(cask)
puts "#{cask.token}: #{cask.version}"
puts formatted_url(cask.homepage) if cask.homepage
installation_info(cask)
puts "From: #{formatted_url(github_info(cask))}" if github_info(cask)
name_info(cask)
artifact_info(cask)
Hbc::Installer.print_caveats(cask)
end
def self.formatted_url(url)
"#{Tty.em}#{url}#{Tty.reset}"
end
def self.installation_info(cask)
if cask.installed?
cask.versions.each do |version|
versioned_staged_path = cask.caskroom_path.join(version)
puts versioned_staged_path.to_s
.concat(" (")
.concat(versioned_staged_path.exist? ? versioned_staged_path.abv : "#{Tty.red}does not exist#{Tty.reset}")
.concat(")")
info(cask)
end
end
else
puts "Not installed"
end
end
def self.name_info(cask)
ohai cask.name.size > 1 ? "Names" : "Name"
puts cask.name.empty? ? "#{Tty.red}None#{Tty.reset}" : cask.name
end
def self.help
"displays information about the given Cask"
end
def self.github_info(cask)
user, repo, token = Hbc::QualifiedToken.parse(Hbc.all_tokens.detect { |t| t.split("/").last == cask.token })
"#{Tap.fetch(user, repo).default_remote}/blob/master/Casks/#{token}.rb"
end
def self.info(cask)
puts "#{cask.token}: #{cask.version}"
puts formatted_url(cask.homepage) if cask.homepage
installation_info(cask)
puts "From: #{formatted_url(github_info(cask))}" if github_info(cask)
name_info(cask)
artifact_info(cask)
Installer.print_caveats(cask)
end
def self.artifact_info(cask)
ohai "Artifacts"
Hbc::DSL::ORDINARY_ARTIFACT_TYPES.each do |type|
next if cask.artifacts[type].empty?
cask.artifacts[type].each do |artifact|
activatable_item = type == :stage_only ? "<none>" : artifact.first
puts "#{activatable_item} (#{type})"
def self.formatted_url(url)
"#{Tty.em}#{url}#{Tty.reset}"
end
def self.installation_info(cask)
if cask.installed?
cask.versions.each do |version|
versioned_staged_path = cask.caskroom_path.join(version)
puts versioned_staged_path.to_s
.concat(" (")
.concat(versioned_staged_path.exist? ? versioned_staged_path.abv : "#{Tty.red}does not exist#{Tty.reset}")
.concat(")")
end
else
puts "Not installed"
end
end
def self.name_info(cask)
ohai cask.name.size > 1 ? "Names" : "Name"
puts cask.name.empty? ? "#{Tty.red}None#{Tty.reset}" : cask.name
end
def self.github_info(cask)
user, repo, token = QualifiedToken.parse(Hbc.all_tokens.detect { |t| t.split("/").last == cask.token })
"#{Tap.fetch(user, repo).default_remote}/blob/master/Casks/#{token}.rb"
end
def self.artifact_info(cask)
ohai "Artifacts"
DSL::ORDINARY_ARTIFACT_TYPES.each do |type|
next if cask.artifacts[type].empty?
cask.artifacts[type].each do |artifact|
activatable_item = type == :stage_only ? "<none>" : artifact.first
puts "#{activatable_item} (#{type})"
end
end
end
end
end

View File

@ -1,60 +1,63 @@
module Hbc
class CLI
class Install < Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise CaskUnspecifiedError if cask_tokens.empty?
force = args.include? "--force"
skip_cask_deps = args.include? "--skip-cask-deps"
require_sha = args.include? "--require-sha"
retval = install_casks cask_tokens, force, skip_cask_deps, require_sha
# retval is ternary: true/false/nil
class Hbc::CLI::Install < Hbc::CLI::Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
force = args.include? "--force"
skip_cask_deps = args.include? "--skip-cask-deps"
require_sha = args.include? "--require-sha"
retval = install_casks cask_tokens, force, skip_cask_deps, require_sha
# retval is ternary: true/false/nil
raise CaskError, "nothing to install" if retval.nil?
raise CaskError, "install incomplete" unless retval
end
raise Hbc::CaskError, "nothing to install" if retval.nil?
raise Hbc::CaskError, "install incomplete" unless retval
end
def self.install_casks(cask_tokens, force, skip_cask_deps, require_sha)
count = 0
cask_tokens.each do |cask_token|
begin
cask = Hbc.load(cask_token)
Installer.new(cask,
force: force,
skip_cask_deps: skip_cask_deps,
require_sha: require_sha).install
count += 1
rescue CaskAlreadyInstalledError => e
opoo e.message
count += 1
rescue CaskAutoUpdatesError => e
opoo e.message
count += 1
rescue CaskUnavailableError => e
warn_unavailable_with_suggestion cask_token, e
rescue CaskNoShasumError => e
opoo e.message
count += 1
end
end
count.zero? ? nil : count == cask_tokens.length
end
def self.install_casks(cask_tokens, force, skip_cask_deps, require_sha)
count = 0
cask_tokens.each do |cask_token|
begin
cask = Hbc.load(cask_token)
Hbc::Installer.new(cask,
force: force,
skip_cask_deps: skip_cask_deps,
require_sha: require_sha).install
count += 1
rescue Hbc::CaskAlreadyInstalledError => e
opoo e.message
count += 1
rescue Hbc::CaskAutoUpdatesError => e
opoo e.message
count += 1
rescue Hbc::CaskUnavailableError => e
warn_unavailable_with_suggestion cask_token, e
rescue Hbc::CaskNoShasumError => e
opoo e.message
count += 1
def self.warn_unavailable_with_suggestion(cask_token, e)
exact_match, partial_matches = Search.search(cask_token)
errmsg = e.message
if exact_match
errmsg.concat(". Did you mean:\n#{exact_match}")
elsif !partial_matches.empty?
errmsg.concat(". Did you mean one of:\n#{puts_columns(partial_matches.take(20))}\n")
end
onoe errmsg
end
def self.help
"installs the given Cask"
end
def self.needs_init?
true
end
end
count.zero? ? nil : count == cask_tokens.length
end
def self.warn_unavailable_with_suggestion(cask_token, e)
exact_match, partial_matches = Hbc::CLI::Search.search(cask_token)
errmsg = e.message
if exact_match
errmsg.concat(". Did you mean:\n#{exact_match}")
elsif !partial_matches.empty?
errmsg.concat(". Did you mean one of:\n#{puts_columns(partial_matches.take(20))}\n")
end
onoe errmsg
end
def self.help
"installs the given Cask"
end
def self.needs_init?
true
end
end

View File

@ -1,135 +1,139 @@
class Hbc::CLI::InternalAuditModifiedCasks < Hbc::CLI::InternalUseBase
RELEVANT_STANZAS = %i{version sha256 url appcast}.freeze
module Hbc
class CLI
class InternalAuditModifiedCasks < InternalUseBase
RELEVANT_STANZAS = %i{version sha256 url appcast}.freeze
class << self
def needs_init?
true
end
class << self
def needs_init?
true
end
def run(*args)
commit_range = commit_range(args)
cleanup = args.any? { |a| a =~ %r{^-+c(leanup)?$}i }
new(commit_range, cleanup: cleanup).run
end
def run(*args)
commit_range = commit_range(args)
cleanup = args.any? { |a| a =~ %r{^-+c(leanup)?$}i }
new(commit_range, cleanup: cleanup).run
end
def commit_range(args)
posargs = args.reject { |a| a.empty? || a.chars.first == "-" }
odie usage unless posargs.size == 1
posargs.first
end
def commit_range(args)
posargs = args.reject { |a| a.empty? || a.chars.first == "-" }
odie usage unless posargs.size == 1
posargs.first
end
def posargs(args)
args.reject { |a| a.empty? || a.chars.first == "-" }
end
def posargs(args)
args.reject { |a| a.empty? || a.chars.first == "-" }
end
def usage
<<-EOS.undent
Usage: brew cask _audit_modified_casks [options...] <commit range>
def usage
<<-EOS.undent
Usage: brew cask _audit_modified_casks [options...] <commit range>
Given a range of Git commits, find any Casks that were modified and run `brew
cask audit' on them. If the `url', `version', or `sha256' stanzas were modified,
run with the `--download' flag to verify the hash.
Given a range of Git commits, find any Casks that were modified and run `brew
cask audit' on them. If the `url', `version', or `sha256' stanzas were modified,
run with the `--download' flag to verify the hash.
Options:
-c, --cleanup
Remove all cached downloads. Use with care.
EOS
end
end
Options:
-c, --cleanup
Remove all cached downloads. Use with care.
EOS
end
end
def initialize(commit_range, cleanup: false)
@commit_range = commit_range
@cleanup = cleanup
end
def initialize(commit_range, cleanup: false)
@commit_range = commit_range
@cleanup = cleanup
end
attr_reader :commit_range
attr_reader :commit_range
def cleanup?
@cleanup
end
def cleanup?
@cleanup
end
def run
at_exit do
cleanup
end
def run
at_exit do
cleanup
end
Dir.chdir git_root do
modified_cask_files.zip(modified_casks).each do |cask_file, cask|
audit(cask, cask_file)
Dir.chdir git_root do
modified_cask_files.zip(modified_casks).each do |cask_file, cask|
audit(cask, cask_file)
end
end
report_failures
end
def git_root
@git_root ||= git("rev-parse", "--show-toplevel")
end
def modified_cask_files
@modified_cask_files ||= git_filter_cask_files("AM")
end
def added_cask_files
@added_cask_files ||= git_filter_cask_files("A")
end
def git_filter_cask_files(filter)
git("diff", "--name-only", "--diff-filter=#{filter}", commit_range,
"--", Pathname.new(git_root).join("Casks", "*.rb").to_s).split("\n")
end
def modified_casks
return @modified_casks if defined? @modified_casks
@modified_casks = modified_cask_files.map { |f| Hbc.load(f) }
if @modified_casks.any?
num_modified = @modified_casks.size
ohai "#{num_modified} modified #{pluralize("cask", num_modified)}: " \
"#{@modified_casks.join(" ")}"
end
@modified_casks
end
def audit(cask, cask_file)
audit_download = audit_download?(cask, cask_file)
check_token_conflicts = added_cask_files.include?(cask_file)
success = Auditor.audit(cask, audit_download: audit_download,
check_token_conflicts: check_token_conflicts)
failed_casks << cask unless success
end
def failed_casks
@failed_casks ||= []
end
def audit_download?(cask, cask_file)
cask.sha256 != :no_check && relevant_stanza_modified?(cask_file)
end
def relevant_stanza_modified?(cask_file)
out = git("diff", commit_range, "--", cask_file)
out =~ %r{^\+\s*(#{RELEVANT_STANZAS.join('|')})}
end
def git(*args)
odebug ["git", *args].join(" ")
out, err, status = Open3.capture3("git", *args)
return out.chomp if status.success?
odie err.chomp
end
def report_failures
return if failed_casks.empty?
num_failed = failed_casks.size
cask_pluralized = pluralize("cask", num_failed)
odie "audit failed for #{num_failed} #{cask_pluralized}: " \
"#{failed_casks.join(" ")}"
end
def pluralize(str, num)
num == 1 ? str : "#{str}s"
end
def cleanup
Cleanup.run if cleanup?
end
end
report_failures
end
def git_root
@git_root ||= git("rev-parse", "--show-toplevel")
end
def modified_cask_files
@modified_cask_files ||= git_filter_cask_files("AM")
end
def added_cask_files
@added_cask_files ||= git_filter_cask_files("A")
end
def git_filter_cask_files(filter)
git("diff", "--name-only", "--diff-filter=#{filter}", commit_range,
"--", Pathname.new(git_root).join("Casks", "*.rb").to_s).split("\n")
end
def modified_casks
return @modified_casks if defined? @modified_casks
@modified_casks = modified_cask_files.map { |f| Hbc.load(f) }
if @modified_casks.any?
num_modified = @modified_casks.size
ohai "#{num_modified} modified #{pluralize("cask", num_modified)}: " \
"#{@modified_casks.join(" ")}"
end
@modified_casks
end
def audit(cask, cask_file)
audit_download = audit_download?(cask, cask_file)
check_token_conflicts = added_cask_files.include?(cask_file)
success = Hbc::Auditor.audit(cask, audit_download: audit_download,
check_token_conflicts: check_token_conflicts)
failed_casks << cask unless success
end
def failed_casks
@failed_casks ||= []
end
def audit_download?(cask, cask_file)
cask.sha256 != :no_check && relevant_stanza_modified?(cask_file)
end
def relevant_stanza_modified?(cask_file)
out = git("diff", commit_range, "--", cask_file)
out =~ %r{^\+\s*(#{RELEVANT_STANZAS.join('|')})}
end
def git(*args)
odebug ["git", *args].join(" ")
out, err, status = Open3.capture3("git", *args)
return out.chomp if status.success?
odie err.chomp
end
def report_failures
return if failed_casks.empty?
num_failed = failed_casks.size
cask_pluralized = pluralize("cask", num_failed)
odie "audit failed for #{num_failed} #{cask_pluralized}: " \
"#{failed_casks.join(" ")}"
end
def pluralize(str, num)
num == 1 ? str : "#{str}s"
end
def cleanup
Hbc::CLI::Cleanup.run if cleanup?
end
end

View File

@ -1,15 +1,19 @@
class Hbc::CLI::InternalCheckurl < Hbc::CLI::InternalUseBase
def self.run(*args)
casks_to_check = args.empty? ? Hbc.all : args.map { |arg| Hbc.load(arg) }
casks_to_check.each do |cask|
odebug "Checking URL for Cask #{cask}"
checker = Hbc::UrlChecker.new(cask)
checker.run
puts checker.summary
module Hbc
class CLI
class InternalCheckurl < InternalUseBase
def self.run(*args)
casks_to_check = args.empty? ? Hbc.all : args.map { |arg| Hbc.load(arg) }
casks_to_check.each do |cask|
odebug "Checking URL for Cask #{cask}"
checker = UrlChecker.new(cask)
checker.run
puts checker.summary
end
end
def self.help
"checks for bad Cask URLs"
end
end
end
def self.help
"checks for bad Cask URLs"
end
end

View File

@ -1,30 +1,34 @@
class Hbc::CLI::InternalDump < Hbc::CLI::InternalUseBase
def self.run(*arguments)
cask_tokens = cask_tokens_from(arguments)
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
retval = dump_casks(*cask_tokens)
# retval is ternary: true/false/nil
module Hbc
class CLI
class InternalDump < InternalUseBase
def self.run(*arguments)
cask_tokens = cask_tokens_from(arguments)
raise CaskUnspecifiedError if cask_tokens.empty?
retval = dump_casks(*cask_tokens)
# retval is ternary: true/false/nil
raise Hbc::CaskError, "nothing to dump" if retval.nil?
raise Hbc::CaskError, "dump incomplete" unless retval
end
raise CaskError, "nothing to dump" if retval.nil?
raise CaskError, "dump incomplete" unless retval
end
def self.dump_casks(*cask_tokens)
Hbc.debug = true # Yuck. At the moment this is the only way to make dumps visible
count = 0
cask_tokens.each do |cask_token|
begin
cask = Hbc.load(cask_token)
count += 1
cask.dumpcask
rescue StandardError => e
opoo "#{cask_token} was not found or would not load: #{e}"
def self.dump_casks(*cask_tokens)
Hbc.debug = true # Yuck. At the moment this is the only way to make dumps visible
count = 0
cask_tokens.each do |cask_token|
begin
cask = Hbc.load(cask_token)
count += 1
cask.dumpcask
rescue StandardError => e
opoo "#{cask_token} was not found or would not load: #{e}"
end
end
count.zero? ? nil : count == cask_tokens.length
end
def self.help
"Dump the given Cask in YAML format"
end
end
count.zero? ? nil : count == cask_tokens.length
end
def self.help
"Dump the given Cask in YAML format"
end
end

View File

@ -1,19 +1,23 @@
class Hbc::CLI::InternalHelp < Hbc::CLI::InternalUseBase
def self.run(*_ignored)
max_command_len = Hbc::CLI.commands.map(&:length).max
puts "Unstable Internal-use Commands:\n\n"
Hbc::CLI.command_classes.each do |klass|
next if klass.visible
puts " #{klass.command_name.ljust(max_command_len)} #{help_for(klass)}"
module Hbc
class CLI
class InternalHelp < InternalUseBase
def self.run(*_ignored)
max_command_len = CLI.commands.map(&:length).max
puts "Unstable Internal-use Commands:\n\n"
CLI.command_classes.each do |klass|
next if klass.visible
puts " #{klass.command_name.ljust(max_command_len)} #{help_for(klass)}"
end
puts "\n"
end
def self.help_for(klass)
klass.respond_to?(:help) ? klass.help : nil
end
def self.help
"Print help strings for unstable internal-use commands"
end
end
puts "\n"
end
def self.help_for(klass)
klass.respond_to?(:help) ? klass.help : nil
end
def self.help
"Print help strings for unstable internal-use commands"
end
end

View File

@ -1,127 +1,131 @@
class Hbc::CLI::InternalStanza < Hbc::CLI::InternalUseBase
# Syntax
#
# brew cask _stanza <stanza_name> [ --table | --yaml | --inspect | --quiet ] [ <cask_token> ... ]
#
# If no tokens are given, then data for all Casks is returned.
#
# The pseudo-stanza "artifacts" is available.
#
# On failure, a blank line is returned on the standard output.
#
# Examples
#
# brew cask _stanza appcast --table
# brew cask _stanza app --table alfred google-chrome adium voicemac logisim vagrant
# brew cask _stanza url --table alfred google-chrome adium voicemac logisim vagrant
# brew cask _stanza version --table alfred google-chrome adium voicemac logisim vagrant
# brew cask _stanza artifacts --table --inspect alfred google-chrome adium voicemac logisim vagrant
# brew cask _stanza artifacts --table --yaml alfred google-chrome adium voicemac logisim vagrant
#
module Hbc
class CLI
class InternalStanza < InternalUseBase
# Syntax
#
# brew cask _stanza <stanza_name> [ --table | --yaml | --inspect | --quiet ] [ <cask_token> ... ]
#
# If no tokens are given, then data for all Casks is returned.
#
# The pseudo-stanza "artifacts" is available.
#
# On failure, a blank line is returned on the standard output.
#
# Examples
#
# brew cask _stanza appcast --table
# brew cask _stanza app --table alfred google-chrome adium voicemac logisim vagrant
# brew cask _stanza url --table alfred google-chrome adium voicemac logisim vagrant
# brew cask _stanza version --table alfred google-chrome adium voicemac logisim vagrant
# brew cask _stanza artifacts --table --inspect alfred google-chrome adium voicemac logisim vagrant
# brew cask _stanza artifacts --table --yaml alfred google-chrome adium voicemac logisim vagrant
#
# TODO: this should be retrievable from Hbc::DSL
ARTIFACTS = Set.new [
:app,
:suite,
:artifact,
:prefpane,
:qlplugin,
:font,
:service,
:colorpicker,
:binary,
:input_method,
:internet_plugin,
:audio_unit_plugin,
:vst_plugin,
:vst3_plugin,
:screen_saver,
:pkg,
:installer,
:stage_only,
:nested_container,
:uninstall,
:postflight,
:uninstall_postflight,
:preflight,
:uninstall_postflight,
]
# TODO: this should be retrievable from Hbc::DSL
ARTIFACTS = Set.new [
:app,
:suite,
:artifact,
:prefpane,
:qlplugin,
:font,
:service,
:colorpicker,
:binary,
:input_method,
:internet_plugin,
:audio_unit_plugin,
:vst_plugin,
:vst3_plugin,
:screen_saver,
:pkg,
:installer,
:stage_only,
:nested_container,
:uninstall,
:postflight,
:uninstall_postflight,
:preflight,
:uninstall_postflight,
]
def self.run(*arguments)
table = arguments.include? "--table"
quiet = arguments.include? "--quiet"
format = :to_yaml if arguments.include? "--yaml"
format = :inspect if arguments.include? "--inspect"
cask_tokens = arguments.reject { |arg| arg.chars.first == "-" }
stanza = cask_tokens.shift.to_sym
cask_tokens = Hbc.all_tokens if cask_tokens.empty?
def self.run(*arguments)
table = arguments.include? "--table"
quiet = arguments.include? "--quiet"
format = :to_yaml if arguments.include? "--yaml"
format = :inspect if arguments.include? "--inspect"
cask_tokens = arguments.reject { |arg| arg.chars.first == "-" }
stanza = cask_tokens.shift.to_sym
cask_tokens = Hbc.all_tokens if cask_tokens.empty?
retval = print_stanzas(stanza, format, table, quiet, *cask_tokens)
retval = print_stanzas(stanza, format, table, quiet, *cask_tokens)
# retval is ternary: true/false/nil
if retval.nil?
exit 1 if quiet
raise Hbc::CaskError, "nothing to print"
elsif !retval
exit 1 if quiet
raise Hbc::CaskError, "print incomplete"
# retval is ternary: true/false/nil
if retval.nil?
exit 1 if quiet
raise CaskError, "nothing to print"
elsif !retval
exit 1 if quiet
raise CaskError, "print incomplete"
end
end
def self.print_stanzas(stanza, format = nil, table = nil, quiet = nil, *cask_tokens)
count = 0
if ARTIFACTS.include?(stanza)
artifact_name = stanza
stanza = :artifacts
end
cask_tokens.each do |cask_token|
print "#{cask_token}\t" if table
begin
cask = Hbc.load(cask_token)
rescue StandardError
opoo "Cask '#{cask_token}' was not found" unless quiet
puts ""
next
end
unless cask.respond_to?(stanza)
opoo "no such stanza '#{stanza}' on Cask '#{cask_token}'" unless quiet
puts ""
next
end
begin
value = cask.send(stanza)
rescue StandardError
opoo "failure calling '#{stanza}' on Cask '#{cask_token}'" unless quiet
puts ""
next
end
if artifact_name && !value.key?(artifact_name)
opoo "no such stanza '#{artifact_name}' on Cask '#{cask_token}'" unless quiet
puts ""
next
end
value = value.fetch(artifact_name).to_a.flatten if artifact_name
if format
puts value.send(format)
elsif artifact_name || value.is_a?(Symbol)
puts value.inspect
else
puts value.to_s
end
count += 1
end
count.zero? ? nil : count == cask_tokens.length
end
def self.help
"Extract and render a specific stanza for the given Casks"
end
end
end
def self.print_stanzas(stanza, format = nil, table = nil, quiet = nil, *cask_tokens)
count = 0
if ARTIFACTS.include?(stanza)
artifact_name = stanza
stanza = :artifacts
end
cask_tokens.each do |cask_token|
print "#{cask_token}\t" if table
begin
cask = Hbc.load(cask_token)
rescue StandardError
opoo "Cask '#{cask_token}' was not found" unless quiet
puts ""
next
end
unless cask.respond_to?(stanza)
opoo "no such stanza '#{stanza}' on Cask '#{cask_token}'" unless quiet
puts ""
next
end
begin
value = cask.send(stanza)
rescue StandardError
opoo "failure calling '#{stanza}' on Cask '#{cask_token}'" unless quiet
puts ""
next
end
if artifact_name && !value.key?(artifact_name)
opoo "no such stanza '#{artifact_name}' on Cask '#{cask_token}'" unless quiet
puts ""
next
end
value = value.fetch(artifact_name).to_a.flatten if artifact_name
if format
puts value.send(format)
elsif artifact_name || value.is_a?(Symbol)
puts value.inspect
else
puts value.to_s
end
count += 1
end
count.zero? ? nil : count == cask_tokens.length
end
def self.help
"Extract and render a specific stanza for the given Casks"
end
end

View File

@ -1,9 +1,13 @@
class Hbc::CLI::InternalUseBase < Hbc::CLI::Base
def self.command_name
super.sub(%r{^internal_}i, "_")
end
module Hbc
class CLI
class InternalUseBase < Base
def self.command_name
super.sub(%r{^internal_}i, "_")
end
def self.visible
false
def self.visible
false
end
end
end
end

View File

@ -1,86 +1,90 @@
class Hbc::CLI::List < Hbc::CLI::Base
def self.run(*arguments)
@options = {}
@options[:one] = true if arguments.delete("-1")
@options[:versions] = true if arguments.delete("--versions")
module Hbc
class CLI
class List < Base
def self.run(*arguments)
@options = {}
@options[:one] = true if arguments.delete("-1")
@options[:versions] = true if arguments.delete("--versions")
if arguments.delete("-l")
@options[:one] = true
opoo "Option -l is obsolete! Implying option -1."
end
retval = arguments.any? ? list(*arguments) : list_installed
# retval is ternary: true/false/nil
if retval.nil? && !arguments.any?
opoo "nothing to list" # special case: avoid exit code
elsif retval.nil?
raise Hbc::CaskError, "nothing to list"
elsif !retval
raise Hbc::CaskError, "listing incomplete"
end
end
def self.list(*cask_tokens)
count = 0
cask_tokens.each do |cask_token|
odebug "Listing files for Cask #{cask_token}"
begin
cask = Hbc.load(cask_token)
if cask.installed?
if @options[:one]
puts cask.token
elsif @options[:versions]
puts format_versioned(cask)
else
installed_caskfile = cask.metadata_master_container_path.join(*cask.timestamped_versions.last, "Casks", "#{cask_token}.rb")
cask = Hbc.load(installed_caskfile)
list_artifacts(cask)
end
count += 1
else
opoo "#{cask} is not installed"
if arguments.delete("-l")
@options[:one] = true
opoo "Option -l is obsolete! Implying option -1."
end
rescue Hbc::CaskUnavailableError => e
onoe e
retval = arguments.any? ? list(*arguments) : list_installed
# retval is ternary: true/false/nil
if retval.nil? && !arguments.any?
opoo "nothing to list" # special case: avoid exit code
elsif retval.nil?
raise CaskError, "nothing to list"
elsif !retval
raise CaskError, "listing incomplete"
end
end
def self.list(*cask_tokens)
count = 0
cask_tokens.each do |cask_token|
odebug "Listing files for Cask #{cask_token}"
begin
cask = Hbc.load(cask_token)
if cask.installed?
if @options[:one]
puts cask.token
elsif @options[:versions]
puts format_versioned(cask)
else
installed_caskfile = cask.metadata_master_container_path.join(*cask.timestamped_versions.last, "Casks", "#{cask_token}.rb")
cask = Hbc.load(installed_caskfile)
list_artifacts(cask)
end
count += 1
else
opoo "#{cask} is not installed"
end
rescue CaskUnavailableError => e
onoe e
end
end
count.zero? ? nil : count == cask_tokens.length
end
def self.list_artifacts(cask)
Artifact.for_cask(cask).each do |artifact|
summary = artifact.new(cask).summary
ohai summary[:english_description], summary[:contents] unless summary.empty?
end
end
def self.list_installed
installed_casks = Hbc.installed
if @options[:one]
puts installed_casks.map(&:to_s)
elsif @options[:versions]
puts installed_casks.map(&method(:format_versioned))
else
puts_columns installed_casks.map(&:to_s)
end
installed_casks.empty? ? nil : true
end
def self.format_versioned(cask)
cask.to_s.concat(cask.versions.map(&:to_s).join(" ").prepend(" "))
end
def self.help
"with no args, lists installed Casks; given installed Casks, lists staged files"
end
def self.needs_init?
true
end
end
count.zero? ? nil : count == cask_tokens.length
end
def self.list_artifacts(cask)
Hbc::Artifact.for_cask(cask).each do |artifact|
summary = artifact.new(cask).summary
ohai summary[:english_description], summary[:contents] unless summary.empty?
end
end
def self.list_installed
installed_casks = Hbc.installed
if @options[:one]
puts installed_casks.map(&:to_s)
elsif @options[:versions]
puts installed_casks.map(&method(:format_versioned))
else
puts_columns installed_casks.map(&:to_s)
end
installed_casks.empty? ? nil : true
end
def self.format_versioned(cask)
cask.to_s.concat(cask.versions.map(&:to_s).join(" ").prepend(" "))
end
def self.help
"with no args, lists installed Casks; given installed Casks, lists staged files"
end
def self.needs_init?
true
end
end

View File

@ -1,55 +1,59 @@
class Hbc::CLI::Search < Hbc::CLI::Base
def self.run(*arguments)
render_results(*search(*arguments))
end
def self.extract_regexp(string)
if string =~ %r{^/(.*)/$}
Regexp.last_match[1]
else
false
end
end
def self.search(*arguments)
exact_match = nil
partial_matches = []
search_term = arguments.join(" ")
search_regexp = extract_regexp arguments.first
all_tokens = Hbc::CLI.nice_listing(Hbc.all_tokens)
if search_regexp
search_term = arguments.first
partial_matches = all_tokens.grep(%r{#{search_regexp}}i)
else
simplified_tokens = all_tokens.map { |t| t.sub(%r{^.*\/}, "").gsub(%r{[^a-z0-9]+}i, "") }
simplified_search_term = search_term.sub(%r{\.rb$}i, "").gsub(%r{[^a-z0-9]+}i, "")
exact_match = simplified_tokens.grep(%r{^#{simplified_search_term}$}i) { |t| all_tokens[simplified_tokens.index(t)] }.first
partial_matches = simplified_tokens.grep(%r{#{simplified_search_term}}i) { |t| all_tokens[simplified_tokens.index(t)] }
partial_matches.delete(exact_match)
end
[exact_match, partial_matches, search_term]
end
def self.render_results(exact_match, partial_matches, search_term)
if !exact_match && partial_matches.empty?
puts "No Cask found for \"#{search_term}\"."
return
end
if exact_match
ohai "Exact match"
puts exact_match
end
unless partial_matches.empty?
if extract_regexp search_term
ohai "Regexp matches"
else
ohai "Partial matches"
module Hbc
class CLI
class Search < Base
def self.run(*arguments)
render_results(*search(*arguments))
end
puts_columns partial_matches
end
end
def self.help
"searches all known Casks"
def self.extract_regexp(string)
if string =~ %r{^/(.*)/$}
Regexp.last_match[1]
else
false
end
end
def self.search(*arguments)
exact_match = nil
partial_matches = []
search_term = arguments.join(" ")
search_regexp = extract_regexp arguments.first
all_tokens = CLI.nice_listing(Hbc.all_tokens)
if search_regexp
search_term = arguments.first
partial_matches = all_tokens.grep(%r{#{search_regexp}}i)
else
simplified_tokens = all_tokens.map { |t| t.sub(%r{^.*\/}, "").gsub(%r{[^a-z0-9]+}i, "") }
simplified_search_term = search_term.sub(%r{\.rb$}i, "").gsub(%r{[^a-z0-9]+}i, "")
exact_match = simplified_tokens.grep(%r{^#{simplified_search_term}$}i) { |t| all_tokens[simplified_tokens.index(t)] }.first
partial_matches = simplified_tokens.grep(%r{#{simplified_search_term}}i) { |t| all_tokens[simplified_tokens.index(t)] }
partial_matches.delete(exact_match)
end
[exact_match, partial_matches, search_term]
end
def self.render_results(exact_match, partial_matches, search_term)
if !exact_match && partial_matches.empty?
puts "No Cask found for \"#{search_term}\"."
return
end
if exact_match
ohai "Exact match"
puts exact_match
end
unless partial_matches.empty?
if extract_regexp search_term
ohai "Regexp matches"
else
ohai "Partial matches"
end
puts_columns partial_matches
end
end
def self.help
"searches all known Casks"
end
end
end
end

View File

@ -1,69 +1,73 @@
require "English"
class Hbc::CLI::Style < Hbc::CLI::Base
def self.help
"checks Cask style using RuboCop"
end
module Hbc
class CLI
class Style < Base
def self.help
"checks Cask style using RuboCop"
end
def self.run(*args)
retval = new(args).run
raise Hbc::CaskError, "style check failed" unless retval
end
def self.run(*args)
retval = new(args).run
raise CaskError, "style check failed" unless retval
end
attr_reader :args
def initialize(args)
@args = args
end
attr_reader :args
def initialize(args)
@args = args
end
def run
install_rubocop
system "rubocop", *rubocop_args, "--", *cask_paths
$CHILD_STATUS.success?
end
def run
install_rubocop
system "rubocop", *rubocop_args, "--", *cask_paths
$CHILD_STATUS.success?
end
RUBOCOP_CASK_VERSION = "~> 0.8.3".freeze
RUBOCOP_CASK_VERSION = "~> 0.8.3".freeze
def install_rubocop
Hbc::Utils.capture_stderr do
begin
Homebrew.install_gem_setup_path! "rubocop-cask", RUBOCOP_CASK_VERSION, "rubocop"
rescue SystemExit
raise Hbc::CaskError, $stderr.string.chomp.sub("#{Tty.red}Error#{Tty.reset}: ", "")
def install_rubocop
Utils.capture_stderr do
begin
Homebrew.install_gem_setup_path! "rubocop-cask", RUBOCOP_CASK_VERSION, "rubocop"
rescue SystemExit
raise CaskError, $stderr.string.chomp.sub("#{Tty.red}Error#{Tty.reset}: ", "")
end
end
end
def cask_paths
@cask_paths ||= if cask_tokens.empty?
Hbc.all_tapped_cask_dirs
elsif cask_tokens.any? { |file| File.exist?(file) }
cask_tokens
else
cask_tokens.map { |token| Hbc.path(token) }
end
end
def cask_tokens
@cask_tokens ||= self.class.cask_tokens_from(args)
end
def rubocop_args
fix? ? autocorrect_args : default_args
end
def default_args
["--format", "simple", "--force-exclusion", "--config", rubocop_config]
end
def autocorrect_args
default_args + ["--auto-correct"]
end
def rubocop_config
Hbc.default_tap.cask_dir.join(".rubocop.yml")
end
def fix?
args.any? { |arg| arg =~ %r{--(fix|(auto-?)?correct)} }
end
end
end
def cask_paths
@cask_paths ||= if cask_tokens.empty?
Hbc.all_tapped_cask_dirs
elsif cask_tokens.any? { |file| File.exist?(file) }
cask_tokens
else
cask_tokens.map { |token| Hbc.path(token) }
end
end
def cask_tokens
@cask_tokens ||= self.class.cask_tokens_from(args)
end
def rubocop_args
fix? ? autocorrect_args : default_args
end
def default_args
["--format", "simple", "--force-exclusion", "--config", rubocop_config]
end
def autocorrect_args
default_args + ["--auto-correct"]
end
def rubocop_config
Hbc.default_tap.cask_dir.join(".rubocop.yml")
end
def fix?
args.any? { |arg| arg =~ %r{--(fix|(auto-?)?correct)} }
end
end

View File

@ -1,40 +1,44 @@
class Hbc::CLI::Uninstall < Hbc::CLI::Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
force = args.include? "--force"
module Hbc
class CLI
class Uninstall < Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise CaskUnspecifiedError if cask_tokens.empty?
force = args.include? "--force"
cask_tokens.each do |cask_token|
odebug "Uninstalling Cask #{cask_token}"
cask = Hbc.load(cask_token)
cask_tokens.each do |cask_token|
odebug "Uninstalling Cask #{cask_token}"
cask = Hbc.load(cask_token)
raise Hbc::CaskNotInstalledError, cask unless cask.installed? || force
raise CaskNotInstalledError, cask unless cask.installed? || force
latest_installed_version = cask.timestamped_versions.last
latest_installed_version = cask.timestamped_versions.last
unless latest_installed_version.nil?
latest_installed_cask_file = cask.metadata_master_container_path
.join(latest_installed_version.join(File::Separator),
"Casks", "#{cask_token}.rb")
unless latest_installed_version.nil?
latest_installed_cask_file = cask.metadata_master_container_path
.join(latest_installed_version.join(File::Separator),
"Casks", "#{cask_token}.rb")
# use the same cask file that was used for installation, if possible
cask = Hbc.load(latest_installed_cask_file) if latest_installed_cask_file.exist?
# use the same cask file that was used for installation, if possible
cask = Hbc.load(latest_installed_cask_file) if latest_installed_cask_file.exist?
end
Installer.new(cask, force: force).uninstall
next if (versions = cask.versions).empty?
single = versions.count == 1
puts <<-EOS.undent
#{cask_token} #{versions.join(", ")} #{single ? "is" : "are"} still installed.
Remove #{single ? "it" : "them all"} with `brew cask uninstall --force #{cask_token}`.
EOS
end
end
Hbc::Installer.new(cask, force: force).uninstall
next if (versions = cask.versions).empty?
single = versions.count == 1
puts <<-EOS.undent
#{cask_token} #{versions.join(", ")} #{single ? "is" : "are"} still installed.
Remove #{single ? "it" : "them all"} with `brew cask uninstall --force #{cask_token}`.
EOS
def self.help
"uninstalls the given Cask"
end
end
end
def self.help
"uninstalls the given Cask"
end
end

View File

@ -1,16 +1,20 @@
class Hbc::CLI::Update < Hbc::CLI::Base
def self.run(*_ignored)
result = Hbc::SystemCommand.run(Hbc.homebrew_executable,
args: %w[update])
# TODO: separating stderr/stdout is undesirable here.
# Hbc::SystemCommand should have an option for plain
# unbuffered output.
print result.stdout
$stderr.print result.stderr
exit result.exit_status
end
module Hbc
class CLI
class Update < Base
def self.run(*_ignored)
result = SystemCommand.run(Hbc.homebrew_executable,
args: %w[update])
# TODO: separating stderr/stdout is undesirable here.
# Hbc::SystemCommand should have an option for plain
# unbuffered output.
print result.stdout
$stderr.print result.stderr
exit result.exit_status
end
def self.help
"a synonym for 'brew update'"
def self.help
"a synonym for 'brew update'"
end
end
end
end

View File

@ -1,15 +1,19 @@
class Hbc::CLI::Zap < Hbc::CLI::Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
cask_tokens.each do |cask_token|
odebug "Zapping Cask #{cask_token}"
cask = Hbc.load(cask_token)
Hbc::Installer.new(cask).zap
module Hbc
class CLI
class Zap < Base
def self.run(*args)
cask_tokens = cask_tokens_from(args)
raise CaskUnspecifiedError if cask_tokens.empty?
cask_tokens.each do |cask_token|
odebug "Zapping Cask #{cask_token}"
cask = Hbc.load(cask_token)
Installer.new(cask).zap
end
end
def self.help
"zaps all files associated with the given Cask"
end
end
end
def self.help
"zaps all files associated with the given Cask"
end
end

View File

@ -1,5 +1,3 @@
class Hbc::Container; end
require "hbc/container/base"
require "hbc/container/air"
require "hbc/container/bzip2"
@ -22,47 +20,49 @@ require "hbc/container/xip"
require "hbc/container/xz"
require "hbc/container/zip"
class Hbc::Container
def self.autodetect_containers
[
Hbc::Container::Pkg,
Hbc::Container::Ttf,
Hbc::Container::Otf,
Hbc::Container::Air,
Hbc::Container::Cab,
Hbc::Container::Dmg,
Hbc::Container::SevenZip,
Hbc::Container::Sit,
Hbc::Container::Rar,
Hbc::Container::Zip,
Hbc::Container::Xip, # needs to be before xar as this is a cpio inside a gzip inside a xar
Hbc::Container::Xar, # need to be before tar as tar can also list xar
Hbc::Container::Tar, # or compressed tar (bzip2/gzip/lzma/xz)
Hbc::Container::Bzip2, # pure bzip2
Hbc::Container::Gzip, # pure gzip
Hbc::Container::Lzma, # pure lzma
Hbc::Container::Xz, # pure xz
]
# for explicit use only (never autodetected):
# Hbc::Container::Naked
# Hbc::Container::GenericUnar
end
def self.for_path(path, command)
odebug "Determining which containers to use based on filetype"
criteria = Hbc::Container::Criteria.new(path, command)
autodetect_containers.find do |c|
odebug "Checking container class #{c}"
c.me?(criteria)
module Hbc
class Container
def self.autodetect_containers
[
Pkg,
Ttf,
Otf,
Air,
Cab,
Dmg,
SevenZip,
Sit,
Rar,
Zip,
Xip, # needs to be before xar as this is a cpio inside a gzip inside a xar
Xar, # need to be before tar as tar can also list xar
Tar, # or compressed tar (bzip2/gzip/lzma/xz)
Bzip2, # pure bzip2
Gzip, # pure gzip
Lzma, # pure lzma
Xz, # pure xz
]
# for explicit use only (never autodetected):
# Hbc::Container::Naked
# Hbc::Container::GenericUnar
end
end
def self.from_type(type)
odebug "Determining which containers to use based on 'container :type'"
begin
Hbc::Container.const_get(type.to_s.split("_").map(&:capitalize).join)
rescue NameError
false
def self.for_path(path, command)
odebug "Determining which containers to use based on filetype"
criteria = Criteria.new(path, command)
autodetect_containers.find do |c|
odebug "Checking container class #{c}"
c.me?(criteria)
end
end
def self.from_type(type)
odebug "Determining which containers to use based on 'container :type'"
begin
self.const_get(type.to_s.split("_").map(&:capitalize).join)
rescue NameError
false
end
end
end
end

View File

@ -1,33 +1,37 @@
require "hbc/container/base"
class Hbc::Container::Air < Hbc::Container::Base
INSTALLER_PATHNAME =
Pathname("/Applications/Utilities/Adobe AIR Application Installer.app" \
"/Contents/MacOS/Adobe AIR Application Installer")
module Hbc
class Container
class Air < Base
INSTALLER_PATHNAME =
Pathname("/Applications/Utilities/Adobe AIR Application Installer.app" \
"/Contents/MacOS/Adobe AIR Application Installer")
def self.me?(criteria)
%w[.air].include?(criteria.path.extname)
end
def self.me?(criteria)
%w[.air].include?(criteria.path.extname)
end
def self.installer_cmd
return @installer_cmd ||= INSTALLER_PATHNAME if installer_exist?
raise Hbc::CaskError, <<-EOS.undent
Adobe AIR runtime not present, try installing it via
def self.installer_cmd
return @installer_cmd ||= INSTALLER_PATHNAME if installer_exist?
raise CaskError, <<-EOS.undent
Adobe AIR runtime not present, try installing it via
brew cask install adobe-air
brew cask install adobe-air
EOS
end
EOS
end
def self.installer_exist?
INSTALLER_PATHNAME.exist?
end
def self.installer_exist?
INSTALLER_PATHNAME.exist?
end
def extract
install = @command.run(self.class.installer_cmd,
args: ["-silent", "-location", @cask.staged_path, Pathname.new(@path).realpath])
def extract
install = @command.run(self.class.installer_cmd,
args: ["-silent", "-location", @cask.staged_path, Pathname.new(@path).realpath])
return unless install.exit_status == 9
raise Hbc::CaskError, "Adobe AIR application #{@cask} already exists on the system, and cannot be reinstalled."
return unless install.exit_status == 9
raise CaskError, "Adobe AIR application #{@cask} already exists on the system, and cannot be reinstalled."
end
end
end
end

View File

@ -1,37 +1,41 @@
class Hbc::Container::Base
def initialize(cask, path, command, nested: false)
@cask = cask
@path = path
@command = command
@nested = nested
end
module Hbc
class Container
class Base
def initialize(cask, path, command, nested: false)
@cask = cask
@path = path
@command = command
@nested = nested
end
def extract_nested_inside(dir)
children = Pathname.new(dir).children
def extract_nested_inside(dir)
children = Pathname.new(dir).children
nested_container = children[0]
nested_container = children[0]
unless children.count == 1 &&
!nested_container.directory? &&
@cask.artifacts[:nested_container].empty? &&
extract_nested_container(nested_container)
unless children.count == 1 &&
!nested_container.directory? &&
@cask.artifacts[:nested_container].empty? &&
extract_nested_container(nested_container)
children.each do |src|
dest = @cask.staged_path.join(src.basename)
FileUtils.rm_r(dest) if dest.exist?
FileUtils.mv(src, dest)
children.each do |src|
dest = @cask.staged_path.join(src.basename)
FileUtils.rm_r(dest) if dest.exist?
FileUtils.mv(src, dest)
end
end
end
def extract_nested_container(source)
container = Container.for_path(source, @command)
return false unless container
ohai "Extracting nested container #{source.basename}"
container.new(@cask, source, @command, nested: true).extract
true
end
end
end
def extract_nested_container(source)
container = Hbc::Container.for_path(source, @command)
return false unless container
ohai "Extracting nested container #{source.basename}"
container.new(@cask, source, @command, nested: true).extract
true
end
end

View File

@ -2,17 +2,21 @@ require "tmpdir"
require "hbc/container/base"
class Hbc::Container::Bzip2 < Hbc::Container::Base
def self.me?(criteria)
criteria.magic_number(%r{^BZh}n)
end
module Hbc
class Container
class Bzip2 < Base
def self.me?(criteria)
criteria.magic_number(%r{^BZh}n)
end
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!("/usr/bin/bunzip2", args: ["--quiet", "--", Pathname.new(unpack_dir).join(@path.basename)])
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!("/usr/bin/bunzip2", args: ["--quiet", "--", Pathname.new(unpack_dir).join(@path.basename)])
extract_nested_inside(unpack_dir)
extract_nested_inside(unpack_dir)
end
end
end
end
end

View File

@ -2,25 +2,29 @@ require "tmpdir"
require "hbc/container/base"
class Hbc::Container::Cab < Hbc::Container::Base
def self.me?(criteria)
cabextract = Hbc.homebrew_prefix.join("bin", "cabextract")
module Hbc
class Container
class Cab < Base
def self.me?(criteria)
cabextract = Hbc.homebrew_prefix.join("bin", "cabextract")
criteria.magic_number(%r{^MSCF}n) &&
cabextract.exist? &&
criteria.command.run(cabextract, args: ["-t", "--", criteria.path.to_s]).stderr.empty?
end
criteria.magic_number(%r{^MSCF}n) &&
cabextract.exist? &&
criteria.command.run(cabextract, args: ["-t", "--", criteria.path.to_s]).stderr.empty?
end
def extract
cabextract = Hbc.homebrew_prefix.join("bin", "cabextract")
def extract
cabextract = Hbc.homebrew_prefix.join("bin", "cabextract")
unless cabextract.exist?
raise Hbc::CaskError, "Expected to find cabextract executable. Cask '#{@cask}' must add: depends_on formula: 'cabextract'"
end
unless cabextract.exist?
raise CaskError, "Expected to find cabextract executable. Cask '#{@cask}' must add: depends_on formula: 'cabextract'"
end
Dir.mktmpdir do |unpack_dir|
@command.run!(cabextract, args: ["-d", unpack_dir, "--", @path])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
Dir.mktmpdir do |unpack_dir|
@command.run!(cabextract, args: ["-d", unpack_dir, "--", @path])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
end
end
end
end

View File

@ -1,18 +1,22 @@
class Hbc::Container::Criteria
attr_reader :path, :command
module Hbc
class Container
class Criteria
attr_reader :path, :command
def initialize(path, command)
@path = path
@command = command
end
def initialize(path, command)
@path = path
@command = command
end
def extension(regex)
path.extname.sub(%r{^\.}, "") =~ Regexp.new(regex.source, regex.options | Regexp::IGNORECASE)
end
def extension(regex)
path.extname.sub(%r{^\.}, "") =~ Regexp.new(regex.source, regex.options | Regexp::IGNORECASE)
end
def magic_number(regex)
# 262: length of the longest regex (currently: Hbc::Container::Tar)
@magic_number ||= File.open(@path, "rb") { |f| f.read(262) }
@magic_number =~ regex
def magic_number(regex)
# 262: length of the longest regex (currently: Hbc::Container::Tar)
@magic_number ||= File.open(@path, "rb") { |f| f.read(262) }
@magic_number =~ regex
end
end
end
end

View File

@ -3,123 +3,127 @@ require "tempfile"
require "hbc/container/base"
class Hbc::Container::Dmg < Hbc::Container::Base
def self.me?(criteria)
!criteria.command.run("/usr/bin/hdiutil",
# realpath is a failsafe against unusual filenames
args: ["imageinfo", Pathname.new(criteria.path).realpath],
print_stderr: false).stdout.empty?
end
module Hbc
class Container
class Dmg < Base
def self.me?(criteria)
!criteria.command.run("/usr/bin/hdiutil",
# realpath is a failsafe against unusual filenames
args: ["imageinfo", Pathname.new(criteria.path).realpath],
print_stderr: false).stdout.empty?
end
attr_reader :mounts
def initialize(*args)
super(*args)
@mounts = []
end
attr_reader :mounts
def initialize(*args)
super(*args)
@mounts = []
end
def extract
mount!
assert_mounts_found
extract_mounts
ensure
eject!
end
def extract
mount!
assert_mounts_found
extract_mounts
ensure
eject!
end
def mount!
plist = @command.run!("/usr/bin/hdiutil",
# realpath is a failsafe against unusual filenames
args: %w[mount -plist -nobrowse -readonly -noidme -mountrandom /tmp] + [Pathname.new(@path).realpath],
input: %w[y])
.plist
@mounts = mounts_from_plist(plist)
end
def mount!
plist = @command.run!("/usr/bin/hdiutil",
# realpath is a failsafe against unusual filenames
args: %w[mount -plist -nobrowse -readonly -noidme -mountrandom /tmp] + [Pathname.new(@path).realpath],
input: %w[y])
.plist
@mounts = mounts_from_plist(plist)
end
def eject!
@mounts.each do |mount|
# realpath is a failsafe against unusual filenames
mountpath = Pathname.new(mount).realpath
next unless mountpath.exist?
def eject!
@mounts.each do |mount|
# realpath is a failsafe against unusual filenames
mountpath = Pathname.new(mount).realpath
next unless mountpath.exist?
begin
tries ||= 2
@command.run("/usr/sbin/diskutil",
args: ["eject", mountpath],
print_stderr: false)
begin
tries ||= 2
@command.run("/usr/sbin/diskutil",
args: ["eject", mountpath],
print_stderr: false)
raise Hbc::CaskError, "Failed to eject #{mountpath}" if mountpath.exist?
rescue Hbc::CaskError => e
raise e if (tries -= 1).zero?
sleep 1
retry
raise CaskError, "Failed to eject #{mountpath}" if mountpath.exist?
rescue CaskError => e
raise e if (tries -= 1).zero?
sleep 1
retry
end
end
end
private
def extract_mounts
@mounts.each(&method(:extract_mount))
end
def extract_mount(mount)
Tempfile.open(["", ".bom"]) do |bomfile|
bomfile.close
Tempfile.open(["", ".list"]) do |filelist|
filelist.write(bom_filelist_from_path(mount))
filelist.close
@command.run!("/usr/bin/mkbom", args: ["-s", "-i", filelist.path, "--", bomfile.path])
@command.run!("/usr/bin/ditto", args: ["--bom", bomfile.path, "--", mount, @cask.staged_path])
end
end
end
def bom_filelist_from_path(mount)
Dir.chdir(mount) {
Dir.glob("**/*", File::FNM_DOTMATCH).map { |path|
next if skip_path?(Pathname(path))
path == "." ? path : path.prepend("./")
}.compact.join("\n").concat("\n")
}
end
def skip_path?(path)
dmg_metadata?(path) || system_dir_symlink?(path)
end
# unnecessary DMG metadata
DMG_METADATA_FILES = %w[
.background
.com.apple.timemachine.donotpresent
.DocumentRevisions-V100
.DS_Store
.fseventsd
.MobileBackups
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
].to_set.freeze
def dmg_metadata?(path)
relative_root = path.sub(%r{/.*}, "")
DMG_METADATA_FILES.include?(relative_root.basename.to_s)
end
def system_dir_symlink?(path)
# symlinks to system directories (commonly to /Applications)
path.symlink? && MacOS.system_dir?(path.readlink)
end
def mounts_from_plist(plist)
return [] unless plist.respond_to?(:fetch)
plist.fetch("system-entities", []).map { |entity|
entity["mount-point"]
}.compact
end
def assert_mounts_found
raise CaskError, "No mounts found in '#{@path}'; perhaps it is a bad DMG?" if @mounts.empty?
end
end
end
private
def extract_mounts
@mounts.each(&method(:extract_mount))
end
def extract_mount(mount)
Tempfile.open(["", ".bom"]) do |bomfile|
bomfile.close
Tempfile.open(["", ".list"]) do |filelist|
filelist.write(bom_filelist_from_path(mount))
filelist.close
@command.run!("/usr/bin/mkbom", args: ["-s", "-i", filelist.path, "--", bomfile.path])
@command.run!("/usr/bin/ditto", args: ["--bom", bomfile.path, "--", mount, @cask.staged_path])
end
end
end
def bom_filelist_from_path(mount)
Dir.chdir(mount) {
Dir.glob("**/*", File::FNM_DOTMATCH).map { |path|
next if skip_path?(Pathname(path))
path == "." ? path : path.prepend("./")
}.compact.join("\n").concat("\n")
}
end
def skip_path?(path)
dmg_metadata?(path) || system_dir_symlink?(path)
end
# unnecessary DMG metadata
DMG_METADATA_FILES = %w[
.background
.com.apple.timemachine.donotpresent
.DocumentRevisions-V100
.DS_Store
.fseventsd
.MobileBackups
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
].to_set.freeze
def dmg_metadata?(path)
relative_root = path.sub(%r{/.*}, "")
DMG_METADATA_FILES.include?(relative_root.basename.to_s)
end
def system_dir_symlink?(path)
# symlinks to system directories (commonly to /Applications)
path.symlink? && MacOS.system_dir?(path.readlink)
end
def mounts_from_plist(plist)
return [] unless plist.respond_to?(:fetch)
plist.fetch("system-entities", []).map { |entity|
entity["mount-point"]
}.compact
end
def assert_mounts_found
raise Hbc::CaskError, "No mounts found in '#{@path}'; perhaps it is a bad DMG?" if @mounts.empty?
end
end

View File

@ -2,25 +2,29 @@ require "tmpdir"
require "hbc/container/base"
class Hbc::Container::GenericUnar < Hbc::Container::Base
def self.me?(criteria)
lsar = Hbc.homebrew_prefix.join("bin", "lsar")
lsar.exist? &&
criteria.command.run(lsar,
args: ["-l", "-t", "--", criteria.path],
print_stderr: false).stdout.chomp.end_with?("passed, 0 failed.")
end
module Hbc
class Container
class GenericUnar < Base
def self.me?(criteria)
lsar = Hbc.homebrew_prefix.join("bin", "lsar")
lsar.exist? &&
criteria.command.run(lsar,
args: ["-l", "-t", "--", criteria.path],
print_stderr: false).stdout.chomp.end_with?("passed, 0 failed.")
end
def extract
unar = Hbc.homebrew_prefix.join("bin", "unar")
def extract
unar = Hbc.homebrew_prefix.join("bin", "unar")
unless unar.exist?
raise Hbc::CaskError, "Expected to find unar executable. Cask #{@cask} must add: depends_on formula: 'unar'"
end
unless unar.exist?
raise CaskError, "Expected to find unar executable. Cask #{@cask} must add: depends_on formula: 'unar'"
end
Dir.mktmpdir do |unpack_dir|
@command.run!(unar, args: ["-force-overwrite", "-quiet", "-no-directory", "-output-directory", unpack_dir, "--", @path])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
Dir.mktmpdir do |unpack_dir|
@command.run!(unar, args: ["-force-overwrite", "-quiet", "-no-directory", "-output-directory", unpack_dir, "--", @path])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
end
end
end
end

View File

@ -2,17 +2,21 @@ require "tmpdir"
require "hbc/container/base"
class Hbc::Container::Gzip < Hbc::Container::Base
def self.me?(criteria)
criteria.magic_number(%r{^\037\213}n)
end
module Hbc
class Container
class Gzip < Base
def self.me?(criteria)
criteria.magic_number(%r{^\037\213}n)
end
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!("/usr/bin/gunzip", args: ["--quiet", "--name", "--", Pathname.new(unpack_dir).join(@path.basename)])
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!("/usr/bin/gunzip", args: ["--quiet", "--name", "--", Pathname.new(unpack_dir).join(@path.basename)])
extract_nested_inside(unpack_dir)
extract_nested_inside(unpack_dir)
end
end
end
end
end

View File

@ -2,22 +2,26 @@ require "tmpdir"
require "hbc/container/base"
class Hbc::Container::Lzma < Hbc::Container::Base
def self.me?(criteria)
criteria.magic_number(%r{^\]\000\000\200\000}n)
end
module Hbc
class Container
class Lzma < Base
def self.me?(criteria)
criteria.magic_number(%r{^\]\000\000\200\000}n)
end
def extract
unlzma = Hbc.homebrew_prefix.join("bin", "unlzma")
def extract
unlzma = Hbc.homebrew_prefix.join("bin", "unlzma")
unless unlzma.exist?
raise Hbc::CaskError, "Expected to find unlzma executable. Cask '#{@cask}' must add: depends_on formula: 'lzma'"
end
unless unlzma.exist?
raise CaskError, "Expected to find unlzma executable. Cask '#{@cask}' must add: depends_on formula: 'lzma'"
end
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!(unlzma, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!(unlzma, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
end
end
end
end

View File

@ -1,19 +1,23 @@
require "hbc/container/base"
class Hbc::Container::Naked < Hbc::Container::Base
# Either inherit from this class and override with self.me?(criteria),
# or use this class directly as "container type: :naked",
# in which case self.me? is not called.
def self.me?(*)
false
end
module Hbc
class Container
class Naked < Base
# Either inherit from this class and override with self.me?(criteria),
# or use this class directly as "container type: :naked",
# in which case self.me? is not called.
def self.me?(*)
false
end
def extract
@command.run!("/usr/bin/ditto", args: ["--", @path, @cask.staged_path.join(target_file)])
end
def extract
@command.run!("/usr/bin/ditto", args: ["--", @path, @cask.staged_path.join(target_file)])
end
def target_file
return @path.basename if @nested
URI.decode(File.basename(@cask.url.path))
def target_file
return @path.basename if @nested
URI.decode(File.basename(@cask.url.path))
end
end
end
end

View File

@ -1,7 +1,11 @@
require "hbc/container/naked"
class Hbc::Container::Otf < Hbc::Container::Naked
def self.me?(criteria)
criteria.magic_number(%r{^OTTO}n)
module Hbc
class Container
class Otf < Naked
def self.me?(criteria)
criteria.magic_number(%r{^OTTO}n)
end
end
end
end

View File

@ -1,9 +1,13 @@
require "hbc/container/naked"
class Hbc::Container::Pkg < Hbc::Container::Naked
def self.me?(criteria)
criteria.extension(%r{m?pkg$}) &&
(criteria.path.directory? ||
criteria.magic_number(%r{^xar!}n))
module Hbc
class Container
class Pkg < Naked
def self.me?(criteria)
criteria.extension(%r{m?pkg$}) &&
(criteria.path.directory? ||
criteria.magic_number(%r{^xar!}n))
end
end
end
end

View File

@ -1,8 +1,12 @@
require "hbc/container/generic_unar"
class Hbc::Container::Rar < Hbc::Container::GenericUnar
def self.me?(criteria)
criteria.magic_number(%r{^Rar!}n) &&
super
module Hbc
class Container
class Rar < GenericUnar
def self.me?(criteria)
criteria.magic_number(%r{^Rar!}n) &&
super
end
end
end
end

View File

@ -1,9 +1,13 @@
require "hbc/container/generic_unar"
class Hbc::Container::SevenZip < Hbc::Container::GenericUnar
def self.me?(criteria)
# TODO: cover self-extracting archives
criteria.magic_number(%r{^7z}n) &&
super
module Hbc
class Container
class SevenZip < GenericUnar
def self.me?(criteria)
# TODO: cover self-extracting archives
criteria.magic_number(%r{^7z}n) &&
super
end
end
end
end

View File

@ -1,8 +1,12 @@
require "hbc/container/generic_unar"
class Hbc::Container::Sit < Hbc::Container::GenericUnar
def self.me?(criteria)
criteria.magic_number(%r{^StuffIt}n) &&
super
module Hbc
class Container
class Sit < GenericUnar
def self.me?(criteria)
criteria.magic_number(%r{^StuffIt}n) &&
super
end
end
end
end

View File

@ -2,17 +2,21 @@ require "tmpdir"
require "hbc/container/base"
class Hbc::Container::Tar < Hbc::Container::Base
def self.me?(criteria)
criteria.magic_number(%r{^.{257}ustar}n) ||
# or compressed tar (bzip2/gzip/lzma/xz)
IO.popen(["/usr/bin/tar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| !io.read(1).nil? }
end
module Hbc
class Container
class Tar < Base
def self.me?(criteria)
criteria.magic_number(%r{^.{257}ustar}n) ||
# or compressed tar (bzip2/gzip/lzma/xz)
IO.popen(["/usr/bin/tar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| !io.read(1).nil? }
end
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/tar", args: ["-x", "-f", @path, "-C", unpack_dir])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/tar", args: ["-x", "-f", @path, "-C", unpack_dir])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
end
end
end
end

View File

@ -1,10 +1,14 @@
require "hbc/container/naked"
class Hbc::Container::Ttf < Hbc::Container::Naked
def self.me?(criteria)
# TrueType Font
criteria.magic_number(%r{^\000\001\000\000\000}n) ||
# Truetype Font Collection
criteria.magic_number(%r{^ttcf}n)
module Hbc
class Container
class Ttf < Naked
def self.me?(criteria)
# TrueType Font
criteria.magic_number(%r{^\000\001\000\000\000}n) ||
# Truetype Font Collection
criteria.magic_number(%r{^ttcf}n)
end
end
end
end

View File

@ -2,15 +2,19 @@ require "tmpdir"
require "hbc/container/base"
class Hbc::Container::Xar < Hbc::Container::Base
def self.me?(criteria)
criteria.magic_number(%r{^xar!}n)
end
module Hbc
class Container
class Xar < Base
def self.me?(criteria)
criteria.magic_number(%r{^xar!}n)
end
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "-C", unpack_dir])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "-C", unpack_dir])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
end
end
end
end

View File

@ -1,24 +1,28 @@
require "tmpdir"
class Hbc::Container::Xip < Hbc::Container::Base
def self.me?(criteria)
criteria.magic_number(%r{^xar!}n) &&
IO.popen(["/usr/bin/xar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| io.read =~ %r{\AContent\nMetadata\n\Z} }
end
def extract
Dir.mktmpdir do |unpack_dir|
begin
ohai "Verifying signature for #{@path.basename}"
@command.run!("/usr/sbin/pkgutil", args: ["--check-signature", @path])
rescue
raise "Signature check failed."
module Hbc
class Container
class Xip < Base
def self.me?(criteria)
criteria.magic_number(%r{^xar!}n) &&
IO.popen(["/usr/bin/xar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| io.read =~ %r{\AContent\nMetadata\n\Z} }
end
@command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "Content", "-C", unpack_dir])
def extract
Dir.mktmpdir do |unpack_dir|
begin
ohai "Verifying signature for #{@path.basename}"
@command.run!("/usr/sbin/pkgutil", args: ["--check-signature", @path])
rescue
raise "Signature check failed."
end
Dir.chdir(@cask.staged_path) do
@command.run!("/usr/bin/cpio", args: ["--quiet", "-i", "-I", Pathname(unpack_dir).join("Content")])
@command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "Content", "-C", unpack_dir])
Dir.chdir(@cask.staged_path) do
@command.run!("/usr/bin/cpio", args: ["--quiet", "-i", "-I", Pathname(unpack_dir).join("Content")])
end
end
end
end
end

View File

@ -2,22 +2,26 @@ require "tmpdir"
require "hbc/container/base"
class Hbc::Container::Xz < Hbc::Container::Base
def self.me?(criteria)
criteria.magic_number(%r{^\xFD7zXZ\x00}n)
end
module Hbc
class Container
class Xz < Base
def self.me?(criteria)
criteria.magic_number(%r{^\xFD7zXZ\x00}n)
end
def extract
unxz = Hbc.homebrew_prefix.join("bin", "unxz")
def extract
unxz = Hbc.homebrew_prefix.join("bin", "unxz")
unless unxz.exist?
raise Hbc::CaskError, "Expected to find unxz executable. Cask '#{@cask}' must add: depends_on formula: 'xz'"
end
unless unxz.exist?
raise CaskError, "Expected to find unxz executable. Cask '#{@cask}' must add: depends_on formula: 'xz'"
end
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!(unxz, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!(unxz, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
end
end
end
end

View File

@ -1,15 +1,19 @@
require "hbc/container/base"
class Hbc::Container::Zip < Hbc::Container::Base
def self.me?(criteria)
criteria.magic_number(%r{^PK(\003\004|\005\006)}n)
end
module Hbc
class Container
class Zip < Base
def self.me?(criteria)
criteria.magic_number(%r{^PK(\003\004|\005\006)}n)
end
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["-x", "-k", "--", @path, unpack_dir])
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["-x", "-k", "--", @path, unpack_dir])
extract_nested_inside(unpack_dir)
extract_nested_inside(unpack_dir)
end
end
end
end
end

View File

@ -1,43 +1,45 @@
require "fileutils"
require "hbc/verify"
class Hbc::Download
attr_reader :cask
module Hbc
class Download
attr_reader :cask
def initialize(cask, force: false)
@cask = cask
@force = force
end
def initialize(cask, force: false)
@cask = cask
@force = force
end
def perform
clear_cache
fetch
downloaded_path
end
def perform
clear_cache
fetch
downloaded_path
end
private
private
attr_reader :force
attr_accessor :downloaded_path
attr_reader :force
attr_accessor :downloaded_path
def downloader
@downloader ||= case cask.url.using
when :svn
Hbc::SubversionDownloadStrategy.new(cask)
when :post
Hbc::CurlPostDownloadStrategy.new(cask)
else
Hbc::CurlDownloadStrategy.new(cask)
end
end
def downloader
@downloader ||= case cask.url.using
when :svn
SubversionDownloadStrategy.new(cask)
when :post
CurlPostDownloadStrategy.new(cask)
else
CurlDownloadStrategy.new(cask)
end
end
def clear_cache
downloader.clear_cache if force || cask.version.latest?
end
def clear_cache
downloader.clear_cache if force || cask.version.latest?
end
def fetch
self.downloaded_path = downloader.fetch
rescue StandardError => e
raise Hbc::CaskError, "Download failed on Cask '#{cask}' with message: #{e}"
def fetch
self.downloaded_path = downloader.fetch
rescue StandardError => e
raise CaskError, "Download failed on Cask '#{cask}' with message: #{e}"
end
end
end

View File

@ -6,327 +6,329 @@ require "cgi"
# * Our overridden fetch methods are expected to return
# a value: the successfully downloaded file.
class Hbc::AbstractDownloadStrategy
attr_reader :cask, :name, :url, :uri_object, :version
module Hbc
class AbstractDownloadStrategy
attr_reader :cask, :name, :url, :uri_object, :version
def initialize(cask, command = Hbc::SystemCommand)
@cask = cask
@command = command
# TODO: this excess of attributes is a function of integrating
# with Homebrew's classes. Later we should be able to remove
# these in favor of @cask
@name = cask.token
@url = cask.url.to_s
@uri_object = cask.url
@version = cask.version
def initialize(cask, command = SystemCommand)
@cask = cask
@command = command
# TODO: this excess of attributes is a function of integrating
# with Homebrew's classes. Later we should be able to remove
# these in favor of @cask
@name = cask.token
@url = cask.url.to_s
@uri_object = cask.url
@version = cask.version
end
# All download strategies are expected to implement these methods
def fetch; end
def cached_location; end
def clear_cache; end
end
# All download strategies are expected to implement these methods
def fetch; end
class HbVCSDownloadStrategy < AbstractDownloadStrategy
REF_TYPES = [:branch, :revision, :revisions, :tag].freeze
def cached_location; end
def initialize(cask, command = SystemCommand)
super
@ref_type, @ref = extract_ref
@clone = Hbc.cache.join(cache_filename)
end
def clear_cache; end
end
def extract_ref
key = REF_TYPES.find { |type|
uri_object.respond_to?(type) && uri_object.send(type)
}
[key, key ? uri_object.send(key) : nil]
end
class Hbc::HbVCSDownloadStrategy < Hbc::AbstractDownloadStrategy
REF_TYPES = [:branch, :revision, :revisions, :tag].freeze
def cache_filename
"#{name}--#{cache_tag}"
end
def initialize(cask, command = Hbc::SystemCommand)
super
@ref_type, @ref = extract_ref
@clone = Hbc.cache.join(cache_filename)
end
def cache_tag
"__UNKNOWN__"
end
def extract_ref
key = REF_TYPES.find { |type|
uri_object.respond_to?(type) && uri_object.send(type)
}
[key, key ? uri_object.send(key) : nil]
end
def cached_location
@clone
end
def cache_filename
"#{name}--#{cache_tag}"
end
def cache_tag
"__UNKNOWN__"
end
def cached_location
@clone
end
def clear_cache
cached_location.rmtree if cached_location.exist?
end
end
class Hbc::CurlDownloadStrategy < Hbc::AbstractDownloadStrategy
# TODO: should be part of url object
def mirrors
@mirrors ||= []
end
def tarball_path
@tarball_path ||= Hbc.cache.join("#{name}--#{version}#{ext}")
end
def temporary_path
@temporary_path ||= tarball_path.sub(%r{$}, ".incomplete")
end
def cached_location
tarball_path
end
def clear_cache
[cached_location, temporary_path].each do |f|
next unless f.exist?
raise CurlDownloadStrategyError, "#{f} is in use by another process" if Hbc::Utils.file_locked?(f)
f.unlink
def clear_cache
cached_location.rmtree if cached_location.exist?
end
end
def downloaded_size
temporary_path.size? || 0
end
class CurlDownloadStrategy < AbstractDownloadStrategy
# TODO: should be part of url object
def mirrors
@mirrors ||= []
end
def _fetch
odebug "Calling curl with args #{cask_curl_args.utf8_inspect}"
curl(*cask_curl_args)
end
def tarball_path
@tarball_path ||= Hbc.cache.join("#{name}--#{version}#{ext}")
end
def fetch
ohai "Downloading #{@url}"
if tarball_path.exist?
puts "Already downloaded: #{tarball_path}"
else
had_incomplete_download = temporary_path.exist?
begin
File.open(temporary_path, "w+") do |f|
f.flock(File::LOCK_EX)
_fetch
f.flock(File::LOCK_UN)
end
rescue ErrorDuringExecution
# 33 == range not supported
# try wiping the incomplete download and retrying once
if $CHILD_STATUS.exitstatus == 33 && had_incomplete_download
ohai "Trying a full download"
temporary_path.unlink
had_incomplete_download = false
retry
end
def temporary_path
@temporary_path ||= tarball_path.sub(%r{$}, ".incomplete")
end
msg = @url
msg.concat("\nThe incomplete download is cached at #{temporary_path}") if temporary_path.exist?
raise CurlDownloadStrategyError, msg
def cached_location
tarball_path
end
def clear_cache
[cached_location, temporary_path].each do |f|
next unless f.exist?
raise CurlDownloadStrategyError, "#{f} is in use by another process" if Utils.file_locked?(f)
f.unlink
end
ignore_interrupts { temporary_path.rename(tarball_path) }
end
tarball_path
rescue CurlDownloadStrategyError
raise if mirrors.empty?
puts "Trying a mirror..."
@url = mirrors.shift
retry
end
private
def downloaded_size
temporary_path.size? || 0
end
def cask_curl_args
default_curl_args.tap do |args|
args.concat(user_agent_args)
args.concat(cookies_args)
args.concat(referer_args)
def _fetch
odebug "Calling curl with args #{cask_curl_args.utf8_inspect}"
curl(*cask_curl_args)
end
def fetch
ohai "Downloading #{@url}"
if tarball_path.exist?
puts "Already downloaded: #{tarball_path}"
else
had_incomplete_download = temporary_path.exist?
begin
File.open(temporary_path, "w+") do |f|
f.flock(File::LOCK_EX)
_fetch
f.flock(File::LOCK_UN)
end
rescue ErrorDuringExecution
# 33 == range not supported
# try wiping the incomplete download and retrying once
if $CHILD_STATUS.exitstatus == 33 && had_incomplete_download
ohai "Trying a full download"
temporary_path.unlink
had_incomplete_download = false
retry
end
msg = @url
msg.concat("\nThe incomplete download is cached at #{temporary_path}") if temporary_path.exist?
raise CurlDownloadStrategyError, msg
end
ignore_interrupts { temporary_path.rename(tarball_path) }
end
tarball_path
rescue CurlDownloadStrategyError
raise if mirrors.empty?
puts "Trying a mirror..."
@url = mirrors.shift
retry
end
private
def cask_curl_args
default_curl_args.tap do |args|
args.concat(user_agent_args)
args.concat(cookies_args)
args.concat(referer_args)
end
end
def default_curl_args
[url, "-C", downloaded_size, "-o", temporary_path]
end
def user_agent_args
if uri_object.user_agent
["-A", uri_object.user_agent]
else
[]
end
end
def cookies_args
if uri_object.cookies
[
"-b",
# sort_by is for predictability between Ruby versions
uri_object
.cookies
.sort_by(&:to_s)
.map { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }
.join(";"),
]
else
[]
end
end
def referer_args
if uri_object.referer
["-e", uri_object.referer]
else
[]
end
end
def ext
Pathname.new(@url).extname
end
end
def default_curl_args
[url, "-C", downloaded_size, "-o", temporary_path]
end
def user_agent_args
if uri_object.user_agent
["-A", uri_object.user_agent]
else
[]
class CurlPostDownloadStrategy < CurlDownloadStrategy
def cask_curl_args
super
default_curl_args.concat(post_args)
end
end
def cookies_args
if uri_object.cookies
[
"-b",
def post_args
if uri_object.data
# sort_by is for predictability between Ruby versions
uri_object
.cookies
.data
.sort_by(&:to_s)
.map { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }
.join(";"),
]
else
[]
end
end
def referer_args
if uri_object.referer
["-e", uri_object.referer]
else
[]
end
end
def ext
Pathname.new(@url).extname
end
end
class Hbc::CurlPostDownloadStrategy < Hbc::CurlDownloadStrategy
def cask_curl_args
super
default_curl_args.concat(post_args)
end
def post_args
if uri_object.data
# sort_by is for predictability between Ruby versions
uri_object
.data
.sort_by(&:to_s)
.map { |key, value| ["-d", "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"] }
.flatten
else
["-X", "POST"]
end
end
end
class Hbc::SubversionDownloadStrategy < Hbc::HbVCSDownloadStrategy
def cache_tag
# TODO: pass versions as symbols, support :head here
version == "head" ? "svn-HEAD" : "svn"
end
def repo_valid?
@clone.join(".svn").directory?
end
def repo_url
`svn info '#{@clone}' 2>/dev/null`.strip[%r{^URL: (.+)$}, 1]
end
# super does not provide checks for already-existing downloads
def fetch
if tarball_path.exist?
puts "Already downloaded: #{tarball_path}"
else
@url = @url.sub(%r{^svn\+}, "") if @url =~ %r{^svn\+http://}
ohai "Checking out #{@url}"
clear_cache unless @url.chomp("/") == repo_url || quiet_system("svn", "switch", @url, @clone)
if @clone.exist? && !repo_valid?
puts "Removing invalid SVN repo from cache"
clear_cache
end
case @ref_type
when :revision
fetch_repo @clone, @url, @ref
when :revisions
# nil is OK for main_revision, as fetch_repo will then get latest
main_revision = @ref[:trunk]
fetch_repo @clone, @url, main_revision, true
fetch_externals do |external_name, external_url|
fetch_repo @clone + external_name, external_url, @ref[external_name], true
end
.map { |key, value| ["-d", "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"] }
.flatten
else
fetch_repo @clone, @url
["-X", "POST"]
end
compress
end
tarball_path
end
# This primary reason for redefining this method is the trust_cert
# option, controllable from the Cask definition. We also force
# consistent timestamps. The rest of this method is similar to
# Homebrew's, but translated to local idiom.
def fetch_repo(target, url, revision = uri_object.revision, ignore_externals = false)
# Use "svn up" when the repository already exists locally.
# This saves on bandwidth and will have a similar effect to verifying the
# cache as it will make any changes to get the right revision.
svncommand = target.directory? ? "up" : "checkout"
args = [svncommand]
# SVN shipped with XCode 3.1.4 can't force a checkout.
args << "--force" unless MacOS.version == :leopard
# make timestamps consistent for checksumming
args.concat(%w[--config-option config:miscellany:use-commit-times=yes])
if uri_object.trust_cert
args << "--trust-server-cert"
args << "--non-interactive"
end
args << url unless target.directory?
args << target
args << "-r" << revision if revision
args << "--ignore-externals" if ignore_externals
@command.run!("/usr/bin/svn",
args: args,
print_stderr: false)
end
def tarball_path
@tarball_path ||= cached_location.dirname.join(cached_location.basename.to_s + "-#{@cask.version}.tar")
end
def shell_quote(str)
# Oh god escaping shell args.
# See http://notetoself.vrensk.com/2008/08/escaping-single-quotes-in-ruby-harder-than-expected/
str.gsub(%r{\\|'}) { |c| "\\#{c}" }
end
def fetch_externals
`svn propget svn:externals '#{shell_quote(@url)}'`.chomp.each_line do |line|
name, url = line.split(%r{\s+})
yield name, url
end
end
private
class SubversionDownloadStrategy < HbVCSDownloadStrategy
def cache_tag
# TODO: pass versions as symbols, support :head here
version == "head" ? "svn-HEAD" : "svn"
end
# TODO/UPDATE: the tar approach explained below is fragile
# against challenges such as case-sensitive filesystems,
# and must be re-implemented.
#
# Seems nutty: we "download" the contents into a tape archive.
# Why?
# * A single file is tractable to the rest of the Cask toolchain,
# * An alternative would be to create a Directory container type.
# However, some type of file-serialization trick would still be
# needed in order to enable calculating a single checksum over
# a directory. So, in that alternative implementation, the
# special cases would propagate outside this class, including
# the use of tar or equivalent.
# * SubversionDownloadStrategy.cached_location is not versioned
# * tarball_path provides a needed return value for our overridden
# fetch method.
# * We can also take this private opportunity to strip files from
# the download which are protocol-specific.
def repo_valid?
@clone.join(".svn").directory?
end
def compress
Dir.chdir(cached_location) do
@command.run!("/usr/bin/tar",
args: ['-s/^\.//', "--exclude", ".svn", "-cf", Pathname.new(tarball_path), "--", "."],
def repo_url
`svn info '#{@clone}' 2>/dev/null`.strip[%r{^URL: (.+)$}, 1]
end
# super does not provide checks for already-existing downloads
def fetch
if tarball_path.exist?
puts "Already downloaded: #{tarball_path}"
else
@url = @url.sub(%r{^svn\+}, "") if @url =~ %r{^svn\+http://}
ohai "Checking out #{@url}"
clear_cache unless @url.chomp("/") == repo_url || quiet_system("svn", "switch", @url, @clone)
if @clone.exist? && !repo_valid?
puts "Removing invalid SVN repo from cache"
clear_cache
end
case @ref_type
when :revision
fetch_repo @clone, @url, @ref
when :revisions
# nil is OK for main_revision, as fetch_repo will then get latest
main_revision = @ref[:trunk]
fetch_repo @clone, @url, main_revision, true
fetch_externals do |external_name, external_url|
fetch_repo @clone + external_name, external_url, @ref[external_name], true
end
else
fetch_repo @clone, @url
end
compress
end
tarball_path
end
# This primary reason for redefining this method is the trust_cert
# option, controllable from the Cask definition. We also force
# consistent timestamps. The rest of this method is similar to
# Homebrew's, but translated to local idiom.
def fetch_repo(target, url, revision = uri_object.revision, ignore_externals = false)
# Use "svn up" when the repository already exists locally.
# This saves on bandwidth and will have a similar effect to verifying the
# cache as it will make any changes to get the right revision.
svncommand = target.directory? ? "up" : "checkout"
args = [svncommand]
# SVN shipped with XCode 3.1.4 can't force a checkout.
args << "--force" unless MacOS.version == :leopard
# make timestamps consistent for checksumming
args.concat(%w[--config-option config:miscellany:use-commit-times=yes])
if uri_object.trust_cert
args << "--trust-server-cert"
args << "--non-interactive"
end
args << url unless target.directory?
args << target
args << "-r" << revision if revision
args << "--ignore-externals" if ignore_externals
@command.run!("/usr/bin/svn",
args: args,
print_stderr: false)
end
clear_cache
def tarball_path
@tarball_path ||= cached_location.dirname.join(cached_location.basename.to_s + "-#{@cask.version}.tar")
end
def shell_quote(str)
# Oh god escaping shell args.
# See http://notetoself.vrensk.com/2008/08/escaping-single-quotes-in-ruby-harder-than-expected/
str.gsub(%r{\\|'}) { |c| "\\#{c}" }
end
def fetch_externals
`svn propget svn:externals '#{shell_quote(@url)}'`.chomp.each_line do |line|
name, url = line.split(%r{\s+})
yield name, url
end
end
private
# TODO/UPDATE: the tar approach explained below is fragile
# against challenges such as case-sensitive filesystems,
# and must be re-implemented.
#
# Seems nutty: we "download" the contents into a tape archive.
# Why?
# * A single file is tractable to the rest of the Cask toolchain,
# * An alternative would be to create a Directory container type.
# However, some type of file-serialization trick would still be
# needed in order to enable calculating a single checksum over
# a directory. So, in that alternative implementation, the
# special cases would propagate outside this class, including
# the use of tar or equivalent.
# * SubversionDownloadStrategy.cached_location is not versioned
# * tarball_path provides a needed return value for our overridden
# fetch method.
# * We can also take this private opportunity to strip files from
# the download which are protocol-specific.
def compress
Dir.chdir(cached_location) do
@command.run!("/usr/bin/tar",
args: ['-s/^\.//', "--exclude", ".svn", "-cf", Pathname.new(tarball_path), "--", "."],
print_stderr: false)
end
clear_cache
end
end
end

View File

@ -1,7 +1,5 @@
require "set"
class Hbc::DSL; end
require "hbc/dsl/appcast"
require "hbc/dsl/base"
require "hbc/dsl/caveats"
@ -18,274 +16,276 @@ require "hbc/dsl/uninstall_postflight"
require "hbc/dsl/uninstall_preflight"
require "hbc/dsl/version"
class Hbc::DSL
ORDINARY_ARTIFACT_TYPES = [
:app,
:artifact,
:audio_unit_plugin,
:binary,
:colorpicker,
:font,
:input_method,
:internet_plugin,
:pkg,
:prefpane,
:qlplugin,
:screen_saver,
:service,
:stage_only,
:suite,
:vst_plugin,
:vst3_plugin,
].freeze
module Hbc
class DSL
ORDINARY_ARTIFACT_TYPES = [
:app,
:artifact,
:audio_unit_plugin,
:binary,
:colorpicker,
:font,
:input_method,
:internet_plugin,
:pkg,
:prefpane,
:qlplugin,
:screen_saver,
:service,
:stage_only,
:suite,
:vst_plugin,
:vst3_plugin,
].freeze
ACTIVATABLE_ARTIFACT_TYPES = ([:installer, *ORDINARY_ARTIFACT_TYPES] - [:stage_only]).freeze
ACTIVATABLE_ARTIFACT_TYPES = ([:installer, *ORDINARY_ARTIFACT_TYPES] - [:stage_only]).freeze
SPECIAL_ARTIFACT_TYPES = [
:uninstall,
:zap,
SPECIAL_ARTIFACT_TYPES = [
:uninstall,
:zap,
].freeze
ARTIFACT_BLOCK_TYPES = [
:preflight,
:postflight,
:uninstall_preflight,
:uninstall_postflight,
].freeze
ARTIFACT_BLOCK_TYPES = [
:preflight,
:postflight,
:uninstall_preflight,
:uninstall_postflight,
].freeze
DSL_METHODS = Set.new [
:accessibility_access,
:appcast,
:artifacts,
:auto_updates,
:caskroom_path,
:caveats,
:conflicts_with,
:container,
:depends_on,
:gpg,
:homepage,
:license,
:name,
:sha256,
:staged_path,
:url,
:version,
:appdir,
*ORDINARY_ARTIFACT_TYPES,
*ACTIVATABLE_ARTIFACT_TYPES,
*SPECIAL_ARTIFACT_TYPES,
*ARTIFACT_BLOCK_TYPES,
].freeze
DSL_METHODS = Set.new [
:accessibility_access,
:appcast,
:artifacts,
:auto_updates,
:caskroom_path,
:caveats,
:conflicts_with,
:container,
:depends_on,
:gpg,
:homepage,
:license,
:name,
:sha256,
:staged_path,
:url,
:version,
:appdir,
*ORDINARY_ARTIFACT_TYPES,
*ACTIVATABLE_ARTIFACT_TYPES,
*SPECIAL_ARTIFACT_TYPES,
*ARTIFACT_BLOCK_TYPES,
].freeze
attr_reader :token
def initialize(token)
@token = token
end
def name(*args)
@name ||= []
return @name if args.empty?
@name.concat(args.flatten)
end
def assert_only_one_stanza_allowed(stanza, arg_given)
return unless instance_variable_defined?("@#{stanza}") && arg_given
raise Hbc::CaskInvalidError.new(token, "'#{stanza}' stanza may only appear once")
end
def homepage(homepage = nil)
assert_only_one_stanza_allowed :homepage, !homepage.nil?
@homepage ||= homepage
end
def url(*args, &block)
url_given = !args.empty? || block_given?
return @url unless url_given
assert_only_one_stanza_allowed :url, url_given
@url ||= begin
Hbc::URL.from(*args, &block)
rescue StandardError => e
raise Hbc::CaskInvalidError.new(token, "'url' stanza failed with: #{e}")
attr_reader :token
def initialize(token)
@token = token
end
end
def appcast(*args)
return @appcast if args.empty?
assert_only_one_stanza_allowed :appcast, !args.empty?
@appcast ||= begin
Hbc::DSL::Appcast.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(token, e)
def name(*args)
@name ||= []
return @name if args.empty?
@name.concat(args.flatten)
end
end
def gpg(*args)
return @gpg if args.empty?
assert_only_one_stanza_allowed :gpg, !args.empty?
@gpg ||= begin
Hbc::DSL::Gpg.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(token, e)
def assert_only_one_stanza_allowed(stanza, arg_given)
return unless instance_variable_defined?("@#{stanza}") && arg_given
raise CaskInvalidError.new(token, "'#{stanza}' stanza may only appear once")
end
end
def container(*args)
return @container if args.empty?
# TODO: remove this constraint, and instead merge multiple container stanzas
assert_only_one_stanza_allowed :container, !args.empty?
@container ||= begin
Hbc::DSL::Container.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(token, e)
def homepage(homepage = nil)
assert_only_one_stanza_allowed :homepage, !homepage.nil?
@homepage ||= homepage
end
# TODO: remove this backward-compatibility section after removing nested_container
if @container && @container.nested
artifacts[:nested_container] << @container.nested
end
@container
end
SYMBOLIC_VERSIONS = Set.new [
:latest,
]
def version(arg = nil)
return @version if arg.nil?
assert_only_one_stanza_allowed :version, !arg.nil?
raise Hbc::CaskInvalidError.new(token, "invalid 'version' value: '#{arg.inspect}'") if !arg.is_a?(String) && !SYMBOLIC_VERSIONS.include?(arg)
@version ||= Hbc::DSL::Version.new(arg)
end
SYMBOLIC_SHA256S = Set.new [
:no_check,
]
def sha256(arg = nil)
return @sha256 if arg.nil?
assert_only_one_stanza_allowed :sha256, !arg.nil?
raise Hbc::CaskInvalidError.new(token, "invalid 'sha256' value: '#{arg.inspect}'") if !arg.is_a?(String) && !SYMBOLIC_SHA256S.include?(arg)
@sha256 ||= arg
end
def license(arg = nil)
return @license if arg.nil?
assert_only_one_stanza_allowed :license, !arg.nil?
@license ||= begin
Hbc::DSL::License.new(arg) unless arg.nil?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(token, e)
end
end
# depends_on uses a load method so that multiple stanzas can be merged
def depends_on(*args)
return @depends_on if args.empty?
@depends_on ||= Hbc::DSL::DependsOn.new
begin
@depends_on.load(*args) unless args.empty?
rescue RuntimeError => e
raise Hbc::CaskInvalidError.new(token, e)
end
@depends_on
end
def conflicts_with(*args)
return @conflicts_with if args.empty?
# TODO: remove this constraint, and instead merge multiple conflicts_with stanzas
assert_only_one_stanza_allowed :conflicts_with, !args.empty?
@conflicts_with ||= begin
Hbc::DSL::ConflictsWith.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(token, e)
end
end
def artifacts
@artifacts ||= Hash.new { |hash, key| hash[key] = Set.new }
end
def caskroom_path
@caskroom_path ||= Hbc.caskroom.join(token)
end
def staged_path
return @staged_path if @staged_path
cask_version = version || :unknown
@staged_path = caskroom_path.join(cask_version.to_s)
end
def caveats(*string, &block)
@caveats ||= []
if block_given?
@caveats << Hbc::Caveats.new(block)
elsif string.any?
@caveats << string.map { |s| s.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n") }
end
@caveats
end
def accessibility_access(accessibility_access = nil)
assert_only_one_stanza_allowed :accessibility_access, !accessibility_access.nil?
@accessibility_access ||= accessibility_access
end
def auto_updates(auto_updates = nil)
assert_only_one_stanza_allowed :auto_updates, !auto_updates.nil?
@auto_updates ||= auto_updates
end
ORDINARY_ARTIFACT_TYPES.each do |type|
define_method(type) do |*args|
if type == :stage_only && args != [true]
raise Hbc::CaskInvalidError.new(token, "'stage_only' takes a single argument: true")
end
artifacts[type] << args
if artifacts.key?(:stage_only) && artifacts.keys.count > 1 &&
!(artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).empty?
raise Hbc::CaskInvalidError.new(token, "'stage_only' must be the only activatable artifact")
def url(*args, &block)
url_given = !args.empty? || block_given?
return @url unless url_given
assert_only_one_stanza_allowed :url, url_given
@url ||= begin
URL.from(*args, &block)
rescue StandardError => e
raise CaskInvalidError.new(token, "'url' stanza failed with: #{e}")
end
end
end
def installer(*args)
return artifacts[:installer] if args.empty?
artifacts[:installer] << Hbc::DSL::Installer.new(*args)
raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only)
rescue StandardError => e
raise Hbc::CaskInvalidError.new(token, e)
end
SPECIAL_ARTIFACT_TYPES.each do |type|
define_method(type) do |*args|
artifacts[type].merge(args)
def appcast(*args)
return @appcast if args.empty?
assert_only_one_stanza_allowed :appcast, !args.empty?
@appcast ||= begin
DSL::Appcast.new(*args) unless args.empty?
rescue StandardError => e
raise CaskInvalidError.new(token, e)
end
end
end
ARTIFACT_BLOCK_TYPES.each do |type|
define_method(type) do |&block|
artifacts[type] << block
def gpg(*args)
return @gpg if args.empty?
assert_only_one_stanza_allowed :gpg, !args.empty?
@gpg ||= begin
DSL::Gpg.new(*args) unless args.empty?
rescue StandardError => e
raise CaskInvalidError.new(token, e)
end
end
end
def method_missing(method, *)
if method
Hbc::Utils.method_missing_message(method, token)
nil
else
super
def container(*args)
return @container if args.empty?
# TODO: remove this constraint, and instead merge multiple container stanzas
assert_only_one_stanza_allowed :container, !args.empty?
@container ||= begin
DSL::Container.new(*args) unless args.empty?
rescue StandardError => e
raise CaskInvalidError.new(token, e)
end
# TODO: remove this backward-compatibility section after removing nested_container
if @container && @container.nested
artifacts[:nested_container] << @container.nested
end
@container
end
end
def respond_to_missing?(*)
true
end
SYMBOLIC_VERSIONS = Set.new [
:latest,
]
def appdir
self.class.appdir
end
def version(arg = nil)
return @version if arg.nil?
assert_only_one_stanza_allowed :version, !arg.nil?
raise CaskInvalidError.new(token, "invalid 'version' value: '#{arg.inspect}'") if !arg.is_a?(String) && !SYMBOLIC_VERSIONS.include?(arg)
@version ||= DSL::Version.new(arg)
end
def self.appdir
Hbc.appdir.sub(%r{\/$}, "")
SYMBOLIC_SHA256S = Set.new [
:no_check,
]
def sha256(arg = nil)
return @sha256 if arg.nil?
assert_only_one_stanza_allowed :sha256, !arg.nil?
raise CaskInvalidError.new(token, "invalid 'sha256' value: '#{arg.inspect}'") if !arg.is_a?(String) && !SYMBOLIC_SHA256S.include?(arg)
@sha256 ||= arg
end
def license(arg = nil)
return @license if arg.nil?
assert_only_one_stanza_allowed :license, !arg.nil?
@license ||= begin
DSL::License.new(arg) unless arg.nil?
rescue StandardError => e
raise CaskInvalidError.new(token, e)
end
end
# depends_on uses a load method so that multiple stanzas can be merged
def depends_on(*args)
return @depends_on if args.empty?
@depends_on ||= DSL::DependsOn.new
begin
@depends_on.load(*args) unless args.empty?
rescue RuntimeError => e
raise CaskInvalidError.new(token, e)
end
@depends_on
end
def conflicts_with(*args)
return @conflicts_with if args.empty?
# TODO: remove this constraint, and instead merge multiple conflicts_with stanzas
assert_only_one_stanza_allowed :conflicts_with, !args.empty?
@conflicts_with ||= begin
DSL::ConflictsWith.new(*args) unless args.empty?
rescue StandardError => e
raise CaskInvalidError.new(token, e)
end
end
def artifacts
@artifacts ||= Hash.new { |hash, key| hash[key] = Set.new }
end
def caskroom_path
@caskroom_path ||= Hbc.caskroom.join(token)
end
def staged_path
return @staged_path if @staged_path
cask_version = version || :unknown
@staged_path = caskroom_path.join(cask_version.to_s)
end
def caveats(*string, &block)
@caveats ||= []
if block_given?
@caveats << Hbc::Caveats.new(block)
elsif string.any?
@caveats << string.map { |s| s.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n") }
end
@caveats
end
def accessibility_access(accessibility_access = nil)
assert_only_one_stanza_allowed :accessibility_access, !accessibility_access.nil?
@accessibility_access ||= accessibility_access
end
def auto_updates(auto_updates = nil)
assert_only_one_stanza_allowed :auto_updates, !auto_updates.nil?
@auto_updates ||= auto_updates
end
ORDINARY_ARTIFACT_TYPES.each do |type|
define_method(type) do |*args|
if type == :stage_only && args != [true]
raise CaskInvalidError.new(token, "'stage_only' takes a single argument: true")
end
artifacts[type] << args
if artifacts.key?(:stage_only) && artifacts.keys.count > 1 &&
!(artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).empty?
raise CaskInvalidError.new(token, "'stage_only' must be the only activatable artifact")
end
end
end
def installer(*args)
return artifacts[:installer] if args.empty?
artifacts[:installer] << DSL::Installer.new(*args)
raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only)
rescue StandardError => e
raise CaskInvalidError.new(token, e)
end
SPECIAL_ARTIFACT_TYPES.each do |type|
define_method(type) do |*args|
artifacts[type].merge(args)
end
end
ARTIFACT_BLOCK_TYPES.each do |type|
define_method(type) do |&block|
artifacts[type] << block
end
end
def method_missing(method, *)
if method
Utils.method_missing_message(method, token)
nil
else
super
end
end
def respond_to_missing?(*)
true
end
def appdir
self.class.appdir
end
def self.appdir
Hbc.appdir.sub(%r{\/$}, "")
end
end
end

View File

@ -1,17 +1,21 @@
class Hbc::DSL::Appcast
attr_reader :parameters, :checkpoint
module Hbc
class DSL
class Appcast
attr_reader :parameters, :checkpoint
def initialize(uri, parameters = {})
@parameters = parameters
@uri = Hbc::UnderscoreSupportingURI.parse(uri)
@checkpoint = @parameters[:checkpoint]
end
def initialize(uri, parameters = {})
@parameters = parameters
@uri = UnderscoreSupportingURI.parse(uri)
@checkpoint = @parameters[:checkpoint]
end
def to_yaml
[@uri, @parameters].to_yaml
end
def to_yaml
[@uri, @parameters].to_yaml
end
def to_s
@uri.to_s
def to_s
@uri.to_s
end
end
end
end

View File

@ -1,29 +1,33 @@
class Hbc::DSL::Base
extend Forwardable
module Hbc
class DSL
class Base
extend Forwardable
def initialize(cask, command = Hbc::SystemCommand)
@cask = cask
@command = command
end
def initialize(cask, command = SystemCommand)
@cask = cask
@command = command
end
def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir
def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir
def system_command(executable, options = {})
@command.run!(executable, options)
end
def system_command(executable, options = {})
@command.run!(executable, options)
end
def method_missing(method, *)
if method
underscored_class = self.class.name.gsub(%r{([[:lower:]])([[:upper:]][[:lower:]])}, '\1_\2').downcase
section = underscored_class.downcase.split("::").last
Hbc::Utils.method_missing_message(method, @cask.to_s, section)
nil
else
super
def method_missing(method, *)
if method
underscored_class = self.class.name.gsub(%r{([[:lower:]])([[:upper:]][[:lower:]])}, '\1_\2').downcase
section = underscored_class.downcase.split("::").last
Utils.method_missing_message(method, @cask.to_s, section)
nil
else
super
end
end
def respond_to_missing?(*)
true
end
end
end
def respond_to_missing?(*)
true
end
end

View File

@ -5,108 +5,112 @@
# ( The return value of the last method in the block is also sent
# to the output by the caller, but that feature is only for the
# convenience of Cask authors. )
class Hbc::DSL::Caveats < Hbc::DSL::Base
def path_environment_variable(path)
puts <<-EOS.undent
To use #{@cask}, you may need to add the #{path} directory
to your PATH environment variable, eg (for bash shell):
module Hbc
class DSL
class Caveats < Base
def path_environment_variable(path)
puts <<-EOS.undent
To use #{@cask}, you may need to add the #{path} directory
to your PATH environment variable, eg (for bash shell):
export PATH=#{path}:"$PATH"
export PATH=#{path}:"$PATH"
EOS
end
EOS
end
def zsh_path_helper(path)
puts <<-EOS.undent
To use #{@cask}, zsh users may need to add the following line to their
~/.zprofile. (Among other effects, #{path} will be added to the
PATH environment variable):
def zsh_path_helper(path)
puts <<-EOS.undent
To use #{@cask}, zsh users may need to add the following line to their
~/.zprofile. (Among other effects, #{path} will be added to the
PATH environment variable):
eval `/usr/libexec/path_helper -s`
eval `/usr/libexec/path_helper -s`
EOS
end
EOS
end
def files_in_usr_local
localpath = "/usr/local"
return unless Hbc.homebrew_prefix.to_s.downcase.start_with?(localpath)
puts <<-EOS.undent
Cask #{@cask} installs files under "#{localpath}". The presence of such
files can cause warnings when running "brew doctor", which is considered
to be a bug in Homebrew-Cask.
def files_in_usr_local
localpath = "/usr/local"
return unless Hbc.homebrew_prefix.to_s.downcase.start_with?(localpath)
puts <<-EOS.undent
Cask #{@cask} installs files under "#{localpath}". The presence of such
files can cause warnings when running "brew doctor", which is considered
to be a bug in Homebrew-Cask.
EOS
end
EOS
end
def depends_on_java(java_version = "any")
if java_version == "any"
puts <<-EOS.undent
#{@cask} requires Java. You can install the latest version with
def depends_on_java(java_version = "any")
if java_version == "any"
puts <<-EOS.undent
#{@cask} requires Java. You can install the latest version with
brew cask install java
brew cask install java
EOS
elsif java_version.include?("8") || java_version.include?("+")
puts <<-EOS.undent
#{@cask} requires Java #{java_version}. You can install the latest version with
EOS
elsif java_version.include?("8") || java_version.include?("+")
puts <<-EOS.undent
#{@cask} requires Java #{java_version}. You can install the latest version with
brew cask install java
brew cask install java
EOS
else
puts <<-EOS.undent
#{@cask} requires Java #{java_version}. You can install it with
EOS
else
puts <<-EOS.undent
#{@cask} requires Java #{java_version}. You can install it with
brew cask install caskroom/versions/java#{java_version}
brew cask install caskroom/versions/java#{java_version}
EOS
EOS
end
end
def logout
puts <<-EOS.undent
You must log out and log back in for the installation of #{@cask}
to take effect.
EOS
end
def reboot
puts <<-EOS.undent
You must reboot for the installation of #{@cask} to take effect.
EOS
end
def discontinued
puts <<-EOS.undent
#{@cask} has been officially discontinued upstream.
It may stop working correctly (or at all) in recent versions of macOS.
EOS
end
def free_license(web_page)
puts <<-EOS.undent
The vendor offers a free license for #{@cask} at
#{web_page}
EOS
end
def malware(radar_number)
puts <<-EOS.undent
#{@cask} has been reported to bundle malware. Like with any app, use at your own risk.
A report has been made to Apple about this app. Their certificate will hopefully be revoked.
See the public report at
https://openradar.appspot.com/#{radar_number}
If this report is accurate, please duplicate it at
https://bugreport.apple.com/
If this report is a mistake, please let us know by opening an issue at
https://github.com/caskroom/homebrew-cask/issues/new
EOS
end
end
end
def logout
puts <<-EOS.undent
You must log out and log back in for the installation of #{@cask}
to take effect.
EOS
end
def reboot
puts <<-EOS.undent
You must reboot for the installation of #{@cask} to take effect.
EOS
end
def discontinued
puts <<-EOS.undent
#{@cask} has been officially discontinued upstream.
It may stop working correctly (or at all) in recent versions of macOS.
EOS
end
def free_license(web_page)
puts <<-EOS.undent
The vendor offers a free license for #{@cask} at
#{web_page}
EOS
end
def malware(radar_number)
puts <<-EOS.undent
#{@cask} has been reported to bundle malware. Like with any app, use at your own risk.
A report has been made to Apple about this app. Their certificate will hopefully be revoked.
See the public report at
https://openradar.appspot.com/#{radar_number}
If this report is accurate, please duplicate it at
https://bugreport.apple.com/
If this report is a mistake, please let us know by opening an issue at
https://github.com/caskroom/homebrew-cask/issues/new
EOS
end
end

View File

@ -1,30 +1,34 @@
class Hbc::DSL::ConflictsWith
VALID_KEYS = Set.new [
:formula,
:cask,
:macos,
:arch,
:x11,
:java,
]
module Hbc
class DSL
class ConflictsWith
VALID_KEYS = Set.new [
:formula,
:cask,
:macos,
:arch,
:x11,
:java,
]
attr_accessor(*VALID_KEYS)
attr_accessor :pairs
attr_accessor(*VALID_KEYS)
attr_accessor :pairs
def initialize(pairs = {})
@pairs = pairs
pairs.each do |key, value|
raise "invalid conflicts_with key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
send(writer_method, value)
def initialize(pairs = {})
@pairs = pairs
pairs.each do |key, value|
raise "invalid conflicts_with key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
send(writer_method, value)
end
end
def to_yaml
@pairs.to_yaml
end
def to_s
@pairs.inspect
end
end
end
def to_yaml
@pairs.to_yaml
end
def to_s
@pairs.inspect
end
end

View File

@ -1,26 +1,30 @@
class Hbc::DSL::Container
VALID_KEYS = Set.new [
:type,
:nested,
]
module Hbc
class DSL
class Container
VALID_KEYS = Set.new [
:type,
:nested,
]
attr_accessor(*VALID_KEYS)
attr_accessor :pairs
attr_accessor(*VALID_KEYS)
attr_accessor :pairs
def initialize(pairs = {})
@pairs = pairs
pairs.each do |key, value|
raise "invalid container key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
send(writer_method, value)
def initialize(pairs = {})
@pairs = pairs
pairs.each do |key, value|
raise "invalid container key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
send(writer_method, value)
end
end
def to_yaml
@pairs.to_yaml
end
def to_s
@pairs.inspect
end
end
end
def to_yaml
@pairs.to_yaml
end
def to_s
@pairs.inspect
end
end

View File

@ -1,124 +1,128 @@
require "rubygems"
class Hbc::DSL::DependsOn
VALID_KEYS = Set.new [
:formula,
:cask,
:macos,
:arch,
:x11,
:java,
].freeze
module Hbc
class DSL
class DependsOn
VALID_KEYS = Set.new [
:formula,
:cask,
:macos,
:arch,
:x11,
:java,
].freeze
VALID_ARCHES = {
intel: { type: :intel, bits: [32, 64] },
ppc: { type: :ppc, bits: [32, 64] },
# specific
i386: { type: :intel, bits: 32 },
x86_64: { type: :intel, bits: 64 },
ppc_7400: { type: :ppc, bits: 32 },
ppc_64: { type: :ppc, bits: 64 },
}.freeze
VALID_ARCHES = {
intel: { type: :intel, bits: [32, 64] },
ppc: { type: :ppc, bits: [32, 64] },
# specific
i386: { type: :intel, bits: 32 },
x86_64: { type: :intel, bits: 64 },
ppc_7400: { type: :ppc, bits: 32 },
ppc_64: { type: :ppc, bits: 64 },
}.freeze
# Intentionally undocumented: catch variant spellings.
ARCH_SYNONYMS = {
x86_32: :i386,
x8632: :i386,
x8664: :x86_64,
intel_32: :i386,
intel32: :i386,
intel_64: :x86_64,
intel64: :x86_64,
amd_64: :x86_64,
amd64: :x86_64,
ppc7400: :ppc_7400,
ppc_32: :ppc_7400,
ppc32: :ppc_7400,
ppc64: :ppc_64,
}.freeze
# Intentionally undocumented: catch variant spellings.
ARCH_SYNONYMS = {
x86_32: :i386,
x8632: :i386,
x8664: :x86_64,
intel_32: :i386,
intel32: :i386,
intel_64: :x86_64,
intel64: :x86_64,
amd_64: :x86_64,
amd64: :x86_64,
ppc7400: :ppc_7400,
ppc_32: :ppc_7400,
ppc32: :ppc_7400,
ppc64: :ppc_64,
}.freeze
attr_accessor :java
attr_accessor :pairs
attr_reader :arch, :cask, :formula, :macos, :x11
attr_accessor :java
attr_accessor :pairs
attr_reader :arch, :cask, :formula, :macos, :x11
def initialize
@pairs ||= {}
end
def load(pairs = {})
pairs.each do |key, value|
raise "invalid depends_on key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
@pairs[key] = send(writer_method, value)
end
end
def self.coerce_os_release(arg)
@macos_symbols ||= MacOS::Version::SYMBOLS
@inverted_macos_symbols ||= @macos_symbols.invert
begin
if arg.is_a?(Symbol)
Gem::Version.new(@macos_symbols.fetch(arg))
elsif arg =~ %r{^\s*:?([a-z]\S+)\s*$}i
Gem::Version.new(@macos_symbols.fetch(Regexp.last_match[1].downcase.to_sym))
elsif @inverted_macos_symbols.key?(arg)
Gem::Version.new(arg)
else
raise
def initialize
@pairs ||= {}
end
def load(pairs = {})
pairs.each do |key, value|
raise "invalid depends_on key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
@pairs[key] = send(writer_method, value)
end
end
def self.coerce_os_release(arg)
@macos_symbols ||= MacOS::Version::SYMBOLS
@inverted_macos_symbols ||= @macos_symbols.invert
begin
if arg.is_a?(Symbol)
Gem::Version.new(@macos_symbols.fetch(arg))
elsif arg =~ %r{^\s*:?([a-z]\S+)\s*$}i
Gem::Version.new(@macos_symbols.fetch(Regexp.last_match[1].downcase.to_sym))
elsif @inverted_macos_symbols.key?(arg)
Gem::Version.new(arg)
else
raise
end
rescue StandardError
raise "invalid 'depends_on macos' value: #{arg.inspect}"
end
end
def formula=(*arg)
@formula ||= []
@formula.concat(Array(*arg))
end
def cask=(*arg)
@cask ||= []
@cask.concat(Array(*arg))
end
def macos=(*arg)
@macos ||= []
macos = if arg.count == 1 && arg.first =~ %r{^\s*(<|>|[=<>]=)\s*(\S+)\s*$}
raise "'depends_on macos' comparison expressions cannot be combined" unless @macos.empty?
operator = Regexp.last_match[1].to_sym
release = self.class.coerce_os_release(Regexp.last_match[2])
[[operator, release]]
else
raise "'depends_on macos' comparison expressions cannot be combined" if @macos.first.is_a?(Symbol)
Array(*arg).map { |elt|
self.class.coerce_os_release(elt)
}.sort
end
@macos.concat(macos)
end
def arch=(*arg)
@arch ||= []
arches = Array(*arg).map { |elt|
elt = elt.to_s.downcase.sub(%r{^:}, "").tr("-", "_").to_sym
ARCH_SYNONYMS.key?(elt) ? ARCH_SYNONYMS[elt] : elt
}
invalid_arches = arches - VALID_ARCHES.keys
raise "invalid 'depends_on arch' values: #{invalid_arches.inspect}" unless invalid_arches.empty?
@arch.concat(arches.map { |arch| VALID_ARCHES[arch] })
end
def x11=(arg)
raise "invalid 'depends_on x11' value: #{arg.inspect}" unless [true, false].include?(arg)
@x11 = arg
end
def to_yaml
@pairs.to_yaml
end
def to_s
@pairs.inspect
end
rescue StandardError
raise "invalid 'depends_on macos' value: #{arg.inspect}"
end
end
def formula=(*arg)
@formula ||= []
@formula.concat(Array(*arg))
end
def cask=(*arg)
@cask ||= []
@cask.concat(Array(*arg))
end
def macos=(*arg)
@macos ||= []
macos = if arg.count == 1 && arg.first =~ %r{^\s*(<|>|[=<>]=)\s*(\S+)\s*$}
raise "'depends_on macos' comparison expressions cannot be combined" unless @macos.empty?
operator = Regexp.last_match[1].to_sym
release = self.class.coerce_os_release(Regexp.last_match[2])
[[operator, release]]
else
raise "'depends_on macos' comparison expressions cannot be combined" if @macos.first.is_a?(Symbol)
Array(*arg).map { |elt|
self.class.coerce_os_release(elt)
}.sort
end
@macos.concat(macos)
end
def arch=(*arg)
@arch ||= []
arches = Array(*arg).map { |elt|
elt = elt.to_s.downcase.sub(%r{^:}, "").tr("-", "_").to_sym
ARCH_SYNONYMS.key?(elt) ? ARCH_SYNONYMS[elt] : elt
}
invalid_arches = arches - VALID_ARCHES.keys
raise "invalid 'depends_on arch' values: #{invalid_arches.inspect}" unless invalid_arches.empty?
@arch.concat(arches.map { |arch| VALID_ARCHES[arch] })
end
def x11=(arg)
raise "invalid 'depends_on x11' value: #{arg.inspect}" unless [true, false].include?(arg)
@x11 = arg
end
def to_yaml
@pairs.to_yaml
end
def to_s
@pairs.inspect
end
end

View File

@ -1,43 +1,47 @@
class Hbc::DSL::Gpg
KEY_PARAMETERS = Set.new [
:key_id,
:key_url,
]
module Hbc
class DSL
class Gpg
KEY_PARAMETERS = Set.new [
:key_id,
:key_url,
]
VALID_PARAMETERS = Set.new []
VALID_PARAMETERS.merge KEY_PARAMETERS
VALID_PARAMETERS = Set.new []
VALID_PARAMETERS.merge KEY_PARAMETERS
attr_accessor(*VALID_PARAMETERS)
attr_accessor :signature
attr_accessor(*VALID_PARAMETERS)
attr_accessor :signature
def initialize(signature, parameters = {})
@parameters = parameters
@signature = Hbc::UnderscoreSupportingURI.parse(signature)
parameters.each do |hkey, hvalue|
raise "invalid 'gpg' parameter: '#{hkey.inspect}'" unless VALID_PARAMETERS.include?(hkey)
writer_method = "#{hkey}=".to_sym
hvalue = Hbc::UnderscoreSupportingURI.parse(hvalue) if hkey == :key_url
valid_id?(hvalue) if hkey == :key_id
send(writer_method, hvalue)
def initialize(signature, parameters = {})
@parameters = parameters
@signature = UnderscoreSupportingURI.parse(signature)
parameters.each do |hkey, hvalue|
raise "invalid 'gpg' parameter: '#{hkey.inspect}'" unless VALID_PARAMETERS.include?(hkey)
writer_method = "#{hkey}=".to_sym
hvalue = UnderscoreSupportingURI.parse(hvalue) if hkey == :key_url
valid_id?(hvalue) if hkey == :key_id
send(writer_method, hvalue)
end
return if KEY_PARAMETERS.intersection(parameters.keys).length == 1
raise "'gpg' stanza must include exactly one of: '#{KEY_PARAMETERS.to_a}'"
end
def valid_id?(id)
legal_lengths = Set.new [8, 16, 40]
is_valid = id.is_a?(String) && legal_lengths.include?(id.length) && id[%r{^[0-9a-f]+$}i]
raise "invalid ':key_id' value: '#{id.inspect}'" unless is_valid
is_valid
end
def to_yaml
# bug, :key_url value is not represented as an instance of Hbc::UnderscoreSupportingURI
[@signature, @parameters].to_yaml
end
def to_s
@signature.to_s
end
end
return if KEY_PARAMETERS.intersection(parameters.keys).length == 1
raise "'gpg' stanza must include exactly one of: '#{KEY_PARAMETERS.to_a}'"
end
def valid_id?(id)
legal_lengths = Set.new [8, 16, 40]
is_valid = id.is_a?(String) && legal_lengths.include?(id.length) && id[%r{^[0-9a-f]+$}i]
raise "invalid ':key_id' value: '#{id.inspect}'" unless is_valid
is_valid
end
def to_yaml
# bug, :key_url value is not represented as an instance of Hbc::UnderscoreSupportingURI
[@signature, @parameters].to_yaml
end
def to_s
@signature.to_s
end
end

View File

@ -1,28 +1,32 @@
class Hbc::DSL::Installer
VALID_KEYS = Set.new [
:manual,
:script,
]
module Hbc
class DSL
class Installer
VALID_KEYS = Set.new [
:manual,
:script,
]
attr_accessor(*VALID_KEYS)
attr_accessor(*VALID_KEYS)
def initialize(*parameters)
raise Hbc::CaskInvalidError.new(token, "'installer' stanza requires an argument") if parameters.empty?
parameters = {}.merge(*parameters)
if parameters.key?(:script) && !parameters[:script].respond_to?(:key?)
if parameters.key?(:executable)
raise Hbc::CaskInvalidError.new(token, "'installer' stanza gave arguments for both :script and :executable")
def initialize(*parameters)
raise CaskInvalidError.new(token, "'installer' stanza requires an argument") if parameters.empty?
parameters = {}.merge(*parameters)
if parameters.key?(:script) && !parameters[:script].respond_to?(:key?)
if parameters.key?(:executable)
raise CaskInvalidError.new(token, "'installer' stanza gave arguments for both :script and :executable")
end
parameters[:executable] = parameters[:script]
parameters.delete(:script)
parameters = { script: parameters }
end
unless parameters.keys.length == 1
raise "invalid 'installer' stanza: only one of #{VALID_KEYS.inspect} is permitted"
end
key = parameters.keys.first
raise "invalid 'installer' stanza key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
send(writer_method, parameters[key])
end
parameters[:executable] = parameters[:script]
parameters.delete(:script)
parameters = { script: parameters }
end
unless parameters.keys.length == 1
raise "invalid 'installer' stanza: only one of #{VALID_KEYS.inspect} is permitted"
end
key = parameters.keys.first
raise "invalid 'installer' stanza key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
send(writer_method, parameters[key])
end
end

View File

@ -1,66 +1,70 @@
class Hbc::DSL::License
# a generic category can always be given as a license, so
# category names should be given as both key and value
VALID_LICENSES = {
# license category
unknown: :unknown,
module Hbc
class DSL
class License
# a generic category can always be given as a license, so
# category names should be given as both key and value
VALID_LICENSES = {
# license category
unknown: :unknown,
other: :other,
other: :other,
closed: :closed,
commercial: :closed,
gratis: :closed,
freemium: :closed,
closed: :closed,
commercial: :closed,
gratis: :closed,
freemium: :closed,
oss: :oss,
affero: :oss,
apache: :oss,
arphic: :oss,
artistic: :oss,
bsd: :oss,
cc: :oss,
eclipse: :oss,
gpl: :oss,
isc: :oss,
lppl: :oss,
ncsa: :oss,
mit: :oss,
mpl: :oss,
ofl: :oss,
public_domain: :oss,
ubuntu_font: :oss,
x11: :oss,
}.freeze
oss: :oss,
affero: :oss,
apache: :oss,
arphic: :oss,
artistic: :oss,
bsd: :oss,
cc: :oss,
eclipse: :oss,
gpl: :oss,
isc: :oss,
lppl: :oss,
ncsa: :oss,
mit: :oss,
mpl: :oss,
ofl: :oss,
public_domain: :oss,
ubuntu_font: :oss,
x11: :oss,
}.freeze
DEFAULT_LICENSE = :unknown
DEFAULT_CATEGORY = VALID_LICENSES[DEFAULT_LICENSE]
DEFAULT_LICENSE = :unknown
DEFAULT_CATEGORY = VALID_LICENSES[DEFAULT_LICENSE]
attr_reader :value
attr_reader :value
def self.check_constants
categories = Set.new(VALID_LICENSES.values)
categories.each do |cat|
next if VALID_LICENSES.key?(cat)
raise "license category is not a value: '#{@cat.inspect}'"
def self.check_constants
categories = Set.new(VALID_LICENSES.values)
categories.each do |cat|
next if VALID_LICENSES.key?(cat)
raise "license category is not a value: '#{@cat.inspect}'"
end
end
def self.category(license)
VALID_LICENSES.fetch(license, DEFAULT_CATEGORY)
end
def initialize(arg)
@value = arg
@value = DEFAULT_LICENSE if @value.nil?
return if VALID_LICENSES.key?(@value)
raise "invalid license value: '#{@value.inspect}'"
end
def category
self.class.category(@value)
end
def to_s
@value.inspect
end
end
end
def self.category(license)
VALID_LICENSES.fetch(license, DEFAULT_CATEGORY)
end
def initialize(arg)
@value = arg
@value = DEFAULT_LICENSE if @value.nil?
return if VALID_LICENSES.key?(@value)
raise "invalid license value: '#{@value.inspect}'"
end
def category
self.class.category(@value)
end
def to_s
@value.inspect
end
end

View File

@ -1,9 +1,13 @@
require "hbc/staged"
class Hbc::DSL::Postflight < Hbc::DSL::Base
include Hbc::Staged
module Hbc
class DSL
class Postflight < Base
include Staged
def suppress_move_to_applications(options = {})
# TODO: Remove from all casks because it is no longer needed
def suppress_move_to_applications(options = {})
# TODO: Remove from all casks because it is no longer needed
end
end
end
end

View File

@ -1,3 +1,7 @@
class Hbc::DSL::Preflight < Hbc::DSL::Base
include Hbc::Staged
module Hbc
class DSL
class Preflight < Base
include Staged
end
end
end

Some files were not shown because too many files have changed in this diff Show More