cattri 0.1.2 → 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 +50 -0
  6. data/Gemfile +12 -0
  7. data/README.md +163 -144
  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 -153
  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 -124
  62. data/lib/cattri/class_attributes.rb +0 -204
  63. data/lib/cattri/instance_attributes.rb +0 -226
  64. data/sig/cattri.rbs +0 -4
@@ -0,0 +1,105 @@
1
+ module Cattri
2
+ # @internal
3
+ #
4
+ # Attribute acts as a thin wrapper around AttributeOptions,
5
+ # exposing core attribute metadata and behavior in a safe, immutable way.
6
+ #
7
+ # Each Attribute instance represents a single logical property,
8
+ # and delegates its behavior (default, visibility, coercion, etc.) to its associated AttributeOptions.
9
+ #
10
+ # @example
11
+ # attribute = Attribute.new(:enabled, default: true, expose: :read_write)
12
+ # attribute.name # => :enabled
13
+ # attribute.default.call # => true
14
+ # attribute.expose # => :read_write
15
+ class Attribute
16
+ @options: AttributeOptions
17
+
18
+ @defined_in: ::Module
19
+
20
+ # @return [Module] the class or module this attribute was defined in
21
+ attr_reader defined_in: ::Module
22
+
23
+ # Initializes a new attribute definition.
24
+ #
25
+ # @param name [Symbol, String] the attribute name
26
+ # @param defined_in [Module] the class or module where this attribute is defined
27
+ # @param options [Hash] configuration options
28
+ # @option options [Boolean] :class whether the attribute is class-level (internally mapped to :class_attribute)
29
+ # @param transformer [Proc] optional block used to coerce/validate assigned values
30
+ def initialize: (identifier name, defined_in: ::Module, **attribute_options options) { (?) -> untyped } -> void
31
+
32
+ # Serializes this attribute and its configuration to a frozen hash.
33
+ #
34
+ # @return [Hash<Symbol, Object>]
35
+ def to_h: () -> ::Hash[::Symbol, untyped]
36
+
37
+ # @return [Symbol] the canonical name of the attribute
38
+ def name: () -> ::Symbol
39
+
40
+ # @return [Symbol] the backing instance variable (e.g., :@enabled)
41
+ def ivar: () -> ::Symbol
42
+
43
+ # @return [Proc] a callable lambda for the attribute’s default value
44
+ def default: () -> ::Proc
45
+
46
+ # @return [Proc] a callable transformer used to process assigned values
47
+ def transformer: () -> ::Proc
48
+
49
+ # @return [Symbol] method exposure type (:read, :write, :read_write, or :none)
50
+ def expose: () -> expose_types
51
+
52
+ # @return [Symbol] method visibility (:public, :protected, :private)
53
+ def visibility: () -> visibility_types
54
+
55
+ # @return [Boolean] whether the reader should remain internal
56
+ def internal_reader?: () -> bool
57
+
58
+ # @return [Boolean] whether the writer should remain internal
59
+ def internal_writer?: () -> bool
60
+
61
+ # @return [Boolean] whether the attribute allows reading
62
+ def readable?: () -> bool
63
+
64
+ # @return [Boolean] whether the attribute allows writing
65
+ def writable?: () -> bool
66
+
67
+ # @return [Boolean] whether the attribute is marked readonly
68
+ def readonly?: () -> bool
69
+
70
+ # @return [Boolean] whether the attribute is marked final (write-once)
71
+ def final?: () -> bool
72
+
73
+ # @return [Boolean] whether the attribute is class-level
74
+ def class_attribute?: () -> bool
75
+
76
+ # @return [Boolean] whether the attribute defines a predicate method (`:name?`)
77
+ def with_predicate?: () -> bool
78
+
79
+ # Returns the methods that will be defined for this attribute.
80
+ #
81
+ # Includes the base accessor, optional writer, and optional predicate.
82
+ #
83
+ # @return [Array<Symbol>] a list of method names
84
+ def allowed_methods: () -> ::Array[::Symbol]
85
+
86
+ # Validates whether this attribute is assignable in the current context.
87
+ #
88
+ # @raise [Cattri::AttributeError] if assignment is disallowed
89
+ def validate_assignment!: () -> void
90
+
91
+ # Resolves the default value for this attribute.
92
+ #
93
+ # @return [Object] the evaluated default
94
+ # @raise [Cattri::AttributeError] if default evaluation fails
95
+ def evaluate_default: () -> untyped
96
+
97
+ # Processes and transforms an incoming assignment for this attribute.
98
+ #
99
+ # @param args [Array] positional arguments to pass to the transformer
100
+ # @param kwargs [Hash] keyword arguments to pass to the transformer
101
+ # @return [Object] the transformed value
102
+ # @raise [Cattri::AttributeError] if transformation fails
103
+ def process_assignment: (*untyped args, **untyped kwargs) -> untyped
104
+ end
105
+ end
@@ -0,0 +1,61 @@
1
+ module Cattri
2
+ # @internal
3
+ #
4
+ # Responsible for defining methods on the target class/module
5
+ # based on the metadata in a {Cattri::Attribute}.
6
+ #
7
+ # This includes:
8
+ # - callable accessors (acting as both reader and writer)
9
+ # - predicate methods
10
+ # - explicit writers (`:name=` methods)
11
+ #
12
+ # Handles both instance and class-level attributes, including
13
+ # memoization and validation of default values for final attributes.
14
+ class AttributeCompiler
15
+ # Defines accessor methods for the given attribute in the provided context.
16
+ #
17
+ # For `final` + `class_attribute` attributes, the default is eagerly assigned.
18
+ # Then, if permitted by `expose`, the reader, writer, and/or predicate methods are defined.
19
+ #
20
+ # @param attribute [Cattri::Attribute] the attribute to define
21
+ # @param context [Cattri::Context] the target context for method definition
22
+ # @return [void]
23
+ def self.define_accessor: (Attribute attribute, Context context) -> void
24
+
25
+ private
26
+
27
+ # Defines a callable method that acts as both getter and setter.
28
+ #
29
+ # If called with no arguments, it returns the default (memoized).
30
+ # If called with arguments, it processes the assignment and writes the value.
31
+ #
32
+ # @param attribute [Cattri::Attribute]
33
+ # @param context [Cattri::Context]
34
+ # @return [void]
35
+ def self.define_accessor!: (Attribute attribute, Context context) -> void
36
+
37
+ # Defines a writer method `:name=`, assigning a transformed value to the backing store.
38
+ #
39
+ # @param attribute [Cattri::Attribute]
40
+ # @param context [Cattri::Context]
41
+ # @return [void]
42
+ def self.define_writer!: (Attribute attribute, Context context) -> void
43
+
44
+ # Defines a predicate method `:name?` that returns the truthiness of the value.
45
+ #
46
+ # @param attribute [Cattri::Attribute]
47
+ # @param context [Cattri::Context]
48
+ # @return [void]
49
+ def self.define_predicate!: (Attribute attribute, Context context) -> void
50
+
51
+ # Returns the default value for the attribute, memoizing it in the backing store.
52
+ #
53
+ # For `final` attributes, raises unless explicitly initialized.
54
+ #
55
+ # @param receiver [Object] the instance or class receiving the value
56
+ # @param attribute [Cattri::Attribute]
57
+ # @return [Object] the stored or evaluated default
58
+ # @raise [Cattri::AttributeError] if final attribute is unset or evaluation fails
59
+ def self.memoize_default_value: (InternalStore receiver, Attribute attribute) -> untyped
60
+ end
61
+ end
@@ -0,0 +1,150 @@
1
+ module Cattri
2
+ # @internal
3
+ #
4
+ # AttributeOptions encapsulates normalized metadata for a single Cattri-defined attribute.
5
+ #
6
+ # It validates, transforms, and freezes all input during initialization,
7
+ # ensuring attribute safety and immutability at runtime.
8
+ #
9
+ # @example
10
+ # options = AttributeOptions.new(:enabled, default: true, expose: :read_write)
11
+ # options.name # => :enabled
12
+ # options.default.call # => true
13
+ # options.expose # => :read_write
14
+ class AttributeOptions
15
+ @name: ::Symbol
16
+
17
+ @ivar: ::Symbol
18
+
19
+ @final: bool
20
+
21
+ @scope: scope_types
22
+
23
+ @predicate: bool
24
+
25
+ @default: ::Proc
26
+
27
+ @transformer: ::Proc
28
+
29
+ @expose: expose_types
30
+
31
+ @visibility: visibility_types
32
+
33
+ # Validates and normalizes the `expose` configuration.
34
+ #
35
+ # @param expose [Symbol, String] one of: :read, :write, :read_write, :none
36
+ # @return [Symbol]
37
+ # @raise [Cattri::AttributeError] if the value is invalid
38
+ def self.validate_expose!: (expose_types | identifier expose) -> untyped
39
+
40
+ # Validates and normalizes method visibility.
41
+ #
42
+ # @param visibility [Symbol, String] one of: :public, :protected, :private
43
+ # @return [Symbol]
44
+ # @raise [Cattri::AttributeError] if the value is invalid
45
+ def self.validate_visibility!: (visibility_types | identifier visibility) -> untyped
46
+
47
+ # Valid method visibility levels.
48
+ VISIBILITIES: ::Array[visibility_types]
49
+
50
+ # Valid expose options for method generation.
51
+ EXPOSE_OPTIONS: ::Array[expose_types]
52
+
53
+ # Valid scope types.
54
+ SCOPES: ::Array[scope_types]
55
+
56
+ # Built-in Ruby value types that are safe to reuse as-is (no dup needed).
57
+ SAFE_VALUE_TYPES: ::Array[untyped]
58
+
59
+ attr_reader name: ::Symbol
60
+
61
+ attr_reader ivar: ::Symbol
62
+
63
+ attr_reader final: bool
64
+
65
+ attr_reader scope: scope_types
66
+
67
+ attr_reader predicate: bool
68
+
69
+ attr_reader default: ::Proc
70
+
71
+ attr_reader transformer: ::Proc
72
+
73
+ attr_reader expose: expose_types
74
+
75
+ attr_reader visibility: visibility_types
76
+
77
+ # Initializes a frozen attribute configuration.
78
+ #
79
+ # @param name [Symbol, String] the attribute name
80
+ # @param ivar [Symbol, String, nil] optional custom instance variable name
81
+ # @param final [Boolean] marks the attribute as write-once
82
+ # @param class_attribute [Boolean] indicates if the attribute is class-level
83
+ # @param predicate [Boolean] whether to define a `?` predicate method
84
+ # @param default [Object, Proc, nil] default value or callable
85
+ # @param transformer [Proc, nil] optional coercion block
86
+ # @param expose [Symbol] access level to define (:read, :write, :read_write, :none)
87
+ # @param visibility [Symbol] method visibility (:public, :protected, :private)
88
+ def initialize: (
89
+ identifier name,
90
+ ?ivar: identifier?,
91
+ ?final: bool,
92
+ ?scope: scope_types,
93
+ ?predicate: bool,
94
+ ?default: untyped,
95
+ ?transformer: ::Proc?,
96
+ ?expose: expose_types,
97
+ ?visibility: visibility_types
98
+ ) -> void
99
+
100
+ # Returns a frozen hash representation of this option set.
101
+ #
102
+ # @return [Hash<Symbol, Object>]
103
+ def to_h: () -> ::Hash[::Symbol, untyped]
104
+
105
+ # Allows hash-style access to the option set.
106
+ #
107
+ # @param key [Symbol, String]
108
+ # @return [Object]
109
+ def []: (untyped key) -> untyped
110
+
111
+ private
112
+
113
+ # Normalizes the instance variable name, defaulting to @name.
114
+ #
115
+ # @param ivar [String, Symbol, nil]
116
+ # @return [Symbol]
117
+ def normalize_ivar: (identifier? ivar) -> ::Symbol
118
+
119
+ # Wraps the default in a Proc with immutability protection.
120
+ #
121
+ # - Returns original Proc if given.
122
+ # - Wraps immutable types as-is.
123
+ # - Duplicates mutable values at runtime.
124
+ #
125
+ # @param default [Object, Proc, nil]
126
+ # @return [Proc]
127
+ def normalize_default: (::Proc | untyped default) -> ::Proc
128
+
129
+ # Returns a normalized assignment transformer.
130
+ #
131
+ # Falls back to a default transformer that returns:
132
+ # - `kwargs` if `args.empty?`
133
+ # - the single argument if one is passed
134
+ # - `[*args, kwargs]` otherwise
135
+ #
136
+ # @param transformer [Proc, nil]
137
+ # @return [Proc]
138
+ def normalize_transformer: (::Proc? transformer) -> ::Proc
139
+
140
+ # Validates and normalizes the provided scope value.
141
+ #
142
+ # If `scope` is `nil`, it defaults to `:instance`. If it's one of the allowed
143
+ # values (`:class`, `:instance`), it is returned as-is. Otherwise, an error is raised.
144
+ #
145
+ # @param scope [Symbol, nil] the requested attribute scope
146
+ # @return [Symbol] the validated scope (`:class` or `:instance`)
147
+ # @raise [Cattri::AttributeError] if the scope is invalid
148
+ def validate_scope!: (scope_types scope) -> ::Symbol
149
+ end
150
+ end
@@ -0,0 +1,95 @@
1
+ module Cattri
2
+ # Cattri::AttributeRegistry is responsible for managing attribute definitions
3
+ # for a given context (class or module). It validates uniqueness, applies
4
+ # definition logic, and supports inheritance and introspection.
5
+ #
6
+ # It handles both eager and deferred attribute compilation and ensures correct
7
+ # behavior for `class: true`, `final: true`, and other attribute options.
8
+ class AttributeRegistry
9
+ @context: Context
10
+
11
+ @__cattri_registered_attributes: ::Hash[::Symbol, Attribute]
12
+
13
+ # @return [Cattri::Context] the context this registry operates within
14
+ attr_reader context: Context
15
+
16
+ # Initializes a new registry for the provided context.
17
+ #
18
+ # @param context [Cattri::Context]
19
+ def initialize: (Context context) -> void
20
+
21
+ # Returns the attributes registered directly on this context.
22
+ #
23
+ # @return [Hash{Symbol => Cattri::Attribute}]
24
+ def registered_attributes: () -> ::Hash[::Symbol, Attribute]
25
+
26
+ # Returns all known attributes, optionally including inherited definitions.
27
+ #
28
+ # @param with_ancestors [Boolean] whether to include ancestors
29
+ # @return [Hash{Symbol => Cattri::Attribute}]
30
+ def defined_attributes: (?with_ancestors: bool) -> ::Hash[::Symbol, Attribute]
31
+
32
+ # Fetches an attribute by name, or returns nil.
33
+ #
34
+ # @param name [String, Symbol]
35
+ # @param with_ancestors [Boolean]
36
+ # @return [Cattri::Attribute, nil]
37
+ def fetch_attribute: (identifier name, ?with_ancestors: bool) -> Attribute
38
+
39
+ # Fetches an attribute by name, or raises if not found.
40
+ #
41
+ # @param name [String, Symbol]
42
+ # @param with_ancestors [Boolean]
43
+ # @return [Cattri::Attribute]
44
+ # @raise [Cattri::AttributeError] if the attribute is not defined
45
+ def fetch_attribute!: (identifier name, ?with_ancestors: bool) -> Attribute
46
+
47
+ # Defines a new attribute and registers it on the current context.
48
+ #
49
+ # @param name [String, Symbol] the attribute name
50
+ # @param value [Object, Proc, nil] default value or initializer
51
+ # @param options [Hash] attribute options (`:class`, `:final`, etc.)
52
+ # @yield [*args] optional transformation block used as setter
53
+ # @return [Array<Symbol>] list of methods defined by this attribute
54
+ # @raise [Cattri::AttributeError] if the name is already defined
55
+ def define_attribute: (
56
+ identifier name,
57
+ Proc | untyped value,
58
+ **attribute_options options
59
+ ) { (?) -> untyped } -> ::Array[::Symbol]
60
+
61
+ # Copies registered attributes from this context to another,
62
+ # preserving definitions and assigning values for `final: true, class: true`.
63
+ #
64
+ # @param target_context [Cattri::Context]
65
+ # @return [void]
66
+ def copy_attributes_to: (Context target_context) -> void
67
+
68
+ private
69
+
70
+ # Validates that no attribute with the same name is already registered.
71
+ #
72
+ # @param name [Symbol]
73
+ # @raise [Cattri::AttributeError]
74
+ def validate_unique!: (::Symbol name) -> void
75
+
76
+ # Registers an attribute and applies or defers its definition.
77
+ #
78
+ # @param attribute [Cattri::Attribute]
79
+ # @return [void]
80
+ def register_attribute: (Attribute attribute) -> void
81
+
82
+ # Defers the attribute definition if in a module context.
83
+ #
84
+ # @param attribute [Cattri::Attribute]
85
+ # @return [void]
86
+ def defer_definition: (Attribute attribute) -> void
87
+
88
+ # Applies the attribute definition using the compiler.
89
+ #
90
+ # @param attribute [Cattri::Attribute]
91
+ # @return [void]
92
+ # @raise [Cattri::AttributeError]
93
+ def apply_definition!: (Attribute attribute) -> void
94
+ end
95
+ end
@@ -0,0 +1,130 @@
1
+ module Cattri
2
+ # Cattri::Context encapsulates the class or module that attributes are being defined on.
3
+ #
4
+ # It provides a safe interface for dynamically defining methods and tracking metadata,
5
+ # such as declared accessors, access visibility, and deferred attribute declarations.
6
+ #
7
+ # It handles:
8
+ # - Attribute method definitions (reader/writer/predicate)
9
+ # - Visibility enforcement
10
+ # - Target resolution (instance vs. class-level)
11
+ # - Method deduplication and tracking
12
+ #
13
+ # All method definitions occur directly on the resolved target (e.g., the class or its singleton).
14
+ class Context
15
+ @target: ::Module
16
+
17
+ @__cattri_defined_methods: ::Hash[::Symbol, ::Set[::Symbol]]
18
+
19
+ # The class or module that owns the attributes.
20
+ #
21
+ # @return [Module]
22
+ attr_reader target: ::Module
23
+
24
+ # @param target [Module, Class]
25
+ def initialize: (::Module target) -> void
26
+
27
+ # Returns a frozen copy of all attribute methods explicitly defined by this context.
28
+ #
29
+ # This does not include inherited or module-defined methods.
30
+ #
31
+ # @return [Hash{Symbol => Set<Symbol>}] map of attribute name to defined method names
32
+ def defined_methods: () -> ::Hash[::Symbol, ::Set[::Symbol]]
33
+
34
+ # Whether this target should defer method definitions (e.g., if it's a module).
35
+ #
36
+ # @return [Boolean]
37
+ def defer_definitions?: () -> bool
38
+
39
+ # Ensures the target includes Cattri::DeferredAttributes if needed.
40
+ #
41
+ # Used to prepare modules for later application of attributes when included elsewhere.
42
+ #
43
+ # @return [void]
44
+ def ensure_deferred_support!: () -> void
45
+
46
+ # All ancestors and included modules used for attribute lookup and inheritance.
47
+ #
48
+ # @return [Array<Module>]
49
+ def attribute_lookup_sources: () -> ::Array[::Module]
50
+
51
+ # Defines a method for the given attribute unless already defined locally.
52
+ #
53
+ # Respects attribute-level force overwrite and enforces visibility rules.
54
+ #
55
+ # @param attribute [Cattri::Attribute]
56
+ # @param name [Symbol, nil] optional method name override
57
+ # @yield method implementation block
58
+ # @raise [Cattri::AttributeError] if method is already defined and not forced
59
+ # @return [void]
60
+ def define_method: (Attribute attribute, ?name: identifier?) { (?) -> untyped } -> void
61
+
62
+ # Checks if the given method is already defined on the resolved target.
63
+ #
64
+ # Only checks methods directly defined on the class or singleton—not ancestors.
65
+ #
66
+ # @param attribute [Cattri::Attribute]
67
+ # @param name [Symbol, nil]
68
+ # @return [Boolean]
69
+ def method_defined?: (Attribute attribute, ?name: identifier?) -> bool
70
+
71
+ private
72
+
73
+ # Internal tracking of explicitly defined methods per attribute.
74
+ #
75
+ # @return [Hash{Symbol => Set<Symbol>}]
76
+ def __cattri_defined_methods: () -> ::Hash[::Symbol, ::Set[::Symbol]]
77
+
78
+ # Determines whether to define the method on the instance or singleton.
79
+ #
80
+ # @param attribute [Cattri::Attribute]
81
+ # @return [Module]
82
+ def target_for: (Attribute attribute) -> ::Module
83
+
84
+ # Defines the method and applies its access visibility.
85
+ #
86
+ # @param target [Module]
87
+ # @param attribute [Cattri::Attribute]
88
+ # @param name [Symbol]
89
+ # @yield method implementation
90
+ # @return [void]
91
+ def define_method!: (::Module target, Attribute attribute, ::Symbol name) { (?) -> untyped } -> void
92
+
93
+ # Applies visibility (`public`, `protected`, `private`) to a method.
94
+ #
95
+ # Skips application for `:public` (default in Ruby).
96
+ #
97
+ # @param target [Module]
98
+ # @param name [Symbol]
99
+ # @param attribute [Cattri::Attribute]
100
+ # @return [void]
101
+ def apply_visibility!: (::Module target, ::Symbol name, Attribute attribute) -> void
102
+
103
+ # Determines the effective visibility of the attribute.
104
+ #
105
+ # - If the attribute has no public writer or reader (i.e., `expose: :write` or `:none`)
106
+ # - Returns `:protected` for class-level attributes
107
+ # - Returns `:private` for instance-level attributes
108
+ # - Otherwise, returns the explicitly declared visibility (`attribute.visibility`)
109
+ #
110
+ # This ensures that internal-only attributes remain inaccessible outside their scope,
111
+ # while still being usable by subclasses if class-level.
112
+ #
113
+ # @param attribute [Cattri::Attribute]
114
+ # @return [Symbol]
115
+ def effective_visibility: (Attribute attribute, ::Symbol name) -> visibility_types
116
+
117
+ # Determines whether the given method name (accessor or writer)
118
+ # should be treated as internal-only based on the attribute's `expose` configuration.
119
+ #
120
+ # This is used when resolving method visibility (e.g., private vs protected).
121
+ #
122
+ # - Writer methods (`:attr=`) are considered internal if the attribute lacks public read access.
123
+ # - Reader methods (`:attr`) are considered internal if the attribute lacks public write access.
124
+ #
125
+ # @param attribute [Cattri::Attribute] the attribute definition
126
+ # @param name [Symbol, String] the method name being defined
127
+ # @return [Boolean] true if the method should be scoped for internal use only
128
+ def internal_method?: (Attribute attribute, Symbol name) -> bool
129
+ end
130
+ end
@@ -0,0 +1,31 @@
1
+ module Cattri
2
+ # Provides per-class or per-module access to the attribute registry and method definition context.
3
+ #
4
+ # This module is included into both the base and singleton class of any class using Cattri.
5
+ # It initializes and exposes a lazily-evaluated `attribute_registry` and `context` specific
6
+ # to the current scope, enabling safe and isolated attribute handling.
7
+ module ContextRegistry
8
+ @attribute_registry: AttributeRegistry
9
+
10
+ @context: Context
11
+
12
+ private
13
+
14
+ # Returns the attribute definition registry for this class or module.
15
+ #
16
+ # The registry is responsible for tracking all defined attributes, both class-level and
17
+ # instance-level, handling application logic, and copying across subclasses where needed.
18
+ #
19
+ # @return [Cattri::AttributeRegistry] the registry used to define and apply attributes
20
+ def attribute_registry: () -> AttributeRegistry
21
+
22
+ # Returns the method definition context for this class or module.
23
+ #
24
+ # The context wraps the current target (class or module) and provides utilities
25
+ # for defining attribute methods (readers, writers, predicates), managing visibility,
26
+ # and recording declared methods to avoid duplication.
27
+ #
28
+ # @return [Cattri::Context] the context used for method definition and visibility tracking
29
+ def context: () -> Context
30
+ end
31
+ end
@@ -0,0 +1,53 @@
1
+ module Cattri
2
+ # Provides support for defining attributes within a module that should be
3
+ # applied later to any class or module that includes or extends it.
4
+ #
5
+ # This allows DSL modules to define Cattri attributes without prematurely
6
+ # applying them to themselves, deferring application to the including/extending context.
7
+ module DeferredAttributes
8
+ @deferred_attributes: ::Hash[::Symbol, Attribute]
9
+
10
+ # Hook into the module extension lifecycle to ensure deferred attributes are
11
+ # applied when the module is included or extended.
12
+ #
13
+ # @param base [Module] the module that extended this module
14
+ # @return [void]
15
+ def self.extended: (::Module base) -> void
16
+
17
+ # Hook methods for inclusion/extension that trigger deferred application.
18
+ module Hook
19
+ # Called when a module including `DeferredAttributes` is included into another module/class.
20
+ #
21
+ # @param target [Module] the including class or module
22
+ # @return [void]
23
+ def included: (::Module target) -> void
24
+
25
+ # Called when a module including `DeferredAttributes` is extended into another module/class.
26
+ #
27
+ # @param target [Module] the extending class or module
28
+ # @return [void]
29
+ def extended: (::Module target) -> void
30
+ end
31
+
32
+ # Registers an attribute to be applied later when this module is included or extended.
33
+ #
34
+ # @param attribute [Cattri::Attribute] the attribute to defer
35
+ # @return [void]
36
+ def defer_attribute: (Attribute attribute) -> void
37
+
38
+ # Applies all deferred attributes to the target class or module.
39
+ #
40
+ # This is triggered automatically by the {Hook} on `included` or `extended`.
41
+ #
42
+ # @param target [Module] the class or module to apply the attributes to
43
+ # @return [void]
44
+ def apply_deferred_attributes: (::Module target) -> void
45
+
46
+ private
47
+
48
+ # Internal storage of deferred attributes for this module.
49
+ #
50
+ # @return [Hash{Symbol => Cattri::Attribute}]
51
+ def deferred_attributes: () -> ::Hash[::Symbol, Attribute]
52
+ end
53
+ end
@@ -0,0 +1,55 @@
1
+ module Cattri
2
+ # Provides the primary DSL for defining class-level and instance-level attributes.
3
+ #
4
+ # This module is extended into any class or module that includes Cattri,
5
+ # and exposes methods like `cattri` and `final_cattri` for concise attribute declaration.
6
+ #
7
+ # All attributes are defined through the underlying attribute registry and
8
+ # are associated with method accessors (reader, writer, predicate) based on options.
9
+ module Dsl
10
+ # Defines a new attribute with optional default, coercion, and visibility options.
11
+ #
12
+ # The attribute can be defined with a static value, a lazy-evaluated block,
13
+ # or with additional options like `final`, `predicate`, or `expose`.
14
+ #
15
+ # The attribute will be defined as either class-level or instance-level
16
+ # depending on the `class:` option.
17
+ #
18
+ # @param name [Symbol, String] the attribute name
19
+ # @param value [Object, nil] optional static value or default
20
+ # @param options [Hash] additional attribute configuration
21
+ # @option options [Boolean] :class whether this is a class-level attribute
22
+ # @option options [Boolean] :final whether the attribute is write-once
23
+ # @option options [Boolean] :predicate whether to define a predicate method
24
+ # @option options [Symbol] :expose whether to expose `:read`, `:write`, `:read_write`, or `:none`
25
+ # @option options [Symbol] :visibility the visibility for generated methods (`:public`, `:protected`, `:private`)
26
+ # @yield optional block to lazily evaluate the attribute’s default value
27
+ # @return [Array<Symbol>]
28
+ def cattri: (
29
+ identifier? name,
30
+ ?untyped? value,
31
+ **attribute_options options
32
+ ) { (?) -> untyped } -> ::Array[::Symbol]
33
+
34
+ # Defines a write-once (final) attribute.
35
+ #
36
+ # Final attributes can be written only once and raise on re-assignment.
37
+ # This is equivalent to `cattri(..., final: true)`.
38
+ #
39
+ # @param name [Symbol, String] the attribute name
40
+ # @param value [Object, nil] static or lazy default value
41
+ # @param options [Hash] additional attribute configuration
42
+ # @option options [Boolean] :class whether this is a class-level attribute
43
+ # @option options [Boolean] :final whether the attribute is write-once
44
+ # @option options [Boolean] :predicate whether to define a predicate method
45
+ # @option options [Symbol] :expose whether to expose `:read`, `:write`, `:read_write`, or `:none`
46
+ # @option options [Symbol] :visibility the visibility for generated methods (`:public`, `:protected`, `:private`)
47
+ # @yield optional block to lazily evaluate the default
48
+ # @return [Array<Symbol>]
49
+ def final_cattri: (
50
+ identifier name,
51
+ untyped value,
52
+ **attribute_options options
53
+ ) { (?) -> untyped } -> ::Array[::Symbol]
54
+ end
55
+ end