literal_enums 1.1.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0ac7656b0ea18bc915ff4bfda4b341e9a9923b3ee84775a834158589ea09e21
4
- data.tar.gz: aa5810e0cd16ae43906aff642757f51bf23bc83fd1a18cba30324614314bf54d
3
+ metadata.gz: b7e6724b81f59fbf8d701b0567dc17c344c53c677c318992a7a799acf3242c05
4
+ data.tar.gz: 735a34d71b23525d8285e4fccb39063946ba1af6ff691081471d9b14f869bc40
5
5
  SHA512:
6
- metadata.gz: 373cc1046ac9963b82af06e94f85a1d1a8dd78647a1ed3d24a6d2f577dcc3aabfecabbcb4966ef34b516f55f0ff27de0c0b86b5d269e15543425aa6939782fa5
7
- data.tar.gz: 293b1b49906a3972bf96a46f9a710f58389bf0d205503cb1481f8c9af11964055578d5a98d6ac83652c303566cdd20661015018a49b7db97272024440716b496
6
+ metadata.gz: 4331f3a30ca04be1eb49fb20463a673eb36a9a721fab8ae7fd0fb8ecab1a1c37d0ba9e43b34b20416230f43fac8f3c738cfff0127b4cfdc8feb3a5d30f1ea064
7
+ data.tar.gz: d25213819f531fb985c7556171a8fc4820d87672f7bad03a41c1cdda26cb7c20c0974db96f832016aba3251a1581337f0000aebe7c4a00fb128884266e67de19
@@ -0,0 +1,3 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: [joeldrapper]
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ tests:
11
+ strategy:
12
+ matrix:
13
+ os: ['ubuntu-latest', 'macos-latest']
14
+ ruby-version: ['2.7', '3.0', '3.1', 'head']
15
+ runs-on: ${{ matrix.os }}
16
+ steps:
17
+ - uses: actions/checkout@v3
18
+
19
+ - name: Setup
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ${{ matrix.ruby-version }}
23
+ bundler-cache: false
24
+
25
+ - name: Install dependencies
26
+ run: bundle install
27
+
28
+ - name: Tests
29
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -6,3 +6,5 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ Gemfile.lock
10
+ .DS_Store
data/CODE_OF_CONDUCT.md CHANGED
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
55
55
  ## Enforcement
56
56
 
57
57
  Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at joel.drapper@shopify.com. All
58
+ reported by contacting the project team at joel@drapper.me. All
59
59
  complaints will be reviewed and investigated and will result in a response that
60
60
  is deemed necessary and appropriate to the circumstances. The project team is
61
61
  obligated to maintain confidentiality with regard to the reporter of an incident.
data/Gemfile CHANGED
@@ -1,6 +1,10 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ source "https://rubygems.org"
4
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
4
5
 
5
- # Specify your gem's dependencies in literal_enums.gemspec
6
6
  gemspec
7
+
8
+ gem "rake"
9
+ gem "minitest"
10
+ gem "benchmark-ips"
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ This documentation is for Literal Enums 2.0, which is not yet supported by Literal Enums Rails. You can see documentaiton for Literal Enums v1 [here](https://github.com/joeldrapper/literal_enums/tree/d49eeba40f01f24c6b26d4dcdc6abb0da28e9ccb#readme).
2
+
1
3
  # literal_enums
2
4
 
3
5
  Literal Enums makes Enumerations first-class citizens in Ruby, providing a literal definition syntax.
@@ -8,9 +10,9 @@ You can define an enum by subclassing `Enum` and using the literal syntax.
8
10
 
9
11
  ```ruby
10
12
  class Color < Enum
11
- Red()
12
- Green()
13
- Blue()
13
+ Red
14
+ Green
15
+ Blue
14
16
  end
15
17
  ```
16
18
 
@@ -20,7 +22,7 @@ Here we’ve enumerated `Color::Red`, `Color::Green`, and `Color::Blue` constant
20
22
  Color::Red.is_a?(Color) # returns true
21
23
  ```
22
24
 
23
- Enum classes have synthetic methods for looking up their members. `Color.members` will return a `Set` of members of the `Color` enumeration: `Color::Red`, `Color::Green`, and `Color::Blue`.
25
+ Enum classes have synthetic methods for looking up their members. `Color.members` will return an `Array` of members of the `Color` enumeration: `Color::Red`, `Color::Green`, and `Color::Blue`.
24
26
 
25
27
  Members also have polymorphic predicate methods for each member of the enumeration in lower-snake-case.
26
28
 
@@ -46,7 +48,7 @@ end
46
48
  Then we can look up all the values.
47
49
 
48
50
  ```ruby
49
- Color.values # returns a Set of "ff0000", "00ff00", "0000f".
51
+ Color.values # returns an Array of ["ff0000", "00ff00", "0000f"].
50
52
  ```
51
53
 
52
54
  We can also look at the value for any member directly.
@@ -90,50 +92,20 @@ It is also possible to define a more complex state machine by defining `transiti
90
92
 
91
93
  ```ruby
92
94
  class State < Enum
93
- Pending do
94
- def transitions_to
95
- [Approved, Rejected]
96
- end
97
- end
98
-
99
- Approved do
100
- def transitions_to
101
- Published
102
- end
103
- end
104
-
105
- Rejected do
106
- def transitions_to
107
- Deleted
108
- end
109
- end
95
+ Pending -> { [Approved, Rejected] }
96
+ Approved -> { Published }
97
+ Rejected -> { Deleted }
110
98
 
111
- Deleted()
112
- Published()
99
+ Deleted
100
+ Published
113
101
  end
114
102
  ```
115
103
 
116
- Given the above definition, we can transition from one state to another by calling `transition_to` with the newly desired state. This will raise a `LiteralEnums::TransitionError` if the transition is invalid.
117
-
118
- ```ruby
119
- State::Pending.transition_to(State::Approved) # returns State::Approved.
120
- State::Pending.transition_to(State::Published) # raises a LiteralEnums::TransitionError.
121
- ```
122
-
123
- Alternatively, we can call the new state as a method on the old state.
124
-
125
- ```ruby
126
- State::Pending.approved # returns State::Approved.
127
- ```
128
-
129
- An invalid transition would return a `NoMethodErorr` in this case as the method is not defined.
130
-
131
- The advantage of using method calls to transition from one state to anotehr is the method calls can be chained.
104
+ Given the above definition, we can transition from one state to another by calling `>>` with the newly desired state. This will raise a `LiteralEnums::TransitionError` if the transition is invalid.
132
105
 
133
106
  ```ruby
134
- State::Pending.approved.published # returns State::Published, since it's valid to
135
- # move to State::Published from State::Approved and it's
136
- # valid to move to State::Approved from State::Pending.
107
+ State::Pending >> State::Approved # returns State::Approved.
108
+ State::Pending >> State::Published # raises a LiteralEnums::TransitionError.
137
109
  ```
138
110
 
139
111
  ## Installation
data/bench.rb ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "literal_enums"
5
+ require "benchmark/ips"
6
+
7
+ class Color < Enum
8
+ Red("#f00")
9
+ Green("#0f0")
10
+ Blue("#00f")
11
+ end
12
+
13
+ Benchmark.ips do |x|
14
+ x.report("Cast") { Color.cast("f00") }
15
+ x.report("Lookup") { Color::Red }
16
+ x.report("Members") { Color.members }
17
+ x.report("Values") { Color.values }
18
+ end
data/lib/enum.rb CHANGED
@@ -7,57 +7,94 @@ class Enum
7
7
  alias_method :inspect, :name
8
8
 
9
9
  def initialize(name, value)
10
+ @short_name = name.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase!
10
11
  @name = "#{self.class.name}::#{name}"
11
- @value = value || @name
12
+ @value = value
12
13
  end
13
14
 
14
15
  class << self
16
+ attr_reader :members
17
+
18
+ def inherited(child)
19
+ TracePoint.new(:end) do |tp|
20
+ if tp.self == child
21
+ tp.self.freeze
22
+ tp.disable
23
+ end
24
+ end.enable
25
+
26
+ child.instance_eval do
27
+ @values = {}
28
+ @members = []
29
+ end
30
+ end
31
+
32
+ def const_missing(name)
33
+ return super if frozen?
34
+ new(name)
35
+ end
36
+
15
37
  def method_missing(name, *args, **kwargs, &block)
16
- return super unless name[0] == name[0].upcase
38
+ return super if frozen?
39
+ return super unless name[0] =~ /[A-Z]/
17
40
  new(name, *args, **kwargs, &block)
18
41
  end
19
42
 
20
43
  def cast(value)
21
- members.find { |v| v.value == value.to_s }
44
+ @values[value]
22
45
  end
23
46
 
24
47
  def values
25
- map(&:value).to_set
48
+ @values.keys
26
49
  end
27
50
 
28
51
  def each(&block)
29
- members.each(&block)
30
- end
31
-
32
- def members
33
- constants.map { |c| const_get(c) }.to_set
52
+ @members.each(&block)
34
53
  end
35
54
 
36
55
  private
37
56
 
38
- def new(name, value = nil, &block)
39
- if self == Enum
40
- raise ArgumentError, "You can't add values to the abstract Enum class itself."
57
+ def new(name, a = nil, b = nil, &block)
58
+
59
+ # If only one positional argument is provided and it's a proc, treat it as the transitions_to definition. Otherwise, the first argument is the value and the second argument is the transitions_to definition.
60
+
61
+ if !b && Proc === a
62
+ transitions_to = a
63
+ value = name
64
+ else
65
+ value = a || name
66
+ transitions_to = b
41
67
  end
42
68
 
43
- if constants.include?(name)
44
- raise ArgumentError, "Name conflict: '#{self.name}::#{name}' is already defined."
69
+ if self == Enum
70
+ raise ArgumentError,
71
+ "You can't add values to the abstract Enum class itself."
45
72
  end
46
73
 
47
- if values.include?(value)
48
- raise ArgumentError, "Value conflict: the value '#{value}' is defined for '#{self.cast(value).name}'."
74
+ if const_defined?(name)
75
+ raise ArgumentError,
76
+ "Name conflict: '#{self.name}::#{name}' is already defined."
49
77
  end
50
78
 
51
- member = super(name, value)
52
- member.instance_eval(&block) if block_given?
79
+ if @values[value]
80
+ raise ArgumentError,
81
+ "Value conflict: the value '#{value}' is defined for '#{self.cast(value).name}'."
82
+ end
53
83
 
54
84
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
55
- def #{name.to_s.underscore}?
56
- name.demodulize.underscore == "#{name.to_s.underscore}"
85
+ def #{name.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase!}?
86
+ @short_name == "#{name.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase!}"
57
87
  end
58
88
  RUBY
59
89
 
60
- const_set(name, member.freeze)
90
+ member = super(name, value)
91
+ member.instance_eval(&block) if block_given?
92
+ member.define_singleton_method(:transitions_to, transitions_to) if transitions_to
93
+ member.freeze
94
+
95
+ const_set(name, member)
96
+ @members << member
97
+ @values[value] = member
61
98
  end
62
99
  end
63
100
  end
@@ -1,32 +1,30 @@
1
1
  module LiteralEnums
2
2
  module Transitions
3
- def respond_to_missing?(name, include_private = false)
4
- return false if name == :transitions_to
5
- valid_next_states.any? { |m| m.name.to_s.demodulize.underscore.to_sym == name }
6
- end
3
+ def >>(new_state)
4
+ return self if new_state == self
7
5
 
8
- def method_missing(name, *args, **kwargs, &block)
9
- valid_next_states.find { |m| m.name.to_s.demodulize.underscore.to_sym == name } || super
6
+ if transitions_to?(new_state)
7
+ new_state
8
+ else
9
+ raise TransitionError,
10
+ "You can't transition from #{self.name} to #{new_state.name}."
11
+ end
10
12
  end
11
13
 
12
- def transition_to(new_state)
13
- return new_state if can_transition_to?(new_state)
14
+ def transitions_to?(new_state)
15
+ possible_states = transitions_to
14
16
 
15
- raise TransitionError, "You can't transition from #{self.name} to #{new_state.name}."
16
- end
17
-
18
- def can_transition_to?(new_state)
19
- unless new_state.is_a?(self.class)
20
- raise ArgumentError "You can only transition to another #{self.class.name}."
17
+ case possible_states
18
+ when Enum
19
+ possible_states == new_state
20
+ when Array
21
+ possible_states.include?(new_state)
21
22
  end
22
-
23
- valid_next_states.include?(new_state)
24
23
  end
25
24
 
26
25
  private
27
26
 
28
- def valid_next_states
29
- return Array(transitions_to) if respond_to? :transitions_to
27
+ def transitions_to
30
28
  []
31
29
  end
32
30
  end
@@ -1,3 +1,3 @@
1
1
  module LiteralEnums
2
- VERSION = "1.1.1"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/literal_enums.rb CHANGED
@@ -1,11 +1,9 @@
1
- require "active_support"
2
- require "active_support/core_ext/string"
3
-
4
1
  require "literal_enums/version"
5
2
  require "literal_enums/transitions"
6
3
  require "enum"
7
4
 
8
5
  module LiteralEnums
9
- class Error < StandardError; end
10
- class TransitionError < Error; end
6
+ Error = Module.new
7
+ StandardError = Class.new(StandardError) { include Error }
8
+ TransitionError = Class.new(StandardError) { include Error }
11
9
  end
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = "https://github.com/joeldrapper/literal_enums"
19
19
  spec.metadata["changelog_uri"] = "https://github.com/joeldrapper/literal_enums"
20
+ spec.metadata["funding_uri"] = "https://github.com/sponsors/joeldrapper"
20
21
 
21
22
  # Specify which files should be added to the gem when it is released.
22
23
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -28,9 +29,5 @@ Gem::Specification.new do |spec|
28
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
30
  spec.require_paths = ["lib"]
30
31
 
31
- spec.add_development_dependency "bundler", "~> 2.3.7"
32
- spec.add_development_dependency "rake", "~> 12.3.3"
33
- spec.add_development_dependency "minitest", "~> 5.0"
34
-
35
- spec.add_dependency "activesupport", ">= 7.0"
32
+ spec.metadata["rubygems_mfa_required"] = "true"
36
33
  end
metadata CHANGED
@@ -1,71 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: literal_enums
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Drapper
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-01 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 2.3.7
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 2.3.7
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 12.3.3
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 12.3.3
41
- - !ruby/object:Gem::Dependency
42
- name: minitest
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '5.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '5.0'
55
- - !ruby/object:Gem::Dependency
56
- name: activesupport
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '7.0'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '7.0'
11
+ date: 2022-12-14 00:00:00.000000000 Z
12
+ dependencies: []
69
13
  description: A comprehensive Enum library for Ruby with literal-style syntax.
70
14
  email:
71
15
  - joel@drapper.me
@@ -73,15 +17,16 @@ executables: []
73
17
  extensions: []
74
18
  extra_rdoc_files: []
75
19
  files:
76
- - ".github/workflows/ruby.yml"
20
+ - ".github/FUNDING.yml"
21
+ - ".github/workflows/ci.yml"
77
22
  - ".gitignore"
78
23
  - ".ruby-version"
79
24
  - CODE_OF_CONDUCT.md
80
25
  - Gemfile
81
- - Gemfile.lock
82
26
  - LICENSE.txt
83
27
  - README.md
84
28
  - Rakefile
29
+ - bench.rb
85
30
  - bin/console
86
31
  - bin/setup
87
32
  - lib/enum.rb
@@ -96,6 +41,8 @@ metadata:
96
41
  homepage_uri: https://github.com/joeldrapper/literal_enums
97
42
  source_code_uri: https://github.com/joeldrapper/literal_enums
98
43
  changelog_uri: https://github.com/joeldrapper/literal_enums
44
+ funding_uri: https://github.com/sponsors/joeldrapper
45
+ rubygems_mfa_required: 'true'
99
46
  post_install_message:
100
47
  rdoc_options: []
101
48
  require_paths:
@@ -1,24 +0,0 @@
1
- name: Ruby
2
-
3
- on:
4
- push:
5
- branches: [ main ]
6
- pull_request:
7
- branches: [ main ]
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- ruby-version: ['2.7', '3.0']
15
-
16
- steps:
17
- - uses: actions/checkout@v2
18
- - name: Set up Ruby
19
- uses: ruby/setup-ruby@v1
20
- with:
21
- ruby-version: ${{ matrix.ruby-version }}
22
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
23
- - name: Run tests
24
- run: bundle exec rake
data/Gemfile.lock DELETED
@@ -1,34 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- literal_enums (1.1.0)
5
- activesupport (~> 7.0.2.2)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activesupport (7.0.2.3)
11
- concurrent-ruby (~> 1.0, >= 1.0.2)
12
- i18n (>= 1.6, < 2)
13
- minitest (>= 5.1)
14
- tzinfo (~> 2.0)
15
- concurrent-ruby (1.1.9)
16
- i18n (1.10.0)
17
- concurrent-ruby (~> 1.0)
18
- minitest (5.15.0)
19
- rake (12.3.3)
20
- tzinfo (2.0.4)
21
- concurrent-ruby (~> 1.0)
22
-
23
- PLATFORMS
24
- arm64-darwin-21
25
- x86_64-linux
26
-
27
- DEPENDENCIES
28
- bundler (~> 2.3.7)
29
- literal_enums!
30
- minitest (~> 5.0)
31
- rake (~> 12.3.3)
32
-
33
- BUNDLED WITH
34
- 2.3.7