mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Cask: Use nested classes and modules.
This commit is contained in:
parent
687f0fcf72
commit
b86c8efb79
@ -86,7 +86,7 @@ Style/BlockDelimiters:
|
|||||||
- proc
|
- proc
|
||||||
|
|
||||||
Style/ClassAndModuleChildren:
|
Style/ClassAndModuleChildren:
|
||||||
EnforcedStyle: compact
|
EnforcedStyle: nested
|
||||||
|
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
@ -40,22 +40,22 @@ require "utils"
|
|||||||
require "vendor/plist/plist"
|
require "vendor/plist/plist"
|
||||||
|
|
||||||
module Hbc
|
module Hbc
|
||||||
include Hbc::Locations
|
include Locations
|
||||||
include Hbc::Scopes
|
include Scopes
|
||||||
include Hbc::Options
|
include Options
|
||||||
include Hbc::Utils
|
include Utils
|
||||||
|
|
||||||
def self.init
|
def self.init
|
||||||
Hbc::Cache.ensure_cache_exists
|
Cache.ensure_cache_exists
|
||||||
Hbc::Cache.migrate_legacy_cache
|
Cache.migrate_legacy_cache
|
||||||
|
|
||||||
Hbc::Caskroom.migrate_caskroom_from_repo_to_prefix
|
Caskroom.migrate_caskroom_from_repo_to_prefix
|
||||||
Hbc::Caskroom.ensure_caskroom_exists
|
Caskroom.ensure_caskroom_exists
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.load(query)
|
def self.load(query)
|
||||||
odebug "Loading Cask definitions"
|
odebug "Loading Cask definitions"
|
||||||
cask = Hbc::Source.for_query(query).load
|
cask = Source.for_query(query).load
|
||||||
cask.dumpcask
|
cask.dumpcask
|
||||||
cask
|
cask
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
module Hbc::Artifact; end
|
|
||||||
|
|
||||||
require "hbc/artifact/app"
|
require "hbc/artifact/app"
|
||||||
require "hbc/artifact/artifact" # generic 'artifact' stanza
|
require "hbc/artifact/artifact" # generic 'artifact' stanza
|
||||||
require "hbc/artifact/binary"
|
require "hbc/artifact/binary"
|
||||||
@ -24,42 +22,44 @@ require "hbc/artifact/suite"
|
|||||||
require "hbc/artifact/uninstall"
|
require "hbc/artifact/uninstall"
|
||||||
require "hbc/artifact/zap"
|
require "hbc/artifact/zap"
|
||||||
|
|
||||||
module Hbc::Artifact
|
module Hbc
|
||||||
# NOTE: order is important here, since we want to extract nested containers
|
module Artifact
|
||||||
# before we handle any other artifacts
|
# NOTE: order is important here, since we want to extract nested containers
|
||||||
def self.artifacts
|
# before we handle any other artifacts
|
||||||
[
|
def self.artifacts
|
||||||
Hbc::Artifact::PreflightBlock,
|
[
|
||||||
Hbc::Artifact::NestedContainer,
|
PreflightBlock,
|
||||||
Hbc::Artifact::Installer,
|
NestedContainer,
|
||||||
Hbc::Artifact::App,
|
Installer,
|
||||||
Hbc::Artifact::Suite,
|
App,
|
||||||
Hbc::Artifact::Artifact, # generic 'artifact' stanza
|
Suite,
|
||||||
Hbc::Artifact::Colorpicker,
|
Artifact, # generic 'artifact' stanza
|
||||||
Hbc::Artifact::Pkg,
|
Colorpicker,
|
||||||
Hbc::Artifact::Prefpane,
|
Pkg,
|
||||||
Hbc::Artifact::Qlplugin,
|
Prefpane,
|
||||||
Hbc::Artifact::Font,
|
Qlplugin,
|
||||||
Hbc::Artifact::Service,
|
Font,
|
||||||
Hbc::Artifact::StageOnly,
|
Service,
|
||||||
Hbc::Artifact::Binary,
|
StageOnly,
|
||||||
Hbc::Artifact::InputMethod,
|
Binary,
|
||||||
Hbc::Artifact::InternetPlugin,
|
InputMethod,
|
||||||
Hbc::Artifact::AudioUnitPlugin,
|
InternetPlugin,
|
||||||
Hbc::Artifact::VstPlugin,
|
AudioUnitPlugin,
|
||||||
Hbc::Artifact::Vst3Plugin,
|
VstPlugin,
|
||||||
Hbc::Artifact::ScreenSaver,
|
Vst3Plugin,
|
||||||
Hbc::Artifact::Uninstall,
|
ScreenSaver,
|
||||||
Hbc::Artifact::PostflightBlock,
|
Uninstall,
|
||||||
Hbc::Artifact::Zap,
|
PostflightBlock,
|
||||||
]
|
Zap,
|
||||||
end
|
]
|
||||||
|
end
|
||||||
|
|
||||||
def self.for_cask(cask)
|
def self.for_cask(cask)
|
||||||
odebug "Determining which artifacts are present in Cask #{cask}"
|
odebug "Determining which artifacts are present in Cask #{cask}"
|
||||||
artifacts.select do |artifact|
|
artifacts.select do |artifact|
|
||||||
odebug "Checking for artifact class #{artifact}"
|
odebug "Checking for artifact class #{artifact}"
|
||||||
artifact.me?(cask)
|
artifact.me?(cask)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,36 +1,40 @@
|
|||||||
require "hbc/artifact/base"
|
require "hbc/artifact/base"
|
||||||
|
|
||||||
class Hbc::Artifact::AbstractFlightBlock < Hbc::Artifact::Base
|
module Hbc
|
||||||
def self.artifact_dsl_key
|
module Artifact
|
||||||
super.to_s.sub(%r{_block$}, "").to_sym
|
class AbstractFlightBlock < Base
|
||||||
end
|
def self.artifact_dsl_key
|
||||||
|
super.to_s.sub(%r{_block$}, "").to_sym
|
||||||
|
end
|
||||||
|
|
||||||
def self.uninstall_artifact_dsl_key
|
def self.uninstall_artifact_dsl_key
|
||||||
artifact_dsl_key.to_s.prepend("uninstall_").to_sym
|
artifact_dsl_key.to_s.prepend("uninstall_").to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.class_for_dsl_key(dsl_key)
|
def self.class_for_dsl_key(dsl_key)
|
||||||
Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}")
|
Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.me?(cask)
|
def self.me?(cask)
|
||||||
cask.artifacts[artifact_dsl_key].any? ||
|
cask.artifacts[artifact_dsl_key].any? ||
|
||||||
cask.artifacts[uninstall_artifact_dsl_key].any?
|
cask.artifacts[uninstall_artifact_dsl_key].any?
|
||||||
end
|
end
|
||||||
|
|
||||||
def install_phase
|
def install_phase
|
||||||
abstract_phase(self.class.artifact_dsl_key)
|
abstract_phase(self.class.artifact_dsl_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_phase
|
def uninstall_phase
|
||||||
abstract_phase(self.class.uninstall_artifact_dsl_key)
|
abstract_phase(self.class.uninstall_artifact_dsl_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def abstract_phase(dsl_key)
|
def abstract_phase(dsl_key)
|
||||||
@cask.artifacts[dsl_key].each do |block|
|
@cask.artifacts[dsl_key].each do |block|
|
||||||
self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block)
|
self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::App < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class App < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::Artifact < Hbc::Artifact::Moved
|
module Hbc
|
||||||
def self.artifact_english_name
|
module Artifact
|
||||||
"Generic Artifact"
|
class Artifact < Moved
|
||||||
end
|
def self.artifact_english_name
|
||||||
|
"Generic Artifact"
|
||||||
|
end
|
||||||
|
|
||||||
def self.artifact_dirmethod
|
def self.artifact_dirmethod
|
||||||
:appdir
|
:appdir
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_specification(artifact_spec)
|
def load_specification(artifact_spec)
|
||||||
source_string, target_hash = artifact_spec
|
source_string, target_hash = artifact_spec
|
||||||
raise Hbc::CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil?
|
raise CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil?
|
||||||
@source = @cask.staged_path.join(source_string)
|
@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)
|
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_hash.assert_valid_keys(:target)
|
||||||
@target = Pathname.new(target_hash[:target])
|
@target = Pathname.new(target_hash[:target])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::AudioUnitPlugin < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class AudioUnitPlugin < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,79 +1,83 @@
|
|||||||
class Hbc::Artifact::Base
|
module Hbc
|
||||||
def self.artifact_name
|
module Artifact
|
||||||
@artifact_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase
|
class Base
|
||||||
end
|
def self.artifact_name
|
||||||
|
@artifact_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase
|
||||||
|
end
|
||||||
|
|
||||||
def self.artifact_english_name
|
def self.artifact_english_name
|
||||||
@artifact_english_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1 \2')
|
@artifact_english_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1 \2')
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.artifact_english_article
|
def self.artifact_english_article
|
||||||
@artifact_english_article ||= artifact_english_name =~ %r{^[aeiou]}i ? "an" : "a"
|
@artifact_english_article ||= artifact_english_name =~ %r{^[aeiou]}i ? "an" : "a"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.artifact_dsl_key
|
def self.artifact_dsl_key
|
||||||
@artifact_dsl_key ||= artifact_name.to_sym
|
@artifact_dsl_key ||= artifact_name.to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.artifact_dirmethod
|
def self.artifact_dirmethod
|
||||||
@artifact_dirmethod ||= "#{artifact_name}dir".to_sym
|
@artifact_dirmethod ||= "#{artifact_name}dir".to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.me?(cask)
|
def self.me?(cask)
|
||||||
cask.artifacts[artifact_dsl_key].any?
|
cask.artifacts[artifact_dsl_key].any?
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :force
|
attr_reader :force
|
||||||
|
|
||||||
def zap_phase
|
def zap_phase
|
||||||
odebug "Nothing to do. The #{self.class.artifact_name} artifact has no zap phase."
|
odebug "Nothing to do. The #{self.class.artifact_name} artifact has no zap phase."
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: this sort of logic would make more sense in dsl.rb, or a
|
# 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.
|
# 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)
|
def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil)
|
||||||
# TODO: when stanza names are harmonized with class names,
|
# TODO: when stanza names are harmonized with class names,
|
||||||
# stanza may not be needed as an explicit argument
|
# stanza may not be needed as an explicit argument
|
||||||
description = stanza.to_s
|
description = stanza.to_s
|
||||||
if key
|
if key
|
||||||
arguments = arguments[key]
|
arguments = arguments[key]
|
||||||
description.concat(" #{key.inspect}")
|
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
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
require "hbc/artifact/symlinked"
|
require "hbc/artifact/symlinked"
|
||||||
|
|
||||||
class Hbc::Artifact::Binary < Hbc::Artifact::Symlinked
|
module Hbc
|
||||||
def install_phase
|
module Artifact
|
||||||
super unless Hbc.no_binaries
|
class Binary < Symlinked
|
||||||
|
def install_phase
|
||||||
|
super unless Hbc.no_binaries
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::Colorpicker < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class Colorpicker < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::Font < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class Font < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::InputMethod < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class InputMethod < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,41 +1,45 @@
|
|||||||
require "hbc/artifact/base"
|
require "hbc/artifact/base"
|
||||||
|
|
||||||
class Hbc::Artifact::Installer < Hbc::Artifact::Base
|
module Hbc
|
||||||
# TODO: for backward compatibility, removeme
|
module Artifact
|
||||||
def install
|
class Installer < Base
|
||||||
install_phase
|
# TODO: for backward compatibility, removeme
|
||||||
end
|
def install
|
||||||
|
install_phase
|
||||||
|
end
|
||||||
|
|
||||||
# TODO: for backward compatibility, removeme
|
# TODO: for backward compatibility, removeme
|
||||||
def uninstall
|
def uninstall
|
||||||
uninstall_phase
|
uninstall_phase
|
||||||
end
|
end
|
||||||
|
|
||||||
def install_phase
|
def install_phase
|
||||||
@cask.artifacts[self.class.artifact_dsl_key].each do |artifact|
|
@cask.artifacts[self.class.artifact_dsl_key].each do |artifact|
|
||||||
if artifact.manual
|
if artifact.manual
|
||||||
puts <<-EOS.undent
|
puts <<-EOS.undent
|
||||||
To complete the installation of Cask #{@cask}, you must also
|
To complete the installation of Cask #{@cask}, you must also
|
||||||
run the installer at
|
run the installer at
|
||||||
|
|
||||||
'#{@cask.staged_path.join(artifact.manual)}'
|
'#{@cask.staged_path.join(artifact.manual)}'
|
||||||
|
|
||||||
EOS
|
EOS
|
||||||
else
|
else
|
||||||
executable, script_arguments = self.class.read_script_arguments(artifact.script,
|
executable, script_arguments = self.class.read_script_arguments(artifact.script,
|
||||||
self.class.artifact_dsl_key.to_s,
|
self.class.artifact_dsl_key.to_s,
|
||||||
{ must_succeed: true, sudo: true },
|
{ must_succeed: true, sudo: true },
|
||||||
print_stdout: true)
|
print_stdout: true)
|
||||||
ohai "Running #{self.class.artifact_dsl_key} script #{executable}"
|
ohai "Running #{self.class.artifact_dsl_key} script #{executable}"
|
||||||
raise Hbc::CaskInvalidError.new(@cask, "#{self.class.artifact_dsl_key} missing executable") if executable.nil?
|
raise CaskInvalidError.new(@cask, "#{self.class.artifact_dsl_key} missing executable") if executable.nil?
|
||||||
executable_path = @cask.staged_path.join(executable)
|
executable_path = @cask.staged_path.join(executable)
|
||||||
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path)
|
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path)
|
||||||
@command.run(executable_path, script_arguments)
|
@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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_phase
|
|
||||||
odebug "Nothing to do. The #{self.class.artifact_dsl_key} artifact has no uninstall phase."
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::InternetPlugin < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class InternetPlugin < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,88 +1,92 @@
|
|||||||
require "hbc/artifact/relocated"
|
require "hbc/artifact/relocated"
|
||||||
|
|
||||||
class Hbc::Artifact::Moved < Hbc::Artifact::Relocated
|
module Hbc
|
||||||
def self.english_description
|
module Artifact
|
||||||
"#{artifact_english_name}s"
|
class Moved < Relocated
|
||||||
end
|
def self.english_description
|
||||||
|
"#{artifact_english_name}s"
|
||||||
|
end
|
||||||
|
|
||||||
def install_phase
|
def install_phase
|
||||||
each_artifact do |artifact|
|
each_artifact do |artifact|
|
||||||
load_specification(artifact)
|
load_specification(artifact)
|
||||||
next unless preflight_checks
|
next unless preflight_checks
|
||||||
delete if Hbc::Utils.path_occupied?(target) && force
|
delete if Utils.path_occupied?(target) && force
|
||||||
move
|
move
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_phase
|
def uninstall_phase
|
||||||
each_artifact do |artifact|
|
each_artifact do |artifact|
|
||||||
load_specification(artifact)
|
load_specification(artifact)
|
||||||
next unless File.exist?(target)
|
next unless File.exist?(target)
|
||||||
delete
|
delete
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def each_artifact
|
def each_artifact
|
||||||
# the sort is for predictability between Ruby versions
|
# the sort is for predictability between Ruby versions
|
||||||
@cask.artifacts[self.class.artifact_dsl_key].sort.each do |artifact|
|
@cask.artifacts[self.class.artifact_dsl_key].sort.each do |artifact|
|
||||||
yield artifact
|
yield artifact
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def move
|
def move
|
||||||
ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'"
|
ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'"
|
||||||
target.dirname.mkpath
|
target.dirname.mkpath
|
||||||
FileUtils.move(source, target)
|
FileUtils.move(source, target)
|
||||||
add_altname_metadata target, source.basename.to_s
|
add_altname_metadata target, source.basename.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def preflight_checks
|
def preflight_checks
|
||||||
if Hbc::Utils.path_occupied?(target)
|
if Utils.path_occupied?(target)
|
||||||
if force
|
if force
|
||||||
ohai(warning_target_exists { |s| s << "overwriting." })
|
ohai(warning_target_exists { |s| s << "overwriting." })
|
||||||
else
|
else
|
||||||
ohai(warning_target_exists { |s| s << "not moving." })
|
ohai(warning_target_exists { |s| s << "not moving." })
|
||||||
return false
|
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
|
||||||
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
|
||||||
end
|
end
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
require "hbc/artifact/base"
|
require "hbc/artifact/base"
|
||||||
|
|
||||||
class Hbc::Artifact::NestedContainer < Hbc::Artifact::Base
|
module Hbc
|
||||||
def install_phase
|
module Artifact
|
||||||
@cask.artifacts[:nested_container].each { |container| extract(container) }
|
class NestedContainer < Base
|
||||||
end
|
def install_phase
|
||||||
|
@cask.artifacts[:nested_container].each { |container| extract(container) }
|
||||||
|
end
|
||||||
|
|
||||||
def uninstall_phase
|
def uninstall_phase
|
||||||
# no need to take action; is removed after extraction
|
# no need to take action; is removed after extraction
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract(container_relative_path)
|
def extract(container_relative_path)
|
||||||
source = @cask.staged_path.join(container_relative_path)
|
source = @cask.staged_path.join(container_relative_path)
|
||||||
container = Hbc::Container.for_path(source, @command)
|
container = Container.for_path(source, @command)
|
||||||
|
|
||||||
unless container
|
unless container
|
||||||
raise Hbc::CaskError, "Aw dang, could not identify nested container at '#{source}'"
|
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
|
end
|
||||||
|
|
||||||
ohai "Extracting nested container #{source.basename}"
|
|
||||||
container.new(@cask, source, @command).extract
|
|
||||||
FileUtils.remove_entry_secure(source)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,53 +1,57 @@
|
|||||||
require "hbc/artifact/base"
|
require "hbc/artifact/base"
|
||||||
|
|
||||||
class Hbc::Artifact::Pkg < Hbc::Artifact::Base
|
module Hbc
|
||||||
attr_reader :pkg_relative_path
|
module Artifact
|
||||||
|
class Pkg < Base
|
||||||
|
attr_reader :pkg_relative_path
|
||||||
|
|
||||||
def self.artifact_dsl_key
|
def self.artifact_dsl_key
|
||||||
:pkg
|
:pkg
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_pkg_description(pkg_description)
|
def load_pkg_description(pkg_description)
|
||||||
@pkg_relative_path = pkg_description.shift
|
@pkg_relative_path = pkg_description.shift
|
||||||
@pkg_install_opts = pkg_description.shift
|
@pkg_install_opts = pkg_description.shift
|
||||||
begin
|
begin
|
||||||
if @pkg_install_opts.respond_to?(:keys)
|
if @pkg_install_opts.respond_to?(:keys)
|
||||||
@pkg_install_opts.assert_valid_keys(:allow_untrusted)
|
@pkg_install_opts.assert_valid_keys(:allow_untrusted)
|
||||||
elsif @pkg_install_opts
|
elsif @pkg_install_opts
|
||||||
raise
|
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
|
end
|
||||||
raise if pkg_description.nil?
|
|
||||||
rescue StandardError
|
|
||||||
raise Hbc::CaskInvalidError.new(@cask, "Bad pkg stanza")
|
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/abstract_flight_block"
|
require "hbc/artifact/abstract_flight_block"
|
||||||
|
|
||||||
class Hbc::Artifact::PostflightBlock < Hbc::Artifact::AbstractFlightBlock
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class PostflightBlock < AbstractFlightBlock
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/abstract_flight_block"
|
require "hbc/artifact/abstract_flight_block"
|
||||||
|
|
||||||
class Hbc::Artifact::PreflightBlock < Hbc::Artifact::AbstractFlightBlock
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class PreflightBlock < AbstractFlightBlock
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::Prefpane < Hbc::Artifact::Moved
|
module Hbc
|
||||||
def self.artifact_english_name
|
module Artifact
|
||||||
"Preference Pane"
|
class Prefpane < Moved
|
||||||
|
def self.artifact_english_name
|
||||||
|
"Preference Pane"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::Qlplugin < Hbc::Artifact::Moved
|
module Hbc
|
||||||
def self.artifact_english_name
|
module Artifact
|
||||||
"QuickLook Plugin"
|
class Qlplugin < Moved
|
||||||
end
|
def self.artifact_english_name
|
||||||
|
"QuickLook Plugin"
|
||||||
|
end
|
||||||
|
|
||||||
def install_phase
|
def install_phase
|
||||||
super
|
super
|
||||||
reload_quicklook
|
reload_quicklook
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_phase
|
def uninstall_phase
|
||||||
super
|
super
|
||||||
reload_quicklook
|
reload_quicklook
|
||||||
end
|
end
|
||||||
|
|
||||||
def reload_quicklook
|
def reload_quicklook
|
||||||
@command.run!("/usr/bin/qlmanage", args: ["-r"])
|
@command.run!("/usr/bin/qlmanage", args: ["-r"])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,53 +1,57 @@
|
|||||||
require "hbc/artifact/base"
|
require "hbc/artifact/base"
|
||||||
|
|
||||||
class Hbc::Artifact::Relocated < Hbc::Artifact::Base
|
module Hbc
|
||||||
def summary
|
module Artifact
|
||||||
{
|
class Relocated < Base
|
||||||
english_description: self.class.english_description,
|
def summary
|
||||||
contents: @cask.artifacts[self.class.artifact_dsl_key].map(&method(:summarize_artifact)).compact,
|
{
|
||||||
}
|
english_description: self.class.english_description,
|
||||||
end
|
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
|
def printable_target
|
||||||
target.to_s.sub(%r{^#{ENV['HOME']}(#{File::SEPARATOR}|$)}, "~/")
|
target.to_s.sub(%r{^#{ENV['HOME']}(#{File::SEPARATOR}|$)}, "~/")
|
||||||
end
|
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
|
# Try to make the asset searchable under the target name. Spotlight
|
||||||
# respects this attribute for many filetypes, but ignores it for App
|
# respects this attribute for many filetypes, but ignores it for App
|
||||||
# bundles. Alfred 2.2 respects it even for App bundles.
|
# bundles. Alfred 2.2 respects it even for App bundles.
|
||||||
def add_altname_metadata(file, altname)
|
def add_altname_metadata(file, altname)
|
||||||
return if altname.casecmp(file.basename).zero?
|
return if altname.casecmp(file.basename).zero?
|
||||||
odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata"
|
odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata"
|
||||||
altnames = @command.run("/usr/bin/xattr",
|
altnames = @command.run("/usr/bin/xattr",
|
||||||
args: ["-p", ALT_NAME_ATTRIBUTE, file.to_s],
|
args: ["-p", ALT_NAME_ATTRIBUTE, file.to_s],
|
||||||
print_stderr: false).stdout.sub(%r{\A\((.*)\)\Z}, '\1')
|
print_stderr: false).stdout.sub(%r{\A\((.*)\)\Z}, '\1')
|
||||||
odebug "Existing metadata is: '#{altnames}'"
|
odebug "Existing metadata is: '#{altnames}'"
|
||||||
altnames.concat(", ") unless altnames.empty?
|
altnames.concat(", ") unless altnames.empty?
|
||||||
altnames.concat(%Q{"#{altname}"})
|
altnames.concat(%Q{"#{altname}"})
|
||||||
altnames = "(#{altnames})"
|
altnames = "(#{altnames})"
|
||||||
|
|
||||||
# Some packges are shipped as u=rx (e.g. Bitcoin Core)
|
# 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!("/bin/chmod", args: ["--", "u=rwx", file.to_s, file.realpath.to_s])
|
||||||
|
|
||||||
@command.run!("/usr/bin/xattr",
|
@command.run!("/usr/bin/xattr",
|
||||||
args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file.to_s],
|
args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file.to_s],
|
||||||
print_stderr: false)
|
print_stderr: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_specification(artifact_spec)
|
def load_specification(artifact_spec)
|
||||||
source_string, target_hash = artifact_spec
|
source_string, target_hash = artifact_spec
|
||||||
raise Hbc::CaskInvalidError if source_string.nil?
|
raise CaskInvalidError if source_string.nil?
|
||||||
@source = @cask.staged_path.join(source_string)
|
@source = @cask.staged_path.join(source_string)
|
||||||
if target_hash
|
if target_hash
|
||||||
raise Hbc::CaskInvalidError unless target_hash.respond_to?(:keys)
|
raise CaskInvalidError unless target_hash.respond_to?(:keys)
|
||||||
target_hash.assert_valid_keys(:target)
|
target_hash.assert_valid_keys(:target)
|
||||||
@target = Hbc.send(self.class.artifact_dirmethod).join(target_hash[:target])
|
@target = Hbc.send(self.class.artifact_dirmethod).join(target_hash[:target])
|
||||||
else
|
else
|
||||||
@target = Hbc.send(self.class.artifact_dirmethod).join(source.basename)
|
@target = Hbc.send(self.class.artifact_dirmethod).join(source.basename)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::ScreenSaver < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class ScreenSaver < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::Service < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class Service < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
require "hbc/artifact/base"
|
require "hbc/artifact/base"
|
||||||
|
|
||||||
class Hbc::Artifact::StageOnly < Hbc::Artifact::Base
|
module Hbc
|
||||||
def self.artifact_dsl_key
|
module Artifact
|
||||||
:stage_only
|
class StageOnly < Base
|
||||||
end
|
def self.artifact_dsl_key
|
||||||
|
:stage_only
|
||||||
|
end
|
||||||
|
|
||||||
def install_phase
|
def install_phase
|
||||||
# do nothing
|
# do nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_phase
|
def uninstall_phase
|
||||||
# do nothing
|
# do nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::Suite < Hbc::Artifact::Moved
|
module Hbc
|
||||||
def self.artifact_english_name
|
module Artifact
|
||||||
"App Suite"
|
class Suite < Moved
|
||||||
end
|
def self.artifact_english_name
|
||||||
|
"App Suite"
|
||||||
|
end
|
||||||
|
|
||||||
def self.artifact_dirmethod
|
def self.artifact_dirmethod
|
||||||
:appdir
|
:appdir
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,65 +1,69 @@
|
|||||||
require "hbc/artifact/relocated"
|
require "hbc/artifact/relocated"
|
||||||
|
|
||||||
class Hbc::Artifact::Symlinked < Hbc::Artifact::Relocated
|
module Hbc
|
||||||
def self.link_type_english_name
|
module Artifact
|
||||||
"Symlink"
|
class Symlinked < Relocated
|
||||||
end
|
def self.link_type_english_name
|
||||||
|
"Symlink"
|
||||||
|
end
|
||||||
|
|
||||||
def self.english_description
|
def self.english_description
|
||||||
"#{artifact_english_name} #{link_type_english_name}s"
|
"#{artifact_english_name} #{link_type_english_name}s"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.islink?(path)
|
def self.islink?(path)
|
||||||
path.symlink?
|
path.symlink?
|
||||||
end
|
end
|
||||||
|
|
||||||
def link(artifact_spec)
|
def link(artifact_spec)
|
||||||
load_specification artifact_spec
|
load_specification artifact_spec
|
||||||
return unless preflight_checks(source, target)
|
return unless preflight_checks(source, target)
|
||||||
ohai "#{self.class.link_type_english_name}ing #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'"
|
ohai "#{self.class.link_type_english_name}ing #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'"
|
||||||
create_filesystem_link(source, target)
|
create_filesystem_link(source, target)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unlink(artifact_spec)
|
def unlink(artifact_spec)
|
||||||
load_specification artifact_spec
|
load_specification artifact_spec
|
||||||
return unless self.class.islink?(target)
|
return unless self.class.islink?(target)
|
||||||
ohai "Removing #{self.class.artifact_english_name} #{self.class.link_type_english_name.downcase}: '#{target}'"
|
ohai "Removing #{self.class.artifact_english_name} #{self.class.link_type_english_name.downcase}: '#{target}'"
|
||||||
target.delete
|
target.delete
|
||||||
end
|
end
|
||||||
|
|
||||||
def install_phase
|
def install_phase
|
||||||
@cask.artifacts[self.class.artifact_dsl_key].each(&method(:link))
|
@cask.artifacts[self.class.artifact_dsl_key].each(&method(:link))
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_phase
|
def uninstall_phase
|
||||||
@cask.artifacts[self.class.artifact_dsl_key].each(&method(:unlink))
|
@cask.artifacts[self.class.artifact_dsl_key].each(&method(:unlink))
|
||||||
end
|
end
|
||||||
|
|
||||||
def preflight_checks(source, target)
|
def preflight_checks(source, target)
|
||||||
if target.exist? && !self.class.islink?(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."
|
ohai "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'; not linking."
|
||||||
return false
|
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
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/uninstall_base"
|
require "hbc/artifact/uninstall_base"
|
||||||
|
|
||||||
class Hbc::Artifact::Uninstall < Hbc::Artifact::UninstallBase
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class Uninstall < UninstallBase
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,248 +2,252 @@ require "pathname"
|
|||||||
|
|
||||||
require "hbc/artifact/base"
|
require "hbc/artifact/base"
|
||||||
|
|
||||||
class Hbc::Artifact::UninstallBase < Hbc::Artifact::Base
|
module Hbc
|
||||||
# TODO: 500 is also hardcoded in cask/pkg.rb, but much of
|
module Artifact
|
||||||
# that logic is probably in the wrong location
|
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 = [
|
ORDERED_DIRECTIVES = [
|
||||||
:early_script,
|
:early_script,
|
||||||
:launchctl,
|
:launchctl,
|
||||||
:quit,
|
:quit,
|
||||||
:signal,
|
:signal,
|
||||||
:login_item,
|
:login_item,
|
||||||
:kext,
|
:kext,
|
||||||
:script,
|
:script,
|
||||||
:pkgutil,
|
:pkgutil,
|
||||||
:delete,
|
:delete,
|
||||||
:trash,
|
:trash,
|
||||||
:rmdir,
|
:rmdir,
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
# TODO: these methods were consolidated here from separate
|
# TODO: these methods were consolidated here from separate
|
||||||
# sources and should be refactored for consistency
|
# sources and should be refactored for consistency
|
||||||
|
|
||||||
def self.expand_path_strings(path_strings)
|
def self.expand_path_strings(path_strings)
|
||||||
path_strings.map { |path_string|
|
path_strings.map { |path_string|
|
||||||
path_string.start_with?("~") ? Pathname.new(path_string).expand_path : Pathname.new(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)
|
|
||||||
end
|
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
|
def self.remove_undeletable_path_strings(action, path_strings)
|
||||||
self.class.artifact_dsl_key
|
undeletable = path_strings.map { |path_string|
|
||||||
end
|
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)
|
def install_phase
|
||||||
unknown_keys = directives.keys - ORDERED_DIRECTIVES
|
odebug "Nothing to do. The uninstall artifact has no install phase."
|
||||||
return if unknown_keys.empty?
|
end
|
||||||
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.
|
def uninstall_phase
|
||||||
# :early_script should not delete files, better defer that to :script.
|
dispatch_uninstall_directives
|
||||||
# If Cask writers never need :early_script it may be removed in the future.
|
end
|
||||||
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 dispatch_uninstall_directives(expand_tilde = true)
|
||||||
def uninstall_launchctl(directives)
|
directives_set = @cask.artifacts[stanza]
|
||||||
Array(directives[:launchctl]).each do |service|
|
ohai "Running #{stanza} process for #{@cask}; your password may be necessary"
|
||||||
ohai "Removing launchctl service #{service}"
|
|
||||||
[false, true].each do |with_sudo|
|
directives_set.each do |directives|
|
||||||
plist_status = @command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout
|
warn_for_unknown_directives(directives)
|
||||||
if plist_status =~ %r{^\{}
|
end
|
||||||
@command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo)
|
|
||||||
|
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
|
sleep 1
|
||||||
end
|
end
|
||||||
paths = ["/Library/LaunchAgents/#{service}.plist",
|
end
|
||||||
"/Library/LaunchDaemons/#{service}.plist"]
|
|
||||||
paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo
|
# :kext should be unloaded before attempting to delete the relevant file
|
||||||
paths = paths.map { |elt| Pathname(elt) }.select(&:exist?)
|
def uninstall_kext(directives)
|
||||||
paths.each do |path|
|
Array(directives[:kext]).each do |kext|
|
||||||
@command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo)
|
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
|
end
|
||||||
# undocumented and untested: pass a path to uninstall :launchctl
|
end
|
||||||
next unless Pathname(service).exist?
|
|
||||||
@command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo)
|
# :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted
|
||||||
@command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo)
|
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
|
sleep 1
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# :quit/:signal must come before :kext so the kext will not be in use by a running process
|
def uninstall_pkgutil(directives)
|
||||||
def uninstall_quit(directives)
|
ohai "Removing files from pkgutil Bill-of-Materials"
|
||||||
Array(directives[:quit]).each do |id|
|
Array(directives[:pkgutil]).each do |regexp|
|
||||||
ohai "Quitting application ID #{id}"
|
pkgs = Hbc::Pkg.all_matching(regexp, @command)
|
||||||
num_running = count_running_processes(id)
|
pkgs.each(&:uninstall)
|
||||||
next unless num_running > 0
|
end
|
||||||
@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
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted
|
def uninstall_delete(directives, expand_tilde = true)
|
||||||
def uninstall_script(directives, directive_name: :script)
|
Array(directives[:delete]).concat(Array(directives[:trash])).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice|
|
||||||
executable, script_arguments = self.class.read_script_arguments(directives,
|
ohai "Removing files: #{path_slice.utf8_inspect}"
|
||||||
"uninstall",
|
path_slice = self.class.expand_path_strings(path_slice) if expand_tilde
|
||||||
{ must_succeed: true, sudo: true },
|
path_slice = self.class.remove_relative_path_strings(:delete, path_slice)
|
||||||
{ print_stdout: true },
|
path_slice = self.class.remove_undeletable_path_strings(:delete, path_slice)
|
||||||
directive_name)
|
@command.run!("/bin/rm", args: path_slice.unshift("-rf", "--"), sudo: true)
|
||||||
ohai "Running uninstall script #{executable}"
|
end
|
||||||
raise Hbc::CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil?
|
end
|
||||||
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_pkgutil(directives)
|
# :trash functionality is stubbed as a synonym for :delete
|
||||||
ohai "Removing files from pkgutil Bill-of-Materials"
|
# TODO: make :trash work differently, moving files to the Trash
|
||||||
Array(directives[:pkgutil]).each do |regexp|
|
def uninstall_trash(directives, expand_tilde = true)
|
||||||
pkgs = Hbc::Pkg.all_matching(regexp, @command)
|
uninstall_delete(directives, expand_tilde)
|
||||||
pkgs.each(&:uninstall)
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def uninstall_delete(directives, expand_tilde = true)
|
def uninstall_rmdir(directives, expand_tilde = true)
|
||||||
Array(directives[:delete]).concat(Array(directives[:trash])).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice|
|
Array(directives[:rmdir]).flatten.each do |directory|
|
||||||
ohai "Removing files: #{path_slice.utf8_inspect}"
|
directory = self.class.expand_path_strings([directory]).first if expand_tilde
|
||||||
path_slice = self.class.expand_path_strings(path_slice) if expand_tilde
|
directory = self.class.remove_relative_path_strings(:rmdir, [directory]).first
|
||||||
path_slice = self.class.remove_relative_path_strings(:delete, path_slice)
|
directory = self.class.remove_undeletable_path_strings(:rmdir, [directory]).first
|
||||||
path_slice = self.class.remove_undeletable_path_strings(:delete, path_slice)
|
next if directory.to_s.empty?
|
||||||
@command.run!("/bin/rm", args: path_slice.unshift("-rf", "--"), sudo: true)
|
ohai "Removing directory if empty: #{directory.to_s.utf8_inspect}"
|
||||||
end
|
directory = Pathname.new(directory)
|
||||||
end
|
next unless directory.exist?
|
||||||
|
@command.run!("/bin/rm",
|
||||||
# :trash functionality is stubbed as a synonym for :delete
|
args: ["-f", "--", directory.join(".DS_Store")],
|
||||||
# TODO: make :trash work differently, moving files to the Trash
|
sudo: true,
|
||||||
def uninstall_trash(directives, expand_tilde = true)
|
print_stderr: false)
|
||||||
uninstall_delete(directives, expand_tilde)
|
@command.run("/bin/rmdir",
|
||||||
end
|
args: ["--", directory],
|
||||||
|
sudo: true,
|
||||||
def uninstall_rmdir(directives, expand_tilde = true)
|
print_stderr: false)
|
||||||
Array(directives[:rmdir]).flatten.each do |directory|
|
end
|
||||||
directory = self.class.expand_path_strings([directory]).first if expand_tilde
|
end
|
||||||
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
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::Vst3Plugin < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class Vst3Plugin < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
require "hbc/artifact/moved"
|
require "hbc/artifact/moved"
|
||||||
|
|
||||||
class Hbc::Artifact::VstPlugin < Hbc::Artifact::Moved
|
module Hbc
|
||||||
|
module Artifact
|
||||||
|
class VstPlugin < Moved
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
require "hbc/artifact/uninstall_base"
|
require "hbc/artifact/uninstall_base"
|
||||||
|
|
||||||
class Hbc::Artifact::Zap < Hbc::Artifact::UninstallBase
|
module Hbc
|
||||||
def install_phase
|
module Artifact
|
||||||
odebug "Nothing to do. The zap artifact has no install phase."
|
class Zap < UninstallBase
|
||||||
end
|
def install_phase
|
||||||
|
odebug "Nothing to do. The zap artifact has no install phase."
|
||||||
|
end
|
||||||
|
|
||||||
def uninstall_phase
|
def uninstall_phase
|
||||||
odebug "Nothing to do. The zap artifact has no uninstall phase."
|
odebug "Nothing to do. The zap artifact has no uninstall phase."
|
||||||
end
|
end
|
||||||
|
|
||||||
def zap_phase
|
def zap_phase
|
||||||
expand_tilde = true
|
expand_tilde = true
|
||||||
dispatch_uninstall_directives(expand_tilde)
|
dispatch_uninstall_directives(expand_tilde)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,215 +2,217 @@ require "hbc/checkable"
|
|||||||
require "hbc/download"
|
require "hbc/download"
|
||||||
require "digest"
|
require "digest"
|
||||||
|
|
||||||
class Hbc::Audit
|
module Hbc
|
||||||
include Hbc::Checkable
|
class Audit
|
||||||
|
include Checkable
|
||||||
|
|
||||||
attr_reader :cask, :download
|
attr_reader :cask, :download
|
||||||
|
|
||||||
def initialize(cask, download: false, check_token_conflicts: false, command: Hbc::SystemCommand)
|
def initialize(cask, download: false, check_token_conflicts: false, command: SystemCommand)
|
||||||
@cask = cask
|
@cask = cask
|
||||||
@download = download
|
@download = download
|
||||||
@check_token_conflicts = check_token_conflicts
|
@check_token_conflicts = check_token_conflicts
|
||||||
@command = command
|
@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)
|
|
||||||
end
|
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
|
def check_token_conflicts?
|
||||||
return unless cask.version
|
@check_token_conflicts
|
||||||
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}"
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def check_appcast_checkpoint_accuracy
|
def run!
|
||||||
odebug "Verifying appcast checkpoint is accurate"
|
check_required_stanzas
|
||||||
result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", Hbc::URL::FAKE_USER_AGENT, cask.appcast], print_stderr: false)
|
check_version
|
||||||
if result.success?
|
check_sha256
|
||||||
processed_appcast_text = result.stdout.gsub(%r{<pubDate>[^<]*</pubDate>}, "")
|
check_appcast
|
||||||
# This step is necessary to replicate running `sed` from the command line
|
check_url
|
||||||
processed_appcast_text << "\n" unless processed_appcast_text.end_with?("\n")
|
check_generic_artifacts
|
||||||
expected = cask.appcast.checkpoint
|
check_token_conflicts
|
||||||
actual = Digest::SHA2.hexdigest(processed_appcast_text)
|
check_download
|
||||||
add_warning <<-EOS.undent unless expected == actual
|
self
|
||||||
appcast checkpoint mismatch
|
rescue StandardError => e
|
||||||
Expected: #{expected}
|
odebug "#{e.message}\n#{e.backtrace.join("\n")}"
|
||||||
Actual: #{actual}
|
add_error "exception while auditing #{cask}: #{e.message}"
|
||||||
EOS
|
self
|
||||||
else
|
|
||||||
add_warning "error retrieving appcast: #{result.stderr}"
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def check_url
|
def success?
|
||||||
return unless cask.url
|
!(errors? || warnings?)
|
||||||
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
|
||||||
end
|
|
||||||
|
|
||||||
def bad_url_format?(regex, valid_formats_array)
|
def summary_header
|
||||||
return false unless cask.url.to_s =~ regex
|
"audit for #{cask}"
|
||||||
valid_formats_array.none? { |format| cask.url.to_s =~ format }
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def bad_sourceforge_url?
|
private
|
||||||
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?
|
def check_required_stanzas
|
||||||
bad_url_format?(%r{osd}, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
|
odebug "Auditing required stanzas"
|
||||||
end
|
%i{version sha256 url homepage}.each do |sym|
|
||||||
|
add_error "a #{sym} stanza is required" unless cask.send(sym)
|
||||||
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
|
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
|
||||||
end
|
|
||||||
|
|
||||||
def check_token_conflicts
|
def check_version
|
||||||
return unless check_token_conflicts?
|
return unless cask.version
|
||||||
return unless core_formula_names.include?(cask.token)
|
check_no_string_version_latest
|
||||||
add_warning "possible duplicate, cask token conflicts with Homebrew core formula: #{core_formula_url}"
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def core_tap
|
def check_no_string_version_latest
|
||||||
@core_tap ||= CoreTap.instance
|
odebug "Verifying version :latest does not appear as a string ('latest')"
|
||||||
end
|
return unless cask.version.raw_version == "latest"
|
||||||
|
add_error "you should use version :latest instead of version 'latest'"
|
||||||
|
end
|
||||||
|
|
||||||
def core_formula_names
|
def check_sha256
|
||||||
core_tap.formula_names
|
return unless cask.sha256
|
||||||
end
|
check_sha256_no_check_if_latest
|
||||||
|
check_sha256_actually_256
|
||||||
|
check_sha256_invalid
|
||||||
|
end
|
||||||
|
|
||||||
def core_formula_url
|
def check_sha256_no_check_if_latest
|
||||||
"#{core_tap.default_remote}/blob/master/Formula/#{cask.token}.rb"
|
odebug "Verifying sha256 :no_check with version :latest"
|
||||||
end
|
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
|
def check_sha256_actually_256(sha256: cask.sha256, stanza: "sha256")
|
||||||
return unless download && cask.url
|
odebug "Verifying #{stanza} string is a legal SHA-256 digest"
|
||||||
odebug "Auditing download"
|
return unless sha256.is_a?(String)
|
||||||
downloaded_path = download.perform
|
return if sha256.length == 64 && sha256[%r{^[0-9a-f]+$}i]
|
||||||
Hbc::Verify.all(cask, downloaded_path)
|
add_error "#{stanza} string must be of 64 hexadecimal characters"
|
||||||
rescue => e
|
end
|
||||||
add_error "download not possible: #{e.message}"
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
class Hbc::Auditor
|
module Hbc
|
||||||
def self.audit(cask, audit_download: false, check_token_conflicts: false)
|
class Auditor
|
||||||
download = audit_download && Hbc::Download.new(cask)
|
def self.audit(cask, audit_download: false, check_token_conflicts: false)
|
||||||
audit = Hbc::Audit.new(cask, download: download,
|
download = audit_download && Download.new(cask)
|
||||||
check_token_conflicts: check_token_conflicts)
|
audit = Audit.new(cask, download: download,
|
||||||
audit.run!
|
check_token_conflicts: check_token_conflicts)
|
||||||
puts audit.summary
|
audit.run!
|
||||||
audit.success?
|
puts audit.summary
|
||||||
|
audit.success?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,34 +1,36 @@
|
|||||||
module Hbc::Cache
|
module Hbc
|
||||||
module_function
|
module Cache
|
||||||
|
module_function
|
||||||
|
|
||||||
def ensure_cache_exists
|
def ensure_cache_exists
|
||||||
return if Hbc.cache.exist?
|
return if Hbc.cache.exist?
|
||||||
|
|
||||||
odebug "Creating Cache at #{Hbc.cache}"
|
odebug "Creating Cache at #{Hbc.cache}"
|
||||||
Hbc.cache.mkpath
|
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)
|
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
@ -2,114 +2,116 @@ require "forwardable"
|
|||||||
|
|
||||||
require "hbc/dsl"
|
require "hbc/dsl"
|
||||||
|
|
||||||
class Hbc::Cask
|
module Hbc
|
||||||
extend Forwardable
|
class Cask
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
attr_reader :token, :sourcefile_path
|
attr_reader :token, :sourcefile_path
|
||||||
def initialize(token, sourcefile_path: nil, dsl: nil, &block)
|
def initialize(token, sourcefile_path: nil, dsl: nil, &block)
|
||||||
@token = token
|
@token = token
|
||||||
@sourcefile_path = sourcefile_path
|
@sourcefile_path = sourcefile_path
|
||||||
@dsl = dsl || Hbc::DSL.new(@token)
|
@dsl = dsl || DSL.new(@token)
|
||||||
@dsl.instance_eval(&block) if block_given?
|
@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"
|
|
||||||
end
|
end
|
||||||
path = if timestamp == :latest
|
|
||||||
Pathname.glob(metadata_versioned_container_path.join("*")).sort.last
|
DSL::DSL_METHODS.each do |method_name|
|
||||||
elsif timestamp == :now
|
define_method(method_name) { @dsl.send(method_name) }
|
||||||
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
|
|
||||||
end
|
end
|
||||||
path
|
|
||||||
end
|
|
||||||
|
|
||||||
def metadata_subdir(leaf, timestamp = :latest, create = false)
|
METADATA_SUBDIR = ".metadata".freeze
|
||||||
if create && timestamp == :latest
|
|
||||||
raise Hbc::CaskError, "Cannot create metadata subdir when timestamp is :latest"
|
def metadata_master_container_path
|
||||||
|
@metadata_master_container_path ||= caskroom_path.join(METADATA_SUBDIR)
|
||||||
end
|
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
|
end
|
||||||
parent = metadata_path(timestamp, create)
|
|
||||||
return nil unless parent.respond_to?(:join)
|
def metadata_path(timestamp = :latest, create = false)
|
||||||
subdir = parent.join(leaf)
|
return nil unless metadata_versioned_container_path.respond_to?(:join)
|
||||||
if create
|
if create && timestamp == :latest
|
||||||
odebug "Creating metadata subdirectory #{subdir}"
|
raise CaskError, "Cannot create metadata path when timestamp is :latest"
|
||||||
FileUtils.mkdir_p subdir
|
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
|
end
|
||||||
subdir
|
|
||||||
end
|
|
||||||
|
|
||||||
def timestamped_versions
|
def metadata_subdir(leaf, timestamp = :latest, create = false)
|
||||||
Pathname.glob(metadata_master_container_path.join("*", "*"))
|
if create && timestamp == :latest
|
||||||
.map { |p| p.relative_path_from(metadata_master_container_path) }
|
raise CaskError, "Cannot create metadata subdir when timestamp is :latest"
|
||||||
.sort_by(&:basename) # sort by timestamp
|
end
|
||||||
.map(&:split)
|
unless leaf.respond_to?(:length) && !leaf.empty?
|
||||||
end
|
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
|
def timestamped_versions
|
||||||
timestamped_versions.map(&:first)
|
Pathname.glob(metadata_master_container_path.join("*", "*"))
|
||||||
.reverse
|
.map { |p| p.relative_path_from(metadata_master_container_path) }
|
||||||
.uniq
|
.sort_by(&:basename) # sort by timestamp
|
||||||
.reverse
|
.map(&:split)
|
||||||
end
|
end
|
||||||
|
|
||||||
def installed?
|
def versions
|
||||||
!versions.empty?
|
timestamped_versions.map(&:first)
|
||||||
end
|
.reverse
|
||||||
|
.uniq
|
||||||
|
.reverse
|
||||||
|
end
|
||||||
|
|
||||||
def to_s
|
def installed?
|
||||||
@token
|
!versions.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def dumpcask
|
def to_s
|
||||||
return unless Hbc.respond_to?(:debug)
|
@token
|
||||||
return unless Hbc.debug
|
end
|
||||||
|
|
||||||
odebug "Cask instance dumps in YAML:"
|
def dumpcask
|
||||||
odebug "Cask instance toplevel:", to_yaml
|
return unless Hbc.respond_to?(:debug)
|
||||||
[
|
return unless Hbc.debug
|
||||||
:name,
|
|
||||||
:homepage,
|
odebug "Cask instance dumps in YAML:"
|
||||||
:url,
|
odebug "Cask instance toplevel:", to_yaml
|
||||||
:appcast,
|
[
|
||||||
:version,
|
:name,
|
||||||
:license,
|
:homepage,
|
||||||
:sha256,
|
:url,
|
||||||
:artifacts,
|
:appcast,
|
||||||
:caveats,
|
:version,
|
||||||
:depends_on,
|
:license,
|
||||||
:conflicts_with,
|
:sha256,
|
||||||
:container,
|
:artifacts,
|
||||||
:gpg,
|
:caveats,
|
||||||
:accessibility_access,
|
:depends_on,
|
||||||
:auto_updates,
|
:conflicts_with,
|
||||||
].each do |method|
|
:container,
|
||||||
odebug "Cask instance method '#{method}':", send(method).to_yaml
|
:gpg,
|
||||||
|
:accessibility_access,
|
||||||
|
:auto_updates,
|
||||||
|
].each do |method|
|
||||||
|
odebug "Cask instance method '#{method}':", send(method).to_yaml
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,33 +1,35 @@
|
|||||||
require "hbc/topological_hash"
|
require "hbc/topological_hash"
|
||||||
|
|
||||||
class Hbc::CaskDependencies
|
module Hbc
|
||||||
attr_reader :cask, :graph, :sorted
|
class CaskDependencies
|
||||||
|
attr_reader :cask, :graph, :sorted
|
||||||
|
|
||||||
def initialize(cask)
|
def initialize(cask)
|
||||||
@cask = cask
|
@cask = cask
|
||||||
@graph = graph_dependencies
|
@graph = graph_dependencies
|
||||||
@sorted = sort
|
@sorted = sort
|
||||||
end
|
end
|
||||||
|
|
||||||
def graph_dependencies
|
def graph_dependencies
|
||||||
deps_in = ->(csk) { csk.depends_on ? csk.depends_on.cask || [] : [] }
|
deps_in = ->(csk) { csk.depends_on ? csk.depends_on.cask || [] : [] }
|
||||||
walk = lambda { |acc, deps|
|
walk = lambda { |acc, deps|
|
||||||
deps.each do |dep|
|
deps.each do |dep|
|
||||||
next if acc.key?(dep)
|
next if acc.key?(dep)
|
||||||
succs = deps_in.call Hbc.load(dep)
|
succs = deps_in.call Hbc.load(dep)
|
||||||
acc[dep] = succs
|
acc[dep] = succs
|
||||||
walk.call(acc, succs)
|
walk.call(acc, succs)
|
||||||
end
|
end
|
||||||
acc
|
acc
|
||||||
}
|
}
|
||||||
|
|
||||||
graphed = walk.call({}, @cask.depends_on.cask)
|
graphed = walk.call({}, @cask.depends_on.cask)
|
||||||
Hbc::TopologicalHash[graphed]
|
TopologicalHash[graphed]
|
||||||
end
|
end
|
||||||
|
|
||||||
def sort
|
def sort
|
||||||
@graph.tsort
|
@graph.tsort
|
||||||
rescue TSort::Cyclic
|
rescue TSort::Cyclic
|
||||||
raise Hbc::CaskCyclicCaskDependencyError, @cask.token
|
raise CaskCyclicCaskDependencyError, @cask.token
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,41 +1,43 @@
|
|||||||
module Hbc::Caskroom
|
module Hbc
|
||||||
module_function
|
module Caskroom
|
||||||
|
module_function
|
||||||
|
|
||||||
def migrate_caskroom_from_repo_to_prefix
|
def migrate_caskroom_from_repo_to_prefix
|
||||||
repo_caskroom = Hbc.homebrew_repository.join("Caskroom")
|
repo_caskroom = Hbc.homebrew_repository.join("Caskroom")
|
||||||
return if Hbc.caskroom.exist?
|
return if Hbc.caskroom.exist?
|
||||||
return unless repo_caskroom.directory?
|
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?
|
if Hbc.caskroom.parent.writable?
|
||||||
FileUtils.mv repo_caskroom, Hbc.caskroom
|
FileUtils.mv repo_caskroom, Hbc.caskroom
|
||||||
else
|
else
|
||||||
opoo "#{Hbc.caskroom.parent} is not writable, sudo is needed to move the Caskroom."
|
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
|
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
|
|
||||||
end
|
end
|
||||||
# sudo in system is rude.
|
end
|
||||||
system "/usr/bin/sudo", "--", "/bin/mkdir", "-p", "--", Hbc.caskroom
|
|
||||||
unless Hbc.caskroom.parent == toplevel_dir
|
def ensure_caskroom_exists
|
||||||
system "/usr/bin/sudo", "--", "/usr/sbin/chown", "-R", "--", "#{Hbc::Utils.current_user}:staff", Hbc.caskroom.parent.to_s
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
class Hbc::Caveats
|
module Hbc
|
||||||
def initialize(block)
|
class Caveats
|
||||||
@block = block
|
def initialize(block)
|
||||||
end
|
@block = block
|
||||||
|
end
|
||||||
|
|
||||||
def eval_and_print(cask)
|
def eval_and_print(cask)
|
||||||
dsl = Hbc::DSL::Caveats.new(cask)
|
dsl = DSL::Caveats.new(cask)
|
||||||
retval = dsl.instance_eval(&@block)
|
retval = dsl.instance_eval(&@block)
|
||||||
return if retval.nil?
|
return if retval.nil?
|
||||||
puts retval.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n")
|
puts retval.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,51 +1,53 @@
|
|||||||
module Hbc::Checkable
|
module Hbc
|
||||||
def errors
|
module Checkable
|
||||||
Array(@errors)
|
def errors
|
||||||
end
|
Array(@errors)
|
||||||
|
|
||||||
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}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
warnings.each do |warning|
|
def warnings
|
||||||
summary << " #{Tty.yellow}-#{Tty.reset} #{warning}"
|
Array(@warnings)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
class Hbc::CLI; end
|
|
||||||
|
|
||||||
require "optparse"
|
require "optparse"
|
||||||
require "shellwords"
|
require "shellwords"
|
||||||
|
|
||||||
@ -28,248 +26,250 @@ require "hbc/cli/internal_dump"
|
|||||||
require "hbc/cli/internal_help"
|
require "hbc/cli/internal_help"
|
||||||
require "hbc/cli/internal_stanza"
|
require "hbc/cli/internal_stanza"
|
||||||
|
|
||||||
class Hbc::CLI
|
module Hbc
|
||||||
ALIASES = {
|
class CLI
|
||||||
"ls" => "list",
|
ALIASES = {
|
||||||
"homepage" => "home",
|
"ls" => "list",
|
||||||
"-S" => "search", # verb starting with "-" is questionable
|
"homepage" => "home",
|
||||||
"up" => "update",
|
"-S" => "search", # verb starting with "-" is questionable
|
||||||
"instal" => "install", # gem does the same
|
"up" => "update",
|
||||||
"rm" => "uninstall",
|
"instal" => "install", # gem does the same
|
||||||
"remove" => "uninstall",
|
"rm" => "uninstall",
|
||||||
"abv" => "info",
|
"remove" => "uninstall",
|
||||||
"dr" => "doctor",
|
"abv" => "info",
|
||||||
# aliases from Homebrew that we don't (yet) support
|
"dr" => "doctor",
|
||||||
# 'ln' => 'link',
|
# aliases from Homebrew that we don't (yet) support
|
||||||
# 'configure' => 'diy',
|
# 'ln' => 'link',
|
||||||
# '--repo' => '--repository',
|
# 'configure' => 'diy',
|
||||||
# 'environment' => '--env',
|
# '--repo' => '--repository',
|
||||||
# '-c1' => '--config',
|
# '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
|
}.freeze
|
||||||
|
|
||||||
OPTIONS = {
|
def self.command_classes
|
||||||
"--caskroom=" => :caskroom=,
|
@command_classes ||= self.constants
|
||||||
"--appdir=" => :appdir=,
|
.map(&method(:const_get))
|
||||||
"--colorpickerdir=" => :colorpickerdir=,
|
.select { |sym| sym.respond_to?(:run) }
|
||||||
"--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
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.process(arguments)
|
def self.commands
|
||||||
command_string, *rest = *arguments
|
@commands ||= command_classes.map(&:command_name)
|
||||||
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}"
|
|
||||||
end
|
end
|
||||||
list = []
|
|
||||||
cask_taps.each do |token, taps|
|
def self.lookup_command(command_string)
|
||||||
if taps.length == 1
|
@lookup ||= Hash[commands.zip(command_classes)]
|
||||||
list.push token
|
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
|
else
|
||||||
taps.each { |r| list.push [r, token].join "/" }
|
# failure
|
||||||
|
NullCommand.new(command).run
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
list.sort
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.parser
|
def self.process(arguments)
|
||||||
# If you modify these arguments, please update USAGE.md
|
command_string, *rest = *arguments
|
||||||
@parser ||= OptionParser.new do |opts|
|
rest = process_options(rest)
|
||||||
OPTIONS.each do |option, method|
|
command = Hbc.help ? "help" : lookup_command(command_string)
|
||||||
opts.on("#{option}" "PATH", Pathname) do |path|
|
Hbc.default_tap.install unless Hbc.default_tap.installed?
|
||||||
Hbc.public_send(method, path)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
opts.on("--binarydir=PATH") do
|
# for compat with Homebrew, not certain if this is desirable
|
||||||
opoo <<-EOS.undent
|
Hbc.verbose = true if !ENV["VERBOSE"].nil? || !ENV["HOMEBREW_VERBOSE"].nil?
|
||||||
Option --binarydir is obsolete!
|
|
||||||
Homebrew-Cask now uses the same location as your Homebrew installation for executable links.
|
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
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
FLAGS.each do |flag, method|
|
def usage
|
||||||
opts.on(flag) do
|
max_command_len = CLI.commands.map(&:length).max
|
||||||
Hbc.public_send(method, true)
|
|
||||||
|
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
|
end
|
||||||
|
puts %Q{\nSee also "man brew-cask"}
|
||||||
end
|
end
|
||||||
|
|
||||||
opts.on("--version") do
|
def help
|
||||||
raise OptionParser::InvalidOption # override default handling of --version
|
""
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.process_options(args)
|
def _help_for(klass)
|
||||||
all_args = Shellwords.shellsplit(ENV["HOMEBREW_CASK_OPTS"] || "") + args
|
klass.respond_to?(:help) ? klass.help : nil
|
||||||
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}'"
|
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
@ -1,52 +1,56 @@
|
|||||||
class Hbc::CLI::Audit < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.help
|
class CLI
|
||||||
"verifies installability of Casks"
|
class Audit < Base
|
||||||
end
|
def self.help
|
||||||
|
"verifies installability of Casks"
|
||||||
|
end
|
||||||
|
|
||||||
def self.run(*args)
|
def self.run(*args)
|
||||||
failed_casks = new(args, Hbc::Auditor).run
|
failed_casks = new(args, Auditor).run
|
||||||
return if failed_casks.empty?
|
return if failed_casks.empty?
|
||||||
raise Hbc::CaskError, "audit failed for casks: #{failed_casks.join(" ")}"
|
raise CaskError, "audit failed for casks: #{failed_casks.join(" ")}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(args, auditor)
|
def initialize(args, auditor)
|
||||||
@args = args
|
@args = args
|
||||||
@auditor = auditor
|
@auditor = auditor
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
casks_to_audit.each_with_object([]) do |cask, failed|
|
casks_to_audit.each_with_object([]) do |cask, failed|
|
||||||
failed << cask unless audit(cask)
|
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
|
||||||
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
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
class Hbc::CLI::Base
|
module Hbc
|
||||||
def self.command_name
|
class CLI
|
||||||
@command_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase
|
class Base
|
||||||
end
|
def self.command_name
|
||||||
|
@command_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase
|
||||||
|
end
|
||||||
|
|
||||||
def self.visible
|
def self.visible
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.cask_tokens_from(args)
|
def self.cask_tokens_from(args)
|
||||||
args.reject { |a| a.empty? || a.chars.first == "-" }
|
args.reject { |a| a.empty? || a.chars.first == "-" }
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.help
|
def self.help
|
||||||
"No help available for the #{command_name} command"
|
"No help available for the #{command_name} command"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.needs_init?
|
def self.needs_init?
|
||||||
false
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
class Hbc::CLI::Cat < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*args)
|
class CLI
|
||||||
cask_tokens = cask_tokens_from(args)
|
class Cat < Base
|
||||||
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
|
def self.run(*args)
|
||||||
# only respects the first argument
|
cask_tokens = cask_tokens_from(args)
|
||||||
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
|
raise CaskUnspecifiedError if cask_tokens.empty?
|
||||||
cask_path = Hbc.path(cask_token)
|
# only respects the first argument
|
||||||
raise Hbc::CaskUnavailableError, cask_token.to_s unless cask_path.exist?
|
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
|
||||||
puts File.open(cask_path, &:read)
|
cask_path = Hbc.path(cask_token)
|
||||||
end
|
raise CaskUnavailableError, cask_token.to_s unless cask_path.exist?
|
||||||
|
puts File.open(cask_path, &:read)
|
||||||
|
end
|
||||||
|
|
||||||
def self.help
|
def self.help
|
||||||
"dump raw source of the given Cask to the standard output"
|
"dump raw source of the given Cask to the standard output"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,108 +1,112 @@
|
|||||||
class Hbc::CLI::Cleanup < Hbc::CLI::Base
|
module Hbc
|
||||||
OUTDATED_DAYS = 10
|
class CLI
|
||||||
OUTDATED_TIMESTAMP = Time.now - (60 * 60 * 24 * OUTDATED_DAYS)
|
class Cleanup < Base
|
||||||
|
OUTDATED_DAYS = 10
|
||||||
|
OUTDATED_TIMESTAMP = Time.now - (60 * 60 * 24 * OUTDATED_DAYS)
|
||||||
|
|
||||||
def self.help
|
def self.help
|
||||||
"cleans up cached downloads and tracker symlinks"
|
"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
|
|
||||||
end
|
end
|
||||||
puts item
|
|
||||||
item_size = File.size?(item)
|
|
||||||
cleanup_size += item_size unless item_size.nil?
|
|
||||||
item.unlink
|
|
||||||
end
|
|
||||||
|
|
||||||
if processed_files.zero?
|
def self.needs_init?
|
||||||
puts "Nothing to do"
|
true
|
||||||
else
|
end
|
||||||
disk_space = disk_usage_readable(cleanup_size)
|
|
||||||
ohai "This operation has freed approximately #{disk_space} of disk space."
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,37 +1,41 @@
|
|||||||
class Hbc::CLI::Create < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*args)
|
class CLI
|
||||||
cask_tokens = cask_tokens_from(args)
|
class Create < Base
|
||||||
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
|
def self.run(*args)
|
||||||
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
|
cask_tokens = cask_tokens_from(args)
|
||||||
cask_path = Hbc.path(cask_token)
|
raise CaskUnspecifiedError if cask_tokens.empty?
|
||||||
odebug "Creating Cask #{cask_token}"
|
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|
|
File.open(cask_path, "w") do |f|
|
||||||
f.write template(cask_token)
|
f.write template(cask_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
exec_editor cask_path
|
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 ''
|
|
||||||
end
|
end
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.help
|
def self.template(cask_token)
|
||||||
"creates the given Cask and opens it in an editor"
|
<<-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
|
||||||
end
|
end
|
||||||
|
@ -1,205 +1,209 @@
|
|||||||
class Hbc::CLI::Doctor < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run
|
class CLI
|
||||||
ohai "macOS Release:", render_with_none_as_error(MacOS.full_version)
|
class Doctor < Base
|
||||||
ohai "Hardware Architecture:", render_with_none_as_error("#{Hardware::CPU.type}-#{Hardware::CPU.bits}")
|
def self.run
|
||||||
ohai "Ruby Version:", render_with_none_as_error("#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}")
|
ohai "macOS Release:", render_with_none_as_error(MacOS.full_version)
|
||||||
ohai "Ruby Path:", render_with_none_as_error(RbConfig.ruby)
|
ohai "Hardware Architecture:", render_with_none_as_error("#{Hardware::CPU.type}-#{Hardware::CPU.bits}")
|
||||||
# TODO: consider removing most Homebrew constants from doctor output
|
ohai "Ruby Version:", render_with_none_as_error("#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}")
|
||||||
ohai "Homebrew Version:", render_with_none_as_error(homebrew_version)
|
ohai "Ruby Path:", render_with_none_as_error(RbConfig.ruby)
|
||||||
ohai "Homebrew Executable Path:", render_with_none_as_error(Hbc.homebrew_executable)
|
# TODO: consider removing most Homebrew constants from doctor output
|
||||||
ohai "Homebrew Cellar Path:", render_with_none_as_error(homebrew_cellar)
|
ohai "Homebrew Version:", render_with_none_as_error(homebrew_version)
|
||||||
ohai "Homebrew Repository Path:", render_with_none_as_error(homebrew_repository)
|
ohai "Homebrew Executable Path:", render_with_none_as_error(Hbc.homebrew_executable)
|
||||||
ohai "Homebrew Origin:", render_with_none_as_error(homebrew_origin)
|
ohai "Homebrew Cellar Path:", render_with_none_as_error(homebrew_cellar)
|
||||||
ohai "Homebrew-Cask Version:", render_with_none_as_error(Hbc.full_version)
|
ohai "Homebrew Repository Path:", render_with_none_as_error(homebrew_repository)
|
||||||
ohai "Homebrew-Cask Install Location:", render_install_location
|
ohai "Homebrew Origin:", render_with_none_as_error(homebrew_origin)
|
||||||
ohai "Homebrew-Cask Staging Location:", render_staging_location(Hbc.caskroom)
|
ohai "Homebrew-Cask Version:", render_with_none_as_error(Hbc.full_version)
|
||||||
ohai "Homebrew-Cask Cached Downloads:", render_cached_downloads
|
ohai "Homebrew-Cask Install Location:", render_install_location
|
||||||
ohai "Homebrew-Cask Default Tap Path:", render_tap_paths(Hbc.default_tap.path)
|
ohai "Homebrew-Cask Staging Location:", render_staging_location(Hbc.caskroom)
|
||||||
ohai "Homebrew-Cask Alternate Cask Taps:", render_tap_paths(alt_taps)
|
ohai "Homebrew-Cask Cached Downloads:", render_cached_downloads
|
||||||
ohai "Homebrew-Cask Default Tap Cask Count:", render_with_none_as_error(default_cask_count)
|
ohai "Homebrew-Cask Default Tap Path:", render_tap_paths(Hbc.default_tap.path)
|
||||||
ohai "Contents of $LOAD_PATH:", render_load_path($LOAD_PATH)
|
ohai "Homebrew-Cask Alternate Cask Taps:", render_tap_paths(alt_taps)
|
||||||
ohai "Contents of $RUBYLIB Environment Variable:", render_env_var("RUBYLIB")
|
ohai "Homebrew-Cask Default Tap Cask Count:", render_with_none_as_error(default_cask_count)
|
||||||
ohai "Contents of $RUBYOPT Environment Variable:", render_env_var("RUBYOPT")
|
ohai "Contents of $LOAD_PATH:", render_load_path($LOAD_PATH)
|
||||||
ohai "Contents of $RUBYPATH Environment Variable:", render_env_var("RUBYPATH")
|
ohai "Contents of $RUBYLIB Environment Variable:", render_env_var("RUBYLIB")
|
||||||
ohai "Contents of $RBENV_VERSION Environment Variable:", render_env_var("RBENV_VERSION")
|
ohai "Contents of $RUBYOPT Environment Variable:", render_env_var("RUBYOPT")
|
||||||
ohai "Contents of $CHRUBY_VERSION Environment Variable:", render_env_var("CHRUBY_VERSION")
|
ohai "Contents of $RUBYPATH Environment Variable:", render_env_var("RUBYPATH")
|
||||||
ohai "Contents of $GEM_HOME Environment Variable:", render_env_var("GEM_HOME")
|
ohai "Contents of $RBENV_VERSION Environment Variable:", render_env_var("RBENV_VERSION")
|
||||||
ohai "Contents of $GEM_PATH Environment Variable:", render_env_var("GEM_PATH")
|
ohai "Contents of $CHRUBY_VERSION Environment Variable:", render_env_var("CHRUBY_VERSION")
|
||||||
ohai "Contents of $BUNDLE_PATH Environment Variable:", render_env_var("BUNDLE_PATH")
|
ohai "Contents of $GEM_HOME Environment Variable:", render_env_var("GEM_HOME")
|
||||||
ohai "Contents of $PATH Environment Variable:", render_env_var("PATH")
|
ohai "Contents of $GEM_PATH Environment Variable:", render_env_var("GEM_PATH")
|
||||||
ohai "Contents of $SHELL Environment Variable:", render_env_var("SHELL")
|
ohai "Contents of $BUNDLE_PATH Environment Variable:", render_env_var("BUNDLE_PATH")
|
||||||
ohai "Contents of Locale Environment Variables:", render_with_none(locale_variables)
|
ohai "Contents of $PATH Environment Variable:", render_env_var("PATH")
|
||||||
ohai "Running As Privileged User:", render_with_none_as_error(privileged_uid)
|
ohai "Contents of $SHELL Environment Variable:", render_env_var("SHELL")
|
||||||
end
|
ohai "Contents of Locale Environment Variables:", render_with_none(locale_variables)
|
||||||
|
ohai "Running As Privileged User:", render_with_none_as_error(privileged_uid)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
if homebrew_origin !~ %r{\S}
|
|
||||||
homebrew_origin = "#{none_string} #{error_string}"
|
def self.alt_taps
|
||||||
elsif homebrew_origin !~ %r{(mxcl|Homebrew)/(home)?brew(\.git)?\Z}
|
Tap.select { |t| t.cask_dir && t != Hbc.default_tap }
|
||||||
homebrew_origin.concat " #{error_string "warning: nonstandard origin"}"
|
.map(&:path)
|
||||||
end
|
end
|
||||||
rescue StandardError
|
|
||||||
homebrew_origin = error_string "Not Found - Error running git"
|
|
||||||
end
|
|
||||||
homebrew_origin
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.homebrew_repository
|
def self.default_cask_count
|
||||||
homebrew_constants("repository")
|
Hbc.default_tap.cask_files.count
|
||||||
end
|
rescue StandardError
|
||||||
|
"0 #{error_string "Error reading #{Hbc.default_tap.path}"}"
|
||||||
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}"
|
|
||||||
end
|
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
|
def self.homebrew_origin
|
||||||
ENV.keys.grep(%r{^(?:LC_\S+|LANG|LANGUAGE)\Z}).collect { |v| %Q{#{v}="#{ENV[v]}"} }.sort.join("\n")
|
homebrew_origin = notfound_string
|
||||||
end
|
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
|
def self.homebrew_repository
|
||||||
Process.euid.zero? ? "Yes #{error_string "warning: not recommended"}" : "No"
|
homebrew_constants("repository")
|
||||||
rescue StandardError
|
end
|
||||||
notfound_string
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.none_string
|
def self.homebrew_cellar
|
||||||
"<NONE>"
|
homebrew_constants("cellar")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.legacy_tap_pattern
|
def self.homebrew_version
|
||||||
%r{phinze}
|
homebrew_constants("version")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.notfound_string
|
def self.homebrew_taps
|
||||||
"#{Tty.red}Not Found - Unknown Error#{Tty.reset}"
|
@homebrew_taps ||= if homebrew_repository.respond_to?(:join)
|
||||||
end
|
homebrew_repository.join("Library", "Taps")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.error_string(string = "Error")
|
def self.homebrew_constants(name)
|
||||||
"#{Tty.red}(#{string})#{Tty.reset}"
|
@homebrew_constants ||= {}
|
||||||
end
|
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)
|
def self.locale_variables
|
||||||
return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty?
|
ENV.keys.grep(%r{^(?:LC_\S+|LANG|LANGUAGE)\Z}).collect { |v| %Q{#{v}="#{ENV[v]}"} }.sort.join("\n")
|
||||||
none_string
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.render_with_none_as_error(string)
|
def self.privileged_uid
|
||||||
return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty?
|
Process.euid.zero? ? "Yes #{error_string "warning: not recommended"}" : "No"
|
||||||
"#{none_string} #{error_string}"
|
rescue StandardError
|
||||||
end
|
notfound_string
|
||||||
|
end
|
||||||
|
|
||||||
def self.render_tap_paths(paths)
|
def self.none_string
|
||||||
paths = [paths] unless paths.respond_to?(:each)
|
"<NONE>"
|
||||||
paths.collect do |dir|
|
end
|
||||||
if dir.nil? || dir.to_s.empty?
|
|
||||||
|
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
|
none_string
|
||||||
elsif dir.to_s.match(legacy_tap_pattern)
|
end
|
||||||
dir.to_s.concat(" #{error_string "Warning: legacy tap path"}")
|
|
||||||
else
|
def self.render_with_none_as_error(string)
|
||||||
dir.to_s
|
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
|
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
|
end
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
class Hbc::CLI::Edit < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*args)
|
class CLI
|
||||||
cask_tokens = cask_tokens_from(args)
|
class Edit < Base
|
||||||
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
|
def self.run(*args)
|
||||||
# only respects the first argument
|
cask_tokens = cask_tokens_from(args)
|
||||||
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
|
raise CaskUnspecifiedError if cask_tokens.empty?
|
||||||
cask_path = Hbc.path(cask_token)
|
# only respects the first argument
|
||||||
odebug "Opening editor for Cask #{cask_token}"
|
cask_token = cask_tokens.first.sub(%r{\.rb$}i, "")
|
||||||
unless cask_path.exist?
|
cask_path = Hbc.path(cask_token)
|
||||||
raise Hbc::CaskUnavailableError, %Q{#{cask_token}, run "brew cask create #{cask_token}" to create a new Cask}
|
odebug "Opening editor for Cask #{cask_token}"
|
||||||
end
|
unless cask_path.exist?
|
||||||
exec_editor cask_path
|
raise CaskUnavailableError, %Q{#{cask_token}, run "brew cask create #{cask_token}" to create a new Cask}
|
||||||
end
|
end
|
||||||
|
exec_editor cask_path
|
||||||
|
end
|
||||||
|
|
||||||
def self.help
|
def self.help
|
||||||
"edits the given Cask"
|
"edits the given Cask"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
class Hbc::CLI::Fetch < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*args)
|
class CLI
|
||||||
cask_tokens = cask_tokens_from(args)
|
class Fetch < Base
|
||||||
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
|
def self.run(*args)
|
||||||
force = args.include? "--force"
|
cask_tokens = cask_tokens_from(args)
|
||||||
|
raise CaskUnspecifiedError if cask_tokens.empty?
|
||||||
|
force = args.include? "--force"
|
||||||
|
|
||||||
cask_tokens.each do |cask_token|
|
cask_tokens.each do |cask_token|
|
||||||
ohai "Downloading external files for Cask #{cask_token}"
|
ohai "Downloading external files for Cask #{cask_token}"
|
||||||
cask = Hbc.load(cask_token)
|
cask = Hbc.load(cask_token)
|
||||||
downloaded_path = Hbc::Download.new(cask, force: force).perform
|
downloaded_path = Download.new(cask, force: force).perform
|
||||||
Hbc::Verify.all(cask, downloaded_path)
|
Verify.all(cask, downloaded_path)
|
||||||
ohai "Success! Downloaded to -> #{downloaded_path}"
|
ohai "Success! Downloaded to -> #{downloaded_path}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.help
|
||||||
|
"downloads remote application files to local cache"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.help
|
|
||||||
"downloads remote application files to local cache"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
class Hbc::CLI::Home < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*cask_tokens)
|
class CLI
|
||||||
if cask_tokens.empty?
|
class Home < Base
|
||||||
odebug "Opening project homepage"
|
def self.run(*cask_tokens)
|
||||||
system "/usr/bin/open", "--", "http://caskroom.io/"
|
if cask_tokens.empty?
|
||||||
else
|
odebug "Opening project homepage"
|
||||||
cask_tokens.each do |cask_token|
|
system "/usr/bin/open", "--", "http://caskroom.io/"
|
||||||
odebug "Opening homepage for Cask #{cask_token}"
|
else
|
||||||
cask = Hbc.load(cask_token)
|
cask_tokens.each do |cask_token|
|
||||||
system "/usr/bin/open", "--", cask.homepage
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.help
|
|
||||||
"opens the homepage of the given Cask"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,65 +1,69 @@
|
|||||||
class Hbc::CLI::Info < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*args)
|
class CLI
|
||||||
cask_tokens = cask_tokens_from(args)
|
class Info < Base
|
||||||
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
|
def self.run(*args)
|
||||||
cask_tokens.each do |cask_token|
|
cask_tokens = cask_tokens_from(args)
|
||||||
odebug "Getting info for Cask #{cask_token}"
|
raise CaskUnspecifiedError if cask_tokens.empty?
|
||||||
cask = Hbc.load(cask_token)
|
cask_tokens.each do |cask_token|
|
||||||
|
odebug "Getting info for Cask #{cask_token}"
|
||||||
|
cask = Hbc.load(cask_token)
|
||||||
|
|
||||||
info(cask)
|
info(cask)
|
||||||
end
|
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(")")
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
puts "Not installed"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.name_info(cask)
|
def self.help
|
||||||
ohai cask.name.size > 1 ? "Names" : "Name"
|
"displays information about the given Cask"
|
||||||
puts cask.name.empty? ? "#{Tty.red}None#{Tty.reset}" : cask.name
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.github_info(cask)
|
def self.info(cask)
|
||||||
user, repo, token = Hbc::QualifiedToken.parse(Hbc.all_tokens.detect { |t| t.split("/").last == cask.token })
|
puts "#{cask.token}: #{cask.version}"
|
||||||
"#{Tap.fetch(user, repo).default_remote}/blob/master/Casks/#{token}.rb"
|
puts formatted_url(cask.homepage) if cask.homepage
|
||||||
end
|
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)
|
def self.formatted_url(url)
|
||||||
ohai "Artifacts"
|
"#{Tty.em}#{url}#{Tty.reset}"
|
||||||
Hbc::DSL::ORDINARY_ARTIFACT_TYPES.each do |type|
|
end
|
||||||
next if cask.artifacts[type].empty?
|
|
||||||
cask.artifacts[type].each do |artifact|
|
def self.installation_info(cask)
|
||||||
activatable_item = type == :stage_only ? "<none>" : artifact.first
|
if cask.installed?
|
||||||
puts "#{activatable_item} (#{type})"
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -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
|
raise CaskError, "nothing to install" if retval.nil?
|
||||||
def self.run(*args)
|
raise CaskError, "install incomplete" unless retval
|
||||||
cask_tokens = cask_tokens_from(args)
|
end
|
||||||
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 Hbc::CaskError, "nothing to install" if retval.nil?
|
def self.install_casks(cask_tokens, force, skip_cask_deps, require_sha)
|
||||||
raise Hbc::CaskError, "install incomplete" unless retval
|
count = 0
|
||||||
end
|
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)
|
def self.warn_unavailable_with_suggestion(cask_token, e)
|
||||||
count = 0
|
exact_match, partial_matches = Search.search(cask_token)
|
||||||
cask_tokens.each do |cask_token|
|
errmsg = e.message
|
||||||
begin
|
if exact_match
|
||||||
cask = Hbc.load(cask_token)
|
errmsg.concat(". Did you mean:\n#{exact_match}")
|
||||||
Hbc::Installer.new(cask,
|
elsif !partial_matches.empty?
|
||||||
force: force,
|
errmsg.concat(". Did you mean one of:\n#{puts_columns(partial_matches.take(20))}\n")
|
||||||
skip_cask_deps: skip_cask_deps,
|
end
|
||||||
require_sha: require_sha).install
|
onoe errmsg
|
||||||
count += 1
|
end
|
||||||
rescue Hbc::CaskAlreadyInstalledError => e
|
|
||||||
opoo e.message
|
def self.help
|
||||||
count += 1
|
"installs the given Cask"
|
||||||
rescue Hbc::CaskAutoUpdatesError => e
|
end
|
||||||
opoo e.message
|
|
||||||
count += 1
|
def self.needs_init?
|
||||||
rescue Hbc::CaskUnavailableError => e
|
true
|
||||||
warn_unavailable_with_suggestion cask_token, e
|
|
||||||
rescue Hbc::CaskNoShasumError => e
|
|
||||||
opoo e.message
|
|
||||||
count += 1
|
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
@ -1,135 +1,139 @@
|
|||||||
class Hbc::CLI::InternalAuditModifiedCasks < Hbc::CLI::InternalUseBase
|
module Hbc
|
||||||
RELEVANT_STANZAS = %i{version sha256 url appcast}.freeze
|
class CLI
|
||||||
|
class InternalAuditModifiedCasks < InternalUseBase
|
||||||
|
RELEVANT_STANZAS = %i{version sha256 url appcast}.freeze
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def needs_init?
|
def needs_init?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(*args)
|
def run(*args)
|
||||||
commit_range = commit_range(args)
|
commit_range = commit_range(args)
|
||||||
cleanup = args.any? { |a| a =~ %r{^-+c(leanup)?$}i }
|
cleanup = args.any? { |a| a =~ %r{^-+c(leanup)?$}i }
|
||||||
new(commit_range, cleanup: cleanup).run
|
new(commit_range, cleanup: cleanup).run
|
||||||
end
|
end
|
||||||
|
|
||||||
def commit_range(args)
|
def commit_range(args)
|
||||||
posargs = args.reject { |a| a.empty? || a.chars.first == "-" }
|
posargs = args.reject { |a| a.empty? || a.chars.first == "-" }
|
||||||
odie usage unless posargs.size == 1
|
odie usage unless posargs.size == 1
|
||||||
posargs.first
|
posargs.first
|
||||||
end
|
end
|
||||||
|
|
||||||
def posargs(args)
|
def posargs(args)
|
||||||
args.reject { |a| a.empty? || a.chars.first == "-" }
|
args.reject { |a| a.empty? || a.chars.first == "-" }
|
||||||
end
|
end
|
||||||
|
|
||||||
def usage
|
def usage
|
||||||
<<-EOS.undent
|
<<-EOS.undent
|
||||||
Usage: brew cask _audit_modified_casks [options...] <commit range>
|
Usage: brew cask _audit_modified_casks [options...] <commit range>
|
||||||
|
|
||||||
Given a range of Git commits, find any Casks that were modified and run `brew
|
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,
|
cask audit' on them. If the `url', `version', or `sha256' stanzas were modified,
|
||||||
run with the `--download' flag to verify the hash.
|
run with the `--download' flag to verify the hash.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-c, --cleanup
|
-c, --cleanup
|
||||||
Remove all cached downloads. Use with care.
|
Remove all cached downloads. Use with care.
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(commit_range, cleanup: false)
|
def initialize(commit_range, cleanup: false)
|
||||||
@commit_range = commit_range
|
@commit_range = commit_range
|
||||||
@cleanup = cleanup
|
@cleanup = cleanup
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :commit_range
|
attr_reader :commit_range
|
||||||
|
|
||||||
def cleanup?
|
def cleanup?
|
||||||
@cleanup
|
@cleanup
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
at_exit do
|
at_exit do
|
||||||
cleanup
|
cleanup
|
||||||
end
|
end
|
||||||
|
|
||||||
Dir.chdir git_root do
|
Dir.chdir git_root do
|
||||||
modified_cask_files.zip(modified_casks).each do |cask_file, cask|
|
modified_cask_files.zip(modified_casks).each do |cask_file, cask|
|
||||||
audit(cask, cask_file)
|
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
|
||||||
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
|
||||||
end
|
end
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
class Hbc::CLI::InternalCheckurl < Hbc::CLI::InternalUseBase
|
module Hbc
|
||||||
def self.run(*args)
|
class CLI
|
||||||
casks_to_check = args.empty? ? Hbc.all : args.map { |arg| Hbc.load(arg) }
|
class InternalCheckurl < InternalUseBase
|
||||||
casks_to_check.each do |cask|
|
def self.run(*args)
|
||||||
odebug "Checking URL for Cask #{cask}"
|
casks_to_check = args.empty? ? Hbc.all : args.map { |arg| Hbc.load(arg) }
|
||||||
checker = Hbc::UrlChecker.new(cask)
|
casks_to_check.each do |cask|
|
||||||
checker.run
|
odebug "Checking URL for Cask #{cask}"
|
||||||
puts checker.summary
|
checker = UrlChecker.new(cask)
|
||||||
|
checker.run
|
||||||
|
puts checker.summary
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.help
|
||||||
|
"checks for bad Cask URLs"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.help
|
|
||||||
"checks for bad Cask URLs"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,30 +1,34 @@
|
|||||||
class Hbc::CLI::InternalDump < Hbc::CLI::InternalUseBase
|
module Hbc
|
||||||
def self.run(*arguments)
|
class CLI
|
||||||
cask_tokens = cask_tokens_from(arguments)
|
class InternalDump < InternalUseBase
|
||||||
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
|
def self.run(*arguments)
|
||||||
retval = dump_casks(*cask_tokens)
|
cask_tokens = cask_tokens_from(arguments)
|
||||||
# retval is ternary: true/false/nil
|
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 CaskError, "nothing to dump" if retval.nil?
|
||||||
raise Hbc::CaskError, "dump incomplete" unless retval
|
raise CaskError, "dump incomplete" unless retval
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.dump_casks(*cask_tokens)
|
def self.dump_casks(*cask_tokens)
|
||||||
Hbc.debug = true # Yuck. At the moment this is the only way to make dumps visible
|
Hbc.debug = true # Yuck. At the moment this is the only way to make dumps visible
|
||||||
count = 0
|
count = 0
|
||||||
cask_tokens.each do |cask_token|
|
cask_tokens.each do |cask_token|
|
||||||
begin
|
begin
|
||||||
cask = Hbc.load(cask_token)
|
cask = Hbc.load(cask_token)
|
||||||
count += 1
|
count += 1
|
||||||
cask.dumpcask
|
cask.dumpcask
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
opoo "#{cask_token} was not found or would not load: #{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
|
||||||
end
|
end
|
||||||
count.zero? ? nil : count == cask_tokens.length
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.help
|
|
||||||
"Dump the given Cask in YAML format"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
class Hbc::CLI::InternalHelp < Hbc::CLI::InternalUseBase
|
module Hbc
|
||||||
def self.run(*_ignored)
|
class CLI
|
||||||
max_command_len = Hbc::CLI.commands.map(&:length).max
|
class InternalHelp < InternalUseBase
|
||||||
puts "Unstable Internal-use Commands:\n\n"
|
def self.run(*_ignored)
|
||||||
Hbc::CLI.command_classes.each do |klass|
|
max_command_len = CLI.commands.map(&:length).max
|
||||||
next if klass.visible
|
puts "Unstable Internal-use Commands:\n\n"
|
||||||
puts " #{klass.command_name.ljust(max_command_len)} #{help_for(klass)}"
|
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
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,127 +1,131 @@
|
|||||||
class Hbc::CLI::InternalStanza < Hbc::CLI::InternalUseBase
|
module Hbc
|
||||||
# Syntax
|
class CLI
|
||||||
#
|
class InternalStanza < InternalUseBase
|
||||||
# brew cask _stanza <stanza_name> [ --table | --yaml | --inspect | --quiet ] [ <cask_token> ... ]
|
# Syntax
|
||||||
#
|
#
|
||||||
# If no tokens are given, then data for all Casks is returned.
|
# brew cask _stanza <stanza_name> [ --table | --yaml | --inspect | --quiet ] [ <cask_token> ... ]
|
||||||
#
|
#
|
||||||
# The pseudo-stanza "artifacts" is available.
|
# If no tokens are given, then data for all Casks is returned.
|
||||||
#
|
#
|
||||||
# On failure, a blank line is returned on the standard output.
|
# The pseudo-stanza "artifacts" is available.
|
||||||
#
|
#
|
||||||
# Examples
|
# On failure, a blank line is returned on the standard output.
|
||||||
#
|
#
|
||||||
# brew cask _stanza appcast --table
|
# Examples
|
||||||
# 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 appcast --table
|
||||||
# brew cask _stanza version --table alfred google-chrome adium voicemac logisim vagrant
|
# brew cask _stanza app --table alfred google-chrome adium voicemac logisim vagrant
|
||||||
# brew cask _stanza artifacts --table --inspect alfred google-chrome adium voicemac logisim vagrant
|
# brew cask _stanza url --table alfred google-chrome adium voicemac logisim vagrant
|
||||||
# brew cask _stanza artifacts --table --yaml 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
|
# TODO: this should be retrievable from Hbc::DSL
|
||||||
ARTIFACTS = Set.new [
|
ARTIFACTS = Set.new [
|
||||||
:app,
|
:app,
|
||||||
:suite,
|
:suite,
|
||||||
:artifact,
|
:artifact,
|
||||||
:prefpane,
|
:prefpane,
|
||||||
:qlplugin,
|
:qlplugin,
|
||||||
:font,
|
:font,
|
||||||
:service,
|
:service,
|
||||||
:colorpicker,
|
:colorpicker,
|
||||||
:binary,
|
:binary,
|
||||||
:input_method,
|
:input_method,
|
||||||
:internet_plugin,
|
:internet_plugin,
|
||||||
:audio_unit_plugin,
|
:audio_unit_plugin,
|
||||||
:vst_plugin,
|
:vst_plugin,
|
||||||
:vst3_plugin,
|
:vst3_plugin,
|
||||||
:screen_saver,
|
:screen_saver,
|
||||||
:pkg,
|
:pkg,
|
||||||
:installer,
|
:installer,
|
||||||
:stage_only,
|
:stage_only,
|
||||||
:nested_container,
|
:nested_container,
|
||||||
:uninstall,
|
:uninstall,
|
||||||
:postflight,
|
:postflight,
|
||||||
:uninstall_postflight,
|
:uninstall_postflight,
|
||||||
:preflight,
|
:preflight,
|
||||||
:uninstall_postflight,
|
:uninstall_postflight,
|
||||||
]
|
]
|
||||||
|
|
||||||
def self.run(*arguments)
|
def self.run(*arguments)
|
||||||
table = arguments.include? "--table"
|
table = arguments.include? "--table"
|
||||||
quiet = arguments.include? "--quiet"
|
quiet = arguments.include? "--quiet"
|
||||||
format = :to_yaml if arguments.include? "--yaml"
|
format = :to_yaml if arguments.include? "--yaml"
|
||||||
format = :inspect if arguments.include? "--inspect"
|
format = :inspect if arguments.include? "--inspect"
|
||||||
cask_tokens = arguments.reject { |arg| arg.chars.first == "-" }
|
cask_tokens = arguments.reject { |arg| arg.chars.first == "-" }
|
||||||
stanza = cask_tokens.shift.to_sym
|
stanza = cask_tokens.shift.to_sym
|
||||||
cask_tokens = Hbc.all_tokens if cask_tokens.empty?
|
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
|
# retval is ternary: true/false/nil
|
||||||
if retval.nil?
|
if retval.nil?
|
||||||
exit 1 if quiet
|
exit 1 if quiet
|
||||||
raise Hbc::CaskError, "nothing to print"
|
raise CaskError, "nothing to print"
|
||||||
elsif !retval
|
elsif !retval
|
||||||
exit 1 if quiet
|
exit 1 if quiet
|
||||||
raise Hbc::CaskError, "print incomplete"
|
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
|
||||||
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
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
class Hbc::CLI::InternalUseBase < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.command_name
|
class CLI
|
||||||
super.sub(%r{^internal_}i, "_")
|
class InternalUseBase < Base
|
||||||
end
|
def self.command_name
|
||||||
|
super.sub(%r{^internal_}i, "_")
|
||||||
|
end
|
||||||
|
|
||||||
def self.visible
|
def self.visible
|
||||||
false
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,86 +1,90 @@
|
|||||||
class Hbc::CLI::List < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*arguments)
|
class CLI
|
||||||
@options = {}
|
class List < Base
|
||||||
@options[:one] = true if arguments.delete("-1")
|
def self.run(*arguments)
|
||||||
@options[:versions] = true if arguments.delete("--versions")
|
@options = {}
|
||||||
|
@options[:one] = true if arguments.delete("-1")
|
||||||
|
@options[:versions] = true if arguments.delete("--versions")
|
||||||
|
|
||||||
if arguments.delete("-l")
|
if arguments.delete("-l")
|
||||||
@options[:one] = true
|
@options[:one] = true
|
||||||
opoo "Option -l is obsolete! Implying option -1."
|
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"
|
|
||||||
end
|
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
|
||||||
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
|
||||||
end
|
end
|
||||||
|
@ -1,55 +1,59 @@
|
|||||||
class Hbc::CLI::Search < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*arguments)
|
class CLI
|
||||||
render_results(*search(*arguments))
|
class Search < Base
|
||||||
end
|
def self.run(*arguments)
|
||||||
|
render_results(*search(*arguments))
|
||||||
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"
|
|
||||||
end
|
end
|
||||||
puts_columns partial_matches
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.help
|
def self.extract_regexp(string)
|
||||||
"searches all known Casks"
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,69 +1,73 @@
|
|||||||
require "English"
|
require "English"
|
||||||
|
|
||||||
class Hbc::CLI::Style < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.help
|
class CLI
|
||||||
"checks Cask style using RuboCop"
|
class Style < Base
|
||||||
end
|
def self.help
|
||||||
|
"checks Cask style using RuboCop"
|
||||||
|
end
|
||||||
|
|
||||||
def self.run(*args)
|
def self.run(*args)
|
||||||
retval = new(args).run
|
retval = new(args).run
|
||||||
raise Hbc::CaskError, "style check failed" unless retval
|
raise CaskError, "style check failed" unless retval
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :args
|
attr_reader :args
|
||||||
def initialize(args)
|
def initialize(args)
|
||||||
@args = args
|
@args = args
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
install_rubocop
|
install_rubocop
|
||||||
system "rubocop", *rubocop_args, "--", *cask_paths
|
system "rubocop", *rubocop_args, "--", *cask_paths
|
||||||
$CHILD_STATUS.success?
|
$CHILD_STATUS.success?
|
||||||
end
|
end
|
||||||
|
|
||||||
RUBOCOP_CASK_VERSION = "~> 0.8.3".freeze
|
RUBOCOP_CASK_VERSION = "~> 0.8.3".freeze
|
||||||
|
|
||||||
def install_rubocop
|
def install_rubocop
|
||||||
Hbc::Utils.capture_stderr do
|
Utils.capture_stderr do
|
||||||
begin
|
begin
|
||||||
Homebrew.install_gem_setup_path! "rubocop-cask", RUBOCOP_CASK_VERSION, "rubocop"
|
Homebrew.install_gem_setup_path! "rubocop-cask", RUBOCOP_CASK_VERSION, "rubocop"
|
||||||
rescue SystemExit
|
rescue SystemExit
|
||||||
raise Hbc::CaskError, $stderr.string.chomp.sub("#{Tty.red}Error#{Tty.reset}: ", "")
|
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
|
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
|
||||||
|
@ -1,40 +1,44 @@
|
|||||||
class Hbc::CLI::Uninstall < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*args)
|
class CLI
|
||||||
cask_tokens = cask_tokens_from(args)
|
class Uninstall < Base
|
||||||
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
|
def self.run(*args)
|
||||||
force = args.include? "--force"
|
cask_tokens = cask_tokens_from(args)
|
||||||
|
raise CaskUnspecifiedError if cask_tokens.empty?
|
||||||
|
force = args.include? "--force"
|
||||||
|
|
||||||
cask_tokens.each do |cask_token|
|
cask_tokens.each do |cask_token|
|
||||||
odebug "Uninstalling Cask #{cask_token}"
|
odebug "Uninstalling Cask #{cask_token}"
|
||||||
cask = Hbc.load(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?
|
unless latest_installed_version.nil?
|
||||||
latest_installed_cask_file = cask.metadata_master_container_path
|
latest_installed_cask_file = cask.metadata_master_container_path
|
||||||
.join(latest_installed_version.join(File::Separator),
|
.join(latest_installed_version.join(File::Separator),
|
||||||
"Casks", "#{cask_token}.rb")
|
"Casks", "#{cask_token}.rb")
|
||||||
|
|
||||||
# use the same cask file that was used for installation, if possible
|
# 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?
|
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
|
end
|
||||||
|
|
||||||
Hbc::Installer.new(cask, force: force).uninstall
|
def self.help
|
||||||
|
"uninstalls the given Cask"
|
||||||
next if (versions = cask.versions).empty?
|
end
|
||||||
|
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.help
|
|
||||||
"uninstalls the given Cask"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
class Hbc::CLI::Update < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*_ignored)
|
class CLI
|
||||||
result = Hbc::SystemCommand.run(Hbc.homebrew_executable,
|
class Update < Base
|
||||||
args: %w[update])
|
def self.run(*_ignored)
|
||||||
# TODO: separating stderr/stdout is undesirable here.
|
result = SystemCommand.run(Hbc.homebrew_executable,
|
||||||
# Hbc::SystemCommand should have an option for plain
|
args: %w[update])
|
||||||
# unbuffered output.
|
# TODO: separating stderr/stdout is undesirable here.
|
||||||
print result.stdout
|
# Hbc::SystemCommand should have an option for plain
|
||||||
$stderr.print result.stderr
|
# unbuffered output.
|
||||||
exit result.exit_status
|
print result.stdout
|
||||||
end
|
$stderr.print result.stderr
|
||||||
|
exit result.exit_status
|
||||||
|
end
|
||||||
|
|
||||||
def self.help
|
def self.help
|
||||||
"a synonym for 'brew update'"
|
"a synonym for 'brew update'"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
class Hbc::CLI::Zap < Hbc::CLI::Base
|
module Hbc
|
||||||
def self.run(*args)
|
class CLI
|
||||||
cask_tokens = cask_tokens_from(args)
|
class Zap < Base
|
||||||
raise Hbc::CaskUnspecifiedError if cask_tokens.empty?
|
def self.run(*args)
|
||||||
cask_tokens.each do |cask_token|
|
cask_tokens = cask_tokens_from(args)
|
||||||
odebug "Zapping Cask #{cask_token}"
|
raise CaskUnspecifiedError if cask_tokens.empty?
|
||||||
cask = Hbc.load(cask_token)
|
cask_tokens.each do |cask_token|
|
||||||
Hbc::Installer.new(cask).zap
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.help
|
|
||||||
"zaps all files associated with the given Cask"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
class Hbc::Container; end
|
|
||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
require "hbc/container/air"
|
require "hbc/container/air"
|
||||||
require "hbc/container/bzip2"
|
require "hbc/container/bzip2"
|
||||||
@ -22,47 +20,49 @@ require "hbc/container/xip"
|
|||||||
require "hbc/container/xz"
|
require "hbc/container/xz"
|
||||||
require "hbc/container/zip"
|
require "hbc/container/zip"
|
||||||
|
|
||||||
class Hbc::Container
|
module Hbc
|
||||||
def self.autodetect_containers
|
class Container
|
||||||
[
|
def self.autodetect_containers
|
||||||
Hbc::Container::Pkg,
|
[
|
||||||
Hbc::Container::Ttf,
|
Pkg,
|
||||||
Hbc::Container::Otf,
|
Ttf,
|
||||||
Hbc::Container::Air,
|
Otf,
|
||||||
Hbc::Container::Cab,
|
Air,
|
||||||
Hbc::Container::Dmg,
|
Cab,
|
||||||
Hbc::Container::SevenZip,
|
Dmg,
|
||||||
Hbc::Container::Sit,
|
SevenZip,
|
||||||
Hbc::Container::Rar,
|
Sit,
|
||||||
Hbc::Container::Zip,
|
Rar,
|
||||||
Hbc::Container::Xip, # needs to be before xar as this is a cpio inside a gzip inside a xar
|
Zip,
|
||||||
Hbc::Container::Xar, # need to be before tar as tar can also list xar
|
Xip, # needs to be before xar as this is a cpio inside a gzip inside a xar
|
||||||
Hbc::Container::Tar, # or compressed tar (bzip2/gzip/lzma/xz)
|
Xar, # need to be before tar as tar can also list xar
|
||||||
Hbc::Container::Bzip2, # pure bzip2
|
Tar, # or compressed tar (bzip2/gzip/lzma/xz)
|
||||||
Hbc::Container::Gzip, # pure gzip
|
Bzip2, # pure bzip2
|
||||||
Hbc::Container::Lzma, # pure lzma
|
Gzip, # pure gzip
|
||||||
Hbc::Container::Xz, # pure xz
|
Lzma, # pure lzma
|
||||||
]
|
Xz, # pure xz
|
||||||
# for explicit use only (never autodetected):
|
]
|
||||||
# Hbc::Container::Naked
|
# for explicit use only (never autodetected):
|
||||||
# Hbc::Container::GenericUnar
|
# Hbc::Container::Naked
|
||||||
end
|
# Hbc::Container::GenericUnar
|
||||||
|
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.from_type(type)
|
def self.for_path(path, command)
|
||||||
odebug "Determining which containers to use based on 'container :type'"
|
odebug "Determining which containers to use based on filetype"
|
||||||
begin
|
criteria = Criteria.new(path, command)
|
||||||
Hbc::Container.const_get(type.to_s.split("_").map(&:capitalize).join)
|
autodetect_containers.find do |c|
|
||||||
rescue NameError
|
odebug "Checking container class #{c}"
|
||||||
false
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,33 +1,37 @@
|
|||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Air < Hbc::Container::Base
|
module Hbc
|
||||||
INSTALLER_PATHNAME =
|
class Container
|
||||||
Pathname("/Applications/Utilities/Adobe AIR Application Installer.app" \
|
class Air < Base
|
||||||
"/Contents/MacOS/Adobe AIR Application Installer")
|
INSTALLER_PATHNAME =
|
||||||
|
Pathname("/Applications/Utilities/Adobe AIR Application Installer.app" \
|
||||||
|
"/Contents/MacOS/Adobe AIR Application Installer")
|
||||||
|
|
||||||
def self.me?(criteria)
|
def self.me?(criteria)
|
||||||
%w[.air].include?(criteria.path.extname)
|
%w[.air].include?(criteria.path.extname)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.installer_cmd
|
def self.installer_cmd
|
||||||
return @installer_cmd ||= INSTALLER_PATHNAME if installer_exist?
|
return @installer_cmd ||= INSTALLER_PATHNAME if installer_exist?
|
||||||
raise Hbc::CaskError, <<-EOS.undent
|
raise CaskError, <<-EOS.undent
|
||||||
Adobe AIR runtime not present, try installing it via
|
Adobe AIR runtime not present, try installing it via
|
||||||
|
|
||||||
brew cask install adobe-air
|
brew cask install adobe-air
|
||||||
|
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.installer_exist?
|
def self.installer_exist?
|
||||||
INSTALLER_PATHNAME.exist?
|
INSTALLER_PATHNAME.exist?
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
install = @command.run(self.class.installer_cmd,
|
install = @command.run(self.class.installer_cmd,
|
||||||
args: ["-silent", "-location", @cask.staged_path, Pathname.new(@path).realpath])
|
args: ["-silent", "-location", @cask.staged_path, Pathname.new(@path).realpath])
|
||||||
|
|
||||||
return unless install.exit_status == 9
|
return unless install.exit_status == 9
|
||||||
raise Hbc::CaskError, "Adobe AIR application #{@cask} already exists on the system, and cannot be reinstalled."
|
raise CaskError, "Adobe AIR application #{@cask} already exists on the system, and cannot be reinstalled."
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,37 +1,41 @@
|
|||||||
class Hbc::Container::Base
|
module Hbc
|
||||||
def initialize(cask, path, command, nested: false)
|
class Container
|
||||||
@cask = cask
|
class Base
|
||||||
@path = path
|
def initialize(cask, path, command, nested: false)
|
||||||
@command = command
|
@cask = cask
|
||||||
@nested = nested
|
@path = path
|
||||||
end
|
@command = command
|
||||||
|
@nested = nested
|
||||||
|
end
|
||||||
|
|
||||||
def extract_nested_inside(dir)
|
def extract_nested_inside(dir)
|
||||||
children = Pathname.new(dir).children
|
children = Pathname.new(dir).children
|
||||||
|
|
||||||
nested_container = children[0]
|
nested_container = children[0]
|
||||||
|
|
||||||
unless children.count == 1 &&
|
unless children.count == 1 &&
|
||||||
!nested_container.directory? &&
|
!nested_container.directory? &&
|
||||||
@cask.artifacts[:nested_container].empty? &&
|
@cask.artifacts[:nested_container].empty? &&
|
||||||
extract_nested_container(nested_container)
|
extract_nested_container(nested_container)
|
||||||
|
|
||||||
children.each do |src|
|
children.each do |src|
|
||||||
dest = @cask.staged_path.join(src.basename)
|
dest = @cask.staged_path.join(src.basename)
|
||||||
FileUtils.rm_r(dest) if dest.exist?
|
FileUtils.rm_r(dest) if dest.exist?
|
||||||
FileUtils.mv(src, dest)
|
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
|
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
|
end
|
||||||
|
@ -2,17 +2,21 @@ require "tmpdir"
|
|||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Bzip2 < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^BZh}n)
|
class Bzip2 < Base
|
||||||
end
|
def self.me?(criteria)
|
||||||
|
criteria.magic_number(%r{^BZh}n)
|
||||||
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
Dir.mktmpdir do |unpack_dir|
|
Dir.mktmpdir do |unpack_dir|
|
||||||
@command.run!("/usr/bin/ditto", args: ["--", @path, 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)])
|
@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
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,25 +2,29 @@ require "tmpdir"
|
|||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Cab < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
cabextract = Hbc.homebrew_prefix.join("bin", "cabextract")
|
class Cab < Base
|
||||||
|
def self.me?(criteria)
|
||||||
|
cabextract = Hbc.homebrew_prefix.join("bin", "cabextract")
|
||||||
|
|
||||||
criteria.magic_number(%r{^MSCF}n) &&
|
criteria.magic_number(%r{^MSCF}n) &&
|
||||||
cabextract.exist? &&
|
cabextract.exist? &&
|
||||||
criteria.command.run(cabextract, args: ["-t", "--", criteria.path.to_s]).stderr.empty?
|
criteria.command.run(cabextract, args: ["-t", "--", criteria.path.to_s]).stderr.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
cabextract = Hbc.homebrew_prefix.join("bin", "cabextract")
|
cabextract = Hbc.homebrew_prefix.join("bin", "cabextract")
|
||||||
|
|
||||||
unless cabextract.exist?
|
unless cabextract.exist?
|
||||||
raise Hbc::CaskError, "Expected to find cabextract executable. Cask '#{@cask}' must add: depends_on formula: 'cabextract'"
|
raise CaskError, "Expected to find cabextract executable. Cask '#{@cask}' must add: depends_on formula: 'cabextract'"
|
||||||
end
|
end
|
||||||
|
|
||||||
Dir.mktmpdir do |unpack_dir|
|
Dir.mktmpdir do |unpack_dir|
|
||||||
@command.run!(cabextract, args: ["-d", unpack_dir, "--", @path])
|
@command.run!(cabextract, args: ["-d", unpack_dir, "--", @path])
|
||||||
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
|
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
class Hbc::Container::Criteria
|
module Hbc
|
||||||
attr_reader :path, :command
|
class Container
|
||||||
|
class Criteria
|
||||||
|
attr_reader :path, :command
|
||||||
|
|
||||||
def initialize(path, command)
|
def initialize(path, command)
|
||||||
@path = path
|
@path = path
|
||||||
@command = command
|
@command = command
|
||||||
end
|
end
|
||||||
|
|
||||||
def extension(regex)
|
def extension(regex)
|
||||||
path.extname.sub(%r{^\.}, "") =~ Regexp.new(regex.source, regex.options | Regexp::IGNORECASE)
|
path.extname.sub(%r{^\.}, "") =~ Regexp.new(regex.source, regex.options | Regexp::IGNORECASE)
|
||||||
end
|
end
|
||||||
|
|
||||||
def magic_number(regex)
|
def magic_number(regex)
|
||||||
# 262: length of the longest regex (currently: Hbc::Container::Tar)
|
# 262: length of the longest regex (currently: Hbc::Container::Tar)
|
||||||
@magic_number ||= File.open(@path, "rb") { |f| f.read(262) }
|
@magic_number ||= File.open(@path, "rb") { |f| f.read(262) }
|
||||||
@magic_number =~ regex
|
@magic_number =~ regex
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,123 +3,127 @@ require "tempfile"
|
|||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Dmg < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
!criteria.command.run("/usr/bin/hdiutil",
|
class Dmg < Base
|
||||||
# realpath is a failsafe against unusual filenames
|
def self.me?(criteria)
|
||||||
args: ["imageinfo", Pathname.new(criteria.path).realpath],
|
!criteria.command.run("/usr/bin/hdiutil",
|
||||||
print_stderr: false).stdout.empty?
|
# realpath is a failsafe against unusual filenames
|
||||||
end
|
args: ["imageinfo", Pathname.new(criteria.path).realpath],
|
||||||
|
print_stderr: false).stdout.empty?
|
||||||
|
end
|
||||||
|
|
||||||
attr_reader :mounts
|
attr_reader :mounts
|
||||||
def initialize(*args)
|
def initialize(*args)
|
||||||
super(*args)
|
super(*args)
|
||||||
@mounts = []
|
@mounts = []
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
mount!
|
mount!
|
||||||
assert_mounts_found
|
assert_mounts_found
|
||||||
extract_mounts
|
extract_mounts
|
||||||
ensure
|
ensure
|
||||||
eject!
|
eject!
|
||||||
end
|
end
|
||||||
|
|
||||||
def mount!
|
def mount!
|
||||||
plist = @command.run!("/usr/bin/hdiutil",
|
plist = @command.run!("/usr/bin/hdiutil",
|
||||||
# realpath is a failsafe against unusual filenames
|
# realpath is a failsafe against unusual filenames
|
||||||
args: %w[mount -plist -nobrowse -readonly -noidme -mountrandom /tmp] + [Pathname.new(@path).realpath],
|
args: %w[mount -plist -nobrowse -readonly -noidme -mountrandom /tmp] + [Pathname.new(@path).realpath],
|
||||||
input: %w[y])
|
input: %w[y])
|
||||||
.plist
|
.plist
|
||||||
@mounts = mounts_from_plist(plist)
|
@mounts = mounts_from_plist(plist)
|
||||||
end
|
end
|
||||||
|
|
||||||
def eject!
|
def eject!
|
||||||
@mounts.each do |mount|
|
@mounts.each do |mount|
|
||||||
# realpath is a failsafe against unusual filenames
|
# realpath is a failsafe against unusual filenames
|
||||||
mountpath = Pathname.new(mount).realpath
|
mountpath = Pathname.new(mount).realpath
|
||||||
next unless mountpath.exist?
|
next unless mountpath.exist?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
tries ||= 2
|
tries ||= 2
|
||||||
@command.run("/usr/sbin/diskutil",
|
@command.run("/usr/sbin/diskutil",
|
||||||
args: ["eject", mountpath],
|
args: ["eject", mountpath],
|
||||||
print_stderr: false)
|
print_stderr: false)
|
||||||
|
|
||||||
raise Hbc::CaskError, "Failed to eject #{mountpath}" if mountpath.exist?
|
raise CaskError, "Failed to eject #{mountpath}" if mountpath.exist?
|
||||||
rescue Hbc::CaskError => e
|
rescue CaskError => e
|
||||||
raise e if (tries -= 1).zero?
|
raise e if (tries -= 1).zero?
|
||||||
sleep 1
|
sleep 1
|
||||||
retry
|
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
|
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
|
end
|
||||||
|
@ -2,25 +2,29 @@ require "tmpdir"
|
|||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::GenericUnar < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
lsar = Hbc.homebrew_prefix.join("bin", "lsar")
|
class GenericUnar < Base
|
||||||
lsar.exist? &&
|
def self.me?(criteria)
|
||||||
criteria.command.run(lsar,
|
lsar = Hbc.homebrew_prefix.join("bin", "lsar")
|
||||||
args: ["-l", "-t", "--", criteria.path],
|
lsar.exist? &&
|
||||||
print_stderr: false).stdout.chomp.end_with?("passed, 0 failed.")
|
criteria.command.run(lsar,
|
||||||
end
|
args: ["-l", "-t", "--", criteria.path],
|
||||||
|
print_stderr: false).stdout.chomp.end_with?("passed, 0 failed.")
|
||||||
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
unar = Hbc.homebrew_prefix.join("bin", "unar")
|
unar = Hbc.homebrew_prefix.join("bin", "unar")
|
||||||
|
|
||||||
unless unar.exist?
|
unless unar.exist?
|
||||||
raise Hbc::CaskError, "Expected to find unar executable. Cask #{@cask} must add: depends_on formula: 'unar'"
|
raise CaskError, "Expected to find unar executable. Cask #{@cask} must add: depends_on formula: 'unar'"
|
||||||
end
|
end
|
||||||
|
|
||||||
Dir.mktmpdir do |unpack_dir|
|
Dir.mktmpdir do |unpack_dir|
|
||||||
@command.run!(unar, args: ["-force-overwrite", "-quiet", "-no-directory", "-output-directory", unpack_dir, "--", @path])
|
@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])
|
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,17 +2,21 @@ require "tmpdir"
|
|||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Gzip < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^\037\213}n)
|
class Gzip < Base
|
||||||
end
|
def self.me?(criteria)
|
||||||
|
criteria.magic_number(%r{^\037\213}n)
|
||||||
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
Dir.mktmpdir do |unpack_dir|
|
Dir.mktmpdir do |unpack_dir|
|
||||||
@command.run!("/usr/bin/ditto", args: ["--", @path, 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)])
|
@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
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,22 +2,26 @@ require "tmpdir"
|
|||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Lzma < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^\]\000\000\200\000}n)
|
class Lzma < Base
|
||||||
end
|
def self.me?(criteria)
|
||||||
|
criteria.magic_number(%r{^\]\000\000\200\000}n)
|
||||||
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
unlzma = Hbc.homebrew_prefix.join("bin", "unlzma")
|
unlzma = Hbc.homebrew_prefix.join("bin", "unlzma")
|
||||||
|
|
||||||
unless unlzma.exist?
|
unless unlzma.exist?
|
||||||
raise Hbc::CaskError, "Expected to find unlzma executable. Cask '#{@cask}' must add: depends_on formula: 'lzma'"
|
raise CaskError, "Expected to find unlzma executable. Cask '#{@cask}' must add: depends_on formula: 'lzma'"
|
||||||
end
|
end
|
||||||
|
|
||||||
Dir.mktmpdir do |unpack_dir|
|
Dir.mktmpdir do |unpack_dir|
|
||||||
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
|
||||||
@command.run!(unlzma, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
|
@command.run!(unlzma, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
|
||||||
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
|
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Naked < Hbc::Container::Base
|
module Hbc
|
||||||
# Either inherit from this class and override with self.me?(criteria),
|
class Container
|
||||||
# or use this class directly as "container type: :naked",
|
class Naked < Base
|
||||||
# in which case self.me? is not called.
|
# Either inherit from this class and override with self.me?(criteria),
|
||||||
def self.me?(*)
|
# or use this class directly as "container type: :naked",
|
||||||
false
|
# in which case self.me? is not called.
|
||||||
end
|
def self.me?(*)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
@command.run!("/usr/bin/ditto", args: ["--", @path, @cask.staged_path.join(target_file)])
|
@command.run!("/usr/bin/ditto", args: ["--", @path, @cask.staged_path.join(target_file)])
|
||||||
end
|
end
|
||||||
|
|
||||||
def target_file
|
def target_file
|
||||||
return @path.basename if @nested
|
return @path.basename if @nested
|
||||||
URI.decode(File.basename(@cask.url.path))
|
URI.decode(File.basename(@cask.url.path))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
require "hbc/container/naked"
|
require "hbc/container/naked"
|
||||||
|
|
||||||
class Hbc::Container::Otf < Hbc::Container::Naked
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^OTTO}n)
|
class Otf < Naked
|
||||||
|
def self.me?(criteria)
|
||||||
|
criteria.magic_number(%r{^OTTO}n)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
require "hbc/container/naked"
|
require "hbc/container/naked"
|
||||||
|
|
||||||
class Hbc::Container::Pkg < Hbc::Container::Naked
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.extension(%r{m?pkg$}) &&
|
class Pkg < Naked
|
||||||
(criteria.path.directory? ||
|
def self.me?(criteria)
|
||||||
criteria.magic_number(%r{^xar!}n))
|
criteria.extension(%r{m?pkg$}) &&
|
||||||
|
(criteria.path.directory? ||
|
||||||
|
criteria.magic_number(%r{^xar!}n))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
require "hbc/container/generic_unar"
|
require "hbc/container/generic_unar"
|
||||||
|
|
||||||
class Hbc::Container::Rar < Hbc::Container::GenericUnar
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^Rar!}n) &&
|
class Rar < GenericUnar
|
||||||
super
|
def self.me?(criteria)
|
||||||
|
criteria.magic_number(%r{^Rar!}n) &&
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
require "hbc/container/generic_unar"
|
require "hbc/container/generic_unar"
|
||||||
|
|
||||||
class Hbc::Container::SevenZip < Hbc::Container::GenericUnar
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
# TODO: cover self-extracting archives
|
class SevenZip < GenericUnar
|
||||||
criteria.magic_number(%r{^7z}n) &&
|
def self.me?(criteria)
|
||||||
super
|
# TODO: cover self-extracting archives
|
||||||
|
criteria.magic_number(%r{^7z}n) &&
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
require "hbc/container/generic_unar"
|
require "hbc/container/generic_unar"
|
||||||
|
|
||||||
class Hbc::Container::Sit < Hbc::Container::GenericUnar
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^StuffIt}n) &&
|
class Sit < GenericUnar
|
||||||
super
|
def self.me?(criteria)
|
||||||
|
criteria.magic_number(%r{^StuffIt}n) &&
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,17 +2,21 @@ require "tmpdir"
|
|||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Tar < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^.{257}ustar}n) ||
|
class Tar < Base
|
||||||
# or compressed tar (bzip2/gzip/lzma/xz)
|
def self.me?(criteria)
|
||||||
IO.popen(["/usr/bin/tar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| !io.read(1).nil? }
|
criteria.magic_number(%r{^.{257}ustar}n) ||
|
||||||
end
|
# 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
|
def extract
|
||||||
Dir.mktmpdir do |unpack_dir|
|
Dir.mktmpdir do |unpack_dir|
|
||||||
@command.run!("/usr/bin/tar", args: ["-x", "-f", @path, "-C", 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])
|
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
require "hbc/container/naked"
|
require "hbc/container/naked"
|
||||||
|
|
||||||
class Hbc::Container::Ttf < Hbc::Container::Naked
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
# TrueType Font
|
class Ttf < Naked
|
||||||
criteria.magic_number(%r{^\000\001\000\000\000}n) ||
|
def self.me?(criteria)
|
||||||
# Truetype Font Collection
|
# TrueType Font
|
||||||
criteria.magic_number(%r{^ttcf}n)
|
criteria.magic_number(%r{^\000\001\000\000\000}n) ||
|
||||||
|
# Truetype Font Collection
|
||||||
|
criteria.magic_number(%r{^ttcf}n)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,15 +2,19 @@ require "tmpdir"
|
|||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Xar < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^xar!}n)
|
class Xar < Base
|
||||||
end
|
def self.me?(criteria)
|
||||||
|
criteria.magic_number(%r{^xar!}n)
|
||||||
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
Dir.mktmpdir do |unpack_dir|
|
Dir.mktmpdir do |unpack_dir|
|
||||||
@command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "-C", 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])
|
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
require "tmpdir"
|
require "tmpdir"
|
||||||
|
|
||||||
class Hbc::Container::Xip < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^xar!}n) &&
|
class Xip < Base
|
||||||
IO.popen(["/usr/bin/xar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| io.read =~ %r{\AContent\nMetadata\n\Z} }
|
def self.me?(criteria)
|
||||||
end
|
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} }
|
||||||
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
|
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/xar", args: ["-x", "-f", @path, "Content", "-C", unpack_dir])
|
||||||
@command.run!("/usr/bin/cpio", args: ["--quiet", "-i", "-I", Pathname(unpack_dir).join("Content")])
|
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,22 +2,26 @@ require "tmpdir"
|
|||||||
|
|
||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Xz < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^\xFD7zXZ\x00}n)
|
class Xz < Base
|
||||||
end
|
def self.me?(criteria)
|
||||||
|
criteria.magic_number(%r{^\xFD7zXZ\x00}n)
|
||||||
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
unxz = Hbc.homebrew_prefix.join("bin", "unxz")
|
unxz = Hbc.homebrew_prefix.join("bin", "unxz")
|
||||||
|
|
||||||
unless unxz.exist?
|
unless unxz.exist?
|
||||||
raise Hbc::CaskError, "Expected to find unxz executable. Cask '#{@cask}' must add: depends_on formula: 'xz'"
|
raise CaskError, "Expected to find unxz executable. Cask '#{@cask}' must add: depends_on formula: 'xz'"
|
||||||
end
|
end
|
||||||
|
|
||||||
Dir.mktmpdir do |unpack_dir|
|
Dir.mktmpdir do |unpack_dir|
|
||||||
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
|
||||||
@command.run!(unxz, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
|
@command.run!(unxz, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
|
||||||
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
|
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
require "hbc/container/base"
|
require "hbc/container/base"
|
||||||
|
|
||||||
class Hbc::Container::Zip < Hbc::Container::Base
|
module Hbc
|
||||||
def self.me?(criteria)
|
class Container
|
||||||
criteria.magic_number(%r{^PK(\003\004|\005\006)}n)
|
class Zip < Base
|
||||||
end
|
def self.me?(criteria)
|
||||||
|
criteria.magic_number(%r{^PK(\003\004|\005\006)}n)
|
||||||
|
end
|
||||||
|
|
||||||
def extract
|
def extract
|
||||||
Dir.mktmpdir do |unpack_dir|
|
Dir.mktmpdir do |unpack_dir|
|
||||||
@command.run!("/usr/bin/ditto", args: ["-x", "-k", "--", @path, 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
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,43 +1,45 @@
|
|||||||
require "fileutils"
|
require "fileutils"
|
||||||
require "hbc/verify"
|
require "hbc/verify"
|
||||||
|
|
||||||
class Hbc::Download
|
module Hbc
|
||||||
attr_reader :cask
|
class Download
|
||||||
|
attr_reader :cask
|
||||||
|
|
||||||
def initialize(cask, force: false)
|
def initialize(cask, force: false)
|
||||||
@cask = cask
|
@cask = cask
|
||||||
@force = force
|
@force = force
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
clear_cache
|
clear_cache
|
||||||
fetch
|
fetch
|
||||||
downloaded_path
|
downloaded_path
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :force
|
attr_reader :force
|
||||||
attr_accessor :downloaded_path
|
attr_accessor :downloaded_path
|
||||||
|
|
||||||
def downloader
|
def downloader
|
||||||
@downloader ||= case cask.url.using
|
@downloader ||= case cask.url.using
|
||||||
when :svn
|
when :svn
|
||||||
Hbc::SubversionDownloadStrategy.new(cask)
|
SubversionDownloadStrategy.new(cask)
|
||||||
when :post
|
when :post
|
||||||
Hbc::CurlPostDownloadStrategy.new(cask)
|
CurlPostDownloadStrategy.new(cask)
|
||||||
else
|
else
|
||||||
Hbc::CurlDownloadStrategy.new(cask)
|
CurlDownloadStrategy.new(cask)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_cache
|
def clear_cache
|
||||||
downloader.clear_cache if force || cask.version.latest?
|
downloader.clear_cache if force || cask.version.latest?
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch
|
def fetch
|
||||||
self.downloaded_path = downloader.fetch
|
self.downloaded_path = downloader.fetch
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
raise Hbc::CaskError, "Download failed on Cask '#{cask}' with message: #{e}"
|
raise CaskError, "Download failed on Cask '#{cask}' with message: #{e}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -6,327 +6,329 @@ require "cgi"
|
|||||||
# * Our overridden fetch methods are expected to return
|
# * Our overridden fetch methods are expected to return
|
||||||
# a value: the successfully downloaded file.
|
# a value: the successfully downloaded file.
|
||||||
|
|
||||||
class Hbc::AbstractDownloadStrategy
|
module Hbc
|
||||||
attr_reader :cask, :name, :url, :uri_object, :version
|
class AbstractDownloadStrategy
|
||||||
|
attr_reader :cask, :name, :url, :uri_object, :version
|
||||||
|
|
||||||
def initialize(cask, command = Hbc::SystemCommand)
|
def initialize(cask, command = SystemCommand)
|
||||||
@cask = cask
|
@cask = cask
|
||||||
@command = command
|
@command = command
|
||||||
# TODO: this excess of attributes is a function of integrating
|
# TODO: this excess of attributes is a function of integrating
|
||||||
# with Homebrew's classes. Later we should be able to remove
|
# with Homebrew's classes. Later we should be able to remove
|
||||||
# these in favor of @cask
|
# these in favor of @cask
|
||||||
@name = cask.token
|
@name = cask.token
|
||||||
@url = cask.url.to_s
|
@url = cask.url.to_s
|
||||||
@uri_object = cask.url
|
@uri_object = cask.url
|
||||||
@version = cask.version
|
@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
|
end
|
||||||
|
|
||||||
# All download strategies are expected to implement these methods
|
class HbVCSDownloadStrategy < AbstractDownloadStrategy
|
||||||
def fetch; end
|
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
|
def extract_ref
|
||||||
end
|
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
|
def cache_filename
|
||||||
REF_TYPES = [:branch, :revision, :revisions, :tag].freeze
|
"#{name}--#{cache_tag}"
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(cask, command = Hbc::SystemCommand)
|
def cache_tag
|
||||||
super
|
"__UNKNOWN__"
|
||||||
@ref_type, @ref = extract_ref
|
end
|
||||||
@clone = Hbc.cache.join(cache_filename)
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_ref
|
def cached_location
|
||||||
key = REF_TYPES.find { |type|
|
@clone
|
||||||
uri_object.respond_to?(type) && uri_object.send(type)
|
end
|
||||||
}
|
|
||||||
[key, key ? uri_object.send(key) : nil]
|
|
||||||
end
|
|
||||||
|
|
||||||
def cache_filename
|
def clear_cache
|
||||||
"#{name}--#{cache_tag}"
|
cached_location.rmtree if cached_location.exist?
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def downloaded_size
|
class CurlDownloadStrategy < AbstractDownloadStrategy
|
||||||
temporary_path.size? || 0
|
# TODO: should be part of url object
|
||||||
end
|
def mirrors
|
||||||
|
@mirrors ||= []
|
||||||
|
end
|
||||||
|
|
||||||
def _fetch
|
def tarball_path
|
||||||
odebug "Calling curl with args #{cask_curl_args.utf8_inspect}"
|
@tarball_path ||= Hbc.cache.join("#{name}--#{version}#{ext}")
|
||||||
curl(*cask_curl_args)
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def fetch
|
def temporary_path
|
||||||
ohai "Downloading #{@url}"
|
@temporary_path ||= tarball_path.sub(%r{$}, ".incomplete")
|
||||||
if tarball_path.exist?
|
end
|
||||||
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
|
def cached_location
|
||||||
msg.concat("\nThe incomplete download is cached at #{temporary_path}") if temporary_path.exist?
|
tarball_path
|
||||||
raise CurlDownloadStrategyError, msg
|
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
|
end
|
||||||
ignore_interrupts { temporary_path.rename(tarball_path) }
|
|
||||||
end
|
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
|
def _fetch
|
||||||
default_curl_args.tap do |args|
|
odebug "Calling curl with args #{cask_curl_args.utf8_inspect}"
|
||||||
args.concat(user_agent_args)
|
curl(*cask_curl_args)
|
||||||
args.concat(cookies_args)
|
end
|
||||||
args.concat(referer_args)
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_curl_args
|
class CurlPostDownloadStrategy < CurlDownloadStrategy
|
||||||
[url, "-C", downloaded_size, "-o", temporary_path]
|
def cask_curl_args
|
||||||
end
|
super
|
||||||
|
default_curl_args.concat(post_args)
|
||||||
def user_agent_args
|
|
||||||
if uri_object.user_agent
|
|
||||||
["-A", uri_object.user_agent]
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def cookies_args
|
def post_args
|
||||||
if uri_object.cookies
|
if uri_object.data
|
||||||
[
|
|
||||||
"-b",
|
|
||||||
# sort_by is for predictability between Ruby versions
|
# sort_by is for predictability between Ruby versions
|
||||||
uri_object
|
uri_object
|
||||||
.cookies
|
.data
|
||||||
.sort_by(&:to_s)
|
.sort_by(&:to_s)
|
||||||
.map { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }
|
.map { |key, value| ["-d", "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"] }
|
||||||
.join(";"),
|
.flatten
|
||||||
]
|
|
||||||
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
|
|
||||||
else
|
else
|
||||||
fetch_repo @clone, @url
|
["-X", "POST"]
|
||||||
end
|
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
|
||||||
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
|
def repo_valid?
|
||||||
# against challenges such as case-sensitive filesystems,
|
@clone.join(".svn").directory?
|
||||||
# and must be re-implemented.
|
end
|
||||||
#
|
|
||||||
# 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
|
def repo_url
|
||||||
Dir.chdir(cached_location) do
|
`svn info '#{@clone}' 2>/dev/null`.strip[%r{^URL: (.+)$}, 1]
|
||||||
@command.run!("/usr/bin/tar",
|
end
|
||||||
args: ['-s/^\.//', "--exclude", ".svn", "-cf", Pathname.new(tarball_path), "--", "."],
|
|
||||||
|
# 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)
|
print_stderr: false)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
require "set"
|
require "set"
|
||||||
|
|
||||||
class Hbc::DSL; end
|
|
||||||
|
|
||||||
require "hbc/dsl/appcast"
|
require "hbc/dsl/appcast"
|
||||||
require "hbc/dsl/base"
|
require "hbc/dsl/base"
|
||||||
require "hbc/dsl/caveats"
|
require "hbc/dsl/caveats"
|
||||||
@ -18,274 +16,276 @@ require "hbc/dsl/uninstall_postflight"
|
|||||||
require "hbc/dsl/uninstall_preflight"
|
require "hbc/dsl/uninstall_preflight"
|
||||||
require "hbc/dsl/version"
|
require "hbc/dsl/version"
|
||||||
|
|
||||||
class Hbc::DSL
|
module Hbc
|
||||||
ORDINARY_ARTIFACT_TYPES = [
|
class DSL
|
||||||
:app,
|
ORDINARY_ARTIFACT_TYPES = [
|
||||||
:artifact,
|
:app,
|
||||||
:audio_unit_plugin,
|
:artifact,
|
||||||
:binary,
|
:audio_unit_plugin,
|
||||||
:colorpicker,
|
:binary,
|
||||||
:font,
|
:colorpicker,
|
||||||
:input_method,
|
:font,
|
||||||
:internet_plugin,
|
:input_method,
|
||||||
:pkg,
|
:internet_plugin,
|
||||||
:prefpane,
|
:pkg,
|
||||||
:qlplugin,
|
:prefpane,
|
||||||
:screen_saver,
|
:qlplugin,
|
||||||
:service,
|
:screen_saver,
|
||||||
:stage_only,
|
:service,
|
||||||
:suite,
|
:stage_only,
|
||||||
:vst_plugin,
|
:suite,
|
||||||
:vst3_plugin,
|
:vst_plugin,
|
||||||
].freeze
|
: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 = [
|
SPECIAL_ARTIFACT_TYPES = [
|
||||||
:uninstall,
|
:uninstall,
|
||||||
:zap,
|
:zap,
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
ARTIFACT_BLOCK_TYPES = [
|
||||||
|
:preflight,
|
||||||
|
:postflight,
|
||||||
|
:uninstall_preflight,
|
||||||
|
:uninstall_postflight,
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
ARTIFACT_BLOCK_TYPES = [
|
DSL_METHODS = Set.new [
|
||||||
:preflight,
|
:accessibility_access,
|
||||||
:postflight,
|
:appcast,
|
||||||
:uninstall_preflight,
|
:artifacts,
|
||||||
:uninstall_postflight,
|
:auto_updates,
|
||||||
].freeze
|
: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 [
|
attr_reader :token
|
||||||
:accessibility_access,
|
def initialize(token)
|
||||||
:appcast,
|
@token = token
|
||||||
: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}")
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def appcast(*args)
|
def name(*args)
|
||||||
return @appcast if args.empty?
|
@name ||= []
|
||||||
assert_only_one_stanza_allowed :appcast, !args.empty?
|
return @name if args.empty?
|
||||||
@appcast ||= begin
|
@name.concat(args.flatten)
|
||||||
Hbc::DSL::Appcast.new(*args) unless args.empty?
|
|
||||||
rescue StandardError => e
|
|
||||||
raise Hbc::CaskInvalidError.new(token, e)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def gpg(*args)
|
def assert_only_one_stanza_allowed(stanza, arg_given)
|
||||||
return @gpg if args.empty?
|
return unless instance_variable_defined?("@#{stanza}") && arg_given
|
||||||
assert_only_one_stanza_allowed :gpg, !args.empty?
|
raise CaskInvalidError.new(token, "'#{stanza}' stanza may only appear once")
|
||||||
@gpg ||= begin
|
|
||||||
Hbc::DSL::Gpg.new(*args) unless args.empty?
|
|
||||||
rescue StandardError => e
|
|
||||||
raise Hbc::CaskInvalidError.new(token, e)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def container(*args)
|
def homepage(homepage = nil)
|
||||||
return @container if args.empty?
|
assert_only_one_stanza_allowed :homepage, !homepage.nil?
|
||||||
# TODO: remove this constraint, and instead merge multiple container stanzas
|
@homepage ||= homepage
|
||||||
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)
|
|
||||||
end
|
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 [
|
def url(*args, &block)
|
||||||
:latest,
|
url_given = !args.empty? || block_given?
|
||||||
]
|
return @url unless url_given
|
||||||
|
assert_only_one_stanza_allowed :url, url_given
|
||||||
def version(arg = nil)
|
@url ||= begin
|
||||||
return @version if arg.nil?
|
URL.from(*args, &block)
|
||||||
assert_only_one_stanza_allowed :version, !arg.nil?
|
rescue StandardError => e
|
||||||
raise Hbc::CaskInvalidError.new(token, "invalid 'version' value: '#{arg.inspect}'") if !arg.is_a?(String) && !SYMBOLIC_VERSIONS.include?(arg)
|
raise CaskInvalidError.new(token, "'url' stanza failed with: #{e}")
|
||||||
@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")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def installer(*args)
|
def appcast(*args)
|
||||||
return artifacts[:installer] if args.empty?
|
return @appcast if args.empty?
|
||||||
artifacts[:installer] << Hbc::DSL::Installer.new(*args)
|
assert_only_one_stanza_allowed :appcast, !args.empty?
|
||||||
raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only)
|
@appcast ||= begin
|
||||||
rescue StandardError => e
|
DSL::Appcast.new(*args) unless args.empty?
|
||||||
raise Hbc::CaskInvalidError.new(token, e)
|
rescue StandardError => e
|
||||||
end
|
raise CaskInvalidError.new(token, e)
|
||||||
|
end
|
||||||
SPECIAL_ARTIFACT_TYPES.each do |type|
|
|
||||||
define_method(type) do |*args|
|
|
||||||
artifacts[type].merge(args)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
ARTIFACT_BLOCK_TYPES.each do |type|
|
def gpg(*args)
|
||||||
define_method(type) do |&block|
|
return @gpg if args.empty?
|
||||||
artifacts[type] << block
|
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
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(method, *)
|
def container(*args)
|
||||||
if method
|
return @container if args.empty?
|
||||||
Hbc::Utils.method_missing_message(method, token)
|
# TODO: remove this constraint, and instead merge multiple container stanzas
|
||||||
nil
|
assert_only_one_stanza_allowed :container, !args.empty?
|
||||||
else
|
@container ||= begin
|
||||||
super
|
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
|
||||||
end
|
|
||||||
|
|
||||||
def respond_to_missing?(*)
|
SYMBOLIC_VERSIONS = Set.new [
|
||||||
true
|
:latest,
|
||||||
end
|
]
|
||||||
|
|
||||||
def appdir
|
def version(arg = nil)
|
||||||
self.class.appdir
|
return @version if arg.nil?
|
||||||
end
|
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
|
SYMBOLIC_SHA256S = Set.new [
|
||||||
Hbc.appdir.sub(%r{\/$}, "")
|
: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
|
||||||
end
|
end
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
class Hbc::DSL::Appcast
|
module Hbc
|
||||||
attr_reader :parameters, :checkpoint
|
class DSL
|
||||||
|
class Appcast
|
||||||
|
attr_reader :parameters, :checkpoint
|
||||||
|
|
||||||
def initialize(uri, parameters = {})
|
def initialize(uri, parameters = {})
|
||||||
@parameters = parameters
|
@parameters = parameters
|
||||||
@uri = Hbc::UnderscoreSupportingURI.parse(uri)
|
@uri = UnderscoreSupportingURI.parse(uri)
|
||||||
@checkpoint = @parameters[:checkpoint]
|
@checkpoint = @parameters[:checkpoint]
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_yaml
|
def to_yaml
|
||||||
[@uri, @parameters].to_yaml
|
[@uri, @parameters].to_yaml
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
@uri.to_s
|
@uri.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,29 +1,33 @@
|
|||||||
class Hbc::DSL::Base
|
module Hbc
|
||||||
extend Forwardable
|
class DSL
|
||||||
|
class Base
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
def initialize(cask, command = Hbc::SystemCommand)
|
def initialize(cask, command = SystemCommand)
|
||||||
@cask = cask
|
@cask = cask
|
||||||
@command = command
|
@command = command
|
||||||
end
|
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 = {})
|
def system_command(executable, options = {})
|
||||||
@command.run!(executable, options)
|
@command.run!(executable, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def method_missing(method, *)
|
def method_missing(method, *)
|
||||||
if method
|
if method
|
||||||
underscored_class = self.class.name.gsub(%r{([[:lower:]])([[:upper:]][[:lower:]])}, '\1_\2').downcase
|
underscored_class = self.class.name.gsub(%r{([[:lower:]])([[:upper:]][[:lower:]])}, '\1_\2').downcase
|
||||||
section = underscored_class.downcase.split("::").last
|
section = underscored_class.downcase.split("::").last
|
||||||
Hbc::Utils.method_missing_message(method, @cask.to_s, section)
|
Utils.method_missing_message(method, @cask.to_s, section)
|
||||||
nil
|
nil
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to_missing?(*)
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def respond_to_missing?(*)
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -5,108 +5,112 @@
|
|||||||
# ( The return value of the last method in the block is also sent
|
# ( 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
|
# to the output by the caller, but that feature is only for the
|
||||||
# convenience of Cask authors. )
|
# convenience of Cask authors. )
|
||||||
class Hbc::DSL::Caveats < Hbc::DSL::Base
|
module Hbc
|
||||||
def path_environment_variable(path)
|
class DSL
|
||||||
puts <<-EOS.undent
|
class Caveats < Base
|
||||||
To use #{@cask}, you may need to add the #{path} directory
|
def path_environment_variable(path)
|
||||||
to your PATH environment variable, eg (for bash shell):
|
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
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
def zsh_path_helper(path)
|
def zsh_path_helper(path)
|
||||||
puts <<-EOS.undent
|
puts <<-EOS.undent
|
||||||
To use #{@cask}, zsh users may need to add the following line to their
|
To use #{@cask}, zsh users may need to add the following line to their
|
||||||
~/.zprofile. (Among other effects, #{path} will be added to the
|
~/.zprofile. (Among other effects, #{path} will be added to the
|
||||||
PATH environment variable):
|
PATH environment variable):
|
||||||
|
|
||||||
eval `/usr/libexec/path_helper -s`
|
eval `/usr/libexec/path_helper -s`
|
||||||
|
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
def files_in_usr_local
|
def files_in_usr_local
|
||||||
localpath = "/usr/local"
|
localpath = "/usr/local"
|
||||||
return unless Hbc.homebrew_prefix.to_s.downcase.start_with?(localpath)
|
return unless Hbc.homebrew_prefix.to_s.downcase.start_with?(localpath)
|
||||||
puts <<-EOS.undent
|
puts <<-EOS.undent
|
||||||
Cask #{@cask} installs files under "#{localpath}". The presence of such
|
Cask #{@cask} installs files under "#{localpath}". The presence of such
|
||||||
files can cause warnings when running "brew doctor", which is considered
|
files can cause warnings when running "brew doctor", which is considered
|
||||||
to be a bug in Homebrew-Cask.
|
to be a bug in Homebrew-Cask.
|
||||||
|
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
def depends_on_java(java_version = "any")
|
def depends_on_java(java_version = "any")
|
||||||
if java_version == "any"
|
if java_version == "any"
|
||||||
puts <<-EOS.undent
|
puts <<-EOS.undent
|
||||||
#{@cask} requires Java. You can install the latest version with
|
#{@cask} requires Java. You can install the latest version with
|
||||||
|
|
||||||
brew cask install java
|
brew cask install java
|
||||||
|
|
||||||
EOS
|
EOS
|
||||||
elsif java_version.include?("8") || java_version.include?("+")
|
elsif java_version.include?("8") || java_version.include?("+")
|
||||||
puts <<-EOS.undent
|
puts <<-EOS.undent
|
||||||
#{@cask} requires Java #{java_version}. You can install the latest version with
|
#{@cask} requires Java #{java_version}. You can install the latest version with
|
||||||
|
|
||||||
brew cask install java
|
brew cask install java
|
||||||
|
|
||||||
EOS
|
EOS
|
||||||
else
|
else
|
||||||
puts <<-EOS.undent
|
puts <<-EOS.undent
|
||||||
#{@cask} requires Java #{java_version}. You can install it with
|
#{@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
|
||||||
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
|
||||||
|
@ -1,30 +1,34 @@
|
|||||||
class Hbc::DSL::ConflictsWith
|
module Hbc
|
||||||
VALID_KEYS = Set.new [
|
class DSL
|
||||||
:formula,
|
class ConflictsWith
|
||||||
:cask,
|
VALID_KEYS = Set.new [
|
||||||
:macos,
|
:formula,
|
||||||
:arch,
|
:cask,
|
||||||
:x11,
|
:macos,
|
||||||
:java,
|
:arch,
|
||||||
]
|
:x11,
|
||||||
|
:java,
|
||||||
|
]
|
||||||
|
|
||||||
attr_accessor(*VALID_KEYS)
|
attr_accessor(*VALID_KEYS)
|
||||||
attr_accessor :pairs
|
attr_accessor :pairs
|
||||||
|
|
||||||
def initialize(pairs = {})
|
def initialize(pairs = {})
|
||||||
@pairs = pairs
|
@pairs = pairs
|
||||||
pairs.each do |key, value|
|
pairs.each do |key, value|
|
||||||
raise "invalid conflicts_with key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
|
raise "invalid conflicts_with key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
|
||||||
writer_method = "#{key}=".to_sym
|
writer_method = "#{key}=".to_sym
|
||||||
send(writer_method, value)
|
send(writer_method, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_yaml
|
||||||
|
@pairs.to_yaml
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
@pairs.inspect
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_yaml
|
|
||||||
@pairs.to_yaml
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
@pairs.inspect
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,26 +1,30 @@
|
|||||||
class Hbc::DSL::Container
|
module Hbc
|
||||||
VALID_KEYS = Set.new [
|
class DSL
|
||||||
:type,
|
class Container
|
||||||
:nested,
|
VALID_KEYS = Set.new [
|
||||||
]
|
:type,
|
||||||
|
:nested,
|
||||||
|
]
|
||||||
|
|
||||||
attr_accessor(*VALID_KEYS)
|
attr_accessor(*VALID_KEYS)
|
||||||
attr_accessor :pairs
|
attr_accessor :pairs
|
||||||
|
|
||||||
def initialize(pairs = {})
|
def initialize(pairs = {})
|
||||||
@pairs = pairs
|
@pairs = pairs
|
||||||
pairs.each do |key, value|
|
pairs.each do |key, value|
|
||||||
raise "invalid container key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
|
raise "invalid container key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
|
||||||
writer_method = "#{key}=".to_sym
|
writer_method = "#{key}=".to_sym
|
||||||
send(writer_method, value)
|
send(writer_method, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_yaml
|
||||||
|
@pairs.to_yaml
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
@pairs.inspect
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_yaml
|
|
||||||
@pairs.to_yaml
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
@pairs.inspect
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,124 +1,128 @@
|
|||||||
require "rubygems"
|
require "rubygems"
|
||||||
|
|
||||||
class Hbc::DSL::DependsOn
|
module Hbc
|
||||||
VALID_KEYS = Set.new [
|
class DSL
|
||||||
:formula,
|
class DependsOn
|
||||||
:cask,
|
VALID_KEYS = Set.new [
|
||||||
:macos,
|
:formula,
|
||||||
:arch,
|
:cask,
|
||||||
:x11,
|
:macos,
|
||||||
:java,
|
:arch,
|
||||||
].freeze
|
:x11,
|
||||||
|
:java,
|
||||||
|
].freeze
|
||||||
|
|
||||||
VALID_ARCHES = {
|
VALID_ARCHES = {
|
||||||
intel: { type: :intel, bits: [32, 64] },
|
intel: { type: :intel, bits: [32, 64] },
|
||||||
ppc: { type: :ppc, bits: [32, 64] },
|
ppc: { type: :ppc, bits: [32, 64] },
|
||||||
# specific
|
# specific
|
||||||
i386: { type: :intel, bits: 32 },
|
i386: { type: :intel, bits: 32 },
|
||||||
x86_64: { type: :intel, bits: 64 },
|
x86_64: { type: :intel, bits: 64 },
|
||||||
ppc_7400: { type: :ppc, bits: 32 },
|
ppc_7400: { type: :ppc, bits: 32 },
|
||||||
ppc_64: { type: :ppc, bits: 64 },
|
ppc_64: { type: :ppc, bits: 64 },
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
# Intentionally undocumented: catch variant spellings.
|
# Intentionally undocumented: catch variant spellings.
|
||||||
ARCH_SYNONYMS = {
|
ARCH_SYNONYMS = {
|
||||||
x86_32: :i386,
|
x86_32: :i386,
|
||||||
x8632: :i386,
|
x8632: :i386,
|
||||||
x8664: :x86_64,
|
x8664: :x86_64,
|
||||||
intel_32: :i386,
|
intel_32: :i386,
|
||||||
intel32: :i386,
|
intel32: :i386,
|
||||||
intel_64: :x86_64,
|
intel_64: :x86_64,
|
||||||
intel64: :x86_64,
|
intel64: :x86_64,
|
||||||
amd_64: :x86_64,
|
amd_64: :x86_64,
|
||||||
amd64: :x86_64,
|
amd64: :x86_64,
|
||||||
ppc7400: :ppc_7400,
|
ppc7400: :ppc_7400,
|
||||||
ppc_32: :ppc_7400,
|
ppc_32: :ppc_7400,
|
||||||
ppc32: :ppc_7400,
|
ppc32: :ppc_7400,
|
||||||
ppc64: :ppc_64,
|
ppc64: :ppc_64,
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
attr_accessor :java
|
attr_accessor :java
|
||||||
attr_accessor :pairs
|
attr_accessor :pairs
|
||||||
attr_reader :arch, :cask, :formula, :macos, :x11
|
attr_reader :arch, :cask, :formula, :macos, :x11
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@pairs ||= {}
|
@pairs ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def load(pairs = {})
|
def load(pairs = {})
|
||||||
pairs.each do |key, value|
|
pairs.each do |key, value|
|
||||||
raise "invalid depends_on key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
|
raise "invalid depends_on key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
|
||||||
writer_method = "#{key}=".to_sym
|
writer_method = "#{key}=".to_sym
|
||||||
@pairs[key] = send(writer_method, value)
|
@pairs[key] = send(writer_method, value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.coerce_os_release(arg)
|
def self.coerce_os_release(arg)
|
||||||
@macos_symbols ||= MacOS::Version::SYMBOLS
|
@macos_symbols ||= MacOS::Version::SYMBOLS
|
||||||
@inverted_macos_symbols ||= @macos_symbols.invert
|
@inverted_macos_symbols ||= @macos_symbols.invert
|
||||||
|
|
||||||
begin
|
begin
|
||||||
if arg.is_a?(Symbol)
|
if arg.is_a?(Symbol)
|
||||||
Gem::Version.new(@macos_symbols.fetch(arg))
|
Gem::Version.new(@macos_symbols.fetch(arg))
|
||||||
elsif arg =~ %r{^\s*:?([a-z]\S+)\s*$}i
|
elsif arg =~ %r{^\s*:?([a-z]\S+)\s*$}i
|
||||||
Gem::Version.new(@macos_symbols.fetch(Regexp.last_match[1].downcase.to_sym))
|
Gem::Version.new(@macos_symbols.fetch(Regexp.last_match[1].downcase.to_sym))
|
||||||
elsif @inverted_macos_symbols.key?(arg)
|
elsif @inverted_macos_symbols.key?(arg)
|
||||||
Gem::Version.new(arg)
|
Gem::Version.new(arg)
|
||||||
else
|
else
|
||||||
raise
|
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
|
end
|
||||||
rescue StandardError
|
|
||||||
raise "invalid 'depends_on macos' value: #{arg.inspect}"
|
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
@ -1,43 +1,47 @@
|
|||||||
class Hbc::DSL::Gpg
|
module Hbc
|
||||||
KEY_PARAMETERS = Set.new [
|
class DSL
|
||||||
:key_id,
|
class Gpg
|
||||||
:key_url,
|
KEY_PARAMETERS = Set.new [
|
||||||
]
|
:key_id,
|
||||||
|
:key_url,
|
||||||
|
]
|
||||||
|
|
||||||
VALID_PARAMETERS = Set.new []
|
VALID_PARAMETERS = Set.new []
|
||||||
VALID_PARAMETERS.merge KEY_PARAMETERS
|
VALID_PARAMETERS.merge KEY_PARAMETERS
|
||||||
|
|
||||||
attr_accessor(*VALID_PARAMETERS)
|
attr_accessor(*VALID_PARAMETERS)
|
||||||
attr_accessor :signature
|
attr_accessor :signature
|
||||||
|
|
||||||
def initialize(signature, parameters = {})
|
def initialize(signature, parameters = {})
|
||||||
@parameters = parameters
|
@parameters = parameters
|
||||||
@signature = Hbc::UnderscoreSupportingURI.parse(signature)
|
@signature = UnderscoreSupportingURI.parse(signature)
|
||||||
parameters.each do |hkey, hvalue|
|
parameters.each do |hkey, hvalue|
|
||||||
raise "invalid 'gpg' parameter: '#{hkey.inspect}'" unless VALID_PARAMETERS.include?(hkey)
|
raise "invalid 'gpg' parameter: '#{hkey.inspect}'" unless VALID_PARAMETERS.include?(hkey)
|
||||||
writer_method = "#{hkey}=".to_sym
|
writer_method = "#{hkey}=".to_sym
|
||||||
hvalue = Hbc::UnderscoreSupportingURI.parse(hvalue) if hkey == :key_url
|
hvalue = UnderscoreSupportingURI.parse(hvalue) if hkey == :key_url
|
||||||
valid_id?(hvalue) if hkey == :key_id
|
valid_id?(hvalue) if hkey == :key_id
|
||||||
send(writer_method, hvalue)
|
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
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,28 +1,32 @@
|
|||||||
class Hbc::DSL::Installer
|
module Hbc
|
||||||
VALID_KEYS = Set.new [
|
class DSL
|
||||||
:manual,
|
class Installer
|
||||||
:script,
|
VALID_KEYS = Set.new [
|
||||||
]
|
:manual,
|
||||||
|
:script,
|
||||||
|
]
|
||||||
|
|
||||||
attr_accessor(*VALID_KEYS)
|
attr_accessor(*VALID_KEYS)
|
||||||
|
|
||||||
def initialize(*parameters)
|
def initialize(*parameters)
|
||||||
raise Hbc::CaskInvalidError.new(token, "'installer' stanza requires an argument") if parameters.empty?
|
raise CaskInvalidError.new(token, "'installer' stanza requires an argument") if parameters.empty?
|
||||||
parameters = {}.merge(*parameters)
|
parameters = {}.merge(*parameters)
|
||||||
if parameters.key?(:script) && !parameters[:script].respond_to?(:key?)
|
if parameters.key?(:script) && !parameters[:script].respond_to?(:key?)
|
||||||
if parameters.key?(:executable)
|
if parameters.key?(:executable)
|
||||||
raise Hbc::CaskInvalidError.new(token, "'installer' stanza gave arguments for both :script and :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
|
end
|
||||||
parameters[:executable] = parameters[:script]
|
|
||||||
parameters.delete(:script)
|
|
||||||
parameters = { script: parameters }
|
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
@ -1,66 +1,70 @@
|
|||||||
class Hbc::DSL::License
|
module Hbc
|
||||||
# a generic category can always be given as a license, so
|
class DSL
|
||||||
# category names should be given as both key and value
|
class License
|
||||||
VALID_LICENSES = {
|
# a generic category can always be given as a license, so
|
||||||
# license category
|
# category names should be given as both key and value
|
||||||
unknown: :unknown,
|
VALID_LICENSES = {
|
||||||
|
# license category
|
||||||
|
unknown: :unknown,
|
||||||
|
|
||||||
other: :other,
|
other: :other,
|
||||||
|
|
||||||
closed: :closed,
|
closed: :closed,
|
||||||
commercial: :closed,
|
commercial: :closed,
|
||||||
gratis: :closed,
|
gratis: :closed,
|
||||||
freemium: :closed,
|
freemium: :closed,
|
||||||
|
|
||||||
oss: :oss,
|
oss: :oss,
|
||||||
affero: :oss,
|
affero: :oss,
|
||||||
apache: :oss,
|
apache: :oss,
|
||||||
arphic: :oss,
|
arphic: :oss,
|
||||||
artistic: :oss,
|
artistic: :oss,
|
||||||
bsd: :oss,
|
bsd: :oss,
|
||||||
cc: :oss,
|
cc: :oss,
|
||||||
eclipse: :oss,
|
eclipse: :oss,
|
||||||
gpl: :oss,
|
gpl: :oss,
|
||||||
isc: :oss,
|
isc: :oss,
|
||||||
lppl: :oss,
|
lppl: :oss,
|
||||||
ncsa: :oss,
|
ncsa: :oss,
|
||||||
mit: :oss,
|
mit: :oss,
|
||||||
mpl: :oss,
|
mpl: :oss,
|
||||||
ofl: :oss,
|
ofl: :oss,
|
||||||
public_domain: :oss,
|
public_domain: :oss,
|
||||||
ubuntu_font: :oss,
|
ubuntu_font: :oss,
|
||||||
x11: :oss,
|
x11: :oss,
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
DEFAULT_LICENSE = :unknown
|
DEFAULT_LICENSE = :unknown
|
||||||
DEFAULT_CATEGORY = VALID_LICENSES[DEFAULT_LICENSE]
|
DEFAULT_CATEGORY = VALID_LICENSES[DEFAULT_LICENSE]
|
||||||
|
|
||||||
attr_reader :value
|
attr_reader :value
|
||||||
|
|
||||||
def self.check_constants
|
def self.check_constants
|
||||||
categories = Set.new(VALID_LICENSES.values)
|
categories = Set.new(VALID_LICENSES.values)
|
||||||
categories.each do |cat|
|
categories.each do |cat|
|
||||||
next if VALID_LICENSES.key?(cat)
|
next if VALID_LICENSES.key?(cat)
|
||||||
raise "license category is not a value: '#{@cat.inspect}'"
|
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
|
||||||
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
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
require "hbc/staged"
|
require "hbc/staged"
|
||||||
|
|
||||||
class Hbc::DSL::Postflight < Hbc::DSL::Base
|
module Hbc
|
||||||
include Hbc::Staged
|
class DSL
|
||||||
|
class Postflight < Base
|
||||||
|
include Staged
|
||||||
|
|
||||||
def suppress_move_to_applications(options = {})
|
def suppress_move_to_applications(options = {})
|
||||||
# TODO: Remove from all casks because it is no longer needed
|
# TODO: Remove from all casks because it is no longer needed
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
class Hbc::DSL::Preflight < Hbc::DSL::Base
|
module Hbc
|
||||||
include Hbc::Staged
|
class DSL
|
||||||
|
class Preflight < Base
|
||||||
|
include Staged
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user