diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index 05b01042ba33bd83cba552749a01cb2d18f77f60..f5cd9c2315e8274e580832ddf7700d1ef707407c 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -330,14 +330,11 @@ module Cask end def check_languages - invalid = [] @cask.languages.each do |language| - invalid << language.to_s unless language.match?(/^[a-z]{2}$/) || language.match?(/^[a-z]{2}-[A-Z]{2}$/) + Locale.parse(language) + rescue Locale::ParserError + add_error "Locale '#{language}' is invalid." end - - return if invalid.empty? - - add_error "locale #{invalid.join(", ")} are invalid" end def check_token_conflicts diff --git a/Library/Homebrew/cask/dsl.rb b/Library/Homebrew/cask/dsl.rb index 3f5ec39412c5201e6d84ac7ad996f0782781ffea..585b93ebb9110ba928be69d4f4b842315b84e6f7 100644 --- a/Library/Homebrew/cask/dsl.rb +++ b/Library/Homebrew/cask/dsl.rb @@ -136,7 +136,7 @@ module Cask end def language_eval - return @language if instance_variable_defined?(:@language) + return @language if defined?(@language) return @language = nil if @language_blocks.nil? || @language_blocks.empty? diff --git a/Library/Homebrew/locale.rb b/Library/Homebrew/locale.rb index 174c594ad90b7813124972070becf263f3735002..c858aa03b5d2281e8c4ad699943f5f4750783944 100644 --- a/Library/Homebrew/locale.rb +++ b/Library/Homebrew/locale.rb @@ -1,27 +1,56 @@ # frozen_string_literal: true +# Representation of a system locale. +# +# Used to compare the system language and languages defined using cask `language` stanza. +# +# @api private class Locale + # Error when a string cannot be parsed to a `Locale`. class ParserError < StandardError end - LANGUAGE_REGEX = /(?:[a-z]{2,3})/.freeze # ISO 639-1 or ISO 639-2 - REGION_REGEX = /(?:[A-Z]{2}|\d{3})/.freeze # ISO 3166-1 or UN M.49 - SCRIPT_REGEX = /(?:[A-Z][a-z]{3})/.freeze # ISO 15924 + # ISO 639-1 or ISO 639-2 + LANGUAGE_REGEX = /(?:[a-z]{2,3})/.freeze + private_constant :LANGUAGE_REGEX + + # ISO 3166-1 or UN M.49 + REGION_REGEX = /(?:[A-Z]{2}|\d{3})/.freeze + private_constant :REGION_REGEX + + # ISO 15924 + SCRIPT_REGEX = /(?:[A-Z][a-z]{3})/.freeze + private_constant :SCRIPT_REGEX LOCALE_REGEX = /\A((?:#{LANGUAGE_REGEX}|#{REGION_REGEX}|#{SCRIPT_REGEX})(?:-|$)){1,3}\Z/.freeze + private_constant :LOCALE_REGEX def self.parse(string) - string = string.to_s + if locale = try_parse(string) + return locale + end + + raise ParserError, "'#{string}' cannot be parsed to a #{self}" + end + + def self.try_parse(string) + return if string.blank? - raise ParserError, "'#{string}' cannot be parsed to a #{self}" unless string.match?(LOCALE_REGEX) + scanner = StringScanner.new(string) - scan = proc do |regex| - string.scan(/(?:-|^)(#{regex})(?:-|$)/).flatten.first + if language = scanner.scan(LANGUAGE_REGEX) + sep = scanner.scan(/-/) + return if (sep && scanner.eos?) || (sep.nil? && !scanner.eos?) end - language = scan.call(LANGUAGE_REGEX) - region = scan.call(REGION_REGEX) - script = scan.call(SCRIPT_REGEX) + if region = scanner.scan(REGION_REGEX) + sep = scanner.scan(/-/) + return if (sep && scanner.eos?) || (sep.nil? && !scanner.eos?) + end + + script = scanner.scan(SCRIPT_REGEX) + + return unless scanner.eos? new(language, region, script) end @@ -46,7 +75,10 @@ class Locale end def include?(other) - other = self.class.parse(other) unless other.is_a?(self.class) + unless other.is_a?(self.class) + other = self.class.try_parse(other) + return false if other.nil? + end [:language, :region, :script].all? do |var| if other.public_send(var).nil? @@ -58,12 +90,14 @@ class Locale end def eql?(other) - other = self.class.parse(other) unless other.is_a?(self.class) + unless other.is_a?(self.class) + other = self.class.try_parse(other) + return false if other.nil? + end + [:language, :region, :script].all? do |var| public_send(var) == other.public_send(var) end - rescue ParserError - false end alias == eql? diff --git a/Library/Homebrew/test/cask/audit_spec.rb b/Library/Homebrew/test/cask/audit_spec.rb index b96b89faf2c8af404bf303a12adf6af972a54f6d..c39a09590a497181172efc300f1979a40058707f 100644 --- a/Library/Homebrew/test/cask/audit_spec.rb +++ b/Library/Homebrew/test/cask/audit_spec.rb @@ -278,7 +278,6 @@ describe Cask::Audit, :cask do end describe "locale validation" do - let(:strict) { true } let(:cask) do tmp_cask "locale-cask-test", <<~RUBY cask 'locale-cask-test' do @@ -318,7 +317,9 @@ describe Cask::Audit, :cask do context "when cask locale is invalid" do it "error with invalid locale" do - expect(subject).to fail_with(/locale ZH-CN, zh-, zh-cn are invalid/) + expect(subject).to fail_with(/Locale 'ZH-CN' is invalid\./) + expect(subject).to fail_with(/Locale 'zh-' is invalid\./) + expect(subject).to fail_with(/Locale 'zh-cn' is invalid\./) end end end diff --git a/Library/Homebrew/test/locale_spec.rb b/Library/Homebrew/test/locale_spec.rb index f7618613487bcbabc6cab9122400ed45ab326bb6..52c0e6aeb6a32c4a3ec7a11d32ab79fa8dc31e25 100644 --- a/Library/Homebrew/test/locale_spec.rb +++ b/Library/Homebrew/test/locale_spec.rb @@ -26,6 +26,9 @@ describe Locale do 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) + expect { described_class.parse("zh-") }.to raise_error(Locale::ParserError) + expect { described_class.parse("ZH-CN") }.to raise_error(Locale::ParserError) + expect { described_class.parse("zh-cn") }.to raise_error(Locale::ParserError) end end end