Use a Formulary-like approach to load Casks.

This commit is contained in:
Markus Reiter 2017-03-12 19:18:41 +01:00
parent c4d8b1696c
commit d1995dad4b
18 changed files with 184 additions and 296 deletions

View File

@ -7,7 +7,6 @@ require "hbc/auditor"
require "hbc/cache" require "hbc/cache"
require "hbc/cask" require "hbc/cask"
require "hbc/cask_loader" require "hbc/cask_loader"
require "hbc/without_source"
require "hbc/caskroom" require "hbc/caskroom"
require "hbc/checkable" require "hbc/checkable"
require "hbc/cli" require "hbc/cli"
@ -24,7 +23,6 @@ require "hbc/macos"
require "hbc/pkg" require "hbc/pkg"
require "hbc/qualified_token" require "hbc/qualified_token"
require "hbc/scopes" require "hbc/scopes"
require "hbc/source"
require "hbc/staged" require "hbc/staged"
require "hbc/system_command" require "hbc/system_command"
require "hbc/topological_hash" require "hbc/topological_hash"
@ -45,10 +43,7 @@ module Hbc
Caskroom.ensure_caskroom_exists Caskroom.ensure_caskroom_exists
end end
def self.load(query) def self.load(ref)
odebug "Loading Cask definitions" CaskLoader.load(ref)
cask = Source.for_query(query).load
cask.dumpcask
cask
end end
end end

View File

@ -1,44 +1,167 @@
module Hbc module Hbc
class CaskLoader module CaskLoader
def self.load_from_file(path) class FromContentLoader
raise CaskError, "File '#{path}' does not exist" unless path.exist? def initialize(content)
raise CaskError, "File '#{path}' is not readable" unless path.readable? @content = content
raise CaskError, "File '#{path}' is not a plain file" unless path.file?
token = path.basename(".rb").to_s
content = IO.read(path).force_encoding("UTF-8")
new(token, content, path).load
end
def self.load_from_string(token, content)
new(token, content).load
end end
def load def load
instance_eval(@content, __FILE__, __LINE__) instance_eval(@content.force_encoding("UTF-8"), __FILE__, __LINE__)
rescue CaskError, StandardError, ScriptError => e
e.message.concat(" while loading '#{@token}'")
e.message.concat(" from '#{@path}'") unless @path.nil?
raise e, e.message
end end
private private
def initialize(token, content, path = nil) def cask(header_token, &block)
@token = token Cask.new(header_token, &block)
@content = content
@path = path unless path.nil?
end end
end
class FromPathLoader < FromContentLoader
def self.can_load?(ref)
path = Pathname.new(ref)
path.extname == ".rb" && path.expand_path.exist?
end
attr_reader :token, :path
def initialize(path)
path = Pathname.new(path).expand_path
@token = path.basename(".rb").to_s
@path = path
end
def load
raise CaskError, "'#{@path}' does not exist." unless @path.exist?
raise CaskError, "'#{@path}' is not readable." unless @path.readable?
raise CaskError, "'#{@path}' is not a file." unless @path.file?
@content = IO.read(@path)
super
end
private
def cask(header_token, &block) def cask(header_token, &block)
raise CaskTokenDoesNotMatchError.new(@token, header_token) unless @token == header_token if @token != header_token
raise CaskTokenDoesNotMatchError.new(@token, header_token)
if @path.nil?
Cask.new(@token, &block)
else
Cask.new(@token, sourcefile_path: @path, &block)
end end
Cask.new(header_token, sourcefile_path: @path, &block)
end
end
class FromURILoader < FromPathLoader
def self.can_load?(ref)
!(ref.to_s !~ ::URI.regexp)
end
def initialize(url)
@url = url
uri = URI(url)
super Hbc.cache/File.basename(uri.path)
end
def load
Hbc.cache.mkpath
FileUtils.rm_f @path
begin
ohai "Downloading #{@url}."
curl @url, "-o", @path
rescue ErrorDuringExecution
raise CaskUnavailableError, @url
end
super
end
end
class FromTapLoader < FromPathLoader
def self.can_load?(ref)
!(ref.to_s !~ HOMEBREW_TAP_CASK_REGEX)
end
def initialize(tapped_name)
user, repo, token = tapped_name.split("/", 3).map(&:downcase)
@tap = Tap.fetch(user, repo)
super @tap.cask_dir/"#{token}.rb"
end
def load
@tap.install unless @tap.installed?
super
end
end
class NullLoader < FromPathLoader
def self.can_load?(*)
true
end
def initialize(ref)
@token = File.basename(ref, ".rb")
super CaskLoader.default_path(@token)
end
def load
raise CaskUnavailableError, @token
end
end
def self.load_from_file(path)
FromPathLoader.new(path).load
end
def self.load_from_string(content)
FromContentLoader.new(content).load
end
def self.path(ref)
self.for(ref).path
end
def self.load(ref)
self.for(ref).load
end
def self.for(ref)
[
FromURILoader,
FromTapLoader,
FromPathLoader,
].each do |loader_class|
return loader_class.new(ref) if loader_class.can_load?(ref)
end
if FromPathLoader.can_load?(default_path(ref))
return FromPathLoader.new(default_path(ref))
end
possible_tap_casks = tap_paths(ref)
if possible_tap_casks.count == 1
possible_tap_cask = possible_tap_casks.first
return FromPathLoader.new(possible_tap_cask)
end
possible_installed_cask = Cask.new(ref)
if possible_installed_cask.installed?
return FromPathLoader.new(possible_installed_cask.installed_caskfile)
end
NullLoader.new(ref)
end
def self.default_path(token)
Hbc.default_tap.cask_dir/"#{token.to_s.downcase}.rb"
end
def self.tap_paths(token)
Tap.map { |t| t.cask_dir/"#{token.to_s.downcase}.rb" }
.select(&:exist?)
end end
end end
end end

View File

@ -126,15 +126,14 @@ module Hbc
end end
if token_with_tap if token_with_tap
user, repo, token = token_with_tap.split("/") user, repo, token = token_with_tap.split("/", 3)
tap = Tap.fetch(user, repo) tap = Tap.fetch(user, repo)
else else
token = query_without_extension token = query_without_extension
tap = Hbc.default_tap tap = Hbc.default_tap
end end
return query_path if tap.cask_dir.nil? CaskLoader.default_path(token)
tap.cask_dir.join("#{token}.rb")
end end
def tcc_db def tcc_db

View File

@ -11,15 +11,15 @@ module Hbc
end end
def all_tapped_cask_dirs def all_tapped_cask_dirs
Tap.map(&:cask_dir).compact Tap.map(&:cask_dir).select(&:directory?)
end end
def all_tokens def all_tokens
Tap.map do |t| Tap.flat_map do |t|
t.cask_files.map do |p| t.cask_files.map do |p|
"#{t.name}/#{File.basename(p, ".rb")}" "#{t.name}/#{File.basename(p, ".rb")}"
end end
end.flatten end
end end
def installed def installed

View File

@ -1,37 +0,0 @@
require "hbc/source/gone"
require "hbc/source/path_slash_required"
require "hbc/source/path_slash_optional"
require "hbc/source/tapped_qualified"
require "hbc/source/untapped_qualified"
require "hbc/source/tapped"
require "hbc/source/uri"
module Hbc
module Source
def self.sources
[
URI,
PathSlashRequired,
TappedQualified,
UntappedQualified,
Tapped,
PathSlashOptional,
Gone,
]
end
def self.for_query(query)
odebug "Translating '#{query}' into a valid Cask source"
raise CaskUnavailableError, query if query.to_s =~ /^\s*$/
source = sources.find do |s|
odebug "Testing source class #{s}"
s.me?(query)
end
raise CaskUnavailableError, query unless source
odebug "Success! Using source class #{source}"
resolved_cask_source = source.new(query)
odebug "Resolved Cask URI or file source to '#{resolved_cask_source}'"
resolved_cask_source
end
end
end

View File

@ -1,23 +0,0 @@
module Hbc
module Source
class Gone
def self.me?(query)
WithoutSource.new(query).installed?
end
attr_reader :query
def initialize(query)
@query = query
end
def load
WithoutSource.new(query)
end
def to_s
""
end
end
end
end

View File

@ -1,29 +0,0 @@
require "rubygems"
require "hbc/cask_loader"
module Hbc
module Source
class PathBase
# derived classes must define method self.me?
def self.path_for_query(query)
Pathname.new(query).sub(/(\.rb)?$/, ".rb")
end
attr_reader :path
def initialize(path)
@path = Pathname.new(path).expand_path
end
def load
CaskLoader.load_from_file(@path)
end
def to_s
# stringify to fully-resolved location
@path.to_s
end
end
end
end

View File

@ -1,12 +0,0 @@
require "hbc/source/path_base"
module Hbc
module Source
class PathSlashOptional < PathBase
def self.me?(query)
path = path_for_query(query)
path.exist?
end
end
end
end

View File

@ -1,12 +0,0 @@
require "hbc/source/path_base"
module Hbc
module Source
class PathSlashRequired < PathBase
def self.me?(query)
path = path_for_query(query)
path.to_s.include?("/") && path.exist?
end
end
end
end

View File

@ -1,24 +0,0 @@
module Hbc
module Source
class Tapped
def self.me?(query)
Hbc.path(query).exist?
end
attr_reader :token
def initialize(token)
@token = token
end
def load
PathSlashOptional.new(Hbc.path(token)).load
end
def to_s
# stringify to fully-resolved location
Hbc.path(token).expand_path.to_s
end
end
end
end

View File

@ -1,21 +0,0 @@
require "hbc/source/tapped"
module Hbc
module Source
class TappedQualified < Tapped
def self.me?(query)
return if (tap = tap_for_query(query)).nil?
tap.installed? && Hbc.path(query).exist?
end
def self.tap_for_query(query)
qualified_token = QualifiedToken.parse(query)
return if qualified_token.nil?
user, repo = qualified_token[0..1]
Tap.fetch(user, repo)
end
end
end
end

View File

@ -1,14 +0,0 @@
require "hbc/source/tapped_qualified"
module Hbc
module Source
class UntappedQualified < TappedQualified
def self.me?(query)
return if (tap = tap_for_query(query)).nil?
tap.install
tap.installed? && Hbc.path(query).exist?
end
end
end
end

View File

@ -1,32 +0,0 @@
module Hbc
module Source
class URI
def self.me?(query)
!(query.to_s =~ ::URI.regexp).nil?
end
attr_reader :uri
def initialize(uri)
@uri = uri
end
def load
Hbc.cache.mkpath
path = Hbc.cache.join(File.basename(uri))
ohai "Downloading #{uri}"
odebug "Download target -> #{path}"
begin
curl(uri, "-o", path.to_s)
rescue ErrorDuringExecution
raise CaskUnavailableError, uri
end
PathSlashOptional.new(path).load
end
def to_s
uri.to_s
end
end
end
end

View File

@ -1,17 +0,0 @@
module Hbc
class WithoutSource < Cask
# Override from `Hbc::DSL` because we don't have a cask source file to work
# with, so we don't know the cask's `version`.
def staged_path
(caskroom_path.children - [metadata_master_container_path]).first
end
def to_s
"#{token} (!)"
end
def installed?
caskroom_path.exist?
end
end
end

View File

@ -19,6 +19,11 @@ class String
end end
end end
def cask
$LOAD_PATH.unshift("#{HOMEBREW_LIBRARY_PATH}/cask/lib")
require "hbc"
end
module Homebrew module Homebrew
module_function module_function

View File

@ -12,7 +12,11 @@ module CaskLoaderCompatibilityLayer
end end
module Hbc module Hbc
class CaskLoader module CaskLoader
class FromContentLoader; end
class FromPathLoader < FromContentLoader
prepend CaskLoaderCompatibilityLayer prepend CaskLoaderCompatibilityLayer
end end
end
end end

View File

@ -285,7 +285,7 @@ class Tap
# path to the directory of all {Formula} files for this {Tap}. # path to the directory of all {Formula} files for this {Tap}.
def formula_dir def formula_dir
@formula_dir ||= potential_formula_dirs.detect(&:directory?) @formula_dir ||= potential_formula_dirs.detect(&:directory?) || path/"Formula"
end end
def potential_formula_dirs def potential_formula_dirs
@ -294,12 +294,12 @@ class Tap
# path to the directory of all {Cask} files for this {Tap}. # path to the directory of all {Cask} files for this {Tap}.
def cask_dir def cask_dir
@cask_dir ||= [path/"Casks"].detect(&:directory?) @cask_dir ||= path/"Casks"
end end
# an array of all {Formula} files of this {Tap}. # an array of all {Formula} files of this {Tap}.
def formula_files def formula_files
@formula_files ||= if formula_dir @formula_files ||= if formula_dir.directory?
formula_dir.children.select(&method(:formula_file?)) formula_dir.children.select(&method(:formula_file?))
else else
[] []
@ -308,7 +308,7 @@ class Tap
# an array of all {Cask} files of this {Tap}. # an array of all {Cask} files of this {Tap}.
def cask_files def cask_files
@cask_files ||= if cask_dir @cask_files ||= if cask_dir.directory?
cask_dir.children.select(&method(:cask_file?)) cask_dir.children.select(&method(:cask_file?))
else else
[] []

View File

@ -40,23 +40,6 @@ describe Hbc::CLI::List, :cask do
end end
end end
describe "when Casks have been renamed" do
let(:caskroom_path) { Hbc.caskroom.join("ive-been-renamed") }
let(:staged_path) { caskroom_path.join("latest") }
before do
staged_path.mkpath
end
it "lists installed Casks without backing ruby files (due to renames or otherwise)" do
expect {
Hbc::CLI::List.run
}.to output(<<-EOS.undent).to_stdout
ive-been-renamed (!)
EOS
end
end
describe "given a set of installed Casks" do describe "given a set of installed Casks" do
let(:caffeine) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb") } let(:caffeine) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb") }
let(:transmission) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb") } let(:transmission) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb") }