Skip to content

Commit 35ee283

Browse files
authored
Merge pull request Homebrew#906 from reitermarkus/os-language
Make `MacOS.language` less opinionated and add `language` stanza.
2 parents 7d31a70 + e2b3753 commit 35ee283

File tree

10 files changed

+281
-4
lines changed

10 files changed

+281
-4
lines changed

Library/Homebrew/cask/lib/hbc/auditor.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
module Hbc
22
class Auditor
33
def self.audit(cask, audit_download: false, check_token_conflicts: false)
4+
saved_languages = MacOS.instance_variable_get(:@languages)
5+
6+
if languages_blocks = cask.instance_variable_get(:@dsl).instance_variable_get(:@language_blocks)
7+
languages_blocks.keys.each do |languages|
8+
ohai "Auditing language: #{languages.map { |lang| "'#{lang}'" }.join(", ")}"
9+
MacOS.instance_variable_set(:@languages, languages)
10+
audit_cask_instance(Hbc.load(cask.sourcefile_path), audit_download, check_token_conflicts)
11+
CLI::Cleanup.run(cask.token) if audit_download
12+
end
13+
else
14+
audit_cask_instance(cask, audit_download, check_token_conflicts)
15+
end
16+
ensure
17+
MacOS.instance_variable_set(:@languages, saved_languages)
18+
end
19+
20+
def self.audit_cask_instance(cask, audit_download, check_token_conflicts)
421
download = audit_download && Download.new(cask)
522
audit = Audit.new(cask, download: download,
623
check_token_conflicts: check_token_conflicts)

Library/Homebrew/cask/lib/hbc/cask.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ def initialize(token, sourcefile_path: nil, dsl: nil, &block)
1111
@token = token
1212
@sourcefile_path = sourcefile_path
1313
@dsl = dsl || DSL.new(@token)
14-
@dsl.instance_eval(&block) if block_given?
14+
if block_given?
15+
@dsl.instance_eval(&block)
16+
@dsl.language_eval
17+
end
1518
end
1619

1720
DSL::DSL_METHODS.each do |method_name|

Library/Homebrew/cask/lib/hbc/cli.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ def self.nice_listing(cask_list)
179179
def self.parser
180180
# If you modify these arguments, please update USAGE.md
181181
@parser ||= OptionParser.new do |opts|
182+
opts.on("--language STRING") do
183+
# handled in OS::Mac
184+
end
185+
182186
OPTIONS.each do |option, method|
183187
opts.on("#{option}" "PATH", Pathname) do |path|
184188
Hbc.public_send(method, path)

Library/Homebrew/cask/lib/hbc/dsl.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "set"
2+
require "locale"
23

34
require "hbc/dsl/appcast"
45
require "hbc/dsl/base"
@@ -64,6 +65,7 @@ class DSL
6465
:depends_on,
6566
:gpg,
6667
:homepage,
68+
:language,
6769
:license,
6870
:name,
6971
:sha256,
@@ -98,6 +100,43 @@ def homepage(homepage = nil)
98100
@homepage ||= homepage
99101
end
100102

103+
def language(*args, default: false, &block)
104+
if !args.empty? && block_given?
105+
@language_blocks ||= {}
106+
@language_blocks[args] = block
107+
108+
return unless default
109+
110+
unless @language_blocks.default.nil?
111+
raise CaskInvalidError.new(token, "Only one default language may be defined")
112+
end
113+
114+
@language_blocks.default = block
115+
else
116+
language_eval
117+
end
118+
end
119+
120+
def language_eval
121+
return @language if instance_variable_defined?(:@language)
122+
123+
if @language_blocks.nil? || @language_blocks.empty?
124+
return @language = nil
125+
end
126+
127+
MacOS.languages.map(&Locale.method(:parse)).each do |locale|
128+
key = @language_blocks.keys.detect { |strings|
129+
strings.any? { |string| locale.include?(string) }
130+
}
131+
132+
next if key.nil?
133+
134+
return @language = @language_blocks[key].call
135+
end
136+
137+
@language = @language_blocks.default.call
138+
end
139+
101140
def url(*args, &block)
102141
url_given = !args.empty? || block_given?
103142
return @url unless url_given

Library/Homebrew/cask/lib/hbc/dsl/base.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def initialize(cask, command = SystemCommand)
88
@command = command
99
end
1010

11-
def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir
11+
def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir, :language
1212

1313
def system_command(executable, options = {})
1414
@command.run!(executable, options)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
require "spec_helper"
2+
require "locale"
3+
4+
describe Locale do
5+
describe "::parse" do
6+
it "parses a string in the correct format" do
7+
expect(described_class.parse("zh")).to eql(described_class.new("zh", nil, nil))
8+
expect(described_class.parse("zh-CN")).to eql(described_class.new("zh", "CN", nil))
9+
expect(described_class.parse("zh-Hans")).to eql(described_class.new("zh", nil, "Hans"))
10+
expect(described_class.parse("zh-CN-Hans")).to eql(described_class.new("zh", "CN", "Hans"))
11+
end
12+
13+
context "raises a ParserError when given" do
14+
it "an empty string" do
15+
expect{ described_class.parse("") }.to raise_error(Locale::ParserError)
16+
end
17+
18+
it "a string in a wrong format" do
19+
expect { described_class.parse("zh_CN_Hans") }.to raise_error(Locale::ParserError)
20+
expect { described_class.parse("zhCNHans") }.to raise_error(Locale::ParserError)
21+
expect { described_class.parse("zh-CN_Hans") }.to raise_error(Locale::ParserError)
22+
expect { described_class.parse("zhCN") }.to raise_error(Locale::ParserError)
23+
expect { described_class.parse("zh_Hans") }.to raise_error(Locale::ParserError)
24+
end
25+
end
26+
end
27+
28+
describe "::new" do
29+
it "raises an ArgumentError when all arguments are nil" do
30+
expect { described_class.new(nil, nil, nil) }.to raise_error(ArgumentError)
31+
end
32+
33+
it "raises a ParserError when one of the arguments does not match the locale format" do
34+
expect { described_class.new("ZH", nil, nil) }.to raise_error(Locale::ParserError)
35+
expect { described_class.new(nil, "cn", nil) }.to raise_error(Locale::ParserError)
36+
expect { described_class.new(nil, nil, "hans") }.to raise_error(Locale::ParserError)
37+
end
38+
end
39+
40+
subject { described_class.new("zh", "CN", "Hans") }
41+
42+
describe "#include?" do
43+
it { is_expected.to include("zh") }
44+
it { is_expected.to include("zh-CN") }
45+
it { is_expected.to include("CN") }
46+
it { is_expected.to include("CN-Hans") }
47+
it { is_expected.to include("Hans") }
48+
it { is_expected.to include("zh-CN-Hans") }
49+
end
50+
51+
describe "#eql?" do
52+
subject { described_class.new("zh", "CN", "Hans") }
53+
54+
context "all parts match" do
55+
it { is_expected.to eql("zh-CN-Hans") }
56+
it { is_expected.to eql(subject) }
57+
end
58+
59+
context "only some parts match" do
60+
it { is_expected.to_not eql("zh") }
61+
it { is_expected.to_not eql("zh-CN") }
62+
it { is_expected.to_not eql("CN") }
63+
it { is_expected.to_not eql("CN-Hans") }
64+
it { is_expected.to_not eql("Hans") }
65+
end
66+
67+
it "does not raise if 'other' cannot be parsed" do
68+
expect { subject.eql?("zh_CN_Hans") }.not_to raise_error
69+
expect(subject.eql?("zh_CN_Hans")).to be false
70+
end
71+
end
72+
end

Library/Homebrew/cask/test/cask/dsl_test.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,56 @@
122122
end
123123
end
124124

125+
describe "language stanza" do
126+
it "allows multilingual casks" do
127+
cask = lambda do
128+
Hbc::Cask.new("cask-with-apps") do
129+
language "zh" do
130+
sha256 "abc123"
131+
"zh-CN"
132+
end
133+
134+
language "en-US", default: true do
135+
sha256 "xyz789"
136+
"en-US"
137+
end
138+
139+
url "https://example.org/#{language}.zip"
140+
end
141+
end
142+
143+
MacOS.stubs(languages: ["zh"])
144+
cask.call.language.must_equal "zh-CN"
145+
cask.call.sha256.must_equal "abc123"
146+
cask.call.url.to_s.must_equal "https://example.org/zh-CN.zip"
147+
148+
MacOS.stubs(languages: ["zh-XX"])
149+
cask.call.language.must_equal "zh-CN"
150+
cask.call.sha256.must_equal "abc123"
151+
cask.call.url.to_s.must_equal "https://example.org/zh-CN.zip"
152+
153+
MacOS.stubs(languages: ["en"])
154+
cask.call.language.must_equal "en-US"
155+
cask.call.sha256.must_equal "xyz789"
156+
cask.call.url.to_s.must_equal "https://example.org/en-US.zip"
157+
158+
MacOS.stubs(languages: ["xx-XX"])
159+
cask.call.language.must_equal "en-US"
160+
cask.call.sha256.must_equal "xyz789"
161+
cask.call.url.to_s.must_equal "https://example.org/en-US.zip"
162+
163+
MacOS.stubs(languages: ["xx-XX", "zh", "en"])
164+
cask.call.language.must_equal "zh-CN"
165+
cask.call.sha256.must_equal "abc123"
166+
cask.call.url.to_s.must_equal "https://example.org/zh-CN.zip"
167+
168+
MacOS.stubs(languages: ["xx-XX", "en-US", "zh"])
169+
cask.call.language.must_equal "en-US"
170+
cask.call.sha256.must_equal "xyz789"
171+
cask.call.url.to_s.must_equal "https://example.org/en-US.zip"
172+
end
173+
end
174+
125175
describe "app stanza" do
126176
it "allows you to specify app stanzas" do
127177
cask = Hbc::Cask.new("cask-with-apps") do

Library/Homebrew/locale.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
class Locale
2+
class ParserError < ::RuntimeError
3+
end
4+
5+
LANGUAGE_REGEX = /(?:[a-z]{2})/
6+
REGION_REGEX = /(?:[A-Z]{2})/
7+
SCRIPT_REGEX = /(?:[A-Z][a-z]{3})/
8+
9+
LOCALE_REGEX = /^(#{LANGUAGE_REGEX})?(?:(?:^|-)(#{REGION_REGEX}))?(?:(?:^|-)(#{SCRIPT_REGEX}))?$/
10+
11+
def self.parse(string)
12+
language, region, script = string.to_s.scan(LOCALE_REGEX)[0]
13+
14+
if language.nil? && region.nil? && script.nil?
15+
raise ParserError, "'#{string}' cannot be parsed to a #{self.class}"
16+
end
17+
18+
new(language, region, script)
19+
end
20+
21+
attr_reader :language, :region, :script
22+
23+
def initialize(language, region, script)
24+
if language.nil? && region.nil? && script.nil?
25+
raise ArgumentError, "#{self.class} cannot be empty"
26+
end
27+
28+
{
29+
language: language,
30+
region: region,
31+
script: script,
32+
}.each do |key, value|
33+
next if value.nil?
34+
35+
regex = self.class.const_get("#{key.upcase}_REGEX")
36+
raise ParserError, "'#{value}' does not match #{regex}" unless value =~ regex
37+
instance_variable_set(:"@#{key}", value)
38+
end
39+
40+
self
41+
end
42+
43+
def include?(other)
44+
other = self.class.parse(other) unless other.is_a?(self.class)
45+
46+
[:language, :region, :script].all? { |var|
47+
if other.public_send(var).nil?
48+
true
49+
else
50+
public_send(var) == other.public_send(var)
51+
end
52+
}
53+
end
54+
55+
def eql?(other)
56+
other = self.class.parse(other) unless other.is_a?(self.class)
57+
[:language, :region, :script].all? { |var|
58+
public_send(var) == other.public_send(var)
59+
}
60+
rescue ParserError
61+
false
62+
end
63+
alias == eql?
64+
65+
def to_s
66+
[@language, @region, @script].compact.join("-")
67+
end
68+
end

Library/Homebrew/os/mac.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,24 @@ def cat
4141
version.to_sym
4242
end
4343

44+
def languages
45+
return @languages unless @languages.nil?
46+
47+
@languages = Utils.popen_read("defaults", "read", ".GlobalPreferences", "AppleLanguages").scan(/[^ \n"(),]+/)
48+
49+
if ENV["HOMEBREW_LANGUAGES"]
50+
@languages = ENV["HOMEBREW_LANGUAGES"].split(",") + @languages
51+
end
52+
53+
if ARGV.value("language")
54+
@languages = ARGV.value("language").split(",") + @languages
55+
end
56+
57+
@languages = @languages.uniq
58+
end
59+
4460
def language
45-
@language ||= Utils.popen_read("defaults", "read", ".GlobalPreferences", "AppleLanguages").delete(" \n\"()").sub(/,.*/, "")
61+
languages.first
4662
end
4763

4864
def active_developer_dir

Library/Homebrew/test/test_os_mac_language.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
require "os/mac"
33

44
class OSMacLanguageTests < Homebrew::TestCase
5+
LANGUAGE_REGEX = /\A[a-z]{2}(-[A-Z]{2})?(-[A-Z][a-z]{3})?\Z/
6+
7+
def test_languages_format
8+
OS::Mac.languages.each do |language|
9+
assert_match LANGUAGE_REGEX, language
10+
end
11+
end
12+
513
def test_language_format
6-
assert_match(/\A[a-z]{2}(-[A-Z]{2})?\Z/, OS::Mac.language)
14+
assert_match LANGUAGE_REGEX, OS::Mac.language
715
end
816
end

0 commit comments

Comments
 (0)