diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 028a2d2..cdbde68 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,3 +24,10 @@ jobs: - run: bundle install --jobs=3 --retry=3 --path=vendor/bundle - run: bundle exec rake spec continue-on-error: ${{ matrix.entry.allowed-failure }} + - name: Specs for when the i18n gem is not available + run: | + cd spec_i18n + bundle install --jobs=3 --retry=3 + pwd + bundle exec rake spec + continue-on-error: ${{ matrix.entry.allowed-failure }} diff --git a/CHANGELOG.md b/CHANGELOG.md index ec2fbe2..eae4f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### 1.0.0 (Next) +* [#41](https://github.com/dblock/ruby-enum/pull/41): Make i18n dependency optional - [@peterfication](https://github.com/peterfication). * [#43](https://github.com/dblock/ruby-enum/pull/43): Add exhaustive case matcher - [@peterfication](https://github.com/peterfication). * [#40](https://github.com/dblock/ruby-enum/pull/39): Enable new Rubocop cops and address/allowlist lints - [@petergoldstein](https://github.com/petergoldstein). * [#39](https://github.com/dblock/ruby-enum/pull/39): Require Ruby >= 2.7 - [@petergoldstein](https://github.com/petergoldstein). diff --git a/README.md b/README.md index 5e6ba08..49ab445 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Enum-like behavior for Ruby, heavily inspired by [this](http://www.rubyfleebie.c - [Duplicate enumerator keys or duplicate values](#duplicate-enumerator-keys-or-duplicate-values) - [Inheritance](#inheritance) - [Exhaustive case matcher](#exhaustive-case-matcher) + - [I18n support](#i18n-support) - [Benchmarks](#benchmarks) - [Contributing](#contributing) - [Copyright and License](#copyright-and-license) @@ -300,6 +301,15 @@ Color.Case(color, { }) ``` +### I18n support + +This gem has an optional dependency to `i18n`. If it's available, the error messages will have a nice description and can be translated. If it's not available, the errors will only contain the message keys. + +```ruby +# Add this to your Gemfile if you want to have a nice error description instead of just a message key. +gem "i18n" +``` + ## Benchmarks Benchmark scripts are defined in the [`benchmarks`](benchmarks) folder and can be run with Rake: diff --git a/lib/ruby-enum.rb b/lib/ruby-enum.rb index 0404a0a..4d9b921 100644 --- a/lib/ruby-enum.rb +++ b/lib/ruby-enum.rb @@ -1,12 +1,23 @@ # frozen_string_literal: true -require 'i18n' - require 'ruby-enum/version' require 'ruby-enum/enum' require 'ruby-enum/enum/case' +require 'ruby-enum/enum/i18n_mock' + +# Try to load the I18n gem and provide a mock if it is not available. +begin + require 'i18n' + Ruby::Enum.i18n = I18n +rescue LoadError + # I18n is not available + # :nocov: + # Tests for this loading are in the spec_i18n folder + Ruby::Enum.i18n = Ruby::Enum::I18nMock + # :nocov: +end -I18n.load_path << File.join(File.dirname(__FILE__), 'config', 'locales', 'en.yml') +Ruby::Enum.i18n.load_path << File.join(File.dirname(__FILE__), 'config', 'locales', 'en.yml') require 'ruby-enum/errors/base' require 'ruby-enum/errors/uninitialized_constant_error' diff --git a/lib/ruby-enum/enum.rb b/lib/ruby-enum/enum.rb index 2c94626..0930e86 100644 --- a/lib/ruby-enum/enum.rb +++ b/lib/ruby-enum/enum.rb @@ -2,6 +2,11 @@ module Ruby module Enum + class << self + # Needed for I18n mock + attr_accessor :i18n + end + attr_reader :key, :value def initialize(key, value) diff --git a/lib/ruby-enum/enum/i18n_mock.rb b/lib/ruby-enum/enum/i18n_mock.rb new file mode 100644 index 0000000..b9adbb9 --- /dev/null +++ b/lib/ruby-enum/enum/i18n_mock.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# :nocov: +module Ruby + module Enum + ## + # Mock I18n module in case the i18n gem is not available. + module I18nMock + def self.load_path + [] + end + + def self.translate(key, _options = {}) + key + end + end + end +end +# :nocov: diff --git a/lib/ruby-enum/errors/base.rb b/lib/ruby-enum/errors/base.rb index 66cc604..5ab2ee1 100644 --- a/lib/ruby-enum/errors/base.rb +++ b/lib/ruby-enum/errors/base.rb @@ -39,7 +39,7 @@ def compose_message(key, attributes = {}) # # Returns a localized error message string. def translate(key, options) - ::I18n.translate("#{BASE_KEY}.#{key}", locale: :en, **options).strip + Ruby::Enum.i18n.translate("#{BASE_KEY}.#{key}", locale: :en, **options).strip end # Create the problem. diff --git a/ruby-enum.gemspec b/ruby-enum.gemspec index 46fe6e0..50da6a9 100644 --- a/ruby-enum.gemspec +++ b/ruby-enum.gemspec @@ -16,6 +16,5 @@ Gem::Specification.new do |s| s.homepage = 'http://github.com/dblock/ruby-enum' s.licenses = ['MIT'] s.summary = 'Enum-like behavior for Ruby.' - s.add_dependency 'i18n' s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/spec/ruby-enum/enum_spec.rb b/spec/ruby-enum/enum_spec.rb index 3cb3b30..bd0f1c9 100644 --- a/spec/ruby-enum/enum_spec.rb +++ b/spec/ruby-enum/enum_spec.rb @@ -23,8 +23,24 @@ class SecondSubclass < FirstSubclass expect(Colors::GREEN).to eq 'green' end - it 'raises UninitializedConstantError on an invalid constant' do - expect { Colors::ANYTHING }.to raise_error Ruby::Enum::Errors::UninitializedConstantError, /The constant Colors::ANYTHING has not been defined./ + context 'when the i18n gem is loaded' do + it 'raises UninitializedConstantError on an invalid constant' do + expect do + Colors::ANYTHING + end.to raise_error Ruby::Enum::Errors::UninitializedConstantError, /The constant Colors::ANYTHING has not been defined./ + end + end + + context 'when the i18n gem is not loaded' do + before do + allow(described_class).to receive(:i18n).and_return(Ruby::Enum::I18nMock) + end + + it 'raises UninitializedConstantError on an invalid constant' do + expect do + Colors::ANYTHING + end.to raise_error Ruby::Enum::Errors::UninitializedConstantError, /ruby.enum.errors.messages.uninitialized_constant.summary/ + end end describe '#each' do @@ -151,22 +167,54 @@ class SecondSubclass < FirstSubclass end context 'when a duplicate key is used' do - it 'raises DuplicateKeyError' do - expect do - Colors.class_eval do - define :RED, 'some' - end - end.to raise_error Ruby::Enum::Errors::DuplicateKeyError, /The constant Colors::RED has already been defined./ + context 'when the i18n gem is loaded' do + it 'raises DuplicateKeyError' do + expect do + Colors.class_eval do + define :RED, 'some' + end + end.to raise_error Ruby::Enum::Errors::DuplicateKeyError, /The constant Colors::RED has already been defined./ + end + end + + context 'when the i18n gem is not loaded' do + before do + allow(described_class).to receive(:i18n).and_return(Ruby::Enum::I18nMock) + end + + it 'raises DuplicateKeyError' do + expect do + Colors.class_eval do + define :RED, 'some' + end + end.to raise_error Ruby::Enum::Errors::DuplicateKeyError, /ruby.enum.errors.messages.duplicate_key.message/ + end end end context 'when a duplicate value is used' do - it 'raises a DuplicateValueError' do - expect do - Colors.class_eval do - define :Other, 'red' - end - end.to raise_error Ruby::Enum::Errors::DuplicateValueError, /The value red has already been defined./ + context 'when the i18n gem is loaded' do + it 'raises a DuplicateValueError' do + expect do + Colors.class_eval do + define :Other, 'red' + end + end.to raise_error Ruby::Enum::Errors::DuplicateValueError, /The value red has already been defined./ + end + end + + context 'when the i18n gem is not loaded' do + before do + allow(described_class).to receive(:i18n).and_return(Ruby::Enum::I18nMock) + end + + it 'raises a DuplicateValueError' do + expect do + Colors.class_eval do + define :Other, 'red' + end + end.to raise_error Ruby::Enum::Errors::DuplicateValueError, /ruby.enum.errors.messages.duplicate_value.summary/ + end end end diff --git a/spec_i18n/Gemfile b/spec_i18n/Gemfile new file mode 100644 index 0000000..84d9f8e --- /dev/null +++ b/spec_i18n/Gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source 'http://rubygems.org' + +gemspec path: '../', name: 'ruby-enum' + +# This Gemfile should not include any gem that has i18n as a dependency. +gem 'rake' +gem 'rspec', '~> 3.0' diff --git a/spec_i18n/Rakefile b/spec_i18n/Rakefile new file mode 100644 index 0000000..9827cdf --- /dev/null +++ b/spec_i18n/Rakefile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rubygems' + +require 'rspec/core' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) do |spec| + spec.pattern = FileList['spec/**/*_spec.rb'] +end + +task default: %i[spec] diff --git a/spec_i18n/spec/i18n_spec.rb b/spec_i18n/spec/i18n_spec.rb new file mode 100644 index 0000000..082ca05 --- /dev/null +++ b/spec_i18n/spec/i18n_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +test_class = Class.new do + include Ruby::Enum + + define :RED, 'red' + define :GREEN, 'green' +end + +describe Ruby::Enum do + context 'when the i18n gem is not loaded' do + it 'raises UninitializedConstantError on an invalid constant' do + expect do + test_class::ANYTHING + end.to raise_error Ruby::Enum::Errors::UninitializedConstantError, /ruby.enum.errors.messages.uninitialized_constant.summary/ + end + end + + context 'when a duplicate key is used' do + context 'when the i18n gem is not loaded' do + before do + allow(described_class).to receive(:i18n).and_return(Ruby::Enum::I18nMock) + end + + it 'raises DuplicateKeyError' do + expect do + test_class.class_eval do + define :RED, 'some' + end + end.to raise_error Ruby::Enum::Errors::DuplicateKeyError, /ruby.enum.errors.messages.duplicate_key.message/ + end + end + end + + context 'when a duplicate value is used' do + context 'when the i18n gem is not loaded' do + before do + allow(described_class).to receive(:i18n).and_return(Ruby::Enum::I18nMock) + end + + it 'raises a DuplicateValueError' do + expect do + test_class.class_eval do + define :Other, 'red' + end + end.to raise_error Ruby::Enum::Errors::DuplicateValueError, /ruby.enum.errors.messages.duplicate_value.summary/ + end + end + end +end diff --git a/spec_i18n/spec/spec_helper.rb b/spec_i18n/spec/spec_helper.rb new file mode 100644 index 0000000..cf56090 --- /dev/null +++ b/spec_i18n/spec/spec_helper.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib')) + +require 'rubygems' + +require 'rspec' +require 'ruby-enum'