Merge pull request #16399 from jawshooah/casks-as-instances

Load casks as instances of Hbc::Cask rather than subclasses
This commit is contained in:
Josh Hagins 2016-01-08 07:31:39 -05:00
commit b390b68a97
27 changed files with 425 additions and 554 deletions

View File

@ -3,17 +3,17 @@ cask 'ax88179' do
sha256 '50b9754649cb9f67a1a911ff07e49c3fc5ac931c49f28dc3235adb95d31e3323'
module Utils
def self.basename
"AX88179_178A_Macintosh_10.6_to_10.11_Driver_Installer_v#{Module.nesting.last.version}"
def self.basename(version)
"AX88179_178A_Macintosh_10.6_to_10.11_Driver_Installer_v#{version}"
end
end
url "http://www.asix.com.tw/FrootAttach/driver/#{Utils.basename}.zip"
url "http://www.asix.com.tw/FrootAttach/driver/#{Utils.basename(version)}.zip"
name 'AX88179'
homepage 'http://www.asix.com.tw/products.php?op=pItemdetail&PItemID=131;71;112&PLine=71'
license :gratis
container :nested => "#{Utils.basename}/AX88179_178A.dmg"
container :nested => "#{Utils.basename(version)}/AX88179_178A.dmg"
pkg "AX88179_178A_v#{version[0..-10]}.pkg"

View File

@ -3,17 +3,17 @@ cask 'ax88772' do
sha256 'cc336a77ed35ab6b9972f76fb2a4c77650072c2844fd1632a1875b035a311c6f'
module Utils
def self.basename
"AX88772C_772B_772A_760_772_Macintosh_10.5_to_10.11_Driver_Installer_v#{Module.nesting.last.version}"
def self.basename(version)
"AX88772C_772B_772A_760_772_Macintosh_10.5_to_10.11_Driver_Installer_v#{version}"
end
end
url "http://www.asix.com.tw/FrootAttach/driver/#{Utils.basename}.zip"
url "http://www.asix.com.tw/FrootAttach/driver/#{Utils.basename(version)}.zip"
name 'AX88772'
homepage 'http://www.asix.com.tw/products.php?op=pItemdetail&PItemID=86;71;101&PLine=71'
license :unknown # TODO: change license and remove this comment; ':unknown' is a machine-generated placeholder
container :nested => "#{Utils.basename}/AX88772.dmg"
container :nested => "#{Utils.basename(version)}/AX88772.dmg"
pkg "AX88772_v#{version.major_minor_patch}.pkg"

View File

@ -3,17 +3,17 @@ cask 'mcs783x' do
sha256 'd86bdf6107cec7d3990f6967a5be782f7945cb722f22789cb04051514ba87a10'
module Utils
def self.basename
"MCS783x_Mac_OSX_10.5_to_10.10_driver_v#{Module.nesting.last.version}"
def self.basename(version)
"MCS783x_Mac_OSX_10.5_to_10.10_driver_v#{version}"
end
end
url "http://www.asix.com.tw/FrootAttach/driver/#{Utils.basename}.zip"
url "http://www.asix.com.tw/FrootAttach/driver/#{Utils.basename(version)}.zip"
name 'ASIX MCS7830/7832 USB to Ethernet Controller Driver'
homepage 'http://www.asix.com.tw/products.php?op=pItemdetail&PItemID=108;71;101&PLine=71'
license :unknown # TODO: change license and remove this comment; ':unknown' is a machine-generated placeholder
container :nested => "#{Utils.basename}/MCS7830_v#{version.major_minor_patch}.dmg"
container :nested => "#{Utils.basename(version)}/MCS7830_v#{version.major_minor_patch}.dmg"
pkg "MCS7830 v#{version.major_minor_patch}.pkg"

View File

@ -37,7 +37,7 @@ class Hbc::Audit
add_error "a #{sym} stanza is required" unless cask.send(sym)
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.full_name.empty?
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,v| [:uninstall, :zap, :nested_container].include?(k)}

View File

@ -1,17 +1,20 @@
require 'forwardable'
require 'hbc/dsl'
class Hbc::Cask
include Hbc::DSL
def self.token
# todo removeme: prepending KlassPrefix is transitional as we move away from representing Casks as classes
self.name.sub(/^KlassPrefix/,'').gsub(/([a-zA-Z\d])([A-Z])/,'\1-\2').gsub(/([a-zA-Z\d])([A-Z])/,'\1-\2').downcase
end
extend Forwardable
attr_reader :token, :sourcefile_path
def initialize(sourcefile_path=nil)
def initialize(token, sourcefile_path: nil, dsl: nil, &block)
@token = token
@sourcefile_path = sourcefile_path
@token = self.class.token
@dsl = dsl || Hbc::DSL.new(@token)
@dsl.instance_eval(&block) if block_given?
end
Hbc::DSL::DSL_METHODS.each do |method_name|
define_method(method_name) { @dsl.send(method_name) }
end
METADATA_SUBDIR = '.metadata'
@ -74,7 +77,7 @@ class Hbc::Cask
odebug "Cask instance dumps in YAML:"
odebug "Cask instance toplevel:", self.to_yaml
[
:full_name,
:name,
:homepage,
:url,
:appcast,
@ -90,9 +93,7 @@ class Hbc::Cask
:accessibility_access,
:auto_updates
].each do |method|
printable_method = method.to_s
printable_method = "name" if printable_method == "full_name"
odebug "Cask instance method '#{printable_method}':", self.send(method).to_yaml
odebug "Cask instance method '#{method}':", self.send(method).to_yaml
end
end
end

View File

@ -115,14 +115,15 @@ class Hbc::CLI
command = lookup_command(command_string)
run_command(command, *rest)
rescue Hbc::CaskError, Hbc::CaskSha256MismatchError => e
onoe e
$stderr.puts Hbc::Utils.error_message_with_suggestions if e.is_a?(Hbc::CaskHeaderParseError)
$stderr.puts e.backtrace if Hbc.debug
msg = e.message
msg << e.backtrace.join("\n") if Hbc.debug
onoe msg
exit 1
rescue StandardError, ScriptError, NoMemoryError => e
onoe e
$stderr.puts Hbc::Utils.error_message_with_suggestions
$stderr.puts e.backtrace
msg = e.message
msg << Hbc::Utils.error_message_with_suggestions
msg << e.backtrace.join("\n")
onoe msg
exit 1
end

View File

@ -8,7 +8,7 @@ class Hbc::CLI::Base
end
def self.cask_tokens_from(args)
args.reject { |a| a.chars.first == '-' }
args.reject { |a| a.empty? || a.chars.first == '-' }
end
def self.help

View File

@ -23,7 +23,7 @@ class Hbc::CLI::Info < Hbc::CLI::Base
# todo completely reformat the info report
<<-PURPOSE
#{cask}: #{cask.version}
#{formatted_full_name(cask) }
#{formatted_name(cask) }
#{cask.homepage or 'No Homepage'}
#{installation}
#{github_info(cask) or 'No GitHub URL'}
@ -31,9 +31,9 @@ class Hbc::CLI::Info < Hbc::CLI::Base
PURPOSE
end
def self.formatted_full_name(cask)
def self.formatted_name(cask)
# todo transitional: make name a required stanza, and then stop substituting cask.token here
cask.full_name.empty? ? cask.token : cask.full_name.join(', ')
cask.name.empty? ? cask.token : cask.name.join(', ')
end
def self.github_info(cask)
@ -58,7 +58,7 @@ PURPOSE
def self.artifact_info(cask)
retval = ''
Hbc::DSL::ClassMethods.ordinary_artifact_types.each do |type|
Hbc::DSL::ORDINARY_ARTIFACT_TYPES.each do |type|
if cask.artifacts[type].length > 0
retval = "#{Tty.blue.bold}==>#{Tty.reset.bold} Contents#{Tty.reset}\n" unless retval.length > 0
cask.artifacts[type].each do |artifact|

View File

@ -71,7 +71,6 @@ class Hbc::CLI::InternalStanza < Hbc::CLI::InternalUseBase
def self.print_stanzas(stanza, format=nil, table=nil, quiet=nil, *cask_tokens)
count = 0
stanza = :full_name if stanza == :name
if ARTIFACTS.include?(stanza)
artifact_name = stanza
stanza = :artifacts

View File

@ -1,6 +1,6 @@
require 'set'
module Hbc::DSL; end
class Hbc::DSL; end
require 'hbc/dsl/appcast'
require 'hbc/dsl/base'
@ -16,312 +16,283 @@ require 'hbc/dsl/uninstall_postflight'
require 'hbc/dsl/uninstall_preflight'
require 'hbc/dsl/version'
module Hbc::DSL
def self.included(base)
base.extend(ClassMethods)
class Hbc::DSL
ORDINARY_ARTIFACT_TYPES = [
:app,
:artifact,
:audio_unit_plugin,
:binary,
:colorpicker,
:font,
:input_method,
:internet_plugin,
:pkg,
:prefpane,
:qlplugin,
:screen_saver,
:service,
:stage_only,
:suite,
:vst_plugin
]
ACTIVATABLE_ARTIFACT_TYPES = [:installer, *ORDINARY_ARTIFACT_TYPES] - [:stage_only]
SPECIAL_ARTIFACT_TYPES = [
:uninstall,
:zap,
]
ARTIFACT_BLOCK_TYPES = [
:preflight,
:postflight,
:uninstall_preflight,
:uninstall_postflight,
]
DSL_METHODS = Set.new [
:accessibility_access,
:appcast,
:artifacts,
:auto_updates,
:caskroom_path,
:caveats,
:conflicts_with,
:container,
:depends_on,
:gpg,
:homepage,
:license,
:name,
:sha256,
:staged_path,
:url,
:version,
*ORDINARY_ARTIFACT_TYPES,
*ACTIVATABLE_ARTIFACT_TYPES,
*SPECIAL_ARTIFACT_TYPES,
*ARTIFACT_BLOCK_TYPES
]
attr_reader :token
def initialize(token)
@token = token
end
def full_name; self.class.full_name; end
def name(*args)
@name ||= []
return @name if args.empty?
@name.concat(args.flatten)
end
def homepage; self.class.homepage; end
def homepage(homepage=nil)
if @homepage and !homepage.nil?
raise Hbc::CaskInvalidError.new(self.token, "'homepage' stanza may only appear once")
end
@homepage ||= homepage
end
def url; self.class.url; end
def url(*args)
return @url if args.empty?
if @url and !args.empty?
raise Hbc::CaskInvalidError.new(self.token, "'url' stanza may only appear once")
end
@url ||= begin
Hbc::URL.new(*args)
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, "'url' stanza failed with: #{e}")
end
end
def appcast; self.class.appcast; end
def appcast(*args)
return @appcast if args.empty?
if @appcast and !args.empty?
raise Hbc::CaskInvalidError.new(self.token, "'appcast' stanza may only appear once")
end
@appcast ||= begin
Hbc::DSL::Appcast.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
end
def gpg; self.class.gpg; end
def gpg(*args)
return @gpg if args.empty?
if @gpg and !args.empty?
raise Hbc::CaskInvalidError.new(self.token, "'gpg' stanza may only appear once")
end
@gpg ||= begin
Hbc::DSL::Gpg.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
end
def version; self.class.version; end
def container(*args)
return @container if args.empty?
if @container and !args.empty?
# todo: remove this constraint, and instead merge multiple container stanzas
raise Hbc::CaskInvalidError.new(self.token, "'container' stanza may only appear once")
end
@container ||= begin
Hbc::DSL::Container.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
# todo: remove this backward-compatibility section after removing nested_container
if @container and @container.nested
artifacts[:nested_container] << @container.nested
end
@container
end
def license; self.class.license; end
SYMBOLIC_VERSIONS = Set.new [
:latest,
]
def depends_on; self.class.depends_on; end
def version(arg=nil)
if arg.nil?
return @version
elsif @version
raise Hbc::CaskInvalidError.new(self.token, "'version' stanza may only appear once")
elsif !arg.is_a?(String) and !SYMBOLIC_VERSIONS.include?(arg)
raise Hbc::CaskInvalidError.new(self.token, "invalid 'version' value: '#{arg.inspect}'")
end
@version ||= Hbc::DSL::Version.new(arg)
end
def conflicts_with; self.class.conflicts_with; end
SYMBOLIC_SHA256S = Set.new [
:no_check,
]
def container; self.class.container; end
def sha256(arg=nil)
if arg.nil?
return @sha256
elsif @sha256
raise Hbc::CaskInvalidError.new(self.token, "'sha256' stanza may only appear once")
elsif !arg.is_a?(String) and !SYMBOLIC_SHA256S.include?(arg)
raise Hbc::CaskInvalidError.new(self.token, "invalid 'sha256' value: '#{arg.inspect}'")
end
@sha256 ||= arg
end
def sha256; self.class.sha256; end
def license(arg=nil)
return @license if arg.nil?
if @license and !arg.nil?
raise Hbc::CaskInvalidError.new(self.token, "'license' stanza may only appear once")
end
@license ||= begin
Hbc::DSL::License.new(arg) unless arg.nil?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
end
def artifacts; self.class.artifacts; end
# depends_on uses a load method so that multiple stanzas can be merged
def depends_on(*args)
return @depends_on if args.empty?
@depends_on ||= Hbc::DSL::DependsOn.new()
begin
@depends_on.load(*args) unless args.empty?
rescue RuntimeError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
@depends_on
end
def caskroom_path; self.class.caskroom_path; end
def conflicts_with(*args)
if @conflicts_with and !args.empty?
# todo: remove this constraint, and instead merge multiple conflicts_with stanzas
raise Hbc::CaskInvalidError.new(self.token, "'conflicts_with' stanza may only appear once")
end
return @conflicts_with if args.empty?
@conflicts_with ||= begin
Hbc::DSL::ConflictsWith.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
end
def staged_path; self.class.staged_path; end
def artifacts
@artifacts ||= Hash.new { |hash, key| hash[key] = Set.new }
end
def caveats; self.class.caveats; end
def caskroom_path
@caskroom_path ||= Hbc.caskroom.join(self.token)
end
def accessibility_access; self.class.accessibility_access; end
def staged_path
return @staged_path if @staged_path
cask_version = self.version || :unknown
@staged_path = self.caskroom_path.join(cask_version.to_s)
end
def auto_updates; self.class.auto_updates; end
def caveats(*string, &block)
@caveats ||= []
if block_given?
@caveats << Hbc::Caveats.new(block)
elsif string.any?
@caveats << string.map{ |s| s.to_s.sub(/[\r\n \t]*\Z/, "\n\n") }
else
# accessor
@caveats
end
end
module ClassMethods
def accessibility_access(accessibility_access=nil)
if @accessibility_access and !accessibility_access.nil?
raise Hbc::CaskInvalidError.new(self.token, "'accessibility_access' stanza may only appear once")
end
@accessibility_access ||= accessibility_access
end
# A quite fragile shim to allow "full_name" be exposed as simply "name"
# in the DSL. We detect the difference with the already-existing "name"
# method by arity, and use "full_name" exclusively in backend code.
def name(*args)
if args.empty?
super
else
self.full_name(args)
def auto_updates(auto_updates=nil)
if @auto_updates and !auto_updates.nil?
raise Hbc::CaskInvalidError.new(self.token, "'auto_updates' stanza may only appear once")
end
@auto_updates ||= auto_updates
end
ORDINARY_ARTIFACT_TYPES.each do |type|
define_method(type) do |*args|
if type == :stage_only and args != [true]
raise Hbc::CaskInvalidError.new(self.token, "'stage_only' takes a single argument: true")
end
artifacts[type] << args
if artifacts.key?(:stage_only) and
artifacts.keys.count > 1 and
! (artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).empty?
raise Hbc::CaskInvalidError.new(self.token, "'stage_only' must be the only activatable artifact")
end
end
end
def full_name(_full_name=nil)
@full_name ||= []
if _full_name
@full_name.concat(Array(*_full_name))
end
@full_name
def installer(*args)
if args.empty?
return artifacts[:installer]
end
def homepage(homepage=nil)
if @homepage and !homepage.nil?
raise Hbc::CaskInvalidError.new(self.token, "'homepage' stanza may only appear once")
end
@homepage ||= homepage
begin
artifacts[:installer] << Hbc::DSL::Installer.new(*args)
raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only)
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
end
def url(*args)
return @url if args.empty?
if @url and !args.empty?
raise Hbc::CaskInvalidError.new(self.token, "'url' stanza may only appear once")
end
@url ||= begin
Hbc::URL.new(*args)
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, "'url' stanza failed with: #{e}")
end
SPECIAL_ARTIFACT_TYPES.each do |type|
define_method(type) do |*args|
artifacts[type].merge(args)
end
end
def appcast(*args)
return @appcast if args.empty?
if @appcast and !args.empty?
raise Hbc::CaskInvalidError.new(self.token, "'appcast' stanza may only appear once")
end
@appcast ||= begin
Hbc::DSL::Appcast.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
ARTIFACT_BLOCK_TYPES.each do |type|
define_method(type) do |&block|
artifacts[type] << block
end
end
def gpg(*args)
return @gpg if args.empty?
if @gpg and !args.empty?
raise Hbc::CaskInvalidError.new(self.token, "'gpg' stanza may only appear once")
end
@gpg ||= begin
Hbc::DSL::Gpg.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
end
def container(*args)
return @container if args.empty?
if @container and !args.empty?
# todo: remove this constraint, and instead merge multiple container stanzas
raise Hbc::CaskInvalidError.new(self.token, "'container' stanza may only appear once")
end
@container ||= begin
Hbc::DSL::Container.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
# todo: remove this backward-compatibility section after removing nested_container
if @container and @container.nested
artifacts[:nested_container] << @container.nested
end
@container
end
SYMBOLIC_VERSIONS = Set.new [
:latest,
]
def version(arg=nil)
if arg.nil?
return @version
elsif @version
raise Hbc::CaskInvalidError.new(self.token, "'version' stanza may only appear once")
elsif !arg.is_a?(String) and !SYMBOLIC_VERSIONS.include?(arg)
raise Hbc::CaskInvalidError.new(self.token, "invalid 'version' value: '#{arg.inspect}'")
end
@version ||= Hbc::DSL::Version.new(arg)
end
SYMBOLIC_SHA256S = Set.new [
:no_check,
]
def sha256(arg=nil)
if arg.nil?
return @sha256
elsif @sha256
raise Hbc::CaskInvalidError.new(self.token, "'sha256' stanza may only appear once")
elsif !arg.is_a?(String) and !SYMBOLIC_SHA256S.include?(arg)
raise Hbc::CaskInvalidError.new(self.token, "invalid 'sha256' value: '#{arg.inspect}'")
end
@sha256 ||= arg
end
def license(arg=nil)
return @license if arg.nil?
if @license and !arg.nil?
raise Hbc::CaskInvalidError.new(self.token, "'license' stanza may only appear once")
end
@license ||= begin
Hbc::DSL::License.new(arg) unless arg.nil?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
end
# depends_on uses a load method so that multiple stanzas can be merged
def depends_on(*args)
return @depends_on if args.empty?
@depends_on ||= Hbc::DSL::DependsOn.new()
begin
@depends_on.load(*args) unless args.empty?
rescue RuntimeError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
@depends_on
end
def conflicts_with(*args)
if @conflicts_with and !args.empty?
# todo: remove this constraint, and instead merge multiple conflicts_with stanzas
raise Hbc::CaskInvalidError.new(self.token, "'conflicts_with' stanza may only appear once")
end
return @conflicts_with if args.empty?
@conflicts_with ||= begin
Hbc::DSL::ConflictsWith.new(*args) unless args.empty?
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
end
def artifacts
@artifacts ||= Hash.new { |hash, key| hash[key] = Set.new }
end
def caskroom_path
@caskroom_path ||= Hbc.caskroom.join(self.token)
end
def staged_path
return @staged_path if @staged_path
cask_version = self.version || :unknown
@staged_path = self.caskroom_path.join(cask_version.to_s)
end
def caveats(*string, &block)
@caveats ||= []
if block_given?
@caveats << Hbc::Caveats.new(block)
elsif string.any?
@caveats << string.map{ |s| s.to_s.sub(/[\r\n \t]*\Z/, "\n\n") }
else
# accessor
@caveats
end
end
def accessibility_access(accessibility_access=nil)
if @accessibility_access and !accessibility_access.nil?
raise Hbc::CaskInvalidError.new(self.token, "'accessibility_access' stanza may only appear once")
end
@accessibility_access ||= accessibility_access
end
def auto_updates(auto_updates=nil)
if @auto_updates and !auto_updates.nil?
raise Hbc::CaskInvalidError.new(self.token, "'auto_updates' stanza may only appear once")
end
@auto_updates ||= auto_updates
end
def self.ordinary_artifact_types
@@ordinary_artifact_types ||= [
:app,
:suite,
:artifact,
:prefpane,
:qlplugin,
:font,
:service,
:colorpicker,
:binary,
:input_method,
:internet_plugin,
:audio_unit_plugin,
:vst_plugin,
:screen_saver,
:pkg,
:stage_only,
]
end
def self.activatable_artifact_types
@@activatable_artifact_types ||= [:installer, *ordinary_artifact_types] - [:stage_only]
end
ordinary_artifact_types.each do |type|
define_method(type) do |*args|
if type == :stage_only and args != [true]
raise Hbc::CaskInvalidError.new(self.token, "'stage_only' takes a single argument: true")
end
artifacts[type] << args
if artifacts.key?(:stage_only) and
artifacts.keys.count > 1 and
! (artifacts.keys & Hbc::DSL::ClassMethods.activatable_artifact_types).empty?
raise Hbc::CaskInvalidError.new(self.token, "'stage_only' must be the only activatable artifact")
end
end
end
def installer(*args)
if args.empty?
return artifacts[:installer]
end
begin
artifacts[:installer] << Hbc::DSL::Installer.new(*args)
raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only)
rescue StandardError => e
raise Hbc::CaskInvalidError.new(self.token, e)
end
end
SPECIAL_ARTIFACT_TYPES = [
:uninstall,
:zap,
]
SPECIAL_ARTIFACT_TYPES.each do |type|
define_method(type) do |*args|
artifacts[type].merge(args)
end
end
ARTIFACT_BLOCK_TYPES = [
:preflight,
:postflight,
:uninstall_preflight,
:uninstall_postflight,
]
ARTIFACT_BLOCK_TYPES.each do |type|
define_method(type) do |&block|
artifacts[type] << block
end
end
def method_missing(method, *args)
Hbc::Utils.method_missing_message(method, self.token)
return nil
end
def method_missing(method, *args)
Hbc::Utils.method_missing_message(method, self.token)
return nil
end
end

View File

@ -126,7 +126,10 @@ class Hbc::CaskInvalidError < Hbc::CaskError
end
end
class Hbc::CaskHeaderParseError < Hbc::CaskInvalidError
class Hbc::CaskTokenDoesNotMatchError < Hbc::CaskInvalidError
def initialize(token, header_token)
super(token, "Bad header line: '#{header_token}' does not match file name")
end
end
class Hbc::CaskSha256MissingError < ArgumentError

View File

@ -5,7 +5,8 @@ module Hbc::Scopes
module ClassMethods
def all
all_tokens.map { |c| self.load c }
@all_casks ||= {}
all_tokens.map { |t| @all_casks[t] ||= self.load(t) }
end
def all_tapped_cask_dirs
@ -35,12 +36,13 @@ module Hbc::Scopes
c = c.split('/').last 4
# => ["caskroom", "example-tap", "Casks", "example"]
c.delete_at(-2)
# => ["example-tap", "example"]
# => ["caskroom", "example-tap", "example"]
c = c.join '/'
}
end
def installed
@installed ||= {}
installed_cask_dirs = Pathname.glob(caskroom.join("*"))
# Hbc.load has some DWIM which is slow. Optimize here
# by spoon-feeding Hbc.load fully-qualified paths.
@ -52,9 +54,9 @@ module Hbc::Scopes
tap_dir.join("#{cask_token}.rb").exist?
end
if path_to_cask
Hbc.load(path_to_cask.join("#{cask_token}.rb"))
@installed[cask_token] ||= Hbc.load(path_to_cask.join("#{cask_token}.rb"))
else
Hbc.load(cask_token)
@installed[cask_token] ||= Hbc.load(cask_token)
end
end
end

View File

@ -20,76 +20,48 @@ class Hbc::Source::PathBase
raise Hbc::CaskError.new "File '#{path}' does not exist" unless path.exist?
raise Hbc::CaskError.new "File '#{path}' is not readable" unless path.readable?
raise Hbc::CaskError.new "File '#{path}' is not a plain file" unless path.file?
begin
# transitional hack: convert first lines of the new form
#
# cask 'google-chrome' do
#
# to the old form
#
# class GoogleChrome < Hbc (class GoogleChrome < Cask)
#
# limitation: does not support Ruby extended quoting such as %Q{}
#
# todo: in the future, this can be pared down to an "eval"
# read Cask
cask_contents = File.open(path, 'rb') do |handle|
contents = handle.read
if defined?(Encoding)
contents.force_encoding('UTF-8')
else
contents
end
end
# munge text
cask_contents.sub!(%r{\A(\s*\#[^\n]*\n)+}, '');
if %r{\A\s*(test_)?cask\s+(?::v[\d_]+(test)?\s+=>\s+)?([\'\"])(\S+?)\3(?:\s*,\s*|\s+)do\s*\n}.match(cask_contents)
is_test = $1 || $2
header_token = $4
superclass_name = is_test ? 'Hbc::TestCask' : 'Hbc::Cask'
cask_contents.sub!(%r{\A[^\n]+\n}, "class #{cask_class_name} < #{superclass_name}\n")
if header_token != cask_token
raise Hbc::CaskInvalidError.new(cask_token, "Bad header line: '#{header_token}' does not match file name")
end
else
raise Hbc::CaskHeaderParseError.new(cask_token, "Bad header line: parse failed")
end
# simulate "require"
begin
Object.const_get(cask_class_name)
rescue NameError
eval(cask_contents, TOPLEVEL_BINDING)
end
rescue Hbc::CaskError, StandardError, ScriptError => e
# bug: e.message.concat doesn't work with Hbc::CaskError exceptions
e.message.concat(" while loading '#{path}'")
raise e
end
begin
Object.const_get(cask_class_name).new(path)
rescue Hbc::CaskError, StandardError, ScriptError => e
# bug: e.message.concat doesn't work with Hbc::CaskError exceptions
e.message.concat(" while instantiating '#{cask_class_name}' from '#{path}'")
raise e
end
end
def cask_token
path.basename.to_s.sub(/\.rb/, '')
end
def cask_class_name
# todo removeme: prepending KlassPrefix is transitional as we move away from representing Casks as classes
'KlassPrefix'.concat cask_token.split('-').map(&:capitalize).join
load_cask
end
def to_s
# stringify to fully-resolved location
path.to_s
end
private
def load_cask
instance_eval(cask_contents, __FILE__, __LINE__)
rescue Hbc::CaskError, StandardError, ScriptError => e
# bug: e.message.concat doesn't work with Hbc::CaskError exceptions
raise e, e.message.concat(" while loading '#{path}'")
end
def cask_contents
File.open(path, 'rb') do |handle|
contents = handle.read
if defined?(Encoding)
contents.force_encoding('UTF-8')
else
contents
end
end
end
def cask(header_token, &block)
build_cask(Hbc::Cask, header_token, &block)
end
def test_cask(header_token, &block)
build_cask(Hbc::TestCask, header_token, &block)
end
def build_cask(cask_class, header_token, &block)
raise Hbc::CaskTokenDoesNotMatchError.new(cask_token, header_token) unless cask_token == header_token
cask_class.new(cask_token, sourcefile_path: path, &block)
end
def cask_token
path.basename.to_s.sub(/\.rb/, '')
end
end

View File

@ -1,15 +1,4 @@
class Hbc::WithoutSource < Hbc::Cask
def initialize(sourcefile_path=nil)
@sourcefile_path = sourcefile_path
@token = sourcefile_path
end
# Override from `Hbc::DSL` because `@token` is set to the constructor argument
# instead of `self.class.token` as in `Hbc::Cask`.
def caskroom_path
Hbc.caskroom.join(token)
end
# Override from `Hbc::DSL` because we don't have a cask source file to work
# with, so we don't know the cask's `version`.
def staged_path

View File

@ -3,7 +3,7 @@ require 'spec_helper'
describe Hbc::Audit do
include AuditMatchers
let(:cask) { Hbc::Cask.new }
let(:cask) { instance_double(Hbc::Cask) }
let(:download) { false }
let(:audit) { Hbc::Audit.new(cask, download) }
@ -125,7 +125,8 @@ describe Hbc::Audit do
end
describe "audit of downloads" do
let(:cask) { Hbc::Cask.new }
let(:cask_token) { 'with-binary' }
let(:cask) { Hbc.load(cask_token) }
let(:download) { instance_double(Hbc::Download) }
let(:verify) { class_double(Hbc::Verify).as_stubbed_const }
let(:error_msg) { "Download Failed" }

View File

@ -15,48 +15,42 @@ describe Hbc::CLI do
])
end
describe ".process" do
context ".process" do
let(:noop_command) { double('CLI::Noop') }
before {
allow(Hbc::CLI).to receive(:lookup_command) { noop_command }
allow(noop_command).to receive(:run)
}
it "respects the env variable when choosing what appdir to create" do
EnvHelper.with_env_var('HOMEBREW_CASK_OPTS', "--appdir=/custom/appdir") do
allow(Hbc).to receive(:init) {
expect(Hbc.appdir.to_s).to eq('/custom/appdir')
}
Hbc::CLI.process('noop')
end
before do
allow(Hbc).to receive(:init)
allow(described_class).to receive(:lookup_command).with('noop').and_return(noop_command)
allow(noop_command).to receive(:run)
end
# todo: merely invoking init causes an attempt to create the caskroom directory
#
# it "respects the ENV variable when choosing a non-default Caskroom location" do
# EnvHelper.with_env_var 'HOMEBREW_CASK_OPTS', "--caskroom=/custom/caskdir" do
# allow(Hbc).to receive(:init) {
# expect(Hbc.caskroom.to_s).to eq('/custom/caskdir')
# }
# Hbc::CLI.process('noop')
# end
# end
it "exits with a status of 1 when something goes wrong" do
Hbc::CLI.expects(:exit).with(1)
Hbc::CLI.expects(:lookup_command).raises(Hbc::CaskError)
allow(Hbc).to receive(:init) {
shutup {
Hbc::CLI.process('noop')
}
}
around do |example|
shutup { example.run }
end
it "passes `--version` along to the subcommand" do
expect(Hbc::CLI).to receive(:run_command).with(noop_command, '--version')
shutup {
Hbc::CLI.process(['noop', '--version'])
}
expect(described_class).to receive(:run_command).with(noop_command, '--version')
described_class.process(%w[noop --version])
end
it "respects the env variable when choosing what appdir to create" do
EnvHelper.with_env_var('HOMEBREW_CASK_OPTS', "--appdir=/custom/appdir") do
expect(Hbc).to receive(:appdir=).with(Pathname('/custom/appdir'))
described_class.process('noop')
end
end
it "respects the env variable when choosing a non-default Caskroom location" do
EnvHelper.with_env_var 'HOMEBREW_CASK_OPTS', "--caskroom=/custom/caskdir" do
expect(Hbc).to receive(:caskroom=).with(Pathname('/custom/caskdir'))
described_class.process('noop')
end
end
it "exits with a status of 1 when something goes wrong" do
allow(described_class).to receive(:lookup_command).and_raise(Hbc::CaskError)
expect(described_class).to receive(:exit).with(1)
described_class.process('noop')
end
end
end

View File

@ -4,7 +4,7 @@ describe 'download strategies' do
let(:url) { 'http://example.com/cask.dmg' }
let(:url_options) { Hash.new }
let(:cask) {
class_double(Hbc::Cask,
instance_double(Hbc::Cask,
:token => 'some-cask',
:url => Hbc::URL.new(url, url_options),
:version => '1.2.3.4'

View File

@ -1,3 +1,5 @@
require 'pathname'
if ENV['COVERAGE']
require 'coveralls'
Coveralls.wear_merged!

View File

@ -33,8 +33,7 @@ describe Hbc::Artifact::App do
end
it "works with an application in a subdir" do
AltSubDirCask = Class.new(Hbc::Cask)
AltSubDirCask.class_eval do
subdir_cask = Hbc::Cask.new('subdir') do
url TestHelper.local_binary_url('caffeine.zip')
homepage 'http://example.com/local-caffeine'
version '1.2.3'
@ -43,9 +42,7 @@ describe Hbc::Artifact::App do
end
begin
subdir_cask = AltSubDirCask.new.tap do |cask|
TestHelper.install_without_artifacts(cask)
end
TestHelper.install_without_artifacts(subdir_cask)
appsubdir = subdir_cask.staged_path.join('subdir').tap(&:mkpath)
FileUtils.mv(subdir_cask.staged_path.join('Caffeine.app'), appsubdir)

View File

@ -19,8 +19,7 @@ describe Hbc::Artifact::App do
end
it "works with an application in a subdir" do
SubDirCask = Class.new(Hbc::Cask)
SubDirCask.class_eval do
subdir_cask = Hbc::Cask.new('subdir') do
url TestHelper.local_binary_url('caffeine.zip')
homepage 'http://example.com/local-caffeine'
version '1.2.3'
@ -29,9 +28,7 @@ describe Hbc::Artifact::App do
end
begin
subdir_cask = SubDirCask.new.tap do |cask|
TestHelper.install_without_artifacts(cask)
end
TestHelper.install_without_artifacts(subdir_cask)
appsubdir = subdir_cask.staged_path.join('subdir').tap(&:mkpath)
FileUtils.mv(subdir_cask.staged_path.join('Caffeine.app'), appsubdir)

View File

@ -6,15 +6,13 @@ describe Hbc::Artifact::PostflightBlock do
called = false
yielded_arg = nil
CaskWithPostflight = Class.new(Hbc::Cask)
CaskWithPostflight.class_eval do
cask = Hbc::Cask.new('with-postflight') do
postflight do |c|
called = true
yielded_arg = c
end
end
cask = CaskWithPostflight.new
Hbc::Artifact::PostflightBlock.new(cask).install_phase
called.must_equal true
@ -27,15 +25,13 @@ describe Hbc::Artifact::PostflightBlock do
called = false
yielded_arg = nil
CaskWithUninstallPostflight = Class.new(Hbc::Cask)
CaskWithUninstallPostflight.class_eval do
cask = Hbc::Cask.new('with-uninstall-postflight') do
uninstall_postflight do |c|
called = true
yielded_arg = c
end
end
cask = CaskWithUninstallPostflight.new
Hbc::Artifact::PostflightBlock.new(cask).uninstall_phase
called.must_equal true

View File

@ -6,15 +6,13 @@ describe Hbc::Artifact::PreflightBlock do
called = false
yielded_arg = nil
CaskWithPreflight = Class.new(Hbc::Cask)
CaskWithPreflight.class_eval do
cask = Hbc::Cask.new('with-preflight') do
preflight do |c|
called = true
yielded_arg = c
end
end
cask = CaskWithPreflight.new
Hbc::Artifact::PreflightBlock.new(cask).install_phase
called.must_equal true
@ -27,15 +25,13 @@ describe Hbc::Artifact::PreflightBlock do
called = false
yielded_arg = nil
CaskWithUninstallPreflight = Class.new(Hbc::Cask)
CaskWithUninstallPreflight.class_eval do
cask = Hbc::Cask.new('with-uninstall-preflight') do
uninstall_preflight do |c|
called = true
yielded_arg = c
end
end
cask = CaskWithUninstallPreflight.new
Hbc::Artifact::PreflightBlock.new(cask).uninstall_phase
called.must_equal true

View File

@ -20,8 +20,7 @@ describe Hbc::Artifact::App do
end
it "works with an application in a subdir" do
AltSubDirTwoAppsCask = Class.new(Hbc::Cask)
AltSubDirTwoAppsCask.class_eval do
subdir_cask = Hbc::Cask.new('alt-subdir-two-apps') do
url TestHelper.local_binary_url('caffeine.zip')
homepage 'http://example.com/local-caffeine'
version '1.2.3'
@ -31,9 +30,7 @@ describe Hbc::Artifact::App do
end
begin
subdir_cask = AltSubDirTwoAppsCask.new.tap do |cask|
TestHelper.install_without_artifacts(cask)
end
TestHelper.install_without_artifacts(subdir_cask)
appsubdir = subdir_cask.staged_path.join('subdir').tap(&:mkpath)
FileUtils.mv(subdir_cask.staged_path.join('Caffeine.app'), appsubdir)

View File

@ -2,13 +2,11 @@ require 'test_helper'
describe Hbc::Container::Naked do
it "saves files with spaces in them from uris with encoded spaces" do
SpaceyCask = Class.new(Hbc::Cask)
SpaceyCask.class_eval do
cask = Hbc::Cask.new('spacey') do
url 'http://example.com/kevin%20spacey.pkg'
version '1.2'
end
cask = SpaceyCask.new
path = '/tmp/downloads/kevin-spacey-1.2.pkg'
expected_destination = cask.staged_path.join('kevin spacey.pkg')
expected_command = ['/usr/bin/ditto', '--', path, expected_destination]

View File

@ -9,12 +9,11 @@ describe Hbc::DSL do
end
describe "when a Cask includes an unknown method" do
UnexpectedMethodCask = Class.new(Hbc::Cask)
attempt_unknown_method = nil
before do
attempt_unknown_method = lambda {
UnexpectedMethodCask.class_eval do
Hbc::Cask.new('unexpected-method-cask') do
future_feature :not_yet_on_your_machine
end
}
@ -48,14 +47,13 @@ describe Hbc::DSL do
it "requires a valid header format" do
err = lambda {
invalid_cask = Hbc.load('invalid/invalid-header-format')
}.must_raise(Hbc::CaskHeaderParseError)
err.message.must_include 'Bad header line: parse failed'
}.must_raise(SyntaxError)
end
it "requires the header token to match the file name" do
err = lambda {
invalid_cask = Hbc.load('invalid/invalid-header-token-mismatch')
}.must_raise(Hbc::CaskInvalidError)
}.must_raise(Hbc::CaskTokenDoesNotMatchError)
err.message.must_include 'Bad header line:'
err.message.must_include 'does not match file name'
end
@ -70,105 +68,91 @@ describe Hbc::DSL do
describe "name stanza" do
it "lets you set the full name via a name stanza" do
NameCask = Class.new(Hbc::Cask)
NameCask.class_eval do
cask = Hbc::Cask.new('name-cask') do
name 'Proper Name'
end
instance = NameCask.new
instance.full_name.must_equal [
'Proper Name',
]
cask.name.must_equal [
'Proper Name',
]
end
it "Accepts an array value to the name stanza" do
ArrayNameCask = Class.new(Hbc::Cask)
ArrayNameCask.class_eval do
cask = Hbc::Cask.new('array-name-cask') do
name ['Proper Name', 'Alternate Name']
end
instance = ArrayNameCask.new
instance.full_name.must_equal [
'Proper Name',
'Alternate Name',
]
cask.name.must_equal [
'Proper Name',
'Alternate Name',
]
end
it "Accepts multiple name stanzas" do
MultiNameCask = Class.new(Hbc::Cask)
MultiNameCask.class_eval do
cask = Hbc::Cask.new('multi-name-cask') do
name 'Proper Name'
name 'Alternate Name'
end
instance = MultiNameCask.new
instance.full_name.must_equal [
'Proper Name',
'Alternate Name',
]
cask.name.must_equal [
'Proper Name',
'Alternate Name',
]
end
end
describe "sha256 stanza" do
it "lets you set checksum via sha256" do
ChecksumCask = Class.new(Hbc::Cask)
ChecksumCask.class_eval do
cask = Hbc::Cask.new('checksum-cask') do
sha256 'imasha2'
end
instance = ChecksumCask.new
instance.sha256.must_equal 'imasha2'
cask.sha256.must_equal 'imasha2'
end
end
describe "app stanza" do
it "allows you to specify app stanzas" do
CaskWithApps = Class.new(Hbc::Cask)
CaskWithApps.class_eval do
cask = Hbc::Cask.new('cask-with-apps') do
app 'Foo.app'
app 'Bar.app'
end
instance = CaskWithApps.new
Array(instance.artifacts[:app]).must_equal [['Foo.app'], ['Bar.app']]
Array(cask.artifacts[:app]).must_equal [['Foo.app'], ['Bar.app']]
end
it "allow app stanzas to be empty" do
CaskWithNoApps = Class.new(Hbc::Cask)
instance = CaskWithNoApps.new
Array(instance.artifacts[:app]).must_equal %w[]
cask = Hbc::Cask.new('cask-with-no-apps')
Array(cask.artifacts[:app]).must_equal %w[]
end
end
describe "caveats stanza" do
it "allows caveats to be specified via a method define" do
PlainCask = Class.new(Hbc::Cask)
cask = Hbc::Cask.new('plain-cask')
instance = PlainCask.new
cask.caveats.must_be :empty?
instance.caveats.must_be :empty?
CaskWithCaveats = Class.new(Hbc::Cask)
CaskWithCaveats.class_eval do
cask = Hbc::Cask.new('cask-with-caveats') do
def caveats; <<-EOS.undent
When you install this Cask, you probably want to know this.
EOS
end
end
instance = CaskWithCaveats.new
instance.caveats.must_equal "When you install this Cask, you probably want to know this.\n"
cask.caveats.must_equal "When you install this Cask, you probably want to know this.\n"
end
end
describe "pkg stanza" do
it "allows installable pkgs to be specified" do
CaskWithPkgs = Class.new(Hbc::Cask)
CaskWithPkgs.class_eval do
cask = Hbc::Cask.new('cask-with-pkgs') do
pkg 'Foo.pkg'
pkg 'Bar.pkg'
end
instance = CaskWithPkgs.new
Array(instance.artifacts[:pkg]).must_equal [['Foo.pkg'], ['Bar.pkg']]
Array(cask.artifacts[:pkg]).must_equal [['Foo.pkg'], ['Bar.pkg']]
end
end

View File

@ -5,35 +5,23 @@ describe "Cask" do
it "returns an instance of the Cask for the given token" do
c = Hbc.load("adium")
c.must_be_kind_of(Hbc::Cask)
c.must_be_instance_of(KlassPrefixAdium)
c.token.must_equal('adium')
end
it "returns an instance of the Cask from a specific file location" do
# defensive constant cleanup is required because Cask
# classes may already be loaded due to audit test
begin
Object.class_eval{remove_const :KlassPrefixDia}
rescue
end
location = File.expand_path('./Casks/dia.rb')
c = Hbc.load(location)
c.must_be_kind_of(Hbc::Cask)
c.must_be_instance_of(KlassPrefixDia)
Object.class_eval{remove_const :KlassPrefixDia}
c.token.must_equal('dia')
end
it "returns an instance of the Cask from a url" do
begin
Object.class_eval{remove_const :KlassPrefixDia}
rescue
end
url = "file://" + File.expand_path('./Casks/dia.rb')
c = shutup do
Hbc.load(url)
end
c.must_be_kind_of(Hbc::Cask)
c.must_be_instance_of(KlassPrefixDia)
Object.class_eval{remove_const :KlassPrefixDia}
c.token.must_equal('dia')
end
it "raises an error when failing to download a Cask from a url" do
@ -46,19 +34,14 @@ describe "Cask" do
end
it "returns an instance of the Cask from a relative file location" do
begin
Object.class_eval{remove_const :KlassPrefixBbedit}
rescue
end
c = Hbc.load("./Casks/bbedit.rb")
c.must_be_kind_of(Hbc::Cask)
c.must_be_instance_of(KlassPrefixBbedit)
Object.class_eval{remove_const :KlassPrefixBbedit}
c.token.must_equal('bbedit')
end
it "uses exact match when loading by token" do
Hbc.load('test-opera').must_be_instance_of(KlassPrefixTestOpera)
Hbc.load('test-opera-mail').must_be_instance_of(KlassPrefixTestOperaMail)
Hbc.load('test-opera').token.must_equal('test-opera')
Hbc.load('test-opera-mail').token.must_equal('test-opera-mail')
end
it "raises an error when attempting to load a Cask that doesn't exist" do
@ -72,19 +55,7 @@ describe "Cask" do
it "returns a token for every Cask" do
all_cask_tokens = Hbc.all_tokens
all_cask_tokens.count.must_be :>, 20
all_cask_tokens.each { |cask| cask.must_be_kind_of String }
end
end
describe "token" do
it "converts a class constant to a token-style dashed string" do
KlassPrefixPascalCasedConstant = Class.new(Hbc::Cask)
KlassPrefixPascalCasedConstant.token.must_equal 'pascal-cased-constant'
end
it "properly dasherizes constants with single letters in the middle" do
KlassPrefixGamesXChange = Class.new(Hbc::Cask)
KlassPrefixGamesXChange.token.must_equal 'games-x-change'
all_cask_tokens.each { |token| token.must_be_kind_of String }
end
end

View File

@ -1,5 +1,6 @@
require 'bundler'
require 'bundler/setup'
require 'pathname'
if ENV['COVERAGE']
require 'coveralls'
@ -18,11 +19,10 @@ lib_path = brew_cask_path.join('lib')
$:.push(lib_path)
# todo: removeme, this is transitional
require "#{brew_cask_path}/spec/support/kernel_at_exit_hacks"
require "#{brew_cask_path}/spec/support/homebrew_testing_environment"
include HomebrewTestingEnvironment
require 'vendor/homebrew-fork/testing_env'
# force some environment variables
ENV['HOMEBREW_NO_EMOJI'] = '1'
ENV['HOMEBREW_CASK_OPTS'] = nil
# todo temporary, copied from old Homebrew, this method is now moved inside a class
@ -87,7 +87,7 @@ class TestHelper
end
def self.test_cask
Hbc.load('basic-cask')
@test_cask ||= Hbc.load('basic-cask')
end
def self.fake_fetcher