diff options
Diffstat (limited to 'Library/Homebrew/cask/lib/hbc/cli')
23 files changed, 1220 insertions, 0 deletions
diff --git a/Library/Homebrew/cask/lib/hbc/cli/audit.rb b/Library/Homebrew/cask/lib/hbc/cli/audit.rb new file mode 100644 index 000000000..289547b44 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/audit.rb @@ -0,0 +1,52 @@ +class Hbc::CLI::Audit < Hbc::CLI::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 initialize(args, auditor) + @args = args + @auditor = auditor + end + + 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 diff --git a/Library/Homebrew/cask/lib/hbc/cli/base.rb b/Library/Homebrew/cask/lib/hbc/cli/base.rb new file mode 100644 index 000000000..af03969af --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/base.rb @@ -0,0 +1,21 @@ +class Hbc::CLI::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.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.needs_init? + false + end +end diff --git a/Library/Homebrew/cask/lib/hbc/cli/cat.rb b/Library/Homebrew/cask/lib/hbc/cli/cat.rb new file mode 100644 index 000000000..d6d545c3b --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/cat.rb @@ -0,0 +1,15 @@ +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 + + def self.help + "dump raw source of the given Cask to the standard output" + end +end diff --git a/Library/Homebrew/cask/lib/hbc/cli/cleanup.rb b/Library/Homebrew/cask/lib/hbc/cli/cleanup.rb new file mode 100644 index 000000000..b098a243d --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/cleanup.rb @@ -0,0 +1,88 @@ +class Hbc::CLI::Cleanup < Hbc::CLI::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(*_ignored) + default.cleanup! + 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_all_cache_files + 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_all_cache_files + message = "Removing cached downloads" + message.concat " older than #{OUTDATED_DAYS} days old" if outdated_only + ohai message + delete_paths(cache_files) + end + + def delete_paths(paths) + cleanup_size = 0 + processed_files = 0 + paths.each do |item| + next unless item.exist? + processed_files += 1 + if Hbc::Utils.file_locked?(item) + puts "skipping: #{item} is locked" + next + end + 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 diff --git a/Library/Homebrew/cask/lib/hbc/cli/create.rb b/Library/Homebrew/cask/lib/hbc/cli/create.rb new file mode 100644 index 000000000..3c1ac76ed --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/create.rb @@ -0,0 +1,37 @@ +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}" + + raise Hbc::CaskAlreadyCreatedError, cask_token if cask_path.exist? + + 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 '' + end + EOS + end + + def self.help + "creates the given Cask and opens it in an editor" + end +end diff --git a/Library/Homebrew/cask/lib/hbc/cli/doctor.rb b/Library/Homebrew/cask/lib/hbc/cli/doctor.rb new file mode 100644 index 000000000..d2feb1e06 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/doctor.rb @@ -0,0 +1,213 @@ +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.directory? && t != Hbc.default_tap } + .map(&:path) + end + + def self.default_cask_count + default_cask_count = notfound_string + begin + default_cask_count = Hbc.default_tap.cask_dir.children.count(&:file?) + rescue StandardError + default_cask_count = "0 #{error_string "Error reading #{Hbc.default_tap.path}"}" + end + default_cask_count + end + + def self.homebrew_origin + homebrew_origin = notfound_string + begin + Dir.chdir(homebrew_repository) do + homebrew_origin = Hbc::SystemCommand.run("/usr/bin/git", + args: %w[config --get remote.origin.url], + print_stderr: false).stdout.strip + end + 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.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}" + 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.privileged_uid + Process.euid == 0 ? "Yes #{error_string 'warning: not recommended'}" : "No" + rescue StandardError + notfound_string + end + + def self.none_string + "<NONE>" + end + + def self.legacy_tap_pattern + %r{phinze} + end + + def self.notfound_string + "#{Hbc::Utils::Tty.red.underline}Not Found - Unknown Error#{Hbc::Utils::Tty.reset}" + end + + def self.error_string(string = "Error") + "#{Hbc::Utils::Tty.red.underline}(#{string})#{Hbc::Utils::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 + 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(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.nil? || paths.empty? + copy = Array.new(paths) + unless Hbc::Utils.file_is_descendant(copy[0], homebrew_taps) + copy[0] = "#{copy[0]} #{error_string 'error: should be descendant of Homebrew taps directory'}" + end + copy + 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 new file mode 100644 index 000000000..b2d4a9156 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/edit.rb @@ -0,0 +1,18 @@ +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 + + def self.help + "edits the given Cask" + end +end diff --git a/Library/Homebrew/cask/lib/hbc/cli/fetch.rb b/Library/Homebrew/cask/lib/hbc/cli/fetch.rb new file mode 100644 index 000000000..647f2af2c --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/fetch.rb @@ -0,0 +1,19 @@ +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" + + 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}" + 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 new file mode 100644 index 000000000..9c8c0a0e4 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/home.rb @@ -0,0 +1,18 @@ +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 + 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 new file mode 100644 index 000000000..dda405705 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/info.rb @@ -0,0 +1,66 @@ +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) + + 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) + "#{Hbc::Utils::Tty.underline}#{url}#{Hbc::Utils::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 : "#{Hbc::Utils::Tty.red}does not exist#{Hbc::Utils::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? ? "#{Hbc::Utils::Tty.red}None#{Hbc::Utils::Tty.reset}" : cask.name + 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.artifact_info(cask) + ohai "Artifacts" + Hbc::DSL::ORDINARY_ARTIFACT_TYPES.each do |type| + next if cask.artifacts[type].empty? + cask.artifacts[type].each do |artifact| + activatable_item = type == :stage_only ? "<none>" : artifact.first + puts "#{activatable_item} (#{type})" + end + end + end +end diff --git a/Library/Homebrew/cask/lib/hbc/cli/install.rb b/Library/Homebrew/cask/lib/hbc/cli/install.rb new file mode 100644 index 000000000..43eab9f3d --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/install.rb @@ -0,0 +1,60 @@ + +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 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) + 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 + end + end + count == 0 ? 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 new file mode 100644 index 000000000..f05dbe803 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_audit_modified_casks.rb @@ -0,0 +1,135 @@ +class Hbc::CLI::InternalAuditModifiedCasks < Hbc::CLI::InternalUseBase + RELEVANT_STANZAS = %i{version sha256 url appcast}.freeze + + 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 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 usage + <<-EOS.undent + Usage: brew cask _audit_modified_casks [options...] <commit range> + + Given a range of Git commits, find any Casks that were modified and run `brew + cask audit' on them. If the `url', `version', or `sha256' stanzas were modified, + run with the `--download' flag to verify the hash. + + 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 + + attr_reader :commit_range + + def cleanup? + @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) + end + end + report_failures + end + + def git_root + @git_root ||= git(*%w[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 new file mode 100644 index 000000000..d53f420e2 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_checkurl.rb @@ -0,0 +1,15 @@ +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 + 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 new file mode 100644 index 000000000..d1cfe8d63 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_dump.rb @@ -0,0 +1,30 @@ +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 + + raise Hbc::CaskError, "nothing to dump" if retval.nil? + raise Hbc::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}" + end + end + count == 0 ? 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 new file mode 100644 index 000000000..81d7ee673 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_help.rb @@ -0,0 +1,19 @@ +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)}" + 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 new file mode 100644 index 000000000..651a9ae37 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb @@ -0,0 +1,127 @@ +class Hbc::CLI::InternalStanza < Hbc::CLI::InternalUseBase + # Syntax + # + # brew cask _stanza <stanza_name> [ --table | --yaml | --inspect | --quiet ] [ <cask_token> ... ] + # + # If no tokens are given, then data for all Casks is returned. + # + # The pseudo-stanza "artifacts" is available. + # + # On failure, a blank line is returned on the standard output. + # + # Examples + # + # brew cask _stanza appcast --table + # brew cask _stanza app --table alfred google-chrome adium voicemac logisim vagrant + # brew cask _stanza url --table alfred google-chrome adium voicemac logisim vagrant + # brew cask _stanza version --table alfred google-chrome adium voicemac logisim vagrant + # brew cask _stanza artifacts --table --inspect alfred google-chrome adium voicemac logisim vagrant + # brew cask _stanza artifacts --table --yaml alfred google-chrome adium voicemac logisim vagrant + # + + # 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? + + 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" + 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 == 0 ? 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 new file mode 100644 index 000000000..6a4359ea1 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_use_base.rb @@ -0,0 +1,9 @@ +class Hbc::CLI::InternalUseBase < Hbc::CLI::Base + def self.command_name + super.sub(%r{^internal_}i, "_") + end + + def self.visible + false + end +end diff --git a/Library/Homebrew/cask/lib/hbc/cli/list.rb b/Library/Homebrew/cask/lib/hbc/cli/list.rb new file mode 100644 index 000000000..ce507a827 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/list.rb @@ -0,0 +1,82 @@ +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") + + 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" + end + rescue Hbc::CaskUnavailableError => e + onoe e + end + end + + count == 0 ? 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 +end diff --git a/Library/Homebrew/cask/lib/hbc/cli/search.rb b/Library/Homebrew/cask/lib/hbc/cli/search.rb new file mode 100644 index 000000000..c356128a6 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/search.rb @@ -0,0 +1,56 @@ +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 + if search_regexp + search_term = arguments.first + partial_matches = Hbc::CLI.nice_listing(Hbc.all_tokens).grep(%r{#{search_regexp}}i) + else + # suppressing search of the font Tap is a quick hack until behavior can be made configurable + all_tokens = Hbc::CLI.nice_listing Hbc.all_tokens.reject { |t| %r{^caskroom/homebrew-fonts/}.match(t) } + 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 diff --git a/Library/Homebrew/cask/lib/hbc/cli/style.rb b/Library/Homebrew/cask/lib/hbc/cli/style.rb new file mode 100644 index 000000000..ac7cbfb44 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/style.rb @@ -0,0 +1,69 @@ +require "English" + +class Hbc::CLI::Style < Hbc::CLI::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 + + attr_reader :args + def initialize(args) + @args = args + end + + def run + install_rubocop + system "rubocop", *rubocop_args, "--", *cask_paths + $CHILD_STATUS.success? + end + + 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}: ", "") + 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 new file mode 100644 index 000000000..cd98b6e61 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb @@ -0,0 +1,40 @@ +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" + + cask_tokens.each do |cask_token| + odebug "Uninstalling Cask #{cask_token}" + cask = Hbc.load(cask_token) + + raise Hbc::CaskNotInstalledError, cask unless cask.installed? || force + + 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") + + # 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 + + Hbc::Installer.new(cask, force: force).uninstall + + next if (versions = cask.versions).empty? + + single = versions.count == 1 + + puts <<-EOF.undent + #{cask_token} #{versions.join(', ')} #{single ? 'is' : 'are'} still installed. + Remove #{single ? 'it' : 'them all'} with `brew cask uninstall --force #{cask_token}`. + EOF + 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 new file mode 100644 index 000000000..ceb947544 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/update.rb @@ -0,0 +1,16 @@ +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 + + def self.help + "a synonym for 'brew update'" + end +end diff --git a/Library/Homebrew/cask/lib/hbc/cli/zap.rb b/Library/Homebrew/cask/lib/hbc/cli/zap.rb new file mode 100644 index 000000000..081378330 --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/cli/zap.rb @@ -0,0 +1,15 @@ +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 + end + end + + def self.help + "zaps all files associated with the given Cask" + end +end |
