Merge branch 'master' into mkdir_with_intermediates

This commit is contained in:
Mike McQuaid 2016-09-24 20:48:03 +01:00
commit e767fd3df9
299 changed files with 7626 additions and 8672 deletions

View File

@ -1,4 +1,4 @@
- [ ] Have you followed the guidelines in our [Contributing](https://github.com/Homebrew/brew/blob/master/.github/CONTRIBUTING.md) document?
- [ ] Have you followed the guidelines in our [Contributing](https://github.com/Homebrew/brew/blob/master/CONTRIBUTING.md) document?
- [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/Homebrew/brew/pulls) for the same change?
- [ ] Have you added an explanation of what your changes do and why you'd like us to include them?
- [ ] Have you written new tests for your changes? [Here's an example](https://github.com/Homebrew/homebrew/pull/49031).

2
.gitignore vendored
View File

@ -13,11 +13,13 @@
/Library/Homebrew/cask/bin
/Library/Homebrew/cask/vendor
/Library/Homebrew/cask/coverage
/Library/Homebrew/cask/tmp
/Library/Homebrew/test/.bundle
/Library/Homebrew/test/bin
/Library/Homebrew/test/vendor
/Library/Homebrew/test/coverage
/Library/Homebrew/test/fs_leak_log
/Library/Homebrew/tmp
/Library/LinkedKegs
/Library/Locks
/Library/PinnedKegs

View File

@ -4,6 +4,8 @@ inherit_from:
AllCops:
TargetRubyVersion: 2.0
Include:
- '**/.simplecov'
Exclude:
- 'Homebrew/cask/**/*'
- 'Homebrew/vendor/**/*'

View File

@ -1,4 +1,3 @@
# ruby style guide favorite
Style/StringLiterals:
EnforcedStyle: double_quotes
@ -7,17 +6,30 @@ Style/StringLiterals:
Style/StringLiteralsInInterpolation:
EnforcedStyle: double_quotes
# only for numbers >= 1_000_000
Style/NumericLiterals:
MinDigits: 7
# zero-prefixed octal literals are just too widely used (and mostly understood)
Style/NumericLiteralPrefix:
EnforcedOctalStyle: zero_only
# percent-x is allowed for multiline
Style/CommandLiteral:
EnforcedStyle: mixed
# depends_on foo: :bar looks rubbish
Style/HashSyntax:
EnforcedStyle: ruby19
Exclude:
- 'Taps/**/*'
# paths abound, easy escape
Style/RegexpLiteral:
EnforcedStyle: slashes
# too prevalent to change this now, but might be discussed/changed later
Style/Alias:
EnforcedStyle: prefer_alias_method
EnforcedStyle: prefer_alias
# our current conditional style is established, clear and
# requiring users to change that now would be confusing.
@ -58,14 +70,6 @@ Lint/ParenthesesAsGroupedExpression:
Style/EmptyLineBetweenDefs:
AllowAdjacentOneLineDefs: true
# port numbers and such tech stuff
Style/NumericLiterals:
Enabled: false
# zero-prefixed octal literals are just too widely used (and mostly understood)
Style/NumericLiteralPrefix:
EnforcedOctalStyle: zero_only
# consistency and readability when faced with string interpolation
Style/PercentLiteralDelimiters:
PreferredDelimiters:
@ -93,15 +97,25 @@ Style/AlignParameters:
# counterproductive in formulas, notably within the install method
Style/GuardClause:
Enabled: false
Exclude:
- 'Taps/**/*'
Style/IfUnlessModifier:
Exclude:
- 'Taps/**/*'
# TODO: enforce when rubocop has fixed this
# https://github.com/bbatsov/rubocop/issues/3516
Style/VariableNumber:
Enabled: false
# TODO: enforce when rubocop has shipped this
# https://github.com/bbatsov/rubocop/pull/3513
Style/TernaryParentheses:
Enabled: false
# dashes in filenames are typical
# TODO: enforce when rubocop has fixed this
# https://github.com/bbatsov/rubocop/issues/1545
Style/FileName:
Enabled: false
Regex: !ruby/regexp /^[\w\@\-\+\.]+(\.rb)?$/
# no percent word array, being friendly to non-ruby users
# TODO: enforce when rubocop has fixed this
@ -134,6 +148,7 @@ Style/MethodName:
Style/PredicateName:
Exclude:
- 'Homebrew/compat/**/*'
NameWhitelist: is_32_bit?, is_64_bit?
# `formula do` uses nested method definitions
Lint/NestedMethodDefinition:

View File

@ -1,21 +1,11 @@
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 30`
# on 2016-09-18 15:15:22 +0100 using RuboCop version 0.41.2.
# `rubocop --auto-gen-config --exclude-limit 100`
# on 2016-09-22 20:07:41 +0200 using RuboCop version 0.43.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: AlignWith, SupportedStyles, AutoCorrect.
# SupportedStyles: keyword, variable, start_of_line
Lint/EndAlignment:
Exclude:
- 'Homebrew/download_strategy.rb'
- 'Homebrew/keg.rb'
- 'Homebrew/os/mac/cctools_mach.rb'
# Offense count: 18
Lint/HandleExceptions:
Exclude:
@ -52,11 +42,6 @@ Lint/NestedMethodDefinition:
- 'Homebrew/dev-cmd/bottle.rb'
- 'Homebrew/dev-cmd/test-bot.rb'
# Offense count: 2
Lint/NonLocalExitFromIterator:
Exclude:
- 'Homebrew/extend/pathname.rb'
# Offense count: 28
Lint/RescueException:
Exclude:
@ -80,12 +65,7 @@ Lint/RescueException:
# Offense count: 1
Lint/ShadowedException:
Exclude:
- 'Homebrew/brew.rb'
# Offense count: 2
Lint/UselessAssignment:
Exclude:
- 'Homebrew/requirements.rb'
- 'Homebrew/utils/fork.rb'
# Offense count: 18
Metrics/BlockNesting:
@ -94,64 +74,17 @@ Metrics/BlockNesting:
# Offense count: 20
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 400
Max: 370
# Offense count: 1
# Offense count: 2
# Configuration parameters: CountKeywordArgs.
Metrics/ParameterLists:
Max: 6
# Offense count: 2
Performance/FixedSize:
Exclude:
- 'Homebrew/dev-cmd/audit.rb'
- 'Homebrew/dev-cmd/bottle.rb'
# Offense count: 8
Style/AccessorMethodName:
Exclude:
- 'Homebrew/download_strategy.rb'
- 'Homebrew/extend/ENV/std.rb'
- 'Homebrew/formula.rb'
- 'Homebrew/formula_lock.rb'
- 'Homebrew/formulary.rb'
- 'Homebrew/migrator.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: prefer_alias, prefer_alias_method
Style/Alias:
Exclude:
- 'Homebrew/blacklist.rb'
# Offense count: 26
Style/CaseEquality:
Exclude:
- 'Homebrew/cleanup.rb'
- 'Homebrew/cmd/search.rb'
- 'Homebrew/compilers.rb'
- 'Homebrew/cxxstdlib.rb'
- 'Homebrew/debrew.rb'
- 'Homebrew/dependencies.rb'
- 'Homebrew/dependency_collector.rb'
- 'Homebrew/download_strategy.rb'
- 'Homebrew/formula.rb'
- 'Homebrew/options.rb'
- 'Homebrew/patch.rb'
- 'Homebrew/pkg_version.rb'
- 'Homebrew/requirement.rb'
- 'Homebrew/requirements.rb'
- 'Homebrew/software_spec.rb'
- 'Homebrew/version.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: IndentWhenRelativeTo, SupportedStyles, IndentOneStep, IndentationWidth.
# SupportedStyles: case, end
Style/CaseIndentation:
Exclude:
- 'Homebrew/keg.rb'
# Offense count: 11
Style/ClassVars:
@ -168,24 +101,6 @@ Style/ConstantName:
Exclude:
- 'Homebrew/os/mac.rb'
# Offense count: 10
Style/DoubleNegation:
Exclude:
- 'Homebrew/extend/ARGV.rb'
- 'Homebrew/formula_installer.rb'
- 'Homebrew/os/mac/cctools_keg.rb'
- 'Homebrew/os/mac/ruby_keg.rb'
- 'Homebrew/os/mac/xcode.rb'
- 'Homebrew/requirement.rb'
- 'Homebrew/software_spec.rb'
# Offense count: 1
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: format, sprintf, percent
Style/FormatString:
Exclude:
- 'Homebrew/formula.rb'
# Offense count: 13
# Configuration parameters: AllowedVariables.
Style/GlobalVars:
@ -193,22 +108,20 @@ Style/GlobalVars:
- 'Homebrew/diagnostic.rb'
- 'Homebrew/utils.rb'
# Offense count: 2
Style/IdenticalConditionalBranches:
# Offense count: 51
# Cop supports --auto-correct.
# Configuration parameters: MaxLineLength.
Style/IfUnlessModifier:
Exclude:
- 'Homebrew/formula_lock.rb'
- 'Taps/**/*'
- 'Homebrew/dev-cmd/audit.rb'
# Offense count: 5
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: snake_case, camelCase
Style/MethodName:
Exclude:
- 'Homebrew/compat/**/*'
- 'Homebrew/cleanup.rb'
- 'Homebrew/diagnostic.rb'
- 'Homebrew/formula_cellar_checks.rb'
- 'Homebrew/formula_installer.rb'
- 'Homebrew/os/mac/cctools_mach.rb'
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
Style/IndentArray:
EnforcedStyle: special_inside_parentheses
# Offense count: 7
# Configuration parameters: EnforcedStyle, SupportedStyles.
@ -219,8 +132,6 @@ Style/ModuleFunction:
- 'Homebrew/os/mac.rb'
- 'Homebrew/os/mac/xcode.rb'
- 'Homebrew/os/mac/xquartz.rb'
- 'Homebrew/utils/github.rb'
- 'Homebrew/utils/json.rb'
# Offense count: 8
Style/MultilineBlockChain:
@ -241,14 +152,6 @@ Style/MutableConstant:
- 'Homebrew/tab.rb'
- 'Homebrew/tap.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
# SupportedStyles: skip_modifier_ifs, always
Style/Next:
Exclude:
- 'Homebrew/dev-cmd/test-bot.rb'
# Offense count: 9
Style/OpMethod:
Exclude:
@ -257,37 +160,9 @@ Style/OpMethod:
- 'Homebrew/install_renamed.rb'
- 'Homebrew/options.rb'
# Offense count: 4
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
# NamePrefix: is_, has_, have_
# NamePrefixBlacklist: is_, has_, have_
# NameWhitelist: is_a?
Style/PredicateName:
Exclude:
- 'Homebrew/compat/**/*'
- 'Homebrew/download_strategy.rb'
- 'Homebrew/hardware.rb'
# Offense count: 7
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Exclude:
- 'Homebrew/diagnostic.rb'
- 'Homebrew/keg.rb'
- 'Homebrew/version.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: AllowAsExpressionSeparator.
Style/Semicolon:
Exclude:
- 'Homebrew/descriptions.rb'
# Offense count: 1
# Configuration parameters: Methods.
# Methods: {"reduce"=>["a", "e"]}, {"inject"=>["a", "e"]}
Style/SingleLineBlockParams:
Exclude:
- 'Homebrew/diagnostic.rb'
# Configuration parameters: SupportedStyles.
# SupportedStyles: use_perl_names, use_english_names
Style/SpecialGlobalVars:
EnforcedStyle: use_perl_names

View File

@ -1,8 +1,8 @@
#!/usr/bin/env ruby
SimpleCov.start do
coverage_dir File.expand_path("../coverage", File.realpath(__FILE__))
root File.expand_path("../..", File.realpath(__FILE__))
coverage_dir File.expand_path("../test/coverage", File.realpath(__FILE__))
root File.expand_path("..", File.realpath(__FILE__))
# We manage the result cache ourselves and the default of 10 minutes can be
# too low (particularly on Travis CI), causing results from some integration
@ -16,14 +16,21 @@ SimpleCov.start do
add_filter "/Homebrew/vendor/"
if ENV["HOMEBREW_INTEGRATION_TEST"]
command_name ENV["HOMEBREW_INTEGRATION_TEST"]
command_name "#{ENV["HOMEBREW_INTEGRATION_TEST"]} (#{$$})"
at_exit do
exit_code = $!.nil? ? 0 : $!.status
$stdout.reopen("/dev/null")
SimpleCov.result # Just save result, but don't write formatted output.
# Just save result, but don't write formatted output.
coverage_result = Coverage.result
SimpleCov.add_not_loaded_files(coverage_result)
simplecov_result = SimpleCov::Result.new(coverage_result)
SimpleCov::ResultMerger.store_result(simplecov_result)
exit! exit_code
end
else
command_name "#{command_name} (#{$$})"
# Not using this during integration tests makes the tests 4x times faster
# without changing the coverage.
track_files "#{SimpleCov.root}/**/*.rb"

View File

@ -14,7 +14,7 @@ $:.unshift(HOMEBREW_LIBRARY_PATH.to_s)
require "global"
if ARGV == %w[--version] || ARGV == %w[-v]
puts "Homebrew #{Homebrew.homebrew_version_string}"
puts "Homebrew #{HOMEBREW_VERSION}"
puts "Homebrew/homebrew-core #{Homebrew.core_tap_version_string}"
exit 0
end
@ -37,9 +37,9 @@ begin
cmd = nil
ARGV.dup.each_with_index do |arg, i|
if help_flag && cmd
break
elsif help_flag_list.include?(arg)
break if help_flag && cmd
if help_flag_list.include?(arg)
# Option-style help: Both `--help <cmd>` and `<cmd> --help` are fine.
help_flag = true
elsif arg == "help" && !cmd

View File

@ -1,4 +1,8 @@
HOMEBREW_VERSION="0.9.9"
HOMEBREW_VERSION="$(git -C "$HOMEBREW_REPOSITORY" describe --tags --dirty 2>/dev/null)"
if [[ -z "$HOMEBREW_VERSION" ]]
then
HOMEBREW_VERSION=">1.0.0 (no git repository)"
fi
onoe() {
if [[ -t 2 ]] # check whether stderr is a tty.

View File

@ -31,9 +31,8 @@ class Build
def post_superenv_hacks
# Only allow Homebrew-approved directories into the PATH, unless
# a formula opts-in to allowing the user's path.
if formula.env.userpaths? || reqs.any? { |rq| rq.env.userpaths? }
ENV.userpaths!
end
return unless formula.env.userpaths? || reqs.any? { |rq| rq.env.userpaths? }
ENV.userpaths!
end
def effective_build_options_for(dependent)

View File

@ -47,7 +47,7 @@ class BuildOptions
def bottle?
include? "build-bottle"
end
alias_method :build_bottle?, :bottle?
alias build_bottle? bottle?
# True if a {Formula} is being built with {Formula.head} instead of {Formula.stable}.
# <pre>args << "--some-new-stuff" if build.head?</pre>

View File

@ -1,10 +1,8 @@
require: 'rubocop-cask'
AllCops:
TargetRubyVersion: 2.0
Exclude:
- '**/.simplecov'
- '**/Casks/**/*'
- 'developer/**/*'
- '**/vendor/**/*'
Metrics/AbcSize:
@ -16,10 +14,10 @@ Metrics/ClassLength:
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/MethodLength:
Metrics/LineLength:
Enabled: false
Metrics/PerceivedComplexity:
Metrics/MethodLength:
Enabled: false
Metrics/ModuleLength:
@ -29,6 +27,16 @@ Metrics/ModuleLength:
- 'lib/hbc/macos.rb'
- 'lib/hbc/utils.rb'
Metrics/PerceivedComplexity:
Enabled: false
Style/AlignHash:
EnforcedHashRocketStyle: table
EnforcedColonStyle: table
Style/BarePercentLiterals:
EnforcedStyle: percent_q
Style/BlockDelimiters:
EnforcedStyle: semantic
FunctionalMethods:
@ -59,6 +67,8 @@ Style/BlockDelimiters:
- chdir
- context
- create
- define_method
- define_singleton_method
- each_with_object
- fork
- measure
@ -75,15 +85,47 @@ Style/BlockDelimiters:
- lambda
- proc
Style/ClassAndModuleChildren:
EnforcedStyle: compact
EnforcedStyle: nested
Style/PredicateName:
NameWhitelist: is_32_bit?, is_64_bit?
Style/Documentation:
Enabled: false
Style/FileName:
Regex: !ruby/regexp /^((([\dA-Z]+|[\da-z]+)(_([\dA-Z]+|[\da-z]+))*|(\-\-)?([\dA-Z]+|[\da-z]+)(-([\dA-Z]+|[\da-z]+))*))(\.rb)?$/
Style/HashSyntax:
EnforcedStyle: ruby19_no_mixed_keys
Style/IndentArray:
EnforcedStyle: align_brackets
Style/IndentHash:
EnforcedStyle: align_braces
Style/PercentLiteralDelimiters:
PreferredDelimiters:
'%': '{}'
'%i': '{}'
'%q': '{}'
'%Q': '{}'
'%r': '{}'
'%s': '()'
'%w': '[]'
'%W': '[]'
'%x': '()'
Style/RaiseArgs:
EnforcedStyle: exploded
Style/RegexpLiteral:
EnforcedStyle: percent_r
Style/StringLiterals:
EnforcedStyle: double_quotes
Style/StringLiteralsInInterpolation:
EnforcedStyle: double_quotes
Style/TrailingCommaInLiteral:
EnforcedStyleForMultiline: comma

View File

@ -1 +1 @@
../test/.simplecov
../.simplecov

View File

@ -7,15 +7,21 @@ group :debug do
gem "pry-byebug", platforms: :mri
end
group :development do
gem "rubocop-cask", "~> 0.8.3"
end
group :test do
# This is SimpleCov v0.12.0 with two fixes merged on top, that finally resolve
# all issues with parallel tests, uncovered files, and tracked files. Switch
# back to stable as soon as v0.12.1 or v0.13.0 is released. For details, see:
# - https://github.com/colszowka/simplecov/pull/513
# - https://github.com/colszowka/simplecov/pull/520
gem "simplecov", "0.12.0",
git: "https://github.com/colszowka/simplecov.git",
branch: "master", # commit 83d8031ddde0927f87ef9327200a98583ca18d77
require: false
gem "codecov", require: false
gem "minitest", "5.4.1"
gem "minitest-reporters"
gem "mocha", "1.1.0", require: false
gem "parallel_tests"
gem "rspec", "~> 3.0.0"
gem "rspec-its", require: false
gem "rspec-wait", require: false

View File

@ -1,8 +1,17 @@
GIT
remote: https://github.com/colszowka/simplecov.git
revision: 83d8031ddde0927f87ef9327200a98583ca18d77
branch: master
specs:
simplecov (0.12.0)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
GEM
remote: https://rubygems.org/
specs:
ansi (1.5.0)
ast (2.3.0)
builder (3.2.2)
byebug (9.0.5)
codecov (0.1.5)
@ -23,9 +32,9 @@ GEM
ruby-progressbar
mocha (1.1.0)
metaclass (~> 0.0.1)
parser (2.3.1.2)
ast (~> 2.2)
powerpack (0.1.1)
parallel (1.9.0)
parallel_tests (2.9.0)
parallel
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@ -33,8 +42,6 @@ GEM
pry-byebug (3.4.0)
byebug (~> 9.0)
pry (~> 0.10)
public_suffix (2.0.2)
rainbow (2.1.0)
rake (10.4.2)
rspec (3.0.0)
rspec-core (~> 3.0.0)
@ -53,23 +60,9 @@ GEM
rspec-support (3.0.4)
rspec-wait (0.0.9)
rspec (>= 3, < 4)
rubocop (0.41.2)
parser (>= 2.3.1.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-cask (0.8.3)
public_suffix (~> 2.0)
rubocop (~> 0.41.1)
ruby-progressbar (1.8.1)
simplecov (0.12.0)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slop (3.6.0)
unicode-display_width (1.1.0)
url (0.3.2)
PLATFORMS
@ -80,13 +73,14 @@ DEPENDENCIES
minitest (= 5.4.1)
minitest-reporters
mocha (= 1.1.0)
parallel_tests
pry
pry-byebug
rake
rspec (~> 3.0.0)
rspec-its
rspec-wait
rubocop-cask (~> 0.8.3)
simplecov (= 0.12.0)!
BUNDLED WITH
1.12.5
1.13.1

View File

@ -1,29 +1,14 @@
require "rake/testtask"
require "rspec/core/rake_task"
require "rubocop/rake_task"
homebrew_repo = `brew --repository`.chomp
$LOAD_PATH.unshift(File.expand_path("#{homebrew_repo}/Library/Homebrew"))
$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
namespace :test do
Rake::TestTask.new(:minitest) do |t|
# TODO: setting the --seed here is an ugly temporary hack, to remain only
# until test-suite glitches are fixed.
ENV["TESTOPTS"] = "--seed=14830" if ENV["TRAVIS"]
t.pattern = "test/**/*_test.rb"
t.libs << "test"
end
RSpec::Core::RakeTask.new(:rspec)
desc "Run tests for minitest and RSpec with coverage"
task :coverage do
ENV["HOMEBREW_TESTS_COVERAGE"] = "1"
Rake::Task[:test].invoke
if ENV["CODECOV_TOKEN"]
namespace :coverage do
desc "Upload coverage to Codecov"
task :upload do
require "simplecov"
require "codecov"
formatter = SimpleCov::Formatter::Codecov.new
@ -32,15 +17,6 @@ namespace :test do
end
end
desc "Run tests for minitest and RSpec"
task test: ["test:minitest", "test:rspec"]
RuboCop::RakeTask.new(:rubocop) do |t|
t.options = ["--force-exclusion"]
end
task default: [:test, :rubocop]
desc "Open a REPL for debugging and experimentation"
task :console do
require "pry"

View File

@ -1,5 +1,12 @@
require "English"
def run_tests(executable, files, args = [])
opts = []
opts << "--serialize-stdout" if ENV["CI"]
system "bundle", "exec", executable, *opts, "--", *args, "--", *files
end
repo_root = Pathname(__FILE__).realpath.parent.parent
repo_root.cd do
ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1"
@ -9,12 +16,26 @@ repo_root.cd do
system "bundle", "install", "--path", "vendor/bundle"
end
test_task = "test"
%w[rspec minitest coverage].each do |subtask|
next unless ARGV.flag?("--#{subtask}")
test_task = "test:#{subtask}"
rspec = ARGV.flag?("--rspec") || !ARGV.flag?("--minitest")
minitest = ARGV.flag?("--minitest") || !ARGV.flag?("--rspec")
ENV["HOMEBREW_TESTS_COVERAGE"] = "1" if ARGV.flag?("--coverage")
if rspec
run_tests "parallel_rspec", Dir["spec/**/*_spec.rb"], %w[
--format progress
--format ParallelTests::RSpec::RuntimeLogger
--out tmp/parallel_runtime_rspec.log
]
end
if minitest
run_tests "parallel_test", Dir["test/**/*_test.rb"]
end
if ENV["CODECOV_TOKEN"]
system "bundle", "exec", "rake", "test:coverage:upload"
end
system "bundle", "exec", "rake", test_task
Homebrew.failed = !$CHILD_STATUS.success?
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,21 +1,23 @@
module Hbc::Cache
module_function
module Hbc
module Cache
module_function
def ensure_cache_exists
return if Hbc.cache.exist?
odebug "Creating Cache at #{Hbc.cache}"
Hbc.cache.mkpath
end
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?
def migrate_legacy_cache
if 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,
.sub(%r{\-((?:(\d|#{DSL::Version::DIVIDER_REGEX})*\-\2*)*[^\-]+)$}x,
'--\1')
renamed_file = Hbc.cache.join(new_name)

View File

@ -2,92 +2,95 @@ 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 to_s
@token
end
def dumpcask
return unless Hbc.respond_to?(:debug)
return unless Hbc.debug
def dumpcask
if Hbc.respond_to?(:debug) && Hbc.debug
odebug "Cask instance dumps in YAML:"
odebug "Cask instance toplevel:", to_yaml
[

View File

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

View File

@ -1,18 +1,26 @@
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")
if !Hbc.caskroom.exist? && repo_caskroom.directory?
ohai "Moving Caskroom from HOMEBREW_REPOSITORY to HOMEBREW_PREFIX"
FileUtils.mv repo_caskroom, Hbc.caskroom
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
end
def ensure_caskroom_exists
unless Hbc.caskroom.exist?
def ensure_caskroom_exists
return if Hbc.caskroom.exist?
ohai "Creating Caskroom at #{Hbc.caskroom}"
if Hbc.caskroom.parent.writable?
Hbc.caskroom.mkpath
else
@ -28,7 +36,7 @@ module Hbc::Caskroom
# 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
system "/usr/bin/sudo", "--", "/usr/sbin/chown", "-R", "--", "#{Utils.current_user}:staff", Hbc.caskroom.parent.to_s
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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