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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -1
- data/README.md +297 -13
- data/castkit.gemspec +3 -0
- data/lib/castkit/attribute.rb +82 -59
- data/lib/castkit/attributes/definition.rb +64 -0
- data/lib/castkit/attributes/options.rb +214 -0
- data/lib/castkit/castkit.rb +18 -5
- data/lib/castkit/cli/generate.rb +112 -0
- data/lib/castkit/cli/list.rb +200 -0
- data/lib/castkit/cli/main.rb +43 -0
- data/lib/castkit/cli.rb +24 -0
- data/lib/castkit/configuration.rb +31 -8
- data/lib/castkit/contract/{generic.rb → base.rb} +5 -17
- data/lib/castkit/contract/result.rb +2 -2
- data/lib/castkit/contract/validator.rb +5 -1
- data/lib/castkit/contract.rb +5 -5
- data/lib/castkit/core/attributes.rb +87 -44
- data/lib/castkit/data_object.rb +11 -30
- data/lib/castkit/{ext → dsl}/attribute/access.rb +1 -1
- data/lib/castkit/{ext → dsl}/attribute/error_handling.rb +1 -1
- data/lib/castkit/{ext → dsl}/attribute/options.rb +1 -1
- data/lib/castkit/{ext → dsl}/attribute/validation.rb +3 -3
- data/lib/castkit/dsl/attribute.rb +47 -0
- data/lib/castkit/{ext → dsl}/data_object/contract.rb +2 -2
- data/lib/castkit/{ext → dsl}/data_object/deserialization.rb +6 -2
- data/lib/castkit/dsl/data_object/plugins.rb +86 -0
- data/lib/castkit/{ext → dsl}/data_object/serialization.rb +1 -1
- data/lib/castkit/dsl/data_object.rb +61 -0
- data/lib/castkit/inflector.rb +1 -1
- data/lib/castkit/plugins.rb +82 -0
- data/lib/castkit/serializers/base.rb +94 -0
- data/lib/castkit/serializers/default_serializer.rb +156 -0
- data/lib/castkit/types/{generic.rb → base.rb} +30 -10
- data/lib/castkit/types/boolean.rb +14 -10
- data/lib/castkit/types/collection.rb +13 -2
- data/lib/castkit/types/date.rb +2 -2
- data/lib/castkit/types/date_time.rb +2 -2
- data/lib/castkit/types/float.rb +5 -5
- data/lib/castkit/types/integer.rb +5 -5
- data/lib/castkit/types/string.rb +2 -2
- data/lib/castkit/types.rb +1 -1
- data/lib/castkit/validators/base.rb +59 -0
- data/lib/castkit/validators/boolean_validator.rb +39 -0
- data/lib/castkit/validators/collection_validator.rb +29 -0
- data/lib/castkit/validators/float_validator.rb +31 -0
- data/lib/castkit/validators/integer_validator.rb +31 -0
- data/lib/castkit/validators/numeric_validator.rb +2 -2
- data/lib/castkit/validators/string_validator.rb +3 -4
- data/lib/castkit/version.rb +1 -1
- data/lib/castkit.rb +1 -4
- data/lib/generators/attribute.rb +39 -0
- data/lib/generators/base.rb +97 -0
- data/lib/generators/contract.rb +68 -0
- data/lib/generators/data_object.rb +48 -0
- data/lib/generators/plugin.rb +25 -0
- data/lib/generators/serializer.rb +28 -0
- data/lib/generators/templates/attribute.rb.tt +21 -0
- data/lib/generators/templates/attribute_spec.rb.tt +41 -0
- data/lib/generators/templates/contract.rb.tt +26 -0
- data/lib/generators/templates/contract_spec.rb.tt +76 -0
- data/lib/generators/templates/data_object.rb.tt +17 -0
- data/lib/generators/templates/data_object_spec.rb.tt +36 -0
- data/lib/generators/templates/plugin.rb.tt +37 -0
- data/lib/generators/templates/plugin_spec.rb.tt +18 -0
- data/lib/generators/templates/serializer.rb.tt +24 -0
- data/lib/generators/templates/serializer_spec.rb.tt +14 -0
- data/lib/generators/templates/type.rb.tt +57 -0
- data/lib/generators/templates/type_spec.rb.tt +42 -0
- data/lib/generators/templates/validator.rb.tt +26 -0
- data/lib/generators/templates/validator_spec.rb.tt +23 -0
- data/lib/generators/type.rb +29 -0
- data/lib/generators/validator.rb +41 -0
- metadata +92 -16
- data/.rspec_status +0 -196
- data/lib/castkit/core/registerable.rb +0 -59
- data/lib/castkit/default_serializer.rb +0 -154
- data/lib/castkit/serializer.rb +0 -92
- data/lib/castkit/validators/base_validator.rb +0 -39
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "options"
|
|
4
|
+
|
|
5
|
+
module Castkit
|
|
6
|
+
module Attributes
|
|
7
|
+
# Provides a class-based DSL for defining reusable attribute definitions.
|
|
8
|
+
#
|
|
9
|
+
# Extend this class in a subclass of `Castkit::Attributes::Base` to define
|
|
10
|
+
# shared attribute settings that can be reused across multiple DataObjects.
|
|
11
|
+
#
|
|
12
|
+
# @example Defining a reusable attribute
|
|
13
|
+
# class UuidDefinition < Castkit::Attributes::Base
|
|
14
|
+
# type :string
|
|
15
|
+
# required true
|
|
16
|
+
# format /\A[0-9a-f\-]{36}\z/
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# attribute :id, UuidDefinition.definition
|
|
20
|
+
#
|
|
21
|
+
class Definition
|
|
22
|
+
extend Castkit::Attributes::Options
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
# @return [Hash] the internal definition hash, containing the type and options
|
|
26
|
+
def definition
|
|
27
|
+
@definition ||= {
|
|
28
|
+
type: nil,
|
|
29
|
+
options: Castkit::Attributes::Options::DEFAULTS.dup
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Hash] the attribute options defined on this class
|
|
34
|
+
def options
|
|
35
|
+
definition[:options]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Defines the attribute's type and configuration using a DSL block.
|
|
39
|
+
#
|
|
40
|
+
# @param type [Symbol, Class<Castkit::DataObject>] the attribute type (e.g., :string, :integer)
|
|
41
|
+
# @param options [Hash] additional options to merge after the block (e.g., default:, access:)
|
|
42
|
+
# @yield DSL block used to set options like `required`, `format`, `readonly`, etc.
|
|
43
|
+
# @return [Array<(Symbol, Hash)>] a tuple of the final type and options hash
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# define :string, default: "none" do
|
|
47
|
+
# required true
|
|
48
|
+
# access [:read]
|
|
49
|
+
# end
|
|
50
|
+
def define(type, **options, &block)
|
|
51
|
+
@__castkit_attribute_dsl = true
|
|
52
|
+
|
|
53
|
+
definition[:type] = type
|
|
54
|
+
instance_eval(&block)
|
|
55
|
+
definition[:options] = definition[:options].merge(options)
|
|
56
|
+
|
|
57
|
+
definition
|
|
58
|
+
ensure
|
|
59
|
+
@__castkit_attribute_dsl = false
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Castkit
|
|
4
|
+
module Attributes
|
|
5
|
+
# Provides a DSL for configuring attribute options within an attribute definition.
|
|
6
|
+
#
|
|
7
|
+
# This module is designed to be extended by class-level definition objects such as
|
|
8
|
+
# `Castkit::Attributes::Definition`, and is used to build reusable sets of options
|
|
9
|
+
# for attributes declared within `Castkit::DataObject` classes.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# class OptionalString < Castkit::Attributes::Definition
|
|
13
|
+
# type :string
|
|
14
|
+
# required false
|
|
15
|
+
# ignore_blank true
|
|
16
|
+
# end
|
|
17
|
+
module Options
|
|
18
|
+
# Valid access modes for an attribute.
|
|
19
|
+
#
|
|
20
|
+
# @return [Array<Symbol>]
|
|
21
|
+
ACCESS_MODES = %i[read write].freeze
|
|
22
|
+
|
|
23
|
+
# Default configuration for attribute options.
|
|
24
|
+
#
|
|
25
|
+
# @return [Hash{Symbol => Object}]
|
|
26
|
+
DEFAULTS = {
|
|
27
|
+
required: true,
|
|
28
|
+
ignore_nil: false,
|
|
29
|
+
ignore_blank: false,
|
|
30
|
+
ignore: false,
|
|
31
|
+
composite: false,
|
|
32
|
+
transient: false,
|
|
33
|
+
unwrapped: false,
|
|
34
|
+
prefix: nil,
|
|
35
|
+
access: ACCESS_MODES,
|
|
36
|
+
force_type: !Castkit.configuration.enforce_typing
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
# Sets or retrieves the attribute type.
|
|
40
|
+
#
|
|
41
|
+
# @param value [Symbol, nil] The type to assign (e.g., :string), or nil to fetch.
|
|
42
|
+
# @return [Symbol]
|
|
43
|
+
def type(value = nil)
|
|
44
|
+
value.nil? ? definition[:type] : (definition[:type] = value.to_sym)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Sets the element type for array attributes.
|
|
48
|
+
#
|
|
49
|
+
# @param value [Symbol, Class] the type of elements in the array
|
|
50
|
+
# @return [void]
|
|
51
|
+
def of(value)
|
|
52
|
+
return unless @type == :array
|
|
53
|
+
|
|
54
|
+
set_option(:of, value)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Sets the default value or proc for the attribute.
|
|
58
|
+
#
|
|
59
|
+
# @param value [Object, Proc] the default value or lambda
|
|
60
|
+
# @return [void]
|
|
61
|
+
def default(value = nil)
|
|
62
|
+
set_option(:default, value)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Enables or disables forced typecasting, or sets a custom flag.
|
|
66
|
+
#
|
|
67
|
+
# @param value [Boolean, nil] the forced type flag
|
|
68
|
+
# @return [void]
|
|
69
|
+
def force_type(value = nil)
|
|
70
|
+
set_option(:force_type, value || !Castkit.configuration.enforce_typing)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Marks the attribute as required or optional.
|
|
74
|
+
#
|
|
75
|
+
# @param value [Boolean]
|
|
76
|
+
# @return [void]
|
|
77
|
+
def required(value = nil)
|
|
78
|
+
set_option(:required, value || true)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Marks the attribute to be ignored entirely.
|
|
82
|
+
#
|
|
83
|
+
# @param value [Boolean]
|
|
84
|
+
# @return [void]
|
|
85
|
+
def ignore(value = nil)
|
|
86
|
+
set_option(:ignore, value || true)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Ignores `nil` values during serialization or persistence.
|
|
90
|
+
#
|
|
91
|
+
# @param value [Boolean]
|
|
92
|
+
# @return [void]
|
|
93
|
+
def ignore_nil(value = nil)
|
|
94
|
+
set_option(:ignore_nil, value || true)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Ignores blank values (`""`, `[]`, `{}`) during serialization.
|
|
98
|
+
#
|
|
99
|
+
# @param value [Boolean]
|
|
100
|
+
# @return [void]
|
|
101
|
+
def ignore_blank(value = nil)
|
|
102
|
+
set_option(:ignore_blank, value || true)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Adds a prefix for unwrapped attribute keys.
|
|
106
|
+
#
|
|
107
|
+
# @param value [String, Symbol, nil]
|
|
108
|
+
# @return [void]
|
|
109
|
+
def prefix(value = nil)
|
|
110
|
+
set_option(:prefix, value)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Marks the attribute as unwrapped (inline merging of nested fields).
|
|
114
|
+
#
|
|
115
|
+
# @param value [Boolean]
|
|
116
|
+
# @return [void]
|
|
117
|
+
def unwrapped(value = nil)
|
|
118
|
+
set_option(:unwrapped, value || true)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Sets access modes for the attribute.
|
|
122
|
+
#
|
|
123
|
+
# @param value [Array<Symbol>, Symbol] valid values: `:read`, `:write`, or both
|
|
124
|
+
# @return [void]
|
|
125
|
+
def access(value = nil)
|
|
126
|
+
value = validate_access_modes!(value)
|
|
127
|
+
set_option(:access, value)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Shortcut to make the attribute readonly (`access: [:read]`).
|
|
131
|
+
#
|
|
132
|
+
# @param value [Boolean]
|
|
133
|
+
# @return [void]
|
|
134
|
+
def readonly(value = nil)
|
|
135
|
+
value = value || true ? [:read] : ACCESS_MODES
|
|
136
|
+
set_option(:access, value)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Marks the attribute as a composite (e.g., nested `DataObject`).
|
|
140
|
+
#
|
|
141
|
+
# @param value [Boolean]
|
|
142
|
+
# @return [void]
|
|
143
|
+
def composite(value = nil)
|
|
144
|
+
set_option(:composite, value || true)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Marks the attribute as transient (not included in persistence or serialization).
|
|
148
|
+
#
|
|
149
|
+
# @param value [Boolean]
|
|
150
|
+
# @return [void]
|
|
151
|
+
def transient(value = nil)
|
|
152
|
+
set_option(:transient, value || true)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Sets a format constraint (e.g., regex validation).
|
|
156
|
+
#
|
|
157
|
+
# @param value [Regexp]
|
|
158
|
+
# @return [void]
|
|
159
|
+
def format(value)
|
|
160
|
+
set_option(:format, value)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Attaches a custom validator callable for this attribute.
|
|
164
|
+
#
|
|
165
|
+
# @param value [Proc, #call]
|
|
166
|
+
# @return [void]
|
|
167
|
+
def validator(value)
|
|
168
|
+
set_option(:validator, value)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
# Converts class or symbol into a normalized type symbol.
|
|
174
|
+
#
|
|
175
|
+
# @param type [Class, Symbol]
|
|
176
|
+
# @return [Symbol]
|
|
177
|
+
# @raise [Castkit::AttributeError] if type cannot be resolved
|
|
178
|
+
def process_type(type)
|
|
179
|
+
case type
|
|
180
|
+
when Class
|
|
181
|
+
return :boolean if [TrueClass, FalseClass].include?(type)
|
|
182
|
+
|
|
183
|
+
type.name.downcase.to_sym
|
|
184
|
+
when Symbol
|
|
185
|
+
type
|
|
186
|
+
else
|
|
187
|
+
raise Castkit::AttributeError.new("Unknown type: #{type.inspect}", context: to_h)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Sets an option key-value pair in the current definition.
|
|
192
|
+
#
|
|
193
|
+
# @param option [Symbol]
|
|
194
|
+
# @param value [Object, nil]
|
|
195
|
+
# @return [Object, nil]
|
|
196
|
+
def set_option(option, value)
|
|
197
|
+
value.nil? ? definition[:options][option] : (definition[:options][option] = value)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Validates and normalizes access mode array.
|
|
201
|
+
#
|
|
202
|
+
# @param value [Array<Symbol>, Symbol, nil]
|
|
203
|
+
# @return [Array<Symbol>]
|
|
204
|
+
# @raise [Castkit::AttributeError] if invalid modes are present
|
|
205
|
+
def validate_access_modes!(value)
|
|
206
|
+
value_array = Array(value || ACCESS_MODES).compact
|
|
207
|
+
unknown_modes = value_array - ACCESS_MODES
|
|
208
|
+
return value_array if unknown_modes.empty?
|
|
209
|
+
|
|
210
|
+
raise Castkit::AttributeError.new("Unknown access flags: #{unknown_modes.inspect}", context: to_h)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
data/lib/castkit/castkit.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
4
|
-
require_relative "inflector"
|
|
3
|
+
require_relative "core/attribute_types"
|
|
5
4
|
|
|
6
5
|
# Castkit is a lightweight, type-safe data object system for Ruby.
|
|
7
6
|
#
|
|
@@ -20,12 +19,15 @@ require_relative "inflector"
|
|
|
20
19
|
# @see Castkit::Contract
|
|
21
20
|
# @see Castkit::DataObject
|
|
22
21
|
module Castkit
|
|
23
|
-
# Namespace used for registering
|
|
22
|
+
# Namespace used for registering generated DataObjects.
|
|
24
23
|
module DataObjects; end
|
|
25
24
|
|
|
26
|
-
# Namespace used for registering
|
|
25
|
+
# Namespace used for registering generated contracts.
|
|
27
26
|
module Contracts; end
|
|
28
27
|
|
|
28
|
+
# Namespace used for registering generated plugins.
|
|
29
|
+
module Plugins; end
|
|
30
|
+
|
|
29
31
|
class << self
|
|
30
32
|
# Yields the global configuration object for customization.
|
|
31
33
|
#
|
|
@@ -60,7 +62,10 @@ module Castkit
|
|
|
60
62
|
# @param obj [Object] the object to test
|
|
61
63
|
# @return [Boolean] true if obj is a Castkit::DataObject class
|
|
62
64
|
def dataobject?(obj)
|
|
63
|
-
obj.is_a?(Class) &&
|
|
65
|
+
obj.is_a?(Class) && (
|
|
66
|
+
obj <= Castkit::DataObject ||
|
|
67
|
+
obj.ancestors.include?(Castkit::DSL::DataObject)
|
|
68
|
+
)
|
|
64
69
|
end
|
|
65
70
|
|
|
66
71
|
# Returns a type caster lambda for the given type.
|
|
@@ -102,3 +107,11 @@ module Castkit
|
|
|
102
107
|
end
|
|
103
108
|
end
|
|
104
109
|
end
|
|
110
|
+
|
|
111
|
+
require_relative "configuration"
|
|
112
|
+
require_relative "plugins"
|
|
113
|
+
require_relative "inflector"
|
|
114
|
+
require_relative "version"
|
|
115
|
+
require_relative "attribute"
|
|
116
|
+
require_relative "contract"
|
|
117
|
+
require_relative "data_object"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require_relative "../../generators/attribute"
|
|
5
|
+
require_relative "../../generators/contract"
|
|
6
|
+
require_relative "../../generators/data_object"
|
|
7
|
+
require_relative "../../generators/plugin"
|
|
8
|
+
require_relative "../../generators/serializer"
|
|
9
|
+
require_relative "../../generators/type"
|
|
10
|
+
require_relative "../../generators/validator"
|
|
11
|
+
|
|
12
|
+
module Castkit
|
|
13
|
+
module CLI
|
|
14
|
+
# Thor CLI class for generating Castkit components.
|
|
15
|
+
#
|
|
16
|
+
# Provides `castkit generate` commands for each major Castkit component, including types,
|
|
17
|
+
# data objects, contracts, validators, serializers, and plugins.
|
|
18
|
+
#
|
|
19
|
+
# All generators support the `--no-spec` flag to skip spec file creation.
|
|
20
|
+
class Generate < Thor
|
|
21
|
+
desc "contract NAME", "Generates a new Castkit contract"
|
|
22
|
+
method_option :spec, type: :boolean, default: true
|
|
23
|
+
# Generates a new contract class.
|
|
24
|
+
#
|
|
25
|
+
# @param name [String] the class name for the contract
|
|
26
|
+
# @param fields [Array<String>] optional attribute definitions
|
|
27
|
+
# @return [void]
|
|
28
|
+
def contract(name, *fields)
|
|
29
|
+
args = [Castkit::Inflector.pascalize(name), fields]
|
|
30
|
+
args << "--no-spec" unless options[:spec]
|
|
31
|
+
Castkit::Generators::Contract.start(args)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "attribute NAME", "Generates a new Castkit attribute"
|
|
35
|
+
method_option :spec, type: :boolean, default: true
|
|
36
|
+
# Generates a new attribute definition class.
|
|
37
|
+
#
|
|
38
|
+
# @param name [String] the class name for the attribute
|
|
39
|
+
# @param fields [Array<String>] optional attribute options
|
|
40
|
+
# @return [void]
|
|
41
|
+
def attribute(name, *fields)
|
|
42
|
+
args = [Castkit::Inflector.pascalize(name), fields]
|
|
43
|
+
args << "--no-spec" unless options[:spec]
|
|
44
|
+
Castkit::Generators::Attribute.start(args)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
desc "dataobject NAME", "Generates a new Castkit DataObject"
|
|
48
|
+
method_option :spec, type: :boolean, default: true
|
|
49
|
+
# Generates a new DataObject class.
|
|
50
|
+
#
|
|
51
|
+
# @param name [String] the class name for the data object
|
|
52
|
+
# @param fields [Array<String>] optional attribute definitions
|
|
53
|
+
# @return [void]
|
|
54
|
+
def dataobject(name, *fields)
|
|
55
|
+
args = [Castkit::Inflector.pascalize(name), fields]
|
|
56
|
+
args << "--no-spec" unless options[:spec]
|
|
57
|
+
Castkit::Generators::DataObject.start(args)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
desc "plugin NAME", "Generates a new Castkit plugin"
|
|
61
|
+
method_option :spec, type: :boolean, default: true
|
|
62
|
+
# Generates a new plugin module.
|
|
63
|
+
#
|
|
64
|
+
# @param name [String] the module name for the plugin
|
|
65
|
+
# @param fields [Array<String>] optional stub fields
|
|
66
|
+
# @return [void]
|
|
67
|
+
def plugin(name, *fields)
|
|
68
|
+
args = [Castkit::Inflector.pascalize(name), fields]
|
|
69
|
+
args << "--no-spec" unless options[:spec]
|
|
70
|
+
Castkit::Generators::Plugin.start(args)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
desc "serializer NAME", "Generates a new Castkit serializer"
|
|
74
|
+
method_option :spec, type: :boolean, default: true
|
|
75
|
+
# Generates a new custom serializer class.
|
|
76
|
+
#
|
|
77
|
+
# @param name [String] the class name for the serializer
|
|
78
|
+
# @param fields [Array<String>] optional stub fields
|
|
79
|
+
# @return [void]
|
|
80
|
+
def serializer(name, *fields)
|
|
81
|
+
args = [Castkit::Inflector.pascalize(name), fields]
|
|
82
|
+
args << "--no-spec" unless options[:spec]
|
|
83
|
+
Castkit::Generators::Serializer.start(args)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
desc "type NAME", "Generates a new Castkit type"
|
|
87
|
+
method_option :spec, type: :boolean, default: true
|
|
88
|
+
# Generates a new custom type.
|
|
89
|
+
#
|
|
90
|
+
# @param name [String] the class name for the type
|
|
91
|
+
# @return [void]
|
|
92
|
+
def type(name)
|
|
93
|
+
args = [Castkit::Inflector.pascalize(name)]
|
|
94
|
+
args << "--no-spec" unless options[:spec]
|
|
95
|
+
Castkit::Generators::Type.start(args)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
desc "validator NAME", "Generates a new Castkit validator"
|
|
99
|
+
method_option :spec, type: :boolean, default: true
|
|
100
|
+
# Generates a new validator class.
|
|
101
|
+
#
|
|
102
|
+
# @param name [String] the class name for the validator
|
|
103
|
+
# @param fields [Array<String>] optional stub fields
|
|
104
|
+
# @return [void]
|
|
105
|
+
def validator(name, *fields)
|
|
106
|
+
args = [Castkit::Inflector.pascalize(name), fields]
|
|
107
|
+
args << "--no-spec" unless options[:spec]
|
|
108
|
+
Castkit::Generators::Validator.start(args)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require "castkit"
|
|
5
|
+
require_relative "../../generators/contract"
|
|
6
|
+
require_relative "../../generators/data_object"
|
|
7
|
+
require_relative "../../generators/plugin"
|
|
8
|
+
require_relative "../../generators/serializer"
|
|
9
|
+
require_relative "../../generators/type"
|
|
10
|
+
require_relative "../../generators/validator"
|
|
11
|
+
|
|
12
|
+
module Castkit
|
|
13
|
+
module CLI
|
|
14
|
+
# CLI commands for listing internal Castkit registry components.
|
|
15
|
+
#
|
|
16
|
+
# Supports listing:
|
|
17
|
+
# - Registered types (`castkit list types`)
|
|
18
|
+
# - Available validators (`castkit list validators`)
|
|
19
|
+
#
|
|
20
|
+
# @example Show all available types
|
|
21
|
+
# $ castkit list types
|
|
22
|
+
#
|
|
23
|
+
# @example Show all defined validators
|
|
24
|
+
# $ castkit list validators
|
|
25
|
+
class List < Thor
|
|
26
|
+
desc "types", "Lists registered Castkit types"
|
|
27
|
+
# Lists registered Castkit types, grouped into native and custom-defined.
|
|
28
|
+
#
|
|
29
|
+
# @return [void]
|
|
30
|
+
def types
|
|
31
|
+
all_keys = Castkit.configuration.types
|
|
32
|
+
default_keys = Castkit::Configuration::DEFAULT_TYPES.keys
|
|
33
|
+
|
|
34
|
+
native_types(all_keys, default_keys)
|
|
35
|
+
custom_types(all_keys, default_keys)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "contracts", "Lists all generated Castkit contracts"
|
|
39
|
+
# Lists all Castkit contract classes defined in the file system or registered under the Castkit namespace.
|
|
40
|
+
#
|
|
41
|
+
# @return [void]
|
|
42
|
+
def contracts
|
|
43
|
+
list_files("contracts")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "dataobjects", "Lists all generated Castkit DataObjects"
|
|
47
|
+
# Lists all Castkit DataObjects classes defined in the file system or registered under the Castkit namespace.
|
|
48
|
+
#
|
|
49
|
+
# @return [void]
|
|
50
|
+
def dataobjects
|
|
51
|
+
list_files("data_objects")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
desc "serializers", "Lists all generated Castkit serializers"
|
|
55
|
+
# Lists all Castkit serializers classes defined in the file system or registered under the Castkit namespace.
|
|
56
|
+
#
|
|
57
|
+
# @return [void]
|
|
58
|
+
def serializers
|
|
59
|
+
list_files("serializers")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
desc "validators", "Lists all generated Castkit validators"
|
|
63
|
+
# Lists all Castkit validator classes defined in the file system or registered under the Castkit namespace.
|
|
64
|
+
#
|
|
65
|
+
# @return [void]
|
|
66
|
+
def validators
|
|
67
|
+
list_files("validators")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
# Prints all native types and their aliases.
|
|
73
|
+
#
|
|
74
|
+
# @param all_types [Hash<Symbol, Object>] all registered types
|
|
75
|
+
# @param default_keys [Array<Symbol>] predefined native type keys
|
|
76
|
+
# @return [void]
|
|
77
|
+
def native_types(all_types, default_keys)
|
|
78
|
+
alias_map = reverse_grouped(Castkit::Configuration::TYPE_ALIASES)
|
|
79
|
+
native = all_types.slice(*default_keys)
|
|
80
|
+
|
|
81
|
+
say "Native Types:", :green
|
|
82
|
+
native.each do |name, type|
|
|
83
|
+
aliases = alias_map[name] || []
|
|
84
|
+
list_type(type.class, [name, *aliases].map(&:to_sym))
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Prints all custom (non-native, non-alias) registered types.
|
|
89
|
+
#
|
|
90
|
+
# @param all_types [Hash<Symbol, Object>]
|
|
91
|
+
# @param default_keys [Array<Symbol>]
|
|
92
|
+
# @return [void]
|
|
93
|
+
def custom_types(all_types, default_keys)
|
|
94
|
+
alias_keys = Castkit::Configuration::TYPE_ALIASES.keys.map(&:to_sym)
|
|
95
|
+
custom = all_types.except(*default_keys).reject { |k, _| alias_keys.include?(k) }
|
|
96
|
+
|
|
97
|
+
say "\nCustom Types:", :green
|
|
98
|
+
return no_custom_types if custom.empty?
|
|
99
|
+
|
|
100
|
+
grouped_custom_types(custom)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Outputs a fallback message if no custom types exist.
|
|
104
|
+
#
|
|
105
|
+
# @return [void]
|
|
106
|
+
def no_custom_types
|
|
107
|
+
say " No registered types, register with " \
|
|
108
|
+
"#{set_color("Castkit.configure { |c| c.register_type(:type, Type) }", :yellow)}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Groups and prints custom types by their class.
|
|
112
|
+
#
|
|
113
|
+
# @param types [Hash<Symbol, Object>]
|
|
114
|
+
# @return [void]
|
|
115
|
+
def grouped_custom_types(types)
|
|
116
|
+
types.group_by { |_, inst| inst.class }.each do |klass, group|
|
|
117
|
+
list_type(klass, group.map(&:first).map(&:to_sym))
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Reverses a hash of alias => type into type => [aliases].
|
|
122
|
+
#
|
|
123
|
+
# @param hash [Hash]
|
|
124
|
+
# @return [Hash{Symbol => Array<Symbol>}]
|
|
125
|
+
def reverse_grouped(hash)
|
|
126
|
+
hash.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(k, v), acc|
|
|
127
|
+
acc[v] << k
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Prints a type or class with all its symbol aliases.
|
|
132
|
+
#
|
|
133
|
+
# @param klass [Class]
|
|
134
|
+
# @param keys [Array<Symbol>]
|
|
135
|
+
# @return [void]
|
|
136
|
+
def list_type(klass, keys)
|
|
137
|
+
types = keys.uniq.sort.map { |k| set_color(":#{k}", :yellow) }.join(", ")
|
|
138
|
+
say " #{(klass.name || "<AnonymousType>").ljust(34)} - #{types}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Lists class references for a component (e.g. validators), distinguishing by source (file or custom).
|
|
142
|
+
#
|
|
143
|
+
# @param component [String] base namespace (e.g. "validators")
|
|
144
|
+
# @return [void]
|
|
145
|
+
def list_files(component)
|
|
146
|
+
path = "lib/castkit/#{component}"
|
|
147
|
+
all_classes, file_classes = component_classes(component, path)
|
|
148
|
+
return say "No registered #{Castkit::Inflector.pascalize(component)} found." if all_classes.empty?
|
|
149
|
+
|
|
150
|
+
max_width = all_classes.map(&:length).max + 5
|
|
151
|
+
say "Castkit #{Castkit::Inflector.pascalize(component)}", :green
|
|
152
|
+
|
|
153
|
+
all_classes.each do |klass|
|
|
154
|
+
tag = file_classes.include?(klass) ? set_color("[Castkit]", :yellow) : set_color("[Custom]", :green)
|
|
155
|
+
say " #{klass.ljust(max_width)} #{tag}"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Gathers all registered and defined constants for a component.
|
|
160
|
+
#
|
|
161
|
+
# @param component [String]
|
|
162
|
+
# @param path [String]
|
|
163
|
+
# @return [Array<[Array<String>, Set<String>]>]
|
|
164
|
+
def component_classes(component, path)
|
|
165
|
+
namespace = Castkit.const_get(Castkit::Inflector.pascalize(component))
|
|
166
|
+
file_classes = file_classes(namespace, path)
|
|
167
|
+
defined_classes = defined_classes(namespace)
|
|
168
|
+
|
|
169
|
+
all_classes = (file_classes + defined_classes).to_a.sort
|
|
170
|
+
[all_classes, file_classes]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Converts file names into class names for a given component.
|
|
174
|
+
#
|
|
175
|
+
# @param namespace [Module]
|
|
176
|
+
# @param path [String]
|
|
177
|
+
# @return [Set<String>]
|
|
178
|
+
def file_classes(namespace, path)
|
|
179
|
+
classes = Dir.glob("#{path}/*.rb")
|
|
180
|
+
.map { |f| File.basename(f, ".rb") }
|
|
181
|
+
.reject { |f| f.to_s == "base" }
|
|
182
|
+
.map { |base| "#{namespace}::#{Castkit::Inflector.pascalize(base)}" }
|
|
183
|
+
|
|
184
|
+
classes.to_set
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Lists actual constants under a namespace, filtering out missing definitions.
|
|
188
|
+
#
|
|
189
|
+
# @param namespace [Module]
|
|
190
|
+
# @return [Set<String>]
|
|
191
|
+
def defined_classes(namespace)
|
|
192
|
+
namespace.constants
|
|
193
|
+
.reject { |const| const.to_s == "Base" }
|
|
194
|
+
.map { |const| "#{namespace}::#{const}" }
|
|
195
|
+
.select { |klass| Object.const_defined?(klass) }
|
|
196
|
+
.to_set
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "generate"
|
|
4
|
+
require_relative "list"
|
|
5
|
+
|
|
6
|
+
module Castkit
|
|
7
|
+
module CLI
|
|
8
|
+
# Main CLI entry point for Castkit.
|
|
9
|
+
#
|
|
10
|
+
# Provides top-level commands for printing the gem version and generating Castkit components.
|
|
11
|
+
#
|
|
12
|
+
# @example Print the version
|
|
13
|
+
# $ castkit version
|
|
14
|
+
#
|
|
15
|
+
# @example Generate a DataObject
|
|
16
|
+
# $ castkit generate dataobject User name:string age:integer
|
|
17
|
+
class Main < Thor
|
|
18
|
+
desc "version", "Prints the version"
|
|
19
|
+
# Outputs the current Castkit version.
|
|
20
|
+
#
|
|
21
|
+
# @return [void]
|
|
22
|
+
def version
|
|
23
|
+
puts Castkit::VERSION
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
desc "generate TYPE NAME", "Generate a Castkit component"
|
|
27
|
+
# Dispatches to the `castkit generate` subcommands.
|
|
28
|
+
#
|
|
29
|
+
# Supports generating components like `type`, `dataobject`, `contract`, etc.
|
|
30
|
+
#
|
|
31
|
+
# @return [void]
|
|
32
|
+
subcommand "generate", Castkit::CLI::Generate
|
|
33
|
+
|
|
34
|
+
desc "list COMPONENT", "List registered Castkit components"
|
|
35
|
+
# Dispatches to the `castkit list` subcommands.
|
|
36
|
+
#
|
|
37
|
+
# Supports listing components like `type`, `dataobject`, `contract`, etc.
|
|
38
|
+
#
|
|
39
|
+
# @return [void]
|
|
40
|
+
subcommand "list", Castkit::CLI::List
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|