diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 54e3ff1..454f9d2 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -9,7 +9,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby: [ '2.5', '2.6', '2.7', '3.0', '3.1' ] + ruby: [ '3.0', '3.1', '3.2', '3.3' ] + task: [ 'spec' ] + include: + - ruby: '3.0' # keep in sync with rubocop.yml and gemspec + task: 'rubocop' name: ${{ matrix.ruby }} rake steps: - uses: actions/checkout@v2 diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..75600ff --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,110 @@ +require: +- rubocop-rake +- rubocop-rspec + +AllCops: + NewCops: enable + TargetRubyVersion: 3.0 # keep in sync with gemspec and actions.yml + +Style/StringLiterals: + Enabled: false + +Bundler/OrderedGems: + Enabled: false + +Metrics: + Enabled: false + +Style/Documentation: + Enabled: false + +Layout/EmptyLineAfterMagicComment: + Enabled: false + +Layout/EndAlignment: + EnforcedStyleAlignWith: variable + +Layout/MultilineOperationIndentation: + Enabled: false + +Layout/MultilineMethodCallIndentation: + EnforcedStyle: indented + +Style/NumericPredicate: + EnforcedStyle: comparison + +Layout/EmptyLineAfterGuardClause: + Enabled: false + +Layout/FirstHashElementLineBreak: + Enabled: true # Opt-in + +# Opt-in +Layout/FirstMethodArgumentLineBreak: + Enabled: true # Opt-in + +Layout/FirstMethodParameterLineBreak: + Enabled: true # Opt-in + +# https://github.com/rubocop-hq/rubocop/issues/5891 +Style/SpecialGlobalVars: + Enabled: false + +Style/WordArray: + EnforcedStyle: brackets + +Style/SymbolArray: + EnforcedStyle: brackets + +Style/GuardClause: + Enabled: false + +Style/EmptyElse: + Enabled: false + +RSpec/DescribedClass: + EnforcedStyle: explicit + +Style/DoubleNegation: + Enabled: false + +RSpec/VerifiedDoubles: + Enabled: false + +RSpec/ExampleLength: + Enabled: false + +Style/CombinableLoops: + Enabled: false + +Lint/Void: + Enabled: false + +Security/MarshalLoad: + Enabled: false + +Lint/EmptyBlock: + Exclude: [spec/**/*.rb] + +Naming/MethodParameterName: + Exclude: [spec/**/*.rb] + +RSpec/BeforeAfterAll: + Enabled: false + +# could change to `#method` and `.method` instead +RSpec/DescribeSymbol: + Enabled: false + +RSpec/NestedGroups: + Enabled: false + +RSpec/EmptyExampleGroup: + Enabled: false + +Lint/SuppressedException: + Enabled: false + +# somehow crashes +RSpec/VariableName: + Enabled: false diff --git a/Gemfile b/Gemfile index edc4b02..7cc83c9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,10 @@ +# frozen_string_literal: true source 'https://rubygems.org' gemspec gem 'bump' gem 'rake' gem 'rspec', '~>3.0' +gem 'rubocop' +gem 'rubocop-rake' +gem 'rubocop-rspec' diff --git a/Gemfile.lock b/Gemfile.lock index 519e5b7..b0ba889 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,9 +7,21 @@ PATH GEM remote: https://rubygems.org/ specs: + ast (2.4.2) bump (0.10.0) diff-lcs (1.4.4) + json (2.7.2) + language_server-protocol (3.17.0.3) + parallel (1.26.3) + parser (3.3.4.2) + ast (~> 2.4.1) + racc + racc (1.8.1) + rainbow (3.1.1) rake (13.0.6) + regexp_parser (2.9.2) + rexml (3.3.6) + strscan rspec (3.10.0) rspec-core (~> 3.10.0) rspec-expectations (~> 3.10.0) @@ -23,7 +35,27 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) rspec-support (3.10.3) + rubocop (1.65.1) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.4, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.32.1) + parser (>= 3.3.1.0) + rubocop-rake (0.6.0) + rubocop (~> 1.0) + rubocop-rspec (2.9.0) + rubocop (~> 1.19) + ruby-progressbar (1.13.0) simple_po_parser (1.1.6) + strscan (3.1.0) + unicode-display_width (2.5.0) PLATFORMS ruby @@ -36,6 +68,9 @@ DEPENDENCIES i18n_data! rake rspec (~> 3.0) + rubocop + rubocop-rake + rubocop-rspec BUNDLED WITH 2.3.12 diff --git a/Rakefile b/Rakefile index 7bc8692..6a9ed17 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'bundler/setup' require 'bundler/gem_tasks' require 'bump/tasks' @@ -6,22 +7,28 @@ require 'yaml' $LOAD_PATH << "lib" require 'i18n_data' -task default: [:spec] +task default: [:spec, :rubocop] +desc "Run tests" task :spec do sh "rspec --warnings spec/" end +desc "Rubocop" +task :rubocop do + sh "rubocop --parallel" +end + desc "write all languages to output" task :all_languages do - I18nData.languages.keys.each do |lc| + I18nData.languages.each_key do |lc| `rake languages LANGUAGE=#{lc}` end end desc "write languages to output/languages_{language}" task :languages do - raise unless language = ENV['LANGUAGE'] + raise unless (language = ENV.fetch('LANGUAGE', nil)) `mkdir -p output` data = I18nData.languages(language.upcase) File.write "output/languages_#{language.downcase}.yml", data.to_yaml @@ -29,34 +36,32 @@ end desc "write all countries to output to debug" task :all_countries do - I18nData.languages.keys.each do |lc| + I18nData.languages.each_key do |lc| `rake countries LANGUAGE=#{lc}` end end desc "write countries to output/countries_{language} to debug" task :countries do - raise unless language = ENV['LANGUAGE'] + raise unless (language = ENV.fetch('LANGUAGE', nil)) `mkdir -p output` data = I18nData.countries(language.upcase) - File.open("output/countries_#{language.downcase}.yml",'w') {|f|f.puts data.to_yaml} + File.open("output/countries_#{language.downcase}.yml", 'w') { |f| f.puts data.to_yaml } end desc "write example output, just to show off :D" task :example_output do `mkdir -p example_output` - #all names for germany, france, united kingdom and unites states - ['DE','FR','GB','US'].each do |cc| + # all names for germany, france, united kingdom and unites states + ['DE', 'FR', 'GB', 'US'].each do |cc| names = I18nData.languages.keys.map do |lc| - begin - [I18nData.countries(lc)[cc], I18nData.languages[lc]] - rescue I18nData::NoTranslationAvailable - nil - end + [I18nData.countries(lc)[cc], I18nData.languages[lc]] + rescue I18nData::NoTranslationAvailable + nil end - File.open("example_output/all_names_for_#{cc}.txt",'w') do |f| - f.puts names.reject(&:nil?).map{|x|x*" ---- "} * "\n" + File.open("example_output/all_names_for_#{cc}.txt", 'w') do |f| + f.puts names.compact.map { |x| x * " ---- " } * "\n" end end end @@ -66,7 +71,7 @@ task :stats do dir = "cache/file_data_provider" [:languages, :countries].each do |type| files = FileList["#{dir}/#{type}*"] - lines = File.readlines(files.first).reject{|l|l.empty?} + lines = File.readlines(files.first).reject(&:empty?) puts "#{lines.size} #{type} in #{files.size} languages" end end diff --git a/i18n_data.gemspec b/i18n_data.gemspec index 8560493..db64fa2 100644 --- a/i18n_data.gemspec +++ b/i18n_data.gemspec @@ -1,3 +1,4 @@ +# frozen_string_literal: true name = "i18n_data" require "./lib/#{name}/version" @@ -8,6 +9,6 @@ Gem::Specification.new name, I18nData::VERSION do |s| s.homepage = "https://github.com/grosser/#{name}" s.files = `git ls-files lib cache `.split("\n") s.license = "MIT" - s.required_ruby_version = '>= 2.5.0' - s.add_runtime_dependency 'simple_po_parser', '~> 1.1' + s.required_ruby_version = '>= 3.0.0' # keep in sync with rubocop.yml and actions.yml + s.add_dependency 'simple_po_parser', '~> 1.1' end diff --git a/lib/i18n_data.rb b/lib/i18n_data.rb index 957cded..1098e75 100644 --- a/lib/i18n_data.rb +++ b/lib/i18n_data.rb @@ -1,7 +1,7 @@ +# frozen_string_literal: true require 'i18n_data/version' module I18nData - class BaseException < StandardError def to_s "#{self.class} -- #{super}" @@ -13,13 +13,13 @@ class AccessDenied < BaseException; end class Unknown < BaseException; end class << self - def languages(language_code='EN') + def languages(language_code = 'EN') fetch :languages, language_code do data_provider.codes(:languages, normal_to_region_code(language_code.to_s.upcase)) end end - def countries(language_code='EN') + def countries(language_code = 'EN') fetch :countries, language_code do data_provider.codes(:countries, normal_to_region_code(language_code.to_s.upcase)) end @@ -34,10 +34,10 @@ def language_code(name) end def data_provider - @data_provider ||= ( + @data_provider ||= begin require 'i18n_data/file_data_provider' FileDataProvider - ) + end end def data_provider=(provider) @@ -57,7 +57,7 @@ def fetch(type, language_code) def normal_to_region_code(normal) { "ZH" => "zh_CN", - "BN" => "bn_IN", + "BN" => "bn_IN" }[normal] || normal end @@ -65,7 +65,7 @@ def recognise_code(type, search) search = search.strip # common languages first <-> faster in majority of cases - language_codes = ['EN','ES','FR','DE','ZH'] | available_language_codes + language_codes = ['EN', 'ES', 'FR', 'DE', 'ZH'] | available_language_codes language_codes.each do |language_code| options = @@ -88,8 +88,8 @@ def recognise_code(type, search) # NOTE: this is not perfect since the used provider might have more or less languages available # but it's better than just using the available english language codes def available_language_codes - @available_languges ||= begin - files = Dir[File.expand_path("../../cache/file_data_provider/languages-*", __FILE__)] + @available_language_codes ||= begin + files = Dir[File.expand_path('../cache/file_data_provider/languages-*', __dir__)] files.map! { |f| f[/languages-(.*)\./, 1] } end end diff --git a/lib/i18n_data/file_data_provider.rb b/lib/i18n_data/file_data_provider.rb index b2be72d..4db6b22 100644 --- a/lib/i18n_data/file_data_provider.rb +++ b/lib/i18n_data/file_data_provider.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true module I18nData module FileDataProvider require 'fileutils' @@ -6,7 +7,7 @@ module FileDataProvider extend self def codes(type, language_code) - unless data = read_from_file(cache_file_for(type, language_code)) + unless (data = read_from_file(cache_file_for(type, language_code))) raise NoTranslationAvailable, "#{type}-#{language_code}" end data @@ -16,14 +17,12 @@ def write_cache(provider) languages = provider.codes(:languages, 'EN').keys + ['zh_CN', 'zh_TW', 'zh_HK', 'bn_IN', 'pt_BR', 'sr@latin'] languages.map do |language_code| [:languages, :countries].each do |type| - begin - data = provider.send(:codes, type, language_code) - write_to_file(data, cache_file_for(type, language_code)) - rescue NoTranslationAvailable - $stderr.puts "No translation available for #{type} #{language_code}" if $DEBUG - rescue AccessDenied - $stderr.puts "Access denied for #{type} #{language_code}" - end + data = provider.send(:codes, type, language_code) + write_to_file(data, cache_file_for(type, language_code)) + rescue NoTranslationAvailable + warn "No translation available for #{type} #{language_code}" if $DEBUG + rescue AccessDenied + warn "Access denied for #{type} #{language_code}" end end end @@ -33,7 +32,7 @@ def write_cache(provider) def read_from_file(file) return nil unless File.exist?(file) data = {} - File.readlines(file, :encoding => 'utf-8').each do |line| + File.readlines(file, encoding: 'utf-8').each do |line| code, translation = line.strip.split(DATA_SEPARATOR, 2) data[code] = translation end @@ -43,12 +42,10 @@ def read_from_file(file) def write_to_file(data, file) return if data.empty? FileUtils.mkdir_p File.dirname(file) - File.open(file,'w') do |f| - f.write data.map{|code, translation| "#{code}#{DATA_SEPARATOR}#{translation}" } * "\n" - end + File.write(file, data.map { |code, translation| "#{code}#{DATA_SEPARATOR}#{translation}" } * "\n") end - def cache_file_for(type,language_code) + def cache_file_for(type, language_code) file = "#{type}-#{language_code.sub('-', '_').upcase}" File.join(File.dirname(__FILE__), '..', '..', 'cache', 'file_data_provider', "#{file}.txt") end diff --git a/lib/i18n_data/live_data_provider.rb b/lib/i18n_data/live_data_provider.rb index ac27286..eba4f54 100644 --- a/lib/i18n_data/live_data_provider.rb +++ b/lib/i18n_data/live_data_provider.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'json' require 'simple_po_parser' @@ -7,14 +8,14 @@ module LiveDataProvider extend self JSON_CODES = { - :countries => 'data/iso_3166-1.json', - :languages => 'data/iso_639-2.json' - } + countries: 'data/iso_3166-1.json', + languages: 'data/iso_639-2.json' + }.freeze TRANSLATIONS = { - :countries => 'iso_3166-1/', - :languages => 'iso_639-2/' - } + countries: 'iso_3166-1/', + languages: 'iso_639-2/' + }.freeze REPO = "https://salsa.debian.org/iso-codes-team/iso-codes.git" CLONE_DEST = "/tmp/i18n_data_iso_clone" @@ -34,7 +35,7 @@ def clear_cache raise unless $?.success? end - private + private def ensure_checkout unless File.exist?(CLONE_DEST) @@ -46,10 +47,8 @@ def ensure_checkout def translated(type, language_code) @translated ||= {} - @translated["#{type}_#{language_code}"] ||= begin - Hash[send("alpha_codes_for_#{type}").map do |alpha2, alpha3| - [alpha2, translate(type, alpha3, language_code) || fallback_name(type, alpha3)] - end] + @translated["#{type}_#{language_code}"] ||= send("alpha_codes_for_#{type}").transform_values do |alpha3| + translate(type, alpha3, language_code) || fallback_name(type, alpha3) end end @@ -68,7 +67,7 @@ def translations(type, language_code) begin file_path = "#{CLONE_DEST}/#{TRANSLATIONS[type]}#{code}.po" data = SimplePoParser.parse(file_path) - data = data[1..-1] # Remove the initial info block in the .po file + data = data[1..] # Remove the initial info block in the .po file # Prefer the "Common name for" blocks, but fallback to "Name for" blocks common_names = get_po_data(data, 'Common name for') @@ -90,7 +89,7 @@ def get_po_data(data, extracted_comment_string) # Maps over the alpha3 country code in the 'extracted_comment' # Eg: "Name for GBR" po_entries.map.with_object({}) do |t, translations| - alpha3 = t[:extracted_comment][-3..-1].upcase + alpha3 = t[:extracted_comment][-3..].upcase translation = t[:msgstr] translations[alpha3] = translation.is_a?(Array) ? translation.join : translation end @@ -118,7 +117,7 @@ def english_languages json(:languages)['639-2'].each do |entry| name = entry['name'].to_s code = entry['alpha_2'].to_s.upcase - next if code.empty? or name.empty? + next if code.empty? || name.empty? codes[code] = name end codes diff --git a/lib/i18n_data/version.rb b/lib/i18n_data/version.rb index bd6f2db..a4cfa84 100644 --- a/lib/i18n_data/version.rb +++ b/lib/i18n_data/version.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true module I18nData - VERSION = Version = "0.17.1" + VERSION = Version = "0.17.1" # rubocop:disable Naming/ConstantName end diff --git a/spec/benchmark.rb b/spec/benchmark.rb index d385392..cc9428f 100644 --- a/spec/benchmark.rb +++ b/spec/benchmark.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true # Benchmark performance by looking up every available language's country and # language translations multiple times. require 'benchmark' require 'i18n_data' -types = %i(countries languages) +types = [:countries, :languages] type_codes = {} types.each do |type| @@ -18,12 +19,10 @@ 10.times do type_codes.each_pair do |type, codes| codes.each do |code| - begin - I18nData.send(type, code).keys.each do |key| - I18nData.send(type, code)[key] - end - rescue I18nData::NoTranslationAvailable + I18nData.send(type, code).each_key do |key| + I18nData.send(type, code)[key] end + rescue I18nData::NoTranslationAvailable end end end diff --git a/spec/i18n_data/file_data_provider_spec.rb b/spec/i18n_data/file_data_provider_spec.rb index face1d0..a23eeeb 100644 --- a/spec/i18n_data/file_data_provider_spec.rb +++ b/spec/i18n_data/file_data_provider_spec.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true require "spec_helper" require 'i18n_data/file_data_provider' describe I18nData::FileDataProvider do - let(:cache_file) { I18nData::FileDataProvider.send(:cache_file_for,"XX","YY") } + let(:cache_file) { I18nData::FileDataProvider.send(:cache_file_for, "XX", "YY") } around do |test| `rm -f #{cache_file}` @@ -10,29 +11,29 @@ `rm -f #{cache_file}` end - def read(x,y) - I18nData::FileDataProvider.codes(x,y) + def read(x, y) + I18nData::FileDataProvider.codes(x, y) end it "always produces uppercase locales to avoid case-sensitive madness" do - cache_file = I18nData::FileDataProvider.send(:cache_file_for,"countries","yy_YY") + cache_file = I18nData::FileDataProvider.send(:cache_file_for, "countries", "yy_YY") File.basename(cache_file).should == "countries-YY_YY.txt" end it "always convert hyphen (-) to underscore (_) in locale names" do - cache_file = I18nData::FileDataProvider.send(:cache_file_for,"countries","yy-YY") + cache_file = I18nData::FileDataProvider.send(:cache_file_for, "countries", "yy-YY") File.basename(cache_file).should == "countries-YY_YY.txt" end it "preserves data when writing and then reading" do - data = {"x"=>"y","z"=>"w"} + data = { "x" => "y", "z" => "w" } I18nData::FileDataProvider.send(:write_to_file, data, cache_file) - read("XX","YY").should == data + read("XX", "YY").should == data end it "does not write empty data sets" do - I18nData::FileDataProvider.send(:write_to_file,{}, cache_file) - lambda { read("XX","YY") }.should raise_error I18nData::NoTranslationAvailable + I18nData::FileDataProvider.send(:write_to_file, {}, cache_file) + -> { read("XX", "YY") }.should raise_error I18nData::NoTranslationAvailable end it "reads taiwan correctly" do diff --git a/spec/i18n_data/live_data_provider_spec.rb b/spec/i18n_data/live_data_provider_spec.rb index 6071c7e..fe40568 100644 --- a/spec/i18n_data/live_data_provider_spec.rb +++ b/spec/i18n_data/live_data_provider_spec.rb @@ -1,7 +1,7 @@ +# frozen_string_literal: true require "spec_helper" require "i18n_data/live_data_provider" # fetching is tested via spec/i18n_data_spec.rb, these are just unit-tests describe I18nData::LiveDataProvider do - end diff --git a/spec/i18n_data_spec.rb b/spec/i18n_data_spec.rb index b86fa06..ccbb47c 100644 --- a/spec/i18n_data_spec.rb +++ b/spec/i18n_data_spec.rb @@ -1,4 +1,4 @@ -# encoding: utf-8 +# frozen_string_literal: true require "spec_helper" NUM_2_LETTER_LANGUAGES = 184 @@ -9,7 +9,7 @@ require "i18n_data/file_data_provider" def blank_keys_or_values(hash) - hash.detect{|k,v| k.to_s.empty? or v.to_s.empty?} + hash.detect { |k, v| k.to_s.empty? or v.to_s.empty? } end around do |t| @@ -40,7 +40,7 @@ def blank_keys_or_values(hash) describe ".languages" do it "raises NoTranslationAvailable for unavailable languages" do - lambda{I18nData.languages('XX')}.should raise_error(I18nData::NoTranslationAvailable) + -> { I18nData.languages('XX') }.should raise_error(I18nData::NoTranslationAvailable) end it "is cached" do @@ -51,7 +51,7 @@ def blank_keys_or_values(hash) describe "english" do it "does not contain blanks" do - blank_keys_or_values(I18nData.languages).should eq nil + blank_keys_or_values(I18nData.languages).should be_nil end it "has english as default" do @@ -77,7 +77,7 @@ def blank_keys_or_values(hash) end it "does not contain blanks" do - blank_keys_or_values(I18nData.languages('GL')).should eq nil + blank_keys_or_values(I18nData.languages('GL')).should be_nil end it "is written in unicode" do @@ -111,7 +111,7 @@ def blank_keys_or_values(hash) end it "does not contain blanks" do - blank_keys_or_values(I18nData.countries).should eq nil + blank_keys_or_values(I18nData.countries).should be_nil end it "contains all countries" do @@ -149,7 +149,7 @@ def blank_keys_or_values(hash) end it "does not contain blanks" do - blank_keys_or_values(I18nData.countries('GL')).should eq nil + blank_keys_or_values(I18nData.countries('GL')).should be_nil end it "is written in unicode" do @@ -174,8 +174,12 @@ def blank_keys_or_values(hash) end describe :country_code do - before :all do + around do |t| + old = I18nData.data_provider I18nData.data_provider = I18nData::FileDataProvider + t.call + ensure + I18nData.data_provider = old end it "recognises a countries name" do @@ -187,7 +191,7 @@ def blank_keys_or_values(hash) end it "returns nil when it cannot recognise" do - I18nData.country_code('XY').should eq nil + I18nData.country_code('XY').should be_nil end it "can find languages that are not in english list" do @@ -218,7 +222,7 @@ def blank_keys_or_values(hash) end it "returns nil when it cannot recognise" do - I18nData.language_code('XY').should eq nil + I18nData.language_code('XY').should be_nil end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2dbc20d..6a7107f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true $LOAD_PATH << 'lib' require 'i18n_data'