castkit 0.2.0 → 0.3.1

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -1
  3. data/README.md +297 -13
  4. data/castkit.gemspec +3 -0
  5. data/lib/castkit/attribute.rb +82 -59
  6. data/lib/castkit/attributes/definition.rb +64 -0
  7. data/lib/castkit/attributes/options.rb +214 -0
  8. data/lib/castkit/castkit.rb +18 -5
  9. data/lib/castkit/cli/generate.rb +112 -0
  10. data/lib/castkit/cli/list.rb +200 -0
  11. data/lib/castkit/cli/main.rb +43 -0
  12. data/lib/castkit/cli.rb +24 -0
  13. data/lib/castkit/configuration.rb +31 -8
  14. data/lib/castkit/contract/{generic.rb → base.rb} +5 -17
  15. data/lib/castkit/contract/result.rb +2 -2
  16. data/lib/castkit/contract/validator.rb +5 -1
  17. data/lib/castkit/contract.rb +5 -5
  18. data/lib/castkit/core/attributes.rb +87 -44
  19. data/lib/castkit/data_object.rb +11 -30
  20. data/lib/castkit/{ext → dsl}/attribute/access.rb +1 -1
  21. data/lib/castkit/{ext → dsl}/attribute/error_handling.rb +1 -1
  22. data/lib/castkit/{ext → dsl}/attribute/options.rb +1 -1
  23. data/lib/castkit/{ext → dsl}/attribute/validation.rb +3 -3
  24. data/lib/castkit/dsl/attribute.rb +47 -0
  25. data/lib/castkit/{ext → dsl}/data_object/contract.rb +2 -2
  26. data/lib/castkit/{ext → dsl}/data_object/deserialization.rb +6 -2
  27. data/lib/castkit/dsl/data_object/plugins.rb +86 -0
  28. data/lib/castkit/{ext → dsl}/data_object/serialization.rb +1 -1
  29. data/lib/castkit/dsl/data_object.rb +61 -0
  30. data/lib/castkit/inflector.rb +1 -1
  31. data/lib/castkit/plugins.rb +82 -0
  32. data/lib/castkit/serializers/base.rb +94 -0
  33. data/lib/castkit/serializers/default_serializer.rb +156 -0
  34. data/lib/castkit/types/{generic.rb → base.rb} +30 -10
  35. data/lib/castkit/types/boolean.rb +14 -10
  36. data/lib/castkit/types/collection.rb +13 -2
  37. data/lib/castkit/types/date.rb +2 -2
  38. data/lib/castkit/types/date_time.rb +2 -2
  39. data/lib/castkit/types/float.rb +5 -5
  40. data/lib/castkit/types/integer.rb +5 -5
  41. data/lib/castkit/types/string.rb +2 -2
  42. data/lib/castkit/types.rb +1 -1
  43. data/lib/castkit/validators/base.rb +59 -0
  44. data/lib/castkit/validators/boolean_validator.rb +39 -0
  45. data/lib/castkit/validators/collection_validator.rb +29 -0
  46. data/lib/castkit/validators/float_validator.rb +31 -0
  47. data/lib/castkit/validators/integer_validator.rb +31 -0
  48. data/lib/castkit/validators/numeric_validator.rb +2 -2
  49. data/lib/castkit/validators/string_validator.rb +3 -4
  50. data/lib/castkit/version.rb +1 -1
  51. data/lib/castkit.rb +1 -4
  52. data/lib/generators/attribute.rb +39 -0
  53. data/lib/generators/base.rb +97 -0
  54. data/lib/generators/contract.rb +68 -0
  55. data/lib/generators/data_object.rb +48 -0
  56. data/lib/generators/plugin.rb +25 -0
  57. data/lib/generators/serializer.rb +28 -0
  58. data/lib/generators/templates/attribute.rb.tt +21 -0
  59. data/lib/generators/templates/attribute_spec.rb.tt +41 -0
  60. data/lib/generators/templates/contract.rb.tt +26 -0
  61. data/lib/generators/templates/contract_spec.rb.tt +76 -0
  62. data/lib/generators/templates/data_object.rb.tt +17 -0
  63. data/lib/generators/templates/data_object_spec.rb.tt +36 -0
  64. data/lib/generators/templates/plugin.rb.tt +37 -0
  65. data/lib/generators/templates/plugin_spec.rb.tt +18 -0
  66. data/lib/generators/templates/serializer.rb.tt +24 -0
  67. data/lib/generators/templates/serializer_spec.rb.tt +14 -0
  68. data/lib/generators/templates/type.rb.tt +57 -0
  69. data/lib/generators/templates/type_spec.rb.tt +42 -0
  70. data/lib/generators/templates/validator.rb.tt +26 -0
  71. data/lib/generators/templates/validator_spec.rb.tt +23 -0
  72. data/lib/generators/type.rb +29 -0
  73. data/lib/generators/validator.rb +41 -0
  74. metadata +92 -16
  75. data/.rspec_status +0 -196
  76. data/lib/castkit/core/registerable.rb +0 -59
  77. data/lib/castkit/default_serializer.rb +0 -154
  78. data/lib/castkit/serializer.rb +0 -92
  79. data/lib/castkit/validators/base_validator.rb +0 -39
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "generic"
3
+ require_relative "base"
4
+ require_relative "../validators/boolean_validator"
4
5
 
5
6
  module Castkit
6
7
  module Types
@@ -10,7 +11,7 @@ module Castkit
10
11
  #
11
12
  # This class is used internally by Castkit when an attribute is defined with:
12
13
  # `boolean :is_active`
13
- class Boolean < Generic
14
+ class Boolean < Base
14
15
  # Deserializes the input into a boolean value.
15
16
  #
16
17
  # Accepts:
@@ -21,14 +22,7 @@ module Castkit
21
22
  # @return [Boolean]
22
23
  # @raise [Castkit::TypeError] if the value cannot be coerced to a boolean
23
24
  def deserialize(value)
24
- case value.to_s.downcase
25
- when "true", "1"
26
- true
27
- when "false", "0"
28
- false
29
- else
30
- type_error!(:boolean, value)
31
- end
25
+ value
32
26
  end
33
27
 
34
28
  # Serializes the boolean value (pass-through).
@@ -38,6 +32,16 @@ module Castkit
38
32
  def serialize(value)
39
33
  value
40
34
  end
35
+
36
+ # Validates the Boolean value.
37
+ #
38
+ # @param value [Object]
39
+ # @param options [Hash] validation options
40
+ # @param context [Symbol, String] attribute context for error messages
41
+ # @return [void]
42
+ def validate!(value, options: {}, context: {})
43
+ Castkit::Validators::BooleanValidator.call(value, options: options, context: context)
44
+ end
41
45
  end
42
46
  end
43
47
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "generic"
3
+ require_relative "base"
4
+ require_relative "../validators/collection_validator"
4
5
 
5
6
  module Castkit
6
7
  module Types
@@ -11,7 +12,7 @@ module Castkit
11
12
  #
12
13
  # This class is used internally by Castkit when an attribute is defined with:
13
14
  # `array :tags, of: :string`
14
- class Collection < Generic
15
+ class Collection < Base
15
16
  # Deserializes the value into an array using `Array(value)`.
16
17
  #
17
18
  # @param value [Object]
@@ -19,6 +20,16 @@ module Castkit
19
20
  def deserialize(value)
20
21
  Array(value)
21
22
  end
23
+
24
+ # Validates the Array value.
25
+ #
26
+ # @param value [Object]
27
+ # @param options [Hash] validation options
28
+ # @param context [Symbol, String, nil] attribute context for error messages
29
+ # @return [void]
30
+ def validate!(value, options: {}, context: nil)
31
+ Castkit::Validators::CollectionValidator.call(value, options: options, context: context)
32
+ end
22
33
  end
23
34
  end
24
35
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "date"
4
- require_relative "generic"
4
+ require_relative "base"
5
5
 
6
6
  module Castkit
7
7
  module Types
@@ -12,7 +12,7 @@ module Castkit
12
12
  #
13
13
  # This class is used internally by Castkit when an attribute is defined with:
14
14
  # `date :published_on`
15
- class Date < Generic
15
+ class Date < Base
16
16
  # Deserializes the input value to a `Date` instance.
17
17
  #
18
18
  # @param value [Object]
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "date"
4
- require_relative "generic"
4
+ require_relative "base"
5
5
 
6
6
  module Castkit
7
7
  module Types
@@ -12,7 +12,7 @@ module Castkit
12
12
  #
13
13
  # This class is used internally by Castkit when an attribute is defined with:
14
14
  # `datetime :published_ad`
15
- class DateTime < Generic
15
+ class DateTime < Base
16
16
  # Deserializes the input value to a `DateTime` instance.
17
17
  #
18
18
  # @param value [Object]
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "generic"
4
- require_relative "../validators/numeric_validator"
3
+ require_relative "base"
4
+ require_relative "../validators/float_validator"
5
5
 
6
6
  module Castkit
7
7
  module Types
@@ -13,7 +13,7 @@ module Castkit
13
13
  #
14
14
  # This class is used internally by Castkit when an attribute is defined with:
15
15
  # `integer :count`
16
- class Float < Generic
16
+ class Float < Base
17
17
  # Deserializes the input value to an Float.
18
18
  #
19
19
  # @param value [Object]
@@ -30,7 +30,7 @@ module Castkit
30
30
  value
31
31
  end
32
32
 
33
- # Validates the Float value using Castkit's NumericValidator.
33
+ # Validates the Float value using Castkit's FloatValidator.
34
34
  #
35
35
  # Supports options like `min:` and `max:`.
36
36
  #
@@ -39,7 +39,7 @@ module Castkit
39
39
  # @param context [Symbol, String] attribute context for error messages
40
40
  # @return [void]
41
41
  def validate!(value, options: {}, context: {})
42
- Castkit::Validators::NumericValidator.call(value, options: options, context: context)
42
+ Castkit::Validators::FloatValidator.call(value, options: options, context: context)
43
43
  end
44
44
  end
45
45
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "generic"
4
- require_relative "../validators/numeric_validator"
3
+ require_relative "base"
4
+ require_relative "../validators/integer_validator"
5
5
 
6
6
  module Castkit
7
7
  module Types
@@ -13,7 +13,7 @@ module Castkit
13
13
  #
14
14
  # This class is used internally by Castkit when an attribute is defined with:
15
15
  # `integer :count`
16
- class Integer < Generic
16
+ class Integer < Base
17
17
  # Deserializes the input value to an Integer.
18
18
  #
19
19
  # @param value [Object]
@@ -30,7 +30,7 @@ module Castkit
30
30
  value
31
31
  end
32
32
 
33
- # Validates the Integer value using Castkit's NumericValidator.
33
+ # Validates the Integer value using Castkit's IntegerValidator.
34
34
  #
35
35
  # Supports options like `min:` and `max:`.
36
36
  #
@@ -39,7 +39,7 @@ module Castkit
39
39
  # @param context [Symbol, String] attribute context for error messages
40
40
  # @return [void]
41
41
  def validate!(value, options: {}, context: {})
42
- Castkit::Validators::NumericValidator.call(value, options: options, context: context)
42
+ Castkit::Validators::IntegerValidator.call(value, options: options, context: context)
43
43
  end
44
44
  end
45
45
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "generic"
3
+ require_relative "base"
4
4
  require_relative "../validators/string_validator"
5
5
 
6
6
  module Castkit
@@ -12,7 +12,7 @@ module Castkit
12
12
  #
13
13
  # This class is used internally by Castkit when an attribute is defined with:
14
14
  # `string :id`
15
- class String < Generic
15
+ class String < Base
16
16
  # Deserializes the value by coercing it to a string using `to_s`.
17
17
  #
18
18
  # @param value [Object]
data/lib/castkit/types.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "types/base"
3
4
  require_relative "types/collection"
4
5
  require_relative "types/boolean"
5
6
  require_relative "types/date"
6
7
  require_relative "types/date_time"
7
8
  require_relative "types/float"
8
- require_relative "types/generic"
9
9
  require_relative "types/integer"
10
10
  require_relative "types/string"
11
11
 
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castkit
4
+ module Validators
5
+ # Abstract base class for all attribute validators.
6
+ #
7
+ # Validators are responsible for enforcing value constraints (e.g., type checks,
8
+ # format rules, numerical bounds). All validators must inherit from this base class
9
+ # and implement the `#call` instance method.
10
+ #
11
+ # Supports both instance-level and class-level invocation via `.call`.
12
+ #
13
+ # @abstract Subclasses must implement `#call`.
14
+ class Base
15
+ class << self
16
+ # Entry point for validating a value using the validator class.
17
+ #
18
+ # Instantiates the validator and invokes `#call` with the provided arguments.
19
+ #
20
+ # @param value [Object] the value to validate
21
+ # @param options [Hash] additional validation options (e.g., `min`, `max`, `format`)
22
+ # @param context [Symbol, String, Hash] context for the validation (usually the attribute name)
23
+ # @return [void]
24
+ # @raise [Castkit::AttributeError] if validation fails and `raise_type_errors` is true
25
+ def call(value, options:, context:)
26
+ new.call(value, options: options, context: context)
27
+ end
28
+ end
29
+
30
+ # Validates the value.
31
+ #
32
+ # @abstract
33
+ # @param value [Object] the value to validate
34
+ # @param options [Hash] validation options (e.g., min, max, format)
35
+ # @param context [Symbol, String, Hash] context for validation errors
36
+ # @return [void]
37
+ # @raise [NotImplementedError] if not implemented in subclass
38
+ def call(value, options:, context:)
39
+ raise NotImplementedError, "#{self.class.name} must implement `#call`"
40
+ end
41
+
42
+ protected
43
+
44
+ # Emits or raises a type error depending on global configuration.
45
+ #
46
+ # @param type [Symbol] the expected type (e.g., `:integer`)
47
+ # @param value [Object, nil] the received value
48
+ # @param context [Symbol, String, nil] context to include in error messages
49
+ # @raise [Castkit::AttributeError] if `raise_type_errors` is enabled
50
+ # @return [void]
51
+ def type_error!(type, value, context: nil)
52
+ message = "#{context || "value"} must be a #{type}, got #{value}"
53
+ raise Castkit::AttributeError, message if Castkit.configuration.raise_type_errors
54
+
55
+ Castkit.warning "[Castkit] #{message}" if Castkit.configuration.enable_warnings
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Castkit
6
+ module Validators
7
+ # Validator for boolean attributes.
8
+ #
9
+ # Accepts various representations of boolean values, including strings and integers.
10
+ # Converts common truthy/falsy string values into booleans, otherwise raises a type error.
11
+ #
12
+ # This validator is typically used internally by `Castkit::Types::Boolean`.
13
+ #
14
+ # @example
15
+ # validator = Castkit::Validators::BooleanValidator.new
16
+ # validator.call("true", _options: {}, context: :enabled) # => true
17
+ # validator.call("0", _options: {}, context: :enabled) # => false
18
+ # validator.call("nope", _options: {}, context: :enabled) # raises Castkit::AttributeError
19
+ class BooleanValidator < Castkit::Validators::Base
20
+ # Validates the Boolean value.
21
+ #
22
+ # @param value [Object] the input to validate
23
+ # @param options [Hash] unused, provided for consistency with other validators
24
+ # @param context [Symbol, String] the attribute name or path for error messages
25
+ # @return [Boolean]
26
+ # @raise [Castkit::AttributeError] if the value is not a recognizable boolean
27
+ def call(value, options:, context:) # rubocop:disable Lint/UnusedMethodArgument
28
+ case value.to_s.downcase
29
+ when "true", "1"
30
+ true
31
+ when "false", "0"
32
+ false
33
+ else
34
+ type_error!(:boolean, value, context: context)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../validators/base"
4
+
5
+ module Castkit
6
+ module Validators
7
+ # Validator for array (collection) attributes.
8
+ #
9
+ # Ensures that the provided value is an instance of `Array`. This validator is
10
+ # typically used by `Castkit::Types::Collection` for attributes defined as arrays.
11
+ #
12
+ # @example
13
+ # validator = Castkit::Validators::CollectionValidator.new
14
+ # validator.call([1, 2, 3], _options: {}, context: :tags) # => passes
15
+ # validator.call("foo", _options: {}, context: :tags) # raises Castkit::AttributeError
16
+ class CollectionValidator < Castkit::Validators::Base
17
+ # Validates that the value is an Array.
18
+ #
19
+ # @param value [Object] the value to validate
20
+ # @param _options [Hash] unused, for interface consistency
21
+ # @param context [Symbol, String] the field or context for error messaging
22
+ # @return [void]
23
+ # @raise [Castkit::AttributeError] if value is not an Array
24
+ def call(value, _options:, context:)
25
+ type_error!(:array, value, context: context) unless value.is_a?(::Array)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "numeric_validator"
4
+
5
+ module Castkit
6
+ module Validators
7
+ # Validator for Float attributes.
8
+ #
9
+ # Ensures the value is a `Float`, and applies any numeric bounds (`min`, `max`)
10
+ # defined in the attribute options. Inherits shared logic from `NumericValidator`.
11
+ #
12
+ # @example
13
+ # validator = Castkit::Validators::FloatValidator.new
14
+ # validator.call(3.14, options: { min: 0.0 }, context: :price) # => passes
15
+ # validator.call(42, options: {}, context: :price) # raises Castkit::AttributeError
16
+ class FloatValidator < Castkit::Validators::NumericValidator
17
+ # Validates that the value is a Float and within optional bounds.
18
+ #
19
+ # @param value [Object, nil] the value to validate
20
+ # @param options [Hash] validation options (e.g., `min`, `max`)
21
+ # @param context [Symbol, String] the attribute name or key for error messages
22
+ # @raise [Castkit::AttributeError] if value is not a Float or out of range
23
+ # @return [void]
24
+ def call(value, options:, context:)
25
+ return type_error!(:float, value, context: context) unless value.is_a?(::Float)
26
+
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "numeric_validator"
4
+
5
+ module Castkit
6
+ module Validators
7
+ # Validator for Integer attributes.
8
+ #
9
+ # Ensures the value is an instance of `Integer` and optionally checks numerical constraints
10
+ # such as `min` and `max`, inherited from `Castkit::Validators::NumericValidator`.
11
+ #
12
+ # @example Validating an Integer attribute
13
+ # IntegerValidator.call(42, options: { min: 10, max: 100 }, context: :count)
14
+ #
15
+ # @see Castkit::Validators::NumericValidator
16
+ class IntegerValidator < Castkit::Validators::NumericValidator
17
+ # Validates the Integer value.
18
+ #
19
+ # @param value [Object, nil] the value to validate
20
+ # @param options [Hash] validation options (e.g., `min`, `max`)
21
+ # @param context [Symbol, String] the attribute name or context for error messages
22
+ # @raise [Castkit::AttributeError] if the value is not an Integer or fails validation rules
23
+ # @return [void]
24
+ def call(value, options:, context:)
25
+ return type_error!(:integer, value, context: context) unless value.is_a?(::Integer)
26
+
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_validator"
3
+ require_relative "base"
4
4
 
5
5
  module Castkit
6
6
  module Validators
7
7
  # Validates that a numeric value falls within the allowed range.
8
8
  #
9
9
  # Supports `:min` and `:max` options to enforce bounds.
10
- class NumericValidator < Castkit::Validators::BaseValidator
10
+ class NumericValidator < Castkit::Validators::Base
11
11
  # Validates the numeric value.
12
12
  #
13
13
  # @param value [Numeric] the value to validate
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_validator"
3
+ require_relative "base"
4
4
 
5
5
  module Castkit
6
6
  module Validators
7
7
  # Validates that a value is a String and optionally conforms to a format.
8
8
  #
9
9
  # Supports format validation using a Regexp or a custom Proc.
10
- class StringValidator < Castkit::Validators::BaseValidator
10
+ class StringValidator < Castkit::Validators::Base
11
11
  # Validates the string value.
12
12
  #
13
13
  # @param value [Object] the value to validate
@@ -16,8 +16,7 @@ module Castkit
16
16
  # @raise [Castkit::AttributeError] if value is not a string or fails format validation
17
17
  # @return [void]
18
18
  def call(value, options:, context:)
19
- raise Castkit::AttributeError, "#{context} must be a string" unless value.is_a?(String)
20
-
19
+ return type_error!(:string, value, context: context) unless value.is_a?(::String)
21
20
  return unless options[:format]
22
21
 
23
22
  case options[:format]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castkit
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/castkit.rb CHANGED
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "castkit/version"
4
- require_relative "castkit/attribute"
5
- require_relative "castkit/contract"
6
- require_relative "castkit/data_object"
3
+ require_relative "castkit/castkit"
7
4
 
8
5
  # Castkit is a lightweight, type-safe data object system for Ruby.
9
6
  #
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor/group"
4
+ require "castkit/inflector"
5
+ require_relative "base"
6
+
7
+ module Castkit
8
+ module Generators
9
+ # Generator for creating Castkit attribute definitions.
10
+ #
11
+ # Generates a class inheriting from `Castkit::Attributes::Definition`
12
+ # and an optional spec file.
13
+ #
14
+ # Example:
15
+ # $ castkit generate attribute OptionalString required:false default:"N/A"
16
+ #
17
+ # This will generate:
18
+ # - lib/castkit/attributes/optional_string.rb
19
+ # - spec/castkit/attributes/optional_string_spec.rb
20
+ #
21
+ # @see Castkit::Generators::Base
22
+ class Attribute < Castkit::Generators::Base
23
+ component :attribute
24
+
25
+ argument :type,
26
+ type: :string,
27
+ desc: "The base type (e.g., string, integer)"
28
+
29
+ private
30
+
31
+ # @return [Hash] configuration passed into templates
32
+ def config
33
+ super.merge(
34
+ type: type.first.gsub(/^:/, "")
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor/group"
4
+ require "castkit/inflector"
5
+
6
+ module Castkit
7
+ module Generators
8
+ # Abstract base class for all Castkit generators.
9
+ #
10
+ # Provides standard behavior for generating a component and optional spec file from
11
+ # ERB templates. Subclasses must define a `component` (e.g., `:type`, `:contract`)
12
+ # and may override `config` or `default_values`.
13
+ #
14
+ # Template variables are injected using the `config` hash, which includes:
15
+ # - `:name` – underscored version of the component name
16
+ # - `:class_name` – PascalCase version of the component name
17
+ #
18
+ # @abstract
19
+ class Base < Thor::Group
20
+ include Thor::Actions
21
+
22
+ class << self
23
+ # Sets or retrieves the component type (e.g., :type, :data_object).
24
+ #
25
+ # @param value [Symbol, nil]
26
+ # @return [Symbol]
27
+ def component(value = nil)
28
+ value.nil? ? @component : (@component = value)
29
+ end
30
+
31
+ # @return [String] the root path to look for templates
32
+ def source_root
33
+ File.expand_path("templates", __dir__)
34
+ end
35
+ end
36
+
37
+ argument :name, desc: "The name of the component to generate"
38
+ class_option :spec, type: :boolean, default: true, desc: "Also generate a spec file"
39
+
40
+ # Creates the main component file using a template.
41
+ #
42
+ # Template: `component.rb.tt`
43
+ # Target: `lib/castkit/#{component}s/#{name}.rb`
44
+ #
45
+ # @return [void]
46
+ def create_component
47
+ template(
48
+ "#{self.class.component}.rb.tt",
49
+ "lib/castkit/#{self.class.component}s/#{config[:name]}.rb", **config
50
+ )
51
+ end
52
+
53
+ # Creates the associated spec file, if enabled.
54
+ #
55
+ # Template: `component_spec.rb.tt`
56
+ # Target: `spec/castkit/#{component}s/#{name}_spec.rb`
57
+ #
58
+ # @return [void]
59
+ def create_spec
60
+ return unless options[:spec]
61
+
62
+ template(
63
+ "#{self.class.component}_spec.rb.tt",
64
+ "spec/castkit/#{self.class.component}s/#{config[:name]}_spec.rb", **config
65
+ )
66
+ end
67
+
68
+ private
69
+
70
+ # Default values for test inputs based on type.
71
+ #
72
+ # These are used in spec templates to provide sample data.
73
+ #
74
+ # @return [Hash{Symbol => Object}]
75
+ def default_values
76
+ {
77
+ string: '"example"',
78
+ integer: 42,
79
+ float: 3.14,
80
+ boolean: true,
81
+ array: [],
82
+ hash: {}
83
+ }
84
+ end
85
+
86
+ # Returns the default config hash passed into templates.
87
+ #
88
+ # @return [Hash{Symbol => Object}]
89
+ def config
90
+ {
91
+ name: Castkit::Inflector.underscore(name),
92
+ class_name: Castkit::Inflector.pascalize(name)
93
+ }
94
+ end
95
+ end
96
+ end
97
+ end