ruby-enum 0.9.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/Dangerfile +2 -0
  4. data/Gemfile +8 -5
  5. data/Gemfile.lock +147 -82
  6. data/README.md +61 -2
  7. data/Rakefile +7 -0
  8. data/benchmarks/case.rb +45 -0
  9. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png +0 -0
  10. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
  11. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png +0 -0
  12. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png +0 -0
  13. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
  14. data/coverage/assets/0.13.2/application.css +1 -0
  15. data/coverage/assets/0.13.2/application.js +7 -0
  16. data/coverage/assets/0.13.2/colorbox/border.png +0 -0
  17. data/coverage/assets/0.13.2/colorbox/controls.png +0 -0
  18. data/coverage/assets/0.13.2/colorbox/loading.gif +0 -0
  19. data/coverage/assets/0.13.2/colorbox/loading_background.png +0 -0
  20. data/coverage/assets/0.13.2/favicon_green.png +0 -0
  21. data/coverage/assets/0.13.2/favicon_red.png +0 -0
  22. data/coverage/assets/0.13.2/favicon_yellow.png +0 -0
  23. data/coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  24. data/coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  25. data/coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  26. data/coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  27. data/coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  28. data/coverage/assets/0.13.2/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  29. data/coverage/assets/0.13.2/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  30. data/coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  31. data/coverage/assets/0.13.2/images/ui-icons_222222_256x240.png +0 -0
  32. data/coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png +0 -0
  33. data/coverage/assets/0.13.2/images/ui-icons_454545_256x240.png +0 -0
  34. data/coverage/assets/0.13.2/images/ui-icons_888888_256x240.png +0 -0
  35. data/coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png +0 -0
  36. data/coverage/assets/0.13.2/loading.gif +0 -0
  37. data/coverage/assets/0.13.2/magnify.png +0 -0
  38. data/coverage/index.html +5768 -973
  39. data/lib/ruby-enum/enum/case.rb +84 -0
  40. data/lib/ruby-enum/enum/i18n_mock.rb +19 -0
  41. data/lib/ruby-enum/enum.rb +9 -3
  42. data/lib/ruby-enum/errors/base.rb +4 -4
  43. data/lib/ruby-enum/version.rb +1 -1
  44. data/lib/ruby-enum.rb +15 -3
  45. data/pkg/ruby-enum-0.9.0.gem +0 -0
  46. data/ruby-enum.gemspec +2 -1
  47. data/spec/ruby-enum/enum/case_spec.rb +119 -0
  48. data/spec/ruby-enum/enum_spec.rb +201 -71
  49. data/spec/spec_helper.rb +2 -2
  50. data/spec_i18n/Gemfile +9 -0
  51. data/spec_i18n/Rakefile +12 -0
  52. data/spec_i18n/spec/i18n_spec.rb +50 -0
  53. data/spec_i18n/spec/spec_helper.rb +8 -0
  54. metadata +45 -20
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Enum
5
+ ##
6
+ # Adds a method to an enum class that allows for exhaustive matching on a value.
7
+ #
8
+ # @example
9
+ # class Color
10
+ # include Ruby::Enum
11
+ # include Ruby::Enum::Case
12
+ #
13
+ # define :RED, :red
14
+ # define :GREEN, :green
15
+ # define :BLUE, :blue
16
+ # define :YELLOW, :yellow
17
+ # end
18
+ #
19
+ # Color.case(Color::RED, {
20
+ # [Color::RED, Color::GREEN] => -> { "red or green" },
21
+ # Color::BLUE => -> { "blue" },
22
+ # Color::YELLOW => -> { "yellow" },
23
+ # })
24
+ #
25
+ # Reserves the :else key for a default case:
26
+ # Color.case(Color::RED, {
27
+ # [Color::RED, Color::GREEN] => -> { "red or green" },
28
+ # else: -> { "blue or yellow" },
29
+ # })
30
+ module Case
31
+ def self.included(klass)
32
+ klass.extend(ClassMethods)
33
+ end
34
+
35
+ ##
36
+ # @see Ruby::Enum::Case
37
+ module ClassMethods
38
+ class ValuesNotDefinedError < StandardError
39
+ end
40
+
41
+ class NotAllCasesHandledError < StandardError
42
+ end
43
+
44
+ def case(value, cases)
45
+ validate_cases(cases)
46
+
47
+ filtered_cases = cases.select do |values, _proc|
48
+ values = [values] unless values.is_a?(Array)
49
+ values.include?(value)
50
+ end
51
+
52
+ return call_proc(cases[:else], value) if filtered_cases.none?
53
+
54
+ results = filtered_cases.map { |_values, proc| call_proc(proc, value) }
55
+
56
+ # Return the first result if there is only one result
57
+ results.size == 1 ? results.first : results
58
+ end
59
+
60
+ private
61
+
62
+ def call_proc(proc, value)
63
+ return if proc.nil?
64
+
65
+ if proc.arity == 1
66
+ proc.call(value)
67
+ else
68
+ proc.call
69
+ end
70
+ end
71
+
72
+ def validate_cases(cases)
73
+ all_values = cases.keys.flatten - [:else]
74
+ else_defined = cases.key?(:else)
75
+ superfluous_values = all_values - values
76
+ missing_values = values - all_values
77
+
78
+ raise ValuesNotDefinedError, "Value(s) not defined: #{superfluous_values.join(', ')}" if superfluous_values.any?
79
+ raise NotAllCasesHandledError, "Not all cases handled: #{missing_values.join(', ')}" if missing_values.any? && !else_defined
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Ruby
5
+ module Enum
6
+ ##
7
+ # Mock I18n module in case the i18n gem is not available.
8
+ module I18nMock
9
+ def self.load_path
10
+ []
11
+ end
12
+
13
+ def self.translate(key, _options = {})
14
+ key
15
+ end
16
+ end
17
+ end
18
+ end
19
+ # :nocov:
@@ -2,6 +2,11 @@
2
2
 
3
3
  module Ruby
4
4
  module Enum
5
+ class << self
6
+ # Needed for I18n mock
7
+ attr_accessor :i18n
8
+ end
9
+
5
10
  attr_reader :key, :value
6
11
 
7
12
  def initialize(key, value)
@@ -14,6 +19,9 @@ module Ruby
14
19
  base.extend ClassMethods
15
20
 
16
21
  base.private_class_method(:new)
22
+
23
+ base.instance_variable_set(:@_enum_hash, {})
24
+ base.instance_variable_set(:@_enums_by_value, {})
17
25
  end
18
26
 
19
27
  module ClassMethods
@@ -144,9 +152,7 @@ module Ruby
144
152
  end
145
153
 
146
154
  def to_h
147
- Hash[@_enum_hash.map do |key, enum|
148
- [key, enum.value]
149
- end]
155
+ @_enum_hash.transform_values(&:value)
150
156
  end
151
157
 
152
158
  private
@@ -22,13 +22,13 @@ module Ruby
22
22
  @summary = create_summary(key, attributes)
23
23
  @resolution = create_resolution(key, attributes)
24
24
 
25
- "\nProblem:\n #{@problem}"\
25
+ "\nProblem:\n #{@problem}" \
26
26
  "\nSummary:\n #{@summary}" + "\nResolution:\n #{@resolution}"
27
27
  end
28
28
 
29
- private
29
+ BASE_KEY = 'ruby.enum.errors.messages' # :nodoc:
30
30
 
31
- BASE_KEY = 'ruby.enum.errors.messages' #:nodoc:
31
+ private
32
32
 
33
33
  # Given the key of the specific error and the options hash, translate the
34
34
  # message.
@@ -39,7 +39,7 @@ module Ruby
39
39
  #
40
40
  # Returns a localized error message string.
41
41
  def translate(key, options)
42
- ::I18n.translate("#{BASE_KEY}.#{key}", **{ locale: :en }.merge(options)).strip
42
+ Ruby::Enum.i18n.translate("#{BASE_KEY}.#{key}", locale: :en, **options).strip
43
43
  end
44
44
 
45
45
  # Create the problem.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ruby
4
4
  module Enum
5
- VERSION = '0.9.0'
5
+ VERSION = '1.1.0'
6
6
  end
7
7
  end
data/lib/ruby-enum.rb CHANGED
@@ -1,11 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n'
4
-
5
3
  require 'ruby-enum/version'
6
4
  require 'ruby-enum/enum'
5
+ require 'ruby-enum/enum/case'
6
+ require 'ruby-enum/enum/i18n_mock'
7
+
8
+ # Try to load the I18n gem and provide a mock if it is not available.
9
+ begin
10
+ require 'i18n'
11
+ Ruby::Enum.i18n = I18n
12
+ rescue LoadError
13
+ # I18n is not available
14
+ # :nocov:
15
+ # Tests for this loading are in the spec_i18n folder
16
+ Ruby::Enum.i18n = Ruby::Enum::I18nMock
17
+ # :nocov:
18
+ end
7
19
 
8
- I18n.load_path << File.join(File.dirname(__FILE__), 'config', 'locales', 'en.yml')
20
+ Ruby::Enum.i18n.load_path << File.join(File.dirname(__FILE__), 'config', 'locales', 'en.yml')
9
21
 
10
22
  require 'ruby-enum/errors/base'
11
23
  require 'ruby-enum/errors/uninitialized_constant_error'
Binary file
data/ruby-enum.gemspec CHANGED
@@ -10,10 +10,11 @@ Gem::Specification.new do |s|
10
10
  s.email = 'dblock@dblock.org'
11
11
  s.platform = Gem::Platform::RUBY
12
12
  s.required_rubygems_version = '>= 1.3.6'
13
+ s.required_ruby_version = '>= 2.7'
13
14
  s.files = Dir['**/*']
14
15
  s.require_paths = ['lib']
15
16
  s.homepage = 'http://github.com/dblock/ruby-enum'
16
17
  s.licenses = ['MIT']
17
18
  s.summary = 'Enum-like behavior for Ruby.'
18
- s.add_dependency 'i18n'
19
+ s.metadata['rubygems_mfa_required'] = 'true'
19
20
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ module Case
6
+ class Colors
7
+ include Ruby::Enum
8
+ include Ruby::Enum::Case
9
+
10
+ define :RED, :red
11
+ define :GREEN, :green
12
+ define :BLUE, :blue
13
+ end
14
+ end
15
+
16
+ RSpec.describe Ruby::Enum::Case do
17
+ describe '.case' do
18
+ context 'when all cases are defined' do
19
+ subject { Case::Colors.case(Case::Colors::RED, cases) }
20
+
21
+ let(:cases) do
22
+ {
23
+ [Case::Colors::RED, Case::Colors::GREEN] => -> { 'red or green' },
24
+ Case::Colors::BLUE => -> { 'blue' }
25
+ }
26
+ end
27
+
28
+ it { is_expected.to eq('red or green') }
29
+
30
+ context 'when the value is nil' do
31
+ subject { Case::Colors.case(nil, cases) }
32
+
33
+ it { is_expected.to be_nil }
34
+ end
35
+
36
+ context 'when the value is empty' do
37
+ subject { Case::Colors.case('', cases) }
38
+
39
+ it { is_expected.to be_nil }
40
+ end
41
+
42
+ context 'when the value is the value of the enum' do
43
+ subject { Case::Colors.case(:red, cases) }
44
+
45
+ it { is_expected.to eq('red or green') }
46
+ end
47
+
48
+ context 'when the value is used inside the lambda' do
49
+ subject { Case::Colors.case(Case::Colors::RED, cases) }
50
+
51
+ let(:cases) do
52
+ {
53
+ [Case::Colors::RED, Case::Colors::GREEN] => ->(color) { "is #{color}" },
54
+ Case::Colors::BLUE => -> { 'blue' }
55
+ }
56
+ end
57
+
58
+ it { is_expected.to eq('is red') }
59
+ end
60
+ end
61
+
62
+ context 'when there are mutliple matches' do
63
+ subject do
64
+ Case::Colors.case(
65
+ Case::Colors::RED,
66
+ {
67
+ [Case::Colors::RED, Case::Colors::GREEN] => -> { 'red or green' },
68
+ Case::Colors::RED => -> { 'red' },
69
+ Case::Colors::BLUE => -> { 'blue' }
70
+ }
71
+ )
72
+ end
73
+
74
+ it { is_expected.to eq(['red or green', 'red']) }
75
+ end
76
+
77
+ context 'when not all cases are defined' do
78
+ it 'raises an error' do
79
+ expect do
80
+ Case::Colors.case(
81
+ Case::Colors::RED,
82
+ { [Case::Colors::RED, Case::Colors::GREEN] => -> { 'red or green' } }
83
+ )
84
+ end.to raise_error(Ruby::Enum::Case::ClassMethods::NotAllCasesHandledError)
85
+ end
86
+ end
87
+
88
+ context 'when not all cases are defined but :else is specified (default case)' do
89
+ it 'does not raise an error' do
90
+ expect do
91
+ result = Case::Colors.case(
92
+ Case::Colors::BLUE,
93
+ {
94
+ [Case::Colors::RED, Case::Colors::GREEN] => -> { 'red or green' },
95
+ else: -> { 'blue' }
96
+ }
97
+ )
98
+
99
+ expect(result).to eq('blue')
100
+ end.not_to raise_error
101
+ end
102
+ end
103
+
104
+ context 'when a superfluous case is defined' do
105
+ it 'raises an error' do
106
+ expect do
107
+ Case::Colors.case(
108
+ Case::Colors::RED,
109
+ {
110
+ [Case::Colors::RED, Case::Colors::GREEN] => -> { 'red or green' },
111
+ Case::Colors::BLUE => -> { 'blue' },
112
+ :something => -> { 'green' }
113
+ }
114
+ )
115
+ end.to raise_error(Ruby::Enum::Case::ClassMethods::ValuesNotDefinedError)
116
+ end
117
+ end
118
+ end
119
+ end