diff --git a/Library/Homebrew/cask/.rubocop.yml b/Library/Homebrew/cask/.rubocop.yml index 076b87f61f..96a0b0e11d 100644 --- a/Library/Homebrew/cask/.rubocop.yml +++ b/Library/Homebrew/cask/.rubocop.yml @@ -86,7 +86,7 @@ Style/BlockDelimiters: - proc Style/ClassAndModuleChildren: - EnforcedStyle: compact + EnforcedStyle: nested Style/Documentation: Enabled: false diff --git a/Library/Homebrew/cask/lib/hbc.rb b/Library/Homebrew/cask/lib/hbc.rb index fd61558abc..62b391638a 100644 --- a/Library/Homebrew/cask/lib/hbc.rb +++ b/Library/Homebrew/cask/lib/hbc.rb @@ -40,22 +40,22 @@ require "utils" require "vendor/plist/plist" module Hbc - include Hbc::Locations - include Hbc::Scopes - include Hbc::Options - include Hbc::Utils + include Locations + include Scopes + include Options + include Utils def self.init - Hbc::Cache.ensure_cache_exists - Hbc::Cache.migrate_legacy_cache + Cache.ensure_cache_exists + Cache.migrate_legacy_cache - Hbc::Caskroom.migrate_caskroom_from_repo_to_prefix - Hbc::Caskroom.ensure_caskroom_exists + Caskroom.migrate_caskroom_from_repo_to_prefix + Caskroom.ensure_caskroom_exists end def self.load(query) odebug "Loading Cask definitions" - cask = Hbc::Source.for_query(query).load + cask = Source.for_query(query).load cask.dumpcask cask end diff --git a/Library/Homebrew/cask/lib/hbc/artifact.rb b/Library/Homebrew/cask/lib/hbc/artifact.rb index 73bd582a59..ec808a614e 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact.rb @@ -1,5 +1,3 @@ -module Hbc::Artifact; end - require "hbc/artifact/app" require "hbc/artifact/artifact" # generic 'artifact' stanza require "hbc/artifact/binary" @@ -24,42 +22,44 @@ require "hbc/artifact/suite" require "hbc/artifact/uninstall" require "hbc/artifact/zap" -module Hbc::Artifact - # NOTE: order is important here, since we want to extract nested containers - # before we handle any other artifacts - def self.artifacts - [ - Hbc::Artifact::PreflightBlock, - Hbc::Artifact::NestedContainer, - Hbc::Artifact::Installer, - Hbc::Artifact::App, - Hbc::Artifact::Suite, - Hbc::Artifact::Artifact, # generic 'artifact' stanza - Hbc::Artifact::Colorpicker, - Hbc::Artifact::Pkg, - Hbc::Artifact::Prefpane, - Hbc::Artifact::Qlplugin, - Hbc::Artifact::Font, - Hbc::Artifact::Service, - Hbc::Artifact::StageOnly, - Hbc::Artifact::Binary, - Hbc::Artifact::InputMethod, - Hbc::Artifact::InternetPlugin, - Hbc::Artifact::AudioUnitPlugin, - Hbc::Artifact::VstPlugin, - Hbc::Artifact::Vst3Plugin, - Hbc::Artifact::ScreenSaver, - Hbc::Artifact::Uninstall, - Hbc::Artifact::PostflightBlock, - Hbc::Artifact::Zap, - ] - end +module Hbc + module Artifact + # NOTE: order is important here, since we want to extract nested containers + # before we handle any other artifacts + def self.artifacts + [ + PreflightBlock, + NestedContainer, + Installer, + App, + Suite, + Artifact, # generic 'artifact' stanza + Colorpicker, + Pkg, + Prefpane, + Qlplugin, + Font, + Service, + StageOnly, + Binary, + InputMethod, + InternetPlugin, + AudioUnitPlugin, + VstPlugin, + Vst3Plugin, + ScreenSaver, + Uninstall, + PostflightBlock, + Zap, + ] + end - def self.for_cask(cask) - odebug "Determining which artifacts are present in Cask #{cask}" - artifacts.select do |artifact| - odebug "Checking for artifact class #{artifact}" - artifact.me?(cask) + def self.for_cask(cask) + odebug "Determining which artifacts are present in Cask #{cask}" + artifacts.select do |artifact| + odebug "Checking for artifact class #{artifact}" + artifact.me?(cask) + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb b/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb index c879fc2be9..30709a0b5d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb @@ -1,36 +1,40 @@ require "hbc/artifact/base" -class Hbc::Artifact::AbstractFlightBlock < Hbc::Artifact::Base - def self.artifact_dsl_key - super.to_s.sub(%r{_block$}, "").to_sym - end +module Hbc + module Artifact + class AbstractFlightBlock < Base + def self.artifact_dsl_key + super.to_s.sub(%r{_block$}, "").to_sym + end - def self.uninstall_artifact_dsl_key - artifact_dsl_key.to_s.prepend("uninstall_").to_sym - end + def self.uninstall_artifact_dsl_key + artifact_dsl_key.to_s.prepend("uninstall_").to_sym + end - def self.class_for_dsl_key(dsl_key) - Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}") - end + def self.class_for_dsl_key(dsl_key) + Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}") + end - def self.me?(cask) - cask.artifacts[artifact_dsl_key].any? || - cask.artifacts[uninstall_artifact_dsl_key].any? - end + def self.me?(cask) + cask.artifacts[artifact_dsl_key].any? || + cask.artifacts[uninstall_artifact_dsl_key].any? + end - def install_phase - abstract_phase(self.class.artifact_dsl_key) - end + def install_phase + abstract_phase(self.class.artifact_dsl_key) + end - def uninstall_phase - abstract_phase(self.class.uninstall_artifact_dsl_key) - end + def uninstall_phase + abstract_phase(self.class.uninstall_artifact_dsl_key) + end - private + private - def abstract_phase(dsl_key) - @cask.artifacts[dsl_key].each do |block| - self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block) + def abstract_phase(dsl_key) + @cask.artifacts[dsl_key].each do |block| + self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/app.rb b/Library/Homebrew/cask/lib/hbc/artifact/app.rb index bbda16f744..cc6ef61a73 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/app.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/app.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::App < Hbc::Artifact::Moved +module Hbc + module Artifact + class App < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb b/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb index e2c06eb70a..cb35821ccf 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb @@ -1,20 +1,24 @@ require "hbc/artifact/moved" -class Hbc::Artifact::Artifact < Hbc::Artifact::Moved - def self.artifact_english_name - "Generic Artifact" - end +module Hbc + module Artifact + class Artifact < Moved + def self.artifact_english_name + "Generic Artifact" + end - def self.artifact_dirmethod - :appdir - end + def self.artifact_dirmethod + :appdir + end - def load_specification(artifact_spec) - source_string, target_hash = artifact_spec - raise Hbc::CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil? - @source = @cask.staged_path.join(source_string) - raise Hbc::CaskInvalidError.new(@cask.token, "target required for generic artifact #{source_string}") unless target_hash.is_a?(Hash) - target_hash.assert_valid_keys(:target) - @target = Pathname.new(target_hash[:target]) + def load_specification(artifact_spec) + source_string, target_hash = artifact_spec + raise CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil? + @source = @cask.staged_path.join(source_string) + raise CaskInvalidError.new(@cask.token, "target required for generic artifact #{source_string}") unless target_hash.is_a?(Hash) + target_hash.assert_valid_keys(:target) + @target = Pathname.new(target_hash[:target]) + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/audio_unit_plugin.rb b/Library/Homebrew/cask/lib/hbc/artifact/audio_unit_plugin.rb index 7f3999306d..3bad78073b 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/audio_unit_plugin.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/audio_unit_plugin.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::AudioUnitPlugin < Hbc::Artifact::Moved +module Hbc + module Artifact + class AudioUnitPlugin < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/base.rb b/Library/Homebrew/cask/lib/hbc/artifact/base.rb index 9a07cc9066..141ab68810 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/base.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/base.rb @@ -1,79 +1,83 @@ -class Hbc::Artifact::Base - def self.artifact_name - @artifact_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase - end +module Hbc + module Artifact + class Base + def self.artifact_name + @artifact_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase + end - def self.artifact_english_name - @artifact_english_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1 \2') - end + def self.artifact_english_name + @artifact_english_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1 \2') + end - def self.artifact_english_article - @artifact_english_article ||= artifact_english_name =~ %r{^[aeiou]}i ? "an" : "a" - end + def self.artifact_english_article + @artifact_english_article ||= artifact_english_name =~ %r{^[aeiou]}i ? "an" : "a" + end - def self.artifact_dsl_key - @artifact_dsl_key ||= artifact_name.to_sym - end + def self.artifact_dsl_key + @artifact_dsl_key ||= artifact_name.to_sym + end - def self.artifact_dirmethod - @artifact_dirmethod ||= "#{artifact_name}dir".to_sym - end + def self.artifact_dirmethod + @artifact_dirmethod ||= "#{artifact_name}dir".to_sym + end - def self.me?(cask) - cask.artifacts[artifact_dsl_key].any? - end + def self.me?(cask) + cask.artifacts[artifact_dsl_key].any? + end - attr_reader :force + attr_reader :force - def zap_phase - odebug "Nothing to do. The #{self.class.artifact_name} artifact has no zap phase." - end + def zap_phase + odebug "Nothing to do. The #{self.class.artifact_name} artifact has no zap phase." + end - # TODO: this sort of logic would make more sense in dsl.rb, or a - # constructor called from dsl.rb, so long as that isn't slow. - def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil) - # TODO: when stanza names are harmonized with class names, - # stanza may not be needed as an explicit argument - description = stanza.to_s - if key - arguments = arguments[key] - description.concat(" #{key.inspect}") + # TODO: this sort of logic would make more sense in dsl.rb, or a + # constructor called from dsl.rb, so long as that isn't slow. + def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil) + # TODO: when stanza names are harmonized with class names, + # stanza may not be needed as an explicit argument + description = stanza.to_s + if key + arguments = arguments[key] + description.concat(" #{key.inspect}") + end + + # backward-compatible string value + arguments = { executable: arguments } if arguments.is_a?(String) + + # key sanity + permitted_keys = [:args, :input, :executable, :must_succeed, :sudo, :bsexec, :print_stdout, :print_stderr] + unknown_keys = arguments.keys - permitted_keys + unless unknown_keys.empty? + opoo %Q{Unknown arguments to #{description} -- #{unknown_keys.inspect} (ignored). Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.} + end + arguments.reject! { |k| !permitted_keys.include?(k) } + + # key warnings + override_keys = override_arguments.keys + ignored_keys = arguments.keys & override_keys + unless ignored_keys.empty? + onoe "Some arguments to #{description} will be ignored -- :#{unknown_keys.inspect} (overridden)." + end + + # extract executable + executable = arguments.key?(:executable) ? arguments.delete(:executable) : nil + + arguments = default_arguments.merge arguments + arguments.merge! override_arguments + + [executable, arguments] + end + + def summary + {} + end + + def initialize(cask, command: SystemCommand, force: false) + @cask = cask + @command = command + @force = force + end end - - # backward-compatible string value - arguments = { executable: arguments } if arguments.is_a?(String) - - # key sanity - permitted_keys = [:args, :input, :executable, :must_succeed, :sudo, :bsexec, :print_stdout, :print_stderr] - unknown_keys = arguments.keys - permitted_keys - unless unknown_keys.empty? - opoo %Q{Unknown arguments to #{description} -- #{unknown_keys.inspect} (ignored). Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.} - end - arguments.reject! { |k| !permitted_keys.include?(k) } - - # key warnings - override_keys = override_arguments.keys - ignored_keys = arguments.keys & override_keys - unless ignored_keys.empty? - onoe "Some arguments to #{description} will be ignored -- :#{unknown_keys.inspect} (overridden)." - end - - # extract executable - executable = arguments.key?(:executable) ? arguments.delete(:executable) : nil - - arguments = default_arguments.merge arguments - arguments.merge! override_arguments - - [executable, arguments] - end - - def summary - {} - end - - def initialize(cask, command: Hbc::SystemCommand, force: false) - @cask = cask - @command = command - @force = force end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/binary.rb b/Library/Homebrew/cask/lib/hbc/artifact/binary.rb index ccaebe0c84..646e5c3ad0 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/binary.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/binary.rb @@ -1,7 +1,11 @@ require "hbc/artifact/symlinked" -class Hbc::Artifact::Binary < Hbc::Artifact::Symlinked - def install_phase - super unless Hbc.no_binaries +module Hbc + module Artifact + class Binary < Symlinked + def install_phase + super unless Hbc.no_binaries + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/colorpicker.rb b/Library/Homebrew/cask/lib/hbc/artifact/colorpicker.rb index 7b56d0ffc9..a866e64a5f 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/colorpicker.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/colorpicker.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::Colorpicker < Hbc::Artifact::Moved +module Hbc + module Artifact + class Colorpicker < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/font.rb b/Library/Homebrew/cask/lib/hbc/artifact/font.rb index 9697d9e138..5c64869b6d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/font.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/font.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::Font < Hbc::Artifact::Moved +module Hbc + module Artifact + class Font < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/input_method.rb b/Library/Homebrew/cask/lib/hbc/artifact/input_method.rb index 3c7f3d9903..0eb75a6c02 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/input_method.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/input_method.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::InputMethod < Hbc::Artifact::Moved +module Hbc + module Artifact + class InputMethod < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/installer.rb b/Library/Homebrew/cask/lib/hbc/artifact/installer.rb index 2f66397e92..b64b00fe3d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/installer.rb @@ -1,41 +1,45 @@ require "hbc/artifact/base" -class Hbc::Artifact::Installer < Hbc::Artifact::Base - # TODO: for backward compatibility, removeme - def install - install_phase - end +module Hbc + module Artifact + class Installer < Base + # TODO: for backward compatibility, removeme + def install + install_phase + end - # TODO: for backward compatibility, removeme - def uninstall - uninstall_phase - end + # TODO: for backward compatibility, removeme + def uninstall + uninstall_phase + end - def install_phase - @cask.artifacts[self.class.artifact_dsl_key].each do |artifact| - if artifact.manual - puts <<-EOS.undent - To complete the installation of Cask #{@cask}, you must also - run the installer at + def install_phase + @cask.artifacts[self.class.artifact_dsl_key].each do |artifact| + if artifact.manual + puts <<-EOS.undent + To complete the installation of Cask #{@cask}, you must also + run the installer at - '#{@cask.staged_path.join(artifact.manual)}' + '#{@cask.staged_path.join(artifact.manual)}' - EOS - else - executable, script_arguments = self.class.read_script_arguments(artifact.script, - self.class.artifact_dsl_key.to_s, - { must_succeed: true, sudo: true }, - print_stdout: true) - ohai "Running #{self.class.artifact_dsl_key} script #{executable}" - raise Hbc::CaskInvalidError.new(@cask, "#{self.class.artifact_dsl_key} missing executable") if executable.nil? - executable_path = @cask.staged_path.join(executable) - @command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path) - @command.run(executable_path, script_arguments) + EOS + else + executable, script_arguments = self.class.read_script_arguments(artifact.script, + self.class.artifact_dsl_key.to_s, + { must_succeed: true, sudo: true }, + print_stdout: true) + ohai "Running #{self.class.artifact_dsl_key} script #{executable}" + raise CaskInvalidError.new(@cask, "#{self.class.artifact_dsl_key} missing executable") if executable.nil? + executable_path = @cask.staged_path.join(executable) + @command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path) + @command.run(executable_path, script_arguments) + end + end + end + + def uninstall_phase + odebug "Nothing to do. The #{self.class.artifact_dsl_key} artifact has no uninstall phase." end end end - - def uninstall_phase - odebug "Nothing to do. The #{self.class.artifact_dsl_key} artifact has no uninstall phase." - end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/internet_plugin.rb b/Library/Homebrew/cask/lib/hbc/artifact/internet_plugin.rb index a444182748..ab8586d699 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/internet_plugin.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/internet_plugin.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::InternetPlugin < Hbc::Artifact::Moved +module Hbc + module Artifact + class InternetPlugin < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/moved.rb b/Library/Homebrew/cask/lib/hbc/artifact/moved.rb index 8f41e9fd54..6095887e3d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/moved.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/moved.rb @@ -1,88 +1,92 @@ require "hbc/artifact/relocated" -class Hbc::Artifact::Moved < Hbc::Artifact::Relocated - def self.english_description - "#{artifact_english_name}s" - end +module Hbc + module Artifact + class Moved < Relocated + def self.english_description + "#{artifact_english_name}s" + end - def install_phase - each_artifact do |artifact| - load_specification(artifact) - next unless preflight_checks - delete if Hbc::Utils.path_occupied?(target) && force - move - end - end + def install_phase + each_artifact do |artifact| + load_specification(artifact) + next unless preflight_checks + delete if Utils.path_occupied?(target) && force + move + end + end - def uninstall_phase - each_artifact do |artifact| - load_specification(artifact) - next unless File.exist?(target) - delete - end - end + def uninstall_phase + each_artifact do |artifact| + load_specification(artifact) + next unless File.exist?(target) + delete + end + end - private + private - def each_artifact - # the sort is for predictability between Ruby versions - @cask.artifacts[self.class.artifact_dsl_key].sort.each do |artifact| - yield artifact - end - end + def each_artifact + # the sort is for predictability between Ruby versions + @cask.artifacts[self.class.artifact_dsl_key].sort.each do |artifact| + yield artifact + end + end - def move - ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'" - target.dirname.mkpath - FileUtils.move(source, target) - add_altname_metadata target, source.basename.to_s - end + def move + ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'" + target.dirname.mkpath + FileUtils.move(source, target) + add_altname_metadata target, source.basename.to_s + end - def preflight_checks - if Hbc::Utils.path_occupied?(target) - if force - ohai(warning_target_exists { |s| s << "overwriting." }) - else - ohai(warning_target_exists { |s| s << "not moving." }) - return false + def preflight_checks + if Utils.path_occupied?(target) + if force + ohai(warning_target_exists { |s| s << "overwriting." }) + else + ohai(warning_target_exists { |s| s << "not moving." }) + return false + end + end + unless source.exist? + message = "It seems the #{self.class.artifact_english_name} source is not there: '#{source}'" + raise CaskError, message + end + true + end + + def warning_target_exists + message_parts = [ + "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'", + ] + yield(message_parts) if block_given? + message_parts.join("; ") + end + + def delete + ohai "Removing #{self.class.artifact_english_name}: '#{target}'" + raise CaskError, "Cannot remove undeletable #{self.class.artifact_english_name}" if MacOS.undeletable?(target) + + if force + Utils.gain_permissions_remove(target, command: @command) + else + target.rmtree + end + end + + def summarize_artifact(artifact_spec) + load_specification artifact_spec + + if target.exist? + target_abv = " (#{target.abv})" + else + warning = "Missing #{self.class.artifact_english_name}" + warning = "#{Tty.red}#{warning}#{Tty.reset}: " + end + + "#{warning}#{printable_target}#{target_abv}" end end - unless source.exist? - message = "It seems the #{self.class.artifact_english_name} source is not there: '#{source}'" - raise Hbc::CaskError, message - end - true - end - - def warning_target_exists - message_parts = [ - "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'", - ] - yield(message_parts) if block_given? - message_parts.join("; ") - end - - def delete - ohai "Removing #{self.class.artifact_english_name}: '#{target}'" - raise Hbc::CaskError, "Cannot remove undeletable #{self.class.artifact_english_name}" if MacOS.undeletable?(target) - - if force - Hbc::Utils.gain_permissions_remove(target, command: @command) - else - target.rmtree - end - end - - def summarize_artifact(artifact_spec) - load_specification artifact_spec - - if target.exist? - target_abv = " (#{target.abv})" - else - warning = "Missing #{self.class.artifact_english_name}" - warning = "#{Tty.red}#{warning}#{Tty.reset}: " - end - - "#{warning}#{printable_target}#{target_abv}" end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb b/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb index 68e4a552cc..1076407972 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb @@ -1,24 +1,28 @@ require "hbc/artifact/base" -class Hbc::Artifact::NestedContainer < Hbc::Artifact::Base - def install_phase - @cask.artifacts[:nested_container].each { |container| extract(container) } - end +module Hbc + module Artifact + class NestedContainer < Base + def install_phase + @cask.artifacts[:nested_container].each { |container| extract(container) } + end - def uninstall_phase - # no need to take action; is removed after extraction - end + def uninstall_phase + # no need to take action; is removed after extraction + end - def extract(container_relative_path) - source = @cask.staged_path.join(container_relative_path) - container = Hbc::Container.for_path(source, @command) + def extract(container_relative_path) + source = @cask.staged_path.join(container_relative_path) + container = Container.for_path(source, @command) - unless container - raise Hbc::CaskError, "Aw dang, could not identify nested container at '#{source}'" + unless container + raise CaskError, "Aw dang, could not identify nested container at '#{source}'" + end + + ohai "Extracting nested container #{source.basename}" + container.new(@cask, source, @command).extract + FileUtils.remove_entry_secure(source) + end end - - ohai "Extracting nested container #{source.basename}" - container.new(@cask, source, @command).extract - FileUtils.remove_entry_secure(source) end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb b/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb index fb27308d74..e590a9082d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb @@ -1,53 +1,57 @@ require "hbc/artifact/base" -class Hbc::Artifact::Pkg < Hbc::Artifact::Base - attr_reader :pkg_relative_path +module Hbc + module Artifact + class Pkg < Base + attr_reader :pkg_relative_path - def self.artifact_dsl_key - :pkg - end - - def load_pkg_description(pkg_description) - @pkg_relative_path = pkg_description.shift - @pkg_install_opts = pkg_description.shift - begin - if @pkg_install_opts.respond_to?(:keys) - @pkg_install_opts.assert_valid_keys(:allow_untrusted) - elsif @pkg_install_opts - raise + def self.artifact_dsl_key + :pkg + end + + def load_pkg_description(pkg_description) + @pkg_relative_path = pkg_description.shift + @pkg_install_opts = pkg_description.shift + begin + if @pkg_install_opts.respond_to?(:keys) + @pkg_install_opts.assert_valid_keys(:allow_untrusted) + elsif @pkg_install_opts + raise + end + raise if pkg_description.nil? + rescue StandardError + raise CaskInvalidError.new(@cask, "Bad pkg stanza") + end + end + + def pkg_install_opts(opt) + @pkg_install_opts[opt] if @pkg_install_opts.respond_to?(:keys) + end + + def install_phase + @cask.artifacts[:pkg].each { |pkg_description| run_installer(pkg_description) } + end + + def uninstall_phase + # Do nothing. Must be handled explicitly by a separate :uninstall stanza. + end + + def run_installer(pkg_description) + load_pkg_description pkg_description + ohai "Running installer for #{@cask}; your password may be necessary." + ohai "Package installers may write to any location; options such as --appdir are ignored." + source = @cask.staged_path.join(pkg_relative_path) + unless source.exist? + raise CaskError, "pkg source file not found: '#{source}'" + end + args = [ + "-pkg", source, + "-target", "/" + ] + args << "-verboseR" if Hbc.verbose + args << "-allowUntrusted" if pkg_install_opts :allow_untrusted + @command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true) end - raise if pkg_description.nil? - rescue StandardError - raise Hbc::CaskInvalidError.new(@cask, "Bad pkg stanza") end end - - def pkg_install_opts(opt) - @pkg_install_opts[opt] if @pkg_install_opts.respond_to?(:keys) - end - - def install_phase - @cask.artifacts[:pkg].each { |pkg_description| run_installer(pkg_description) } - end - - def uninstall_phase - # Do nothing. Must be handled explicitly by a separate :uninstall stanza. - end - - def run_installer(pkg_description) - load_pkg_description pkg_description - ohai "Running installer for #{@cask}; your password may be necessary." - ohai "Package installers may write to any location; options such as --appdir are ignored." - source = @cask.staged_path.join(pkg_relative_path) - unless source.exist? - raise Hbc::CaskError, "pkg source file not found: '#{source}'" - end - args = [ - "-pkg", source, - "-target", "/" - ] - args << "-verboseR" if Hbc.verbose - args << "-allowUntrusted" if pkg_install_opts :allow_untrusted - @command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true) - end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/postflight_block.rb b/Library/Homebrew/cask/lib/hbc/artifact/postflight_block.rb index 92b21a83f9..bfe218f958 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/postflight_block.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/postflight_block.rb @@ -1,4 +1,8 @@ require "hbc/artifact/abstract_flight_block" -class Hbc::Artifact::PostflightBlock < Hbc::Artifact::AbstractFlightBlock +module Hbc + module Artifact + class PostflightBlock < AbstractFlightBlock + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/preflight_block.rb b/Library/Homebrew/cask/lib/hbc/artifact/preflight_block.rb index 772a88016d..35142df47d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/preflight_block.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/preflight_block.rb @@ -1,4 +1,8 @@ require "hbc/artifact/abstract_flight_block" -class Hbc::Artifact::PreflightBlock < Hbc::Artifact::AbstractFlightBlock +module Hbc + module Artifact + class PreflightBlock < AbstractFlightBlock + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb b/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb index e45cc0b19d..a44f8ae3a3 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb @@ -1,7 +1,11 @@ require "hbc/artifact/moved" -class Hbc::Artifact::Prefpane < Hbc::Artifact::Moved - def self.artifact_english_name - "Preference Pane" +module Hbc + module Artifact + class Prefpane < Moved + def self.artifact_english_name + "Preference Pane" + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb b/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb index 6702aa5ef9..ee41de2fe2 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb @@ -1,21 +1,25 @@ require "hbc/artifact/moved" -class Hbc::Artifact::Qlplugin < Hbc::Artifact::Moved - def self.artifact_english_name - "QuickLook Plugin" - end +module Hbc + module Artifact + class Qlplugin < Moved + def self.artifact_english_name + "QuickLook Plugin" + end - def install_phase - super - reload_quicklook - end + def install_phase + super + reload_quicklook + end - def uninstall_phase - super - reload_quicklook - end + def uninstall_phase + super + reload_quicklook + end - def reload_quicklook - @command.run!("/usr/bin/qlmanage", args: ["-r"]) + def reload_quicklook + @command.run!("/usr/bin/qlmanage", args: ["-r"]) + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb b/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb index cd0054188c..953045b32b 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb @@ -1,53 +1,57 @@ require "hbc/artifact/base" -class Hbc::Artifact::Relocated < Hbc::Artifact::Base - def summary - { - english_description: self.class.english_description, - contents: @cask.artifacts[self.class.artifact_dsl_key].map(&method(:summarize_artifact)).compact, - } - end +module Hbc + module Artifact + class Relocated < Base + def summary + { + english_description: self.class.english_description, + contents: @cask.artifacts[self.class.artifact_dsl_key].map(&method(:summarize_artifact)).compact, + } + end - attr_reader :source, :target + attr_reader :source, :target - def printable_target - target.to_s.sub(%r{^#{ENV['HOME']}(#{File::SEPARATOR}|$)}, "~/") - end + def printable_target + target.to_s.sub(%r{^#{ENV['HOME']}(#{File::SEPARATOR}|$)}, "~/") + end - ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames".freeze + ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames".freeze - # Try to make the asset searchable under the target name. Spotlight - # respects this attribute for many filetypes, but ignores it for App - # bundles. Alfred 2.2 respects it even for App bundles. - def add_altname_metadata(file, altname) - return if altname.casecmp(file.basename).zero? - odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata" - altnames = @command.run("/usr/bin/xattr", - args: ["-p", ALT_NAME_ATTRIBUTE, file.to_s], - print_stderr: false).stdout.sub(%r{\A\((.*)\)\Z}, '\1') - odebug "Existing metadata is: '#{altnames}'" - altnames.concat(", ") unless altnames.empty? - altnames.concat(%Q{"#{altname}"}) - altnames = "(#{altnames})" + # Try to make the asset searchable under the target name. Spotlight + # respects this attribute for many filetypes, but ignores it for App + # bundles. Alfred 2.2 respects it even for App bundles. + def add_altname_metadata(file, altname) + return if altname.casecmp(file.basename).zero? + odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata" + altnames = @command.run("/usr/bin/xattr", + args: ["-p", ALT_NAME_ATTRIBUTE, file.to_s], + print_stderr: false).stdout.sub(%r{\A\((.*)\)\Z}, '\1') + odebug "Existing metadata is: '#{altnames}'" + altnames.concat(", ") unless altnames.empty? + altnames.concat(%Q{"#{altname}"}) + altnames = "(#{altnames})" - # Some packges are shipped as u=rx (e.g. Bitcoin Core) - @command.run!("/bin/chmod", args: ["--", "u=rwx", file.to_s, file.realpath.to_s]) + # Some packges are shipped as u=rx (e.g. Bitcoin Core) + @command.run!("/bin/chmod", args: ["--", "u=rwx", file.to_s, file.realpath.to_s]) - @command.run!("/usr/bin/xattr", - args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file.to_s], - print_stderr: false) - end + @command.run!("/usr/bin/xattr", + args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file.to_s], + print_stderr: false) + end - def load_specification(artifact_spec) - source_string, target_hash = artifact_spec - raise Hbc::CaskInvalidError if source_string.nil? - @source = @cask.staged_path.join(source_string) - if target_hash - raise Hbc::CaskInvalidError unless target_hash.respond_to?(:keys) - target_hash.assert_valid_keys(:target) - @target = Hbc.send(self.class.artifact_dirmethod).join(target_hash[:target]) - else - @target = Hbc.send(self.class.artifact_dirmethod).join(source.basename) + def load_specification(artifact_spec) + source_string, target_hash = artifact_spec + raise CaskInvalidError if source_string.nil? + @source = @cask.staged_path.join(source_string) + if target_hash + raise CaskInvalidError unless target_hash.respond_to?(:keys) + target_hash.assert_valid_keys(:target) + @target = Hbc.send(self.class.artifact_dirmethod).join(target_hash[:target]) + else + @target = Hbc.send(self.class.artifact_dirmethod).join(source.basename) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/screen_saver.rb b/Library/Homebrew/cask/lib/hbc/artifact/screen_saver.rb index bbd9291527..4cdc6037c5 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/screen_saver.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/screen_saver.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::ScreenSaver < Hbc::Artifact::Moved +module Hbc + module Artifact + class ScreenSaver < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/service.rb b/Library/Homebrew/cask/lib/hbc/artifact/service.rb index d5a00e4fe0..1af93c5334 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/service.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/service.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::Service < Hbc::Artifact::Moved +module Hbc + module Artifact + class Service < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb b/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb index 7a48b19aa1..594c5bef97 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb @@ -1,15 +1,19 @@ require "hbc/artifact/base" -class Hbc::Artifact::StageOnly < Hbc::Artifact::Base - def self.artifact_dsl_key - :stage_only - end +module Hbc + module Artifact + class StageOnly < Base + def self.artifact_dsl_key + :stage_only + end - def install_phase - # do nothing - end + def install_phase + # do nothing + end - def uninstall_phase - # do nothing + def uninstall_phase + # do nothing + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/suite.rb b/Library/Homebrew/cask/lib/hbc/artifact/suite.rb index cdfb757ddf..35251f70c1 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/suite.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/suite.rb @@ -1,11 +1,15 @@ require "hbc/artifact/moved" -class Hbc::Artifact::Suite < Hbc::Artifact::Moved - def self.artifact_english_name - "App Suite" - end +module Hbc + module Artifact + class Suite < Moved + def self.artifact_english_name + "App Suite" + end - def self.artifact_dirmethod - :appdir + def self.artifact_dirmethod + :appdir + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb b/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb index 7432df5776..3ab45cccc1 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb @@ -1,65 +1,69 @@ require "hbc/artifact/relocated" -class Hbc::Artifact::Symlinked < Hbc::Artifact::Relocated - def self.link_type_english_name - "Symlink" - end +module Hbc + module Artifact + class Symlinked < Relocated + def self.link_type_english_name + "Symlink" + end - def self.english_description - "#{artifact_english_name} #{link_type_english_name}s" - end + def self.english_description + "#{artifact_english_name} #{link_type_english_name}s" + end - def self.islink?(path) - path.symlink? - end + def self.islink?(path) + path.symlink? + end - def link(artifact_spec) - load_specification artifact_spec - return unless preflight_checks(source, target) - ohai "#{self.class.link_type_english_name}ing #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'" - create_filesystem_link(source, target) - end + def link(artifact_spec) + load_specification artifact_spec + return unless preflight_checks(source, target) + ohai "#{self.class.link_type_english_name}ing #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'" + create_filesystem_link(source, target) + end - def unlink(artifact_spec) - load_specification artifact_spec - return unless self.class.islink?(target) - ohai "Removing #{self.class.artifact_english_name} #{self.class.link_type_english_name.downcase}: '#{target}'" - target.delete - end + def unlink(artifact_spec) + load_specification artifact_spec + return unless self.class.islink?(target) + ohai "Removing #{self.class.artifact_english_name} #{self.class.link_type_english_name.downcase}: '#{target}'" + target.delete + end - def install_phase - @cask.artifacts[self.class.artifact_dsl_key].each(&method(:link)) - end + def install_phase + @cask.artifacts[self.class.artifact_dsl_key].each(&method(:link)) + end - def uninstall_phase - @cask.artifacts[self.class.artifact_dsl_key].each(&method(:unlink)) - end + def uninstall_phase + @cask.artifacts[self.class.artifact_dsl_key].each(&method(:unlink)) + end - def preflight_checks(source, target) - if target.exist? && !self.class.islink?(target) - ohai "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'; not linking." - return false + def preflight_checks(source, target) + if target.exist? && !self.class.islink?(target) + ohai "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'; not linking." + return false + end + unless source.exist? + raise CaskError, "It seems the #{self.class.link_type_english_name.downcase} source is not there: '#{source}'" + end + true + end + + def create_filesystem_link(source, target) + Pathname.new(target).dirname.mkpath + @command.run!("/bin/ln", args: ["-hfs", "--", source, target]) + add_altname_metadata source, target.basename.to_s + end + + def summarize_artifact(artifact_spec) + load_specification artifact_spec + + return unless self.class.islink?(target) + + link_description = "#{Tty.red}Broken Link#{Tty.reset}: " unless target.exist? + target_readlink_abv = " (#{target.readlink.abv})" if target.readlink.exist? + + "#{link_description}#{printable_target} -> #{target.readlink}#{target_readlink_abv}" + end end - unless source.exist? - raise Hbc::CaskError, "It seems the #{self.class.link_type_english_name.downcase} source is not there: '#{source}'" - end - true - end - - def create_filesystem_link(source, target) - Pathname.new(target).dirname.mkpath - @command.run!("/bin/ln", args: ["-hfs", "--", source, target]) - add_altname_metadata source, target.basename.to_s - end - - def summarize_artifact(artifact_spec) - load_specification artifact_spec - - return unless self.class.islink?(target) - - link_description = "#{Tty.red}Broken Link#{Tty.reset}: " unless target.exist? - target_readlink_abv = " (#{target.readlink.abv})" if target.readlink.exist? - - "#{link_description}#{printable_target} -> #{target.readlink}#{target_readlink_abv}" end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb b/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb index 12010aeb8b..8b5603064e 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb @@ -1,4 +1,8 @@ require "hbc/artifact/uninstall_base" -class Hbc::Artifact::Uninstall < Hbc::Artifact::UninstallBase +module Hbc + module Artifact + class Uninstall < UninstallBase + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/uninstall_base.rb b/Library/Homebrew/cask/lib/hbc/artifact/uninstall_base.rb index f92e09a893..63ffd18c9c 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/uninstall_base.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/uninstall_base.rb @@ -2,248 +2,252 @@ require "pathname" require "hbc/artifact/base" -class Hbc::Artifact::UninstallBase < Hbc::Artifact::Base - # TODO: 500 is also hardcoded in cask/pkg.rb, but much of - # that logic is probably in the wrong location +module Hbc + module Artifact + class UninstallBase < Base + # TODO: 500 is also hardcoded in cask/pkg.rb, but much of + # that logic is probably in the wrong location - PATH_ARG_SLICE_SIZE = 500 + PATH_ARG_SLICE_SIZE = 500 - ORDERED_DIRECTIVES = [ - :early_script, - :launchctl, - :quit, - :signal, - :login_item, - :kext, - :script, - :pkgutil, - :delete, - :trash, - :rmdir, - ].freeze + ORDERED_DIRECTIVES = [ + :early_script, + :launchctl, + :quit, + :signal, + :login_item, + :kext, + :script, + :pkgutil, + :delete, + :trash, + :rmdir, + ].freeze - # TODO: these methods were consolidated here from separate - # sources and should be refactored for consistency + # TODO: these methods were consolidated here from separate + # sources and should be refactored for consistency - def self.expand_path_strings(path_strings) - path_strings.map { |path_string| - path_string.start_with?("~") ? Pathname.new(path_string).expand_path : Pathname.new(path_string) - } - end - - def self.remove_relative_path_strings(action, path_strings) - relative = path_strings.map { |path_string| - path_string if %r{/\.\.(?:/|\Z)}.match(path_string) || !%r{\A/}.match(path_string) - }.compact - relative.each do |path_string| - opoo "Skipping #{action} for relative path #{path_string}" - end - path_strings - relative - end - - def self.remove_undeletable_path_strings(action, path_strings) - undeletable = path_strings.map { |path_string| - path_string if MacOS.undeletable?(Pathname.new(path_string)) - }.compact - undeletable.each do |path_string| - opoo "Skipping #{action} for undeletable path #{path_string}" - end - path_strings - undeletable - end - - def install_phase - odebug "Nothing to do. The uninstall artifact has no install phase." - end - - def uninstall_phase - dispatch_uninstall_directives - end - - def dispatch_uninstall_directives(expand_tilde = true) - directives_set = @cask.artifacts[stanza] - ohai "Running #{stanza} process for #{@cask}; your password may be necessary" - - directives_set.each do |directives| - warn_for_unknown_directives(directives) - end - - ORDERED_DIRECTIVES.each do |directive_sym| - directives_set.select { |h| h.key?(directive_sym) }.each do |directives| - args = [directives] - args << expand_tilde if [:delete, :trash, :rmdir].include?(directive_sym) - send("uninstall_#{directive_sym}", *args) + def self.expand_path_strings(path_strings) + path_strings.map { |path_string| + path_string.start_with?("~") ? Pathname.new(path_string).expand_path : Pathname.new(path_string) + } end - end - end - private + def self.remove_relative_path_strings(action, path_strings) + relative = path_strings.map { |path_string| + path_string if %r{/\.\.(?:/|\Z)}.match(path_string) || !%r{\A/}.match(path_string) + }.compact + relative.each do |path_string| + opoo "Skipping #{action} for relative path #{path_string}" + end + path_strings - relative + end - def stanza - self.class.artifact_dsl_key - end + def self.remove_undeletable_path_strings(action, path_strings) + undeletable = path_strings.map { |path_string| + path_string if MacOS.undeletable?(Pathname.new(path_string)) + }.compact + undeletable.each do |path_string| + opoo "Skipping #{action} for undeletable path #{path_string}" + end + path_strings - undeletable + end - def warn_for_unknown_directives(directives) - unknown_keys = directives.keys - ORDERED_DIRECTIVES - return if unknown_keys.empty? - opoo %Q{Unknown arguments to #{stanza} -- #{unknown_keys.inspect}. Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.} - end + def install_phase + odebug "Nothing to do. The uninstall artifact has no install phase." + end - # Preserve prior functionality of script which runs first. Should rarely be needed. - # :early_script should not delete files, better defer that to :script. - # If Cask writers never need :early_script it may be removed in the future. - def uninstall_early_script(directives) - uninstall_script(directives, directive_name: :early_script) - end + def uninstall_phase + dispatch_uninstall_directives + end - # :launchctl must come before :quit/:signal for cases where app would instantly re-launch - def uninstall_launchctl(directives) - Array(directives[:launchctl]).each do |service| - ohai "Removing launchctl service #{service}" - [false, true].each do |with_sudo| - plist_status = @command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout - if plist_status =~ %r{^\{} - @command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo) + def dispatch_uninstall_directives(expand_tilde = true) + directives_set = @cask.artifacts[stanza] + ohai "Running #{stanza} process for #{@cask}; your password may be necessary" + + directives_set.each do |directives| + warn_for_unknown_directives(directives) + end + + ORDERED_DIRECTIVES.each do |directive_sym| + directives_set.select { |h| h.key?(directive_sym) }.each do |directives| + args = [directives] + args << expand_tilde if [:delete, :trash, :rmdir].include?(directive_sym) + send("uninstall_#{directive_sym}", *args) + end + end + end + + private + + def stanza + self.class.artifact_dsl_key + end + + def warn_for_unknown_directives(directives) + unknown_keys = directives.keys - ORDERED_DIRECTIVES + return if unknown_keys.empty? + opoo %Q{Unknown arguments to #{stanza} -- #{unknown_keys.inspect}. Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.} + end + + # Preserve prior functionality of script which runs first. Should rarely be needed. + # :early_script should not delete files, better defer that to :script. + # If Cask writers never need :early_script it may be removed in the future. + def uninstall_early_script(directives) + uninstall_script(directives, directive_name: :early_script) + end + + # :launchctl must come before :quit/:signal for cases where app would instantly re-launch + def uninstall_launchctl(directives) + Array(directives[:launchctl]).each do |service| + ohai "Removing launchctl service #{service}" + [false, true].each do |with_sudo| + plist_status = @command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout + if plist_status =~ %r{^\{} + @command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo) + sleep 1 + end + paths = ["/Library/LaunchAgents/#{service}.plist", + "/Library/LaunchDaemons/#{service}.plist"] + paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo + paths = paths.map { |elt| Pathname(elt) }.select(&:exist?) + paths.each do |path| + @command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo) + end + # undocumented and untested: pass a path to uninstall :launchctl + next unless Pathname(service).exist? + @command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo) + @command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo) + sleep 1 + end + end + end + + # :quit/:signal must come before :kext so the kext will not be in use by a running process + def uninstall_quit(directives) + Array(directives[:quit]).each do |id| + ohai "Quitting application ID #{id}" + num_running = count_running_processes(id) + next unless num_running > 0 + @command.run!("/usr/bin/osascript", args: ["-e", %Q{tell application id "#{id}" to quit}], sudo: true) + sleep 3 + end + end + + # :signal should come after :quit so it can be used as a backup when :quit fails + def uninstall_signal(directives) + Array(directives[:signal]).flatten.each_slice(2) do |pair| + raise CaskInvalidError.new(@cask, "Each #{stanza} :signal must have 2 elements.") unless pair.length == 2 + signal, id = pair + ohai "Signalling '#{signal}' to application ID '#{id}'" + pids = get_unix_pids(id) + next unless pids.any? + # Note that unlike :quit, signals are sent from the current user (not + # upgraded to the superuser). This is a todo item for the future, but + # there should be some additional thought/safety checks about that, as a + # misapplied "kill" by root could bring down the system. The fact that we + # learned the pid from AppleScript is already some degree of protection, + # though indirect. + odebug "Unix ids are #{pids.inspect} for processes with bundle identifier #{id}" + Process.kill(signal, *pids) + sleep 3 + end + end + + def count_running_processes(bundle_id) + @command.run!("/usr/bin/osascript", + args: ["-e", %Q{tell application "System Events" to count processes whose bundle identifier is "#{bundle_id}"}], + sudo: true).stdout.to_i + end + + def get_unix_pids(bundle_id) + pid_string = @command.run!("/usr/bin/osascript", + args: ["-e", %Q{tell application "System Events" to get the unix id of every process whose bundle identifier is "#{bundle_id}"}], + sudo: true).stdout.chomp + return [] unless pid_string =~ %r{\A\d+(?:\s*,\s*\d+)*\Z} # sanity check + pid_string.split(%r{\s*,\s*}).map(&:strip).map(&:to_i) + end + + def uninstall_login_item(directives) + Array(directives[:login_item]).each do |name| + ohai "Removing login item #{name}" + @command.run!("/usr/bin/osascript", + args: ["-e", %Q{tell application "System Events" to delete every login item whose name is "#{name}"}], + sudo: false) sleep 1 end - paths = ["/Library/LaunchAgents/#{service}.plist", - "/Library/LaunchDaemons/#{service}.plist"] - paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo - paths = paths.map { |elt| Pathname(elt) }.select(&:exist?) - paths.each do |path| - @command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo) + end + + # :kext should be unloaded before attempting to delete the relevant file + def uninstall_kext(directives) + Array(directives[:kext]).each do |kext| + ohai "Unloading kernel extension #{kext}" + is_loaded = @command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout + if is_loaded.length > 1 + @command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true) + sleep 1 + end end - # undocumented and untested: pass a path to uninstall :launchctl - next unless Pathname(service).exist? - @command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo) - @command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo) + end + + # :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted + def uninstall_script(directives, directive_name: :script) + executable, script_arguments = self.class.read_script_arguments(directives, + "uninstall", + { must_succeed: true, sudo: true }, + { print_stdout: true }, + directive_name) + ohai "Running uninstall script #{executable}" + raise CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil? + executable_path = @cask.staged_path.join(executable) + @command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path) + @command.run(executable_path, script_arguments) sleep 1 end - end - end - # :quit/:signal must come before :kext so the kext will not be in use by a running process - def uninstall_quit(directives) - Array(directives[:quit]).each do |id| - ohai "Quitting application ID #{id}" - num_running = count_running_processes(id) - next unless num_running > 0 - @command.run!("/usr/bin/osascript", args: ["-e", %Q{tell application id "#{id}" to quit}], sudo: true) - sleep 3 - end - end - - # :signal should come after :quit so it can be used as a backup when :quit fails - def uninstall_signal(directives) - Array(directives[:signal]).flatten.each_slice(2) do |pair| - raise Hbc::CaskInvalidError.new(@cask, "Each #{stanza} :signal must have 2 elements.") unless pair.length == 2 - signal, id = pair - ohai "Signalling '#{signal}' to application ID '#{id}'" - pids = get_unix_pids(id) - next unless pids.any? - # Note that unlike :quit, signals are sent from the current user (not - # upgraded to the superuser). This is a todo item for the future, but - # there should be some additional thought/safety checks about that, as a - # misapplied "kill" by root could bring down the system. The fact that we - # learned the pid from AppleScript is already some degree of protection, - # though indirect. - odebug "Unix ids are #{pids.inspect} for processes with bundle identifier #{id}" - Process.kill(signal, *pids) - sleep 3 - end - end - - def count_running_processes(bundle_id) - @command.run!("/usr/bin/osascript", - args: ["-e", %Q{tell application "System Events" to count processes whose bundle identifier is "#{bundle_id}"}], - sudo: true).stdout.to_i - end - - def get_unix_pids(bundle_id) - pid_string = @command.run!("/usr/bin/osascript", - args: ["-e", %Q{tell application "System Events" to get the unix id of every process whose bundle identifier is "#{bundle_id}"}], - sudo: true).stdout.chomp - return [] unless pid_string =~ %r{\A\d+(?:\s*,\s*\d+)*\Z} # sanity check - pid_string.split(%r{\s*,\s*}).map(&:strip).map(&:to_i) - end - - def uninstall_login_item(directives) - Array(directives[:login_item]).each do |name| - ohai "Removing login item #{name}" - @command.run!("/usr/bin/osascript", - args: ["-e", %Q{tell application "System Events" to delete every login item whose name is "#{name}"}], - sudo: false) - sleep 1 - end - end - - # :kext should be unloaded before attempting to delete the relevant file - def uninstall_kext(directives) - Array(directives[:kext]).each do |kext| - ohai "Unloading kernel extension #{kext}" - is_loaded = @command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout - if is_loaded.length > 1 - @command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true) - sleep 1 + def uninstall_pkgutil(directives) + ohai "Removing files from pkgutil Bill-of-Materials" + Array(directives[:pkgutil]).each do |regexp| + pkgs = Hbc::Pkg.all_matching(regexp, @command) + pkgs.each(&:uninstall) + end end - end - end - # :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted - def uninstall_script(directives, directive_name: :script) - executable, script_arguments = self.class.read_script_arguments(directives, - "uninstall", - { must_succeed: true, sudo: true }, - { print_stdout: true }, - directive_name) - ohai "Running uninstall script #{executable}" - raise Hbc::CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil? - executable_path = @cask.staged_path.join(executable) - @command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path) - @command.run(executable_path, script_arguments) - sleep 1 - end + def uninstall_delete(directives, expand_tilde = true) + Array(directives[:delete]).concat(Array(directives[:trash])).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice| + ohai "Removing files: #{path_slice.utf8_inspect}" + path_slice = self.class.expand_path_strings(path_slice) if expand_tilde + path_slice = self.class.remove_relative_path_strings(:delete, path_slice) + path_slice = self.class.remove_undeletable_path_strings(:delete, path_slice) + @command.run!("/bin/rm", args: path_slice.unshift("-rf", "--"), sudo: true) + end + end - def uninstall_pkgutil(directives) - ohai "Removing files from pkgutil Bill-of-Materials" - Array(directives[:pkgutil]).each do |regexp| - pkgs = Hbc::Pkg.all_matching(regexp, @command) - pkgs.each(&:uninstall) - end - end + # :trash functionality is stubbed as a synonym for :delete + # TODO: make :trash work differently, moving files to the Trash + def uninstall_trash(directives, expand_tilde = true) + uninstall_delete(directives, expand_tilde) + end - def uninstall_delete(directives, expand_tilde = true) - Array(directives[:delete]).concat(Array(directives[:trash])).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice| - ohai "Removing files: #{path_slice.utf8_inspect}" - path_slice = self.class.expand_path_strings(path_slice) if expand_tilde - path_slice = self.class.remove_relative_path_strings(:delete, path_slice) - path_slice = self.class.remove_undeletable_path_strings(:delete, path_slice) - @command.run!("/bin/rm", args: path_slice.unshift("-rf", "--"), sudo: true) - end - end - - # :trash functionality is stubbed as a synonym for :delete - # TODO: make :trash work differently, moving files to the Trash - def uninstall_trash(directives, expand_tilde = true) - uninstall_delete(directives, expand_tilde) - end - - def uninstall_rmdir(directives, expand_tilde = true) - Array(directives[:rmdir]).flatten.each do |directory| - directory = self.class.expand_path_strings([directory]).first if expand_tilde - directory = self.class.remove_relative_path_strings(:rmdir, [directory]).first - directory = self.class.remove_undeletable_path_strings(:rmdir, [directory]).first - next if directory.to_s.empty? - ohai "Removing directory if empty: #{directory.to_s.utf8_inspect}" - directory = Pathname.new(directory) - next unless directory.exist? - @command.run!("/bin/rm", - args: ["-f", "--", directory.join(".DS_Store")], - sudo: true, - print_stderr: false) - @command.run("/bin/rmdir", - args: ["--", directory], - sudo: true, - print_stderr: false) + def uninstall_rmdir(directives, expand_tilde = true) + Array(directives[:rmdir]).flatten.each do |directory| + directory = self.class.expand_path_strings([directory]).first if expand_tilde + directory = self.class.remove_relative_path_strings(:rmdir, [directory]).first + directory = self.class.remove_undeletable_path_strings(:rmdir, [directory]).first + next if directory.to_s.empty? + ohai "Removing directory if empty: #{directory.to_s.utf8_inspect}" + directory = Pathname.new(directory) + next unless directory.exist? + @command.run!("/bin/rm", + args: ["-f", "--", directory.join(".DS_Store")], + sudo: true, + print_stderr: false) + @command.run("/bin/rmdir", + args: ["--", directory], + sudo: true, + print_stderr: false) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/vst3_plugin.rb b/Library/Homebrew/cask/lib/hbc/artifact/vst3_plugin.rb index 2438844357..056fffc2a9 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/vst3_plugin.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/vst3_plugin.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::Vst3Plugin < Hbc::Artifact::Moved +module Hbc + module Artifact + class Vst3Plugin < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/vst_plugin.rb b/Library/Homebrew/cask/lib/hbc/artifact/vst_plugin.rb index 8d05464805..f388046359 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/vst_plugin.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/vst_plugin.rb @@ -1,4 +1,8 @@ require "hbc/artifact/moved" -class Hbc::Artifact::VstPlugin < Hbc::Artifact::Moved +module Hbc + module Artifact + class VstPlugin < Moved + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/zap.rb b/Library/Homebrew/cask/lib/hbc/artifact/zap.rb index 8bd8da63bb..503ea35c49 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/zap.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/zap.rb @@ -1,16 +1,20 @@ require "hbc/artifact/uninstall_base" -class Hbc::Artifact::Zap < Hbc::Artifact::UninstallBase - def install_phase - odebug "Nothing to do. The zap artifact has no install phase." - end +module Hbc + module Artifact + class Zap < UninstallBase + def install_phase + odebug "Nothing to do. The zap artifact has no install phase." + end - def uninstall_phase - odebug "Nothing to do. The zap artifact has no uninstall phase." - end + def uninstall_phase + odebug "Nothing to do. The zap artifact has no uninstall phase." + end - def zap_phase - expand_tilde = true - dispatch_uninstall_directives(expand_tilde) + def zap_phase + expand_tilde = true + dispatch_uninstall_directives(expand_tilde) + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/audit.rb b/Library/Homebrew/cask/lib/hbc/audit.rb index 98f09ffa43..476f2aec05 100644 --- a/Library/Homebrew/cask/lib/hbc/audit.rb +++ b/Library/Homebrew/cask/lib/hbc/audit.rb @@ -2,215 +2,217 @@ require "hbc/checkable" require "hbc/download" require "digest" -class Hbc::Audit - include Hbc::Checkable +module Hbc + class Audit + include Checkable - attr_reader :cask, :download + attr_reader :cask, :download - def initialize(cask, download: false, check_token_conflicts: false, command: Hbc::SystemCommand) - @cask = cask - @download = download - @check_token_conflicts = check_token_conflicts - @command = command - end - - def check_token_conflicts? - @check_token_conflicts - end - - def run! - check_required_stanzas - check_version - check_sha256 - check_appcast - check_url - check_generic_artifacts - check_token_conflicts - check_download - self - rescue StandardError => e - odebug "#{e.message}\n#{e.backtrace.join("\n")}" - add_error "exception while auditing #{cask}: #{e.message}" - self - end - - def success? - !(errors? || warnings?) - end - - def summary_header - "audit for #{cask}" - end - - private - - def check_required_stanzas - odebug "Auditing required stanzas" - %i{version sha256 url homepage}.each do |sym| - add_error "a #{sym} stanza is required" unless cask.send(sym) + def initialize(cask, download: false, check_token_conflicts: false, command: SystemCommand) + @cask = cask + @download = download + @check_token_conflicts = check_token_conflicts + @command = command end - add_error "a license stanza is required (:unknown is OK)" unless cask.license - add_error "at least one name stanza is required" if cask.name.empty? - # TODO: specific DSL knowledge should not be spread around in various files like this - # TODO: nested_container should not still be a pseudo-artifact at this point - installable_artifacts = cask.artifacts.reject { |k| [:uninstall, :zap, :nested_container].include?(k) } - add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty? - end - def check_version - return unless cask.version - check_no_string_version_latest - end - - def check_no_string_version_latest - odebug "Verifying version :latest does not appear as a string ('latest')" - return unless cask.version.raw_version == "latest" - add_error "you should use version :latest instead of version 'latest'" - end - - def check_sha256 - return unless cask.sha256 - check_sha256_no_check_if_latest - check_sha256_actually_256 - check_sha256_invalid - end - - def check_sha256_no_check_if_latest - odebug "Verifying sha256 :no_check with version :latest" - return unless cask.version.latest? && cask.sha256 != :no_check - add_error "you should use sha256 :no_check when version is :latest" - end - - def check_sha256_actually_256(sha256: cask.sha256, stanza: "sha256") - odebug "Verifying #{stanza} string is a legal SHA-256 digest" - return unless sha256.is_a?(String) - return if sha256.length == 64 && sha256[%r{^[0-9a-f]+$}i] - add_error "#{stanza} string must be of 64 hexadecimal characters" - end - - def check_sha256_invalid(sha256: cask.sha256, stanza: "sha256") - odebug "Verifying #{stanza} is not a known invalid value" - empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - return unless sha256 == empty_sha256 - add_error "cannot use the sha256 for an empty string in #{stanza}: #{empty_sha256}" - end - - def check_appcast - return unless cask.appcast - odebug "Auditing appcast" - check_appcast_has_checkpoint - return unless cask.appcast.checkpoint - check_sha256_actually_256(sha256: cask.appcast.checkpoint, stanza: "appcast :checkpoint") - check_sha256_invalid(sha256: cask.appcast.checkpoint, stanza: "appcast :checkpoint") - return unless download - check_appcast_http_code - check_appcast_checkpoint_accuracy - end - - def check_appcast_has_checkpoint - odebug "Verifying appcast has :checkpoint key" - add_error "a checkpoint sha256 is required for appcast" unless cask.appcast.checkpoint - end - - def check_appcast_http_code - odebug "Verifying appcast returns 200 HTTP response code" - result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", Hbc::URL::FAKE_USER_AGENT, "--output", "/dev/null", "--write-out", "%{http_code}", cask.appcast], print_stderr: false) - if result.success? - http_code = result.stdout.chomp - add_warning "unexpected HTTP response code retrieving appcast: #{http_code}" unless http_code == "200" - else - add_warning "error retrieving appcast: #{result.stderr}" + def check_token_conflicts? + @check_token_conflicts end - end - def check_appcast_checkpoint_accuracy - odebug "Verifying appcast checkpoint is accurate" - result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", Hbc::URL::FAKE_USER_AGENT, cask.appcast], print_stderr: false) - if result.success? - processed_appcast_text = result.stdout.gsub(%r{[^<]*}, "") - # This step is necessary to replicate running `sed` from the command line - processed_appcast_text << "\n" unless processed_appcast_text.end_with?("\n") - expected = cask.appcast.checkpoint - actual = Digest::SHA2.hexdigest(processed_appcast_text) - add_warning <<-EOS.undent unless expected == actual - appcast checkpoint mismatch - Expected: #{expected} - Actual: #{actual} - EOS - else - add_warning "error retrieving appcast: #{result.stderr}" + def run! + check_required_stanzas + check_version + check_sha256 + check_appcast + check_url + check_generic_artifacts + check_token_conflicts + check_download + self + rescue StandardError => e + odebug "#{e.message}\n#{e.backtrace.join("\n")}" + add_error "exception while auditing #{cask}: #{e.message}" + self end - end - def check_url - return unless cask.url - check_download_url_format - end - - def check_download_url_format - odebug "Auditing URL format" - if bad_sourceforge_url? - add_warning "SourceForge URL format incorrect. See https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/url.md#sourceforgeosdn-urls" - elsif bad_osdn_url? - add_warning "OSDN URL format incorrect. See https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/url.md#sourceforgeosdn-urls" + def success? + !(errors? || warnings?) end - end - def bad_url_format?(regex, valid_formats_array) - return false unless cask.url.to_s =~ regex - valid_formats_array.none? { |format| cask.url.to_s =~ format } - end + def summary_header + "audit for #{cask}" + end - def bad_sourceforge_url? - bad_url_format?(%r{sourceforge}, - [ - %r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z}, - %r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)\/)}, - # special cases: cannot find canonical format URL - %r{\Ahttps?://brushviewer\.sourceforge\.net/brushviewql\.zip\Z}, - %r{\Ahttps?://doublecommand\.sourceforge\.net/files/}, - %r{\Ahttps?://excalibur\.sourceforge\.net/get\.php\?id=}, - ]) - end + private - def bad_osdn_url? - bad_url_format?(%r{osd}, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}]) - end - - def check_generic_artifacts - cask.artifacts[:artifact].each do |source, target_hash| - unless target_hash.is_a?(Hash) && target_hash[:target] - add_error "target required for generic artifact #{source}" - next + def check_required_stanzas + odebug "Auditing required stanzas" + %i{version sha256 url homepage}.each do |sym| + add_error "a #{sym} stanza is required" unless cask.send(sym) end - add_error "target must be absolute path for generic artifact #{source}" unless Pathname.new(target_hash[:target]).absolute? + add_error "a license stanza is required (:unknown is OK)" unless cask.license + add_error "at least one name stanza is required" if cask.name.empty? + # TODO: specific DSL knowledge should not be spread around in various files like this + # TODO: nested_container should not still be a pseudo-artifact at this point + installable_artifacts = cask.artifacts.reject { |k| [:uninstall, :zap, :nested_container].include?(k) } + add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty? end - end - def check_token_conflicts - return unless check_token_conflicts? - return unless core_formula_names.include?(cask.token) - add_warning "possible duplicate, cask token conflicts with Homebrew core formula: #{core_formula_url}" - end + def check_version + return unless cask.version + check_no_string_version_latest + end - def core_tap - @core_tap ||= CoreTap.instance - end + def check_no_string_version_latest + odebug "Verifying version :latest does not appear as a string ('latest')" + return unless cask.version.raw_version == "latest" + add_error "you should use version :latest instead of version 'latest'" + end - def core_formula_names - core_tap.formula_names - end + def check_sha256 + return unless cask.sha256 + check_sha256_no_check_if_latest + check_sha256_actually_256 + check_sha256_invalid + end - def core_formula_url - "#{core_tap.default_remote}/blob/master/Formula/#{cask.token}.rb" - end + def check_sha256_no_check_if_latest + odebug "Verifying sha256 :no_check with version :latest" + return unless cask.version.latest? && cask.sha256 != :no_check + add_error "you should use sha256 :no_check when version is :latest" + end - def check_download - return unless download && cask.url - odebug "Auditing download" - downloaded_path = download.perform - Hbc::Verify.all(cask, downloaded_path) - rescue => e - add_error "download not possible: #{e.message}" + def check_sha256_actually_256(sha256: cask.sha256, stanza: "sha256") + odebug "Verifying #{stanza} string is a legal SHA-256 digest" + return unless sha256.is_a?(String) + return if sha256.length == 64 && sha256[%r{^[0-9a-f]+$}i] + add_error "#{stanza} string must be of 64 hexadecimal characters" + end + + def check_sha256_invalid(sha256: cask.sha256, stanza: "sha256") + odebug "Verifying #{stanza} is not a known invalid value" + empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + return unless sha256 == empty_sha256 + add_error "cannot use the sha256 for an empty string in #{stanza}: #{empty_sha256}" + end + + def check_appcast + return unless cask.appcast + odebug "Auditing appcast" + check_appcast_has_checkpoint + return unless cask.appcast.checkpoint + check_sha256_actually_256(sha256: cask.appcast.checkpoint, stanza: "appcast :checkpoint") + check_sha256_invalid(sha256: cask.appcast.checkpoint, stanza: "appcast :checkpoint") + return unless download + check_appcast_http_code + check_appcast_checkpoint_accuracy + end + + def check_appcast_has_checkpoint + odebug "Verifying appcast has :checkpoint key" + add_error "a checkpoint sha256 is required for appcast" unless cask.appcast.checkpoint + end + + def check_appcast_http_code + odebug "Verifying appcast returns 200 HTTP response code" + result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", URL::FAKE_USER_AGENT, "--output", "/dev/null", "--write-out", "%{http_code}", cask.appcast], print_stderr: false) + if result.success? + http_code = result.stdout.chomp + add_warning "unexpected HTTP response code retrieving appcast: #{http_code}" unless http_code == "200" + else + add_warning "error retrieving appcast: #{result.stderr}" + end + end + + def check_appcast_checkpoint_accuracy + odebug "Verifying appcast checkpoint is accurate" + result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", URL::FAKE_USER_AGENT, cask.appcast], print_stderr: false) + if result.success? + processed_appcast_text = result.stdout.gsub(%r{[^<]*}, "") + # 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 diff --git a/Library/Homebrew/cask/lib/hbc/auditor.rb b/Library/Homebrew/cask/lib/hbc/auditor.rb index 89947c1aa3..6b0c1c4764 100644 --- a/Library/Homebrew/cask/lib/hbc/auditor.rb +++ b/Library/Homebrew/cask/lib/hbc/auditor.rb @@ -1,10 +1,12 @@ -class Hbc::Auditor - def self.audit(cask, audit_download: false, check_token_conflicts: false) - download = audit_download && Hbc::Download.new(cask) - audit = Hbc::Audit.new(cask, download: download, - check_token_conflicts: check_token_conflicts) - audit.run! - puts audit.summary - audit.success? +module Hbc + class Auditor + def self.audit(cask, audit_download: false, check_token_conflicts: false) + download = audit_download && Download.new(cask) + audit = Audit.new(cask, download: download, + check_token_conflicts: check_token_conflicts) + audit.run! + puts audit.summary + audit.success? + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cache.rb b/Library/Homebrew/cask/lib/hbc/cache.rb index 7ae5fcc8cd..db3ec536b9 100644 --- a/Library/Homebrew/cask/lib/hbc/cache.rb +++ b/Library/Homebrew/cask/lib/hbc/cache.rb @@ -1,34 +1,36 @@ -module Hbc::Cache - module_function +module Hbc + module Cache + module_function - def ensure_cache_exists - return if Hbc.cache.exist? + def ensure_cache_exists + return if Hbc.cache.exist? - odebug "Creating Cache at #{Hbc.cache}" - Hbc.cache.mkpath - end - - def migrate_legacy_cache - return unless Hbc.legacy_cache.exist? - - ohai "Migrating cached files to #{Hbc.cache}..." - Hbc.legacy_cache.children.select(&:symlink?).each do |symlink| - file = symlink.readlink - - new_name = file.basename - .sub(%r{\-((?:(\d|#{Hbc::DSL::Version::DIVIDER_REGEX})*\-\2*)*[^\-]+)$}x, - '--\1') - - renamed_file = Hbc.cache.join(new_name) - - if file.exist? - puts "#{file} -> #{renamed_file}" - FileUtils.mv(file, renamed_file) - end - - FileUtils.rm(symlink) + odebug "Creating Cache at #{Hbc.cache}" + Hbc.cache.mkpath end - FileUtils.remove_entry_secure(Hbc.legacy_cache) + def migrate_legacy_cache + return unless Hbc.legacy_cache.exist? + + ohai "Migrating cached files to #{Hbc.cache}..." + Hbc.legacy_cache.children.select(&:symlink?).each do |symlink| + file = symlink.readlink + + new_name = file.basename + .sub(%r{\-((?:(\d|#{DSL::Version::DIVIDER_REGEX})*\-\2*)*[^\-]+)$}x, + '--\1') + + renamed_file = Hbc.cache.join(new_name) + + if file.exist? + puts "#{file} -> #{renamed_file}" + FileUtils.mv(file, renamed_file) + end + + FileUtils.rm(symlink) + end + + FileUtils.remove_entry_secure(Hbc.legacy_cache) + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cask.rb b/Library/Homebrew/cask/lib/hbc/cask.rb index f19e5b81fc..756b05b83f 100644 --- a/Library/Homebrew/cask/lib/hbc/cask.rb +++ b/Library/Homebrew/cask/lib/hbc/cask.rb @@ -2,114 +2,116 @@ require "forwardable" require "hbc/dsl" -class Hbc::Cask - extend Forwardable +module Hbc + class Cask + extend Forwardable - attr_reader :token, :sourcefile_path - def initialize(token, sourcefile_path: nil, dsl: nil, &block) - @token = token - @sourcefile_path = sourcefile_path - @dsl = dsl || Hbc::DSL.new(@token) - @dsl.instance_eval(&block) if block_given? - end - - Hbc::DSL::DSL_METHODS.each do |method_name| - define_method(method_name) { @dsl.send(method_name) } - end - - METADATA_SUBDIR = ".metadata".freeze - - def metadata_master_container_path - @metadata_master_container_path ||= caskroom_path.join(METADATA_SUBDIR) - end - - def metadata_versioned_container_path - cask_version = version ? version : :unknown - metadata_master_container_path.join(cask_version.to_s) - end - - def metadata_path(timestamp = :latest, create = false) - return nil unless metadata_versioned_container_path.respond_to?(:join) - if create && timestamp == :latest - raise Hbc::CaskError, "Cannot create metadata path when timestamp is :latest" + attr_reader :token, :sourcefile_path + def initialize(token, sourcefile_path: nil, dsl: nil, &block) + @token = token + @sourcefile_path = sourcefile_path + @dsl = dsl || DSL.new(@token) + @dsl.instance_eval(&block) if block_given? end - path = if timestamp == :latest - Pathname.glob(metadata_versioned_container_path.join("*")).sort.last - elsif timestamp == :now - Hbc::Utils.nowstamp_metadata_path(metadata_versioned_container_path) - else - metadata_versioned_container_path.join(timestamp) - end - if create - odebug "Creating metadata directory #{path}" - FileUtils.mkdir_p path + + DSL::DSL_METHODS.each do |method_name| + define_method(method_name) { @dsl.send(method_name) } end - path - end - def metadata_subdir(leaf, timestamp = :latest, create = false) - if create && timestamp == :latest - raise Hbc::CaskError, "Cannot create metadata subdir when timestamp is :latest" + METADATA_SUBDIR = ".metadata".freeze + + def metadata_master_container_path + @metadata_master_container_path ||= caskroom_path.join(METADATA_SUBDIR) end - unless leaf.respond_to?(:length) && !leaf.empty? - raise Hbc::CaskError, "Cannot create metadata subdir for empty leaf" + + def metadata_versioned_container_path + cask_version = version ? version : :unknown + metadata_master_container_path.join(cask_version.to_s) end - parent = metadata_path(timestamp, create) - return nil unless parent.respond_to?(:join) - subdir = parent.join(leaf) - if create - odebug "Creating metadata subdirectory #{subdir}" - FileUtils.mkdir_p subdir + + def metadata_path(timestamp = :latest, create = false) + return nil unless metadata_versioned_container_path.respond_to?(:join) + if create && timestamp == :latest + raise CaskError, "Cannot create metadata path when timestamp is :latest" + end + path = if timestamp == :latest + Pathname.glob(metadata_versioned_container_path.join("*")).sort.last + elsif timestamp == :now + Utils.nowstamp_metadata_path(metadata_versioned_container_path) + else + metadata_versioned_container_path.join(timestamp) + end + if create + odebug "Creating metadata directory #{path}" + FileUtils.mkdir_p path + end + path end - subdir - end - def timestamped_versions - Pathname.glob(metadata_master_container_path.join("*", "*")) - .map { |p| p.relative_path_from(metadata_master_container_path) } - .sort_by(&:basename) # sort by timestamp - .map(&:split) - end + def metadata_subdir(leaf, timestamp = :latest, create = false) + if create && timestamp == :latest + raise CaskError, "Cannot create metadata subdir when timestamp is :latest" + end + unless leaf.respond_to?(:length) && !leaf.empty? + raise CaskError, "Cannot create metadata subdir for empty leaf" + end + parent = metadata_path(timestamp, create) + return nil unless parent.respond_to?(:join) + subdir = parent.join(leaf) + if create + odebug "Creating metadata subdirectory #{subdir}" + FileUtils.mkdir_p subdir + end + subdir + end - def versions - timestamped_versions.map(&:first) - .reverse - .uniq - .reverse - end + def timestamped_versions + Pathname.glob(metadata_master_container_path.join("*", "*")) + .map { |p| p.relative_path_from(metadata_master_container_path) } + .sort_by(&:basename) # sort by timestamp + .map(&:split) + end - def installed? - !versions.empty? - end + def versions + timestamped_versions.map(&:first) + .reverse + .uniq + .reverse + end - def to_s - @token - end + def installed? + !versions.empty? + end - def dumpcask - return unless Hbc.respond_to?(:debug) - return unless Hbc.debug + def to_s + @token + end - odebug "Cask instance dumps in YAML:" - odebug "Cask instance toplevel:", to_yaml - [ - :name, - :homepage, - :url, - :appcast, - :version, - :license, - :sha256, - :artifacts, - :caveats, - :depends_on, - :conflicts_with, - :container, - :gpg, - :accessibility_access, - :auto_updates, - ].each do |method| - odebug "Cask instance method '#{method}':", send(method).to_yaml + def dumpcask + return unless Hbc.respond_to?(:debug) + return unless Hbc.debug + + odebug "Cask instance dumps in YAML:" + odebug "Cask instance toplevel:", to_yaml + [ + :name, + :homepage, + :url, + :appcast, + :version, + :license, + :sha256, + :artifacts, + :caveats, + :depends_on, + :conflicts_with, + :container, + :gpg, + :accessibility_access, + :auto_updates, + ].each do |method| + odebug "Cask instance method '#{method}':", send(method).to_yaml + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/cask_dependencies.rb b/Library/Homebrew/cask/lib/hbc/cask_dependencies.rb index 6cbfd05af2..4b4f042d87 100644 --- a/Library/Homebrew/cask/lib/hbc/cask_dependencies.rb +++ b/Library/Homebrew/cask/lib/hbc/cask_dependencies.rb @@ -1,33 +1,35 @@ require "hbc/topological_hash" -class Hbc::CaskDependencies - attr_reader :cask, :graph, :sorted +module Hbc + class CaskDependencies + attr_reader :cask, :graph, :sorted - def initialize(cask) - @cask = cask - @graph = graph_dependencies - @sorted = sort - end + def initialize(cask) + @cask = cask + @graph = graph_dependencies + @sorted = sort + end - def graph_dependencies - deps_in = ->(csk) { csk.depends_on ? csk.depends_on.cask || [] : [] } - walk = lambda { |acc, deps| - deps.each do |dep| - next if acc.key?(dep) - succs = deps_in.call Hbc.load(dep) - acc[dep] = succs - walk.call(acc, succs) - end - acc - } + def graph_dependencies + deps_in = ->(csk) { csk.depends_on ? csk.depends_on.cask || [] : [] } + walk = lambda { |acc, deps| + deps.each do |dep| + next if acc.key?(dep) + succs = deps_in.call Hbc.load(dep) + acc[dep] = succs + walk.call(acc, succs) + end + acc + } - graphed = walk.call({}, @cask.depends_on.cask) - Hbc::TopologicalHash[graphed] - end + graphed = walk.call({}, @cask.depends_on.cask) + TopologicalHash[graphed] + end - def sort - @graph.tsort - rescue TSort::Cyclic - raise Hbc::CaskCyclicCaskDependencyError, @cask.token + def sort + @graph.tsort + rescue TSort::Cyclic + raise CaskCyclicCaskDependencyError, @cask.token + end end end diff --git a/Library/Homebrew/cask/lib/hbc/caskroom.rb b/Library/Homebrew/cask/lib/hbc/caskroom.rb index 5d26ac222a..583cac34a0 100644 --- a/Library/Homebrew/cask/lib/hbc/caskroom.rb +++ b/Library/Homebrew/cask/lib/hbc/caskroom.rb @@ -1,41 +1,43 @@ -module Hbc::Caskroom - module_function +module Hbc + module Caskroom + module_function - def migrate_caskroom_from_repo_to_prefix - repo_caskroom = Hbc.homebrew_repository.join("Caskroom") - return if Hbc.caskroom.exist? - return unless repo_caskroom.directory? + def migrate_caskroom_from_repo_to_prefix + repo_caskroom = Hbc.homebrew_repository.join("Caskroom") + return if Hbc.caskroom.exist? + return unless repo_caskroom.directory? - ohai "Moving Caskroom from HOMEBREW_REPOSITORY to HOMEBREW_PREFIX" + ohai "Moving Caskroom from HOMEBREW_REPOSITORY to HOMEBREW_PREFIX" - if Hbc.caskroom.parent.writable? - FileUtils.mv repo_caskroom, Hbc.caskroom - else - opoo "#{Hbc.caskroom.parent} is not writable, sudo is needed to move the Caskroom." - system "/usr/bin/sudo", "--", "/bin/mv", "--", repo_caskroom.to_s, Hbc.caskroom.parent.to_s - end - end - - def ensure_caskroom_exists - return if Hbc.caskroom.exist? - - ohai "Creating Caskroom at #{Hbc.caskroom}" - if Hbc.caskroom.parent.writable? - Hbc.caskroom.mkpath - else - ohai "We'll set permissions properly so we won't need sudo in the future" - toplevel_dir = Hbc.caskroom - toplevel_dir = toplevel_dir.parent until toplevel_dir.parent.root? - unless toplevel_dir.directory? - # If a toplevel dir such as '/opt' must be created, enforce standard permissions. - # sudo in system is rude. - system "/usr/bin/sudo", "--", "/bin/mkdir", "--", toplevel_dir - system "/usr/bin/sudo", "--", "/bin/chmod", "--", "0775", toplevel_dir + if Hbc.caskroom.parent.writable? + FileUtils.mv repo_caskroom, Hbc.caskroom + else + opoo "#{Hbc.caskroom.parent} is not writable, sudo is needed to move the Caskroom." + system "/usr/bin/sudo", "--", "/bin/mv", "--", repo_caskroom.to_s, Hbc.caskroom.parent.to_s end - # sudo in system is rude. - system "/usr/bin/sudo", "--", "/bin/mkdir", "-p", "--", Hbc.caskroom - unless Hbc.caskroom.parent == toplevel_dir - system "/usr/bin/sudo", "--", "/usr/sbin/chown", "-R", "--", "#{Hbc::Utils.current_user}:staff", Hbc.caskroom.parent.to_s + end + + def ensure_caskroom_exists + return if Hbc.caskroom.exist? + + ohai "Creating Caskroom at #{Hbc.caskroom}" + if Hbc.caskroom.parent.writable? + Hbc.caskroom.mkpath + else + ohai "We'll set permissions properly so we won't need sudo in the future" + toplevel_dir = Hbc.caskroom + toplevel_dir = toplevel_dir.parent until toplevel_dir.parent.root? + unless toplevel_dir.directory? + # If a toplevel dir such as '/opt' must be created, enforce standard permissions. + # sudo in system is rude. + system "/usr/bin/sudo", "--", "/bin/mkdir", "--", toplevel_dir + system "/usr/bin/sudo", "--", "/bin/chmod", "--", "0775", toplevel_dir + end + # sudo in system is rude. + system "/usr/bin/sudo", "--", "/bin/mkdir", "-p", "--", Hbc.caskroom + unless Hbc.caskroom.parent == toplevel_dir + system "/usr/bin/sudo", "--", "/usr/sbin/chown", "-R", "--", "#{Utils.current_user}:staff", Hbc.caskroom.parent.to_s + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/caveats.rb b/Library/Homebrew/cask/lib/hbc/caveats.rb index 04bbcf2186..a854291513 100644 --- a/Library/Homebrew/cask/lib/hbc/caveats.rb +++ b/Library/Homebrew/cask/lib/hbc/caveats.rb @@ -1,12 +1,14 @@ -class Hbc::Caveats - def initialize(block) - @block = block - end +module Hbc + class Caveats + def initialize(block) + @block = block + end - def eval_and_print(cask) - dsl = Hbc::DSL::Caveats.new(cask) - retval = dsl.instance_eval(&@block) - return if retval.nil? - puts retval.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n") + def eval_and_print(cask) + dsl = DSL::Caveats.new(cask) + retval = dsl.instance_eval(&@block) + return if retval.nil? + puts retval.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n") + end end end diff --git a/Library/Homebrew/cask/lib/hbc/checkable.rb b/Library/Homebrew/cask/lib/hbc/checkable.rb index 9c12f80cab..42c47ea78b 100644 --- a/Library/Homebrew/cask/lib/hbc/checkable.rb +++ b/Library/Homebrew/cask/lib/hbc/checkable.rb @@ -1,51 +1,53 @@ -module Hbc::Checkable - def errors - Array(@errors) - end - - def warnings - Array(@warnings) - end - - def add_error(message) - @errors ||= [] - @errors << message - end - - def add_warning(message) - @warnings ||= [] - @warnings << message - end - - def errors? - Array(@errors).any? - end - - def warnings? - Array(@warnings).any? - end - - def result - if errors? - "#{Tty.red}failed#{Tty.reset}" - elsif warnings? - "#{Tty.yellow}warning#{Tty.reset}" - else - "#{Tty.green}passed#{Tty.reset}" - end - end - - def summary - summary = ["#{summary_header}: #{result}"] - - errors.each do |error| - summary << " #{Tty.red}-#{Tty.reset} #{error}" +module Hbc + module Checkable + def errors + Array(@errors) end - warnings.each do |warning| - summary << " #{Tty.yellow}-#{Tty.reset} #{warning}" + def warnings + Array(@warnings) end - summary.join("\n") + def add_error(message) + @errors ||= [] + @errors << message + end + + def add_warning(message) + @warnings ||= [] + @warnings << message + end + + def errors? + Array(@errors).any? + end + + def warnings? + Array(@warnings).any? + end + + def result + if errors? + "#{Tty.red}failed#{Tty.reset}" + elsif warnings? + "#{Tty.yellow}warning#{Tty.reset}" + else + "#{Tty.green}passed#{Tty.reset}" + end + end + + def summary + summary = ["#{summary_header}: #{result}"] + + errors.each do |error| + summary << " #{Tty.red}-#{Tty.reset} #{error}" + end + + warnings.each do |warning| + summary << " #{Tty.yellow}-#{Tty.reset} #{warning}" + end + + summary.join("\n") + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli.rb b/Library/Homebrew/cask/lib/hbc/cli.rb index 7b3b886b91..3f67e131dd 100644 --- a/Library/Homebrew/cask/lib/hbc/cli.rb +++ b/Library/Homebrew/cask/lib/hbc/cli.rb @@ -1,5 +1,3 @@ -class Hbc::CLI; end - require "optparse" require "shellwords" @@ -28,248 +26,250 @@ require "hbc/cli/internal_dump" require "hbc/cli/internal_help" require "hbc/cli/internal_stanza" -class Hbc::CLI - ALIASES = { - "ls" => "list", - "homepage" => "home", - "-S" => "search", # verb starting with "-" is questionable - "up" => "update", - "instal" => "install", # gem does the same - "rm" => "uninstall", - "remove" => "uninstall", - "abv" => "info", - "dr" => "doctor", - # aliases from Homebrew that we don't (yet) support - # 'ln' => 'link', - # 'configure' => 'diy', - # '--repo' => '--repository', - # 'environment' => '--env', - # '-c1' => '--config', +module Hbc + class CLI + ALIASES = { + "ls" => "list", + "homepage" => "home", + "-S" => "search", # verb starting with "-" is questionable + "up" => "update", + "instal" => "install", # gem does the same + "rm" => "uninstall", + "remove" => "uninstall", + "abv" => "info", + "dr" => "doctor", + # aliases from Homebrew that we don't (yet) support + # 'ln' => 'link', + # 'configure' => 'diy', + # '--repo' => '--repository', + # 'environment' => '--env', + # '-c1' => '--config', + }.freeze + + OPTIONS = { + "--caskroom=" => :caskroom=, + "--appdir=" => :appdir=, + "--colorpickerdir=" => :colorpickerdir=, + "--prefpanedir=" => :prefpanedir=, + "--qlplugindir=" => :qlplugindir=, + "--fontdir=" => :fontdir=, + "--servicedir=" => :servicedir=, + "--input_methoddir=" => :input_methoddir=, + "--internet_plugindir=" => :internet_plugindir=, + "--audio_unit_plugindir=" => :audio_unit_plugindir=, + "--vst_plugindir=" => :vst_plugindir=, + "--vst3_plugindir=" => :vst3_plugindir=, + "--screen_saverdir=" => :screen_saverdir=, + }.freeze + + FLAGS = { + "--no-binaries" => :no_binaries=, + "--debug" => :debug=, + "--verbose" => :verbose=, + "--outdated" => :cleanup_outdated=, + "--help" => :help=, }.freeze - OPTIONS = { - "--caskroom=" => :caskroom=, - "--appdir=" => :appdir=, - "--colorpickerdir=" => :colorpickerdir=, - "--prefpanedir=" => :prefpanedir=, - "--qlplugindir=" => :qlplugindir=, - "--fontdir=" => :fontdir=, - "--servicedir=" => :servicedir=, - "--input_methoddir=" => :input_methoddir=, - "--internet_plugindir=" => :internet_plugindir=, - "--audio_unit_plugindir=" => :audio_unit_plugindir=, - "--vst_plugindir=" => :vst_plugindir=, - "--vst3_plugindir=" => :vst3_plugindir=, - "--screen_saverdir=" => :screen_saverdir=, - }.freeze - - FLAGS = { - "--no-binaries" => :no_binaries=, - "--debug" => :debug=, - "--verbose" => :verbose=, - "--outdated" => :cleanup_outdated=, - "--help" => :help=, - }.freeze - - def self.command_classes - @command_classes ||= Hbc::CLI.constants - .map(&Hbc::CLI.method(:const_get)) - .select { |sym| sym.respond_to?(:run) } - end - - def self.commands - @commands ||= command_classes.map(&:command_name) - end - - def self.lookup_command(command_string) - @lookup ||= Hash[commands.zip(command_classes)] - command_string = ALIASES.fetch(command_string, command_string) - @lookup.fetch(command_string, command_string) - end - - # modified from Homebrew - def self.require?(path) - require path - true # OK if already loaded - rescue LoadError => e - # HACK: :( because we should raise on syntax errors - # but not if the file doesn't exist. - # TODO: make robust! - raise unless e.to_s.include? path - end - - def self.should_init?(command) - (command.is_a? Class) && (command < Hbc::CLI::Base) && command.needs_init? - end - - def self.run_command(command, *rest) - if command.respond_to?(:run) - # usual case: built-in command verb - command.run(*rest) - elsif require? Hbc::Utils.which("brewcask-#{command}.rb").to_s - # external command as Ruby library on PATH, Homebrew-style - elsif command.to_s.include?("/") && require?(command.to_s) - # external command as Ruby library with literal path, useful - # for development and troubleshooting - sym = Pathname.new(command.to_s).basename(".rb").to_s.capitalize - klass = begin - Hbc::CLI.const_get(sym) - rescue NameError - nil - end - - if klass.respond_to?(:run) - # invoke "run" on a Ruby library which follows our coding conventions - # other Ruby libraries must do everything via "require" - klass.run(*rest) - end - elsif Hbc::Utils.which "brewcask-#{command}" - # arbitrary external executable on PATH, Homebrew-style - exec "brewcask-#{command}", *ARGV[1..-1] - elsif Pathname.new(command.to_s).executable? && - command.to_s.include?("/") && - !command.to_s.match(%r{\.rb$}) - # arbitrary external executable with literal path, useful - # for development and troubleshooting - exec command, *ARGV[1..-1] - else - # failure - Hbc::CLI::NullCommand.new(command).run + def self.command_classes + @command_classes ||= self.constants + .map(&method(:const_get)) + .select { |sym| sym.respond_to?(:run) } end - end - def self.process(arguments) - command_string, *rest = *arguments - rest = process_options(rest) - command = Hbc.help ? "help" : lookup_command(command_string) - Hbc.default_tap.install unless Hbc.default_tap.installed? - Hbc.init if should_init?(command) - run_command(command, *rest) - rescue Hbc::CaskError, Hbc::CaskSha256MismatchError => e - msg = e.message - msg << e.backtrace.join("\n") if Hbc.debug - onoe msg - exit 1 - rescue StandardError, ScriptError, NoMemoryError => e - msg = e.message - msg << Hbc::Utils.error_message_with_suggestions - msg << e.backtrace.join("\n") - onoe msg - exit 1 - end - - def self.nice_listing(cask_list) - cask_taps = {} - cask_list.each do |c| - user, repo, token = c.split "/" - repo.sub!(%r{^homebrew-}i, "") - cask_taps[token] ||= [] - cask_taps[token].push "#{user}/#{repo}" + def self.commands + @commands ||= command_classes.map(&:command_name) end - list = [] - cask_taps.each do |token, taps| - if taps.length == 1 - list.push token + + def self.lookup_command(command_string) + @lookup ||= Hash[commands.zip(command_classes)] + command_string = ALIASES.fetch(command_string, command_string) + @lookup.fetch(command_string, command_string) + end + + # modified from Homebrew + def self.require?(path) + require path + true # OK if already loaded + rescue LoadError => e + # HACK: :( because we should raise on syntax errors + # but not if the file doesn't exist. + # TODO: make robust! + raise unless e.to_s.include? path + end + + def self.should_init?(command) + (command.is_a? Class) && (command < CLI::Base) && command.needs_init? + end + + def self.run_command(command, *rest) + if command.respond_to?(:run) + # usual case: built-in command verb + command.run(*rest) + elsif require? Utils.which("brewcask-#{command}.rb").to_s + # external command as Ruby library on PATH, Homebrew-style + elsif command.to_s.include?("/") && require?(command.to_s) + # external command as Ruby library with literal path, useful + # for development and troubleshooting + sym = Pathname.new(command.to_s).basename(".rb").to_s.capitalize + klass = begin + self.const_get(sym) + rescue NameError + nil + end + + if klass.respond_to?(:run) + # invoke "run" on a Ruby library which follows our coding conventions + # other Ruby libraries must do everything via "require" + klass.run(*rest) + end + elsif Utils.which "brewcask-#{command}" + # arbitrary external executable on PATH, Homebrew-style + exec "brewcask-#{command}", *ARGV[1..-1] + elsif Pathname.new(command.to_s).executable? && + command.to_s.include?("/") && + !command.to_s.match(%r{\.rb$}) + # arbitrary external executable with literal path, useful + # for development and troubleshooting + exec command, *ARGV[1..-1] else - taps.each { |r| list.push [r, token].join "/" } + # failure + NullCommand.new(command).run end end - list.sort - end - def self.parser - # If you modify these arguments, please update USAGE.md - @parser ||= OptionParser.new do |opts| - OPTIONS.each do |option, method| - opts.on("#{option}" "PATH", Pathname) do |path| - Hbc.public_send(method, path) + def self.process(arguments) + command_string, *rest = *arguments + rest = process_options(rest) + command = Hbc.help ? "help" : lookup_command(command_string) + Hbc.default_tap.install unless Hbc.default_tap.installed? + Hbc.init if should_init?(command) + run_command(command, *rest) + rescue CaskError, CaskSha256MismatchError => e + msg = e.message + msg << e.backtrace.join("\n") if Hbc.debug + onoe msg + exit 1 + rescue StandardError, ScriptError, NoMemoryError => e + msg = e.message + msg << Utils.error_message_with_suggestions + msg << e.backtrace.join("\n") + onoe msg + exit 1 + end + + def self.nice_listing(cask_list) + cask_taps = {} + cask_list.each do |c| + user, repo, token = c.split "/" + repo.sub!(%r{^homebrew-}i, "") + cask_taps[token] ||= [] + cask_taps[token].push "#{user}/#{repo}" + end + list = [] + cask_taps.each do |token, taps| + if taps.length == 1 + list.push token + else + taps.each { |r| list.push [r, token].join "/" } + end + end + list.sort + end + + def self.parser + # If you modify these arguments, please update USAGE.md + @parser ||= OptionParser.new do |opts| + OPTIONS.each do |option, method| + opts.on("#{option}" "PATH", Pathname) do |path| + Hbc.public_send(method, path) + end + end + + opts.on("--binarydir=PATH") do + opoo <<-EOS.undent + Option --binarydir is obsolete! + Homebrew-Cask now uses the same location as your Homebrew installation for executable links. + EOS + end + + FLAGS.each do |flag, method| + opts.on(flag) do + Hbc.public_send(method, true) + end + end + + opts.on("--version") do + raise OptionParser::InvalidOption # override default handling of --version + end + end + end + + def self.process_options(args) + all_args = Shellwords.shellsplit(ENV["HOMEBREW_CASK_OPTS"] || "") + args + remaining = [] + until all_args.empty? + begin + head = all_args.shift + remaining.concat(parser.parse([head])) + rescue OptionParser::InvalidOption + remaining << head + retry + rescue OptionParser::MissingArgument + raise CaskError, "The option '#{head}' requires an argument" + rescue OptionParser::AmbiguousOption + raise CaskError, "There is more than one possible option that starts with '#{head}'" end end - opts.on("--binarydir=PATH") do - opoo <<-EOS.undent - Option --binarydir is obsolete! - Homebrew-Cask now uses the same location as your Homebrew installation for executable links. + # for compat with Homebrew, not certain if this is desirable + Hbc.verbose = true if !ENV["VERBOSE"].nil? || !ENV["HOMEBREW_VERBOSE"].nil? + + remaining + end + + class NullCommand + def initialize(attempted_verb) + @attempted_verb = attempted_verb + end + + def run(*args) + if args.include?("--version") || @attempted_verb == "--version" + puts Hbc.full_version + else + purpose + usage + unless @attempted_verb.to_s.strip.empty? || @attempted_verb == "help" + raise CaskError, "Unknown command: #{@attempted_verb}" + end + end + end + + def purpose + puts <<-EOS.undent + brew-cask provides a friendly homebrew-style CLI workflow for the + administration of macOS applications distributed as binaries. + EOS end - FLAGS.each do |flag, method| - opts.on(flag) do - Hbc.public_send(method, true) + def usage + max_command_len = CLI.commands.map(&:length).max + + puts "Commands:\n\n" + CLI.command_classes.each do |klass| + next unless klass.visible + puts " #{klass.command_name.ljust(max_command_len)} #{_help_for(klass)}" end + puts %Q{\nSee also "man brew-cask"} end - opts.on("--version") do - raise OptionParser::InvalidOption # override default handling of --version + def help + "" end - end - end - def self.process_options(args) - all_args = Shellwords.shellsplit(ENV["HOMEBREW_CASK_OPTS"] || "") + args - remaining = [] - until all_args.empty? - begin - head = all_args.shift - remaining.concat(parser.parse([head])) - rescue OptionParser::InvalidOption - remaining << head - retry - rescue OptionParser::MissingArgument - raise Hbc::CaskError, "The option '#{head}' requires an argument" - rescue OptionParser::AmbiguousOption - raise Hbc::CaskError, "There is more than one possible option that starts with '#{head}'" + def _help_for(klass) + klass.respond_to?(:help) ? klass.help : nil end end - - # for compat with Homebrew, not certain if this is desirable - Hbc.verbose = true if !ENV["VERBOSE"].nil? || !ENV["HOMEBREW_VERBOSE"].nil? - - remaining - end - - class NullCommand - def initialize(attempted_verb) - @attempted_verb = attempted_verb - end - - def run(*args) - if args.include?("--version") || @attempted_verb == "--version" - puts Hbc.full_version - else - purpose - usage - unless @attempted_verb.to_s.strip.empty? || @attempted_verb == "help" - raise Hbc::CaskError, "Unknown command: #{@attempted_verb}" - end - end - end - - def purpose - puts <<-EOS.undent - brew-cask provides a friendly homebrew-style CLI workflow for the - administration of macOS applications distributed as binaries. - - EOS - end - - def usage - max_command_len = Hbc::CLI.commands.map(&:length).max - - puts "Commands:\n\n" - Hbc::CLI.command_classes.each do |klass| - next unless klass.visible - puts " #{klass.command_name.ljust(max_command_len)} #{_help_for(klass)}" - end - puts %Q{\nSee also "man brew-cask"} - end - - def help - "" - end - - def _help_for(klass) - klass.respond_to?(:help) ? klass.help : nil - end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/audit.rb b/Library/Homebrew/cask/lib/hbc/cli/audit.rb index 14f3d82540..a06f71c60d 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/audit.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/audit.rb @@ -1,52 +1,56 @@ -class Hbc::CLI::Audit < Hbc::CLI::Base - def self.help - "verifies installability of Casks" - end +module Hbc + class CLI + class Audit < Base + def self.help + "verifies installability of Casks" + end - def self.run(*args) - failed_casks = new(args, Hbc::Auditor).run - return if failed_casks.empty? - raise Hbc::CaskError, "audit failed for casks: #{failed_casks.join(" ")}" - end + def self.run(*args) + failed_casks = new(args, Auditor).run + return if failed_casks.empty? + raise CaskError, "audit failed for casks: #{failed_casks.join(" ")}" + end - def initialize(args, auditor) - @args = args - @auditor = auditor - end + def initialize(args, auditor) + @args = args + @auditor = auditor + end - def run - casks_to_audit.each_with_object([]) do |cask, failed| - failed << cask unless audit(cask) + def run + casks_to_audit.each_with_object([]) do |cask, failed| + failed << cask unless audit(cask) + end + end + + def audit(cask) + odebug "Auditing Cask #{cask}" + @auditor.audit(cask, audit_download: audit_download?, + check_token_conflicts: check_token_conflicts?) + end + + def audit_download? + @args.include?("--download") + end + + def check_token_conflicts? + @args.include?("--token-conflicts") + end + + def casks_to_audit + if cask_tokens.empty? + Hbc.all + else + cask_tokens.map { |token| Hbc.load(token) } + end + end + + def cask_tokens + @cask_tokens ||= self.class.cask_tokens_from(@args) + end + + def self.needs_init? + true + end end end - - def audit(cask) - odebug "Auditing Cask #{cask}" - @auditor.audit(cask, audit_download: audit_download?, - check_token_conflicts: check_token_conflicts?) - end - - def audit_download? - @args.include?("--download") - end - - def check_token_conflicts? - @args.include?("--token-conflicts") - end - - def casks_to_audit - if cask_tokens.empty? - Hbc.all - else - cask_tokens.map { |token| Hbc.load(token) } - end - end - - def cask_tokens - @cask_tokens ||= self.class.cask_tokens_from(@args) - end - - def self.needs_init? - true - end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/base.rb b/Library/Homebrew/cask/lib/hbc/cli/base.rb index af03969af7..6c706d5379 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/base.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/base.rb @@ -1,21 +1,25 @@ -class Hbc::CLI::Base - def self.command_name - @command_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase - end +module Hbc + class CLI + class Base + def self.command_name + @command_name ||= name.sub(%r{^.*:}, "").gsub(%r{(.)([A-Z])}, '\1_\2').downcase + end - def self.visible - true - end + def self.visible + true + end - def self.cask_tokens_from(args) - args.reject { |a| a.empty? || a.chars.first == "-" } - end + def self.cask_tokens_from(args) + args.reject { |a| a.empty? || a.chars.first == "-" } + end - def self.help - "No help available for the #{command_name} command" - end + def self.help + "No help available for the #{command_name} command" + end - def self.needs_init? - false + def self.needs_init? + false + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/cat.rb b/Library/Homebrew/cask/lib/hbc/cli/cat.rb index d6d545c3b5..75f6c1e91e 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/cat.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/cat.rb @@ -1,15 +1,19 @@ -class Hbc::CLI::Cat < Hbc::CLI::Base - def self.run(*args) - cask_tokens = cask_tokens_from(args) - raise Hbc::CaskUnspecifiedError if cask_tokens.empty? - # only respects the first argument - cask_token = cask_tokens.first.sub(%r{\.rb$}i, "") - cask_path = Hbc.path(cask_token) - raise Hbc::CaskUnavailableError, cask_token.to_s unless cask_path.exist? - puts File.open(cask_path, &:read) - end +module Hbc + class CLI + class Cat < Base + def self.run(*args) + cask_tokens = cask_tokens_from(args) + raise CaskUnspecifiedError if cask_tokens.empty? + # only respects the first argument + cask_token = cask_tokens.first.sub(%r{\.rb$}i, "") + cask_path = Hbc.path(cask_token) + raise CaskUnavailableError, cask_token.to_s unless cask_path.exist? + puts File.open(cask_path, &:read) + end - def self.help - "dump raw source of the given Cask to the standard output" + def self.help + "dump raw source of the given Cask to the standard output" + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/cleanup.rb b/Library/Homebrew/cask/lib/hbc/cli/cleanup.rb index 39a90e7028..0296dc96dc 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/cleanup.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/cleanup.rb @@ -1,108 +1,112 @@ -class Hbc::CLI::Cleanup < Hbc::CLI::Base - OUTDATED_DAYS = 10 - OUTDATED_TIMESTAMP = Time.now - (60 * 60 * 24 * OUTDATED_DAYS) +module Hbc + class CLI + class Cleanup < Base + OUTDATED_DAYS = 10 + OUTDATED_TIMESTAMP = Time.now - (60 * 60 * 24 * OUTDATED_DAYS) - def self.help - "cleans up cached downloads and tracker symlinks" - end - - def self.needs_init? - true - end - - def self.run(*args) - if args.empty? - default.cleanup! - else - default.cleanup(args) - end - end - - def self.default - @default ||= new(Hbc.cache, Hbc.cleanup_outdated) - end - - attr_reader :cache_location, :outdated_only - def initialize(cache_location, outdated_only) - @cache_location = Pathname.new(cache_location) - @outdated_only = outdated_only - end - - def cleanup! - remove_cache_files - end - - def cleanup(tokens) - remove_cache_files(*tokens) - end - - def cache_files - return [] unless cache_location.exist? - cache_location.children - .map(&method(:Pathname)) - .reject(&method(:outdated?)) - end - - def outdated?(file) - outdated_only && file && file.stat.mtime > OUTDATED_TIMESTAMP - end - - def incomplete?(file) - file.extname == ".incomplete" - end - - def cache_incompletes - cache_files.select(&method(:incomplete?)) - end - - def cache_completes - cache_files.reject(&method(:incomplete?)) - end - - def disk_cleanup_size - Hbc::Utils.size_in_bytes(cache_files) - end - - def remove_cache_files(*tokens) - message = "Removing cached downloads" - message.concat " for #{tokens.join(", ")}" unless tokens.empty? - message.concat " older than #{OUTDATED_DAYS} days old" if outdated_only - ohai message - - deletable_cache_files = if tokens.empty? - cache_files - else - start_withs = tokens.map { |token| "#{token}--" } - - cache_files.select { |path| - path.basename.to_s.start_with?(*start_withs) - } - end - - delete_paths(deletable_cache_files) - end - - def delete_paths(paths) - cleanup_size = 0 - processed_files = 0 - paths.each do |item| - next unless item.exist? - processed_files += 1 - if Hbc::Utils.file_locked?(item) - puts "skipping: #{item} is locked" - next + def self.help + "cleans up cached downloads and tracker symlinks" end - puts item - item_size = File.size?(item) - cleanup_size += item_size unless item_size.nil? - item.unlink - end - if processed_files.zero? - puts "Nothing to do" - else - disk_space = disk_usage_readable(cleanup_size) - ohai "This operation has freed approximately #{disk_space} of disk space." + def self.needs_init? + true + end + + def self.run(*args) + if args.empty? + default.cleanup! + else + default.cleanup(args) + end + end + + def self.default + @default ||= new(Hbc.cache, Hbc.cleanup_outdated) + end + + attr_reader :cache_location, :outdated_only + def initialize(cache_location, outdated_only) + @cache_location = Pathname.new(cache_location) + @outdated_only = outdated_only + end + + def cleanup! + remove_cache_files + end + + def cleanup(tokens) + remove_cache_files(*tokens) + end + + def cache_files + return [] unless cache_location.exist? + cache_location.children + .map(&method(:Pathname)) + .reject(&method(:outdated?)) + end + + def outdated?(file) + outdated_only && file && file.stat.mtime > OUTDATED_TIMESTAMP + end + + def incomplete?(file) + file.extname == ".incomplete" + end + + def cache_incompletes + cache_files.select(&method(:incomplete?)) + end + + def cache_completes + cache_files.reject(&method(:incomplete?)) + end + + def disk_cleanup_size + Utils.size_in_bytes(cache_files) + end + + def remove_cache_files(*tokens) + message = "Removing cached downloads" + message.concat " for #{tokens.join(", ")}" unless tokens.empty? + message.concat " older than #{OUTDATED_DAYS} days old" if outdated_only + ohai message + + deletable_cache_files = if tokens.empty? + cache_files + else + start_withs = tokens.map { |token| "#{token}--" } + + cache_files.select { |path| + path.basename.to_s.start_with?(*start_withs) + } + end + + delete_paths(deletable_cache_files) + end + + def delete_paths(paths) + cleanup_size = 0 + processed_files = 0 + paths.each do |item| + next unless item.exist? + processed_files += 1 + if Utils.file_locked?(item) + puts "skipping: #{item} is locked" + next + end + puts item + item_size = File.size?(item) + cleanup_size += item_size unless item_size.nil? + item.unlink + end + + if processed_files.zero? + puts "Nothing to do" + else + disk_space = disk_usage_readable(cleanup_size) + ohai "This operation has freed approximately #{disk_space} of disk space." + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/create.rb b/Library/Homebrew/cask/lib/hbc/cli/create.rb index 3c1ac76ed0..14860942f7 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/create.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/create.rb @@ -1,37 +1,41 @@ -class Hbc::CLI::Create < Hbc::CLI::Base - def self.run(*args) - cask_tokens = cask_tokens_from(args) - raise Hbc::CaskUnspecifiedError if cask_tokens.empty? - cask_token = cask_tokens.first.sub(%r{\.rb$}i, "") - cask_path = Hbc.path(cask_token) - odebug "Creating Cask #{cask_token}" +module Hbc + class CLI + class Create < Base + def self.run(*args) + cask_tokens = cask_tokens_from(args) + raise CaskUnspecifiedError if cask_tokens.empty? + cask_token = cask_tokens.first.sub(%r{\.rb$}i, "") + cask_path = Hbc.path(cask_token) + odebug "Creating Cask #{cask_token}" - raise Hbc::CaskAlreadyCreatedError, cask_token if cask_path.exist? + raise CaskAlreadyCreatedError, cask_token if cask_path.exist? - File.open(cask_path, "w") do |f| - f.write template(cask_token) - end + File.open(cask_path, "w") do |f| + f.write template(cask_token) + end - exec_editor cask_path - end - - def self.template(cask_token) - <<-EOS.undent - cask '#{cask_token}' do - version '' - sha256 '' - - url 'https://' - name '' - homepage '' - license :unknown # TODO: change license and remove this comment; ':unknown' is a machine-generated placeholder - - app '' + exec_editor cask_path end - EOS - end - def self.help - "creates the given Cask and opens it in an editor" + def self.template(cask_token) + <<-EOS.undent + cask '#{cask_token}' do + version '' + sha256 '' + + url 'https://' + name '' + homepage '' + license :unknown # TODO: change license and remove this comment; ':unknown' is a machine-generated placeholder + + app '' + end + EOS + end + + def self.help + "creates the given Cask and opens it in an editor" + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/doctor.rb b/Library/Homebrew/cask/lib/hbc/cli/doctor.rb index 7cfccd8b90..34f000b29e 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/doctor.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/doctor.rb @@ -1,205 +1,209 @@ -class Hbc::CLI::Doctor < Hbc::CLI::Base - def self.run - ohai "macOS Release:", render_with_none_as_error(MacOS.full_version) - ohai "Hardware Architecture:", render_with_none_as_error("#{Hardware::CPU.type}-#{Hardware::CPU.bits}") - ohai "Ruby Version:", render_with_none_as_error("#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}") - ohai "Ruby Path:", render_with_none_as_error(RbConfig.ruby) - # TODO: consider removing most Homebrew constants from doctor output - ohai "Homebrew Version:", render_with_none_as_error(homebrew_version) - ohai "Homebrew Executable Path:", render_with_none_as_error(Hbc.homebrew_executable) - ohai "Homebrew Cellar Path:", render_with_none_as_error(homebrew_cellar) - ohai "Homebrew Repository Path:", render_with_none_as_error(homebrew_repository) - ohai "Homebrew Origin:", render_with_none_as_error(homebrew_origin) - ohai "Homebrew-Cask Version:", render_with_none_as_error(Hbc.full_version) - ohai "Homebrew-Cask Install Location:", render_install_location - ohai "Homebrew-Cask Staging Location:", render_staging_location(Hbc.caskroom) - ohai "Homebrew-Cask Cached Downloads:", render_cached_downloads - ohai "Homebrew-Cask Default Tap Path:", render_tap_paths(Hbc.default_tap.path) - ohai "Homebrew-Cask Alternate Cask Taps:", render_tap_paths(alt_taps) - ohai "Homebrew-Cask Default Tap Cask Count:", render_with_none_as_error(default_cask_count) - ohai "Contents of $LOAD_PATH:", render_load_path($LOAD_PATH) - ohai "Contents of $RUBYLIB Environment Variable:", render_env_var("RUBYLIB") - ohai "Contents of $RUBYOPT Environment Variable:", render_env_var("RUBYOPT") - ohai "Contents of $RUBYPATH Environment Variable:", render_env_var("RUBYPATH") - ohai "Contents of $RBENV_VERSION Environment Variable:", render_env_var("RBENV_VERSION") - ohai "Contents of $CHRUBY_VERSION Environment Variable:", render_env_var("CHRUBY_VERSION") - ohai "Contents of $GEM_HOME Environment Variable:", render_env_var("GEM_HOME") - ohai "Contents of $GEM_PATH Environment Variable:", render_env_var("GEM_PATH") - ohai "Contents of $BUNDLE_PATH Environment Variable:", render_env_var("BUNDLE_PATH") - ohai "Contents of $PATH Environment Variable:", render_env_var("PATH") - ohai "Contents of $SHELL Environment Variable:", render_env_var("SHELL") - ohai "Contents of Locale Environment Variables:", render_with_none(locale_variables) - ohai "Running As Privileged User:", render_with_none_as_error(privileged_uid) - end - - def self.alt_taps - Tap.select { |t| t.cask_dir && t != Hbc.default_tap } - .map(&:path) - end - - def self.default_cask_count - Hbc.default_tap.cask_files.count - rescue StandardError - "0 #{error_string "Error reading #{Hbc.default_tap.path}"}" - end - - def self.homebrew_origin - homebrew_origin = notfound_string - begin - Dir.chdir(homebrew_repository) do - homebrew_origin = Hbc::SystemCommand.run("/usr/bin/git", - args: %w[config --get remote.origin.url], - print_stderr: false).stdout.strip +module Hbc + class CLI + class Doctor < Base + def self.run + ohai "macOS Release:", render_with_none_as_error(MacOS.full_version) + ohai "Hardware Architecture:", render_with_none_as_error("#{Hardware::CPU.type}-#{Hardware::CPU.bits}") + ohai "Ruby Version:", render_with_none_as_error("#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}") + ohai "Ruby Path:", render_with_none_as_error(RbConfig.ruby) + # TODO: consider removing most Homebrew constants from doctor output + ohai "Homebrew Version:", render_with_none_as_error(homebrew_version) + ohai "Homebrew Executable Path:", render_with_none_as_error(Hbc.homebrew_executable) + ohai "Homebrew Cellar Path:", render_with_none_as_error(homebrew_cellar) + ohai "Homebrew Repository Path:", render_with_none_as_error(homebrew_repository) + ohai "Homebrew Origin:", render_with_none_as_error(homebrew_origin) + ohai "Homebrew-Cask Version:", render_with_none_as_error(Hbc.full_version) + ohai "Homebrew-Cask Install Location:", render_install_location + ohai "Homebrew-Cask Staging Location:", render_staging_location(Hbc.caskroom) + ohai "Homebrew-Cask Cached Downloads:", render_cached_downloads + ohai "Homebrew-Cask Default Tap Path:", render_tap_paths(Hbc.default_tap.path) + ohai "Homebrew-Cask Alternate Cask Taps:", render_tap_paths(alt_taps) + ohai "Homebrew-Cask Default Tap Cask Count:", render_with_none_as_error(default_cask_count) + ohai "Contents of $LOAD_PATH:", render_load_path($LOAD_PATH) + ohai "Contents of $RUBYLIB Environment Variable:", render_env_var("RUBYLIB") + ohai "Contents of $RUBYOPT Environment Variable:", render_env_var("RUBYOPT") + ohai "Contents of $RUBYPATH Environment Variable:", render_env_var("RUBYPATH") + ohai "Contents of $RBENV_VERSION Environment Variable:", render_env_var("RBENV_VERSION") + ohai "Contents of $CHRUBY_VERSION Environment Variable:", render_env_var("CHRUBY_VERSION") + ohai "Contents of $GEM_HOME Environment Variable:", render_env_var("GEM_HOME") + ohai "Contents of $GEM_PATH Environment Variable:", render_env_var("GEM_PATH") + ohai "Contents of $BUNDLE_PATH Environment Variable:", render_env_var("BUNDLE_PATH") + ohai "Contents of $PATH Environment Variable:", render_env_var("PATH") + ohai "Contents of $SHELL Environment Variable:", render_env_var("SHELL") + ohai "Contents of Locale Environment Variables:", render_with_none(locale_variables) + ohai "Running As Privileged User:", render_with_none_as_error(privileged_uid) end - if homebrew_origin !~ %r{\S} - homebrew_origin = "#{none_string} #{error_string}" - elsif homebrew_origin !~ %r{(mxcl|Homebrew)/(home)?brew(\.git)?\Z} - homebrew_origin.concat " #{error_string "warning: nonstandard origin"}" + + def self.alt_taps + Tap.select { |t| t.cask_dir && t != Hbc.default_tap } + .map(&:path) end - rescue StandardError - homebrew_origin = error_string "Not Found - Error running git" - end - homebrew_origin - end - def self.homebrew_repository - homebrew_constants("repository") - end - - def self.homebrew_cellar - homebrew_constants("cellar") - end - - def self.homebrew_version - homebrew_constants("version") - end - - def self.homebrew_taps - @homebrew_taps ||= if homebrew_repository.respond_to?(:join) - homebrew_repository.join("Library", "Taps") - end - end - - def self.homebrew_constants(name) - @homebrew_constants ||= {} - return @homebrew_constants[name] if @homebrew_constants.key?(name) - @homebrew_constants[name] = notfound_string - begin - @homebrew_constants[name] = Hbc::SystemCommand.run!(Hbc.homebrew_executable, - args: ["--#{name}"], - print_stderr: false) - .stdout - .strip - if @homebrew_constants[name] !~ %r{\S} - @homebrew_constants[name] = "#{none_string} #{error_string}" + def self.default_cask_count + Hbc.default_tap.cask_files.count + rescue StandardError + "0 #{error_string "Error reading #{Hbc.default_tap.path}"}" end - path = Pathname.new(@homebrew_constants[name]) - @homebrew_constants[name] = path if path.exist? - rescue StandardError - @homebrew_constants[name] = error_string "Not Found - Error running brew" - end - @homebrew_constants[name] - end - def self.locale_variables - ENV.keys.grep(%r{^(?:LC_\S+|LANG|LANGUAGE)\Z}).collect { |v| %Q{#{v}="#{ENV[v]}"} }.sort.join("\n") - end + def self.homebrew_origin + homebrew_origin = notfound_string + begin + Dir.chdir(homebrew_repository) do + homebrew_origin = SystemCommand.run("/usr/bin/git", + args: %w[config --get remote.origin.url], + print_stderr: false).stdout.strip + end + if homebrew_origin !~ %r{\S} + homebrew_origin = "#{none_string} #{error_string}" + elsif homebrew_origin !~ %r{(mxcl|Homebrew)/(home)?brew(\.git)?\Z} + homebrew_origin.concat " #{error_string "warning: nonstandard origin"}" + end + rescue StandardError + homebrew_origin = error_string "Not Found - Error running git" + end + homebrew_origin + end - def self.privileged_uid - Process.euid.zero? ? "Yes #{error_string "warning: not recommended"}" : "No" - rescue StandardError - notfound_string - end + def self.homebrew_repository + homebrew_constants("repository") + end - def self.none_string - "" - end + def self.homebrew_cellar + homebrew_constants("cellar") + end - def self.legacy_tap_pattern - %r{phinze} - end + def self.homebrew_version + homebrew_constants("version") + end - def self.notfound_string - "#{Tty.red}Not Found - Unknown Error#{Tty.reset}" - end + def self.homebrew_taps + @homebrew_taps ||= if homebrew_repository.respond_to?(:join) + homebrew_repository.join("Library", "Taps") + end + end - def self.error_string(string = "Error") - "#{Tty.red}(#{string})#{Tty.reset}" - end + def self.homebrew_constants(name) + @homebrew_constants ||= {} + return @homebrew_constants[name] if @homebrew_constants.key?(name) + @homebrew_constants[name] = notfound_string + begin + @homebrew_constants[name] = SystemCommand.run!(Hbc.homebrew_executable, + args: ["--#{name}"], + print_stderr: false) + .stdout + .strip + if @homebrew_constants[name] !~ %r{\S} + @homebrew_constants[name] = "#{none_string} #{error_string}" + end + path = Pathname.new(@homebrew_constants[name]) + @homebrew_constants[name] = path if path.exist? + rescue StandardError + @homebrew_constants[name] = error_string "Not Found - Error running brew" + end + @homebrew_constants[name] + end - def self.render_with_none(string) - return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty? - none_string - end + def self.locale_variables + ENV.keys.grep(%r{^(?:LC_\S+|LANG|LANGUAGE)\Z}).collect { |v| %Q{#{v}="#{ENV[v]}"} }.sort.join("\n") + end - def self.render_with_none_as_error(string) - return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty? - "#{none_string} #{error_string}" - end + def self.privileged_uid + Process.euid.zero? ? "Yes #{error_string "warning: not recommended"}" : "No" + rescue StandardError + notfound_string + end - def self.render_tap_paths(paths) - paths = [paths] unless paths.respond_to?(:each) - paths.collect do |dir| - if dir.nil? || dir.to_s.empty? + def self.none_string + "" + end + + def self.legacy_tap_pattern + %r{phinze} + end + + def self.notfound_string + "#{Tty.red}Not Found - Unknown Error#{Tty.reset}" + end + + def self.error_string(string = "Error") + "#{Tty.red}(#{string})#{Tty.reset}" + end + + def self.render_with_none(string) + return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty? none_string - elsif dir.to_s.match(legacy_tap_pattern) - dir.to_s.concat(" #{error_string "Warning: legacy tap path"}") - else - dir.to_s + end + + def self.render_with_none_as_error(string) + return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty? + "#{none_string} #{error_string}" + end + + def self.render_tap_paths(paths) + paths = [paths] unless paths.respond_to?(:each) + paths.collect do |dir| + if dir.nil? || dir.to_s.empty? + none_string + elsif dir.to_s.match(legacy_tap_pattern) + dir.to_s.concat(" #{error_string "Warning: legacy tap path"}") + else + dir.to_s + end + end + end + + def self.render_env_var(var) + if ENV.key?(var) + %Q{#{var}="#{ENV[var]}"} + else + none_string + end + end + + # This could be done by calling into Homebrew, but the situation + # where "doctor" is needed is precisely the situation where such + # things are less dependable. + def self.render_install_location + locations = Dir.glob(Pathname.new(homebrew_cellar).join("brew-cask", "*")).reverse + if locations.empty? + none_string + else + locations.collect do |l| + "#{l} #{error_string 'error: legacy install. Run "brew uninstall --force brew-cask".'}" + end + end + end + + def self.render_staging_location(path) + path = Pathname.new(path) + if !path.exist? + "#{path} #{error_string "error: path does not exist"}}" + elsif !path.writable? + "#{path} #{error_string "error: not writable by current user"}" + else + path + end + end + + def self.render_load_path(paths) + return "#{none_string} #{error_string}" if [*paths].empty? + paths + end + + def self.render_cached_downloads + cleanup = CLI::Cleanup.default + files = cleanup.cache_files + count = files.count + size = cleanup.disk_cleanup_size + size_msg = "#{number_readable(count)} files, #{disk_usage_readable(size)}" + warn_msg = error_string('warning: run "brew cask cleanup"') + size_msg << " #{warn_msg}" if count > 0 + [Hbc.cache, size_msg] + end + + def self.help + "checks for configuration issues" end end end - - def self.render_env_var(var) - if ENV.key?(var) - %Q{#{var}="#{ENV[var]}"} - else - none_string - end - end - - # This could be done by calling into Homebrew, but the situation - # where "doctor" is needed is precisely the situation where such - # things are less dependable. - def self.render_install_location - locations = Dir.glob(Pathname.new(homebrew_cellar).join("brew-cask", "*")).reverse - if locations.empty? - none_string - else - locations.collect do |l| - "#{l} #{error_string 'error: legacy install. Run "brew uninstall --force brew-cask".'}" - end - end - end - - def self.render_staging_location(path) - path = Pathname.new(path) - if !path.exist? - "#{path} #{error_string "error: path does not exist"}}" - elsif !path.writable? - "#{path} #{error_string "error: not writable by current user"}" - else - path - end - end - - def self.render_load_path(paths) - return "#{none_string} #{error_string}" if [*paths].empty? - paths - end - - def self.render_cached_downloads - cleanup = Hbc::CLI::Cleanup.default - files = cleanup.cache_files - count = files.count - size = cleanup.disk_cleanup_size - size_msg = "#{number_readable(count)} files, #{disk_usage_readable(size)}" - warn_msg = error_string('warning: run "brew cask cleanup"') - size_msg << " #{warn_msg}" if count > 0 - [Hbc.cache, size_msg] - end - - def self.help - "checks for configuration issues" - end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/edit.rb b/Library/Homebrew/cask/lib/hbc/cli/edit.rb index b2d4a91564..3090cf1960 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/edit.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/edit.rb @@ -1,18 +1,22 @@ -class Hbc::CLI::Edit < Hbc::CLI::Base - def self.run(*args) - cask_tokens = cask_tokens_from(args) - raise Hbc::CaskUnspecifiedError if cask_tokens.empty? - # only respects the first argument - cask_token = cask_tokens.first.sub(%r{\.rb$}i, "") - cask_path = Hbc.path(cask_token) - odebug "Opening editor for Cask #{cask_token}" - unless cask_path.exist? - raise Hbc::CaskUnavailableError, %Q{#{cask_token}, run "brew cask create #{cask_token}" to create a new Cask} - end - exec_editor cask_path - end +module Hbc + class CLI + class Edit < Base + def self.run(*args) + cask_tokens = cask_tokens_from(args) + raise CaskUnspecifiedError if cask_tokens.empty? + # only respects the first argument + cask_token = cask_tokens.first.sub(%r{\.rb$}i, "") + cask_path = Hbc.path(cask_token) + odebug "Opening editor for Cask #{cask_token}" + unless cask_path.exist? + raise CaskUnavailableError, %Q{#{cask_token}, run "brew cask create #{cask_token}" to create a new Cask} + end + exec_editor cask_path + end - def self.help - "edits the given Cask" + def self.help + "edits the given Cask" + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/fetch.rb b/Library/Homebrew/cask/lib/hbc/cli/fetch.rb index 647f2af2c1..9b3d5099cf 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/fetch.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/fetch.rb @@ -1,19 +1,23 @@ -class Hbc::CLI::Fetch < Hbc::CLI::Base - def self.run(*args) - cask_tokens = cask_tokens_from(args) - raise Hbc::CaskUnspecifiedError if cask_tokens.empty? - force = args.include? "--force" +module Hbc + class CLI + class Fetch < Base + def self.run(*args) + cask_tokens = cask_tokens_from(args) + raise CaskUnspecifiedError if cask_tokens.empty? + force = args.include? "--force" - cask_tokens.each do |cask_token| - ohai "Downloading external files for Cask #{cask_token}" - cask = Hbc.load(cask_token) - downloaded_path = Hbc::Download.new(cask, force: force).perform - Hbc::Verify.all(cask, downloaded_path) - ohai "Success! Downloaded to -> #{downloaded_path}" + cask_tokens.each do |cask_token| + ohai "Downloading external files for Cask #{cask_token}" + cask = Hbc.load(cask_token) + downloaded_path = Download.new(cask, force: force).perform + Verify.all(cask, downloaded_path) + ohai "Success! Downloaded to -> #{downloaded_path}" + end + end + + def self.help + "downloads remote application files to local cache" + end end end - - def self.help - "downloads remote application files to local cache" - end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/home.rb b/Library/Homebrew/cask/lib/hbc/cli/home.rb index 9c8c0a0e43..b35b08279b 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/home.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/home.rb @@ -1,18 +1,22 @@ -class Hbc::CLI::Home < Hbc::CLI::Base - def self.run(*cask_tokens) - if cask_tokens.empty? - odebug "Opening project homepage" - system "/usr/bin/open", "--", "http://caskroom.io/" - else - cask_tokens.each do |cask_token| - odebug "Opening homepage for Cask #{cask_token}" - cask = Hbc.load(cask_token) - system "/usr/bin/open", "--", cask.homepage +module Hbc + class CLI + class Home < Base + def self.run(*cask_tokens) + if cask_tokens.empty? + odebug "Opening project homepage" + system "/usr/bin/open", "--", "http://caskroom.io/" + else + cask_tokens.each do |cask_token| + odebug "Opening homepage for Cask #{cask_token}" + cask = Hbc.load(cask_token) + system "/usr/bin/open", "--", cask.homepage + end + end + end + + def self.help + "opens the homepage of the given Cask" end end end - - def self.help - "opens the homepage of the given Cask" - end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/info.rb b/Library/Homebrew/cask/lib/hbc/cli/info.rb index 76f2d13668..7fbdff3ebb 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/info.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/info.rb @@ -1,65 +1,69 @@ -class Hbc::CLI::Info < Hbc::CLI::Base - def self.run(*args) - cask_tokens = cask_tokens_from(args) - raise Hbc::CaskUnspecifiedError if cask_tokens.empty? - cask_tokens.each do |cask_token| - odebug "Getting info for Cask #{cask_token}" - cask = Hbc.load(cask_token) +module Hbc + class CLI + class Info < Base + def self.run(*args) + cask_tokens = cask_tokens_from(args) + raise CaskUnspecifiedError if cask_tokens.empty? + cask_tokens.each do |cask_token| + odebug "Getting info for Cask #{cask_token}" + cask = Hbc.load(cask_token) - info(cask) - end - end - - def self.help - "displays information about the given Cask" - end - - def self.info(cask) - puts "#{cask.token}: #{cask.version}" - puts formatted_url(cask.homepage) if cask.homepage - installation_info(cask) - puts "From: #{formatted_url(github_info(cask))}" if github_info(cask) - name_info(cask) - artifact_info(cask) - Hbc::Installer.print_caveats(cask) - end - - def self.formatted_url(url) - "#{Tty.em}#{url}#{Tty.reset}" - end - - def self.installation_info(cask) - if cask.installed? - cask.versions.each do |version| - versioned_staged_path = cask.caskroom_path.join(version) - - puts versioned_staged_path.to_s - .concat(" (") - .concat(versioned_staged_path.exist? ? versioned_staged_path.abv : "#{Tty.red}does not exist#{Tty.reset}") - .concat(")") + info(cask) + end end - else - puts "Not installed" - end - end - def self.name_info(cask) - ohai cask.name.size > 1 ? "Names" : "Name" - puts cask.name.empty? ? "#{Tty.red}None#{Tty.reset}" : cask.name - end + def self.help + "displays information about the given Cask" + end - def self.github_info(cask) - user, repo, token = Hbc::QualifiedToken.parse(Hbc.all_tokens.detect { |t| t.split("/").last == cask.token }) - "#{Tap.fetch(user, repo).default_remote}/blob/master/Casks/#{token}.rb" - end + def self.info(cask) + puts "#{cask.token}: #{cask.version}" + puts formatted_url(cask.homepage) if cask.homepage + installation_info(cask) + puts "From: #{formatted_url(github_info(cask))}" if github_info(cask) + name_info(cask) + artifact_info(cask) + Installer.print_caveats(cask) + end - def self.artifact_info(cask) - ohai "Artifacts" - Hbc::DSL::ORDINARY_ARTIFACT_TYPES.each do |type| - next if cask.artifacts[type].empty? - cask.artifacts[type].each do |artifact| - activatable_item = type == :stage_only ? "" : artifact.first - puts "#{activatable_item} (#{type})" + def self.formatted_url(url) + "#{Tty.em}#{url}#{Tty.reset}" + end + + def self.installation_info(cask) + if cask.installed? + cask.versions.each do |version| + versioned_staged_path = cask.caskroom_path.join(version) + + puts versioned_staged_path.to_s + .concat(" (") + .concat(versioned_staged_path.exist? ? versioned_staged_path.abv : "#{Tty.red}does not exist#{Tty.reset}") + .concat(")") + end + else + puts "Not installed" + end + end + + def self.name_info(cask) + ohai cask.name.size > 1 ? "Names" : "Name" + puts cask.name.empty? ? "#{Tty.red}None#{Tty.reset}" : cask.name + end + + def self.github_info(cask) + user, repo, token = QualifiedToken.parse(Hbc.all_tokens.detect { |t| t.split("/").last == cask.token }) + "#{Tap.fetch(user, repo).default_remote}/blob/master/Casks/#{token}.rb" + end + + def self.artifact_info(cask) + ohai "Artifacts" + DSL::ORDINARY_ARTIFACT_TYPES.each do |type| + next if cask.artifacts[type].empty? + cask.artifacts[type].each do |artifact| + activatable_item = type == :stage_only ? "" : artifact.first + puts "#{activatable_item} (#{type})" + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/install.rb b/Library/Homebrew/cask/lib/hbc/cli/install.rb index 16e3b8121b..d7575344db 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/install.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/install.rb @@ -1,60 +1,63 @@ +module Hbc + class CLI + class Install < Base + def self.run(*args) + cask_tokens = cask_tokens_from(args) + raise CaskUnspecifiedError if cask_tokens.empty? + force = args.include? "--force" + skip_cask_deps = args.include? "--skip-cask-deps" + require_sha = args.include? "--require-sha" + retval = install_casks cask_tokens, force, skip_cask_deps, require_sha + # retval is ternary: true/false/nil -class Hbc::CLI::Install < Hbc::CLI::Base - def self.run(*args) - cask_tokens = cask_tokens_from(args) - raise Hbc::CaskUnspecifiedError if cask_tokens.empty? - force = args.include? "--force" - skip_cask_deps = args.include? "--skip-cask-deps" - require_sha = args.include? "--require-sha" - retval = install_casks cask_tokens, force, skip_cask_deps, require_sha - # retval is ternary: true/false/nil + raise CaskError, "nothing to install" if retval.nil? + raise CaskError, "install incomplete" unless retval + end - raise Hbc::CaskError, "nothing to install" if retval.nil? - raise Hbc::CaskError, "install incomplete" unless retval - end + def self.install_casks(cask_tokens, force, skip_cask_deps, require_sha) + count = 0 + cask_tokens.each do |cask_token| + begin + cask = Hbc.load(cask_token) + Installer.new(cask, + force: force, + skip_cask_deps: skip_cask_deps, + require_sha: require_sha).install + count += 1 + rescue CaskAlreadyInstalledError => e + opoo e.message + count += 1 + rescue CaskAutoUpdatesError => e + opoo e.message + count += 1 + rescue CaskUnavailableError => e + warn_unavailable_with_suggestion cask_token, e + rescue CaskNoShasumError => e + opoo e.message + count += 1 + end + end + count.zero? ? nil : count == cask_tokens.length + end - def self.install_casks(cask_tokens, force, skip_cask_deps, require_sha) - count = 0 - cask_tokens.each do |cask_token| - begin - cask = Hbc.load(cask_token) - Hbc::Installer.new(cask, - force: force, - skip_cask_deps: skip_cask_deps, - require_sha: require_sha).install - count += 1 - rescue Hbc::CaskAlreadyInstalledError => e - opoo e.message - count += 1 - rescue Hbc::CaskAutoUpdatesError => e - opoo e.message - count += 1 - rescue Hbc::CaskUnavailableError => e - warn_unavailable_with_suggestion cask_token, e - rescue Hbc::CaskNoShasumError => e - opoo e.message - count += 1 + def self.warn_unavailable_with_suggestion(cask_token, e) + exact_match, partial_matches = Search.search(cask_token) + errmsg = e.message + if exact_match + errmsg.concat(". Did you mean:\n#{exact_match}") + elsif !partial_matches.empty? + errmsg.concat(". Did you mean one of:\n#{puts_columns(partial_matches.take(20))}\n") + end + onoe errmsg + end + + def self.help + "installs the given Cask" + end + + def self.needs_init? + true end end - count.zero? ? nil : count == cask_tokens.length - end - - def self.warn_unavailable_with_suggestion(cask_token, e) - exact_match, partial_matches = Hbc::CLI::Search.search(cask_token) - errmsg = e.message - if exact_match - errmsg.concat(". Did you mean:\n#{exact_match}") - elsif !partial_matches.empty? - errmsg.concat(". Did you mean one of:\n#{puts_columns(partial_matches.take(20))}\n") - end - onoe errmsg - end - - def self.help - "installs the given Cask" - end - - def self.needs_init? - true end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_audit_modified_casks.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_audit_modified_casks.rb index 2cee09eeff..47258a7b2e 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_audit_modified_casks.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_audit_modified_casks.rb @@ -1,135 +1,139 @@ -class Hbc::CLI::InternalAuditModifiedCasks < Hbc::CLI::InternalUseBase - RELEVANT_STANZAS = %i{version sha256 url appcast}.freeze +module Hbc + class CLI + class InternalAuditModifiedCasks < InternalUseBase + RELEVANT_STANZAS = %i{version sha256 url appcast}.freeze - class << self - def needs_init? - true - end + class << self + def needs_init? + true + end - def run(*args) - commit_range = commit_range(args) - cleanup = args.any? { |a| a =~ %r{^-+c(leanup)?$}i } - new(commit_range, cleanup: cleanup).run - end + def run(*args) + commit_range = commit_range(args) + cleanup = args.any? { |a| a =~ %r{^-+c(leanup)?$}i } + new(commit_range, cleanup: cleanup).run + end - def commit_range(args) - posargs = args.reject { |a| a.empty? || a.chars.first == "-" } - odie usage unless posargs.size == 1 - posargs.first - end + def commit_range(args) + posargs = args.reject { |a| a.empty? || a.chars.first == "-" } + odie usage unless posargs.size == 1 + posargs.first + end - def posargs(args) - args.reject { |a| a.empty? || a.chars.first == "-" } - end + def posargs(args) + args.reject { |a| a.empty? || a.chars.first == "-" } + end - def usage - <<-EOS.undent - Usage: brew cask _audit_modified_casks [options...] + def usage + <<-EOS.undent + Usage: brew cask _audit_modified_casks [options...] - Given a range of Git commits, find any Casks that were modified and run `brew - cask audit' on them. If the `url', `version', or `sha256' stanzas were modified, - run with the `--download' flag to verify the hash. + Given a range of Git commits, find any Casks that were modified and run `brew + cask audit' on them. If the `url', `version', or `sha256' stanzas were modified, + run with the `--download' flag to verify the hash. - Options: - -c, --cleanup - Remove all cached downloads. Use with care. - EOS - end - end + Options: + -c, --cleanup + Remove all cached downloads. Use with care. + EOS + end + end - def initialize(commit_range, cleanup: false) - @commit_range = commit_range - @cleanup = cleanup - end + def initialize(commit_range, cleanup: false) + @commit_range = commit_range + @cleanup = cleanup + end - attr_reader :commit_range + attr_reader :commit_range - def cleanup? - @cleanup - end + def cleanup? + @cleanup + end - def run - at_exit do - cleanup - end + def run + at_exit do + cleanup + end - Dir.chdir git_root do - modified_cask_files.zip(modified_casks).each do |cask_file, cask| - audit(cask, cask_file) + Dir.chdir git_root do + modified_cask_files.zip(modified_casks).each do |cask_file, cask| + audit(cask, cask_file) + end + end + report_failures + end + + def git_root + @git_root ||= git("rev-parse", "--show-toplevel") + end + + def modified_cask_files + @modified_cask_files ||= git_filter_cask_files("AM") + end + + def added_cask_files + @added_cask_files ||= git_filter_cask_files("A") + end + + def git_filter_cask_files(filter) + git("diff", "--name-only", "--diff-filter=#{filter}", commit_range, + "--", Pathname.new(git_root).join("Casks", "*.rb").to_s).split("\n") + end + + def modified_casks + return @modified_casks if defined? @modified_casks + @modified_casks = modified_cask_files.map { |f| Hbc.load(f) } + if @modified_casks.any? + num_modified = @modified_casks.size + ohai "#{num_modified} modified #{pluralize("cask", num_modified)}: " \ + "#{@modified_casks.join(" ")}" + end + @modified_casks + end + + def audit(cask, cask_file) + audit_download = audit_download?(cask, cask_file) + check_token_conflicts = added_cask_files.include?(cask_file) + success = Auditor.audit(cask, audit_download: audit_download, + check_token_conflicts: check_token_conflicts) + failed_casks << cask unless success + end + + def failed_casks + @failed_casks ||= [] + end + + def audit_download?(cask, cask_file) + cask.sha256 != :no_check && relevant_stanza_modified?(cask_file) + end + + def relevant_stanza_modified?(cask_file) + out = git("diff", commit_range, "--", cask_file) + out =~ %r{^\+\s*(#{RELEVANT_STANZAS.join('|')})} + end + + def git(*args) + odebug ["git", *args].join(" ") + out, err, status = Open3.capture3("git", *args) + return out.chomp if status.success? + odie err.chomp + end + + def report_failures + return if failed_casks.empty? + num_failed = failed_casks.size + cask_pluralized = pluralize("cask", num_failed) + odie "audit failed for #{num_failed} #{cask_pluralized}: " \ + "#{failed_casks.join(" ")}" + end + + def pluralize(str, num) + num == 1 ? str : "#{str}s" + end + + def cleanup + Cleanup.run if cleanup? end end - report_failures - end - - def git_root - @git_root ||= git("rev-parse", "--show-toplevel") - end - - def modified_cask_files - @modified_cask_files ||= git_filter_cask_files("AM") - end - - def added_cask_files - @added_cask_files ||= git_filter_cask_files("A") - end - - def git_filter_cask_files(filter) - git("diff", "--name-only", "--diff-filter=#{filter}", commit_range, - "--", Pathname.new(git_root).join("Casks", "*.rb").to_s).split("\n") - end - - def modified_casks - return @modified_casks if defined? @modified_casks - @modified_casks = modified_cask_files.map { |f| Hbc.load(f) } - if @modified_casks.any? - num_modified = @modified_casks.size - ohai "#{num_modified} modified #{pluralize("cask", num_modified)}: " \ - "#{@modified_casks.join(" ")}" - end - @modified_casks - end - - def audit(cask, cask_file) - audit_download = audit_download?(cask, cask_file) - check_token_conflicts = added_cask_files.include?(cask_file) - success = Hbc::Auditor.audit(cask, audit_download: audit_download, - check_token_conflicts: check_token_conflicts) - failed_casks << cask unless success - end - - def failed_casks - @failed_casks ||= [] - end - - def audit_download?(cask, cask_file) - cask.sha256 != :no_check && relevant_stanza_modified?(cask_file) - end - - def relevant_stanza_modified?(cask_file) - out = git("diff", commit_range, "--", cask_file) - out =~ %r{^\+\s*(#{RELEVANT_STANZAS.join('|')})} - end - - def git(*args) - odebug ["git", *args].join(" ") - out, err, status = Open3.capture3("git", *args) - return out.chomp if status.success? - odie err.chomp - end - - def report_failures - return if failed_casks.empty? - num_failed = failed_casks.size - cask_pluralized = pluralize("cask", num_failed) - odie "audit failed for #{num_failed} #{cask_pluralized}: " \ - "#{failed_casks.join(" ")}" - end - - def pluralize(str, num) - num == 1 ? str : "#{str}s" - end - - def cleanup - Hbc::CLI::Cleanup.run if cleanup? end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_checkurl.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_checkurl.rb index d53f420e28..77cf250b1f 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_checkurl.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_checkurl.rb @@ -1,15 +1,19 @@ -class Hbc::CLI::InternalCheckurl < Hbc::CLI::InternalUseBase - def self.run(*args) - casks_to_check = args.empty? ? Hbc.all : args.map { |arg| Hbc.load(arg) } - casks_to_check.each do |cask| - odebug "Checking URL for Cask #{cask}" - checker = Hbc::UrlChecker.new(cask) - checker.run - puts checker.summary +module Hbc + class CLI + class InternalCheckurl < InternalUseBase + def self.run(*args) + casks_to_check = args.empty? ? Hbc.all : args.map { |arg| Hbc.load(arg) } + casks_to_check.each do |cask| + odebug "Checking URL for Cask #{cask}" + checker = UrlChecker.new(cask) + checker.run + puts checker.summary + end + end + + def self.help + "checks for bad Cask URLs" + end end end - - def self.help - "checks for bad Cask URLs" - end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_dump.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_dump.rb index 1bccf610de..af1494e5f9 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_dump.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_dump.rb @@ -1,30 +1,34 @@ -class Hbc::CLI::InternalDump < Hbc::CLI::InternalUseBase - def self.run(*arguments) - cask_tokens = cask_tokens_from(arguments) - raise Hbc::CaskUnspecifiedError if cask_tokens.empty? - retval = dump_casks(*cask_tokens) - # retval is ternary: true/false/nil +module Hbc + class CLI + class InternalDump < InternalUseBase + def self.run(*arguments) + cask_tokens = cask_tokens_from(arguments) + raise CaskUnspecifiedError if cask_tokens.empty? + retval = dump_casks(*cask_tokens) + # retval is ternary: true/false/nil - raise Hbc::CaskError, "nothing to dump" if retval.nil? - raise Hbc::CaskError, "dump incomplete" unless retval - end + raise CaskError, "nothing to dump" if retval.nil? + raise CaskError, "dump incomplete" unless retval + end - def self.dump_casks(*cask_tokens) - Hbc.debug = true # Yuck. At the moment this is the only way to make dumps visible - count = 0 - cask_tokens.each do |cask_token| - begin - cask = Hbc.load(cask_token) - count += 1 - cask.dumpcask - rescue StandardError => e - opoo "#{cask_token} was not found or would not load: #{e}" + def self.dump_casks(*cask_tokens) + Hbc.debug = true # Yuck. At the moment this is the only way to make dumps visible + count = 0 + cask_tokens.each do |cask_token| + begin + cask = Hbc.load(cask_token) + count += 1 + cask.dumpcask + rescue StandardError => e + opoo "#{cask_token} was not found or would not load: #{e}" + end + end + count.zero? ? nil : count == cask_tokens.length + end + + def self.help + "Dump the given Cask in YAML format" end end - count.zero? ? nil : count == cask_tokens.length - end - - def self.help - "Dump the given Cask in YAML format" end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_help.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_help.rb index 81d7ee6731..6c646cfd7f 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_help.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_help.rb @@ -1,19 +1,23 @@ -class Hbc::CLI::InternalHelp < Hbc::CLI::InternalUseBase - def self.run(*_ignored) - max_command_len = Hbc::CLI.commands.map(&:length).max - puts "Unstable Internal-use Commands:\n\n" - Hbc::CLI.command_classes.each do |klass| - next if klass.visible - puts " #{klass.command_name.ljust(max_command_len)} #{help_for(klass)}" +module Hbc + class CLI + class InternalHelp < InternalUseBase + def self.run(*_ignored) + max_command_len = CLI.commands.map(&:length).max + puts "Unstable Internal-use Commands:\n\n" + CLI.command_classes.each do |klass| + next if klass.visible + puts " #{klass.command_name.ljust(max_command_len)} #{help_for(klass)}" + end + puts "\n" + end + + def self.help_for(klass) + klass.respond_to?(:help) ? klass.help : nil + end + + def self.help + "Print help strings for unstable internal-use commands" + end end - puts "\n" - end - - def self.help_for(klass) - klass.respond_to?(:help) ? klass.help : nil - end - - def self.help - "Print help strings for unstable internal-use commands" end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb index 0fb99cc72d..0265e34b77 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb @@ -1,127 +1,131 @@ -class Hbc::CLI::InternalStanza < Hbc::CLI::InternalUseBase - # Syntax - # - # brew cask _stanza [ --table | --yaml | --inspect | --quiet ] [ ... ] - # - # If no tokens are given, then data for all Casks is returned. - # - # The pseudo-stanza "artifacts" is available. - # - # On failure, a blank line is returned on the standard output. - # - # Examples - # - # brew cask _stanza appcast --table - # brew cask _stanza app --table alfred google-chrome adium voicemac logisim vagrant - # brew cask _stanza url --table alfred google-chrome adium voicemac logisim vagrant - # brew cask _stanza version --table alfred google-chrome adium voicemac logisim vagrant - # brew cask _stanza artifacts --table --inspect alfred google-chrome adium voicemac logisim vagrant - # brew cask _stanza artifacts --table --yaml alfred google-chrome adium voicemac logisim vagrant - # +module Hbc + class CLI + class InternalStanza < InternalUseBase + # Syntax + # + # brew cask _stanza [ --table | --yaml | --inspect | --quiet ] [ ... ] + # + # If no tokens are given, then data for all Casks is returned. + # + # The pseudo-stanza "artifacts" is available. + # + # On failure, a blank line is returned on the standard output. + # + # Examples + # + # brew cask _stanza appcast --table + # brew cask _stanza app --table alfred google-chrome adium voicemac logisim vagrant + # brew cask _stanza url --table alfred google-chrome adium voicemac logisim vagrant + # brew cask _stanza version --table alfred google-chrome adium voicemac logisim vagrant + # brew cask _stanza artifacts --table --inspect alfred google-chrome adium voicemac logisim vagrant + # brew cask _stanza artifacts --table --yaml alfred google-chrome adium voicemac logisim vagrant + # - # TODO: this should be retrievable from Hbc::DSL - ARTIFACTS = Set.new [ - :app, - :suite, - :artifact, - :prefpane, - :qlplugin, - :font, - :service, - :colorpicker, - :binary, - :input_method, - :internet_plugin, - :audio_unit_plugin, - :vst_plugin, - :vst3_plugin, - :screen_saver, - :pkg, - :installer, - :stage_only, - :nested_container, - :uninstall, - :postflight, - :uninstall_postflight, - :preflight, - :uninstall_postflight, - ] + # TODO: this should be retrievable from Hbc::DSL + ARTIFACTS = Set.new [ + :app, + :suite, + :artifact, + :prefpane, + :qlplugin, + :font, + :service, + :colorpicker, + :binary, + :input_method, + :internet_plugin, + :audio_unit_plugin, + :vst_plugin, + :vst3_plugin, + :screen_saver, + :pkg, + :installer, + :stage_only, + :nested_container, + :uninstall, + :postflight, + :uninstall_postflight, + :preflight, + :uninstall_postflight, + ] - def self.run(*arguments) - table = arguments.include? "--table" - quiet = arguments.include? "--quiet" - format = :to_yaml if arguments.include? "--yaml" - format = :inspect if arguments.include? "--inspect" - cask_tokens = arguments.reject { |arg| arg.chars.first == "-" } - stanza = cask_tokens.shift.to_sym - cask_tokens = Hbc.all_tokens if cask_tokens.empty? + def self.run(*arguments) + table = arguments.include? "--table" + quiet = arguments.include? "--quiet" + format = :to_yaml if arguments.include? "--yaml" + format = :inspect if arguments.include? "--inspect" + cask_tokens = arguments.reject { |arg| arg.chars.first == "-" } + stanza = cask_tokens.shift.to_sym + cask_tokens = Hbc.all_tokens if cask_tokens.empty? - retval = print_stanzas(stanza, format, table, quiet, *cask_tokens) + retval = print_stanzas(stanza, format, table, quiet, *cask_tokens) - # retval is ternary: true/false/nil - if retval.nil? - exit 1 if quiet - raise Hbc::CaskError, "nothing to print" - elsif !retval - exit 1 if quiet - raise Hbc::CaskError, "print incomplete" + # retval is ternary: true/false/nil + if retval.nil? + exit 1 if quiet + raise CaskError, "nothing to print" + elsif !retval + exit 1 if quiet + raise CaskError, "print incomplete" + end + end + + def self.print_stanzas(stanza, format = nil, table = nil, quiet = nil, *cask_tokens) + count = 0 + if ARTIFACTS.include?(stanza) + artifact_name = stanza + stanza = :artifacts + end + + cask_tokens.each do |cask_token| + print "#{cask_token}\t" if table + + begin + cask = Hbc.load(cask_token) + rescue StandardError + opoo "Cask '#{cask_token}' was not found" unless quiet + puts "" + next + end + + unless cask.respond_to?(stanza) + opoo "no such stanza '#{stanza}' on Cask '#{cask_token}'" unless quiet + puts "" + next + end + + begin + value = cask.send(stanza) + rescue StandardError + opoo "failure calling '#{stanza}' on Cask '#{cask_token}'" unless quiet + puts "" + next + end + + if artifact_name && !value.key?(artifact_name) + opoo "no such stanza '#{artifact_name}' on Cask '#{cask_token}'" unless quiet + puts "" + next + end + + value = value.fetch(artifact_name).to_a.flatten if artifact_name + + if format + puts value.send(format) + elsif artifact_name || value.is_a?(Symbol) + puts value.inspect + else + puts value.to_s + end + + count += 1 + end + count.zero? ? nil : count == cask_tokens.length + end + + def self.help + "Extract and render a specific stanza for the given Casks" + end end end - - def self.print_stanzas(stanza, format = nil, table = nil, quiet = nil, *cask_tokens) - count = 0 - if ARTIFACTS.include?(stanza) - artifact_name = stanza - stanza = :artifacts - end - - cask_tokens.each do |cask_token| - print "#{cask_token}\t" if table - - begin - cask = Hbc.load(cask_token) - rescue StandardError - opoo "Cask '#{cask_token}' was not found" unless quiet - puts "" - next - end - - unless cask.respond_to?(stanza) - opoo "no such stanza '#{stanza}' on Cask '#{cask_token}'" unless quiet - puts "" - next - end - - begin - value = cask.send(stanza) - rescue StandardError - opoo "failure calling '#{stanza}' on Cask '#{cask_token}'" unless quiet - puts "" - next - end - - if artifact_name && !value.key?(artifact_name) - opoo "no such stanza '#{artifact_name}' on Cask '#{cask_token}'" unless quiet - puts "" - next - end - - value = value.fetch(artifact_name).to_a.flatten if artifact_name - - if format - puts value.send(format) - elsif artifact_name || value.is_a?(Symbol) - puts value.inspect - else - puts value.to_s - end - - count += 1 - end - count.zero? ? nil : count == cask_tokens.length - end - - def self.help - "Extract and render a specific stanza for the given Casks" - end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_use_base.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_use_base.rb index 6a4359ea17..96b6eeaa8f 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_use_base.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_use_base.rb @@ -1,9 +1,13 @@ -class Hbc::CLI::InternalUseBase < Hbc::CLI::Base - def self.command_name - super.sub(%r{^internal_}i, "_") - end +module Hbc + class CLI + class InternalUseBase < Base + def self.command_name + super.sub(%r{^internal_}i, "_") + end - def self.visible - false + def self.visible + false + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/list.rb b/Library/Homebrew/cask/lib/hbc/cli/list.rb index 8b9c671051..3a993f8e6f 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/list.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/list.rb @@ -1,86 +1,90 @@ -class Hbc::CLI::List < Hbc::CLI::Base - def self.run(*arguments) - @options = {} - @options[:one] = true if arguments.delete("-1") - @options[:versions] = true if arguments.delete("--versions") +module Hbc + class CLI + class List < Base + def self.run(*arguments) + @options = {} + @options[:one] = true if arguments.delete("-1") + @options[:versions] = true if arguments.delete("--versions") - if arguments.delete("-l") - @options[:one] = true - opoo "Option -l is obsolete! Implying option -1." - end - - retval = arguments.any? ? list(*arguments) : list_installed - # retval is ternary: true/false/nil - if retval.nil? && !arguments.any? - opoo "nothing to list" # special case: avoid exit code - elsif retval.nil? - raise Hbc::CaskError, "nothing to list" - elsif !retval - raise Hbc::CaskError, "listing incomplete" - end - end - - def self.list(*cask_tokens) - count = 0 - - cask_tokens.each do |cask_token| - odebug "Listing files for Cask #{cask_token}" - begin - cask = Hbc.load(cask_token) - - if cask.installed? - if @options[:one] - puts cask.token - elsif @options[:versions] - puts format_versioned(cask) - else - installed_caskfile = cask.metadata_master_container_path.join(*cask.timestamped_versions.last, "Casks", "#{cask_token}.rb") - cask = Hbc.load(installed_caskfile) - list_artifacts(cask) - end - - count += 1 - else - opoo "#{cask} is not installed" + if arguments.delete("-l") + @options[:one] = true + opoo "Option -l is obsolete! Implying option -1." end - rescue Hbc::CaskUnavailableError => e - onoe e + + retval = arguments.any? ? list(*arguments) : list_installed + # retval is ternary: true/false/nil + if retval.nil? && !arguments.any? + opoo "nothing to list" # special case: avoid exit code + elsif retval.nil? + raise CaskError, "nothing to list" + elsif !retval + raise CaskError, "listing incomplete" + end + end + + def self.list(*cask_tokens) + count = 0 + + cask_tokens.each do |cask_token| + odebug "Listing files for Cask #{cask_token}" + begin + cask = Hbc.load(cask_token) + + if cask.installed? + if @options[:one] + puts cask.token + elsif @options[:versions] + puts format_versioned(cask) + else + installed_caskfile = cask.metadata_master_container_path.join(*cask.timestamped_versions.last, "Casks", "#{cask_token}.rb") + cask = Hbc.load(installed_caskfile) + list_artifacts(cask) + end + + count += 1 + else + opoo "#{cask} is not installed" + end + rescue CaskUnavailableError => e + onoe e + end + end + + count.zero? ? nil : count == cask_tokens.length + end + + def self.list_artifacts(cask) + Artifact.for_cask(cask).each do |artifact| + summary = artifact.new(cask).summary + ohai summary[:english_description], summary[:contents] unless summary.empty? + end + end + + def self.list_installed + installed_casks = Hbc.installed + + if @options[:one] + puts installed_casks.map(&:to_s) + elsif @options[:versions] + puts installed_casks.map(&method(:format_versioned)) + else + puts_columns installed_casks.map(&:to_s) + end + + installed_casks.empty? ? nil : true + end + + def self.format_versioned(cask) + cask.to_s.concat(cask.versions.map(&:to_s).join(" ").prepend(" ")) + end + + def self.help + "with no args, lists installed Casks; given installed Casks, lists staged files" + end + + def self.needs_init? + true end end - - count.zero? ? nil : count == cask_tokens.length - end - - def self.list_artifacts(cask) - Hbc::Artifact.for_cask(cask).each do |artifact| - summary = artifact.new(cask).summary - ohai summary[:english_description], summary[:contents] unless summary.empty? - end - end - - def self.list_installed - installed_casks = Hbc.installed - - if @options[:one] - puts installed_casks.map(&:to_s) - elsif @options[:versions] - puts installed_casks.map(&method(:format_versioned)) - else - puts_columns installed_casks.map(&:to_s) - end - - installed_casks.empty? ? nil : true - end - - def self.format_versioned(cask) - cask.to_s.concat(cask.versions.map(&:to_s).join(" ").prepend(" ")) - end - - def self.help - "with no args, lists installed Casks; given installed Casks, lists staged files" - end - - def self.needs_init? - true end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/search.rb b/Library/Homebrew/cask/lib/hbc/cli/search.rb index 5c57ba43da..b671ea8626 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/search.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/search.rb @@ -1,55 +1,59 @@ -class Hbc::CLI::Search < Hbc::CLI::Base - def self.run(*arguments) - render_results(*search(*arguments)) - end - - def self.extract_regexp(string) - if string =~ %r{^/(.*)/$} - Regexp.last_match[1] - else - false - end - end - - def self.search(*arguments) - exact_match = nil - partial_matches = [] - search_term = arguments.join(" ") - search_regexp = extract_regexp arguments.first - all_tokens = Hbc::CLI.nice_listing(Hbc.all_tokens) - if search_regexp - search_term = arguments.first - partial_matches = all_tokens.grep(%r{#{search_regexp}}i) - else - simplified_tokens = all_tokens.map { |t| t.sub(%r{^.*\/}, "").gsub(%r{[^a-z0-9]+}i, "") } - simplified_search_term = search_term.sub(%r{\.rb$}i, "").gsub(%r{[^a-z0-9]+}i, "") - exact_match = simplified_tokens.grep(%r{^#{simplified_search_term}$}i) { |t| all_tokens[simplified_tokens.index(t)] }.first - partial_matches = simplified_tokens.grep(%r{#{simplified_search_term}}i) { |t| all_tokens[simplified_tokens.index(t)] } - partial_matches.delete(exact_match) - end - [exact_match, partial_matches, search_term] - end - - def self.render_results(exact_match, partial_matches, search_term) - if !exact_match && partial_matches.empty? - puts "No Cask found for \"#{search_term}\"." - return - end - if exact_match - ohai "Exact match" - puts exact_match - end - unless partial_matches.empty? - if extract_regexp search_term - ohai "Regexp matches" - else - ohai "Partial matches" +module Hbc + class CLI + class Search < Base + def self.run(*arguments) + render_results(*search(*arguments)) end - puts_columns partial_matches - end - end - def self.help - "searches all known Casks" + def self.extract_regexp(string) + if string =~ %r{^/(.*)/$} + Regexp.last_match[1] + else + false + end + end + + def self.search(*arguments) + exact_match = nil + partial_matches = [] + search_term = arguments.join(" ") + search_regexp = extract_regexp arguments.first + all_tokens = CLI.nice_listing(Hbc.all_tokens) + if search_regexp + search_term = arguments.first + partial_matches = all_tokens.grep(%r{#{search_regexp}}i) + else + simplified_tokens = all_tokens.map { |t| t.sub(%r{^.*\/}, "").gsub(%r{[^a-z0-9]+}i, "") } + simplified_search_term = search_term.sub(%r{\.rb$}i, "").gsub(%r{[^a-z0-9]+}i, "") + exact_match = simplified_tokens.grep(%r{^#{simplified_search_term}$}i) { |t| all_tokens[simplified_tokens.index(t)] }.first + partial_matches = simplified_tokens.grep(%r{#{simplified_search_term}}i) { |t| all_tokens[simplified_tokens.index(t)] } + partial_matches.delete(exact_match) + end + [exact_match, partial_matches, search_term] + end + + def self.render_results(exact_match, partial_matches, search_term) + if !exact_match && partial_matches.empty? + puts "No Cask found for \"#{search_term}\"." + return + end + if exact_match + ohai "Exact match" + puts exact_match + end + unless partial_matches.empty? + if extract_regexp search_term + ohai "Regexp matches" + else + ohai "Partial matches" + end + puts_columns partial_matches + end + end + + def self.help + "searches all known Casks" + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/style.rb b/Library/Homebrew/cask/lib/hbc/cli/style.rb index 8793d2afd2..638e256854 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/style.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/style.rb @@ -1,69 +1,73 @@ require "English" -class Hbc::CLI::Style < Hbc::CLI::Base - def self.help - "checks Cask style using RuboCop" - end +module Hbc + class CLI + class Style < Base + def self.help + "checks Cask style using RuboCop" + end - def self.run(*args) - retval = new(args).run - raise Hbc::CaskError, "style check failed" unless retval - end + def self.run(*args) + retval = new(args).run + raise CaskError, "style check failed" unless retval + end - attr_reader :args - def initialize(args) - @args = args - end + attr_reader :args + def initialize(args) + @args = args + end - def run - install_rubocop - system "rubocop", *rubocop_args, "--", *cask_paths - $CHILD_STATUS.success? - end + def run + install_rubocop + system "rubocop", *rubocop_args, "--", *cask_paths + $CHILD_STATUS.success? + end - RUBOCOP_CASK_VERSION = "~> 0.8.3".freeze + RUBOCOP_CASK_VERSION = "~> 0.8.3".freeze - def install_rubocop - Hbc::Utils.capture_stderr do - begin - Homebrew.install_gem_setup_path! "rubocop-cask", RUBOCOP_CASK_VERSION, "rubocop" - rescue SystemExit - raise Hbc::CaskError, $stderr.string.chomp.sub("#{Tty.red}Error#{Tty.reset}: ", "") + def install_rubocop + Utils.capture_stderr do + begin + Homebrew.install_gem_setup_path! "rubocop-cask", RUBOCOP_CASK_VERSION, "rubocop" + rescue SystemExit + raise CaskError, $stderr.string.chomp.sub("#{Tty.red}Error#{Tty.reset}: ", "") + end + end + end + + def cask_paths + @cask_paths ||= if cask_tokens.empty? + Hbc.all_tapped_cask_dirs + elsif cask_tokens.any? { |file| File.exist?(file) } + cask_tokens + else + cask_tokens.map { |token| Hbc.path(token) } + end + end + + def cask_tokens + @cask_tokens ||= self.class.cask_tokens_from(args) + end + + def rubocop_args + fix? ? autocorrect_args : default_args + end + + def default_args + ["--format", "simple", "--force-exclusion", "--config", rubocop_config] + end + + def autocorrect_args + default_args + ["--auto-correct"] + end + + def rubocop_config + Hbc.default_tap.cask_dir.join(".rubocop.yml") + end + + def fix? + args.any? { |arg| arg =~ %r{--(fix|(auto-?)?correct)} } end end end - - def cask_paths - @cask_paths ||= if cask_tokens.empty? - Hbc.all_tapped_cask_dirs - elsif cask_tokens.any? { |file| File.exist?(file) } - cask_tokens - else - cask_tokens.map { |token| Hbc.path(token) } - end - end - - def cask_tokens - @cask_tokens ||= self.class.cask_tokens_from(args) - end - - def rubocop_args - fix? ? autocorrect_args : default_args - end - - def default_args - ["--format", "simple", "--force-exclusion", "--config", rubocop_config] - end - - def autocorrect_args - default_args + ["--auto-correct"] - end - - def rubocop_config - Hbc.default_tap.cask_dir.join(".rubocop.yml") - end - - def fix? - args.any? { |arg| arg =~ %r{--(fix|(auto-?)?correct)} } - end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb b/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb index 2ce9b7d4da..2bebb97b09 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb @@ -1,40 +1,44 @@ -class Hbc::CLI::Uninstall < Hbc::CLI::Base - def self.run(*args) - cask_tokens = cask_tokens_from(args) - raise Hbc::CaskUnspecifiedError if cask_tokens.empty? - force = args.include? "--force" +module Hbc + class CLI + class Uninstall < Base + def self.run(*args) + cask_tokens = cask_tokens_from(args) + raise CaskUnspecifiedError if cask_tokens.empty? + force = args.include? "--force" - cask_tokens.each do |cask_token| - odebug "Uninstalling Cask #{cask_token}" - cask = Hbc.load(cask_token) + cask_tokens.each do |cask_token| + odebug "Uninstalling Cask #{cask_token}" + cask = Hbc.load(cask_token) - raise Hbc::CaskNotInstalledError, cask unless cask.installed? || force + raise CaskNotInstalledError, cask unless cask.installed? || force - latest_installed_version = cask.timestamped_versions.last + latest_installed_version = cask.timestamped_versions.last - unless latest_installed_version.nil? - latest_installed_cask_file = cask.metadata_master_container_path - .join(latest_installed_version.join(File::Separator), - "Casks", "#{cask_token}.rb") + unless latest_installed_version.nil? + latest_installed_cask_file = cask.metadata_master_container_path + .join(latest_installed_version.join(File::Separator), + "Casks", "#{cask_token}.rb") - # use the same cask file that was used for installation, if possible - cask = Hbc.load(latest_installed_cask_file) if latest_installed_cask_file.exist? + # use the same cask file that was used for installation, if possible + cask = Hbc.load(latest_installed_cask_file) if latest_installed_cask_file.exist? + end + + Installer.new(cask, force: force).uninstall + + next if (versions = cask.versions).empty? + + single = versions.count == 1 + + puts <<-EOS.undent + #{cask_token} #{versions.join(", ")} #{single ? "is" : "are"} still installed. + Remove #{single ? "it" : "them all"} with `brew cask uninstall --force #{cask_token}`. + EOS + end end - Hbc::Installer.new(cask, force: force).uninstall - - next if (versions = cask.versions).empty? - - single = versions.count == 1 - - puts <<-EOS.undent - #{cask_token} #{versions.join(", ")} #{single ? "is" : "are"} still installed. - Remove #{single ? "it" : "them all"} with `brew cask uninstall --force #{cask_token}`. - EOS + def self.help + "uninstalls the given Cask" + end end end - - def self.help - "uninstalls the given Cask" - end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/update.rb b/Library/Homebrew/cask/lib/hbc/cli/update.rb index ceb9475442..1d6ed2af54 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/update.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/update.rb @@ -1,16 +1,20 @@ -class Hbc::CLI::Update < Hbc::CLI::Base - def self.run(*_ignored) - result = Hbc::SystemCommand.run(Hbc.homebrew_executable, - args: %w[update]) - # TODO: separating stderr/stdout is undesirable here. - # Hbc::SystemCommand should have an option for plain - # unbuffered output. - print result.stdout - $stderr.print result.stderr - exit result.exit_status - end +module Hbc + class CLI + class Update < Base + def self.run(*_ignored) + result = SystemCommand.run(Hbc.homebrew_executable, + args: %w[update]) + # TODO: separating stderr/stdout is undesirable here. + # Hbc::SystemCommand should have an option for plain + # unbuffered output. + print result.stdout + $stderr.print result.stderr + exit result.exit_status + end - def self.help - "a synonym for 'brew update'" + def self.help + "a synonym for 'brew update'" + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/zap.rb b/Library/Homebrew/cask/lib/hbc/cli/zap.rb index 0813783309..2f9723858f 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/zap.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/zap.rb @@ -1,15 +1,19 @@ -class Hbc::CLI::Zap < Hbc::CLI::Base - def self.run(*args) - cask_tokens = cask_tokens_from(args) - raise Hbc::CaskUnspecifiedError if cask_tokens.empty? - cask_tokens.each do |cask_token| - odebug "Zapping Cask #{cask_token}" - cask = Hbc.load(cask_token) - Hbc::Installer.new(cask).zap +module Hbc + class CLI + class Zap < Base + def self.run(*args) + cask_tokens = cask_tokens_from(args) + raise CaskUnspecifiedError if cask_tokens.empty? + cask_tokens.each do |cask_token| + odebug "Zapping Cask #{cask_token}" + cask = Hbc.load(cask_token) + Installer.new(cask).zap + end + end + + def self.help + "zaps all files associated with the given Cask" + end end end - - def self.help - "zaps all files associated with the given Cask" - end end diff --git a/Library/Homebrew/cask/lib/hbc/container.rb b/Library/Homebrew/cask/lib/hbc/container.rb index e2b21a3ef0..01dd103375 100644 --- a/Library/Homebrew/cask/lib/hbc/container.rb +++ b/Library/Homebrew/cask/lib/hbc/container.rb @@ -1,5 +1,3 @@ -class Hbc::Container; end - require "hbc/container/base" require "hbc/container/air" require "hbc/container/bzip2" @@ -22,47 +20,49 @@ require "hbc/container/xip" require "hbc/container/xz" require "hbc/container/zip" -class Hbc::Container - def self.autodetect_containers - [ - Hbc::Container::Pkg, - Hbc::Container::Ttf, - Hbc::Container::Otf, - Hbc::Container::Air, - Hbc::Container::Cab, - Hbc::Container::Dmg, - Hbc::Container::SevenZip, - Hbc::Container::Sit, - Hbc::Container::Rar, - Hbc::Container::Zip, - Hbc::Container::Xip, # needs to be before xar as this is a cpio inside a gzip inside a xar - Hbc::Container::Xar, # need to be before tar as tar can also list xar - Hbc::Container::Tar, # or compressed tar (bzip2/gzip/lzma/xz) - Hbc::Container::Bzip2, # pure bzip2 - Hbc::Container::Gzip, # pure gzip - Hbc::Container::Lzma, # pure lzma - Hbc::Container::Xz, # pure xz - ] - # for explicit use only (never autodetected): - # Hbc::Container::Naked - # Hbc::Container::GenericUnar - end - - def self.for_path(path, command) - odebug "Determining which containers to use based on filetype" - criteria = Hbc::Container::Criteria.new(path, command) - autodetect_containers.find do |c| - odebug "Checking container class #{c}" - c.me?(criteria) +module Hbc + class Container + def self.autodetect_containers + [ + Pkg, + Ttf, + Otf, + Air, + Cab, + Dmg, + SevenZip, + Sit, + Rar, + Zip, + Xip, # needs to be before xar as this is a cpio inside a gzip inside a xar + Xar, # need to be before tar as tar can also list xar + Tar, # or compressed tar (bzip2/gzip/lzma/xz) + Bzip2, # pure bzip2 + Gzip, # pure gzip + Lzma, # pure lzma + Xz, # pure xz + ] + # for explicit use only (never autodetected): + # Hbc::Container::Naked + # Hbc::Container::GenericUnar end - end - def self.from_type(type) - odebug "Determining which containers to use based on 'container :type'" - begin - Hbc::Container.const_get(type.to_s.split("_").map(&:capitalize).join) - rescue NameError - false + def self.for_path(path, command) + odebug "Determining which containers to use based on filetype" + criteria = Criteria.new(path, command) + autodetect_containers.find do |c| + odebug "Checking container class #{c}" + c.me?(criteria) + end + end + + def self.from_type(type) + odebug "Determining which containers to use based on 'container :type'" + begin + self.const_get(type.to_s.split("_").map(&:capitalize).join) + rescue NameError + false + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/air.rb b/Library/Homebrew/cask/lib/hbc/container/air.rb index fc618db831..4b083e538b 100644 --- a/Library/Homebrew/cask/lib/hbc/container/air.rb +++ b/Library/Homebrew/cask/lib/hbc/container/air.rb @@ -1,33 +1,37 @@ require "hbc/container/base" -class Hbc::Container::Air < Hbc::Container::Base - INSTALLER_PATHNAME = - Pathname("/Applications/Utilities/Adobe AIR Application Installer.app" \ - "/Contents/MacOS/Adobe AIR Application Installer") +module Hbc + class Container + class Air < Base + INSTALLER_PATHNAME = + Pathname("/Applications/Utilities/Adobe AIR Application Installer.app" \ + "/Contents/MacOS/Adobe AIR Application Installer") - def self.me?(criteria) - %w[.air].include?(criteria.path.extname) - end + def self.me?(criteria) + %w[.air].include?(criteria.path.extname) + end - def self.installer_cmd - return @installer_cmd ||= INSTALLER_PATHNAME if installer_exist? - raise Hbc::CaskError, <<-EOS.undent - Adobe AIR runtime not present, try installing it via + def self.installer_cmd + return @installer_cmd ||= INSTALLER_PATHNAME if installer_exist? + raise CaskError, <<-EOS.undent + Adobe AIR runtime not present, try installing it via - brew cask install adobe-air + brew cask install adobe-air - EOS - end + EOS + end - def self.installer_exist? - INSTALLER_PATHNAME.exist? - end + def self.installer_exist? + INSTALLER_PATHNAME.exist? + end - def extract - install = @command.run(self.class.installer_cmd, - args: ["-silent", "-location", @cask.staged_path, Pathname.new(@path).realpath]) + def extract + install = @command.run(self.class.installer_cmd, + args: ["-silent", "-location", @cask.staged_path, Pathname.new(@path).realpath]) - return unless install.exit_status == 9 - raise Hbc::CaskError, "Adobe AIR application #{@cask} already exists on the system, and cannot be reinstalled." + return unless install.exit_status == 9 + raise CaskError, "Adobe AIR application #{@cask} already exists on the system, and cannot be reinstalled." + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/base.rb b/Library/Homebrew/cask/lib/hbc/container/base.rb index 42331df31e..1f1c9ad9b3 100644 --- a/Library/Homebrew/cask/lib/hbc/container/base.rb +++ b/Library/Homebrew/cask/lib/hbc/container/base.rb @@ -1,37 +1,41 @@ -class Hbc::Container::Base - def initialize(cask, path, command, nested: false) - @cask = cask - @path = path - @command = command - @nested = nested - end +module Hbc + class Container + class Base + def initialize(cask, path, command, nested: false) + @cask = cask + @path = path + @command = command + @nested = nested + end - def extract_nested_inside(dir) - children = Pathname.new(dir).children + def extract_nested_inside(dir) + children = Pathname.new(dir).children - nested_container = children[0] + nested_container = children[0] - unless children.count == 1 && - !nested_container.directory? && - @cask.artifacts[:nested_container].empty? && - extract_nested_container(nested_container) + unless children.count == 1 && + !nested_container.directory? && + @cask.artifacts[:nested_container].empty? && + extract_nested_container(nested_container) - children.each do |src| - dest = @cask.staged_path.join(src.basename) - FileUtils.rm_r(dest) if dest.exist? - FileUtils.mv(src, dest) + children.each do |src| + dest = @cask.staged_path.join(src.basename) + FileUtils.rm_r(dest) if dest.exist? + FileUtils.mv(src, dest) + end + end + end + + def extract_nested_container(source) + container = Container.for_path(source, @command) + + return false unless container + + ohai "Extracting nested container #{source.basename}" + container.new(@cask, source, @command, nested: true).extract + + true end end end - - def extract_nested_container(source) - container = Hbc::Container.for_path(source, @command) - - return false unless container - - ohai "Extracting nested container #{source.basename}" - container.new(@cask, source, @command, nested: true).extract - - true - end end diff --git a/Library/Homebrew/cask/lib/hbc/container/bzip2.rb b/Library/Homebrew/cask/lib/hbc/container/bzip2.rb index 617c68b321..81808071fe 100644 --- a/Library/Homebrew/cask/lib/hbc/container/bzip2.rb +++ b/Library/Homebrew/cask/lib/hbc/container/bzip2.rb @@ -2,17 +2,21 @@ require "tmpdir" require "hbc/container/base" -class Hbc::Container::Bzip2 < Hbc::Container::Base - def self.me?(criteria) - criteria.magic_number(%r{^BZh}n) - end +module Hbc + class Container + class Bzip2 < Base + def self.me?(criteria) + criteria.magic_number(%r{^BZh}n) + end - def extract - Dir.mktmpdir do |unpack_dir| - @command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir]) - @command.run!("/usr/bin/bunzip2", args: ["--quiet", "--", Pathname.new(unpack_dir).join(@path.basename)]) + def extract + Dir.mktmpdir do |unpack_dir| + @command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir]) + @command.run!("/usr/bin/bunzip2", args: ["--quiet", "--", Pathname.new(unpack_dir).join(@path.basename)]) - extract_nested_inside(unpack_dir) + extract_nested_inside(unpack_dir) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/cab.rb b/Library/Homebrew/cask/lib/hbc/container/cab.rb index 28000a5a3f..d32b1b2055 100644 --- a/Library/Homebrew/cask/lib/hbc/container/cab.rb +++ b/Library/Homebrew/cask/lib/hbc/container/cab.rb @@ -2,25 +2,29 @@ require "tmpdir" require "hbc/container/base" -class Hbc::Container::Cab < Hbc::Container::Base - def self.me?(criteria) - cabextract = Hbc.homebrew_prefix.join("bin", "cabextract") +module Hbc + class Container + class Cab < Base + def self.me?(criteria) + cabextract = Hbc.homebrew_prefix.join("bin", "cabextract") - criteria.magic_number(%r{^MSCF}n) && - cabextract.exist? && - criteria.command.run(cabextract, args: ["-t", "--", criteria.path.to_s]).stderr.empty? - end + criteria.magic_number(%r{^MSCF}n) && + cabextract.exist? && + criteria.command.run(cabextract, args: ["-t", "--", criteria.path.to_s]).stderr.empty? + end - def extract - cabextract = Hbc.homebrew_prefix.join("bin", "cabextract") + def extract + cabextract = Hbc.homebrew_prefix.join("bin", "cabextract") - unless cabextract.exist? - raise Hbc::CaskError, "Expected to find cabextract executable. Cask '#{@cask}' must add: depends_on formula: 'cabextract'" - end + unless cabextract.exist? + raise CaskError, "Expected to find cabextract executable. Cask '#{@cask}' must add: depends_on formula: 'cabextract'" + end - Dir.mktmpdir do |unpack_dir| - @command.run!(cabextract, args: ["-d", unpack_dir, "--", @path]) - @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + Dir.mktmpdir do |unpack_dir| + @command.run!(cabextract, args: ["-d", unpack_dir, "--", @path]) + @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/criteria.rb b/Library/Homebrew/cask/lib/hbc/container/criteria.rb index 2ebb9d6fa4..207c5f3cd4 100644 --- a/Library/Homebrew/cask/lib/hbc/container/criteria.rb +++ b/Library/Homebrew/cask/lib/hbc/container/criteria.rb @@ -1,18 +1,22 @@ -class Hbc::Container::Criteria - attr_reader :path, :command +module Hbc + class Container + class Criteria + attr_reader :path, :command - def initialize(path, command) - @path = path - @command = command - end + def initialize(path, command) + @path = path + @command = command + end - def extension(regex) - path.extname.sub(%r{^\.}, "") =~ Regexp.new(regex.source, regex.options | Regexp::IGNORECASE) - end + def extension(regex) + path.extname.sub(%r{^\.}, "") =~ Regexp.new(regex.source, regex.options | Regexp::IGNORECASE) + end - def magic_number(regex) - # 262: length of the longest regex (currently: Hbc::Container::Tar) - @magic_number ||= File.open(@path, "rb") { |f| f.read(262) } - @magic_number =~ regex + def magic_number(regex) + # 262: length of the longest regex (currently: Hbc::Container::Tar) + @magic_number ||= File.open(@path, "rb") { |f| f.read(262) } + @magic_number =~ regex + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/dmg.rb b/Library/Homebrew/cask/lib/hbc/container/dmg.rb index 7e4b9340da..d53c077cb4 100644 --- a/Library/Homebrew/cask/lib/hbc/container/dmg.rb +++ b/Library/Homebrew/cask/lib/hbc/container/dmg.rb @@ -3,123 +3,127 @@ require "tempfile" require "hbc/container/base" -class Hbc::Container::Dmg < Hbc::Container::Base - def self.me?(criteria) - !criteria.command.run("/usr/bin/hdiutil", - # realpath is a failsafe against unusual filenames - args: ["imageinfo", Pathname.new(criteria.path).realpath], - print_stderr: false).stdout.empty? - end +module Hbc + class Container + class Dmg < Base + def self.me?(criteria) + !criteria.command.run("/usr/bin/hdiutil", + # realpath is a failsafe against unusual filenames + args: ["imageinfo", Pathname.new(criteria.path).realpath], + print_stderr: false).stdout.empty? + end - attr_reader :mounts - def initialize(*args) - super(*args) - @mounts = [] - end + attr_reader :mounts + def initialize(*args) + super(*args) + @mounts = [] + end - def extract - mount! - assert_mounts_found - extract_mounts - ensure - eject! - end + def extract + mount! + assert_mounts_found + extract_mounts + ensure + eject! + end - def mount! - plist = @command.run!("/usr/bin/hdiutil", - # realpath is a failsafe against unusual filenames - args: %w[mount -plist -nobrowse -readonly -noidme -mountrandom /tmp] + [Pathname.new(@path).realpath], - input: %w[y]) - .plist - @mounts = mounts_from_plist(plist) - end + def mount! + plist = @command.run!("/usr/bin/hdiutil", + # realpath is a failsafe against unusual filenames + args: %w[mount -plist -nobrowse -readonly -noidme -mountrandom /tmp] + [Pathname.new(@path).realpath], + input: %w[y]) + .plist + @mounts = mounts_from_plist(plist) + end - def eject! - @mounts.each do |mount| - # realpath is a failsafe against unusual filenames - mountpath = Pathname.new(mount).realpath - next unless mountpath.exist? + def eject! + @mounts.each do |mount| + # realpath is a failsafe against unusual filenames + mountpath = Pathname.new(mount).realpath + next unless mountpath.exist? - begin - tries ||= 2 - @command.run("/usr/sbin/diskutil", - args: ["eject", mountpath], - print_stderr: false) + begin + tries ||= 2 + @command.run("/usr/sbin/diskutil", + args: ["eject", mountpath], + print_stderr: false) - raise Hbc::CaskError, "Failed to eject #{mountpath}" if mountpath.exist? - rescue Hbc::CaskError => e - raise e if (tries -= 1).zero? - sleep 1 - retry + raise CaskError, "Failed to eject #{mountpath}" if mountpath.exist? + rescue CaskError => e + raise e if (tries -= 1).zero? + sleep 1 + retry + end + end + end + + private + + def extract_mounts + @mounts.each(&method(:extract_mount)) + end + + def extract_mount(mount) + Tempfile.open(["", ".bom"]) do |bomfile| + bomfile.close + + Tempfile.open(["", ".list"]) do |filelist| + filelist.write(bom_filelist_from_path(mount)) + filelist.close + + @command.run!("/usr/bin/mkbom", args: ["-s", "-i", filelist.path, "--", bomfile.path]) + @command.run!("/usr/bin/ditto", args: ["--bom", bomfile.path, "--", mount, @cask.staged_path]) + end + end + end + + def bom_filelist_from_path(mount) + Dir.chdir(mount) { + Dir.glob("**/*", File::FNM_DOTMATCH).map { |path| + next if skip_path?(Pathname(path)) + path == "." ? path : path.prepend("./") + }.compact.join("\n").concat("\n") + } + end + + def skip_path?(path) + dmg_metadata?(path) || system_dir_symlink?(path) + end + + # unnecessary DMG metadata + DMG_METADATA_FILES = %w[ + .background + .com.apple.timemachine.donotpresent + .DocumentRevisions-V100 + .DS_Store + .fseventsd + .MobileBackups + .Spotlight-V100 + .TemporaryItems + .Trashes + .VolumeIcon.icns + ].to_set.freeze + + def dmg_metadata?(path) + relative_root = path.sub(%r{/.*}, "") + DMG_METADATA_FILES.include?(relative_root.basename.to_s) + end + + def system_dir_symlink?(path) + # symlinks to system directories (commonly to /Applications) + path.symlink? && MacOS.system_dir?(path.readlink) + end + + def mounts_from_plist(plist) + return [] unless plist.respond_to?(:fetch) + plist.fetch("system-entities", []).map { |entity| + entity["mount-point"] + }.compact + end + + def assert_mounts_found + raise CaskError, "No mounts found in '#{@path}'; perhaps it is a bad DMG?" if @mounts.empty? end end end - - private - - def extract_mounts - @mounts.each(&method(:extract_mount)) - end - - def extract_mount(mount) - Tempfile.open(["", ".bom"]) do |bomfile| - bomfile.close - - Tempfile.open(["", ".list"]) do |filelist| - filelist.write(bom_filelist_from_path(mount)) - filelist.close - - @command.run!("/usr/bin/mkbom", args: ["-s", "-i", filelist.path, "--", bomfile.path]) - @command.run!("/usr/bin/ditto", args: ["--bom", bomfile.path, "--", mount, @cask.staged_path]) - end - end - end - - def bom_filelist_from_path(mount) - Dir.chdir(mount) { - Dir.glob("**/*", File::FNM_DOTMATCH).map { |path| - next if skip_path?(Pathname(path)) - path == "." ? path : path.prepend("./") - }.compact.join("\n").concat("\n") - } - end - - def skip_path?(path) - dmg_metadata?(path) || system_dir_symlink?(path) - end - - # unnecessary DMG metadata - DMG_METADATA_FILES = %w[ - .background - .com.apple.timemachine.donotpresent - .DocumentRevisions-V100 - .DS_Store - .fseventsd - .MobileBackups - .Spotlight-V100 - .TemporaryItems - .Trashes - .VolumeIcon.icns - ].to_set.freeze - - def dmg_metadata?(path) - relative_root = path.sub(%r{/.*}, "") - DMG_METADATA_FILES.include?(relative_root.basename.to_s) - end - - def system_dir_symlink?(path) - # symlinks to system directories (commonly to /Applications) - path.symlink? && MacOS.system_dir?(path.readlink) - end - - def mounts_from_plist(plist) - return [] unless plist.respond_to?(:fetch) - plist.fetch("system-entities", []).map { |entity| - entity["mount-point"] - }.compact - end - - def assert_mounts_found - raise Hbc::CaskError, "No mounts found in '#{@path}'; perhaps it is a bad DMG?" if @mounts.empty? - end end diff --git a/Library/Homebrew/cask/lib/hbc/container/generic_unar.rb b/Library/Homebrew/cask/lib/hbc/container/generic_unar.rb index 1dcc0997ac..32bbc8d159 100644 --- a/Library/Homebrew/cask/lib/hbc/container/generic_unar.rb +++ b/Library/Homebrew/cask/lib/hbc/container/generic_unar.rb @@ -2,25 +2,29 @@ require "tmpdir" require "hbc/container/base" -class Hbc::Container::GenericUnar < Hbc::Container::Base - def self.me?(criteria) - lsar = Hbc.homebrew_prefix.join("bin", "lsar") - lsar.exist? && - criteria.command.run(lsar, - args: ["-l", "-t", "--", criteria.path], - print_stderr: false).stdout.chomp.end_with?("passed, 0 failed.") - end +module Hbc + class Container + class GenericUnar < Base + def self.me?(criteria) + lsar = Hbc.homebrew_prefix.join("bin", "lsar") + lsar.exist? && + criteria.command.run(lsar, + args: ["-l", "-t", "--", criteria.path], + print_stderr: false).stdout.chomp.end_with?("passed, 0 failed.") + end - def extract - unar = Hbc.homebrew_prefix.join("bin", "unar") + def extract + unar = Hbc.homebrew_prefix.join("bin", "unar") - unless unar.exist? - raise Hbc::CaskError, "Expected to find unar executable. Cask #{@cask} must add: depends_on formula: 'unar'" - end + unless unar.exist? + raise CaskError, "Expected to find unar executable. Cask #{@cask} must add: depends_on formula: 'unar'" + end - Dir.mktmpdir do |unpack_dir| - @command.run!(unar, args: ["-force-overwrite", "-quiet", "-no-directory", "-output-directory", unpack_dir, "--", @path]) - @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + Dir.mktmpdir do |unpack_dir| + @command.run!(unar, args: ["-force-overwrite", "-quiet", "-no-directory", "-output-directory", unpack_dir, "--", @path]) + @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/gzip.rb b/Library/Homebrew/cask/lib/hbc/container/gzip.rb index 1615781620..7e4722a7f8 100644 --- a/Library/Homebrew/cask/lib/hbc/container/gzip.rb +++ b/Library/Homebrew/cask/lib/hbc/container/gzip.rb @@ -2,17 +2,21 @@ require "tmpdir" require "hbc/container/base" -class Hbc::Container::Gzip < Hbc::Container::Base - def self.me?(criteria) - criteria.magic_number(%r{^\037\213}n) - end +module Hbc + class Container + class Gzip < Base + def self.me?(criteria) + criteria.magic_number(%r{^\037\213}n) + end - def extract - Dir.mktmpdir do |unpack_dir| - @command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir]) - @command.run!("/usr/bin/gunzip", args: ["--quiet", "--name", "--", Pathname.new(unpack_dir).join(@path.basename)]) + def extract + Dir.mktmpdir do |unpack_dir| + @command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir]) + @command.run!("/usr/bin/gunzip", args: ["--quiet", "--name", "--", Pathname.new(unpack_dir).join(@path.basename)]) - extract_nested_inside(unpack_dir) + extract_nested_inside(unpack_dir) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/lzma.rb b/Library/Homebrew/cask/lib/hbc/container/lzma.rb index e538b3779e..a91132b556 100644 --- a/Library/Homebrew/cask/lib/hbc/container/lzma.rb +++ b/Library/Homebrew/cask/lib/hbc/container/lzma.rb @@ -2,22 +2,26 @@ require "tmpdir" require "hbc/container/base" -class Hbc::Container::Lzma < Hbc::Container::Base - def self.me?(criteria) - criteria.magic_number(%r{^\]\000\000\200\000}n) - end +module Hbc + class Container + class Lzma < Base + def self.me?(criteria) + criteria.magic_number(%r{^\]\000\000\200\000}n) + end - def extract - unlzma = Hbc.homebrew_prefix.join("bin", "unlzma") + def extract + unlzma = Hbc.homebrew_prefix.join("bin", "unlzma") - unless unlzma.exist? - raise Hbc::CaskError, "Expected to find unlzma executable. Cask '#{@cask}' must add: depends_on formula: 'lzma'" - end + unless unlzma.exist? + raise CaskError, "Expected to find unlzma executable. Cask '#{@cask}' must add: depends_on formula: 'lzma'" + end - Dir.mktmpdir do |unpack_dir| - @command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir]) - @command.run!(unlzma, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)]) - @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + Dir.mktmpdir do |unpack_dir| + @command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir]) + @command.run!(unlzma, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)]) + @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/naked.rb b/Library/Homebrew/cask/lib/hbc/container/naked.rb index 596f50789c..375d62f7ae 100644 --- a/Library/Homebrew/cask/lib/hbc/container/naked.rb +++ b/Library/Homebrew/cask/lib/hbc/container/naked.rb @@ -1,19 +1,23 @@ require "hbc/container/base" -class Hbc::Container::Naked < Hbc::Container::Base - # Either inherit from this class and override with self.me?(criteria), - # or use this class directly as "container type: :naked", - # in which case self.me? is not called. - def self.me?(*) - false - end +module Hbc + class Container + class Naked < Base + # Either inherit from this class and override with self.me?(criteria), + # or use this class directly as "container type: :naked", + # in which case self.me? is not called. + def self.me?(*) + false + end - def extract - @command.run!("/usr/bin/ditto", args: ["--", @path, @cask.staged_path.join(target_file)]) - end + def extract + @command.run!("/usr/bin/ditto", args: ["--", @path, @cask.staged_path.join(target_file)]) + end - def target_file - return @path.basename if @nested - URI.decode(File.basename(@cask.url.path)) + def target_file + return @path.basename if @nested + URI.decode(File.basename(@cask.url.path)) + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/otf.rb b/Library/Homebrew/cask/lib/hbc/container/otf.rb index f9a25e1ed0..66222ad8d6 100644 --- a/Library/Homebrew/cask/lib/hbc/container/otf.rb +++ b/Library/Homebrew/cask/lib/hbc/container/otf.rb @@ -1,7 +1,11 @@ require "hbc/container/naked" -class Hbc::Container::Otf < Hbc::Container::Naked - def self.me?(criteria) - criteria.magic_number(%r{^OTTO}n) +module Hbc + class Container + class Otf < Naked + def self.me?(criteria) + criteria.magic_number(%r{^OTTO}n) + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/pkg.rb b/Library/Homebrew/cask/lib/hbc/container/pkg.rb index 5d2282d0fb..dfd62a8633 100644 --- a/Library/Homebrew/cask/lib/hbc/container/pkg.rb +++ b/Library/Homebrew/cask/lib/hbc/container/pkg.rb @@ -1,9 +1,13 @@ require "hbc/container/naked" -class Hbc::Container::Pkg < Hbc::Container::Naked - def self.me?(criteria) - criteria.extension(%r{m?pkg$}) && - (criteria.path.directory? || - criteria.magic_number(%r{^xar!}n)) +module Hbc + class Container + class Pkg < Naked + def self.me?(criteria) + criteria.extension(%r{m?pkg$}) && + (criteria.path.directory? || + criteria.magic_number(%r{^xar!}n)) + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/rar.rb b/Library/Homebrew/cask/lib/hbc/container/rar.rb index 9c144006f6..bcf225623b 100644 --- a/Library/Homebrew/cask/lib/hbc/container/rar.rb +++ b/Library/Homebrew/cask/lib/hbc/container/rar.rb @@ -1,8 +1,12 @@ require "hbc/container/generic_unar" -class Hbc::Container::Rar < Hbc::Container::GenericUnar - def self.me?(criteria) - criteria.magic_number(%r{^Rar!}n) && - super +module Hbc + class Container + class Rar < GenericUnar + def self.me?(criteria) + criteria.magic_number(%r{^Rar!}n) && + super + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/seven_zip.rb b/Library/Homebrew/cask/lib/hbc/container/seven_zip.rb index f0d183064e..7a144d5327 100644 --- a/Library/Homebrew/cask/lib/hbc/container/seven_zip.rb +++ b/Library/Homebrew/cask/lib/hbc/container/seven_zip.rb @@ -1,9 +1,13 @@ require "hbc/container/generic_unar" -class Hbc::Container::SevenZip < Hbc::Container::GenericUnar - def self.me?(criteria) - # TODO: cover self-extracting archives - criteria.magic_number(%r{^7z}n) && - super +module Hbc + class Container + class SevenZip < GenericUnar + def self.me?(criteria) + # TODO: cover self-extracting archives + criteria.magic_number(%r{^7z}n) && + super + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/sit.rb b/Library/Homebrew/cask/lib/hbc/container/sit.rb index 155b93f3f4..2d88493994 100644 --- a/Library/Homebrew/cask/lib/hbc/container/sit.rb +++ b/Library/Homebrew/cask/lib/hbc/container/sit.rb @@ -1,8 +1,12 @@ require "hbc/container/generic_unar" -class Hbc::Container::Sit < Hbc::Container::GenericUnar - def self.me?(criteria) - criteria.magic_number(%r{^StuffIt}n) && - super +module Hbc + class Container + class Sit < GenericUnar + def self.me?(criteria) + criteria.magic_number(%r{^StuffIt}n) && + super + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/tar.rb b/Library/Homebrew/cask/lib/hbc/container/tar.rb index 8bc7c5f643..df77e454d0 100644 --- a/Library/Homebrew/cask/lib/hbc/container/tar.rb +++ b/Library/Homebrew/cask/lib/hbc/container/tar.rb @@ -2,17 +2,21 @@ require "tmpdir" require "hbc/container/base" -class Hbc::Container::Tar < Hbc::Container::Base - def self.me?(criteria) - criteria.magic_number(%r{^.{257}ustar}n) || - # or compressed tar (bzip2/gzip/lzma/xz) - IO.popen(["/usr/bin/tar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| !io.read(1).nil? } - end +module Hbc + class Container + class Tar < Base + def self.me?(criteria) + criteria.magic_number(%r{^.{257}ustar}n) || + # or compressed tar (bzip2/gzip/lzma/xz) + IO.popen(["/usr/bin/tar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| !io.read(1).nil? } + end - def extract - Dir.mktmpdir do |unpack_dir| - @command.run!("/usr/bin/tar", args: ["-x", "-f", @path, "-C", unpack_dir]) - @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + def extract + Dir.mktmpdir do |unpack_dir| + @command.run!("/usr/bin/tar", args: ["-x", "-f", @path, "-C", unpack_dir]) + @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/ttf.rb b/Library/Homebrew/cask/lib/hbc/container/ttf.rb index 8d787f360c..62ca29b223 100644 --- a/Library/Homebrew/cask/lib/hbc/container/ttf.rb +++ b/Library/Homebrew/cask/lib/hbc/container/ttf.rb @@ -1,10 +1,14 @@ require "hbc/container/naked" -class Hbc::Container::Ttf < Hbc::Container::Naked - def self.me?(criteria) - # TrueType Font - criteria.magic_number(%r{^\000\001\000\000\000}n) || - # Truetype Font Collection - criteria.magic_number(%r{^ttcf}n) +module Hbc + class Container + class Ttf < Naked + def self.me?(criteria) + # TrueType Font + criteria.magic_number(%r{^\000\001\000\000\000}n) || + # Truetype Font Collection + criteria.magic_number(%r{^ttcf}n) + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/xar.rb b/Library/Homebrew/cask/lib/hbc/container/xar.rb index 5afc78bc55..228ab23435 100644 --- a/Library/Homebrew/cask/lib/hbc/container/xar.rb +++ b/Library/Homebrew/cask/lib/hbc/container/xar.rb @@ -2,15 +2,19 @@ require "tmpdir" require "hbc/container/base" -class Hbc::Container::Xar < Hbc::Container::Base - def self.me?(criteria) - criteria.magic_number(%r{^xar!}n) - end +module Hbc + class Container + class Xar < Base + def self.me?(criteria) + criteria.magic_number(%r{^xar!}n) + end - def extract - Dir.mktmpdir do |unpack_dir| - @command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "-C", unpack_dir]) - @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + def extract + Dir.mktmpdir do |unpack_dir| + @command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "-C", unpack_dir]) + @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/xip.rb b/Library/Homebrew/cask/lib/hbc/container/xip.rb index 579f28fe0d..2d0f17e2e8 100644 --- a/Library/Homebrew/cask/lib/hbc/container/xip.rb +++ b/Library/Homebrew/cask/lib/hbc/container/xip.rb @@ -1,24 +1,28 @@ require "tmpdir" -class Hbc::Container::Xip < Hbc::Container::Base - def self.me?(criteria) - criteria.magic_number(%r{^xar!}n) && - IO.popen(["/usr/bin/xar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| io.read =~ %r{\AContent\nMetadata\n\Z} } - end - - def extract - Dir.mktmpdir do |unpack_dir| - begin - ohai "Verifying signature for #{@path.basename}" - @command.run!("/usr/sbin/pkgutil", args: ["--check-signature", @path]) - rescue - raise "Signature check failed." +module Hbc + class Container + class Xip < Base + def self.me?(criteria) + criteria.magic_number(%r{^xar!}n) && + IO.popen(["/usr/bin/xar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| io.read =~ %r{\AContent\nMetadata\n\Z} } end - @command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "Content", "-C", unpack_dir]) + def extract + Dir.mktmpdir do |unpack_dir| + begin + ohai "Verifying signature for #{@path.basename}" + @command.run!("/usr/sbin/pkgutil", args: ["--check-signature", @path]) + rescue + raise "Signature check failed." + end - Dir.chdir(@cask.staged_path) do - @command.run!("/usr/bin/cpio", args: ["--quiet", "-i", "-I", Pathname(unpack_dir).join("Content")]) + @command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "Content", "-C", unpack_dir]) + + Dir.chdir(@cask.staged_path) do + @command.run!("/usr/bin/cpio", args: ["--quiet", "-i", "-I", Pathname(unpack_dir).join("Content")]) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/xz.rb b/Library/Homebrew/cask/lib/hbc/container/xz.rb index 228532943d..831bef5aa8 100644 --- a/Library/Homebrew/cask/lib/hbc/container/xz.rb +++ b/Library/Homebrew/cask/lib/hbc/container/xz.rb @@ -2,22 +2,26 @@ require "tmpdir" require "hbc/container/base" -class Hbc::Container::Xz < Hbc::Container::Base - def self.me?(criteria) - criteria.magic_number(%r{^\xFD7zXZ\x00}n) - end +module Hbc + class Container + class Xz < Base + def self.me?(criteria) + criteria.magic_number(%r{^\xFD7zXZ\x00}n) + end - def extract - unxz = Hbc.homebrew_prefix.join("bin", "unxz") + def extract + unxz = Hbc.homebrew_prefix.join("bin", "unxz") - unless unxz.exist? - raise Hbc::CaskError, "Expected to find unxz executable. Cask '#{@cask}' must add: depends_on formula: 'xz'" - end + unless unxz.exist? + raise CaskError, "Expected to find unxz executable. Cask '#{@cask}' must add: depends_on formula: 'xz'" + end - Dir.mktmpdir do |unpack_dir| - @command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir]) - @command.run!(unxz, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)]) - @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + Dir.mktmpdir do |unpack_dir| + @command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir]) + @command.run!(unxz, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)]) + @command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path]) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/container/zip.rb b/Library/Homebrew/cask/lib/hbc/container/zip.rb index c6702fbb5e..86e2d1bd69 100644 --- a/Library/Homebrew/cask/lib/hbc/container/zip.rb +++ b/Library/Homebrew/cask/lib/hbc/container/zip.rb @@ -1,15 +1,19 @@ require "hbc/container/base" -class Hbc::Container::Zip < Hbc::Container::Base - def self.me?(criteria) - criteria.magic_number(%r{^PK(\003\004|\005\006)}n) - end +module Hbc + class Container + class Zip < Base + def self.me?(criteria) + criteria.magic_number(%r{^PK(\003\004|\005\006)}n) + end - def extract - Dir.mktmpdir do |unpack_dir| - @command.run!("/usr/bin/ditto", args: ["-x", "-k", "--", @path, unpack_dir]) + def extract + Dir.mktmpdir do |unpack_dir| + @command.run!("/usr/bin/ditto", args: ["-x", "-k", "--", @path, unpack_dir]) - extract_nested_inside(unpack_dir) + extract_nested_inside(unpack_dir) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/download.rb b/Library/Homebrew/cask/lib/hbc/download.rb index 18dd7fe44c..4166fc74dc 100644 --- a/Library/Homebrew/cask/lib/hbc/download.rb +++ b/Library/Homebrew/cask/lib/hbc/download.rb @@ -1,43 +1,45 @@ require "fileutils" require "hbc/verify" -class Hbc::Download - attr_reader :cask +module Hbc + class Download + attr_reader :cask - def initialize(cask, force: false) - @cask = cask - @force = force - end + def initialize(cask, force: false) + @cask = cask + @force = force + end - def perform - clear_cache - fetch - downloaded_path - end + def perform + clear_cache + fetch + downloaded_path + end - private + private - attr_reader :force - attr_accessor :downloaded_path + attr_reader :force + attr_accessor :downloaded_path - def downloader - @downloader ||= case cask.url.using - when :svn - Hbc::SubversionDownloadStrategy.new(cask) - when :post - Hbc::CurlPostDownloadStrategy.new(cask) - else - Hbc::CurlDownloadStrategy.new(cask) - end - end + def downloader + @downloader ||= case cask.url.using + when :svn + SubversionDownloadStrategy.new(cask) + when :post + CurlPostDownloadStrategy.new(cask) + else + CurlDownloadStrategy.new(cask) + end + end - def clear_cache - downloader.clear_cache if force || cask.version.latest? - end + def clear_cache + downloader.clear_cache if force || cask.version.latest? + end - def fetch - self.downloaded_path = downloader.fetch - rescue StandardError => e - raise Hbc::CaskError, "Download failed on Cask '#{cask}' with message: #{e}" + def fetch + self.downloaded_path = downloader.fetch + rescue StandardError => e + raise CaskError, "Download failed on Cask '#{cask}' with message: #{e}" + end end end diff --git a/Library/Homebrew/cask/lib/hbc/download_strategy.rb b/Library/Homebrew/cask/lib/hbc/download_strategy.rb index 88ffb50505..400b9ffc87 100644 --- a/Library/Homebrew/cask/lib/hbc/download_strategy.rb +++ b/Library/Homebrew/cask/lib/hbc/download_strategy.rb @@ -6,327 +6,329 @@ require "cgi" # * Our overridden fetch methods are expected to return # a value: the successfully downloaded file. -class Hbc::AbstractDownloadStrategy - attr_reader :cask, :name, :url, :uri_object, :version +module Hbc + class AbstractDownloadStrategy + attr_reader :cask, :name, :url, :uri_object, :version - def initialize(cask, command = Hbc::SystemCommand) - @cask = cask - @command = command - # TODO: this excess of attributes is a function of integrating - # with Homebrew's classes. Later we should be able to remove - # these in favor of @cask - @name = cask.token - @url = cask.url.to_s - @uri_object = cask.url - @version = cask.version + def initialize(cask, command = SystemCommand) + @cask = cask + @command = command + # TODO: this excess of attributes is a function of integrating + # with Homebrew's classes. Later we should be able to remove + # these in favor of @cask + @name = cask.token + @url = cask.url.to_s + @uri_object = cask.url + @version = cask.version + end + + # All download strategies are expected to implement these methods + def fetch; end + + def cached_location; end + + def clear_cache; end end - # All download strategies are expected to implement these methods - def fetch; end + class HbVCSDownloadStrategy < AbstractDownloadStrategy + REF_TYPES = [:branch, :revision, :revisions, :tag].freeze - def cached_location; end + def initialize(cask, command = SystemCommand) + super + @ref_type, @ref = extract_ref + @clone = Hbc.cache.join(cache_filename) + end - def clear_cache; end -end + def extract_ref + key = REF_TYPES.find { |type| + uri_object.respond_to?(type) && uri_object.send(type) + } + [key, key ? uri_object.send(key) : nil] + end -class Hbc::HbVCSDownloadStrategy < Hbc::AbstractDownloadStrategy - REF_TYPES = [:branch, :revision, :revisions, :tag].freeze + def cache_filename + "#{name}--#{cache_tag}" + end - def initialize(cask, command = Hbc::SystemCommand) - super - @ref_type, @ref = extract_ref - @clone = Hbc.cache.join(cache_filename) - end + def cache_tag + "__UNKNOWN__" + end - def extract_ref - key = REF_TYPES.find { |type| - uri_object.respond_to?(type) && uri_object.send(type) - } - [key, key ? uri_object.send(key) : nil] - end + def cached_location + @clone + end - def cache_filename - "#{name}--#{cache_tag}" - end - - def cache_tag - "__UNKNOWN__" - end - - def cached_location - @clone - end - - def clear_cache - cached_location.rmtree if cached_location.exist? - end -end - -class Hbc::CurlDownloadStrategy < Hbc::AbstractDownloadStrategy - # TODO: should be part of url object - def mirrors - @mirrors ||= [] - end - - def tarball_path - @tarball_path ||= Hbc.cache.join("#{name}--#{version}#{ext}") - end - - def temporary_path - @temporary_path ||= tarball_path.sub(%r{$}, ".incomplete") - end - - def cached_location - tarball_path - end - - def clear_cache - [cached_location, temporary_path].each do |f| - next unless f.exist? - raise CurlDownloadStrategyError, "#{f} is in use by another process" if Hbc::Utils.file_locked?(f) - f.unlink + def clear_cache + cached_location.rmtree if cached_location.exist? end end - def downloaded_size - temporary_path.size? || 0 - end + class CurlDownloadStrategy < AbstractDownloadStrategy + # TODO: should be part of url object + def mirrors + @mirrors ||= [] + end - def _fetch - odebug "Calling curl with args #{cask_curl_args.utf8_inspect}" - curl(*cask_curl_args) - end + def tarball_path + @tarball_path ||= Hbc.cache.join("#{name}--#{version}#{ext}") + end - def fetch - ohai "Downloading #{@url}" - if tarball_path.exist? - puts "Already downloaded: #{tarball_path}" - else - had_incomplete_download = temporary_path.exist? - begin - File.open(temporary_path, "w+") do |f| - f.flock(File::LOCK_EX) - _fetch - f.flock(File::LOCK_UN) - end - rescue ErrorDuringExecution - # 33 == range not supported - # try wiping the incomplete download and retrying once - if $CHILD_STATUS.exitstatus == 33 && had_incomplete_download - ohai "Trying a full download" - temporary_path.unlink - had_incomplete_download = false - retry - end + def temporary_path + @temporary_path ||= tarball_path.sub(%r{$}, ".incomplete") + end - msg = @url - msg.concat("\nThe incomplete download is cached at #{temporary_path}") if temporary_path.exist? - raise CurlDownloadStrategyError, msg + def cached_location + tarball_path + end + + def clear_cache + [cached_location, temporary_path].each do |f| + next unless f.exist? + raise CurlDownloadStrategyError, "#{f} is in use by another process" if Utils.file_locked?(f) + f.unlink end - ignore_interrupts { temporary_path.rename(tarball_path) } end - tarball_path - rescue CurlDownloadStrategyError - raise if mirrors.empty? - puts "Trying a mirror..." - @url = mirrors.shift - retry - end - private + def downloaded_size + temporary_path.size? || 0 + end - def cask_curl_args - default_curl_args.tap do |args| - args.concat(user_agent_args) - args.concat(cookies_args) - args.concat(referer_args) + def _fetch + odebug "Calling curl with args #{cask_curl_args.utf8_inspect}" + curl(*cask_curl_args) + end + + def fetch + ohai "Downloading #{@url}" + if tarball_path.exist? + puts "Already downloaded: #{tarball_path}" + else + had_incomplete_download = temporary_path.exist? + begin + File.open(temporary_path, "w+") do |f| + f.flock(File::LOCK_EX) + _fetch + f.flock(File::LOCK_UN) + end + rescue ErrorDuringExecution + # 33 == range not supported + # try wiping the incomplete download and retrying once + if $CHILD_STATUS.exitstatus == 33 && had_incomplete_download + ohai "Trying a full download" + temporary_path.unlink + had_incomplete_download = false + retry + end + + msg = @url + msg.concat("\nThe incomplete download is cached at #{temporary_path}") if temporary_path.exist? + raise CurlDownloadStrategyError, msg + end + ignore_interrupts { temporary_path.rename(tarball_path) } + end + tarball_path + rescue CurlDownloadStrategyError + raise if mirrors.empty? + puts "Trying a mirror..." + @url = mirrors.shift + retry + end + + private + + def cask_curl_args + default_curl_args.tap do |args| + args.concat(user_agent_args) + args.concat(cookies_args) + args.concat(referer_args) + end + end + + def default_curl_args + [url, "-C", downloaded_size, "-o", temporary_path] + end + + def user_agent_args + if uri_object.user_agent + ["-A", uri_object.user_agent] + else + [] + end + end + + def cookies_args + if uri_object.cookies + [ + "-b", + # sort_by is for predictability between Ruby versions + uri_object + .cookies + .sort_by(&:to_s) + .map { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" } + .join(";"), + ] + else + [] + end + end + + def referer_args + if uri_object.referer + ["-e", uri_object.referer] + else + [] + end + end + + def ext + Pathname.new(@url).extname end end - def default_curl_args - [url, "-C", downloaded_size, "-o", temporary_path] - end - - def user_agent_args - if uri_object.user_agent - ["-A", uri_object.user_agent] - else - [] + class CurlPostDownloadStrategy < CurlDownloadStrategy + def cask_curl_args + super + default_curl_args.concat(post_args) end - end - def cookies_args - if uri_object.cookies - [ - "-b", + def post_args + if uri_object.data # sort_by is for predictability between Ruby versions uri_object - .cookies + .data .sort_by(&:to_s) - .map { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" } - .join(";"), - ] - else - [] - end - end - - def referer_args - if uri_object.referer - ["-e", uri_object.referer] - else - [] - end - end - - def ext - Pathname.new(@url).extname - end -end - -class Hbc::CurlPostDownloadStrategy < Hbc::CurlDownloadStrategy - def cask_curl_args - super - default_curl_args.concat(post_args) - end - - def post_args - if uri_object.data - # sort_by is for predictability between Ruby versions - uri_object - .data - .sort_by(&:to_s) - .map { |key, value| ["-d", "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"] } - .flatten - else - ["-X", "POST"] - end - end -end - -class Hbc::SubversionDownloadStrategy < Hbc::HbVCSDownloadStrategy - def cache_tag - # TODO: pass versions as symbols, support :head here - version == "head" ? "svn-HEAD" : "svn" - end - - def repo_valid? - @clone.join(".svn").directory? - end - - def repo_url - `svn info '#{@clone}' 2>/dev/null`.strip[%r{^URL: (.+)$}, 1] - end - - # super does not provide checks for already-existing downloads - def fetch - if tarball_path.exist? - puts "Already downloaded: #{tarball_path}" - else - @url = @url.sub(%r{^svn\+}, "") if @url =~ %r{^svn\+http://} - ohai "Checking out #{@url}" - - clear_cache unless @url.chomp("/") == repo_url || quiet_system("svn", "switch", @url, @clone) - - if @clone.exist? && !repo_valid? - puts "Removing invalid SVN repo from cache" - clear_cache - end - - case @ref_type - when :revision - fetch_repo @clone, @url, @ref - when :revisions - # nil is OK for main_revision, as fetch_repo will then get latest - main_revision = @ref[:trunk] - fetch_repo @clone, @url, main_revision, true - - fetch_externals do |external_name, external_url| - fetch_repo @clone + external_name, external_url, @ref[external_name], true - end + .map { |key, value| ["-d", "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"] } + .flatten else - fetch_repo @clone, @url + ["-X", "POST"] end - compress - end - tarball_path - end - - # This primary reason for redefining this method is the trust_cert - # option, controllable from the Cask definition. We also force - # consistent timestamps. The rest of this method is similar to - # Homebrew's, but translated to local idiom. - def fetch_repo(target, url, revision = uri_object.revision, ignore_externals = false) - # Use "svn up" when the repository already exists locally. - # This saves on bandwidth and will have a similar effect to verifying the - # cache as it will make any changes to get the right revision. - svncommand = target.directory? ? "up" : "checkout" - args = [svncommand] - - # SVN shipped with XCode 3.1.4 can't force a checkout. - args << "--force" unless MacOS.version == :leopard - - # make timestamps consistent for checksumming - args.concat(%w[--config-option config:miscellany:use-commit-times=yes]) - - if uri_object.trust_cert - args << "--trust-server-cert" - args << "--non-interactive" - end - - args << url unless target.directory? - args << target - args << "-r" << revision if revision - args << "--ignore-externals" if ignore_externals - @command.run!("/usr/bin/svn", - args: args, - print_stderr: false) - end - - def tarball_path - @tarball_path ||= cached_location.dirname.join(cached_location.basename.to_s + "-#{@cask.version}.tar") - end - - def shell_quote(str) - # Oh god escaping shell args. - # See http://notetoself.vrensk.com/2008/08/escaping-single-quotes-in-ruby-harder-than-expected/ - str.gsub(%r{\\|'}) { |c| "\\#{c}" } - end - - def fetch_externals - `svn propget svn:externals '#{shell_quote(@url)}'`.chomp.each_line do |line| - name, url = line.split(%r{\s+}) - yield name, url end end - private + class SubversionDownloadStrategy < HbVCSDownloadStrategy + def cache_tag + # TODO: pass versions as symbols, support :head here + version == "head" ? "svn-HEAD" : "svn" + end - # TODO/UPDATE: the tar approach explained below is fragile - # against challenges such as case-sensitive filesystems, - # and must be re-implemented. - # - # Seems nutty: we "download" the contents into a tape archive. - # Why? - # * A single file is tractable to the rest of the Cask toolchain, - # * An alternative would be to create a Directory container type. - # However, some type of file-serialization trick would still be - # needed in order to enable calculating a single checksum over - # a directory. So, in that alternative implementation, the - # special cases would propagate outside this class, including - # the use of tar or equivalent. - # * SubversionDownloadStrategy.cached_location is not versioned - # * tarball_path provides a needed return value for our overridden - # fetch method. - # * We can also take this private opportunity to strip files from - # the download which are protocol-specific. + def repo_valid? + @clone.join(".svn").directory? + end - def compress - Dir.chdir(cached_location) do - @command.run!("/usr/bin/tar", - args: ['-s/^\.//', "--exclude", ".svn", "-cf", Pathname.new(tarball_path), "--", "."], + def repo_url + `svn info '#{@clone}' 2>/dev/null`.strip[%r{^URL: (.+)$}, 1] + end + + # super does not provide checks for already-existing downloads + def fetch + if tarball_path.exist? + puts "Already downloaded: #{tarball_path}" + else + @url = @url.sub(%r{^svn\+}, "") if @url =~ %r{^svn\+http://} + ohai "Checking out #{@url}" + + clear_cache unless @url.chomp("/") == repo_url || quiet_system("svn", "switch", @url, @clone) + + if @clone.exist? && !repo_valid? + puts "Removing invalid SVN repo from cache" + clear_cache + end + + case @ref_type + when :revision + fetch_repo @clone, @url, @ref + when :revisions + # nil is OK for main_revision, as fetch_repo will then get latest + main_revision = @ref[:trunk] + fetch_repo @clone, @url, main_revision, true + + fetch_externals do |external_name, external_url| + fetch_repo @clone + external_name, external_url, @ref[external_name], true + end + else + fetch_repo @clone, @url + end + compress + end + tarball_path + end + + # This primary reason for redefining this method is the trust_cert + # option, controllable from the Cask definition. We also force + # consistent timestamps. The rest of this method is similar to + # Homebrew's, but translated to local idiom. + def fetch_repo(target, url, revision = uri_object.revision, ignore_externals = false) + # Use "svn up" when the repository already exists locally. + # This saves on bandwidth and will have a similar effect to verifying the + # cache as it will make any changes to get the right revision. + svncommand = target.directory? ? "up" : "checkout" + args = [svncommand] + + # SVN shipped with XCode 3.1.4 can't force a checkout. + args << "--force" unless MacOS.version == :leopard + + # make timestamps consistent for checksumming + args.concat(%w[--config-option config:miscellany:use-commit-times=yes]) + + if uri_object.trust_cert + args << "--trust-server-cert" + args << "--non-interactive" + end + + args << url unless target.directory? + args << target + args << "-r" << revision if revision + args << "--ignore-externals" if ignore_externals + @command.run!("/usr/bin/svn", + args: args, print_stderr: false) end - clear_cache + + def tarball_path + @tarball_path ||= cached_location.dirname.join(cached_location.basename.to_s + "-#{@cask.version}.tar") + end + + def shell_quote(str) + # Oh god escaping shell args. + # See http://notetoself.vrensk.com/2008/08/escaping-single-quotes-in-ruby-harder-than-expected/ + str.gsub(%r{\\|'}) { |c| "\\#{c}" } + end + + def fetch_externals + `svn propget svn:externals '#{shell_quote(@url)}'`.chomp.each_line do |line| + name, url = line.split(%r{\s+}) + yield name, url + end + end + + private + + # TODO/UPDATE: the tar approach explained below is fragile + # against challenges such as case-sensitive filesystems, + # and must be re-implemented. + # + # Seems nutty: we "download" the contents into a tape archive. + # Why? + # * A single file is tractable to the rest of the Cask toolchain, + # * An alternative would be to create a Directory container type. + # However, some type of file-serialization trick would still be + # needed in order to enable calculating a single checksum over + # a directory. So, in that alternative implementation, the + # special cases would propagate outside this class, including + # the use of tar or equivalent. + # * SubversionDownloadStrategy.cached_location is not versioned + # * tarball_path provides a needed return value for our overridden + # fetch method. + # * We can also take this private opportunity to strip files from + # the download which are protocol-specific. + + def compress + Dir.chdir(cached_location) do + @command.run!("/usr/bin/tar", + args: ['-s/^\.//', "--exclude", ".svn", "-cf", Pathname.new(tarball_path), "--", "."], + print_stderr: false) + end + clear_cache + end end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl.rb b/Library/Homebrew/cask/lib/hbc/dsl.rb index b947741eab..83c0bf1fb3 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl.rb @@ -1,7 +1,5 @@ require "set" -class Hbc::DSL; end - require "hbc/dsl/appcast" require "hbc/dsl/base" require "hbc/dsl/caveats" @@ -18,274 +16,276 @@ require "hbc/dsl/uninstall_postflight" require "hbc/dsl/uninstall_preflight" require "hbc/dsl/version" -class Hbc::DSL - ORDINARY_ARTIFACT_TYPES = [ - :app, - :artifact, - :audio_unit_plugin, - :binary, - :colorpicker, - :font, - :input_method, - :internet_plugin, - :pkg, - :prefpane, - :qlplugin, - :screen_saver, - :service, - :stage_only, - :suite, - :vst_plugin, - :vst3_plugin, - ].freeze +module Hbc + class DSL + ORDINARY_ARTIFACT_TYPES = [ + :app, + :artifact, + :audio_unit_plugin, + :binary, + :colorpicker, + :font, + :input_method, + :internet_plugin, + :pkg, + :prefpane, + :qlplugin, + :screen_saver, + :service, + :stage_only, + :suite, + :vst_plugin, + :vst3_plugin, + ].freeze - ACTIVATABLE_ARTIFACT_TYPES = ([:installer, *ORDINARY_ARTIFACT_TYPES] - [:stage_only]).freeze + ACTIVATABLE_ARTIFACT_TYPES = ([:installer, *ORDINARY_ARTIFACT_TYPES] - [:stage_only]).freeze - SPECIAL_ARTIFACT_TYPES = [ - :uninstall, - :zap, + SPECIAL_ARTIFACT_TYPES = [ + :uninstall, + :zap, + ].freeze + + ARTIFACT_BLOCK_TYPES = [ + :preflight, + :postflight, + :uninstall_preflight, + :uninstall_postflight, ].freeze - ARTIFACT_BLOCK_TYPES = [ - :preflight, - :postflight, - :uninstall_preflight, - :uninstall_postflight, - ].freeze + DSL_METHODS = Set.new [ + :accessibility_access, + :appcast, + :artifacts, + :auto_updates, + :caskroom_path, + :caveats, + :conflicts_with, + :container, + :depends_on, + :gpg, + :homepage, + :license, + :name, + :sha256, + :staged_path, + :url, + :version, + :appdir, + *ORDINARY_ARTIFACT_TYPES, + *ACTIVATABLE_ARTIFACT_TYPES, + *SPECIAL_ARTIFACT_TYPES, + *ARTIFACT_BLOCK_TYPES, + ].freeze - DSL_METHODS = Set.new [ - :accessibility_access, - :appcast, - :artifacts, - :auto_updates, - :caskroom_path, - :caveats, - :conflicts_with, - :container, - :depends_on, - :gpg, - :homepage, - :license, - :name, - :sha256, - :staged_path, - :url, - :version, - :appdir, - *ORDINARY_ARTIFACT_TYPES, - *ACTIVATABLE_ARTIFACT_TYPES, - *SPECIAL_ARTIFACT_TYPES, - *ARTIFACT_BLOCK_TYPES, - ].freeze - - attr_reader :token - def initialize(token) - @token = token - end - - def name(*args) - @name ||= [] - return @name if args.empty? - @name.concat(args.flatten) - end - - def assert_only_one_stanza_allowed(stanza, arg_given) - return unless instance_variable_defined?("@#{stanza}") && arg_given - raise Hbc::CaskInvalidError.new(token, "'#{stanza}' stanza may only appear once") - end - - def homepage(homepage = nil) - assert_only_one_stanza_allowed :homepage, !homepage.nil? - @homepage ||= homepage - end - - def url(*args, &block) - url_given = !args.empty? || block_given? - return @url unless url_given - assert_only_one_stanza_allowed :url, url_given - @url ||= begin - Hbc::URL.from(*args, &block) - rescue StandardError => e - raise Hbc::CaskInvalidError.new(token, "'url' stanza failed with: #{e}") + attr_reader :token + def initialize(token) + @token = token end - end - def appcast(*args) - return @appcast if args.empty? - assert_only_one_stanza_allowed :appcast, !args.empty? - @appcast ||= begin - Hbc::DSL::Appcast.new(*args) unless args.empty? - rescue StandardError => e - raise Hbc::CaskInvalidError.new(token, e) + def name(*args) + @name ||= [] + return @name if args.empty? + @name.concat(args.flatten) end - end - def gpg(*args) - return @gpg if args.empty? - assert_only_one_stanza_allowed :gpg, !args.empty? - @gpg ||= begin - Hbc::DSL::Gpg.new(*args) unless args.empty? - rescue StandardError => e - raise Hbc::CaskInvalidError.new(token, e) + def assert_only_one_stanza_allowed(stanza, arg_given) + return unless instance_variable_defined?("@#{stanza}") && arg_given + raise CaskInvalidError.new(token, "'#{stanza}' stanza may only appear once") end - end - def container(*args) - return @container if args.empty? - # TODO: remove this constraint, and instead merge multiple container stanzas - assert_only_one_stanza_allowed :container, !args.empty? - @container ||= begin - Hbc::DSL::Container.new(*args) unless args.empty? - rescue StandardError => e - raise Hbc::CaskInvalidError.new(token, e) + def homepage(homepage = nil) + assert_only_one_stanza_allowed :homepage, !homepage.nil? + @homepage ||= homepage end - # TODO: remove this backward-compatibility section after removing nested_container - if @container && @container.nested - artifacts[:nested_container] << @container.nested - end - @container - end - SYMBOLIC_VERSIONS = Set.new [ - :latest, - ] - - def version(arg = nil) - return @version if arg.nil? - assert_only_one_stanza_allowed :version, !arg.nil? - raise Hbc::CaskInvalidError.new(token, "invalid 'version' value: '#{arg.inspect}'") if !arg.is_a?(String) && !SYMBOLIC_VERSIONS.include?(arg) - @version ||= Hbc::DSL::Version.new(arg) - end - - SYMBOLIC_SHA256S = Set.new [ - :no_check, - ] - - def sha256(arg = nil) - return @sha256 if arg.nil? - assert_only_one_stanza_allowed :sha256, !arg.nil? - raise Hbc::CaskInvalidError.new(token, "invalid 'sha256' value: '#{arg.inspect}'") if !arg.is_a?(String) && !SYMBOLIC_SHA256S.include?(arg) - @sha256 ||= arg - end - - def license(arg = nil) - return @license if arg.nil? - assert_only_one_stanza_allowed :license, !arg.nil? - @license ||= begin - Hbc::DSL::License.new(arg) unless arg.nil? - rescue StandardError => e - raise Hbc::CaskInvalidError.new(token, e) - end - end - - # depends_on uses a load method so that multiple stanzas can be merged - def depends_on(*args) - return @depends_on if args.empty? - @depends_on ||= Hbc::DSL::DependsOn.new - begin - @depends_on.load(*args) unless args.empty? - rescue RuntimeError => e - raise Hbc::CaskInvalidError.new(token, e) - end - @depends_on - end - - def conflicts_with(*args) - return @conflicts_with if args.empty? - # TODO: remove this constraint, and instead merge multiple conflicts_with stanzas - assert_only_one_stanza_allowed :conflicts_with, !args.empty? - @conflicts_with ||= begin - Hbc::DSL::ConflictsWith.new(*args) unless args.empty? - rescue StandardError => e - raise Hbc::CaskInvalidError.new(token, e) - end - end - - def artifacts - @artifacts ||= Hash.new { |hash, key| hash[key] = Set.new } - end - - def caskroom_path - @caskroom_path ||= Hbc.caskroom.join(token) - end - - def staged_path - return @staged_path if @staged_path - cask_version = version || :unknown - @staged_path = caskroom_path.join(cask_version.to_s) - end - - def caveats(*string, &block) - @caveats ||= [] - if block_given? - @caveats << Hbc::Caveats.new(block) - elsif string.any? - @caveats << string.map { |s| s.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n") } - end - @caveats - end - - def accessibility_access(accessibility_access = nil) - assert_only_one_stanza_allowed :accessibility_access, !accessibility_access.nil? - @accessibility_access ||= accessibility_access - end - - def auto_updates(auto_updates = nil) - assert_only_one_stanza_allowed :auto_updates, !auto_updates.nil? - @auto_updates ||= auto_updates - end - - ORDINARY_ARTIFACT_TYPES.each do |type| - define_method(type) do |*args| - if type == :stage_only && args != [true] - raise Hbc::CaskInvalidError.new(token, "'stage_only' takes a single argument: true") - end - artifacts[type] << args - if artifacts.key?(:stage_only) && artifacts.keys.count > 1 && - !(artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).empty? - raise Hbc::CaskInvalidError.new(token, "'stage_only' must be the only activatable artifact") + def url(*args, &block) + url_given = !args.empty? || block_given? + return @url unless url_given + assert_only_one_stanza_allowed :url, url_given + @url ||= begin + URL.from(*args, &block) + rescue StandardError => e + raise CaskInvalidError.new(token, "'url' stanza failed with: #{e}") end end - end - def installer(*args) - return artifacts[:installer] if args.empty? - artifacts[:installer] << Hbc::DSL::Installer.new(*args) - raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only) - rescue StandardError => e - raise Hbc::CaskInvalidError.new(token, e) - end - - SPECIAL_ARTIFACT_TYPES.each do |type| - define_method(type) do |*args| - artifacts[type].merge(args) + def appcast(*args) + return @appcast if args.empty? + assert_only_one_stanza_allowed :appcast, !args.empty? + @appcast ||= begin + DSL::Appcast.new(*args) unless args.empty? + rescue StandardError => e + raise CaskInvalidError.new(token, e) + end end - end - ARTIFACT_BLOCK_TYPES.each do |type| - define_method(type) do |&block| - artifacts[type] << block + def gpg(*args) + return @gpg if args.empty? + assert_only_one_stanza_allowed :gpg, !args.empty? + @gpg ||= begin + DSL::Gpg.new(*args) unless args.empty? + rescue StandardError => e + raise CaskInvalidError.new(token, e) + end end - end - def method_missing(method, *) - if method - Hbc::Utils.method_missing_message(method, token) - nil - else - super + def container(*args) + return @container if args.empty? + # TODO: remove this constraint, and instead merge multiple container stanzas + assert_only_one_stanza_allowed :container, !args.empty? + @container ||= begin + DSL::Container.new(*args) unless args.empty? + rescue StandardError => e + raise CaskInvalidError.new(token, e) + end + # TODO: remove this backward-compatibility section after removing nested_container + if @container && @container.nested + artifacts[:nested_container] << @container.nested + end + @container end - end - def respond_to_missing?(*) - true - end + SYMBOLIC_VERSIONS = Set.new [ + :latest, + ] - def appdir - self.class.appdir - end + def version(arg = nil) + return @version if arg.nil? + assert_only_one_stanza_allowed :version, !arg.nil? + raise CaskInvalidError.new(token, "invalid 'version' value: '#{arg.inspect}'") if !arg.is_a?(String) && !SYMBOLIC_VERSIONS.include?(arg) + @version ||= DSL::Version.new(arg) + end - def self.appdir - Hbc.appdir.sub(%r{\/$}, "") + SYMBOLIC_SHA256S = Set.new [ + :no_check, + ] + + def sha256(arg = nil) + return @sha256 if arg.nil? + assert_only_one_stanza_allowed :sha256, !arg.nil? + raise CaskInvalidError.new(token, "invalid 'sha256' value: '#{arg.inspect}'") if !arg.is_a?(String) && !SYMBOLIC_SHA256S.include?(arg) + @sha256 ||= arg + end + + def license(arg = nil) + return @license if arg.nil? + assert_only_one_stanza_allowed :license, !arg.nil? + @license ||= begin + DSL::License.new(arg) unless arg.nil? + rescue StandardError => e + raise CaskInvalidError.new(token, e) + end + end + + # depends_on uses a load method so that multiple stanzas can be merged + def depends_on(*args) + return @depends_on if args.empty? + @depends_on ||= DSL::DependsOn.new + begin + @depends_on.load(*args) unless args.empty? + rescue RuntimeError => e + raise CaskInvalidError.new(token, e) + end + @depends_on + end + + def conflicts_with(*args) + return @conflicts_with if args.empty? + # TODO: remove this constraint, and instead merge multiple conflicts_with stanzas + assert_only_one_stanza_allowed :conflicts_with, !args.empty? + @conflicts_with ||= begin + DSL::ConflictsWith.new(*args) unless args.empty? + rescue StandardError => e + raise CaskInvalidError.new(token, e) + end + end + + def artifacts + @artifacts ||= Hash.new { |hash, key| hash[key] = Set.new } + end + + def caskroom_path + @caskroom_path ||= Hbc.caskroom.join(token) + end + + def staged_path + return @staged_path if @staged_path + cask_version = version || :unknown + @staged_path = caskroom_path.join(cask_version.to_s) + end + + def caveats(*string, &block) + @caveats ||= [] + if block_given? + @caveats << Hbc::Caveats.new(block) + elsif string.any? + @caveats << string.map { |s| s.to_s.sub(%r{[\r\n \t]*\Z}, "\n\n") } + end + @caveats + end + + def accessibility_access(accessibility_access = nil) + assert_only_one_stanza_allowed :accessibility_access, !accessibility_access.nil? + @accessibility_access ||= accessibility_access + end + + def auto_updates(auto_updates = nil) + assert_only_one_stanza_allowed :auto_updates, !auto_updates.nil? + @auto_updates ||= auto_updates + end + + ORDINARY_ARTIFACT_TYPES.each do |type| + define_method(type) do |*args| + if type == :stage_only && args != [true] + raise CaskInvalidError.new(token, "'stage_only' takes a single argument: true") + end + artifacts[type] << args + if artifacts.key?(:stage_only) && artifacts.keys.count > 1 && + !(artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).empty? + raise CaskInvalidError.new(token, "'stage_only' must be the only activatable artifact") + end + end + end + + def installer(*args) + return artifacts[:installer] if args.empty? + artifacts[:installer] << DSL::Installer.new(*args) + raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only) + rescue StandardError => e + raise CaskInvalidError.new(token, e) + end + + SPECIAL_ARTIFACT_TYPES.each do |type| + define_method(type) do |*args| + artifacts[type].merge(args) + end + end + + ARTIFACT_BLOCK_TYPES.each do |type| + define_method(type) do |&block| + artifacts[type] << block + end + end + + def method_missing(method, *) + if method + Utils.method_missing_message(method, token) + nil + else + super + end + end + + def respond_to_missing?(*) + true + end + + def appdir + self.class.appdir + end + + def self.appdir + Hbc.appdir.sub(%r{\/$}, "") + end end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/appcast.rb b/Library/Homebrew/cask/lib/hbc/dsl/appcast.rb index b02616cfe3..2f1245d3d9 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/appcast.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/appcast.rb @@ -1,17 +1,21 @@ -class Hbc::DSL::Appcast - attr_reader :parameters, :checkpoint +module Hbc + class DSL + class Appcast + attr_reader :parameters, :checkpoint - def initialize(uri, parameters = {}) - @parameters = parameters - @uri = Hbc::UnderscoreSupportingURI.parse(uri) - @checkpoint = @parameters[:checkpoint] - end + def initialize(uri, parameters = {}) + @parameters = parameters + @uri = UnderscoreSupportingURI.parse(uri) + @checkpoint = @parameters[:checkpoint] + end - def to_yaml - [@uri, @parameters].to_yaml - end + def to_yaml + [@uri, @parameters].to_yaml + end - def to_s - @uri.to_s + def to_s + @uri.to_s + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/base.rb b/Library/Homebrew/cask/lib/hbc/dsl/base.rb index f47f3aab78..ccf93dae9a 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/base.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/base.rb @@ -1,29 +1,33 @@ -class Hbc::DSL::Base - extend Forwardable +module Hbc + class DSL + class Base + extend Forwardable - def initialize(cask, command = Hbc::SystemCommand) - @cask = cask - @command = command - end + def initialize(cask, command = SystemCommand) + @cask = cask + @command = command + end - def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir + def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir - def system_command(executable, options = {}) - @command.run!(executable, options) - end + def system_command(executable, options = {}) + @command.run!(executable, options) + end - def method_missing(method, *) - if method - underscored_class = self.class.name.gsub(%r{([[:lower:]])([[:upper:]][[:lower:]])}, '\1_\2').downcase - section = underscored_class.downcase.split("::").last - Hbc::Utils.method_missing_message(method, @cask.to_s, section) - nil - else - super + def method_missing(method, *) + if method + underscored_class = self.class.name.gsub(%r{([[:lower:]])([[:upper:]][[:lower:]])}, '\1_\2').downcase + section = underscored_class.downcase.split("::").last + Utils.method_missing_message(method, @cask.to_s, section) + nil + else + super + end + end + + def respond_to_missing?(*) + true + end end end - - def respond_to_missing?(*) - true - end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/caveats.rb b/Library/Homebrew/cask/lib/hbc/dsl/caveats.rb index d872f49cb9..5efd7d5621 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/caveats.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/caveats.rb @@ -5,108 +5,112 @@ # ( The return value of the last method in the block is also sent # to the output by the caller, but that feature is only for the # convenience of Cask authors. ) -class Hbc::DSL::Caveats < Hbc::DSL::Base - def path_environment_variable(path) - puts <<-EOS.undent - To use #{@cask}, you may need to add the #{path} directory - to your PATH environment variable, eg (for bash shell): +module Hbc + class DSL + class Caveats < Base + def path_environment_variable(path) + puts <<-EOS.undent + To use #{@cask}, you may need to add the #{path} directory + to your PATH environment variable, eg (for bash shell): - export PATH=#{path}:"$PATH" + export PATH=#{path}:"$PATH" - EOS - end + EOS + end - def zsh_path_helper(path) - puts <<-EOS.undent - To use #{@cask}, zsh users may need to add the following line to their - ~/.zprofile. (Among other effects, #{path} will be added to the - PATH environment variable): + def zsh_path_helper(path) + puts <<-EOS.undent + To use #{@cask}, zsh users may need to add the following line to their + ~/.zprofile. (Among other effects, #{path} will be added to the + PATH environment variable): - eval `/usr/libexec/path_helper -s` + eval `/usr/libexec/path_helper -s` - EOS - end + EOS + end - def files_in_usr_local - localpath = "/usr/local" - return unless Hbc.homebrew_prefix.to_s.downcase.start_with?(localpath) - puts <<-EOS.undent - Cask #{@cask} installs files under "#{localpath}". The presence of such - files can cause warnings when running "brew doctor", which is considered - to be a bug in Homebrew-Cask. + def files_in_usr_local + localpath = "/usr/local" + return unless Hbc.homebrew_prefix.to_s.downcase.start_with?(localpath) + puts <<-EOS.undent + Cask #{@cask} installs files under "#{localpath}". The presence of such + files can cause warnings when running "brew doctor", which is considered + to be a bug in Homebrew-Cask. - EOS - end + EOS + end - def depends_on_java(java_version = "any") - if java_version == "any" - puts <<-EOS.undent - #{@cask} requires Java. You can install the latest version with + def depends_on_java(java_version = "any") + if java_version == "any" + puts <<-EOS.undent + #{@cask} requires Java. You can install the latest version with - brew cask install java + brew cask install java - EOS - elsif java_version.include?("8") || java_version.include?("+") - puts <<-EOS.undent - #{@cask} requires Java #{java_version}. You can install the latest version with + EOS + elsif java_version.include?("8") || java_version.include?("+") + puts <<-EOS.undent + #{@cask} requires Java #{java_version}. You can install the latest version with - brew cask install java + brew cask install java - EOS - else - puts <<-EOS.undent - #{@cask} requires Java #{java_version}. You can install it with + EOS + else + puts <<-EOS.undent + #{@cask} requires Java #{java_version}. You can install it with - brew cask install caskroom/versions/java#{java_version} + brew cask install caskroom/versions/java#{java_version} - EOS + EOS + end + end + + def logout + puts <<-EOS.undent + You must log out and log back in for the installation of #{@cask} + to take effect. + + EOS + end + + def reboot + puts <<-EOS.undent + You must reboot for the installation of #{@cask} to take effect. + + EOS + end + + def discontinued + puts <<-EOS.undent + #{@cask} has been officially discontinued upstream. + It may stop working correctly (or at all) in recent versions of macOS. + + EOS + end + + def free_license(web_page) + puts <<-EOS.undent + The vendor offers a free license for #{@cask} at + #{web_page} + + EOS + end + + def malware(radar_number) + puts <<-EOS.undent + #{@cask} has been reported to bundle malware. Like with any app, use at your own risk. + + A report has been made to Apple about this app. Their certificate will hopefully be revoked. + See the public report at + https://openradar.appspot.com/#{radar_number} + + If this report is accurate, please duplicate it at + https://bugreport.apple.com/ + If this report is a mistake, please let us know by opening an issue at + https://github.com/caskroom/homebrew-cask/issues/new + + EOS + end end end - - def logout - puts <<-EOS.undent - You must log out and log back in for the installation of #{@cask} - to take effect. - - EOS - end - - def reboot - puts <<-EOS.undent - You must reboot for the installation of #{@cask} to take effect. - - EOS - end - - def discontinued - puts <<-EOS.undent - #{@cask} has been officially discontinued upstream. - It may stop working correctly (or at all) in recent versions of macOS. - - EOS - end - - def free_license(web_page) - puts <<-EOS.undent - The vendor offers a free license for #{@cask} at - #{web_page} - - EOS - end - - def malware(radar_number) - puts <<-EOS.undent - #{@cask} has been reported to bundle malware. Like with any app, use at your own risk. - - A report has been made to Apple about this app. Their certificate will hopefully be revoked. - See the public report at - https://openradar.appspot.com/#{radar_number} - - If this report is accurate, please duplicate it at - https://bugreport.apple.com/ - If this report is a mistake, please let us know by opening an issue at - https://github.com/caskroom/homebrew-cask/issues/new - - EOS - end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/conflicts_with.rb b/Library/Homebrew/cask/lib/hbc/dsl/conflicts_with.rb index b2de2cd45e..e121e5373e 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/conflicts_with.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/conflicts_with.rb @@ -1,30 +1,34 @@ -class Hbc::DSL::ConflictsWith - VALID_KEYS = Set.new [ - :formula, - :cask, - :macos, - :arch, - :x11, - :java, - ] +module Hbc + class DSL + class ConflictsWith + VALID_KEYS = Set.new [ + :formula, + :cask, + :macos, + :arch, + :x11, + :java, + ] - attr_accessor(*VALID_KEYS) - attr_accessor :pairs + attr_accessor(*VALID_KEYS) + attr_accessor :pairs - def initialize(pairs = {}) - @pairs = pairs - pairs.each do |key, value| - raise "invalid conflicts_with key: '#{key.inspect}'" unless VALID_KEYS.include?(key) - writer_method = "#{key}=".to_sym - send(writer_method, value) + def initialize(pairs = {}) + @pairs = pairs + pairs.each do |key, value| + raise "invalid conflicts_with key: '#{key.inspect}'" unless VALID_KEYS.include?(key) + writer_method = "#{key}=".to_sym + send(writer_method, value) + end + end + + def to_yaml + @pairs.to_yaml + end + + def to_s + @pairs.inspect + end end end - - def to_yaml - @pairs.to_yaml - end - - def to_s - @pairs.inspect - end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/container.rb b/Library/Homebrew/cask/lib/hbc/dsl/container.rb index 39f1566686..56db31d970 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/container.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/container.rb @@ -1,26 +1,30 @@ -class Hbc::DSL::Container - VALID_KEYS = Set.new [ - :type, - :nested, - ] +module Hbc + class DSL + class Container + VALID_KEYS = Set.new [ + :type, + :nested, + ] - attr_accessor(*VALID_KEYS) - attr_accessor :pairs + attr_accessor(*VALID_KEYS) + attr_accessor :pairs - def initialize(pairs = {}) - @pairs = pairs - pairs.each do |key, value| - raise "invalid container key: '#{key.inspect}'" unless VALID_KEYS.include?(key) - writer_method = "#{key}=".to_sym - send(writer_method, value) + def initialize(pairs = {}) + @pairs = pairs + pairs.each do |key, value| + raise "invalid container key: '#{key.inspect}'" unless VALID_KEYS.include?(key) + writer_method = "#{key}=".to_sym + send(writer_method, value) + end + end + + def to_yaml + @pairs.to_yaml + end + + def to_s + @pairs.inspect + end end end - - def to_yaml - @pairs.to_yaml - end - - def to_s - @pairs.inspect - end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/depends_on.rb b/Library/Homebrew/cask/lib/hbc/dsl/depends_on.rb index a7dba36439..0e80938b78 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/depends_on.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/depends_on.rb @@ -1,124 +1,128 @@ require "rubygems" -class Hbc::DSL::DependsOn - VALID_KEYS = Set.new [ - :formula, - :cask, - :macos, - :arch, - :x11, - :java, - ].freeze +module Hbc + class DSL + class DependsOn + VALID_KEYS = Set.new [ + :formula, + :cask, + :macos, + :arch, + :x11, + :java, + ].freeze - VALID_ARCHES = { - intel: { type: :intel, bits: [32, 64] }, - ppc: { type: :ppc, bits: [32, 64] }, - # specific - i386: { type: :intel, bits: 32 }, - x86_64: { type: :intel, bits: 64 }, - ppc_7400: { type: :ppc, bits: 32 }, - ppc_64: { type: :ppc, bits: 64 }, - }.freeze + VALID_ARCHES = { + intel: { type: :intel, bits: [32, 64] }, + ppc: { type: :ppc, bits: [32, 64] }, + # specific + i386: { type: :intel, bits: 32 }, + x86_64: { type: :intel, bits: 64 }, + ppc_7400: { type: :ppc, bits: 32 }, + ppc_64: { type: :ppc, bits: 64 }, + }.freeze - # Intentionally undocumented: catch variant spellings. - ARCH_SYNONYMS = { - x86_32: :i386, - x8632: :i386, - x8664: :x86_64, - intel_32: :i386, - intel32: :i386, - intel_64: :x86_64, - intel64: :x86_64, - amd_64: :x86_64, - amd64: :x86_64, - ppc7400: :ppc_7400, - ppc_32: :ppc_7400, - ppc32: :ppc_7400, - ppc64: :ppc_64, - }.freeze + # Intentionally undocumented: catch variant spellings. + ARCH_SYNONYMS = { + x86_32: :i386, + x8632: :i386, + x8664: :x86_64, + intel_32: :i386, + intel32: :i386, + intel_64: :x86_64, + intel64: :x86_64, + amd_64: :x86_64, + amd64: :x86_64, + ppc7400: :ppc_7400, + ppc_32: :ppc_7400, + ppc32: :ppc_7400, + ppc64: :ppc_64, + }.freeze - attr_accessor :java - attr_accessor :pairs - attr_reader :arch, :cask, :formula, :macos, :x11 + attr_accessor :java + attr_accessor :pairs + attr_reader :arch, :cask, :formula, :macos, :x11 - def initialize - @pairs ||= {} - end - - def load(pairs = {}) - pairs.each do |key, value| - raise "invalid depends_on key: '#{key.inspect}'" unless VALID_KEYS.include?(key) - writer_method = "#{key}=".to_sym - @pairs[key] = send(writer_method, value) - end - end - - def self.coerce_os_release(arg) - @macos_symbols ||= MacOS::Version::SYMBOLS - @inverted_macos_symbols ||= @macos_symbols.invert - - begin - if arg.is_a?(Symbol) - Gem::Version.new(@macos_symbols.fetch(arg)) - elsif arg =~ %r{^\s*:?([a-z]\S+)\s*$}i - Gem::Version.new(@macos_symbols.fetch(Regexp.last_match[1].downcase.to_sym)) - elsif @inverted_macos_symbols.key?(arg) - Gem::Version.new(arg) - else - raise + def initialize + @pairs ||= {} + end + + def load(pairs = {}) + pairs.each do |key, value| + raise "invalid depends_on key: '#{key.inspect}'" unless VALID_KEYS.include?(key) + writer_method = "#{key}=".to_sym + @pairs[key] = send(writer_method, value) + end + end + + def self.coerce_os_release(arg) + @macos_symbols ||= MacOS::Version::SYMBOLS + @inverted_macos_symbols ||= @macos_symbols.invert + + begin + if arg.is_a?(Symbol) + Gem::Version.new(@macos_symbols.fetch(arg)) + elsif arg =~ %r{^\s*:?([a-z]\S+)\s*$}i + Gem::Version.new(@macos_symbols.fetch(Regexp.last_match[1].downcase.to_sym)) + elsif @inverted_macos_symbols.key?(arg) + Gem::Version.new(arg) + else + raise + end + rescue StandardError + raise "invalid 'depends_on macos' value: #{arg.inspect}" + end + end + + def formula=(*arg) + @formula ||= [] + @formula.concat(Array(*arg)) + end + + def cask=(*arg) + @cask ||= [] + @cask.concat(Array(*arg)) + end + + def macos=(*arg) + @macos ||= [] + macos = if arg.count == 1 && arg.first =~ %r{^\s*(<|>|[=<>]=)\s*(\S+)\s*$} + raise "'depends_on macos' comparison expressions cannot be combined" unless @macos.empty? + operator = Regexp.last_match[1].to_sym + release = self.class.coerce_os_release(Regexp.last_match[2]) + [[operator, release]] + else + raise "'depends_on macos' comparison expressions cannot be combined" if @macos.first.is_a?(Symbol) + Array(*arg).map { |elt| + self.class.coerce_os_release(elt) + }.sort + end + @macos.concat(macos) + end + + def arch=(*arg) + @arch ||= [] + arches = Array(*arg).map { |elt| + elt = elt.to_s.downcase.sub(%r{^:}, "").tr("-", "_").to_sym + ARCH_SYNONYMS.key?(elt) ? ARCH_SYNONYMS[elt] : elt + } + invalid_arches = arches - VALID_ARCHES.keys + raise "invalid 'depends_on arch' values: #{invalid_arches.inspect}" unless invalid_arches.empty? + @arch.concat(arches.map { |arch| VALID_ARCHES[arch] }) + end + + def x11=(arg) + raise "invalid 'depends_on x11' value: #{arg.inspect}" unless [true, false].include?(arg) + @x11 = arg + end + + def to_yaml + @pairs.to_yaml + end + + def to_s + @pairs.inspect end - rescue StandardError - raise "invalid 'depends_on macos' value: #{arg.inspect}" end end - - def formula=(*arg) - @formula ||= [] - @formula.concat(Array(*arg)) - end - - def cask=(*arg) - @cask ||= [] - @cask.concat(Array(*arg)) - end - - def macos=(*arg) - @macos ||= [] - macos = if arg.count == 1 && arg.first =~ %r{^\s*(<|>|[=<>]=)\s*(\S+)\s*$} - raise "'depends_on macos' comparison expressions cannot be combined" unless @macos.empty? - operator = Regexp.last_match[1].to_sym - release = self.class.coerce_os_release(Regexp.last_match[2]) - [[operator, release]] - else - raise "'depends_on macos' comparison expressions cannot be combined" if @macos.first.is_a?(Symbol) - Array(*arg).map { |elt| - self.class.coerce_os_release(elt) - }.sort - end - @macos.concat(macos) - end - - def arch=(*arg) - @arch ||= [] - arches = Array(*arg).map { |elt| - elt = elt.to_s.downcase.sub(%r{^:}, "").tr("-", "_").to_sym - ARCH_SYNONYMS.key?(elt) ? ARCH_SYNONYMS[elt] : elt - } - invalid_arches = arches - VALID_ARCHES.keys - raise "invalid 'depends_on arch' values: #{invalid_arches.inspect}" unless invalid_arches.empty? - @arch.concat(arches.map { |arch| VALID_ARCHES[arch] }) - end - - def x11=(arg) - raise "invalid 'depends_on x11' value: #{arg.inspect}" unless [true, false].include?(arg) - @x11 = arg - end - - def to_yaml - @pairs.to_yaml - end - - def to_s - @pairs.inspect - end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/gpg.rb b/Library/Homebrew/cask/lib/hbc/dsl/gpg.rb index 9496a8c05a..572ede2279 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/gpg.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/gpg.rb @@ -1,43 +1,47 @@ -class Hbc::DSL::Gpg - KEY_PARAMETERS = Set.new [ - :key_id, - :key_url, - ] +module Hbc + class DSL + class Gpg + KEY_PARAMETERS = Set.new [ + :key_id, + :key_url, + ] - VALID_PARAMETERS = Set.new [] - VALID_PARAMETERS.merge KEY_PARAMETERS + VALID_PARAMETERS = Set.new [] + VALID_PARAMETERS.merge KEY_PARAMETERS - attr_accessor(*VALID_PARAMETERS) - attr_accessor :signature + attr_accessor(*VALID_PARAMETERS) + attr_accessor :signature - def initialize(signature, parameters = {}) - @parameters = parameters - @signature = Hbc::UnderscoreSupportingURI.parse(signature) - parameters.each do |hkey, hvalue| - raise "invalid 'gpg' parameter: '#{hkey.inspect}'" unless VALID_PARAMETERS.include?(hkey) - writer_method = "#{hkey}=".to_sym - hvalue = Hbc::UnderscoreSupportingURI.parse(hvalue) if hkey == :key_url - valid_id?(hvalue) if hkey == :key_id - send(writer_method, hvalue) + def initialize(signature, parameters = {}) + @parameters = parameters + @signature = UnderscoreSupportingURI.parse(signature) + parameters.each do |hkey, hvalue| + raise "invalid 'gpg' parameter: '#{hkey.inspect}'" unless VALID_PARAMETERS.include?(hkey) + writer_method = "#{hkey}=".to_sym + hvalue = UnderscoreSupportingURI.parse(hvalue) if hkey == :key_url + valid_id?(hvalue) if hkey == :key_id + send(writer_method, hvalue) + end + return if KEY_PARAMETERS.intersection(parameters.keys).length == 1 + raise "'gpg' stanza must include exactly one of: '#{KEY_PARAMETERS.to_a}'" + end + + def valid_id?(id) + legal_lengths = Set.new [8, 16, 40] + is_valid = id.is_a?(String) && legal_lengths.include?(id.length) && id[%r{^[0-9a-f]+$}i] + raise "invalid ':key_id' value: '#{id.inspect}'" unless is_valid + + is_valid + end + + def to_yaml + # bug, :key_url value is not represented as an instance of Hbc::UnderscoreSupportingURI + [@signature, @parameters].to_yaml + end + + def to_s + @signature.to_s + end end - return if KEY_PARAMETERS.intersection(parameters.keys).length == 1 - raise "'gpg' stanza must include exactly one of: '#{KEY_PARAMETERS.to_a}'" - end - - def valid_id?(id) - legal_lengths = Set.new [8, 16, 40] - is_valid = id.is_a?(String) && legal_lengths.include?(id.length) && id[%r{^[0-9a-f]+$}i] - raise "invalid ':key_id' value: '#{id.inspect}'" unless is_valid - - is_valid - end - - def to_yaml - # bug, :key_url value is not represented as an instance of Hbc::UnderscoreSupportingURI - [@signature, @parameters].to_yaml - end - - def to_s - @signature.to_s end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/installer.rb b/Library/Homebrew/cask/lib/hbc/dsl/installer.rb index 74b4b3a91c..92561c7033 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/installer.rb @@ -1,28 +1,32 @@ -class Hbc::DSL::Installer - VALID_KEYS = Set.new [ - :manual, - :script, - ] +module Hbc + class DSL + class Installer + VALID_KEYS = Set.new [ + :manual, + :script, + ] - attr_accessor(*VALID_KEYS) + attr_accessor(*VALID_KEYS) - def initialize(*parameters) - raise Hbc::CaskInvalidError.new(token, "'installer' stanza requires an argument") if parameters.empty? - parameters = {}.merge(*parameters) - if parameters.key?(:script) && !parameters[:script].respond_to?(:key?) - if parameters.key?(:executable) - raise Hbc::CaskInvalidError.new(token, "'installer' stanza gave arguments for both :script and :executable") + def initialize(*parameters) + raise CaskInvalidError.new(token, "'installer' stanza requires an argument") if parameters.empty? + parameters = {}.merge(*parameters) + if parameters.key?(:script) && !parameters[:script].respond_to?(:key?) + if parameters.key?(:executable) + raise CaskInvalidError.new(token, "'installer' stanza gave arguments for both :script and :executable") + end + parameters[:executable] = parameters[:script] + parameters.delete(:script) + parameters = { script: parameters } + end + unless parameters.keys.length == 1 + raise "invalid 'installer' stanza: only one of #{VALID_KEYS.inspect} is permitted" + end + key = parameters.keys.first + raise "invalid 'installer' stanza key: '#{key.inspect}'" unless VALID_KEYS.include?(key) + writer_method = "#{key}=".to_sym + send(writer_method, parameters[key]) end - parameters[:executable] = parameters[:script] - parameters.delete(:script) - parameters = { script: parameters } end - unless parameters.keys.length == 1 - raise "invalid 'installer' stanza: only one of #{VALID_KEYS.inspect} is permitted" - end - key = parameters.keys.first - raise "invalid 'installer' stanza key: '#{key.inspect}'" unless VALID_KEYS.include?(key) - writer_method = "#{key}=".to_sym - send(writer_method, parameters[key]) end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/license.rb b/Library/Homebrew/cask/lib/hbc/dsl/license.rb index 5f607c2685..affbc08f51 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/license.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/license.rb @@ -1,66 +1,70 @@ -class Hbc::DSL::License - # a generic category can always be given as a license, so - # category names should be given as both key and value - VALID_LICENSES = { - # license category - unknown: :unknown, +module Hbc + class DSL + class License + # a generic category can always be given as a license, so + # category names should be given as both key and value + VALID_LICENSES = { + # license category + unknown: :unknown, - other: :other, + other: :other, - closed: :closed, - commercial: :closed, - gratis: :closed, - freemium: :closed, + closed: :closed, + commercial: :closed, + gratis: :closed, + freemium: :closed, - oss: :oss, - affero: :oss, - apache: :oss, - arphic: :oss, - artistic: :oss, - bsd: :oss, - cc: :oss, - eclipse: :oss, - gpl: :oss, - isc: :oss, - lppl: :oss, - ncsa: :oss, - mit: :oss, - mpl: :oss, - ofl: :oss, - public_domain: :oss, - ubuntu_font: :oss, - x11: :oss, - }.freeze + oss: :oss, + affero: :oss, + apache: :oss, + arphic: :oss, + artistic: :oss, + bsd: :oss, + cc: :oss, + eclipse: :oss, + gpl: :oss, + isc: :oss, + lppl: :oss, + ncsa: :oss, + mit: :oss, + mpl: :oss, + ofl: :oss, + public_domain: :oss, + ubuntu_font: :oss, + x11: :oss, + }.freeze - DEFAULT_LICENSE = :unknown - DEFAULT_CATEGORY = VALID_LICENSES[DEFAULT_LICENSE] + DEFAULT_LICENSE = :unknown + DEFAULT_CATEGORY = VALID_LICENSES[DEFAULT_LICENSE] - attr_reader :value + attr_reader :value - def self.check_constants - categories = Set.new(VALID_LICENSES.values) - categories.each do |cat| - next if VALID_LICENSES.key?(cat) - raise "license category is not a value: '#{@cat.inspect}'" + def self.check_constants + categories = Set.new(VALID_LICENSES.values) + categories.each do |cat| + next if VALID_LICENSES.key?(cat) + raise "license category is not a value: '#{@cat.inspect}'" + end + end + + def self.category(license) + VALID_LICENSES.fetch(license, DEFAULT_CATEGORY) + end + + def initialize(arg) + @value = arg + @value = DEFAULT_LICENSE if @value.nil? + return if VALID_LICENSES.key?(@value) + raise "invalid license value: '#{@value.inspect}'" + end + + def category + self.class.category(@value) + end + + def to_s + @value.inspect + end end end - - def self.category(license) - VALID_LICENSES.fetch(license, DEFAULT_CATEGORY) - end - - def initialize(arg) - @value = arg - @value = DEFAULT_LICENSE if @value.nil? - return if VALID_LICENSES.key?(@value) - raise "invalid license value: '#{@value.inspect}'" - end - - def category - self.class.category(@value) - end - - def to_s - @value.inspect - end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/postflight.rb b/Library/Homebrew/cask/lib/hbc/dsl/postflight.rb index 321c7e81a4..1026f6de60 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/postflight.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/postflight.rb @@ -1,9 +1,13 @@ require "hbc/staged" -class Hbc::DSL::Postflight < Hbc::DSL::Base - include Hbc::Staged +module Hbc + class DSL + class Postflight < Base + include Staged - def suppress_move_to_applications(options = {}) - # TODO: Remove from all casks because it is no longer needed + def suppress_move_to_applications(options = {}) + # TODO: Remove from all casks because it is no longer needed + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/preflight.rb b/Library/Homebrew/cask/lib/hbc/dsl/preflight.rb index a0d53c69ce..55a7787064 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/preflight.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/preflight.rb @@ -1,3 +1,7 @@ -class Hbc::DSL::Preflight < Hbc::DSL::Base - include Hbc::Staged +module Hbc + class DSL + class Preflight < Base + include Staged + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/stanza_proxy.rb b/Library/Homebrew/cask/lib/hbc/dsl/stanza_proxy.rb index 218178faa2..b1a850c5aa 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/stanza_proxy.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/stanza_proxy.rb @@ -1,45 +1,49 @@ -class Hbc::DSL::StanzaProxy - attr_reader :type +module Hbc + class DSL + class StanzaProxy + attr_reader :type - def self.once(type) - resolved = nil - new(type) { resolved ||= yield } - end + def self.once(type) + resolved = nil + new(type) { resolved ||= yield } + end - def initialize(type, &resolver) - @type = type - @resolver = resolver - end + def initialize(type, &resolver) + @type = type + @resolver = resolver + end - def proxy? - true - end + def proxy? + true + end - def to_s - @resolver.call.to_s - end + def to_s + @resolver.call.to_s + end - # Serialization for dumpcask - def encode_with(coder) - coder["type"] = type - coder["resolved"] = @resolver.call - end + # Serialization for dumpcask + def encode_with(coder) + coder["type"] = type + coder["resolved"] = @resolver.call + end - def method_missing(method, *args) - if method != :to_ary - @resolver.call.send(method, *args) - else - super + def method_missing(method, *args) + if method != :to_ary + @resolver.call.send(method, *args) + else + super + end + end + + def respond_to?(method, include_private = false) + return true if %i{encode_with proxy? to_s type}.include?(method) + return false if method == :to_ary + @resolver.call.respond_to?(method, include_private) + end + + def respond_to_missing?(*) + true + end end end - - def respond_to?(method, include_private = false) - return true if %i{encode_with proxy? to_s type}.include?(method) - return false if method == :to_ary - @resolver.call.respond_to?(method, include_private) - end - - def respond_to_missing?(*) - true - end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/uninstall_postflight.rb b/Library/Homebrew/cask/lib/hbc/dsl/uninstall_postflight.rb index bd8777ca72..f481cc3573 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/uninstall_postflight.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/uninstall_postflight.rb @@ -1,2 +1,6 @@ -class Hbc::DSL::UninstallPostflight < Hbc::DSL::Base +module Hbc + class DSL + class UninstallPostflight < Base + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/uninstall_preflight.rb b/Library/Homebrew/cask/lib/hbc/dsl/uninstall_preflight.rb index 994151c259..36cdec12f5 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/uninstall_preflight.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/uninstall_preflight.rb @@ -1,5 +1,9 @@ require "hbc/staged" -class Hbc::DSL::UninstallPreflight < Hbc::DSL::Base - include Hbc::Staged +module Hbc + class DSL + class UninstallPreflight < Base + include Staged + end + end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/version.rb b/Library/Homebrew/cask/lib/hbc/dsl/version.rb index e01e67ea29..d4697460af 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/version.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/version.rb @@ -1,111 +1,115 @@ -class Hbc::DSL::Version < ::String - DIVIDERS = { - "." => :dots, - "-" => :hyphens, - "_" => :underscores, - "/" => :slashes, - }.freeze +module Hbc + class DSL + class Version < ::String + DIVIDERS = { + "." => :dots, + "-" => :hyphens, + "_" => :underscores, + "/" => :slashes, + }.freeze - DIVIDER_REGEX = %r{(#{DIVIDERS.keys.map { |v| Regexp.quote(v) }.join('|')})} + DIVIDER_REGEX = %r{(#{DIVIDERS.keys.map { |v| Regexp.quote(v) }.join('|')})} - MAJOR_MINOR_PATCH_REGEX = %r{^(\d+)(?:\.(\d+)(?:\.(\d+))?)?} + MAJOR_MINOR_PATCH_REGEX = %r{^(\d+)(?:\.(\d+)(?:\.(\d+))?)?} - class << self - private + class << self + private - def define_divider_methods(divider) - define_divider_deletion_method(divider) - define_divider_conversion_methods(divider) - end + def define_divider_methods(divider) + define_divider_deletion_method(divider) + define_divider_conversion_methods(divider) + end - def define_divider_deletion_method(divider) - method_name = deletion_method_name(divider) - define_method(method_name) do - version { delete(divider) } + def define_divider_deletion_method(divider) + method_name = deletion_method_name(divider) + define_method(method_name) do + version { delete(divider) } + end + end + + def deletion_method_name(divider) + "no_#{DIVIDERS[divider]}" + end + + def define_divider_conversion_methods(left_divider) + (DIVIDERS.keys - [left_divider]).each do |right_divider| + define_divider_conversion_method(left_divider, right_divider) + end + end + + def define_divider_conversion_method(left_divider, right_divider) + method_name = conversion_method_name(left_divider, right_divider) + define_method(method_name) do + version { gsub(left_divider, right_divider) } + end + end + + def conversion_method_name(left_divider, right_divider) + "#{DIVIDERS[left_divider]}_to_#{DIVIDERS[right_divider]}" + end + end + + DIVIDERS.keys.each do |divider| + define_divider_methods(divider) + end + + attr_reader :raw_version + + def initialize(raw_version) + @raw_version = raw_version + super(raw_version.to_s) + end + + def latest? + to_s == "latest" + end + + def major + version { slice(MAJOR_MINOR_PATCH_REGEX, 1) } + end + + def minor + version { slice(MAJOR_MINOR_PATCH_REGEX, 2) } + end + + def patch + version { slice(MAJOR_MINOR_PATCH_REGEX, 3) } + end + + def major_minor + version { [major, minor].reject(&:empty?).join(".") } + end + + def major_minor_patch + version { [major, minor, patch].reject(&:empty?).join(".") } + end + + def before_comma + version { split(",", 2)[0] } + end + + def after_comma + version { split(",", 2)[1] } + end + + def before_colon + version { split(":", 2)[0] } + end + + def after_colon + version { split(":", 2)[1] } + end + + def no_dividers + version { gsub(DIVIDER_REGEX, "") } + end + + private + + def version + return self if empty? || latest? + self.class.new(yield) end end - - def deletion_method_name(divider) - "no_#{DIVIDERS[divider]}" - end - - def define_divider_conversion_methods(left_divider) - (DIVIDERS.keys - [left_divider]).each do |right_divider| - define_divider_conversion_method(left_divider, right_divider) - end - end - - def define_divider_conversion_method(left_divider, right_divider) - method_name = conversion_method_name(left_divider, right_divider) - define_method(method_name) do - version { gsub(left_divider, right_divider) } - end - end - - def conversion_method_name(left_divider, right_divider) - "#{DIVIDERS[left_divider]}_to_#{DIVIDERS[right_divider]}" - end - end - - DIVIDERS.keys.each do |divider| - define_divider_methods(divider) - end - - attr_reader :raw_version - - def initialize(raw_version) - @raw_version = raw_version - super(raw_version.to_s) - end - - def latest? - to_s == "latest" - end - - def major - version { slice(MAJOR_MINOR_PATCH_REGEX, 1) } - end - - def minor - version { slice(MAJOR_MINOR_PATCH_REGEX, 2) } - end - - def patch - version { slice(MAJOR_MINOR_PATCH_REGEX, 3) } - end - - def major_minor - version { [major, minor].reject(&:empty?).join(".") } - end - - def major_minor_patch - version { [major, minor, patch].reject(&:empty?).join(".") } - end - - def before_comma - version { split(",", 2)[0] } - end - - def after_comma - version { split(",", 2)[1] } - end - - def before_colon - version { split(":", 2)[0] } - end - - def after_colon - version { split(":", 2)[1] } - end - - def no_dividers - version { gsub(DIVIDER_REGEX, "") } - end - - private - - def version - return self if empty? || latest? - self.class.new(yield) end end diff --git a/Library/Homebrew/cask/lib/hbc/exceptions.rb b/Library/Homebrew/cask/lib/hbc/exceptions.rb index 8813aaedf8..f77106d924 100644 --- a/Library/Homebrew/cask/lib/hbc/exceptions.rb +++ b/Library/Homebrew/cask/lib/hbc/exceptions.rb @@ -1,146 +1,148 @@ -class Hbc::CaskError < RuntimeError; end +module Hbc + class CaskError < RuntimeError; end -class Hbc::AbstractCaskErrorWithToken < Hbc::CaskError - attr_reader :token + class AbstractCaskErrorWithToken < CaskError + attr_reader :token - def initialize(token) - @token = token - end -end - -class Hbc::CaskNotInstalledError < Hbc::AbstractCaskErrorWithToken - def to_s - "#{token} is not installed" - end -end - -class Hbc::CaskUnavailableError < Hbc::AbstractCaskErrorWithToken - def to_s - "No available Cask for #{token}" - end -end - -class Hbc::CaskAlreadyCreatedError < Hbc::AbstractCaskErrorWithToken - def to_s - %Q{A Cask for #{token} already exists. Run "brew cask cat #{token}" to see it.} - end -end - -class Hbc::CaskAlreadyInstalledError < Hbc::AbstractCaskErrorWithToken - def to_s - %Q{A Cask for #{token} is already installed. Add the "--force" option to force re-install.} - end -end - -class Hbc::CaskAutoUpdatesError < Hbc::AbstractCaskErrorWithToken - def to_s - %Q{A Cask for #{token} is already installed and using auto-updates. Add the "--force" option to force re-install.} - end -end - -class Hbc::CaskCommandFailedError < Hbc::CaskError - def initialize(cmd, stdout, stderr, status) - @cmd = cmd - @stdout = stdout - @stderr = stderr - @status = status - end - - def to_s - <<-EOS -Command failed to execute! - -==> Failed command: -#{@cmd} - -==> Standard Output of failed command: -#{@stdout} - -==> Standard Error of failed command: -#{@stderr} - -==> Exit status of failed command: -#{@status.inspect} - EOS - end -end - -class Hbc::CaskX11DependencyError < Hbc::AbstractCaskErrorWithToken - def to_s - <<-EOS.undent - #{token} requires XQuartz/X11, which can be installed via homebrew-cask by - - brew cask install xquartz - - or manually, by downloading the package from - - https://www.xquartz.org/ - EOS - end -end - -class Hbc::CaskCyclicCaskDependencyError < Hbc::AbstractCaskErrorWithToken - def to_s - "Cask '#{token}' includes cyclic dependencies on other Casks and could not be installed." - end -end - -class Hbc::CaskUnspecifiedError < Hbc::CaskError - def to_s - "This command requires a Cask token" - end -end - -class Hbc::CaskInvalidError < Hbc::AbstractCaskErrorWithToken - attr_reader :submsg - def initialize(token, *submsg) - super(token) - @submsg = submsg.join(" ") - end - - def to_s - "Cask '#{token}' definition is invalid" + (!submsg.empty? ? ": #{submsg}" : "") - end -end - -class Hbc::CaskTokenDoesNotMatchError < Hbc::CaskInvalidError - def initialize(token, header_token) - super(token, "Bad header line: '#{header_token}' does not match file name") - end -end - -class Hbc::CaskSha256MissingError < ArgumentError -end - -class Hbc::CaskSha256MismatchError < RuntimeError - attr_reader :path, :expected, :actual - def initialize(path, expected, actual) - @path = path - @expected = expected - @actual = actual - end - - def to_s - <<-EOS.undent - sha256 mismatch - Expected: #{expected} - Actual: #{actual} - File: #{path} - To retry an incomplete download, remove the file above. - EOS - end -end - -class Hbc::CaskNoShasumError < Hbc::CaskError - attr_reader :token - def initialize(token) - @token = token - end - - def to_s - <<-EOS.undent - Cask '#{token}' does not have a sha256 checksum defined and was not installed. - This means you have the "--require-sha" option set, perhaps in your HOMEBREW_CASK_OPTS. - EOS + def initialize(token) + @token = token + end + end + + class CaskNotInstalledError < AbstractCaskErrorWithToken + def to_s + "#{token} is not installed" + end + end + + class CaskUnavailableError < AbstractCaskErrorWithToken + def to_s + "No available Cask for #{token}" + end + end + + class CaskAlreadyCreatedError < AbstractCaskErrorWithToken + def to_s + %Q{A Cask for #{token} already exists. Run "brew cask cat #{token}" to see it.} + end + end + + class CaskAlreadyInstalledError < AbstractCaskErrorWithToken + def to_s + %Q{A Cask for #{token} is already installed. Add the "--force" option to force re-install.} + end + end + + class CaskAutoUpdatesError < AbstractCaskErrorWithToken + def to_s + %Q{A Cask for #{token} is already installed and using auto-updates. Add the "--force" option to force re-install.} + end + end + + class CaskCommandFailedError < CaskError + def initialize(cmd, stdout, stderr, status) + @cmd = cmd + @stdout = stdout + @stderr = stderr + @status = status + end + + def to_s + <<-EOS + Command failed to execute! + + ==> Failed command: + #{@cmd} + + ==> Standard Output of failed command: + #{@stdout} + + ==> Standard Error of failed command: + #{@stderr} + + ==> Exit status of failed command: + #{@status.inspect} + EOS + end + end + + class CaskX11DependencyError < AbstractCaskErrorWithToken + def to_s + <<-EOS.undent + #{token} requires XQuartz/X11, which can be installed via homebrew-cask by + + brew cask install xquartz + + or manually, by downloading the package from + + https://www.xquartz.org/ + EOS + end + end + + class CaskCyclicCaskDependencyError < AbstractCaskErrorWithToken + def to_s + "Cask '#{token}' includes cyclic dependencies on other Casks and could not be installed." + end + end + + class CaskUnspecifiedError < CaskError + def to_s + "This command requires a Cask token" + end + end + + class CaskInvalidError < AbstractCaskErrorWithToken + attr_reader :submsg + def initialize(token, *submsg) + super(token) + @submsg = submsg.join(" ") + end + + def to_s + "Cask '#{token}' definition is invalid" + (!submsg.empty? ? ": #{submsg}" : "") + end + end + + class CaskTokenDoesNotMatchError < CaskInvalidError + def initialize(token, header_token) + super(token, "Bad header line: '#{header_token}' does not match file name") + end + end + + class CaskSha256MissingError < ArgumentError + end + + class CaskSha256MismatchError < RuntimeError + attr_reader :path, :expected, :actual + def initialize(path, expected, actual) + @path = path + @expected = expected + @actual = actual + end + + def to_s + <<-EOS.undent + sha256 mismatch + Expected: #{expected} + Actual: #{actual} + File: #{path} + To retry an incomplete download, remove the file above. + EOS + end + end + + class CaskNoShasumError < CaskError + attr_reader :token + def initialize(token) + @token = token + end + + def to_s + <<-EOS.undent + Cask '#{token}' does not have a sha256 checksum defined and was not installed. + This means you have the "--require-sha" option set, perhaps in your HOMEBREW_CASK_OPTS. + EOS + end end end diff --git a/Library/Homebrew/cask/lib/hbc/extend/hash.rb b/Library/Homebrew/cask/lib/hbc/extend/hash.rb index dc28cfb29d..3a06ba3af9 100644 --- a/Library/Homebrew/cask/lib/hbc/extend/hash.rb +++ b/Library/Homebrew/cask/lib/hbc/extend/hash.rb @@ -2,6 +2,6 @@ class Hash def assert_valid_keys(*valid_keys) unknown_keys = keys - valid_keys return if unknown_keys.empty? - raise Hbc::CaskError, %Q{Unknown keys: #{unknown_keys.inspect}. Running "#{UPDATE_CMD}" will likely fix it.} + raise CaskError, %Q{Unknown keys: #{unknown_keys.inspect}. Running "#{UPDATE_CMD}" will likely fix it.} end end diff --git a/Library/Homebrew/cask/lib/hbc/fetcher.rb b/Library/Homebrew/cask/lib/hbc/fetcher.rb index 44a898ce0a..6f58ca0237 100644 --- a/Library/Homebrew/cask/lib/hbc/fetcher.rb +++ b/Library/Homebrew/cask/lib/hbc/fetcher.rb @@ -1,22 +1,24 @@ require "open3" -class Hbc::Fetcher - TIMEOUT = 10 +module Hbc + class Fetcher + TIMEOUT = 10 - def self.head(url) - if url.to_s =~ %r{googlecode} - googlecode_fake_head(url) - else - Hbc::SystemCommand.run("/usr/bin/curl", - args: ["--max-time", TIMEOUT, "--silent", "--location", "--head", url]).stdout + def self.head(url) + if url.to_s =~ %r{googlecode} + googlecode_fake_head(url) + else + SystemCommand.run("/usr/bin/curl", + args: ["--max-time", TIMEOUT, "--silent", "--location", "--head", url]).stdout + end + end + + # google code does not properly respond to HTTP HEAD requests, like a jerk + # this fakes a HEAD by doing a GET, taking the first 20 lines, then running away + def self.googlecode_fake_head(url) + command = "curl --max-time #{TIMEOUT} --verbose --location '#{url}' | head -n 20 > /dev/null" + stderr = Open3.capture3(command)[1] + stderr.split("\n").grep(%r{^< }).map { |line| line.sub(%r{^< }, "") }.join("\n") end end - - # google code does not properly respond to HTTP HEAD requests, like a jerk - # this fakes a HEAD by doing a GET, taking the first 20 lines, then running away - def self.googlecode_fake_head(url) - command = "curl --max-time #{TIMEOUT} --verbose --location '#{url}' | head -n 20 > /dev/null" - stderr = Open3.capture3(command)[1] - stderr.split("\n").grep(%r{^< }).map { |line| line.sub(%r{^< }, "") }.join("\n") - end end diff --git a/Library/Homebrew/cask/lib/hbc/installer.rb b/Library/Homebrew/cask/lib/hbc/installer.rb index ea67078f58..f0cc0d1a9d 100644 --- a/Library/Homebrew/cask/lib/hbc/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/installer.rb @@ -4,344 +4,346 @@ require "hbc/cask_dependencies" require "hbc/staged" require "hbc/verify" -class Hbc::Installer - # TODO: it is unwise for Hbc::Staged to be a module, when we are - # dealing with both staged and unstaged Casks here. This should - # either be a class which is only sometimes instantiated, or there - # should be explicit checks on whether staged state is valid in - # every method. - include Hbc::Staged - include Hbc::Verify +module Hbc + class Installer + # TODO: it is unwise for Hbc::Staged to be a module, when we are + # dealing with both staged and unstaged Casks here. This should + # either be a class which is only sometimes instantiated, or there + # should be explicit checks on whether staged state is valid in + # every method. + include Staged + include Verify - attr_reader :force, :skip_cask_deps + attr_reader :force, :skip_cask_deps - PERSISTENT_METADATA_SUBDIRS = ["gpg"].freeze + PERSISTENT_METADATA_SUBDIRS = ["gpg"].freeze - def initialize(cask, command: Hbc::SystemCommand, force: false, skip_cask_deps: false, require_sha: false) - @cask = cask - @command = command - @force = force - @skip_cask_deps = skip_cask_deps - @require_sha = require_sha - end + def initialize(cask, command: SystemCommand, force: false, skip_cask_deps: false, require_sha: false) + @cask = cask + @command = command + @force = force + @skip_cask_deps = skip_cask_deps + @require_sha = require_sha + end - def self.print_caveats(cask) - odebug "Printing caveats" - unless cask.caveats.empty? - output = capture_output do - cask.caveats.each do |caveat| - if caveat.respond_to?(:eval_and_print) - caveat.eval_and_print(cask) + def self.print_caveats(cask) + odebug "Printing caveats" + unless cask.caveats.empty? + output = capture_output do + cask.caveats.each do |caveat| + if caveat.respond_to?(:eval_and_print) + caveat.eval_and_print(cask) + else + puts caveat + end + end + end + + unless output.empty? + ohai "Caveats" + puts output + end + end + end + + def self.capture_output(&block) + old_stdout = $stdout + $stdout = Buffer.new($stdout.tty?) + block.call + output = $stdout.string + $stdout = old_stdout + output + end + + def install + odebug "Hbc::Installer.install" + + if @cask.installed? && @cask.auto_updates && !force + raise CaskAutoUpdatesError, @cask + end + + raise CaskAlreadyInstalledError, @cask if @cask.installed? && !force + + print_caveats + + begin + satisfy_dependencies + verify_has_sha if @require_sha && !@force + download + verify + extract_primary_container + install_artifacts + save_caskfile + enable_accessibility_access + rescue StandardError => e + purge_versioned_files + raise e + end + + puts summary + end + + def summary + s = if MacOS.version >= :lion && !ENV["HOMEBREW_NO_EMOJI"] + (ENV["HOMEBREW_INSTALL_BADGE"] || "\xf0\x9f\x8d\xba") + " " else - puts caveat + "#{Tty.blue}==>#{Tty.reset} #{Tty.white}Success!#{Tty.reset} " + end + s << "#{@cask} was successfully installed!" + end + + def download + odebug "Downloading" + download = Download.new(@cask, force: false) + @downloaded_path = download.perform + odebug "Downloaded to -> #{@downloaded_path}" + @downloaded_path + end + + def verify_has_sha + odebug "Checking cask has checksum" + return unless @cask.sha256 == :no_check + raise CaskNoShasumError, @cask + end + + def verify + Verify.all(@cask, @downloaded_path) + end + + def extract_primary_container + odebug "Extracting primary container" + FileUtils.mkdir_p @cask.staged_path + container = if @cask.container && @cask.container.type + Container.from_type(@cask.container.type) + else + Container.for_path(@downloaded_path, @command) + end + unless container + raise CaskError, "Uh oh, could not figure out how to unpack '#{@downloaded_path}'" + end + odebug "Using container class #{container} for #{@downloaded_path}" + container.new(@cask, @downloaded_path, @command).extract + end + + def install_artifacts + odebug "Installing artifacts" + artifacts = Artifact.for_cask(@cask) + odebug "#{artifacts.length} artifact/s defined", artifacts + artifacts.each do |artifact| + odebug "Installing artifact of class #{artifact}" + options = { command: @command, force: force } + artifact.new(@cask, options).install_phase + end + end + + # TODO: move dependencies to a separate class + # dependencies should also apply for "brew cask stage" + # override dependencies with --force or perhaps --force-deps + def satisfy_dependencies + return unless @cask.depends_on + + ohai "Satisfying dependencies" + macos_dependencies + arch_dependencies + x11_dependencies + formula_dependencies + cask_dependencies unless skip_cask_deps + puts "complete" + end + + def macos_dependencies + return unless @cask.depends_on.macos + if @cask.depends_on.macos.first.is_a?(Array) + operator, release = @cask.depends_on.macos.first + unless MacOS.version.send(operator, release) + raise CaskError, "Cask #{@cask} depends on macOS release #{operator} #{release}, but you are running release #{MacOS.version}." + end + elsif @cask.depends_on.macos.length > 1 + unless @cask.depends_on.macos.include?(Gem::Version.new(MacOS.version.to_s)) + raise CaskError, "Cask #{@cask} depends on macOS release being one of [#{@cask.depends_on.macos.map(&:to_s).join(", ")}], but you are running release #{MacOS.version}." + end + else + unless MacOS.version == @cask.depends_on.macos.first + raise CaskError, "Cask #{@cask} depends on macOS release #{@cask.depends_on.macos.first}, but you are running release #{MacOS.version}." + end + end + end + + def arch_dependencies + return if @cask.depends_on.arch.nil? + @current_arch ||= { type: Hardware::CPU.type, bits: Hardware::CPU.bits } + return if @cask.depends_on.arch.any? { |arch| + arch[:type] == @current_arch[:type] && + Array(arch[:bits]).include?(@current_arch[:bits]) + } + raise CaskError, "Cask #{@cask} depends on hardware architecture being one of [#{@cask.depends_on.arch.map(&:to_s).join(", ")}], but you are running #{@current_arch}" + end + + def x11_dependencies + return unless @cask.depends_on.x11 + raise CaskX11DependencyError, @cask.token if Hbc.x11_libpng.select(&:exist?).empty? + end + + def formula_dependencies + return unless @cask.depends_on.formula && !@cask.depends_on.formula.empty? + ohai "Installing Formula dependencies from Homebrew" + @cask.depends_on.formula.each do |dep_name| + print "#{dep_name} ... " + installed = @command.run(Hbc.homebrew_executable, + args: ["list", "--versions", dep_name], + print_stderr: false).stdout.include?(dep_name) + if installed + puts "already installed" + else + @command.run!(Hbc.homebrew_executable, + args: ["install", dep_name]) + puts "done" + end + end + end + + def cask_dependencies + return unless @cask.depends_on.cask && !@cask.depends_on.cask.empty? + ohai "Installing Cask dependencies: #{@cask.depends_on.cask.join(", ")}" + deps = CaskDependencies.new(@cask) + deps.sorted.each do |dep_token| + puts "#{dep_token} ..." + dep = Hbc.load(dep_token) + if dep.installed? + puts "already installed" + else + Installer.new(dep, force: false, skip_cask_deps: true).install + puts "done" + end + end + end + + def print_caveats + self.class.print_caveats(@cask) + end + + # TODO: logically could be in a separate class + def enable_accessibility_access + return unless @cask.accessibility_access + ohai "Enabling accessibility access" + if MacOS.version <= :mountain_lion + @command.run!("/usr/bin/touch", + args: [Hbc.pre_mavericks_accessibility_dotfile], + sudo: true) + elsif MacOS.version <= :yosemite + @command.run!("/usr/bin/sqlite3", + args: [ + Hbc.tcc_db, + "INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility','#{bundle_identifier}',0,1,1,NULL);", + ], + sudo: true) + elsif MacOS.version <= :el_capitan + @command.run!("/usr/bin/sqlite3", + args: [ + Hbc.tcc_db, + "INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility','#{bundle_identifier}',0,1,1,NULL,NULL);", + ], + sudo: true) + else + opoo <<-EOS.undent + Accessibility access cannot be enabled automatically on this version of macOS. + See System Preferences to enable it manually. + EOS + end + end + + def disable_accessibility_access + return unless @cask.accessibility_access + if MacOS.version >= :mavericks && MacOS.version <= :el_capitan + ohai "Disabling accessibility access" + @command.run!("/usr/bin/sqlite3", + args: [ + Hbc.tcc_db, + "DELETE FROM access WHERE client='#{bundle_identifier}';", + ], + sudo: true) + else + opoo <<-EOS.undent + Accessibility access cannot be disabled automatically on this version of macOS. + See System Preferences to disable it manually. + EOS + end + end + + def save_caskfile + timestamp = :now + create = true + savedir = @cask.metadata_subdir("Casks", timestamp, create) + if Dir.entries(savedir).size > 2 + # should not happen + raise CaskAlreadyInstalledError, @cask unless force + savedir.rmtree + FileUtils.mkdir_p savedir + end + FileUtils.copy(@cask.sourcefile_path, savedir) if @cask.sourcefile_path + end + + def uninstall + odebug "Hbc::Installer.uninstall" + disable_accessibility_access + uninstall_artifacts + purge_versioned_files + purge_caskroom_path if force + end + + def uninstall_artifacts + odebug "Un-installing artifacts" + artifacts = Artifact.for_cask(@cask) + odebug "#{artifacts.length} artifact/s defined", artifacts + artifacts.each do |artifact| + odebug "Un-installing artifact of class #{artifact}" + options = { command: @command, force: force } + artifact.new(@cask, options).uninstall_phase + end + end + + def zap + ohai %Q{Implied "brew cask uninstall #{@cask}"} + uninstall_artifacts + if Artifact::Zap.me?(@cask) + ohai "Dispatching zap stanza" + Artifact::Zap.new(@cask, command: @command).zap_phase + else + opoo "No zap stanza present for Cask '#{@cask}'" + end + ohai "Removing all staged versions of Cask '#{@cask}'" + purge_caskroom_path + end + + def gain_permissions_remove(path) + Utils.gain_permissions_remove(path, command: @command) + end + + def purge_versioned_files + odebug "Purging files for version #{@cask.version} of Cask #{@cask}" + + # versioned staged distribution + gain_permissions_remove(@cask.staged_path) if !@cask.staged_path.nil? && @cask.staged_path.exist? + + # Homebrew-Cask metadata + if @cask.metadata_versioned_container_path.respond_to?(:children) && + @cask.metadata_versioned_container_path.exist? + @cask.metadata_versioned_container_path.children.each do |subdir| + unless PERSISTENT_METADATA_SUBDIRS.include?(subdir.basename) + gain_permissions_remove(subdir) end end end + @cask.metadata_versioned_container_path.rmdir_if_possible + @cask.metadata_master_container_path.rmdir_if_possible - unless output.empty? - ohai "Caveats" - puts output - end - end - end - - def self.capture_output(&block) - old_stdout = $stdout - $stdout = Buffer.new($stdout.tty?) - block.call - output = $stdout.string - $stdout = old_stdout - output - end - - def install - odebug "Hbc::Installer.install" - - if @cask.installed? && @cask.auto_updates && !force - raise Hbc::CaskAutoUpdatesError, @cask + # toplevel staged distribution + @cask.caskroom_path.rmdir_if_possible end - raise Hbc::CaskAlreadyInstalledError, @cask if @cask.installed? && !force - - print_caveats - - begin - satisfy_dependencies - verify_has_sha if @require_sha && !@force - download - verify - extract_primary_container - install_artifacts - save_caskfile - enable_accessibility_access - rescue StandardError => e - purge_versioned_files - raise e + def purge_caskroom_path + odebug "Purging all staged versions of Cask #{@cask}" + gain_permissions_remove(@cask.caskroom_path) end - - puts summary - end - - def summary - s = if MacOS.version >= :lion && !ENV["HOMEBREW_NO_EMOJI"] - (ENV["HOMEBREW_INSTALL_BADGE"] || "\xf0\x9f\x8d\xba") + " " - else - "#{Tty.blue}==>#{Tty.reset} #{Tty.white}Success!#{Tty.reset} " - end - s << "#{@cask} was successfully installed!" - end - - def download - odebug "Downloading" - download = Hbc::Download.new(@cask, force: false) - @downloaded_path = download.perform - odebug "Downloaded to -> #{@downloaded_path}" - @downloaded_path - end - - def verify_has_sha - odebug "Checking cask has checksum" - return unless @cask.sha256 == :no_check - raise Hbc::CaskNoShasumError, @cask - end - - def verify - Hbc::Verify.all(@cask, @downloaded_path) - end - - def extract_primary_container - odebug "Extracting primary container" - FileUtils.mkdir_p @cask.staged_path - container = if @cask.container && @cask.container.type - Hbc::Container.from_type(@cask.container.type) - else - Hbc::Container.for_path(@downloaded_path, @command) - end - unless container - raise Hbc::CaskError, "Uh oh, could not figure out how to unpack '#{@downloaded_path}'" - end - odebug "Using container class #{container} for #{@downloaded_path}" - container.new(@cask, @downloaded_path, @command).extract - end - - def install_artifacts - odebug "Installing artifacts" - artifacts = Hbc::Artifact.for_cask(@cask) - odebug "#{artifacts.length} artifact/s defined", artifacts - artifacts.each do |artifact| - odebug "Installing artifact of class #{artifact}" - options = { command: @command, force: force } - artifact.new(@cask, options).install_phase - end - end - - # TODO: move dependencies to a separate class - # dependencies should also apply for "brew cask stage" - # override dependencies with --force or perhaps --force-deps - def satisfy_dependencies - return unless @cask.depends_on - - ohai "Satisfying dependencies" - macos_dependencies - arch_dependencies - x11_dependencies - formula_dependencies - cask_dependencies unless skip_cask_deps - puts "complete" - end - - def macos_dependencies - return unless @cask.depends_on.macos - if @cask.depends_on.macos.first.is_a?(Array) - operator, release = @cask.depends_on.macos.first - unless MacOS.version.send(operator, release) - raise Hbc::CaskError, "Cask #{@cask} depends on macOS release #{operator} #{release}, but you are running release #{MacOS.version}." - end - elsif @cask.depends_on.macos.length > 1 - unless @cask.depends_on.macos.include?(Gem::Version.new(MacOS.version.to_s)) - raise Hbc::CaskError, "Cask #{@cask} depends on macOS release being one of [#{@cask.depends_on.macos.map(&:to_s).join(", ")}], but you are running release #{MacOS.version}." - end - else - unless MacOS.version == @cask.depends_on.macos.first - raise Hbc::CaskError, "Cask #{@cask} depends on macOS release #{@cask.depends_on.macos.first}, but you are running release #{MacOS.version}." - end - end - end - - def arch_dependencies - return if @cask.depends_on.arch.nil? - @current_arch ||= { type: Hardware::CPU.type, bits: Hardware::CPU.bits } - return if @cask.depends_on.arch.any? { |arch| - arch[:type] == @current_arch[:type] && - Array(arch[:bits]).include?(@current_arch[:bits]) - } - raise Hbc::CaskError, "Cask #{@cask} depends on hardware architecture being one of [#{@cask.depends_on.arch.map(&:to_s).join(", ")}], but you are running #{@current_arch}" - end - - def x11_dependencies - return unless @cask.depends_on.x11 - raise Hbc::CaskX11DependencyError, @cask.token if Hbc.x11_libpng.select(&:exist?).empty? - end - - def formula_dependencies - return unless @cask.depends_on.formula && !@cask.depends_on.formula.empty? - ohai "Installing Formula dependencies from Homebrew" - @cask.depends_on.formula.each do |dep_name| - print "#{dep_name} ... " - installed = @command.run(Hbc.homebrew_executable, - args: ["list", "--versions", dep_name], - print_stderr: false).stdout.include?(dep_name) - if installed - puts "already installed" - else - @command.run!(Hbc.homebrew_executable, - args: ["install", dep_name]) - puts "done" - end - end - end - - def cask_dependencies - return unless @cask.depends_on.cask && !@cask.depends_on.cask.empty? - ohai "Installing Cask dependencies: #{@cask.depends_on.cask.join(", ")}" - deps = Hbc::CaskDependencies.new(@cask) - deps.sorted.each do |dep_token| - puts "#{dep_token} ..." - dep = Hbc.load(dep_token) - if dep.installed? - puts "already installed" - else - Hbc::Installer.new(dep, force: false, skip_cask_deps: true).install - puts "done" - end - end - end - - def print_caveats - self.class.print_caveats(@cask) - end - - # TODO: logically could be in a separate class - def enable_accessibility_access - return unless @cask.accessibility_access - ohai "Enabling accessibility access" - if MacOS.version <= :mountain_lion - @command.run!("/usr/bin/touch", - args: [Hbc.pre_mavericks_accessibility_dotfile], - sudo: true) - elsif MacOS.version <= :yosemite - @command.run!("/usr/bin/sqlite3", - args: [ - Hbc.tcc_db, - "INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility','#{bundle_identifier}',0,1,1,NULL);", - ], - sudo: true) - elsif MacOS.version <= :el_capitan - @command.run!("/usr/bin/sqlite3", - args: [ - Hbc.tcc_db, - "INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility','#{bundle_identifier}',0,1,1,NULL,NULL);", - ], - sudo: true) - else - opoo <<-EOS.undent - Accessibility access cannot be enabled automatically on this version of macOS. - See System Preferences to enable it manually. - EOS - end - end - - def disable_accessibility_access - return unless @cask.accessibility_access - if MacOS.version >= :mavericks && MacOS.version <= :el_capitan - ohai "Disabling accessibility access" - @command.run!("/usr/bin/sqlite3", - args: [ - Hbc.tcc_db, - "DELETE FROM access WHERE client='#{bundle_identifier}';", - ], - sudo: true) - else - opoo <<-EOS.undent - Accessibility access cannot be disabled automatically on this version of macOS. - See System Preferences to disable it manually. - EOS - end - end - - def save_caskfile - timestamp = :now - create = true - savedir = @cask.metadata_subdir("Casks", timestamp, create) - if Dir.entries(savedir).size > 2 - # should not happen - raise Hbc::CaskAlreadyInstalledError, @cask unless force - savedir.rmtree - FileUtils.mkdir_p savedir - end - FileUtils.copy(@cask.sourcefile_path, savedir) if @cask.sourcefile_path - end - - def uninstall - odebug "Hbc::Installer.uninstall" - disable_accessibility_access - uninstall_artifacts - purge_versioned_files - purge_caskroom_path if force - end - - def uninstall_artifacts - odebug "Un-installing artifacts" - artifacts = Hbc::Artifact.for_cask(@cask) - odebug "#{artifacts.length} artifact/s defined", artifacts - artifacts.each do |artifact| - odebug "Un-installing artifact of class #{artifact}" - options = { command: @command, force: force } - artifact.new(@cask, options).uninstall_phase - end - end - - def zap - ohai %Q{Implied "brew cask uninstall #{@cask}"} - uninstall_artifacts - if Hbc::Artifact::Zap.me?(@cask) - ohai "Dispatching zap stanza" - Hbc::Artifact::Zap.new(@cask, command: @command).zap_phase - else - opoo "No zap stanza present for Cask '#{@cask}'" - end - ohai "Removing all staged versions of Cask '#{@cask}'" - purge_caskroom_path - end - - def gain_permissions_remove(path) - Hbc::Utils.gain_permissions_remove(path, command: @command) - end - - def purge_versioned_files - odebug "Purging files for version #{@cask.version} of Cask #{@cask}" - - # versioned staged distribution - gain_permissions_remove(@cask.staged_path) if !@cask.staged_path.nil? && @cask.staged_path.exist? - - # Homebrew-Cask metadata - if @cask.metadata_versioned_container_path.respond_to?(:children) && - @cask.metadata_versioned_container_path.exist? - @cask.metadata_versioned_container_path.children.each do |subdir| - unless PERSISTENT_METADATA_SUBDIRS.include?(subdir.basename) - gain_permissions_remove(subdir) - end - end - end - @cask.metadata_versioned_container_path.rmdir_if_possible - @cask.metadata_master_container_path.rmdir_if_possible - - # toplevel staged distribution - @cask.caskroom_path.rmdir_if_possible - end - - def purge_caskroom_path - odebug "Purging all staged versions of Cask #{@cask}" - gain_permissions_remove(@cask.caskroom_path) end end diff --git a/Library/Homebrew/cask/lib/hbc/locations.rb b/Library/Homebrew/cask/lib/hbc/locations.rb index 5e18ba8fe6..32c95413cd 100644 --- a/Library/Homebrew/cask/lib/hbc/locations.rb +++ b/Library/Homebrew/cask/lib/hbc/locations.rb @@ -1,196 +1,198 @@ -module Hbc::Locations - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - def legacy_caskroom - @legacy_caskroom ||= Pathname.new("/opt/homebrew-cask/Caskroom") +module Hbc + module Locations + def self.included(base) + base.extend(ClassMethods) end - def default_caskroom - @default_caskroom ||= homebrew_prefix.join("Caskroom") - end + module ClassMethods + def legacy_caskroom + @legacy_caskroom ||= Pathname.new("/opt/homebrew-cask/Caskroom") + end - def caskroom - @caskroom ||= begin - if Hbc::Utils.path_occupied?(legacy_caskroom) - opoo <<-EOS.undent - The default Caskroom location has moved to #{default_caskroom}. + def default_caskroom + @default_caskroom ||= homebrew_prefix.join("Caskroom") + end - Please migrate your Casks to the new location and delete #{legacy_caskroom}, - or if you would like to keep your Caskroom at #{legacy_caskroom}, add the - following to your HOMEBREW_CASK_OPTS: + def caskroom + @caskroom ||= begin + if Utils.path_occupied?(legacy_caskroom) + opoo <<-EOS.undent + The default Caskroom location has moved to #{default_caskroom}. - --caskroom=#{legacy_caskroom} + Please migrate your Casks to the new location and delete #{legacy_caskroom}, + or if you would like to keep your Caskroom at #{legacy_caskroom}, add the + following to your HOMEBREW_CASK_OPTS: - For more details on each of those options, see https://github.com/caskroom/homebrew-cask/issues/21913. - EOS - legacy_caskroom - else - default_caskroom + --caskroom=#{legacy_caskroom} + + For more details on each of those options, see https://github.com/caskroom/homebrew-cask/issues/21913. + EOS + legacy_caskroom + else + default_caskroom + end end end - end - def caskroom=(caskroom) - @caskroom = caskroom - end - - def legacy_cache - @legacy_cache ||= homebrew_cache.join("Casks") - end - - def cache - @cache ||= homebrew_cache.join("Cask") - end - - attr_writer :appdir - - def appdir - @appdir ||= Pathname.new("/Applications").expand_path - end - - attr_writer :prefpanedir - - def prefpanedir - @prefpanedir ||= Pathname.new("~/Library/PreferencePanes").expand_path - end - - attr_writer :qlplugindir - - def qlplugindir - @qlplugindir ||= Pathname.new("~/Library/QuickLook").expand_path - end - - attr_writer :fontdir - - def fontdir - @fontdir ||= Pathname.new("~/Library/Fonts").expand_path - end - - attr_writer :colorpickerdir - - def colorpickerdir - @colorpickerdir ||= Pathname.new("~/Library/ColorPickers").expand_path - end - - attr_writer :servicedir - - def servicedir - @servicedir ||= Pathname.new("~/Library/Services").expand_path - end - - attr_writer :binarydir - - def binarydir - @binarydir ||= homebrew_prefix.join("bin") - end - - attr_writer :input_methoddir - - def input_methoddir - @input_methoddir ||= Pathname.new("~/Library/Input Methods").expand_path - end - - attr_writer :internet_plugindir - - def internet_plugindir - @internet_plugindir ||= Pathname.new("~/Library/Internet Plug-Ins").expand_path - end - - attr_writer :audio_unit_plugindir - - def audio_unit_plugindir - @audio_unit_plugindir ||= Pathname.new("~/Library/Audio/Plug-Ins/Components").expand_path - end - - attr_writer :vst_plugindir - - def vst_plugindir - @vst_plugindir ||= Pathname.new("~/Library/Audio/Plug-Ins/VST").expand_path - end - - attr_writer :vst3_plugindir - - def vst3_plugindir - @vst3_plugindir ||= Pathname.new("~/Library/Audio/Plug-Ins/VST3").expand_path - end - - attr_writer :screen_saverdir - - def screen_saverdir - @screen_saverdir ||= Pathname.new("~/Library/Screen Savers").expand_path - end - - attr_writer :default_tap - - def default_tap - @default_tap ||= Tap.fetch("caskroom", "homebrew-cask") - end - - def path(query) - query = query.sub(%r{\.rb$}i, "") - token_with_tap = if query.include?("/") - query - else - all_tokens.detect do |tap_and_token| - tap_and_token.split("/")[2] == query - end - end - - if token_with_tap - user, repo, token = token_with_tap.split("/") - Tap.fetch(user, repo).cask_dir.join("#{token}.rb") - else - default_tap.cask_dir.join("#{query}.rb") + def caskroom=(caskroom) + @caskroom = caskroom end - end - def tcc_db - @tcc_db ||= Pathname.new("/Library/Application Support/com.apple.TCC/TCC.db") - end + def legacy_cache + @legacy_cache ||= homebrew_cache.join("Casks") + end - def pre_mavericks_accessibility_dotfile - @pre_mavericks_accessibility_dotfile ||= Pathname.new("/private/var/db/.AccessibilityAPIEnabled") - end + def cache + @cache ||= homebrew_cache.join("Cask") + end - def x11_executable - @x11_executable ||= Pathname.new("/usr/X11/bin/X") - end + attr_writer :appdir - def x11_libpng - @x11_libpng ||= [Pathname.new("/opt/X11/lib/libpng.dylib"), Pathname.new("/usr/X11/lib/libpng.dylib")] - end + def appdir + @appdir ||= Pathname.new("/Applications").expand_path + end - def homebrew_cache - @homebrew_cache ||= HOMEBREW_CACHE - end + attr_writer :prefpanedir - def homebrew_cache=(path) - @homebrew_cache = path ? Pathname.new(path) : path - end + def prefpanedir + @prefpanedir ||= Pathname.new("~/Library/PreferencePanes").expand_path + end - def homebrew_executable - @homebrew_executable ||= HOMEBREW_BREW_FILE - end + attr_writer :qlplugindir - def homebrew_prefix - # where Homebrew links - @homebrew_prefix ||= HOMEBREW_PREFIX - end + def qlplugindir + @qlplugindir ||= Pathname.new("~/Library/QuickLook").expand_path + end - def homebrew_prefix=(path) - @homebrew_prefix = path ? Pathname.new(path) : path - end + attr_writer :fontdir - def homebrew_repository - # where Homebrew's .git dir is found - @homebrew_repository ||= HOMEBREW_REPOSITORY - end + def fontdir + @fontdir ||= Pathname.new("~/Library/Fonts").expand_path + end - def homebrew_repository=(path) - @homebrew_repository = path ? Pathname.new(path) : path + attr_writer :colorpickerdir + + def colorpickerdir + @colorpickerdir ||= Pathname.new("~/Library/ColorPickers").expand_path + end + + attr_writer :servicedir + + def servicedir + @servicedir ||= Pathname.new("~/Library/Services").expand_path + end + + attr_writer :binarydir + + def binarydir + @binarydir ||= homebrew_prefix.join("bin") + end + + attr_writer :input_methoddir + + def input_methoddir + @input_methoddir ||= Pathname.new("~/Library/Input Methods").expand_path + end + + attr_writer :internet_plugindir + + def internet_plugindir + @internet_plugindir ||= Pathname.new("~/Library/Internet Plug-Ins").expand_path + end + + attr_writer :audio_unit_plugindir + + def audio_unit_plugindir + @audio_unit_plugindir ||= Pathname.new("~/Library/Audio/Plug-Ins/Components").expand_path + end + + attr_writer :vst_plugindir + + def vst_plugindir + @vst_plugindir ||= Pathname.new("~/Library/Audio/Plug-Ins/VST").expand_path + end + + attr_writer :vst3_plugindir + + def vst3_plugindir + @vst3_plugindir ||= Pathname.new("~/Library/Audio/Plug-Ins/VST3").expand_path + end + + attr_writer :screen_saverdir + + def screen_saverdir + @screen_saverdir ||= Pathname.new("~/Library/Screen Savers").expand_path + end + + attr_writer :default_tap + + def default_tap + @default_tap ||= Tap.fetch("caskroom", "homebrew-cask") + end + + def path(query) + query = query.sub(%r{\.rb$}i, "") + token_with_tap = if query.include?("/") + query + else + all_tokens.detect do |tap_and_token| + tap_and_token.split("/")[2] == query + end + end + + if token_with_tap + user, repo, token = token_with_tap.split("/") + Tap.fetch(user, repo).cask_dir.join("#{token}.rb") + else + default_tap.cask_dir.join("#{query}.rb") + end + end + + def tcc_db + @tcc_db ||= Pathname.new("/Library/Application Support/com.apple.TCC/TCC.db") + end + + def pre_mavericks_accessibility_dotfile + @pre_mavericks_accessibility_dotfile ||= Pathname.new("/private/var/db/.AccessibilityAPIEnabled") + end + + def x11_executable + @x11_executable ||= Pathname.new("/usr/X11/bin/X") + end + + def x11_libpng + @x11_libpng ||= [Pathname.new("/opt/X11/lib/libpng.dylib"), Pathname.new("/usr/X11/lib/libpng.dylib")] + end + + def homebrew_cache + @homebrew_cache ||= HOMEBREW_CACHE + end + + def homebrew_cache=(path) + @homebrew_cache = path ? Pathname.new(path) : path + end + + def homebrew_executable + @homebrew_executable ||= HOMEBREW_BREW_FILE + end + + def homebrew_prefix + # where Homebrew links + @homebrew_prefix ||= HOMEBREW_PREFIX + end + + def homebrew_prefix=(path) + @homebrew_prefix = path ? Pathname.new(path) : path + end + + def homebrew_repository + # where Homebrew's .git dir is found + @homebrew_repository ||= HOMEBREW_REPOSITORY + end + + def homebrew_repository=(path) + @homebrew_repository = path ? Pathname.new(path) : path + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/macos.rb b/Library/Homebrew/cask/lib/hbc/macos.rb index 5e25c657ed..fb15be1081 100644 --- a/Library/Homebrew/cask/lib/hbc/macos.rb +++ b/Library/Homebrew/cask/lib/hbc/macos.rb @@ -2,377 +2,377 @@ require "set" require "os/mac/version" -module OS::Mac - SYSTEM_DIRS = [ - "/", - "/Applications", - "/Applications/Utilities", - "/Incompatible Software", - "/Library", - "/Library/Application Support", - "/Library/Audio", - "/Library/Caches", - "/Library/ColorPickers", - "/Library/ColorSync", - "/Library/Components", - "/Library/Compositions", - "/Library/Contextual Menu Items", - "/Library/CoreMediaIO", - "/Library/Desktop Pictures", - "/Library/Developer", - "/Library/Dictionaries", - "/Library/DirectoryServices", - "/Library/Documentation", - "/Library/Extensions", - "/Library/Filesystems", - "/Library/Fonts", - "/Library/Frameworks", - "/Library/Graphics", - "/Library/Image Capture", - "/Library/Input Methods", - "/Library/Internet Plug-Ins", - "/Library/Java", - "/Library/Keyboard Layouts", - "/Library/Keychains", - "/Library/LaunchAgents", - "/Library/LaunchDaemons", - "/Library/Logs", - "/Library/Messages", - "/Library/Modem Scripts", - "/Library/OpenDirectory", - "/Library/PDF Services", - "/Library/Perl", - "/Library/PreferencePanes", - "/Library/Preferences", - "/Library/Printers", - "/Library/PrivilegedHelperTools", - "/Library/Python", - "/Library/QuickLook", - "/Library/QuickTime", - "/Library/Receipts", - "/Library/Ruby", - "/Library/Sandbox", - "/Library/Screen Savers", - "/Library/ScriptingAdditions", - "/Library/Scripts", - "/Library/Security", - "/Library/Speech", - "/Library/Spelling", - "/Library/Spotlight", - "/Library/StartupItems", - "/Library/SystemProfiler", - "/Library/Updates", - "/Library/User Pictures", - "/Library/Video", - "/Library/WebServer", - "/Library/Widgets", - "/Library/iTunes", - "/Network", - "/System", - "/System/Library", - "/System/Library/Accessibility", - "/System/Library/Accounts", - "/System/Library/Address Book Plug-Ins", - "/System/Library/Assistant", - "/System/Library/Automator", - "/System/Library/BridgeSupport", - "/System/Library/Caches", - "/System/Library/ColorPickers", - "/System/Library/ColorSync", - "/System/Library/Colors", - "/System/Library/Components", - "/System/Library/Compositions", - "/System/Library/CoreServices", - "/System/Library/DTDs", - "/System/Library/DirectoryServices", - "/System/Library/Displays", - "/System/Library/Extensions", - "/System/Library/Filesystems", - "/System/Library/Filters", - "/System/Library/Fonts", - "/System/Library/Frameworks", - "/System/Library/Graphics", - "/System/Library/IdentityServices", - "/System/Library/Image Capture", - "/System/Library/Input Methods", - "/System/Library/InternetAccounts", - "/System/Library/Java", - "/System/Library/KerberosPlugins", - "/System/Library/Keyboard Layouts", - "/System/Library/Keychains", - "/System/Library/LaunchAgents", - "/System/Library/LaunchDaemons", - "/System/Library/LinguisticData", - "/System/Library/LocationBundles", - "/System/Library/LoginPlugins", - "/System/Library/Messages", - "/System/Library/Metadata", - "/System/Library/MonitorPanels", - "/System/Library/OpenDirectory", - "/System/Library/OpenSSL", - "/System/Library/Password Server Filters", - "/System/Library/PerformanceMetrics", - "/System/Library/Perl", - "/System/Library/PreferencePanes", - "/System/Library/Printers", - "/System/Library/PrivateFrameworks", - "/System/Library/QuickLook", - "/System/Library/QuickTime", - "/System/Library/QuickTimeJava", - "/System/Library/Recents", - "/System/Library/SDKSettingsPlist", - "/System/Library/Sandbox", - "/System/Library/Screen Savers", - "/System/Library/ScreenReader", - "/System/Library/ScriptingAdditions", - "/System/Library/ScriptingDefinitions", - "/System/Library/Security", - "/System/Library/Services", - "/System/Library/Sounds", - "/System/Library/Speech", - "/System/Library/Spelling", - "/System/Library/Spotlight", - "/System/Library/StartupItems", - "/System/Library/SyncServices", - "/System/Library/SystemConfiguration", - "/System/Library/SystemProfiler", - "/System/Library/Tcl", - "/System/Library/TextEncodings", - "/System/Library/User Template", - "/System/Library/UserEventPlugins", - "/System/Library/Video", - "/System/Library/WidgetResources", - "/User Information", - "/Users", - "/Volumes", - "/bin", - "/boot", - "/cores", - "/dev", - "/etc", - "/etc/X11", - "/etc/opt", - "/etc/sgml", - "/etc/xml", - "/home", - "/libexec", - "/lost+found", - "/media", - "/mnt", - "/net", - "/opt", - "/private", - "/private/etc", - "/private/tftpboot", - "/private/tmp", - "/private/var", - "/proc", - "/root", - "/sbin", - "/srv", - "/tmp", - "/usr", - "/usr/X11R6", - "/usr/bin", - "/usr/etc", - "/usr/include", - "/usr/lib", - "/usr/libexec", - "/usr/local", - "/usr/local/Cellar", - "/usr/local/Frameworks", - "/usr/local/Library", - "/usr/local/bin", - "/usr/local/etc", - "/usr/local/include", - "/usr/local/lib", - "/usr/local/libexec", - "/usr/local/opt", - "/usr/local/share", - "/usr/local/share/man", - "/usr/local/share/man/man1", - "/usr/local/share/man/man2", - "/usr/local/share/man/man3", - "/usr/local/share/man/man4", - "/usr/local/share/man/man5", - "/usr/local/share/man/man6", - "/usr/local/share/man/man7", - "/usr/local/share/man/man8", - "/usr/local/share/man/man9", - "/usr/local/share/man/mann", - "/usr/local/var", - "/usr/local/var/lib", - "/usr/local/var/lock", - "/usr/local/var/run", - "/usr/sbin", - "/usr/share", - "/usr/share/man", - "/usr/share/man/man1", - "/usr/share/man/man2", - "/usr/share/man/man3", - "/usr/share/man/man4", - "/usr/share/man/man5", - "/usr/share/man/man6", - "/usr/share/man/man7", - "/usr/share/man/man8", - "/usr/share/man/man9", - "/usr/share/man/mann", - "/usr/src", - "/var", - "/var/cache", - "/var/lib", - "/var/lock", - "/var/log", - "/var/mail", - "/var/run", - "/var/spool", - "/var/spool/mail", - "/var/tmp", - ] - .map(&method(:Pathname)) - .to_set - .freeze +module OS + module Mac + SYSTEM_DIRS = [ + "/", + "/Applications", + "/Applications/Utilities", + "/Incompatible Software", + "/Library", + "/Library/Application Support", + "/Library/Audio", + "/Library/Caches", + "/Library/ColorPickers", + "/Library/ColorSync", + "/Library/Components", + "/Library/Compositions", + "/Library/Contextual Menu Items", + "/Library/CoreMediaIO", + "/Library/Desktop Pictures", + "/Library/Developer", + "/Library/Dictionaries", + "/Library/DirectoryServices", + "/Library/Documentation", + "/Library/Extensions", + "/Library/Filesystems", + "/Library/Fonts", + "/Library/Frameworks", + "/Library/Graphics", + "/Library/Image Capture", + "/Library/Input Methods", + "/Library/Internet Plug-Ins", + "/Library/Java", + "/Library/Keyboard Layouts", + "/Library/Keychains", + "/Library/LaunchAgents", + "/Library/LaunchDaemons", + "/Library/Logs", + "/Library/Messages", + "/Library/Modem Scripts", + "/Library/OpenDirectory", + "/Library/PDF Services", + "/Library/Perl", + "/Library/PreferencePanes", + "/Library/Preferences", + "/Library/Printers", + "/Library/PrivilegedHelperTools", + "/Library/Python", + "/Library/QuickLook", + "/Library/QuickTime", + "/Library/Receipts", + "/Library/Ruby", + "/Library/Sandbox", + "/Library/Screen Savers", + "/Library/ScriptingAdditions", + "/Library/Scripts", + "/Library/Security", + "/Library/Speech", + "/Library/Spelling", + "/Library/Spotlight", + "/Library/StartupItems", + "/Library/SystemProfiler", + "/Library/Updates", + "/Library/User Pictures", + "/Library/Video", + "/Library/WebServer", + "/Library/Widgets", + "/Library/iTunes", + "/Network", + "/System", + "/System/Library", + "/System/Library/Accessibility", + "/System/Library/Accounts", + "/System/Library/Address Book Plug-Ins", + "/System/Library/Assistant", + "/System/Library/Automator", + "/System/Library/BridgeSupport", + "/System/Library/Caches", + "/System/Library/ColorPickers", + "/System/Library/ColorSync", + "/System/Library/Colors", + "/System/Library/Components", + "/System/Library/Compositions", + "/System/Library/CoreServices", + "/System/Library/DTDs", + "/System/Library/DirectoryServices", + "/System/Library/Displays", + "/System/Library/Extensions", + "/System/Library/Filesystems", + "/System/Library/Filters", + "/System/Library/Fonts", + "/System/Library/Frameworks", + "/System/Library/Graphics", + "/System/Library/IdentityServices", + "/System/Library/Image Capture", + "/System/Library/Input Methods", + "/System/Library/InternetAccounts", + "/System/Library/Java", + "/System/Library/KerberosPlugins", + "/System/Library/Keyboard Layouts", + "/System/Library/Keychains", + "/System/Library/LaunchAgents", + "/System/Library/LaunchDaemons", + "/System/Library/LinguisticData", + "/System/Library/LocationBundles", + "/System/Library/LoginPlugins", + "/System/Library/Messages", + "/System/Library/Metadata", + "/System/Library/MonitorPanels", + "/System/Library/OpenDirectory", + "/System/Library/OpenSSL", + "/System/Library/Password Server Filters", + "/System/Library/PerformanceMetrics", + "/System/Library/Perl", + "/System/Library/PreferencePanes", + "/System/Library/Printers", + "/System/Library/PrivateFrameworks", + "/System/Library/QuickLook", + "/System/Library/QuickTime", + "/System/Library/QuickTimeJava", + "/System/Library/Recents", + "/System/Library/SDKSettingsPlist", + "/System/Library/Sandbox", + "/System/Library/Screen Savers", + "/System/Library/ScreenReader", + "/System/Library/ScriptingAdditions", + "/System/Library/ScriptingDefinitions", + "/System/Library/Security", + "/System/Library/Services", + "/System/Library/Sounds", + "/System/Library/Speech", + "/System/Library/Spelling", + "/System/Library/Spotlight", + "/System/Library/StartupItems", + "/System/Library/SyncServices", + "/System/Library/SystemConfiguration", + "/System/Library/SystemProfiler", + "/System/Library/Tcl", + "/System/Library/TextEncodings", + "/System/Library/User Template", + "/System/Library/UserEventPlugins", + "/System/Library/Video", + "/System/Library/WidgetResources", + "/User Information", + "/Users", + "/Volumes", + "/bin", + "/boot", + "/cores", + "/dev", + "/etc", + "/etc/X11", + "/etc/opt", + "/etc/sgml", + "/etc/xml", + "/home", + "/libexec", + "/lost+found", + "/media", + "/mnt", + "/net", + "/opt", + "/private", + "/private/etc", + "/private/tftpboot", + "/private/tmp", + "/private/var", + "/proc", + "/root", + "/sbin", + "/srv", + "/tmp", + "/usr", + "/usr/X11R6", + "/usr/bin", + "/usr/etc", + "/usr/include", + "/usr/lib", + "/usr/libexec", + "/usr/local", + "/usr/local/Cellar", + "/usr/local/Frameworks", + "/usr/local/Library", + "/usr/local/bin", + "/usr/local/etc", + "/usr/local/include", + "/usr/local/lib", + "/usr/local/libexec", + "/usr/local/opt", + "/usr/local/share", + "/usr/local/share/man", + "/usr/local/share/man/man1", + "/usr/local/share/man/man2", + "/usr/local/share/man/man3", + "/usr/local/share/man/man4", + "/usr/local/share/man/man5", + "/usr/local/share/man/man6", + "/usr/local/share/man/man7", + "/usr/local/share/man/man8", + "/usr/local/share/man/man9", + "/usr/local/share/man/mann", + "/usr/local/var", + "/usr/local/var/lib", + "/usr/local/var/lock", + "/usr/local/var/run", + "/usr/sbin", + "/usr/share", + "/usr/share/man", + "/usr/share/man/man1", + "/usr/share/man/man2", + "/usr/share/man/man3", + "/usr/share/man/man4", + "/usr/share/man/man5", + "/usr/share/man/man6", + "/usr/share/man/man7", + "/usr/share/man/man8", + "/usr/share/man/man9", + "/usr/share/man/mann", + "/usr/src", + "/var", + "/var/cache", + "/var/lib", + "/var/lock", + "/var/log", + "/var/mail", + "/var/run", + "/var/spool", + "/var/spool/mail", + "/var/tmp", + ] + .map(&method(:Pathname)) + .to_set + .freeze - # TODO: There should be a way to specify a containing - # directory under which nothing can be deleted. - UNDELETABLE_DIRS = [ - "~/", - "~/Applications", - "~/Desktop", - "~/Documents", - "~/Downloads", - "~/Mail", - "~/Movies", - "~/Music", - "~/Music/iTunes", - "~/Music/iTunes/iTunes Music", - "~/Music/iTunes/Album Artwork", - "~/News", - "~/Pictures", - "~/Pictures/Desktops", - "~/Pictures/Photo Booth", - "~/Pictures/iChat Icons", - "~/Pictures/iPhoto Library", - "~/Public", - "~/Sites", - "~/Library", - "~/Library/.localized", - "~/Library/Accessibility", - "~/Library/Accounts", - "~/Library/Address Book Plug-Ins", - "~/Library/Application Scripts", - "~/Library/Application Support", - "~/Library/Application Support/Apple", - "~/Library/Application Support/com.apple.AssistiveControl", - "~/Library/Application Support/com.apple.QuickLook", - "~/Library/Application Support/com.apple.TCC", - "~/Library/Assistants", - "~/Library/Audio", - "~/Library/Automator", - "~/Library/Autosave Information", - "~/Library/Caches", - "~/Library/Calendars", - "~/Library/ColorPickers", - "~/Library/ColorSync", - "~/Library/Colors", - "~/Library/Components", - "~/Library/Compositions", - "~/Library/Containers", - "~/Library/Contextual Menu Items", - "~/Library/Cookies", - "~/Library/DTDs", - "~/Library/Desktop Pictures", - "~/Library/Developer", - "~/Library/Dictionaries", - "~/Library/DirectoryServices", - "~/Library/Displays", - "~/Library/Documentation", - "~/Library/Extensions", - "~/Library/Favorites", - "~/Library/FileSync", - "~/Library/Filesystems", - "~/Library/Filters", - "~/Library/FontCollections", - "~/Library/Fonts", - "~/Library/Frameworks", - "~/Library/GameKit", - "~/Library/Graphics", - "~/Library/Group Containers", - "~/Library/Icons", - "~/Library/IdentityServices", - "~/Library/Image Capture", - "~/Library/Images", - "~/Library/Input Methods", - "~/Library/Internet Plug-Ins", - "~/Library/InternetAccounts", - "~/Library/iTunes", - "~/Library/KeyBindings", - "~/Library/Keyboard Layouts", - "~/Library/Keychains", - "~/Library/LaunchAgents", - "~/Library/LaunchDaemons", - "~/Library/LocationBundles", - "~/Library/LoginPlugins", - "~/Library/Logs", - "~/Library/Mail", - "~/Library/Mail Downloads", - "~/Library/Messages", - "~/Library/Metadata", - "~/Library/Mobile Documents", - "~/Library/MonitorPanels", - "~/Library/OpenDirectory", - "~/Library/PDF Services", - "~/Library/PhonePlugins", - "~/Library/Phones", - "~/Library/PreferencePanes", - "~/Library/Preferences", - "~/Library/Printers", - "~/Library/PrivateFrameworks", - "~/Library/PubSub", - "~/Library/QuickLook", - "~/Library/QuickTime", - "~/Library/Receipts", - "~/Library/Recent Servers", - "~/Library/Recents", - "~/Library/Safari", - "~/Library/Saved Application State", - "~/Library/Screen Savers", - "~/Library/ScreenReader", - "~/Library/ScriptingAdditions", - "~/Library/ScriptingDefinitions", - "~/Library/Scripts", - "~/Library/Security", - "~/Library/Services", - "~/Library/Sounds", - "~/Library/Speech", - "~/Library/Spelling", - "~/Library/Spotlight", - "~/Library/StartupItems", - "~/Library/StickiesDatabase", - "~/Library/Sync Services", - "~/Library/SyncServices", - "~/Library/SyncedPreferences", - "~/Library/TextEncodings", - "~/Library/User Pictures", - "~/Library/Video", - "~/Library/Voices", - "~/Library/WebKit", - "~/Library/WidgetResources", - "~/Library/Widgets", - "~/Library/Workflows", - ] - .map { |x| Pathname(x.sub(%r{^~(?=(/|$))}, Dir.home)).expand_path } - .to_set - .union(SYSTEM_DIRS) - .freeze + # TODO: There should be a way to specify a containing + # directory under which nothing can be deleted. + UNDELETABLE_DIRS = [ + "~/", + "~/Applications", + "~/Desktop", + "~/Documents", + "~/Downloads", + "~/Mail", + "~/Movies", + "~/Music", + "~/Music/iTunes", + "~/Music/iTunes/iTunes Music", + "~/Music/iTunes/Album Artwork", + "~/News", + "~/Pictures", + "~/Pictures/Desktops", + "~/Pictures/Photo Booth", + "~/Pictures/iChat Icons", + "~/Pictures/iPhoto Library", + "~/Public", + "~/Sites", + "~/Library", + "~/Library/.localized", + "~/Library/Accessibility", + "~/Library/Accounts", + "~/Library/Address Book Plug-Ins", + "~/Library/Application Scripts", + "~/Library/Application Support", + "~/Library/Application Support/Apple", + "~/Library/Application Support/com.apple.AssistiveControl", + "~/Library/Application Support/com.apple.QuickLook", + "~/Library/Application Support/com.apple.TCC", + "~/Library/Assistants", + "~/Library/Audio", + "~/Library/Automator", + "~/Library/Autosave Information", + "~/Library/Caches", + "~/Library/Calendars", + "~/Library/ColorPickers", + "~/Library/ColorSync", + "~/Library/Colors", + "~/Library/Components", + "~/Library/Compositions", + "~/Library/Containers", + "~/Library/Contextual Menu Items", + "~/Library/Cookies", + "~/Library/DTDs", + "~/Library/Desktop Pictures", + "~/Library/Developer", + "~/Library/Dictionaries", + "~/Library/DirectoryServices", + "~/Library/Displays", + "~/Library/Documentation", + "~/Library/Extensions", + "~/Library/Favorites", + "~/Library/FileSync", + "~/Library/Filesystems", + "~/Library/Filters", + "~/Library/FontCollections", + "~/Library/Fonts", + "~/Library/Frameworks", + "~/Library/GameKit", + "~/Library/Graphics", + "~/Library/Group Containers", + "~/Library/Icons", + "~/Library/IdentityServices", + "~/Library/Image Capture", + "~/Library/Images", + "~/Library/Input Methods", + "~/Library/Internet Plug-Ins", + "~/Library/InternetAccounts", + "~/Library/iTunes", + "~/Library/KeyBindings", + "~/Library/Keyboard Layouts", + "~/Library/Keychains", + "~/Library/LaunchAgents", + "~/Library/LaunchDaemons", + "~/Library/LocationBundles", + "~/Library/LoginPlugins", + "~/Library/Logs", + "~/Library/Mail", + "~/Library/Mail Downloads", + "~/Library/Messages", + "~/Library/Metadata", + "~/Library/Mobile Documents", + "~/Library/MonitorPanels", + "~/Library/OpenDirectory", + "~/Library/PDF Services", + "~/Library/PhonePlugins", + "~/Library/Phones", + "~/Library/PreferencePanes", + "~/Library/Preferences", + "~/Library/Printers", + "~/Library/PrivateFrameworks", + "~/Library/PubSub", + "~/Library/QuickLook", + "~/Library/QuickTime", + "~/Library/Receipts", + "~/Library/Recent Servers", + "~/Library/Recents", + "~/Library/Safari", + "~/Library/Saved Application State", + "~/Library/Screen Savers", + "~/Library/ScreenReader", + "~/Library/ScriptingAdditions", + "~/Library/ScriptingDefinitions", + "~/Library/Scripts", + "~/Library/Security", + "~/Library/Services", + "~/Library/Sounds", + "~/Library/Speech", + "~/Library/Spelling", + "~/Library/Spotlight", + "~/Library/StartupItems", + "~/Library/StickiesDatabase", + "~/Library/Sync Services", + "~/Library/SyncServices", + "~/Library/SyncedPreferences", + "~/Library/TextEncodings", + "~/Library/User Pictures", + "~/Library/Video", + "~/Library/Voices", + "~/Library/WebKit", + "~/Library/WidgetResources", + "~/Library/Widgets", + "~/Library/Workflows", + ] + .map { |x| Pathname(x.sub(%r{^~(?=(/|$))}, Dir.home)).expand_path } + .to_set + .union(SYSTEM_DIRS) + .freeze - def system_dir?(dir) - SYSTEM_DIRS.include?(Pathname.new(dir).expand_path) + def system_dir?(dir) + SYSTEM_DIRS.include?(Pathname.new(dir).expand_path) + end + + def undeletable?(dir) + UNDELETABLE_DIRS.include?(Pathname.new(dir).expand_path) + end end - - def undeletable?(dir) - UNDELETABLE_DIRS.include?(Pathname.new(dir).expand_path) - end - - alias release version end diff --git a/Library/Homebrew/cask/lib/hbc/options.rb b/Library/Homebrew/cask/lib/hbc/options.rb index c0e3e2ed0d..e9ba54ff6c 100644 --- a/Library/Homebrew/cask/lib/hbc/options.rb +++ b/Library/Homebrew/cask/lib/hbc/options.rb @@ -1,37 +1,39 @@ -module Hbc::Options - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - attr_writer :no_binaries - - def no_binaries - @no_binaries ||= false +module Hbc + module Options + def self.included(base) + base.extend(ClassMethods) end - attr_writer :debug + module ClassMethods + attr_writer :no_binaries - def debug - @debug ||= false - end + def no_binaries + @no_binaries ||= false + end - attr_writer :verbose + attr_writer :debug - def verbose - @verbose ||= false - end + def debug + @debug ||= false + end - attr_writer :cleanup_outdated + attr_writer :verbose - def cleanup_outdated - @cleanup_outdated ||= false - end + def verbose + @verbose ||= false + end - attr_writer :help + attr_writer :cleanup_outdated - def help - @help ||= false + def cleanup_outdated + @cleanup_outdated ||= false + end + + attr_writer :help + + def help + @help ||= false + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/pkg.rb b/Library/Homebrew/cask/lib/hbc/pkg.rb index 6f8d28c246..7f187c9de0 100644 --- a/Library/Homebrew/cask/lib/hbc/pkg.rb +++ b/Library/Homebrew/cask/lib/hbc/pkg.rb @@ -1,113 +1,115 @@ -class Hbc::Pkg - def self.all_matching(regexp, command) - command.run("/usr/sbin/pkgutil", args: ["--pkgs=#{regexp}"]).stdout.split("\n").map { |package_id| - new(package_id.chomp, command) - } - end - - attr_reader :package_id - - def initialize(package_id, command = Hbc::SystemCommand) - @package_id = package_id - @command = command - end - - def uninstall - odebug "Deleting pkg files" - pkgutil_bom_files.each_slice(500) do |file_slice| - @command.run("/bin/rm", args: file_slice.unshift("-f", "--"), sudo: true) +module Hbc + class Pkg + def self.all_matching(regexp, command) + command.run("/usr/sbin/pkgutil", args: ["--pkgs=#{regexp}"]).stdout.split("\n").map { |package_id| + new(package_id.chomp, command) + } end - odebug "Deleting pkg symlinks and special files" - pkgutil_bom_specials.each_slice(500) do |file_slice| - @command.run("/bin/rm", args: file_slice.unshift("-f", "--"), sudo: true) + + attr_reader :package_id + + def initialize(package_id, command = SystemCommand) + @package_id = package_id + @command = command end - odebug "Deleting pkg directories" - _deepest_path_first(pkgutil_bom_dirs).each do |dir| - next unless dir.exist? && !MacOS.undeletable?(dir) - _with_full_permissions(dir) do - _clean_broken_symlinks(dir) - _clean_ds_store(dir) - _rmdir(dir) + + def uninstall + odebug "Deleting pkg files" + pkgutil_bom_files.each_slice(500) do |file_slice| + @command.run("/bin/rm", args: file_slice.unshift("-f", "--"), sudo: true) + end + odebug "Deleting pkg symlinks and special files" + pkgutil_bom_specials.each_slice(500) do |file_slice| + @command.run("/bin/rm", args: file_slice.unshift("-f", "--"), sudo: true) + end + odebug "Deleting pkg directories" + _deepest_path_first(pkgutil_bom_dirs).each do |dir| + next unless dir.exist? && !MacOS.undeletable?(dir) + _with_full_permissions(dir) do + _clean_broken_symlinks(dir) + _clean_ds_store(dir) + _rmdir(dir) + end + end + forget + end + + def forget + odebug "Unregistering pkg receipt (aka forgetting)" + @command.run!("/usr/sbin/pkgutil", args: ["--forget", package_id], sudo: true) + end + + def pkgutil_bom(*type) + @command.run!("/usr/sbin/pkgutil", args: [*type, "--files", package_id].compact) + .stdout + .split("\n") + .map { |path| root.join(path) } + end + + def pkgutil_bom_files + @pkgutil_bom_files ||= pkgutil_bom("--only-files") + end + + def pkgutil_bom_dirs + @pkgutil_bom_dirs ||= pkgutil_bom("--only-dirs") + end + + def pkgutil_bom_all + @pkgutil_bom_all ||= pkgutil_bom + end + + def pkgutil_bom_specials + pkgutil_bom_all - pkgutil_bom_files - pkgutil_bom_dirs + end + + def root + @root ||= Pathname(info.fetch("volume")).join(info.fetch("install-location")) + end + + def info + @command.run!("/usr/sbin/pkgutil", args: ["--pkg-info-plist", package_id]) + .plist + end + + def _rmdir(path) + @command.run!("/bin/rmdir", args: ["--", path], sudo: true) if path.children.empty? + end + + def _with_full_permissions(path) + original_mode = (path.stat.mode % 0o1000).to_s(8) + # TODO: similarly read and restore macOS flags (cf man chflags) + @command.run!("/bin/chmod", args: ["--", "777", path], sudo: true) + yield + ensure + if path.exist? # block may have removed dir + @command.run!("/bin/chmod", args: ["--", original_mode, path], sudo: true) end end - forget - end - def forget - odebug "Unregistering pkg receipt (aka forgetting)" - @command.run!("/usr/sbin/pkgutil", args: ["--forget", package_id], sudo: true) - end - - def pkgutil_bom(*type) - @command.run!("/usr/sbin/pkgutil", args: [*type, "--files", package_id].compact) - .stdout - .split("\n") - .map { |path| root.join(path) } - end - - def pkgutil_bom_files - @pkgutil_bom_files ||= pkgutil_bom("--only-files") - end - - def pkgutil_bom_dirs - @pkgutil_bom_dirs ||= pkgutil_bom("--only-dirs") - end - - def pkgutil_bom_all - @pkgutil_bom_all ||= pkgutil_bom - end - - def pkgutil_bom_specials - pkgutil_bom_all - pkgutil_bom_files - pkgutil_bom_dirs - end - - def root - @root ||= Pathname(info.fetch("volume")).join(info.fetch("install-location")) - end - - def info - @command.run!("/usr/sbin/pkgutil", args: ["--pkg-info-plist", package_id]) - .plist - end - - def _rmdir(path) - @command.run!("/bin/rmdir", args: ["--", path], sudo: true) if path.children.empty? - end - - def _with_full_permissions(path) - original_mode = (path.stat.mode % 0o1000).to_s(8) - # TODO: similarly read and restore macOS flags (cf man chflags) - @command.run!("/bin/chmod", args: ["--", "777", path], sudo: true) - yield - ensure - if path.exist? # block may have removed dir - @command.run!("/bin/chmod", args: ["--", original_mode, path], sudo: true) - end - end - - def _deepest_path_first(paths) - paths.sort do |path_a, path_b| - path_b.to_s.split("/").count <=> path_a.to_s.split("/").count - end - end - - # Some pkgs leave broken symlinks hanging around; we clean them out before - # attempting to rmdir to prevent extra cruft from lying around after - # uninstall - def _clean_broken_symlinks(dir) - dir.children.each do |child| - if _broken_symlink?(child) - @command.run!("/bin/rm", args: ["--", child], sudo: true) + def _deepest_path_first(paths) + paths.sort do |path_a, path_b| + path_b.to_s.split("/").count <=> path_a.to_s.split("/").count end end - end - def _clean_ds_store(dir) - ds_store = dir.join(".DS_Store") - @command.run!("/bin/rm", args: ["--", ds_store], sudo: true) if ds_store.exist? - end + # Some pkgs leave broken symlinks hanging around; we clean them out before + # attempting to rmdir to prevent extra cruft from lying around after + # uninstall + def _clean_broken_symlinks(dir) + dir.children.each do |child| + if _broken_symlink?(child) + @command.run!("/bin/rm", args: ["--", child], sudo: true) + end + end + end - def _broken_symlink?(path) - path.symlink? && !path.exist? + def _clean_ds_store(dir) + ds_store = dir.join(".DS_Store") + @command.run!("/bin/rm", args: ["--", ds_store], sudo: true) if ds_store.exist? + end + + def _broken_symlink?(path) + path.symlink? && !path.exist? + end end end diff --git a/Library/Homebrew/cask/lib/hbc/qualified_token.rb b/Library/Homebrew/cask/lib/hbc/qualified_token.rb index 635e1cb3d6..ccfe08b451 100644 --- a/Library/Homebrew/cask/lib/hbc/qualified_token.rb +++ b/Library/Homebrew/cask/lib/hbc/qualified_token.rb @@ -1,37 +1,39 @@ -module Hbc::QualifiedToken - REPO_PREFIX = "homebrew-".freeze +module Hbc + module QualifiedToken + REPO_PREFIX = "homebrew-".freeze - # per https://github.com/Homebrew/homebrew/blob/4c7bc9ec3bca729c898ee347b6135ba692ee0274/Library/Homebrew/cmd/tap.rb#L121 - USER_REGEX = %r{[a-z_\-]+} + # per https://github.com/Homebrew/homebrew/blob/4c7bc9ec3bca729c898ee347b6135ba692ee0274/Library/Homebrew/cmd/tap.rb#L121 + USER_REGEX = %r{[a-z_\-]+} - # per https://github.com/Homebrew/homebrew/blob/4c7bc9ec3bca729c898ee347b6135ba692ee0274/Library/Homebrew/cmd/tap.rb#L121 - REPO_REGEX = %r{(?:#{REPO_PREFIX})?\w+} + # per https://github.com/Homebrew/homebrew/blob/4c7bc9ec3bca729c898ee347b6135ba692ee0274/Library/Homebrew/cmd/tap.rb#L121 + REPO_REGEX = %r{(?:#{REPO_PREFIX})?\w+} - # per https://github.com/caskroom/homebrew-cask/blob/master/CONTRIBUTING.md#generating-a-token-for-the-cask - TOKEN_REGEX = %r{[a-z0-9\-]+} + # per https://github.com/caskroom/homebrew-cask/blob/master/CONTRIBUTING.md#generating-a-token-for-the-cask + TOKEN_REGEX = %r{[a-z0-9\-]+} - TAP_REGEX = %r{#{USER_REGEX}[/\-]#{REPO_REGEX}} + TAP_REGEX = %r{#{USER_REGEX}[/\-]#{REPO_REGEX}} - QUALIFIED_TOKEN_REGEX ||= %r{#{TAP_REGEX}/#{TOKEN_REGEX}} + QUALIFIED_TOKEN_REGEX ||= %r{#{TAP_REGEX}/#{TOKEN_REGEX}} - def self.parse(arg) - return nil unless arg.is_a?(String) && arg.downcase =~ %r{^#{QUALIFIED_TOKEN_REGEX}$} - path_elements = arg.downcase.split("/") - if path_elements.count == 2 - # eg phinze-cask/google-chrome. - # Not certain this form is needed, but it was supported in the past. - token = path_elements[1] - dash_elements = path_elements[0].split("-") - repo = dash_elements.pop - dash_elements.pop if dash_elements.count > 1 && dash_elements[-1] + "-" == REPO_PREFIX - user = dash_elements.join("-") - else - # eg caskroom/cask/google-chrome - # per https://github.com/Homebrew/homebrew/wiki/brew-tap - user, repo, token = path_elements + def self.parse(arg) + return nil unless arg.is_a?(String) && arg.downcase =~ %r{^#{QUALIFIED_TOKEN_REGEX}$} + path_elements = arg.downcase.split("/") + if path_elements.count == 2 + # eg phinze-cask/google-chrome. + # Not certain this form is needed, but it was supported in the past. + token = path_elements[1] + dash_elements = path_elements[0].split("-") + repo = dash_elements.pop + dash_elements.pop if dash_elements.count > 1 && dash_elements[-1] + "-" == REPO_PREFIX + user = dash_elements.join("-") + else + # eg caskroom/cask/google-chrome + # per https://github.com/Homebrew/homebrew/wiki/brew-tap + user, repo, token = path_elements + end + repo.sub!(%r{^#{REPO_PREFIX}}, "") + odebug "[user, repo, token] might be [#{user}, #{repo}, #{token}]" + [user, repo, token] end - repo.sub!(%r{^#{REPO_PREFIX}}, "") - odebug "[user, repo, token] might be [#{user}, #{repo}, #{token}]" - [user, repo, token] end end diff --git a/Library/Homebrew/cask/lib/hbc/scopes.rb b/Library/Homebrew/cask/lib/hbc/scopes.rb index 431c3ff475..8520028c94 100644 --- a/Library/Homebrew/cask/lib/hbc/scopes.rb +++ b/Library/Homebrew/cask/lib/hbc/scopes.rb @@ -1,45 +1,47 @@ -module Hbc::Scopes - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - def all - @all_casks ||= {} - all_tokens.map { |t| @all_casks[t] ||= load(t) } +module Hbc + module Scopes + def self.included(base) + base.extend(ClassMethods) end - def all_tapped_cask_dirs - Tap.map(&:cask_dir).compact - end + module ClassMethods + def all + @all_casks ||= {} + all_tokens.map { |t| @all_casks[t] ||= load(t) } + end - def all_tokens - Tap.map { |t| - t.cask_files.map { |p| - "#{t.name}/#{File.basename(p, ".rb")}" - } - }.flatten - end + def all_tapped_cask_dirs + Tap.map(&:cask_dir).compact + end - def installed - # Hbc.load has some DWIM which is slow. Optimize here - # by spoon-feeding Hbc.load fully-qualified paths. - # TODO: speed up Hbc::Source::Tapped (main perf drag is calling Hbc.all_tokens repeatedly) - # TODO: ability to specify expected source when calling Hbc.load (minor perf benefit) - Pathname.glob(caskroom.join("*")) - .map { |caskroom_path| - token = caskroom_path.basename.to_s + def all_tokens + Tap.map { |t| + t.cask_files.map { |p| + "#{t.name}/#{File.basename(p, ".rb")}" + } + }.flatten + end - path_to_cask = all_tapped_cask_dirs.find { |tap_dir| - tap_dir.join("#{token}.rb").exist? + def installed + # Hbc.load has some DWIM which is slow. Optimize here + # by spoon-feeding Hbc.load fully-qualified paths. + # TODO: speed up Hbc::Source::Tapped (main perf drag is calling Hbc.all_tokens repeatedly) + # TODO: ability to specify expected source when calling Hbc.load (minor perf benefit) + Pathname.glob(caskroom.join("*")) + .map { |caskroom_path| + token = caskroom_path.basename.to_s + + path_to_cask = all_tapped_cask_dirs.find { |tap_dir| + tap_dir.join("#{token}.rb").exist? + } + + if path_to_cask + Hbc.load(path_to_cask.join("#{token}.rb")) + else + Hbc.load(token) + end } - - if path_to_cask - Hbc.load(path_to_cask.join("#{token}.rb")) - else - Hbc.load(token) - end - } + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/source.rb b/Library/Homebrew/cask/lib/hbc/source.rb index af298108a7..b4d105894d 100644 --- a/Library/Homebrew/cask/lib/hbc/source.rb +++ b/Library/Homebrew/cask/lib/hbc/source.rb @@ -1,5 +1,3 @@ -module Hbc::Source; end - require "hbc/source/gone" require "hbc/source/path_slash_required" require "hbc/source/path_slash_optional" @@ -8,30 +6,32 @@ require "hbc/source/untapped_qualified" require "hbc/source/tapped" require "hbc/source/uri" -module Hbc::Source - def self.sources - [ - Hbc::Source::URI, - Hbc::Source::PathSlashRequired, - Hbc::Source::TappedQualified, - Hbc::Source::UntappedQualified, - Hbc::Source::Tapped, - Hbc::Source::PathSlashOptional, - Hbc::Source::Gone, - ] - end +module Hbc + module Source + def self.sources + [ + URI, + PathSlashRequired, + TappedQualified, + UntappedQualified, + Tapped, + PathSlashOptional, + Gone, + ] + end - def self.for_query(query) - odebug "Translating '#{query}' into a valid Cask source" - raise Hbc::CaskUnavailableError, query if query.to_s =~ %r{^\s*$} - source = sources.find { |s| - odebug "Testing source class #{s}" - s.me?(query) - } - raise Hbc::CaskUnavailableError, query unless source - odebug "Success! Using source class #{source}" - resolved_cask_source = source.new(query) - odebug "Resolved Cask URI or file source to '#{resolved_cask_source}'" - resolved_cask_source + def self.for_query(query) + odebug "Translating '#{query}' into a valid Cask source" + raise CaskUnavailableError, query if query.to_s =~ %r{^\s*$} + source = sources.find { |s| + odebug "Testing source class #{s}" + s.me?(query) + } + raise CaskUnavailableError, query unless source + odebug "Success! Using source class #{source}" + resolved_cask_source = source.new(query) + odebug "Resolved Cask URI or file source to '#{resolved_cask_source}'" + resolved_cask_source + end end end diff --git a/Library/Homebrew/cask/lib/hbc/source/gone.rb b/Library/Homebrew/cask/lib/hbc/source/gone.rb index 2b9f2b5f2f..1c744d0dbb 100644 --- a/Library/Homebrew/cask/lib/hbc/source/gone.rb +++ b/Library/Homebrew/cask/lib/hbc/source/gone.rb @@ -1,19 +1,23 @@ -class Hbc::Source::Gone - def self.me?(query) - Hbc::WithoutSource.new(query).installed? - end +module Hbc + module Source + class Gone + def self.me?(query) + WithoutSource.new(query).installed? + end - attr_reader :query + attr_reader :query - def initialize(query) - @query = query - end + def initialize(query) + @query = query + end - def load - Hbc::WithoutSource.new(query) - end + def load + WithoutSource.new(query) + end - def to_s - "" + def to_s + "" + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/source/path_base.rb b/Library/Homebrew/cask/lib/hbc/source/path_base.rb index bd85ab425c..5725380ec5 100644 --- a/Library/Homebrew/cask/lib/hbc/source/path_base.rb +++ b/Library/Homebrew/cask/lib/hbc/source/path_base.rb @@ -1,69 +1,73 @@ require "rubygems" -class Hbc::Source::PathBase - # derived classes must define method self.me? +module Hbc + module Source + class PathBase + # derived classes must define method self.me? - def self.path_for_query(query) - query_string = query.to_s - Pathname.new(query_string.end_with?(".rb") ? query_string : query_string + ".rb") - end + def self.path_for_query(query) + query_string = query.to_s + Pathname.new(query_string.end_with?(".rb") ? query_string : query_string + ".rb") + end - attr_reader :path + attr_reader :path - def initialize(path) - @path = Pathname(path).expand_path - end + def initialize(path) + @path = Pathname(path).expand_path + end - def load - raise Hbc::CaskError, "File '#{path}' does not exist" unless path.exist? - raise Hbc::CaskError, "File '#{path}' is not readable" unless path.readable? - raise Hbc::CaskError, "File '#{path}' is not a plain file" unless path.file? - load_cask - end + def load + raise CaskError, "File '#{path}' does not exist" unless path.exist? + raise CaskError, "File '#{path}' is not readable" unless path.readable? + raise CaskError, "File '#{path}' is not a plain file" unless path.file? + load_cask + end - def to_s - # stringify to fully-resolved location - path.to_s - end + def to_s + # stringify to fully-resolved location + path.to_s + end - private + private - def load_cask - instance_eval(cask_contents, __FILE__, __LINE__) - rescue Hbc::CaskError, StandardError, ScriptError => e - # bug: e.message.concat doesn't work with Hbc::CaskError exceptions - raise e, e.message.concat(" while loading '#{path}'") - end + def load_cask + instance_eval(cask_contents, __FILE__, __LINE__) + rescue CaskError, StandardError, ScriptError => e + # bug: e.message.concat doesn't work with CaskError exceptions + raise e, e.message.concat(" while loading '#{path}'") + end - def cask_contents - File.open(path, "rb") do |handle| - contents = handle.read - if defined?(Encoding) - contents.force_encoding("UTF-8") - else - contents + def cask_contents + File.open(path, "rb") do |handle| + contents = handle.read + if defined?(Encoding) + contents.force_encoding("UTF-8") + else + contents + end + end + end + + def cask(header_token, &block) + build_cask(Cask, header_token, &block) + end + + def test_cask(header_token, &block) + build_cask(TestCask, header_token, &block) + end + + def build_cask(cask_class, header_token, &block) + if header_token.is_a?(Hash) + # Cask file is using old `cask :v1 => 'token'` syntax + header_token = header_token.values.first + end + raise CaskTokenDoesNotMatchError.new(cask_token, header_token) unless cask_token == header_token + cask_class.new(cask_token, sourcefile_path: path, &block) + end + + def cask_token + path.basename.to_s.sub(%r{\.rb}, "") end end end - - def cask(header_token, &block) - build_cask(Hbc::Cask, header_token, &block) - end - - def test_cask(header_token, &block) - build_cask(Hbc::TestCask, header_token, &block) - end - - def build_cask(cask_class, header_token, &block) - if header_token.is_a?(Hash) - # Cask file is using old `cask :v1 => 'token'` syntax - header_token = header_token.values.first - end - raise Hbc::CaskTokenDoesNotMatchError.new(cask_token, header_token) unless cask_token == header_token - cask_class.new(cask_token, sourcefile_path: path, &block) - end - - def cask_token - path.basename.to_s.sub(%r{\.rb}, "") - end end diff --git a/Library/Homebrew/cask/lib/hbc/source/path_slash_optional.rb b/Library/Homebrew/cask/lib/hbc/source/path_slash_optional.rb index fb34c481a0..d96a41130d 100644 --- a/Library/Homebrew/cask/lib/hbc/source/path_slash_optional.rb +++ b/Library/Homebrew/cask/lib/hbc/source/path_slash_optional.rb @@ -1,8 +1,12 @@ require "hbc/source/path_base" -class Hbc::Source::PathSlashOptional < Hbc::Source::PathBase - def self.me?(query) - path = path_for_query(query) - path.exist? +module Hbc + module Source + class PathSlashOptional < PathBase + def self.me?(query) + path = path_for_query(query) + path.exist? + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/source/path_slash_required.rb b/Library/Homebrew/cask/lib/hbc/source/path_slash_required.rb index 0c533a8a50..2753b17109 100644 --- a/Library/Homebrew/cask/lib/hbc/source/path_slash_required.rb +++ b/Library/Homebrew/cask/lib/hbc/source/path_slash_required.rb @@ -1,8 +1,12 @@ require "hbc/source/path_base" -class Hbc::Source::PathSlashRequired < Hbc::Source::PathBase - def self.me?(query) - path = path_for_query(query) - path.to_s.include?("/") && path.exist? +module Hbc + module Source + class PathSlashRequired < PathBase + def self.me?(query) + path = path_for_query(query) + path.to_s.include?("/") && path.exist? + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/source/tapped.rb b/Library/Homebrew/cask/lib/hbc/source/tapped.rb index da9366840c..6716e3a351 100644 --- a/Library/Homebrew/cask/lib/hbc/source/tapped.rb +++ b/Library/Homebrew/cask/lib/hbc/source/tapped.rb @@ -1,35 +1,39 @@ -class Hbc::Source::Tapped - def self.me?(query) - path_for_query(query).exist? - end +module Hbc + module Source + class Tapped + def self.me?(query) + path_for_query(query).exist? + end - def self.path_for_query(query) - # Repeating Hbc.all_tokens is very slow for operations such as - # brew cask list, but memoizing the value might cause breakage - # elsewhere, given that installation and tap status is permitted - # to change during the course of an invocation. - token_with_tap = Hbc.all_tokens.find { |t| t.split("/").last == query.sub(%r{\.rb$}i, "") } - if token_with_tap - user, repo, token = token_with_tap.split("/") - Tap.fetch(user, repo).cask_dir.join("#{token}.rb") - else - Hbc.default_tap.cask_dir.join(query.sub(%r{(\.rb)?$}i, ".rb")) + def self.path_for_query(query) + # Repeating Hbc.all_tokens is very slow for operations such as + # brew cask list, but memoizing the value might cause breakage + # elsewhere, given that installation and tap status is permitted + # to change during the course of an invocation. + token_with_tap = Hbc.all_tokens.find { |t| t.split("/").last == query.sub(%r{\.rb$}i, "") } + if token_with_tap + user, repo, token = token_with_tap.split("/") + Tap.fetch(user, repo).cask_dir.join("#{token}.rb") + else + Hbc.default_tap.cask_dir.join(query.sub(%r{(\.rb)?$}i, ".rb")) + end + end + + attr_reader :token + + def initialize(token) + @token = token + end + + def load + path = self.class.path_for_query(token) + PathSlashOptional.new(path).load + end + + def to_s + # stringify to fully-resolved location + self.class.path_for_query(token).expand_path.to_s + end end end - - attr_reader :token - - def initialize(token) - @token = token - end - - def load - path = self.class.path_for_query(token) - Hbc::Source::PathSlashOptional.new(path).load - end - - def to_s - # stringify to fully-resolved location - self.class.path_for_query(token).expand_path.to_s - end end diff --git a/Library/Homebrew/cask/lib/hbc/source/tapped_qualified.rb b/Library/Homebrew/cask/lib/hbc/source/tapped_qualified.rb index 97a35defeb..52191f279a 100644 --- a/Library/Homebrew/cask/lib/hbc/source/tapped_qualified.rb +++ b/Library/Homebrew/cask/lib/hbc/source/tapped_qualified.rb @@ -1,22 +1,26 @@ require "hbc/source/tapped" -class Hbc::Source::TappedQualified < Hbc::Source::Tapped - def self.me?(query) - return if (tap = tap_for_query(query)).nil? +module Hbc + module Source + class TappedQualified < Tapped + def self.me?(query) + return if (tap = tap_for_query(query)).nil? - tap.installed? && path_for_query(query).exist? - end + tap.installed? && path_for_query(query).exist? + end - def self.tap_for_query(query) - qualified_token = Hbc::QualifiedToken.parse(query) - return if qualified_token.nil? + def self.tap_for_query(query) + qualified_token = QualifiedToken.parse(query) + return if qualified_token.nil? - user, repo = qualified_token[0..1] - Tap.fetch(user, repo) - end + user, repo = qualified_token[0..1] + Tap.fetch(user, repo) + end - def self.path_for_query(query) - user, repo, token = Hbc::QualifiedToken.parse(query) - Tap.fetch(user, repo).cask_dir.join(token.sub(%r{(\.rb)?$}i, ".rb")) + def self.path_for_query(query) + user, repo, token = QualifiedToken.parse(query) + Tap.fetch(user, repo).cask_dir.join(token.sub(%r{(\.rb)?$}i, ".rb")) + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/source/untapped_qualified.rb b/Library/Homebrew/cask/lib/hbc/source/untapped_qualified.rb index 7188d34cef..6041605d8c 100644 --- a/Library/Homebrew/cask/lib/hbc/source/untapped_qualified.rb +++ b/Library/Homebrew/cask/lib/hbc/source/untapped_qualified.rb @@ -1,10 +1,14 @@ require "hbc/source/tapped_qualified" -class Hbc::Source::UntappedQualified < Hbc::Source::TappedQualified - def self.me?(query) - return if (tap = tap_for_query(query)).nil? +module Hbc + module Source + class UntappedQualified < TappedQualified + def self.me?(query) + return if (tap = tap_for_query(query)).nil? - tap.install - tap.installed? && path_for_query(query).exist? + tap.install + tap.installed? && path_for_query(query).exist? + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/source/uri.rb b/Library/Homebrew/cask/lib/hbc/source/uri.rb index 99bc506880..09fab4bd0e 100644 --- a/Library/Homebrew/cask/lib/hbc/source/uri.rb +++ b/Library/Homebrew/cask/lib/hbc/source/uri.rb @@ -1,28 +1,32 @@ -class Hbc::Source::URI - def self.me?(query) - !(query.to_s =~ URI.regexp).nil? - end +module Hbc + module Source + class URI + def self.me?(query) + !(query.to_s =~ ::URI.regexp).nil? + end - attr_reader :uri + attr_reader :uri - def initialize(uri) - @uri = uri - end + def initialize(uri) + @uri = uri + end - def load - Hbc.cache.mkpath - path = Hbc.cache.join(File.basename(uri)) - ohai "Downloading #{uri}" - odebug "Download target -> #{path}" - begin - curl(uri, "-o", path.to_s) - rescue ErrorDuringExecution - raise Hbc::CaskUnavailableError, uri + def load + Hbc.cache.mkpath + path = Hbc.cache.join(File.basename(uri)) + ohai "Downloading #{uri}" + odebug "Download target -> #{path}" + begin + curl(uri, "-o", path.to_s) + rescue ErrorDuringExecution + raise CaskUnavailableError, uri + end + PathSlashOptional.new(path).load + end + + def to_s + uri.to_s + end end - Hbc::Source::PathSlashOptional.new(path).load - end - - def to_s - uri.to_s end end diff --git a/Library/Homebrew/cask/lib/hbc/staged.rb b/Library/Homebrew/cask/lib/hbc/staged.rb index 7e2c935411..dcc05ef5df 100644 --- a/Library/Homebrew/cask/lib/hbc/staged.rb +++ b/Library/Homebrew/cask/lib/hbc/staged.rb @@ -1,48 +1,50 @@ -module Hbc::Staged - def info_plist_file(index = 0) - index = 0 if index == :first - index = 1 if index == :second - index = -1 if index == :last - Hbc.appdir.join(@cask.artifacts[:app].to_a.at(index).first, "Contents", "Info.plist") - end +module Hbc + module Staged + def info_plist_file(index = 0) + index = 0 if index == :first + index = 1 if index == :second + index = -1 if index == :last + Hbc.appdir.join(@cask.artifacts[:app].to_a.at(index).first, "Contents", "Info.plist") + end - def plist_exec(cmd) - @command.run!("/usr/libexec/PlistBuddy", args: ["-c", cmd, info_plist_file]) - end + def plist_exec(cmd) + @command.run!("/usr/libexec/PlistBuddy", args: ["-c", cmd, info_plist_file]) + end - def plist_set(key, value) - plist_exec("Set #{key} #{value}") - rescue StandardError => e - raise Hbc::CaskError, "#{@cask.token}: 'plist_set' failed with: #{e}" - end + def plist_set(key, value) + plist_exec("Set #{key} #{value}") + rescue StandardError => e + raise CaskError, "#{@cask.token}: 'plist_set' failed with: #{e}" + end - def bundle_identifier - plist_exec("Print CFBundleIdentifier").stdout.chomp - rescue StandardError => e - raise Hbc::CaskError, "#{@cask.token}: 'bundle_identifier' failed with: #{e}" - end + def bundle_identifier + plist_exec("Print CFBundleIdentifier").stdout.chomp + rescue StandardError => e + raise CaskError, "#{@cask.token}: 'bundle_identifier' failed with: #{e}" + end - def set_permissions(paths, permissions_str) - full_paths = remove_nonexistent(paths) - return if full_paths.empty? - @command.run!("/bin/chmod", args: ["-R", "--", permissions_str] + full_paths, - sudo: true) - end + def set_permissions(paths, permissions_str) + full_paths = remove_nonexistent(paths) + return if full_paths.empty? + @command.run!("/bin/chmod", args: ["-R", "--", permissions_str] + full_paths, + sudo: true) + end - def set_ownership(paths, user: current_user, group: "staff") - full_paths = remove_nonexistent(paths) - return if full_paths.empty? - @command.run!("/usr/sbin/chown", args: ["-R", "--", "#{user}:#{group}"] + full_paths, - sudo: true) - end + def set_ownership(paths, user: current_user, group: "staff") + full_paths = remove_nonexistent(paths) + return if full_paths.empty? + @command.run!("/usr/sbin/chown", args: ["-R", "--", "#{user}:#{group}"] + full_paths, + sudo: true) + end - def current_user - Hbc::Utils.current_user - end + def current_user + Utils.current_user + end - private + private - def remove_nonexistent(paths) - Array(paths).map { |p| Pathname(p).expand_path }.select(&:exist?) + def remove_nonexistent(paths) + Array(paths).map { |p| Pathname(p).expand_path }.select(&:exist?) + end end end diff --git a/Library/Homebrew/cask/lib/hbc/system_command.rb b/Library/Homebrew/cask/lib/hbc/system_command.rb index 9ffe9da387..160aadff9e 100644 --- a/Library/Homebrew/cask/lib/hbc/system_command.rb +++ b/Library/Homebrew/cask/lib/hbc/system_command.rb @@ -1,173 +1,179 @@ require "open3" require "shellwords" -class Hbc::SystemCommand - attr_reader :command +module Hbc + class SystemCommand + attr_reader :command - def self.run(executable, options = {}) - new(executable, options).run! - end - - def self.run!(command, options = {}) - run(command, options.merge(must_succeed: true)) - end - - def run! - @processed_output = { stdout: "", stderr: "" } - odebug "Executing: #{expanded_command.utf8_inspect}" - - each_output_line do |type, line| - case type - when :stdout - processed_output[:stdout] << line - ohai line.chomp if options[:print_stdout] - when :stderr - processed_output[:stderr] << line - ohai line.chomp if options[:print_stderr] - end + def self.run(executable, options = {}) + new(executable, options).run! end - assert_success if options[:must_succeed] - result - end + def self.run!(command, options = {}) + run(command, options.merge(must_succeed: true)) + end - def initialize(executable, options) - @executable = executable - @options = options - process_options! - end + def run! + @processed_output = { stdout: "", stderr: "" } + odebug "Executing: #{expanded_command.utf8_inspect}" - private - - attr_reader :executable, :options, :processed_output, :processed_status - - def process_options! - options.assert_valid_keys :input, :print_stdout, :print_stderr, :args, :must_succeed, :sudo, :bsexec - sudo_prefix = %w[/usr/bin/sudo -E --] - bsexec_prefix = ["/bin/launchctl", "bsexec", options[:bsexec] == :startup ? "/" : options[:bsexec]] - @command = [executable] - options[:print_stderr] = true unless options.key?(:print_stderr) - @command.unshift(*bsexec_prefix) if options[:bsexec] - @command.unshift(*sudo_prefix) if options[:sudo] - @command.concat(options[:args]) if options.key?(:args) && !options[:args].empty? - @command[0] = Shellwords.shellescape(@command[0]) if @command.size == 1 - nil - end - - def assert_success - return if processed_status && processed_status.success? - raise Hbc::CaskCommandFailedError.new(command.utf8_inspect, processed_output[:stdout], processed_output[:stderr], processed_status) - end - - def expanded_command - @expanded_command ||= command.map { |arg| - if arg.respond_to?(:to_path) - File.absolute_path(arg) - else - String(arg) - end - } - end - - def each_output_line(&b) - raw_stdin, raw_stdout, raw_stderr, raw_wait_thr = - Open3.popen3(*expanded_command) - - write_input_to(raw_stdin) if options[:input] - raw_stdin.close_write - each_line_from [raw_stdout, raw_stderr], &b - - @processed_status = raw_wait_thr.value - end - - def write_input_to(raw_stdin) - Array(options[:input]).each { |line| raw_stdin.puts line } - end - - def each_line_from(sources) - loop do - readable_sources = IO.select(sources)[0] - readable_sources.delete_if(&:eof?).first(1).each do |source| - type = (source == sources[0] ? :stdout : :stderr) - begin - yield(type, source.readline_nonblock || "") - rescue IO::WaitReadable, EOFError - next + each_output_line do |type, line| + case type + when :stdout + processed_output[:stdout] << line + ohai line.chomp if options[:print_stdout] + when :stderr + processed_output[:stderr] << line + ohai line.chomp if options[:print_stderr] end end - break if readable_sources.empty? - end - sources.each(&:close_read) - end - def result - Hbc::SystemCommand::Result.new(command, - processed_output[:stdout], - processed_output[:stderr], - processed_status.exitstatus) + assert_success if options[:must_succeed] + result + end + + def initialize(executable, options) + @executable = executable + @options = options + process_options! + end + + private + + attr_reader :executable, :options, :processed_output, :processed_status + + def process_options! + options.assert_valid_keys :input, :print_stdout, :print_stderr, :args, :must_succeed, :sudo, :bsexec + sudo_prefix = %w[/usr/bin/sudo -E --] + bsexec_prefix = ["/bin/launchctl", "bsexec", options[:bsexec] == :startup ? "/" : options[:bsexec]] + @command = [executable] + options[:print_stderr] = true unless options.key?(:print_stderr) + @command.unshift(*bsexec_prefix) if options[:bsexec] + @command.unshift(*sudo_prefix) if options[:sudo] + @command.concat(options[:args]) if options.key?(:args) && !options[:args].empty? + @command[0] = Shellwords.shellescape(@command[0]) if @command.size == 1 + nil + end + + def assert_success + return if processed_status && processed_status.success? + raise CaskCommandFailedError.new(command.utf8_inspect, processed_output[:stdout], processed_output[:stderr], processed_status) + end + + def expanded_command + @expanded_command ||= command.map { |arg| + if arg.respond_to?(:to_path) + File.absolute_path(arg) + else + String(arg) + end + } + end + + def each_output_line(&b) + raw_stdin, raw_stdout, raw_stderr, raw_wait_thr = + Open3.popen3(*expanded_command) + + write_input_to(raw_stdin) if options[:input] + raw_stdin.close_write + each_line_from [raw_stdout, raw_stderr], &b + + @processed_status = raw_wait_thr.value + end + + def write_input_to(raw_stdin) + Array(options[:input]).each { |line| raw_stdin.puts line } + end + + def each_line_from(sources) + loop do + readable_sources = IO.select(sources)[0] + readable_sources.delete_if(&:eof?).first(1).each do |source| + type = (source == sources[0] ? :stdout : :stderr) + begin + yield(type, source.readline_nonblock || "") + rescue IO::WaitReadable, EOFError + next + end + end + break if readable_sources.empty? + end + sources.each(&:close_read) + end + + def result + Result.new(command, + processed_output[:stdout], + processed_output[:stderr], + processed_status.exitstatus) + end end end -class Hbc::SystemCommand::Result - attr_accessor :command, :stdout, :stderr, :exit_status +module Hbc + class SystemCommand + class Result + attr_accessor :command, :stdout, :stderr, :exit_status - def initialize(command, stdout, stderr, exit_status) - @command = command - @stdout = stdout - @stderr = stderr - @exit_status = exit_status - end + def initialize(command, stdout, stderr, exit_status) + @command = command + @stdout = stdout + @stderr = stderr + @exit_status = exit_status + end - def plist - @plist ||= self.class._parse_plist(@command, @stdout.dup) - end + def plist + @plist ||= self.class._parse_plist(@command, @stdout.dup) + end - def success? - @exit_status.zero? - end + def success? + @exit_status.zero? + end - def merged_output - @merged_output ||= @stdout + @stderr - end + def merged_output + @merged_output ||= @stdout + @stderr + end - def to_s - @stdout - end + def to_s + @stdout + end - def self._warn_plist_garbage(command, garbage) - return true unless garbage =~ %r{\S} - external = File.basename(command.first) - lines = garbage.strip.split("\n") - opoo "Non-XML stdout from #{external}:" - $stderr.puts lines.map { |l| " #{l}" } - end + def self._warn_plist_garbage(command, garbage) + return true unless garbage =~ %r{\S} + external = File.basename(command.first) + lines = garbage.strip.split("\n") + opoo "Non-XML stdout from #{external}:" + $stderr.puts lines.map { |l| " #{l}" } + end - def self._parse_plist(command, output) - raise Hbc::CaskError, "Empty plist input" unless output =~ %r{\S} - output.sub!(%r{\A(.*?)(<\?\s*xml)}m, '\2') - _warn_plist_garbage(command, Regexp.last_match[1]) if Hbc.debug - output.sub!(%r{(<\s*/\s*plist\s*>)(.*?)\Z}m, '\1') - _warn_plist_garbage(command, Regexp.last_match[2]) - xml = Plist.parse_xml(output) - unless xml.respond_to?(:keys) && !xml.keys.empty? - raise Hbc::CaskError, <<-EOS -Empty result parsing plist output from command. - command was: - #{command.utf8_inspect} - output we attempted to parse: - #{output} - EOS + def self._parse_plist(command, output) + raise CaskError, "Empty plist input" unless output =~ %r{\S} + output.sub!(%r{\A(.*?)(<\?\s*xml)}m, '\2') + _warn_plist_garbage(command, Regexp.last_match[1]) if Hbc.debug + output.sub!(%r{(<\s*/\s*plist\s*>)(.*?)\Z}m, '\1') + _warn_plist_garbage(command, Regexp.last_match[2]) + xml = Plist.parse_xml(output) + unless xml.respond_to?(:keys) && !xml.keys.empty? + raise CaskError, <<-EOS + Empty result parsing plist output from command. + command was: + #{command.utf8_inspect} + output we attempted to parse: + #{output} + EOS + end + xml + rescue Plist::ParseError => e + raise CaskError, <<-EOS + Error parsing plist output from command. + command was: + #{command.utf8_inspect} + error was: + #{e} + output we attempted to parse: + #{output} + EOS + end end - xml - rescue Plist::ParseError => e - raise Hbc::CaskError, <<-EOS -Error parsing plist output from command. - command was: - #{command.utf8_inspect} - error was: - #{e} - output we attempted to parse: - #{output} - EOS end end diff --git a/Library/Homebrew/cask/lib/hbc/topological_hash.rb b/Library/Homebrew/cask/lib/hbc/topological_hash.rb index bbad1bb4d9..2e484a28d3 100644 --- a/Library/Homebrew/cask/lib/hbc/topological_hash.rb +++ b/Library/Homebrew/cask/lib/hbc/topological_hash.rb @@ -1,12 +1,14 @@ require "tsort" # a basic topologically sortable hashmap -class Hbc::TopologicalHash < Hash - include TSort +module Hbc + class TopologicalHash < Hash + include TSort - alias tsort_each_node each_key + alias tsort_each_node each_key - def tsort_each_child(node, &block) - fetch(node).each(&block) + def tsort_each_child(node, &block) + fetch(node).each(&block) + end end end diff --git a/Library/Homebrew/cask/lib/hbc/underscore_supporting_uri.rb b/Library/Homebrew/cask/lib/hbc/underscore_supporting_uri.rb index 34bfea387b..8f8f66f43e 100644 --- a/Library/Homebrew/cask/lib/hbc/underscore_supporting_uri.rb +++ b/Library/Homebrew/cask/lib/hbc/underscore_supporting_uri.rb @@ -1,26 +1,28 @@ require "uri" -module Hbc::UnderscoreSupportingURI - def self.parse(maybe_uri) - return nil if maybe_uri.nil? - URI.parse(maybe_uri) - rescue URI::InvalidURIError => e - scheme, host, path = simple_parse(maybe_uri) - raise e unless path && host.include?("_") - URI.parse(without_host_underscores(scheme, host, path)).tap do |uri| - uri.instance_variable_set("@host", host) +module Hbc + module UnderscoreSupportingURI + def self.parse(maybe_uri) + return nil if maybe_uri.nil? + URI.parse(maybe_uri) + rescue URI::InvalidURIError => e + scheme, host, path = simple_parse(maybe_uri) + raise e unless path && host.include?("_") + URI.parse(without_host_underscores(scheme, host, path)).tap do |uri| + uri.instance_variable_set("@host", host) + end + end + + def self.simple_parse(maybe_uri) + scheme, host_and_path = maybe_uri.split("://") + host, path = host_and_path.split("/", 2) + [scheme, host, path] + rescue StandardError + nil + end + + def self.without_host_underscores(scheme, host, path) + ["#{scheme}:/", host.tr("_", "-"), path].join("/") end end - - def self.simple_parse(maybe_uri) - scheme, host_and_path = maybe_uri.split("://") - host, path = host_and_path.split("/", 2) - [scheme, host, path] - rescue StandardError - nil - end - - def self.without_host_underscores(scheme, host, path) - ["#{scheme}:/", host.tr("_", "-"), path].join("/") - end end diff --git a/Library/Homebrew/cask/lib/hbc/url.rb b/Library/Homebrew/cask/lib/hbc/url.rb index 5f763ca8a0..c09aaa0615 100644 --- a/Library/Homebrew/cask/lib/hbc/url.rb +++ b/Library/Homebrew/cask/lib/hbc/url.rb @@ -1,37 +1,39 @@ require "forwardable" -class Hbc::URL - FAKE_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10) http://caskroom.io".freeze +module Hbc + class URL + FAKE_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10) http://caskroom.io".freeze - attr_reader :using, :revision, :trust_cert, :uri, :cookies, :referer, :data + attr_reader :using, :revision, :trust_cert, :uri, :cookies, :referer, :data - extend Forwardable - def_delegators :uri, :path, :scheme, :to_s + extend Forwardable + def_delegators :uri, :path, :scheme, :to_s - def self.from(*args, &block) - if block_given? - Hbc::DSL::StanzaProxy.once(self) { new(*block.call) } - else - new(*args) + def self.from(*args, &block) + if block_given? + Hbc::DSL::StanzaProxy.once(self) { new(*block.call) } + else + new(*args) + end end - end - def initialize(uri, options = {}) - @uri = Hbc::UnderscoreSupportingURI.parse(uri) - @user_agent = options[:user_agent] - @cookies = options[:cookies] - @referer = options[:referer] - @using = options[:using] - @revision = options[:revision] - @trust_cert = options[:trust_cert] - @data = options[:data] - end + def initialize(uri, options = {}) + @uri = Hbc::UnderscoreSupportingURI.parse(uri) + @user_agent = options[:user_agent] + @cookies = options[:cookies] + @referer = options[:referer] + @using = options[:using] + @revision = options[:revision] + @trust_cert = options[:trust_cert] + @data = options[:data] + end - def user_agent - if @user_agent == :fake - FAKE_USER_AGENT - else - @user_agent + def user_agent + if @user_agent == :fake + FAKE_USER_AGENT + else + @user_agent + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/url_checker.rb b/Library/Homebrew/cask/lib/hbc/url_checker.rb index 327306b2bb..4dc1f136aa 100644 --- a/Library/Homebrew/cask/lib/hbc/url_checker.rb +++ b/Library/Homebrew/cask/lib/hbc/url_checker.rb @@ -1,75 +1,77 @@ require "hbc/checkable" -class Hbc::UrlChecker - attr_accessor :cask, :response_status, :headers +module Hbc + class UrlChecker + attr_accessor :cask, :response_status, :headers - include Hbc::Checkable + include Checkable - def initialize(cask, fetcher = Hbc::Fetcher) - @cask = cask - @fetcher = fetcher - @headers = {} - end - - def summary_header - "url check result for #{cask}" - end - - def run - _get_data_from_request - return if errors? - _check_response_status - end - - HTTP_RESPONSES = [ - "HTTP/1.0 200 OK", - "HTTP/1.1 200 OK", - "HTTP/1.1 302 Found", - ].freeze - - OK_RESPONSES = { - "http" => HTTP_RESPONSES, - "https" => HTTP_RESPONSES, - "ftp" => ["OK"], - }.freeze - - def _check_response_status - ok = OK_RESPONSES[cask.url.scheme] - return if ok.include?(@response_status) - add_error "unexpected http response, expecting #{ok.map(&:utf8_inspect).join(" or ")}, got #{@response_status.utf8_inspect}" - end - - def _get_data_from_request - response = @fetcher.head(cask.url) - - if response.empty? - add_error "timeout while requesting #{cask.url}" - return + def initialize(cask, fetcher = Fetcher) + @cask = cask + @fetcher = fetcher + @headers = {} end - response_lines = response.split("\n").map(&:chomp) + def summary_header + "url check result for #{cask}" + end - case cask.url.scheme - when "http", "https" then - @response_status = response_lines.grep(%r{^HTTP}).last - if @response_status.respond_to?(:strip) - @response_status.strip! - unless response_lines.index(@response_status).nil? - http_headers = response_lines[(response_lines.index(@response_status) + 1)..-1] - http_headers.each do |line| - header_name, header_value = line.split(": ") - @headers[header_name] = header_value + def run + _get_data_from_request + return if errors? + _check_response_status + end + + HTTP_RESPONSES = [ + "HTTP/1.0 200 OK", + "HTTP/1.1 200 OK", + "HTTP/1.1 302 Found", + ].freeze + + OK_RESPONSES = { + "http" => HTTP_RESPONSES, + "https" => HTTP_RESPONSES, + "ftp" => ["OK"], + }.freeze + + def _check_response_status + ok = OK_RESPONSES[cask.url.scheme] + return if ok.include?(@response_status) + add_error "unexpected http response, expecting #{ok.map(&:utf8_inspect).join(" or ")}, got #{@response_status.utf8_inspect}" + end + + def _get_data_from_request + response = @fetcher.head(cask.url) + + if response.empty? + add_error "timeout while requesting #{cask.url}" + return + end + + response_lines = response.split("\n").map(&:chomp) + + case cask.url.scheme + when "http", "https" then + @response_status = response_lines.grep(%r{^HTTP}).last + if @response_status.respond_to?(:strip) + @response_status.strip! + unless response_lines.index(@response_status).nil? + http_headers = response_lines[(response_lines.index(@response_status) + 1)..-1] + http_headers.each do |line| + header_name, header_value = line.split(": ") + @headers[header_name] = header_value + end end end + when "ftp" then + @response_status = "OK" + response_lines.each do |line| + header_name, header_value = line.split(": ") + @headers[header_name] = header_value + end + else + add_error "unknown scheme for #{cask.url}" end - when "ftp" then - @response_status = "OK" - response_lines.each do |line| - header_name, header_value = line.split(": ") - @headers[header_name] = header_value - end - else - add_error "unknown scheme for #{cask.url}" end end end diff --git a/Library/Homebrew/cask/lib/hbc/utils.rb b/Library/Homebrew/cask/lib/hbc/utils.rb index 1b80f6ae22..9159544a63 100644 --- a/Library/Homebrew/cask/lib/hbc/utils.rb +++ b/Library/Homebrew/cask/lib/hbc/utils.rb @@ -1,5 +1,3 @@ -module Hbc::Utils; end - require "yaml" require "open3" require "stringio" @@ -43,155 +41,157 @@ def odebug(title, *sput) puts sput unless sput.empty? end -module Hbc::Utils - def self.which(cmd, path = ENV["PATH"]) - unless File.basename(cmd) == cmd.to_s - # cmd contains a directory element +module Hbc + module Utils + def self.which(cmd, path = ENV["PATH"]) + unless File.basename(cmd) == cmd.to_s + # cmd contains a directory element + cmd_pn = Pathname(cmd) + return nil unless cmd_pn.absolute? + return resolve_executable(cmd_pn) + end + path.split(File::PATH_SEPARATOR).each do |elt| + fq_cmd = Pathname(elt).expand_path.join(cmd) + resolved = resolve_executable fq_cmd + return resolved if resolved + end + nil + end + + def self.resolve_executable(cmd) cmd_pn = Pathname(cmd) - return nil unless cmd_pn.absolute? - return resolve_executable(cmd_pn) - end - path.split(File::PATH_SEPARATOR).each do |elt| - fq_cmd = Pathname(elt).expand_path.join(cmd) - resolved = resolve_executable fq_cmd - return resolved if resolved - end - nil - end - - def self.resolve_executable(cmd) - cmd_pn = Pathname(cmd) - return nil unless cmd_pn.exist? - return nil unless cmd_pn.executable? - begin - cmd_pn = Pathname(cmd_pn.realpath) - rescue RuntimeError - return nil - end - return nil unless cmd_pn.file? - cmd_pn - end - - def self.gain_permissions_remove(path, command: Hbc::SystemCommand) - if path.respond_to?(:rmtree) && path.exist? - gain_permissions(path, ["-R"], command, &:rmtree) - elsif File.symlink?(path) - gain_permissions(path, ["-h"], command, &FileUtils.method(:rm_f)) - end - end - - def self.gain_permissions(path, command_args, command) - tried_permissions = false - tried_ownership = false - begin - yield path - rescue StandardError - # in case of permissions problems - unless tried_permissions - # TODO: Better handling for the case where path is a symlink. - # The -h and -R flags cannot be combined, and behavior is - # dependent on whether the file argument has a trailing - # slash. This should do the right thing, but is fragile. - command.run("/usr/bin/chflags", - must_succeed: false, - args: command_args + ["--", "000", path]) - command.run("/bin/chmod", - must_succeed: false, - args: command_args + ["--", "u+rwx", path]) - command.run("/bin/chmod", - must_succeed: false, - args: command_args + ["-N", path]) - tried_permissions = true - retry # rmtree + return nil unless cmd_pn.exist? + return nil unless cmd_pn.executable? + begin + cmd_pn = Pathname(cmd_pn.realpath) + rescue RuntimeError + return nil end - unless tried_ownership - # in case of ownership problems - # TODO: Further examine files to see if ownership is the problem - # before using sudo+chown - ohai "Using sudo to gain ownership of path '#{path}'" - command.run("/usr/sbin/chown", - args: command_args + ["--", current_user, path], - sudo: true) - tried_ownership = true - # retry chflags/chmod after chown - tried_permissions = false - retry # rmtree + return nil unless cmd_pn.file? + cmd_pn + end + + def self.gain_permissions_remove(path, command: SystemCommand) + if path.respond_to?(:rmtree) && path.exist? + gain_permissions(path, ["-R"], command, &:rmtree) + elsif File.symlink?(path) + gain_permissions(path, ["-h"], command, &FileUtils.method(:rm_f)) end end - end - def self.current_user - Etc.getpwuid(Process.euid).name - end - - # paths that "look" descendant (textually) will still - # return false unless both the given paths exist - def self.file_is_descendant(file, dir) - file = Pathname.new(file) - dir = Pathname.new(dir) - return false unless file.exist? && dir.exist? - unless dir.directory? - onoe "Argument must be a directory: '#{dir}'" - return false + def self.gain_permissions(path, command_args, command) + tried_permissions = false + tried_ownership = false + begin + yield path + rescue StandardError + # in case of permissions problems + unless tried_permissions + # TODO: Better handling for the case where path is a symlink. + # The -h and -R flags cannot be combined, and behavior is + # dependent on whether the file argument has a trailing + # slash. This should do the right thing, but is fragile. + command.run("/usr/bin/chflags", + must_succeed: false, + args: command_args + ["--", "000", path]) + command.run("/bin/chmod", + must_succeed: false, + args: command_args + ["--", "u+rwx", path]) + command.run("/bin/chmod", + must_succeed: false, + args: command_args + ["-N", path]) + tried_permissions = true + retry # rmtree + end + unless tried_ownership + # in case of ownership problems + # TODO: Further examine files to see if ownership is the problem + # before using sudo+chown + ohai "Using sudo to gain ownership of path '#{path}'" + command.run("/usr/sbin/chown", + args: command_args + ["--", current_user, path], + sudo: true) + tried_ownership = true + # retry chflags/chmod after chown + tried_permissions = false + retry # rmtree + end + end end - unless file.absolute? && dir.absolute? - onoe "Both arguments must be absolute: '#{file}', '#{dir}'" - return false + + def self.current_user + Etc.getpwuid(Process.euid).name end - while file.parent != file - return true if File.identical?(file, dir) - file = file.parent + + # paths that "look" descendant (textually) will still + # return false unless both the given paths exist + def self.file_is_descendant(file, dir) + file = Pathname.new(file) + dir = Pathname.new(dir) + return false unless file.exist? && dir.exist? + unless dir.directory? + onoe "Argument must be a directory: '#{dir}'" + return false + end + unless file.absolute? && dir.absolute? + onoe "Both arguments must be absolute: '#{file}', '#{dir}'" + return false + end + while file.parent != file + return true if File.identical?(file, dir) + file = file.parent + end + false end - false - end - def self.path_occupied?(path) - File.exist?(path) || File.symlink?(path) - end - - def self.error_message_with_suggestions - <<-EOS.undent - Most likely, this means you have an outdated version of Homebrew-Cask. Please run: - - #{Tty.green}#{UPDATE_CMD} - - #{Tty.reset}If this doesn’t fix the problem, please report this bug: - - #{Tty.em}#{ISSUES_URL}#{Tty.reset} - - EOS - end - - def self.method_missing_message(method, token, section = nil) - poo = [] - poo << "Unexpected method '#{method}' called" - poo << "during #{section}" if section - poo << "on Cask #{token}." - - opoo(poo.join(" ") + "\n" + error_message_with_suggestions) - end - - def self.nowstamp_metadata_path(container_path) - @timenow ||= Time.now.gmtime - if container_path.respond_to?(:join) - precision = 3 - timestamp = @timenow.strftime("%Y%m%d%H%M%S") - fraction = format("%.#{precision}f", @timenow.to_f - @timenow.to_i)[1..-1] - timestamp.concat(fraction) - container_path.join(timestamp) + def self.path_occupied?(path) + File.exist?(path) || File.symlink?(path) end - end - def self.size_in_bytes(files) - Array(files).reduce(0) { |a, e| a + (File.size?(e) || 0) } - end + def self.error_message_with_suggestions + <<-EOS.undent + Most likely, this means you have an outdated version of Homebrew-Cask. Please run: - def self.capture_stderr - previous_stderr = $stderr - $stderr = StringIO.new - yield - $stderr.string - ensure - $stderr = previous_stderr + #{Tty.green}#{UPDATE_CMD} + + #{Tty.reset}If this doesn’t fix the problem, please report this bug: + + #{Tty.em}#{ISSUES_URL}#{Tty.reset} + + EOS + end + + def self.method_missing_message(method, token, section = nil) + poo = [] + poo << "Unexpected method '#{method}' called" + poo << "during #{section}" if section + poo << "on Cask #{token}." + + opoo(poo.join(" ") + "\n" + error_message_with_suggestions) + end + + def self.nowstamp_metadata_path(container_path) + @timenow ||= Time.now.gmtime + if container_path.respond_to?(:join) + precision = 3 + timestamp = @timenow.strftime("%Y%m%d%H%M%S") + fraction = format("%.#{precision}f", @timenow.to_f - @timenow.to_i)[1..-1] + timestamp.concat(fraction) + container_path.join(timestamp) + end + end + + def self.size_in_bytes(files) + Array(files).reduce(0) { |a, e| a + (File.size?(e) || 0) } + end + + def self.capture_stderr + previous_stderr = $stderr + $stderr = StringIO.new + yield + $stderr.string + ensure + $stderr = previous_stderr + end end end diff --git a/Library/Homebrew/cask/lib/hbc/utils/file.rb b/Library/Homebrew/cask/lib/hbc/utils/file.rb index 967c6834f2..6b80f33ce6 100644 --- a/Library/Homebrew/cask/lib/hbc/utils/file.rb +++ b/Library/Homebrew/cask/lib/hbc/utils/file.rb @@ -1,12 +1,14 @@ -module Hbc::Utils - module_function +module Hbc + module Utils + module_function - def file_locked?(file) - unlocked = File.open(file).flock(File::LOCK_EX | File::LOCK_NB) - # revert lock if file was unlocked before check - File.open(file).flock(File::LOCK_UN) if unlocked - !unlocked - rescue - true + def file_locked?(file) + unlocked = File.open(file).flock(File::LOCK_EX | File::LOCK_NB) + # revert lock if file was unlocked before check + File.open(file).flock(File::LOCK_UN) if unlocked + !unlocked + rescue + true + end end end diff --git a/Library/Homebrew/cask/lib/hbc/verify.rb b/Library/Homebrew/cask/lib/hbc/verify.rb index d3c2713e7d..dbf3d0e880 100644 --- a/Library/Homebrew/cask/lib/hbc/verify.rb +++ b/Library/Homebrew/cask/lib/hbc/verify.rb @@ -1,33 +1,33 @@ -module Hbc::Verify; end - require "hbc/verify/checksum" require "hbc/verify/gpg" -module Hbc::Verify - module_function +module Hbc + module Verify + module_function - def verifications - [ - Hbc::Verify::Checksum - # TODO: Hbc::Verify::Gpg - ] - end - - def all(cask, downloaded_path) - odebug "Verifying download" - verifications = for_cask(cask) - odebug "#{verifications.size} verifications defined", verifications - verifications.each do |verification| - odebug "Running verification of class #{verification}" - verification.new(cask, downloaded_path).verify + def verifications + [ + Hbc::Verify::Checksum + # TODO: Hbc::Verify::Gpg + ] end - end - def for_cask(cask) - odebug "Determining which verifications to run for Cask #{cask}" - verifications.select do |verification| - odebug "Checking for verification class #{verification}" - verification.me?(cask) + def all(cask, downloaded_path) + odebug "Verifying download" + verifications = for_cask(cask) + odebug "#{verifications.size} verifications defined", verifications + verifications.each do |verification| + odebug "Running verification of class #{verification}" + verification.new(cask, downloaded_path).verify + end + end + + def for_cask(cask) + odebug "Determining which verifications to run for Cask #{cask}" + verifications.select do |verification| + odebug "Checking for verification class #{verification}" + verification.me?(cask) + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/verify/checksum.rb b/Library/Homebrew/cask/lib/hbc/verify/checksum.rb index 3af6f1667f..d079a44464 100644 --- a/Library/Homebrew/cask/lib/hbc/verify/checksum.rb +++ b/Library/Homebrew/cask/lib/hbc/verify/checksum.rb @@ -1,43 +1,47 @@ require "digest" -class Hbc::Verify::Checksum - def self.me?(cask) - return true unless cask.sha256 == :no_check - ohai "No checksum defined for Cask #{cask}, skipping verification" - false - end +module Hbc + module Verify + class Checksum + def self.me?(cask) + return true unless cask.sha256 == :no_check + ohai "No checksum defined for Cask #{cask}, skipping verification" + false + end - attr_reader :cask, :downloaded_path + attr_reader :cask, :downloaded_path - def initialize(cask, downloaded_path) - @cask = cask - @downloaded_path = downloaded_path - end + def initialize(cask, downloaded_path) + @cask = cask + @downloaded_path = downloaded_path + end - def verify - return unless self.class.me?(cask) - ohai "Verifying checksum for Cask #{cask}" - verify_checksum - end + def verify + return unless self.class.me?(cask) + ohai "Verifying checksum for Cask #{cask}" + verify_checksum + end - private + private - def expected - @expected ||= cask.sha256 - end + def expected + @expected ||= cask.sha256 + end - def computed - @computed ||= Digest::SHA2.file(downloaded_path).hexdigest - end + def computed + @computed ||= Digest::SHA2.file(downloaded_path).hexdigest + end - def verify_checksum - raise Hbc::CaskSha256MissingError, "sha256 required: sha256 '#{computed}'" if expected.nil? || expected.empty? + def verify_checksum + raise CaskSha256MissingError, "sha256 required: sha256 '#{computed}'" if expected.nil? || expected.empty? - if expected == computed - odebug "SHA256 checksums match" - else - ohai 'Note: running "brew update" may fix sha256 checksum errors' - raise Hbc::CaskSha256MismatchError.new(downloaded_path, expected, computed) + if expected == computed + odebug "SHA256 checksums match" + else + ohai 'Note: running "brew update" may fix sha256 checksum errors' + raise CaskSha256MismatchError.new(downloaded_path, expected, computed) + end + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/verify/gpg.rb b/Library/Homebrew/cask/lib/hbc/verify/gpg.rb index 6190f67d14..6eb5fc23b2 100644 --- a/Library/Homebrew/cask/lib/hbc/verify/gpg.rb +++ b/Library/Homebrew/cask/lib/hbc/verify/gpg.rb @@ -1,60 +1,64 @@ -class Hbc::Verify::Gpg - def self.me?(cask) - cask.gpg - end +module Hbc + module Verify + class Gpg + def self.me?(cask) + cask.gpg + end - attr_reader :cask, :downloaded_path + attr_reader :cask, :downloaded_path - def initialize(cask, downloaded_path, command = Hbc::SystemCommand) - @command = command - @cask = cask - @downloaded_path = downloaded_path - end + def initialize(cask, downloaded_path, command = Hbc::SystemCommand) + @command = command + @cask = cask + @downloaded_path = downloaded_path + end - def available? - return @available unless @available.nil? - @available = self.class.me?(cask) && installed? - end + def available? + return @available unless @available.nil? + @available = self.class.me?(cask) && installed? + end - def installed? - cmd = @command.run("/usr/bin/type", - args: ["-p", "gpg"]) + def installed? + cmd = @command.run("/usr/bin/type", + args: ["-p", "gpg"]) - # if `gpg` is found, return its absolute path - cmd.success? ? cmd.stdout : false - end + # if `gpg` is found, return its absolute path + cmd.success? ? cmd.stdout : false + end - def fetch_sig(force = false) - unversioned_cask = cask.version.is_a?(Symbol) - cached = cask.metadata_subdir("gpg") unless unversioned_cask + def fetch_sig(force = false) + unversioned_cask = cask.version.is_a?(Symbol) + cached = cask.metadata_subdir("gpg") unless unversioned_cask - meta_dir = cached || cask.metadata_subdir("gpg", :now, true) - sig_path = meta_dir.join("signature.asc") + meta_dir = cached || cask.metadata_subdir("gpg", :now, true) + sig_path = meta_dir.join("signature.asc") - curl(cask.gpg.signature, "-o", sig_path.to_s) unless cached || force + curl(cask.gpg.signature, "-o", sig_path.to_s) unless cached || force - sig_path - end + sig_path + end - def import_key - args = if cask.gpg.key_id - ["--recv-keys", cask.gpg.key_id] - elsif cask.gpg.key_url - ["--fetch-key", cask.gpg.key_url.to_s] - end + def import_key + args = if cask.gpg.key_id + ["--recv-keys", cask.gpg.key_id] + elsif cask.gpg.key_url + ["--fetch-key", cask.gpg.key_url.to_s] + end - @command.run!("gpg", args: args) - end + @command.run!("gpg", args: args) + end - def verify - return unless available? - import_key - sig = fetch_sig + def verify + return unless available? + import_key + sig = fetch_sig - ohai "Verifying GPG signature for #{cask}" + ohai "Verifying GPG signature for #{cask}" - @command.run!("gpg", - args: ["--verify", sig, downloaded_path], - print_stdout: true) + @command.run!("gpg", + args: ["--verify", sig, downloaded_path], + print_stdout: true) + end + end end end diff --git a/Library/Homebrew/cask/lib/hbc/without_source.rb b/Library/Homebrew/cask/lib/hbc/without_source.rb index 6ed826e41e..69131d5c70 100644 --- a/Library/Homebrew/cask/lib/hbc/without_source.rb +++ b/Library/Homebrew/cask/lib/hbc/without_source.rb @@ -1,15 +1,17 @@ -class Hbc::WithoutSource < Hbc::Cask - # Override from `Hbc::DSL` because we don't have a cask source file to work - # with, so we don't know the cask's `version`. - def staged_path - (caskroom_path.children - [metadata_master_container_path]).first - end +module Hbc + class WithoutSource < Cask + # Override from `Hbc::DSL` because we don't have a cask source file to work + # with, so we don't know the cask's `version`. + def staged_path + (caskroom_path.children - [metadata_master_container_path]).first + end - def to_s - "#{token} (!)" - end + def to_s + "#{token} (!)" + end - def installed? - caskroom_path.exist? + def installed? + caskroom_path.exist? + end end end diff --git a/Library/Homebrew/cask/spec/spec_helper.rb b/Library/Homebrew/cask/spec/spec_helper.rb index 5a26884ac3..aff2147451 100644 --- a/Library/Homebrew/cask/spec/spec_helper.rb +++ b/Library/Homebrew/cask/spec/spec_helper.rb @@ -29,7 +29,9 @@ require "mocha/api" require "hbc" -class Hbc::TestCask < Hbc::Cask; end +module Hbc + class TestCask < Cask; end +end TEST_TMPDIR = Dir.mktmpdir("homebrew_cask_tests") at_exit do diff --git a/Library/Homebrew/cask/test/cask/cli/create_test.rb b/Library/Homebrew/cask/test/cask/cli/create_test.rb index cf3cf28a36..efca1a5064 100644 --- a/Library/Homebrew/cask/test/cask/cli/create_test.rb +++ b/Library/Homebrew/cask/test/cask/cli/create_test.rb @@ -1,17 +1,21 @@ require "test_helper" # monkeypatch for testing -class Hbc::CLI::Create - def self.exec_editor(*command) - editor_commands << command - end +module Hbc + class CLI + class Create + def self.exec_editor(*command) + editor_commands << command + end - def self.reset! - @editor_commands = [] - end + def self.reset! + @editor_commands = [] + end - def self.editor_commands - @editor_commands ||= [] + def self.editor_commands + @editor_commands ||= [] + end + end end end diff --git a/Library/Homebrew/cask/test/cask/cli/edit_test.rb b/Library/Homebrew/cask/test/cask/cli/edit_test.rb index ebec5f414d..3f385eb336 100644 --- a/Library/Homebrew/cask/test/cask/cli/edit_test.rb +++ b/Library/Homebrew/cask/test/cask/cli/edit_test.rb @@ -1,17 +1,21 @@ require "test_helper" # monkeypatch for testing -class Hbc::CLI::Edit - def self.exec_editor(*command) - editor_commands << command - end +module Hbc + class CLI + class Edit + def self.exec_editor(*command) + editor_commands << command + end - def self.reset! - @editor_commands = [] - end + def self.reset! + @editor_commands = [] + end - def self.editor_commands - @editor_commands ||= [] + def self.editor_commands + @editor_commands ||= [] + end + end end end diff --git a/Library/Homebrew/cask/test/cask/cli/home_test.rb b/Library/Homebrew/cask/test/cask/cli/home_test.rb index 28fd2a391c..78302abd19 100644 --- a/Library/Homebrew/cask/test/cask/cli/home_test.rb +++ b/Library/Homebrew/cask/test/cask/cli/home_test.rb @@ -1,17 +1,21 @@ require "test_helper" # monkeypatch for testing -class Hbc::CLI::Home - def self.system(*command) - system_commands << command - end +module Hbc + class CLI + class Home + def self.system(*command) + system_commands << command + end - def self.reset! - @system_commands = [] - end + def self.reset! + @system_commands = [] + end - def self.system_commands - @system_commands ||= [] + def self.system_commands + @system_commands ||= [] + end + end end end diff --git a/Library/Homebrew/cask/test/support/cleanup.rb b/Library/Homebrew/cask/test/support/cleanup.rb index 0cb9c02fac..c31a74be22 100644 --- a/Library/Homebrew/cask/test/support/cleanup.rb +++ b/Library/Homebrew/cask/test/support/cleanup.rb @@ -1,12 +1,10 @@ -module Hbc::CleanupHooks - def after_teardown - super - Hbc.installed.each do |cask| - Hbc::Installer.new(cask).purge_versioned_files +module MiniTest + class Spec + def after_teardown + super + Hbc.installed.each do |cask| + Hbc::Installer.new(cask).purge_versioned_files + end end end end - -class MiniTest::Spec - include Hbc::CleanupHooks -end diff --git a/Library/Homebrew/cask/test/support/fake_dirs.rb b/Library/Homebrew/cask/test/support/fake_dirs.rb index 89612f850a..f8e36bbbb2 100644 --- a/Library/Homebrew/cask/test/support/fake_dirs.rb +++ b/Library/Homebrew/cask/test/support/fake_dirs.rb @@ -24,6 +24,8 @@ module FakeDirHooks end end -class MiniTest::Spec - include FakeDirHooks +module MiniTest + class Spec + include FakeDirHooks + end end diff --git a/Library/Homebrew/cask/test/support/fake_fetcher.rb b/Library/Homebrew/cask/test/support/fake_fetcher.rb index a49a89f7f4..43483751fe 100644 --- a/Library/Homebrew/cask/test/support/fake_fetcher.rb +++ b/Library/Homebrew/cask/test/support/fake_fetcher.rb @@ -1,20 +1,22 @@ -class Hbc::FakeFetcher - def self.fake_response_for(url, response) - @responses[url] = response - end +module Hbc + class FakeFetcher + def self.fake_response_for(url, response) + @responses[url] = response + end - def self.head(url) - @responses ||= {} - raise("no response faked for #{url.inspect}") unless @responses.key?(url) - @responses[url] - end + def self.head(url) + @responses ||= {} + raise("no response faked for #{url.inspect}") unless @responses.key?(url) + @responses[url] + end - def self.init - @responses = {} - end + def self.init + @responses = {} + end - def self.clear - @responses = {} + def self.clear + @responses = {} + end end end @@ -30,6 +32,8 @@ module FakeFetcherHooks end end -class MiniTest::Spec - include FakeFetcherHooks +module MiniTest + class Spec + include FakeFetcherHooks + end end diff --git a/Library/Homebrew/cask/test/support/fake_system_command.rb b/Library/Homebrew/cask/test/support/fake_system_command.rb index e19330fd04..97efd0761a 100644 --- a/Library/Homebrew/cask/test/support/fake_system_command.rb +++ b/Library/Homebrew/cask/test/support/fake_system_command.rb @@ -1,62 +1,64 @@ -class Hbc::FakeSystemCommand - def self.responses - @responses ||= {} - end +module Hbc + class FakeSystemCommand + def self.responses + @responses ||= {} + end - def self.expectations - @expectations ||= {} - end + def self.expectations + @expectations ||= {} + end - def self.system_calls - @system_calls ||= Hash.new(0) - end + def self.system_calls + @system_calls ||= Hash.new(0) + end - def self.clear - @responses = nil - @expectations = nil - @system_calls = nil - end + def self.clear + @responses = nil + @expectations = nil + @system_calls = nil + end - def self.stubs_command(command, response = "") - responses[command] = response - end + def self.stubs_command(command, response = "") + responses[command] = response + end - def self.expects_command(command, response = "", times = 1) - stubs_command(command, response) - expectations[command] = times - end + def self.expects_command(command, response = "", times = 1) + stubs_command(command, response) + expectations[command] = times + end - def self.expect_and_pass_through(command, times = 1) - pass_through = ->(cmd, opts) { Hbc::SystemCommand.run(cmd, opts) } - expects_command(command, pass_through, times) - end + def self.expect_and_pass_through(command, times = 1) + pass_through = ->(cmd, opts) { Hbc::SystemCommand.run(cmd, opts) } + expects_command(command, pass_through, times) + end - def self.verify_expectations! - expectations.each do |command, times| - unless system_calls[command] == times - raise("expected #{command.inspect} to be run #{times} times, but got #{system_calls[command]}") + def self.verify_expectations! + expectations.each do |command, times| + unless system_calls[command] == times + raise("expected #{command.inspect} to be run #{times} times, but got #{system_calls[command]}") + end end end - end - def self.run(command_string, options = {}) - command = Hbc::SystemCommand.new(command_string, options).command - unless responses.key?(command) - raise("no response faked for #{command.inspect}, faked responses are: #{responses.inspect}") + def self.run(command_string, options = {}) + command = Hbc::SystemCommand.new(command_string, options).command + unless responses.key?(command) + raise("no response faked for #{command.inspect}, faked responses are: #{responses.inspect}") + end + system_calls[command] += 1 + + response = responses[command] + if response.respond_to?(:call) + response.call(command_string, options) + else + Hbc::SystemCommand::Result.new(command, response, "", 0) + end end - system_calls[command] += 1 - response = responses[command] - if response.respond_to?(:call) - response.call(command_string, options) - else - Hbc::SystemCommand::Result.new(command, response, "", 0) + def self.run!(command, options = {}) + run(command, options.merge(must_succeed: true)) end end - - def self.run!(command, options = {}) - run(command, options.merge(must_succeed: true)) - end end module FakeSystemCommandHooks @@ -68,6 +70,8 @@ module FakeSystemCommandHooks end end -class MiniTest::Spec - include FakeSystemCommandHooks +module MiniTest + class Spec + include FakeSystemCommandHooks + end end diff --git a/Library/Homebrew/cask/test/support/never_sudo_system_command.rb b/Library/Homebrew/cask/test/support/never_sudo_system_command.rb index 50f510a7a5..8a370df447 100644 --- a/Library/Homebrew/cask/test/support/never_sudo_system_command.rb +++ b/Library/Homebrew/cask/test/support/never_sudo_system_command.rb @@ -1,5 +1,7 @@ -class Hbc::NeverSudoSystemCommand < Hbc::SystemCommand - def self.run(command, options = {}) - super(command, options.merge(sudo: false)) +module Hbc + class NeverSudoSystemCommand < SystemCommand + def self.run(command, options = {}) + super(command, options.merge(sudo: false)) + end end end diff --git a/Library/Homebrew/cask/test/support/shared_examples.rb b/Library/Homebrew/cask/test/support/shared_examples.rb index e846af4047..594ca81c17 100644 --- a/Library/Homebrew/cask/test/support/shared_examples.rb +++ b/Library/Homebrew/cask/test/support/shared_examples.rb @@ -5,14 +5,18 @@ MiniTest::Spec.class_eval do end end -module MiniTest::Spec::SharedExamples - def shared_examples_for(desc, &block) - MiniTest::Spec.shared_examples[desc] = block - end +module MiniTest + class Spec + module SharedExamples + def shared_examples_for(desc, &block) + MiniTest::Spec.shared_examples[desc] = block + end - def it_behaves_like(desc, *args, &block) - instance_exec(*args, &MiniTest::Spec.shared_examples[desc]) - instance_eval(&block) if block_given? + def it_behaves_like(desc, *args, &block) + instance_exec(*args, &MiniTest::Spec.shared_examples[desc]) + instance_eval(&block) if block_given? + end + end end end diff --git a/Library/Homebrew/cask/test/test_helper.rb b/Library/Homebrew/cask/test/test_helper.rb index 4d795bffe9..47c1b4cdb3 100644 --- a/Library/Homebrew/cask/test/test_helper.rb +++ b/Library/Homebrew/cask/test/test_helper.rb @@ -159,7 +159,9 @@ require "tempfile" FileUtils.mkdir_p Hbc.homebrew_prefix.join("bin") # Common superclass for test Casks for when we need to filter them out -class Hbc::TestCask < Hbc::Cask; end +module Hbc + class TestCask < Cask; end +end # jack in some optional utilities FileUtils.ln_s "/usr/local/bin/cabextract", Hbc.homebrew_prefix.join("bin/cabextract")