cattri 0.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.
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cattri
4
+ # Provides a DSL for defining instance-level attributes with support for:
5
+ #
6
+ # - Default values (static or callable)
7
+ # - Optional reader/writer method generation
8
+ # - Custom coercion logic for writers
9
+ # - Full attribute metadata access and reset capabilities
10
+ #
11
+ # This module is designed for mixin into classes or modules that want to offer
12
+ # configurable instance attributes (e.g., plugin systems, DTOs, DSLs).
13
+ #
14
+ # Example:
15
+ #
16
+ # class MyObject
17
+ # extend Cattri::InstanceAttributes
18
+ #
19
+ # iattr :name, default: "anonymous"
20
+ # iattr_writer :age, default: 0 do |v|
21
+ # Integer(v)
22
+ # end
23
+ # end
24
+ #
25
+ # obj = MyObject.new
26
+ # obj.name # => "anonymous"
27
+ # obj.age = "42"
28
+ # obj.instance_variable_get(:@age) # => 42
29
+ module InstanceAttributes
30
+ include Cattri::Helpers
31
+
32
+ def self.included(base)
33
+ base.extend(ClassMethods)
34
+ end
35
+
36
+ # Class-level methods for defining instance attributes
37
+ module ClassMethods
38
+ include Cattri::Helpers
39
+
40
+ # Default options for all instance attributes
41
+ DEFAULT_OPTIONS = { default: nil, reader: true, writer: true }.freeze
42
+
43
+ # Defines a new instance-level attribute with optional default and coercion.
44
+ #
45
+ # @param name [Symbol, String] the name of the attribute
46
+ # @param options [Hash] additional options like `:default`, `:reader`, `:writer`
47
+ # @option options [Object, Proc] :default the default value or proc returning the value
48
+ # @option options [Boolean] :reader whether to define a reader method (default: true)
49
+ # @option options [Boolean] :writer whether to define a writer method (default: true)
50
+ # @yield [value] optional coercion logic for the writer
51
+ # @return [void]
52
+ def instance_attribute(name, **options, &block)
53
+ name, definition = define_attribute(name, options, block, DEFAULT_OPTIONS)
54
+ __cattri_instance_attributes[name] = definition
55
+
56
+ define_reader(name, definition) if options.fetch(:reader, true)
57
+ define_writer(name, definition) if options.fetch(:writer, true)
58
+ end
59
+
60
+ # Defines a read-only instance-level attribute.
61
+ #
62
+ # @param name [Symbol, String]
63
+ # @param options [Hash]
64
+ # @return [void]
65
+ def instance_attribute_reader(name, **options)
66
+ instance_attribute(name, writer: false, **options)
67
+ end
68
+
69
+ # Defines a write-only instance-level attribute.
70
+ #
71
+ # @param name [Symbol, String]
72
+ # @param options [Hash]
73
+ # @yield [value] coercion logic
74
+ # @return [void]
75
+ def instance_attribute_writer(name, **options, &block)
76
+ instance_attribute(name, reader: false, **options, &block)
77
+ end
78
+
79
+ def __cattri_instance_attributes
80
+ @__cattri_instance_attributes ||= {}
81
+ end
82
+
83
+ # Returns all defined instance-level attribute names.
84
+ #
85
+ # @return [Array<Symbol>]
86
+ def instance_attributes
87
+ __cattri_instance_attributes.keys
88
+ end
89
+
90
+ # Checks whether an instance attribute is defined.
91
+ #
92
+ # @param name [Symbol, String]
93
+ # @return [Boolean]
94
+ def instance_attribute_defined?(name)
95
+ __cattri_instance_attributes.key?(name.to_sym)
96
+ end
97
+
98
+ # Fetches the full definition hash for a specific attribute.
99
+ #
100
+ # @param name [Symbol, String]
101
+ # @return [Hash, nil]
102
+ def instance_attribute_definition(name)
103
+ __cattri_instance_attributes[name.to_sym]
104
+ end
105
+
106
+ # @!method iattr(name, **options, &block)
107
+ # Alias for {.instance_attribute}
108
+ # @see .instance_attribute
109
+ alias iattr instance_attribute
110
+
111
+ # @!method iattr_accessor(name, **options, &block)
112
+ # Alias for {.instance_attribute}
113
+ # @see .instance_attribute
114
+ alias iattr_accessor instance_attribute
115
+
116
+ # @!method iattr_reader(name, **options)
117
+ # Alias for {.instance_attribute_reader}
118
+ # @see .instance_attribute_reader
119
+ alias iattr_reader instance_attribute_reader
120
+
121
+ # @!method iattr_writer(name, **options, &block)
122
+ # Alias for {.instance_attribute_writer}
123
+ # @see .instance_attribute_writer
124
+ alias iattr_writer instance_attribute_writer
125
+
126
+ # @!method iattrs
127
+ # @return [Hash<Symbol, Hash>] all defined attributes
128
+ # @see .instance_attributes
129
+ alias iattrs instance_attributes
130
+
131
+ # @!method iattr_defined?(name)
132
+ # @return [Boolean]
133
+ # @see .instance_attribute_defined?
134
+ alias iattr_defined? instance_attribute_defined?
135
+
136
+ # @!method iattr_for(name)
137
+ # @return [Hash, nil]
138
+ # @see .instance_attribute_definition
139
+ alias iattr_definition instance_attribute_definition
140
+
141
+ private
142
+
143
+ # Defines the reader method for an instance attribute.
144
+ #
145
+ # @param name [Symbol] attribute name
146
+ # @param definition [Hash] full attribute definition
147
+ def define_reader(name, definition)
148
+ ivar = definition[:ivar]
149
+
150
+ define_method(name) do
151
+ return instance_variable_get(ivar) if instance_variable_defined?(ivar)
152
+
153
+ value = definition[:default].call
154
+ instance_variable_set(ivar, value)
155
+ end
156
+ end
157
+
158
+ # Defines the writer method for an instance attribute.
159
+ #
160
+ # @param name [Symbol] attribute name
161
+ # @param definition [Hash] full attribute definition
162
+ def define_writer(name, definition)
163
+ define_method("#{name}=") do |value|
164
+ coerced_value = definition[:setter].call(value)
165
+ instance_variable_set(definition[:ivar], coerced_value)
166
+ end
167
+ end
168
+ end
169
+
170
+ # Resets all defined attributes to their default values.
171
+ #
172
+ # @return [void]
173
+ def reset_instance_attributes!
174
+ reset_attributes!(self, self.class.__cattri_instance_attributes.values)
175
+ end
176
+
177
+ # Resets a specific attribute to its default value.
178
+ #
179
+ # @param name [Symbol, String]
180
+ # @return [void]
181
+ def reset_instance_attribute!(name)
182
+ definition = self.class.__cattri_instance_attributes[name]
183
+ return unless definition
184
+
185
+ reset_attributes!(self, [definition])
186
+ end
187
+
188
+ # @!method reset_iattrs!
189
+ # @return [void]
190
+ # @see .reset_instance_attributes!
191
+ alias reset_iattrs! reset_instance_attributes!
192
+
193
+ # @!method reset_iattr!(name)
194
+ # @return [void]
195
+ # @see .reset_instance_attribute!
196
+ alias reset_iattr! reset_instance_attribute!
197
+ end
198
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cattri
4
+ # Adds debugging and inspection helpers for reading the current values of
5
+ # class- and instance-level attributes defined via Cattri.
6
+ #
7
+ # This module is intended for use in development and test environments to
8
+ # help capture attribute state at a given moment.
9
+ #
10
+ # It can be included in any class or module that uses {Cattri::ClassAttributes}
11
+ # or {Cattri::InstanceAttributes}.
12
+ #
13
+ # @example
14
+ # class MyConfig
15
+ # extend Cattri::ClassAttributes
16
+ # include Cattri::Introspection
17
+ #
18
+ # cattr :items, default: []
19
+ # end
20
+ #
21
+ # MyConfig.items << :a
22
+ # MyConfig.snapshot_class_attributes #=> { items: [:a] }
23
+ module Introspection
24
+ # Hook called when the module is included. Extends the base with class methods.
25
+ #
26
+ # @param base [Class, Module]
27
+ # @return [void]
28
+ def self.included(base)
29
+ base.extend(ClassMethods)
30
+ end
31
+
32
+ # Class-level methods for introspection.
33
+ module ClassMethods
34
+ # Returns a hash of current class attribute values.
35
+ #
36
+ # @return [Hash<Symbol, Object>] a snapshot of each defined class attribute
37
+ def snapshot_class_attributes
38
+ return {} unless respond_to?(:class_attributes)
39
+
40
+ class_attributes.each_with_object({}) do |attribute, hash|
41
+ hash[attribute] = send(attribute)
42
+ end
43
+ end
44
+
45
+ # @!method snapshot_cattrs
46
+ # Alias for {.snapshot_class_attributes}
47
+ # @see .snapshot_class_attributes
48
+ alias snapshot_cattrs snapshot_class_attributes
49
+ end
50
+
51
+ # Returns a hash of current instance attribute values for this object.
52
+ #
53
+ # @return [Hash<Symbol, Object>] a snapshot of each defined instance attribute
54
+ def snapshot_instance_attributes
55
+ return {} unless self.class.respond_to?(:instance_attributes)
56
+
57
+ self.class.instance_attributes.each_with_object({}) do |attribute, hash|
58
+ hash[attribute] = send(attribute)
59
+ rescue NoMethodError
60
+ # Catch for write-only methods
61
+ hash[attribute] = instance_variable_get(:"@#{attribute}")
62
+ end
63
+ end
64
+
65
+ # @!method snapshot_iattrs
66
+ # Alias for {#snapshot_instance_attributes}
67
+ # @see #snapshot_instance_attributes
68
+ alias snapshot_iattrs snapshot_instance_attributes
69
+ end
70
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cattri
4
+ VERSION = "0.1.0"
5
+ end
data/lib/cattri.rb ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cattri/version"
4
+ require_relative "cattri/class_attributes"
5
+ require_relative "cattri/instance_attributes"
6
+ require_relative "cattri/introspection"
7
+
8
+ # The primary entry point for the Cattri gem.
9
+ #
10
+ # When included, it adds support for both class-level and instance-level
11
+ # attribute declarations using `cattr` and `iattr` style methods.
12
+ #
13
+ # This module does **not** include introspection helpers by default —
14
+ # use `include Cattri::Introspection` explicitly if needed.
15
+ #
16
+ # @example Using both class and instance attributes
17
+ # class MyConfig
18
+ # include Cattri
19
+ #
20
+ # cattr :enabled, default: true
21
+ # iattr :name, default: "anonymous"
22
+ # end
23
+ #
24
+ # MyConfig.enabled # => true
25
+ # MyConfig.new.name # => "anonymous"
26
+ module Cattri
27
+ # Hook triggered when `include Cattri` is called.
28
+ # Adds class and instance attribute support to the target.
29
+ #
30
+ # @param base [Class, Module] the receiving class or module
31
+ # @return [void]
32
+ def self.included(base)
33
+ base.extend(Cattri::ClassAttributes)
34
+ base.include(Cattri::InstanceAttributes)
35
+ end
36
+ end
data/sig/cattri.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Cattri
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cattri
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Lucas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-04-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov-cobertura
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov-html
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Cattri provides a clean DSL for defining class-level and instance-level
98
+ attributes with optional defaults, coercion, accessors, and inheritance support.
99
+ email:
100
+ - bnlucas@outlook.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".idea/workspace.xml"
106
+ - ".rspec"
107
+ - ".rspec_status"
108
+ - ".rubocop.yml"
109
+ - CHANGELOG.md
110
+ - CODE_OF_CONDUCT.md
111
+ - LICENSE.txt
112
+ - README.md
113
+ - Rakefile
114
+ - cattri.gemspec
115
+ - lib/cattri.rb
116
+ - lib/cattri/class_attributes.rb
117
+ - lib/cattri/error.rb
118
+ - lib/cattri/helpers.rb
119
+ - lib/cattri/instance_attributes.rb
120
+ - lib/cattri/introspection.rb
121
+ - lib/cattri/version.rb
122
+ - sig/cattri.rbs
123
+ homepage: https://github.com/bnlucas/cattri
124
+ licenses:
125
+ - MIT
126
+ metadata:
127
+ homepage_uri: https://github.com/bnlucas/cattri
128
+ source_code_uri: https://github.com/bnlucas/cattri
129
+ changelog_uri: https://github.com/bnlucas/cattri/blob/main/CHANGELOG.md
130
+ rubygems_mfa_required: 'true'
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: 2.7.0
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.5.4
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Simple class and instance attribute DSL for Ruby.
150
+ test_files: []