Merge pull request #906 from reitermarkus/os-language

Make `MacOS.language` less opinionated and add `language` stanza.
This commit is contained in:
Markus Reiter 2016-10-03 04:03:26 +02:00 committed by GitHub
commit 35ee283108
10 changed files with 281 additions and 4 deletions

View File

@ -1,6 +1,23 @@
module Hbc
class Auditor
def self.audit(cask, audit_download: false, check_token_conflicts: false)
saved_languages = MacOS.instance_variable_get(:@languages)
if languages_blocks = cask.instance_variable_get(:@dsl).instance_variable_get(:@language_blocks)
languages_blocks.keys.each do |languages|
ohai "Auditing language: #{languages.map { |lang| "'#{lang}'" }.join(", ")}"
MacOS.instance_variable_set(:@languages, languages)
audit_cask_instance(Hbc.load(cask.sourcefile_path), audit_download, check_token_conflicts)
CLI::Cleanup.run(cask.token) if audit_download
end
else
audit_cask_instance(cask, audit_download, check_token_conflicts)
end
ensure
MacOS.instance_variable_set(:@languages, saved_languages)
end
def self.audit_cask_instance(cask, audit_download, check_token_conflicts)
download = audit_download && Download.new(cask)
audit = Audit.new(cask, download: download,
check_token_conflicts: check_token_conflicts)

View File

@ -11,7 +11,10 @@ module Hbc
@token = token
@sourcefile_path = sourcefile_path
@dsl = dsl || DSL.new(@token)
@dsl.instance_eval(&block) if block_given?
if block_given?
@dsl.instance_eval(&block)
@dsl.language_eval
end
end
DSL::DSL_METHODS.each do |method_name|

View File

@ -179,6 +179,10 @@ module Hbc
def self.parser
# If you modify these arguments, please update USAGE.md
@parser ||= OptionParser.new do |opts|
opts.on("--language STRING") do
# handled in OS::Mac
end
OPTIONS.each do |option, method|
opts.on("#{option}" "PATH", Pathname) do |path|
Hbc.public_send(method, path)

View File

@ -1,4 +1,5 @@
require "set"
require "locale"
require "hbc/dsl/appcast"
require "hbc/dsl/base"
@ -64,6 +65,7 @@ module Hbc
:depends_on,
:gpg,
:homepage,
:language,
:license,
:name,
:sha256,
@ -98,6 +100,43 @@ module Hbc
@homepage ||= homepage
end
def language(*args, default: false, &block)
if !args.empty? && block_given?
@language_blocks ||= {}
@language_blocks[args] = block
return unless default
unless @language_blocks.default.nil?
raise CaskInvalidError.new(token, "Only one default language may be defined")
end
@language_blocks.default = block
else
language_eval
end
end
def language_eval
return @language if instance_variable_defined?(:@language)
if @language_blocks.nil? || @language_blocks.empty?
return @language = nil
end
MacOS.languages.map(&Locale.method(:parse)).each do |locale|
key = @language_blocks.keys.detect { |strings|
strings.any? { |string| locale.include?(string) }
}
next if key.nil?
return @language = @language_blocks[key].call
end
@language = @language_blocks.default.call
end
def url(*args, &block)
url_given = !args.empty? || block_given?
return @url unless url_given

View File

@ -8,7 +8,7 @@ module Hbc
@command = command
end
def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir
def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir, :language
def system_command(executable, options = {})
@command.run!(executable, options)

View File

@ -0,0 +1,72 @@
require "spec_helper"
require "locale"
describe Locale do
describe "::parse" do
it "parses a string in the correct format" do
expect(described_class.parse("zh")).to eql(described_class.new("zh", nil, nil))
expect(described_class.parse("zh-CN")).to eql(described_class.new("zh", "CN", nil))
expect(described_class.parse("zh-Hans")).to eql(described_class.new("zh", nil, "Hans"))
expect(described_class.parse("zh-CN-Hans")).to eql(described_class.new("zh", "CN", "Hans"))
end
context "raises a ParserError when given" do
it "an empty string" do
expect{ described_class.parse("") }.to raise_error(Locale::ParserError)
end
it "a string in a wrong format" do
expect { described_class.parse("zh_CN_Hans") }.to raise_error(Locale::ParserError)
expect { described_class.parse("zhCNHans") }.to raise_error(Locale::ParserError)
expect { described_class.parse("zh-CN_Hans") }.to raise_error(Locale::ParserError)
expect { described_class.parse("zhCN") }.to raise_error(Locale::ParserError)
expect { described_class.parse("zh_Hans") }.to raise_error(Locale::ParserError)
end
end
end
describe "::new" do
it "raises an ArgumentError when all arguments are nil" do
expect { described_class.new(nil, nil, nil) }.to raise_error(ArgumentError)
end
it "raises a ParserError when one of the arguments does not match the locale format" do
expect { described_class.new("ZH", nil, nil) }.to raise_error(Locale::ParserError)
expect { described_class.new(nil, "cn", nil) }.to raise_error(Locale::ParserError)
expect { described_class.new(nil, nil, "hans") }.to raise_error(Locale::ParserError)
end
end
subject { described_class.new("zh", "CN", "Hans") }
describe "#include?" do
it { is_expected.to include("zh") }
it { is_expected.to include("zh-CN") }
it { is_expected.to include("CN") }
it { is_expected.to include("CN-Hans") }
it { is_expected.to include("Hans") }
it { is_expected.to include("zh-CN-Hans") }
end
describe "#eql?" do
subject { described_class.new("zh", "CN", "Hans") }
context "all parts match" do
it { is_expected.to eql("zh-CN-Hans") }
it { is_expected.to eql(subject) }
end
context "only some parts match" do
it { is_expected.to_not eql("zh") }
it { is_expected.to_not eql("zh-CN") }
it { is_expected.to_not eql("CN") }
it { is_expected.to_not eql("CN-Hans") }
it { is_expected.to_not eql("Hans") }
end
it "does not raise if 'other' cannot be parsed" do
expect { subject.eql?("zh_CN_Hans") }.not_to raise_error
expect(subject.eql?("zh_CN_Hans")).to be false
end
end
end

View File

@ -122,6 +122,56 @@ describe Hbc::DSL do
end
end
describe "language stanza" do
it "allows multilingual casks" do
cask = lambda do
Hbc::Cask.new("cask-with-apps") do
language "zh" do
sha256 "abc123"
"zh-CN"
end
language "en-US", default: true do
sha256 "xyz789"
"en-US"
end
url "https://example.org/#{language}.zip"
end
end
MacOS.stubs(languages: ["zh"])
cask.call.language.must_equal "zh-CN"
cask.call.sha256.must_equal "abc123"
cask.call.url.to_s.must_equal "https://example.org/zh-CN.zip"
MacOS.stubs(languages: ["zh-XX"])
cask.call.language.must_equal "zh-CN"
cask.call.sha256.must_equal "abc123"
cask.call.url.to_s.must_equal "https://example.org/zh-CN.zip"
MacOS.stubs(languages: ["en"])
cask.call.language.must_equal "en-US"
cask.call.sha256.must_equal "xyz789"
cask.call.url.to_s.must_equal "https://example.org/en-US.zip"
MacOS.stubs(languages: ["xx-XX"])
cask.call.language.must_equal "en-US"
cask.call.sha256.must_equal "xyz789"
cask.call.url.to_s.must_equal "https://example.org/en-US.zip"
MacOS.stubs(languages: ["xx-XX", "zh", "en"])
cask.call.language.must_equal "zh-CN"
cask.call.sha256.must_equal "abc123"
cask.call.url.to_s.must_equal "https://example.org/zh-CN.zip"
MacOS.stubs(languages: ["xx-XX", "en-US", "zh"])
cask.call.language.must_equal "en-US"
cask.call.sha256.must_equal "xyz789"
cask.call.url.to_s.must_equal "https://example.org/en-US.zip"
end
end
describe "app stanza" do
it "allows you to specify app stanzas" do
cask = Hbc::Cask.new("cask-with-apps") do

View File

@ -0,0 +1,68 @@
class Locale
class ParserError < ::RuntimeError
end
LANGUAGE_REGEX = /(?:[a-z]{2})/
REGION_REGEX = /(?:[A-Z]{2})/
SCRIPT_REGEX = /(?:[A-Z][a-z]{3})/
LOCALE_REGEX = /^(#{LANGUAGE_REGEX})?(?:(?:^|-)(#{REGION_REGEX}))?(?:(?:^|-)(#{SCRIPT_REGEX}))?$/
def self.parse(string)
language, region, script = string.to_s.scan(LOCALE_REGEX)[0]
if language.nil? && region.nil? && script.nil?
raise ParserError, "'#{string}' cannot be parsed to a #{self.class}"
end
new(language, region, script)
end
attr_reader :language, :region, :script
def initialize(language, region, script)
if language.nil? && region.nil? && script.nil?
raise ArgumentError, "#{self.class} cannot be empty"
end
{
language: language,
region: region,
script: script,
}.each do |key, value|
next if value.nil?
regex = self.class.const_get("#{key.upcase}_REGEX")
raise ParserError, "'#{value}' does not match #{regex}" unless value =~ regex
instance_variable_set(:"@#{key}", value)
end
self
end
def include?(other)
other = self.class.parse(other) unless other.is_a?(self.class)
[:language, :region, :script].all? { |var|
if other.public_send(var).nil?
true
else
public_send(var) == other.public_send(var)
end
}
end
def eql?(other)
other = self.class.parse(other) unless other.is_a?(self.class)
[:language, :region, :script].all? { |var|
public_send(var) == other.public_send(var)
}
rescue ParserError
false
end
alias == eql?
def to_s
[@language, @region, @script].compact.join("-")
end
end

View File

@ -41,8 +41,24 @@ module OS
version.to_sym
end
def languages
return @languages unless @languages.nil?
@languages = Utils.popen_read("defaults", "read", ".GlobalPreferences", "AppleLanguages").scan(/[^ \n"(),]+/)
if ENV["HOMEBREW_LANGUAGES"]
@languages = ENV["HOMEBREW_LANGUAGES"].split(",") + @languages
end
if ARGV.value("language")
@languages = ARGV.value("language").split(",") + @languages
end
@languages = @languages.uniq
end
def language
@language ||= Utils.popen_read("defaults", "read", ".GlobalPreferences", "AppleLanguages").delete(" \n\"()").sub(/,.*/, "")
languages.first
end
def active_developer_dir

View File

@ -2,7 +2,15 @@ require "testing_env"
require "os/mac"
class OSMacLanguageTests < Homebrew::TestCase
LANGUAGE_REGEX = /\A[a-z]{2}(-[A-Z]{2})?(-[A-Z][a-z]{3})?\Z/
def test_languages_format
OS::Mac.languages.each do |language|
assert_match LANGUAGE_REGEX, language
end
end
def test_language_format
assert_match(/\A[a-z]{2}(-[A-Z]{2})?\Z/, OS::Mac.language)
assert_match LANGUAGE_REGEX, OS::Mac.language
end
end