cattri 0.1.3 → 0.2.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +34 -0
  3. data/.gitignore +72 -0
  4. data/.rubocop.yml +6 -3
  5. data/CHANGELOG.md +41 -0
  6. data/Gemfile +12 -0
  7. data/README.md +163 -151
  8. data/Steepfile +6 -0
  9. data/bin/console +33 -0
  10. data/bin/setup +8 -0
  11. data/cattri.gemspec +5 -5
  12. data/lib/cattri/attribute.rb +119 -155
  13. data/lib/cattri/attribute_compiler.rb +104 -0
  14. data/lib/cattri/attribute_options.rb +183 -0
  15. data/lib/cattri/attribute_registry.rb +155 -0
  16. data/lib/cattri/context.rb +124 -106
  17. data/lib/cattri/context_registry.rb +36 -0
  18. data/lib/cattri/deferred_attributes.rb +73 -0
  19. data/lib/cattri/dsl.rb +54 -0
  20. data/lib/cattri/error.rb +17 -90
  21. data/lib/cattri/inheritance.rb +35 -0
  22. data/lib/cattri/initializer_patch.rb +37 -0
  23. data/lib/cattri/internal_store.rb +104 -0
  24. data/lib/cattri/introspection.rb +56 -49
  25. data/lib/cattri/version.rb +3 -1
  26. data/lib/cattri.rb +38 -99
  27. data/sig/lib/cattri/attribute.rbs +105 -0
  28. data/sig/lib/cattri/attribute_compiler.rbs +61 -0
  29. data/sig/lib/cattri/attribute_options.rbs +150 -0
  30. data/sig/lib/cattri/attribute_registry.rbs +95 -0
  31. data/sig/lib/cattri/context.rbs +130 -0
  32. data/sig/lib/cattri/context_registry.rbs +31 -0
  33. data/sig/lib/cattri/deferred_attributes.rbs +53 -0
  34. data/sig/lib/cattri/dsl.rbs +55 -0
  35. data/sig/lib/cattri/error.rbs +28 -0
  36. data/sig/lib/cattri/inheritance.rbs +21 -0
  37. data/sig/lib/cattri/initializer_patch.rbs +26 -0
  38. data/sig/lib/cattri/internal_store.rbs +75 -0
  39. data/sig/lib/cattri/introspection.rbs +61 -0
  40. data/sig/lib/cattri/types.rbs +19 -0
  41. data/sig/lib/cattri/visibility.rbs +55 -0
  42. data/sig/lib/cattri.rbs +37 -0
  43. data/spec/cattri/attribute_compiler_spec.rb +179 -0
  44. data/spec/cattri/attribute_options_spec.rb +267 -0
  45. data/spec/cattri/attribute_registry_spec.rb +257 -0
  46. data/spec/cattri/attribute_spec.rb +297 -0
  47. data/spec/cattri/context_registry_spec.rb +45 -0
  48. data/spec/cattri/context_spec.rb +346 -0
  49. data/spec/cattri/deferred_attrributes_spec.rb +117 -0
  50. data/spec/cattri/dsl_spec.rb +69 -0
  51. data/spec/cattri/error_spec.rb +37 -0
  52. data/spec/cattri/inheritance_spec.rb +60 -0
  53. data/spec/cattri/initializer_patch_spec.rb +35 -0
  54. data/spec/cattri/internal_store_spec.rb +139 -0
  55. data/spec/cattri/introspection_spec.rb +90 -0
  56. data/spec/cattri/visibility_spec.rb +68 -0
  57. data/spec/cattri_spec.rb +54 -0
  58. data/spec/simplecov_helper.rb +21 -0
  59. data/spec/spec_helper.rb +16 -0
  60. metadata +79 -6
  61. data/lib/cattri/attribute_definer.rb +0 -143
  62. data/lib/cattri/class_attributes.rb +0 -277
  63. data/lib/cattri/instance_attributes.rb +0 -276
  64. data/sig/cattri.rbs +0 -4
@@ -1,276 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "attribute_definer"
4
-
5
- module Cattri
6
- # Mixin that provides support for defining instance-level attributes.
7
- #
8
- # This module is included into a class (via `include Cattri`) and exposes
9
- # a DSL similar to `attr_accessor`, with enhancements:
10
- #
11
- # - Lazy or static default values
12
- # - Coercion via custom setter blocks
13
- # - Visibility control (`:public`, `:protected`, `:private`)
14
- # - Read-only or write-only support
15
- #
16
- # Each defined attribute is stored as metadata and linked to a reader and/or writer.
17
- # Values are accessed and stored via standard instance variables.
18
- module InstanceAttributes
19
- # Hook called when this module is included into a class.
20
- #
21
- # @param base [Class]
22
- # @return [void]
23
- def self.included(base)
24
- base.extend(ClassMethods)
25
- end
26
-
27
- # Defines instance-level attribute DSL methods.
28
- module ClassMethods
29
- # Defines one or more instance-level attributes with optional default and coercion.
30
- #
31
- # This method supports defining multiple attributes at once, provided they share the same options.
32
- # If a block is given, only one attribute may be defined to avoid ambiguity.
33
- #
34
- # @example Define multiple attributes with shared defaults
35
- # iattr :foo, :bar, default: []
36
- #
37
- # @example Define a single attribute with coercion
38
- # iattr :level do |val|
39
- # Integer(val)
40
- # end
41
- #
42
- # @param names [Array<Symbol | String>] the names of the attributes to define
43
- # @param options [Hash] additional options like `:default`, `:reader`, `:writer`
44
- # @option options [Object, Proc] :default the default value or lambda
45
- # @option options [Boolean] :reader whether to define a reader method (default: true)
46
- # @option options [Boolean] :writer whether to define a writer method (default: true)
47
- # @option options [Symbol] :access method visibility (:public, :protected, :private)
48
- # @option options [Boolean] :predicate whether to define a predicate-style alias method
49
- # (e.g., `foo?`) for the attribute
50
- # @yieldparam value [Object] optional custom coercion logic for the setter
51
- # @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
52
- # `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
53
- # already defined or an error occurs while defining methods)
54
- # @return [void]
55
- def instance_attribute(*names, **options, &block)
56
- raise Cattri::AmbiguousBlockError if names.size > 1 && block_given?
57
-
58
- names.each do |name|
59
- if name.end_with?("?")
60
- raise Cattri::AttributeError,
61
- "Attribute names ending in '?' are not allowed. Use `predicate: true` or `iattr_alias` instead."
62
-
63
- end
64
-
65
- define_instance_attribute(name, options, block)
66
- end
67
- end
68
-
69
- # Defines a read-only instance-level attribute.
70
- #
71
- # Equivalent to `instance_attribute(..., writer: false)`
72
- #
73
- # @param names [Array<Symbol | String>] the names of the attributes to define
74
- # @param options [Hash] additional options like `:default`, `:reader`, `:writer`
75
- # @option options [Object, Proc] :default the default value or lambda
76
- # @option options [Boolean] :reader whether to define a reader method (default: true)
77
- # @option options [Symbol] :access method visibility (:public, :protected, :private)
78
- # @option options [Boolean] :predicate whether to define a predicate-style alias method
79
- # (e.g., `foo?`) for the attribute
80
- # @yieldparam value [Object] optional custom coercion logic for the setter
81
- # @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
82
- # `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
83
- # already defined or an error occurs while defining methods)
84
- # @return [void]
85
- def instance_attribute_reader(*names, **options)
86
- instance_attribute(*names, **options, writer: false)
87
- end
88
-
89
- # Defines a write-only instance-level attribute.
90
- #
91
- # Equivalent to `instance_attribute(..., reader: false)`. The predicate: option is not allowed
92
- # when defining writer methods.
93
- #
94
- # @param names [Array<Symbol | String>] the names of the attributes to define
95
- # @param options [Hash] additional options like `:default`, `:reader`, `:writer`
96
- # @option options [Object, Proc] :default the default value or lambda
97
- # @option options [Boolean] :writer whether to define a writer method (default: true)
98
- # @option options [Symbol] :access method visibility (:public, :protected, :private)
99
- # @yieldparam value [Object] optional custom coercion logic for the setter
100
- # @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
101
- # `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
102
- # already defined or an error occurs while defining methods)
103
- # @return [void]
104
- def instance_attribute_writer(*names, **options, &block)
105
- instance_attribute(*names, **options.merge(reader: false, predicate: false), &block)
106
- end
107
-
108
- # Updates the setter behavior of an existing instance-level attribute.
109
- #
110
- # This allows coercion logic to be defined or overridden after the attribute
111
- # has been declared using `iattr`, as long as the writer method exists.
112
- #
113
- # @example Add coercion to an existing attribute
114
- # iattr :format
115
- # iattr_setter :format do |val|
116
- # val.to_s.downcase.to_sym
117
- # end
118
- #
119
- # @param name [Symbol, String] the name of the attribute
120
- # @yieldparam value [Object] the value passed to the setter
121
- # @yieldreturn [Object] the coerced value to be assigned
122
- # @raise [Cattri::AttributeNotDefinedError] if the attribute is not defined or the writer method does not exist
123
- # @raise [Cattri::AttributeDefinitionError] if method redefinition fails
124
- # @return [void]
125
- def instance_attribute_setter(name, &block)
126
- attribute = __cattri_instance_attributes[name.to_sym]
127
-
128
- raise Cattri::AttributeNotDefinedError.new(:instance, name) if attribute.nil?
129
- raise Cattri::AttributeError, "Cannot define setter for readonly attribute :#{name}" unless attribute[:writer]
130
-
131
- attribute.instance_variable_set(:@setter, attribute.send(:normalize_setter, block))
132
- Cattri::AttributeDefiner.define_writer!(attribute, context)
133
- end
134
-
135
- # Defines an alias method for an existing instance-level attribute.
136
- #
137
- # This does **not** register a new attribute; it simply defines a method
138
- # (e.g., a predicate-style alias like `foo?`) that delegates to an existing one.
139
- #
140
- # The alias method inherits the visibility of the original attribute.
141
- #
142
- # @param alias_name [Symbol, String] the new method name (e.g., `:foo?`)
143
- # @param original [Symbol, String] the name of the existing attribute to delegate to (e.g., `:foo`)
144
- # @raise [Cattri::AttributeNotDefinedError] if the original attribute is not defined
145
- # @return [void]
146
- def instance_attribute_alias(alias_name, original)
147
- attribute = __cattri_instance_attributes[original.to_sym]
148
- raise Cattri::AttributeNotDefinedError.new(:instance, original) if attribute.nil?
149
-
150
- context.define_method(attribute, name: alias_name) { public_send(original) }
151
- end
152
-
153
- # Returns a list of defined instance-level attribute names.
154
- #
155
- # @return [Array<Symbol>]
156
- def instance_attributes
157
- ([self] + ancestors + singleton_class.included_modules)
158
- .uniq
159
- .select { |mod| mod.respond_to?(:__cattri_instance_attributes, true) }
160
- .flat_map { |mod| mod.send(:__cattri_instance_attributes).keys }
161
- .uniq
162
- end
163
-
164
- # Checks if an instance-level attribute has been defined.
165
- #
166
- # @param name [Symbol, String]
167
- # @return [Boolean]
168
- def instance_attribute_defined?(name)
169
- __cattri_instance_attributes.key?(name.to_sym)
170
- end
171
-
172
- # Returns the full attribute definition for a given name.
173
- #
174
- # @param name [Symbol, String]
175
- # @return [Cattri::Attribute, nil]
176
- def instance_attribute_definition(name)
177
- __cattri_instance_attributes[name.to_sym]
178
- end
179
-
180
- # @!method iattr(name, **options, &block)
181
- # Alias for {#instance_attribute}
182
- # @see #instance_attribute
183
- alias iattr instance_attribute
184
-
185
- # @!method iattr_accessor(name, **options, &block)
186
- # Alias for {#instance_attribute}
187
- # @see #instance_attribute
188
- alias iattr_accessor instance_attribute
189
-
190
- # @!method iattr_reader(name, **options)
191
- # Alias for {#instance_attribute_reader}
192
- # @see #instance_attribute_reader
193
- alias iattr_reader instance_attribute_reader
194
-
195
- # @!method iattr_writer(name, **options, &block)
196
- # Alias for {#instance_attribute_writer}
197
- # @see #instance_attribute_writer
198
- alias iattr_writer instance_attribute_writer
199
-
200
- # @!method iattr_setter(name, &block)
201
- # Alias for {#instance_attribute_setter}
202
- # @see #instance_attribute_setter
203
- alias iattr_setter instance_attribute_setter
204
-
205
- # @!method iattr_alias(name, &block)
206
- # Alias for {#instance_attribute_alias}
207
- # @see #instance_attribute_alias
208
- alias iattr_alias instance_attribute_alias
209
-
210
- # @!method iattrs
211
- # Alias for {#instance_attributes}
212
- # @see #instance_attributes
213
- alias iattrs instance_attributes
214
-
215
- # @!method iattr_defined?(name)
216
- # Alias for {#instance_attribute_defined?}
217
- # @see #instance_attribute_defined?
218
- alias iattr_defined? instance_attribute_defined?
219
-
220
- # @!method iattr_definition(name)
221
- # Alias for {#instance_attribute_definition}
222
- # @see #instance_attribute_definition
223
- alias iattr_definition instance_attribute_definition
224
-
225
- private
226
-
227
- # Defines a single instance-level attribute.
228
- #
229
- # This is the internal implementation used by {.instance_attribute} and its aliases.
230
- # It creates a `Cattri::Attribute`, registers it, and defines the appropriate
231
- # reader and/or writer methods on the class.
232
- #
233
- # @param name [Symbol, String] the attribute name
234
- # @param options [Hash] additional options for the attribute
235
- # @param block [Proc, nil] optional setter coercion logic
236
- #
237
- # @raise [Cattri::AttributeDefinedError] if the attribute has already been defined
238
- # @raise [Cattri::AttributeDefinitionError] if method definition fails
239
- #
240
- # @return [void]
241
- def define_instance_attribute(name, options, block) # rubocop:disable Metrics/AbcSize
242
- options[:access] ||= __cattri_visibility
243
- attribute = Cattri::Attribute.new(name, :instance, options, block)
244
-
245
- raise Cattri::AttributeDefinedError.new(:instance, name) if instance_attribute_defined?(attribute.name)
246
-
247
- begin
248
- __cattri_instance_attributes[name.to_sym] = attribute
249
- Cattri::AttributeDefiner.define_accessor(attribute, context)
250
- rescue StandardError => e
251
- raise Cattri::AttributeDefinitionError.new(self, attribute, e)
252
- end
253
-
254
- context.define_method(attribute, name: :"#{name}?") { !!send(attribute.name) } if options[:predicate]
255
- end
256
-
257
- # Internal registry of instance attributes defined on the class.
258
- #
259
- # @return [Hash{Symbol => Cattri::Attribute}]
260
- def __cattri_instance_attributes
261
- @__cattri_instance_attributes ||= {}
262
- end
263
-
264
- # Returns the context used to define methods for this class.
265
- #
266
- # Used internally to encapsulate method definition and visibility rules.
267
- #
268
- # @return [Cattri::Context]
269
- # :nocov:
270
- def context
271
- @context ||= Context.new(self)
272
- end
273
- # :nocov:
274
- end
275
- end
276
- end
data/sig/cattri.rbs DELETED
@@ -1,4 +0,0 @@
1
- module Cattri
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end