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 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 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). - [ ] 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/bin
/Library/Homebrew/cask/vendor /Library/Homebrew/cask/vendor
/Library/Homebrew/cask/coverage /Library/Homebrew/cask/coverage
/Library/Homebrew/cask/tmp
/Library/Homebrew/test/.bundle /Library/Homebrew/test/.bundle
/Library/Homebrew/test/bin /Library/Homebrew/test/bin
/Library/Homebrew/test/vendor /Library/Homebrew/test/vendor
/Library/Homebrew/test/coverage /Library/Homebrew/test/coverage
/Library/Homebrew/test/fs_leak_log /Library/Homebrew/test/fs_leak_log
/Library/Homebrew/tmp
/Library/LinkedKegs /Library/LinkedKegs
/Library/Locks /Library/Locks
/Library/PinnedKegs /Library/PinnedKegs

View File

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

View File

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

View File

@ -1,21 +1,11 @@
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 30` # `rubocop --auto-gen-config --exclude-limit 100`
# on 2016-09-18 15:15:22 +0100 using RuboCop version 0.41.2. # 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 # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again. # 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 # Offense count: 18
Lint/HandleExceptions: Lint/HandleExceptions:
Exclude: Exclude:
@ -52,11 +42,6 @@ Lint/NestedMethodDefinition:
- 'Homebrew/dev-cmd/bottle.rb' - 'Homebrew/dev-cmd/bottle.rb'
- 'Homebrew/dev-cmd/test-bot.rb' - 'Homebrew/dev-cmd/test-bot.rb'
# Offense count: 2
Lint/NonLocalExitFromIterator:
Exclude:
- 'Homebrew/extend/pathname.rb'
# Offense count: 28 # Offense count: 28
Lint/RescueException: Lint/RescueException:
Exclude: Exclude:
@ -80,12 +65,7 @@ Lint/RescueException:
# Offense count: 1 # Offense count: 1
Lint/ShadowedException: Lint/ShadowedException:
Exclude: Exclude:
- 'Homebrew/brew.rb' - 'Homebrew/utils/fork.rb'
# Offense count: 2
Lint/UselessAssignment:
Exclude:
- 'Homebrew/requirements.rb'
# Offense count: 18 # Offense count: 18
Metrics/BlockNesting: Metrics/BlockNesting:
@ -94,64 +74,17 @@ Metrics/BlockNesting:
# Offense count: 20 # Offense count: 20
# Configuration parameters: CountComments. # Configuration parameters: CountComments.
Metrics/ModuleLength: Metrics/ModuleLength:
Max: 400 Max: 370
# Offense count: 1 # Offense count: 2
# Configuration parameters: CountKeywordArgs. # Configuration parameters: CountKeywordArgs.
Metrics/ParameterLists: Metrics/ParameterLists:
Max: 6 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 # 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: Style/CaseEquality:
Exclude: Exclude:
- 'Homebrew/cleanup.rb'
- 'Homebrew/cmd/search.rb'
- 'Homebrew/compilers.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 # Offense count: 11
Style/ClassVars: Style/ClassVars:
@ -168,24 +101,6 @@ Style/ConstantName:
Exclude: Exclude:
- 'Homebrew/os/mac.rb' - '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 # Offense count: 13
# Configuration parameters: AllowedVariables. # Configuration parameters: AllowedVariables.
Style/GlobalVars: Style/GlobalVars:
@ -193,22 +108,20 @@ Style/GlobalVars:
- 'Homebrew/diagnostic.rb' - 'Homebrew/diagnostic.rb'
- 'Homebrew/utils.rb' - 'Homebrew/utils.rb'
# Offense count: 2 # Offense count: 51
Style/IdenticalConditionalBranches: # Cop supports --auto-correct.
# Configuration parameters: MaxLineLength.
Style/IfUnlessModifier:
Exclude: Exclude:
- 'Homebrew/formula_lock.rb' - 'Taps/**/*'
- 'Homebrew/dev-cmd/audit.rb'
# Offense count: 5 # Offense count: 2
# Configuration parameters: EnforcedStyle, SupportedStyles. # Cop supports --auto-correct.
# SupportedStyles: snake_case, camelCase # Configuration parameters: SupportedStyles, IndentationWidth.
Style/MethodName: # SupportedStyles: special_inside_parentheses, consistent, align_brackets
Exclude: Style/IndentArray:
- 'Homebrew/compat/**/*' EnforcedStyle: special_inside_parentheses
- 'Homebrew/cleanup.rb'
- 'Homebrew/diagnostic.rb'
- 'Homebrew/formula_cellar_checks.rb'
- 'Homebrew/formula_installer.rb'
- 'Homebrew/os/mac/cctools_mach.rb'
# Offense count: 7 # Offense count: 7
# Configuration parameters: EnforcedStyle, SupportedStyles. # Configuration parameters: EnforcedStyle, SupportedStyles.
@ -219,8 +132,6 @@ Style/ModuleFunction:
- 'Homebrew/os/mac.rb' - 'Homebrew/os/mac.rb'
- 'Homebrew/os/mac/xcode.rb' - 'Homebrew/os/mac/xcode.rb'
- 'Homebrew/os/mac/xquartz.rb' - 'Homebrew/os/mac/xquartz.rb'
- 'Homebrew/utils/github.rb'
- 'Homebrew/utils/json.rb'
# Offense count: 8 # Offense count: 8
Style/MultilineBlockChain: Style/MultilineBlockChain:
@ -241,14 +152,6 @@ Style/MutableConstant:
- 'Homebrew/tab.rb' - 'Homebrew/tab.rb'
- 'Homebrew/tap.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 # Offense count: 9
Style/OpMethod: Style/OpMethod:
Exclude: Exclude:
@ -257,37 +160,9 @@ Style/OpMethod:
- 'Homebrew/install_renamed.rb' - 'Homebrew/install_renamed.rb'
- 'Homebrew/options.rb' - 'Homebrew/options.rb'
# Offense count: 4 # Offense count: 2
# 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
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. # Configuration parameters: SupportedStyles.
# SupportedStyles: slashes, percent_r, mixed # SupportedStyles: use_perl_names, use_english_names
Style/RegexpLiteral: Style/SpecialGlobalVars:
Exclude: EnforcedStyle: use_perl_names
- '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'

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,8 @@
require: 'rubocop-cask'
AllCops: AllCops:
TargetRubyVersion: 2.0 TargetRubyVersion: 2.0
Exclude: Exclude:
- '**/.simplecov'
- '**/Casks/**/*' - '**/Casks/**/*'
- 'developer/**/*'
- '**/vendor/**/*' - '**/vendor/**/*'
Metrics/AbcSize: Metrics/AbcSize:
@ -16,10 +14,10 @@ Metrics/ClassLength:
Metrics/CyclomaticComplexity: Metrics/CyclomaticComplexity:
Enabled: false Enabled: false
Metrics/MethodLength: Metrics/LineLength:
Enabled: false Enabled: false
Metrics/PerceivedComplexity: Metrics/MethodLength:
Enabled: false Enabled: false
Metrics/ModuleLength: Metrics/ModuleLength:
@ -29,6 +27,16 @@ Metrics/ModuleLength:
- 'lib/hbc/macos.rb' - 'lib/hbc/macos.rb'
- 'lib/hbc/utils.rb' - 'lib/hbc/utils.rb'
Metrics/PerceivedComplexity:
Enabled: false
Style/AlignHash:
EnforcedHashRocketStyle: table
EnforcedColonStyle: table
Style/BarePercentLiterals:
EnforcedStyle: percent_q
Style/BlockDelimiters: Style/BlockDelimiters:
EnforcedStyle: semantic EnforcedStyle: semantic
FunctionalMethods: FunctionalMethods:
@ -59,6 +67,8 @@ Style/BlockDelimiters:
- chdir - chdir
- context - context
- create - create
- define_method
- define_singleton_method
- each_with_object - each_with_object
- fork - fork
- measure - measure
@ -75,15 +85,47 @@ Style/BlockDelimiters:
- lambda - lambda
- proc - proc
Style/ClassAndModuleChildren: Style/ClassAndModuleChildren:
EnforcedStyle: compact EnforcedStyle: nested
Style/PredicateName: Style/Documentation:
NameWhitelist: is_32_bit?, is_64_bit? 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: Style/RaiseArgs:
EnforcedStyle: exploded EnforcedStyle: exploded
Style/RegexpLiteral:
EnforcedStyle: percent_r
Style/StringLiterals: Style/StringLiterals:
EnforcedStyle: double_quotes 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 gem "pry-byebug", platforms: :mri
end end
group :development do
gem "rubocop-cask", "~> 0.8.3"
end
group :test do 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 "codecov", require: false
gem "minitest", "5.4.1" gem "minitest", "5.4.1"
gem "minitest-reporters" gem "minitest-reporters"
gem "mocha", "1.1.0", require: false gem "mocha", "1.1.0", require: false
gem "parallel_tests"
gem "rspec", "~> 3.0.0" gem "rspec", "~> 3.0.0"
gem "rspec-its", require: false gem "rspec-its", require: false
gem "rspec-wait", 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 GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
ansi (1.5.0) ansi (1.5.0)
ast (2.3.0)
builder (3.2.2) builder (3.2.2)
byebug (9.0.5) byebug (9.0.5)
codecov (0.1.5) codecov (0.1.5)
@ -23,9 +32,9 @@ GEM
ruby-progressbar ruby-progressbar
mocha (1.1.0) mocha (1.1.0)
metaclass (~> 0.0.1) metaclass (~> 0.0.1)
parser (2.3.1.2) parallel (1.9.0)
ast (~> 2.2) parallel_tests (2.9.0)
powerpack (0.1.1) parallel
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
@ -33,8 +42,6 @@ GEM
pry-byebug (3.4.0) pry-byebug (3.4.0)
byebug (~> 9.0) byebug (~> 9.0)
pry (~> 0.10) pry (~> 0.10)
public_suffix (2.0.2)
rainbow (2.1.0)
rake (10.4.2) rake (10.4.2)
rspec (3.0.0) rspec (3.0.0)
rspec-core (~> 3.0.0) rspec-core (~> 3.0.0)
@ -53,23 +60,9 @@ GEM
rspec-support (3.0.4) rspec-support (3.0.4)
rspec-wait (0.0.9) rspec-wait (0.0.9)
rspec (>= 3, < 4) 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) 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) simplecov-html (0.10.0)
slop (3.6.0) slop (3.6.0)
unicode-display_width (1.1.0)
url (0.3.2) url (0.3.2)
PLATFORMS PLATFORMS
@ -80,13 +73,14 @@ DEPENDENCIES
minitest (= 5.4.1) minitest (= 5.4.1)
minitest-reporters minitest-reporters
mocha (= 1.1.0) mocha (= 1.1.0)
parallel_tests
pry pry
pry-byebug pry-byebug
rake rake
rspec (~> 3.0.0) rspec (~> 3.0.0)
rspec-its rspec-its
rspec-wait rspec-wait
rubocop-cask (~> 0.8.3) simplecov (= 0.12.0)!
BUNDLED WITH BUNDLED WITH
1.12.5 1.13.1

View File

@ -1,29 +1,14 @@
require "rake/testtask" require "rake/testtask"
require "rspec/core/rake_task" require "rspec/core/rake_task"
require "rubocop/rake_task"
homebrew_repo = `brew --repository`.chomp homebrew_repo = `brew --repository`.chomp
$LOAD_PATH.unshift(File.expand_path("#{homebrew_repo}/Library/Homebrew")) $LOAD_PATH.unshift(File.expand_path("#{homebrew_repo}/Library/Homebrew"))
$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__)) $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
namespace :test do namespace :test do
Rake::TestTask.new(:minitest) do |t| namespace :coverage do
# TODO: setting the --seed here is an ugly temporary hack, to remain only desc "Upload coverage to Codecov"
# until test-suite glitches are fixed. task :upload do
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"]
require "simplecov" require "simplecov"
require "codecov" require "codecov"
formatter = SimpleCov::Formatter::Codecov.new formatter = SimpleCov::Formatter::Codecov.new
@ -32,15 +17,6 @@ namespace :test do
end end
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" desc "Open a REPL for debugging and experimentation"
task :console do task :console do
require "pry" require "pry"

View File

@ -1,5 +1,12 @@
require "English" 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 = Pathname(__FILE__).realpath.parent.parent
repo_root.cd do repo_root.cd do
ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1" ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1"
@ -9,12 +16,26 @@ repo_root.cd do
system "bundle", "install", "--path", "vendor/bundle" system "bundle", "install", "--path", "vendor/bundle"
end end
test_task = "test" rspec = ARGV.flag?("--rspec") || !ARGV.flag?("--minitest")
%w[rspec minitest coverage].each do |subtask| minitest = ARGV.flag?("--minitest") || !ARGV.flag?("--rspec")
next unless ARGV.flag?("--#{subtask}")
test_task = "test:#{subtask}" 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 end
system "bundle", "exec", "rake", test_task
Homebrew.failed = !$CHILD_STATUS.success? Homebrew.failed = !$CHILD_STATUS.success?
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,26 @@
module Hbc::Caskroom module Hbc
module_function 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" 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
end
def ensure_caskroom_exists def ensure_caskroom_exists
unless Hbc.caskroom.exist? return if Hbc.caskroom.exist?
ohai "Creating Caskroom at #{Hbc.caskroom}" ohai "Creating Caskroom at #{Hbc.caskroom}"
if Hbc.caskroom.parent.writable? if Hbc.caskroom.parent.writable?
Hbc.caskroom.mkpath Hbc.caskroom.mkpath
else else
@ -28,7 +36,7 @@ module Hbc::Caskroom
# sudo in system is rude. # sudo in system is rude.
system "/usr/bin/sudo", "--", "/bin/mkdir", "-p", "--", Hbc.caskroom system "/usr/bin/sudo", "--", "/bin/mkdir", "-p", "--", Hbc.caskroom
unless Hbc.caskroom.parent == toplevel_dir 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 end
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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