enumancer 1.0.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 (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/enum.rb +222 -0
  3. metadata +45 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 84c1bea1a7dd401a30ccc5688a86cfba9829a7cfceb58c5bb60b4124bbbacea2
4
+ data.tar.gz: ec380b6ec4a8d8b219995676508d02572b78ab40eb0080838cb14fded29a7048
5
+ SHA512:
6
+ metadata.gz: 7505cd08d8b118be3dac15c7e29b9b2b1372e93858b1889972e8112d0f6bb33e1246d874cd4423c2fab210f3c71e736455169f57deaa1e2fa391fe7418cc5fff
7
+ data.tar.gz: 5c290f4da876dd4edab3ded6760aa4ee1ccbe7243dbd13fdcae161872b9d72eb49381e8289f229abcaa78978050be0679de1639738fd0fd796f372b050299887
data/lib/enum.rb ADDED
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Enumancer::Enum provides a declarative, type-safe registry for named values.
4
+ # Each entry is unique by both name and value. Values can be of any type,
5
+ # but you may optionally declare a type constraint using `type`.
6
+ # If type is defined, values are strictly checked.
7
+ #
8
+ # Enum entries are registered via `.entry(name, value)` and accessed via `.name` or `[]`.
9
+ # Each entry becomes a singleton method of the class and returns an instance of the enum.
10
+ #
11
+ # You may enable strict mode via `type Klass, strict: true`, which disables fallbacks
12
+ # and raises errors when accessing unknown names or values.
13
+ #
14
+ # @example Define an enum
15
+ # class MyEnum < Enumancer::Enum
16
+ # type Integer, strict: true
17
+ # entry :low, 1
18
+ # entry :high, 2
19
+ # end
20
+ #
21
+ # @example Access values
22
+ # MyEnum[:low].value # => 1
23
+ # MyEnum.high.to_sym # => :low
24
+ # MyEnum.low == MyEnum[:low] # => true
25
+ #
26
+ # @example Serialize to JSON
27
+ # MyEnum.low.to_json # => '{"name":"low","value":1}'
28
+ #
29
+ # @example Deserialize from JSON
30
+ # MyEnum.from_json('{"name":"low"}') # => MyEnum.low
31
+
32
+ require 'json'
33
+
34
+ module Enumancer
35
+ class Enum
36
+ attr_reader :value
37
+
38
+ def initialize(value)
39
+ @value = value
40
+ end
41
+
42
+ # Returns the symbolic name of the value as a string
43
+ #
44
+ # @return [String]
45
+ def to_s
46
+ to_sym.to_s
47
+ end
48
+
49
+ # Returns the symbolic name of the value as a symbol
50
+ #
51
+ # @return [Symbol]
52
+ def to_sym
53
+ name = self.class.name_for(value)
54
+ raise KeyError, "Unregistered enum value: #{value.inspect}" if name.nil? && self.class.strict?
55
+ name || :unknown
56
+ end
57
+
58
+ # Returns a debug-friendly string representation
59
+ #
60
+ # @return [String]
61
+ def inspect
62
+ "#<#{self.class.name} #{to_sym.inspect}:#{value.inspect}>"
63
+ end
64
+
65
+ # Equality based on class and value
66
+ #
67
+ # @param other [Object]
68
+ # @return [Boolean]
69
+ def ==(other)
70
+ other.is_a?(self.class) && other.value == value
71
+ end
72
+
73
+ alias eql? ==
74
+ def hash = value.hash
75
+
76
+ # Serializes the enum to JSON
77
+ #
78
+ # @return [String]
79
+ def to_json(*args)
80
+ { name: to_sym, value: value }.to_json(*args)
81
+ end
82
+
83
+ class << self
84
+ # Called when subclassing Enum
85
+ #
86
+ # @param subclass [Class]
87
+ # @return [void]
88
+ def inherited(subclass)
89
+ subclass.instance_variable_set(:@registry, {})
90
+ subclass.instance_variable_set(:@values, {})
91
+ subclass.instance_variable_set(:@strict_mode, false)
92
+ end
93
+
94
+ # Declares the expected type of all enum values
95
+ #
96
+ # @param klass [Class] the type constraint for values
97
+ # @param strict [Boolean] whether to enable strict mode
98
+ # @return [void]
99
+ def type(klass, strict: false)
100
+ unless klass.is_a?(Class)
101
+ raise ArgumentError, "Expected a Class, got #{klass.inspect}"
102
+ end
103
+ @value_type = klass
104
+ @strict_mode = strict
105
+ end
106
+
107
+ # Returns whether strict mode is enabled
108
+ #
109
+ # @return [Boolean]
110
+ def strict?
111
+ @strict_mode == true
112
+ end
113
+
114
+ # Registers a new enum entry with a unique name and value
115
+ #
116
+ # @param name [Symbol, String] symbolic name of the entry
117
+ # @param value [Object] value of the entry
118
+ # @raise [ArgumentError] if name or value is already registered
119
+ # @raise [TypeError] if value does not match declared type
120
+ # @return [void]
121
+ def entry(name, value)
122
+ name = name.to_sym
123
+
124
+ if defined?(@value_type) && !value.is_a?(@value_type)
125
+ raise TypeError, "Invalid value type for #{name}: expected #{@value_type}, got #{value.class}"
126
+ end
127
+
128
+ if @values.key?(value)
129
+ existing = @values[value]
130
+ raise ArgumentError, "Duplicate value #{value.inspect} for #{name}; already assigned to #{existing}"
131
+ end
132
+
133
+ if @registry.key?(name)
134
+ raise ArgumentError, "Duplicate name #{name}; already registered with value #{@registry[name].value.inspect}"
135
+ end
136
+
137
+ instance = new(value)
138
+ @registry[name] = instance
139
+ @values[value] = name
140
+
141
+ define_singleton_method(name) { instance }
142
+ end
143
+
144
+ # Retrieves an enum instance by name
145
+ #
146
+ # @param name [Symbol, String]
147
+ # @return [Enumancer::Enum, nil]
148
+ def [](name)
149
+ @registry[name.to_sym]
150
+ end
151
+
152
+ # Retrieves an enum instance by name, raising if not found
153
+ #
154
+ # @param name [Symbol, String]
155
+ # @return [Enumancer::Enum]
156
+ # @raise [KeyError]
157
+ def fetch(name)
158
+ @registry.fetch(name.to_sym)
159
+ end
160
+
161
+ # Resolves the symbolic name for a given value
162
+ #
163
+ # @param value [Object]
164
+ # @return [Symbol, nil]
165
+ def name_for(value)
166
+ @values[value]
167
+ end
168
+
169
+ # Returns all registered enum instances
170
+ #
171
+ # @return [Array<Enumancer::Enum>]
172
+ def all
173
+ @registry.values
174
+ end
175
+
176
+ # Returns all registered names
177
+ #
178
+ # @return [Array<Symbol>]
179
+ def keys
180
+ @registry.keys
181
+ end
182
+
183
+ # Returns all raw values
184
+ #
185
+ # @return [Array<Object>]
186
+ def values
187
+ @registry.values.map(&:value)
188
+ end
189
+
190
+ # Removes an enum entry by name
191
+ #
192
+ # @param name [Symbol, String]
193
+ # @return [Enumancer::Enum, nil] the removed entry or nil
194
+ def remove(name)
195
+ name = name.to_sym
196
+ entry = @registry.delete(name)
197
+ if entry
198
+ @values.delete(entry.value)
199
+ singleton_class.undef_method(name) if respond_to?(name)
200
+ end
201
+ entry
202
+ end
203
+
204
+ # Deserializes an enum from JSON
205
+ #
206
+ # @param json [String]
207
+ # @return [Enumancer::Enum, nil]
208
+ # @raise [KeyError] if strict and name is unknown
209
+ def from_json(json)
210
+ data = JSON.parse(json, symbolize_names: true)
211
+ name = data[:name]
212
+ raise KeyError, "Missing 'name' key in JSON" if name.nil?
213
+
214
+ entry = self[name]
215
+ if strict? && entry.nil?
216
+ raise KeyError, "Unregistered enum name: #{name.inspect}"
217
+ end
218
+ entry
219
+ end
220
+ end
221
+ end
222
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: enumancer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Łasačka
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: |2
13
+ Enumancer provides a predictable, type-safe registry for named values.
14
+ Each entry is unique by both name and value, with optional type constraints and strict mode.
15
+ email: saikinmirai@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/enum.rb
21
+ homepage: https://github.com/lasaczka/enumancer
22
+ licenses:
23
+ - BSD-3-Clause-Attribution
24
+ metadata:
25
+ changelog_uri: https://github.com/lasaczka/enumancer/blob/main/CHANGELOG.md
26
+ bug_tracker_uri: https://github.com/lasaczka/enumancer/issues
27
+ documentation_uri: https://rubydoc.info/gems/kolor/1.0.0
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 3.0.0
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubygems_version: 3.8.0.dev
43
+ specification_version: 4
44
+ summary: Easy to use typed enums.
45
+ test_files: []