castkit 0.2.0 → 0.3.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec_status +118 -119
  3. data/CHANGELOG.md +1 -1
  4. data/README.md +287 -11
  5. data/castkit.gemspec +1 -0
  6. data/lib/castkit/castkit.rb +5 -2
  7. data/lib/castkit/cli/generate.rb +98 -0
  8. data/lib/castkit/cli/list.rb +200 -0
  9. data/lib/castkit/cli/main.rb +43 -0
  10. data/lib/castkit/cli.rb +24 -0
  11. data/lib/castkit/configuration.rb +31 -8
  12. data/lib/castkit/contract/{generic.rb → base.rb} +5 -5
  13. data/lib/castkit/contract/result.rb +2 -2
  14. data/lib/castkit/contract.rb +5 -5
  15. data/lib/castkit/data_object.rb +11 -7
  16. data/lib/castkit/ext/data_object/contract.rb +1 -1
  17. data/lib/castkit/ext/data_object/plugins.rb +86 -0
  18. data/lib/castkit/inflector.rb +1 -1
  19. data/lib/castkit/plugins.rb +82 -0
  20. data/lib/castkit/serializers/base.rb +94 -0
  21. data/lib/castkit/serializers/default_serializer.rb +156 -0
  22. data/lib/castkit/types/{generic.rb → base.rb} +6 -7
  23. data/lib/castkit/types/boolean.rb +14 -10
  24. data/lib/castkit/types/collection.rb +13 -2
  25. data/lib/castkit/types/date.rb +2 -2
  26. data/lib/castkit/types/date_time.rb +2 -2
  27. data/lib/castkit/types/float.rb +5 -5
  28. data/lib/castkit/types/integer.rb +5 -5
  29. data/lib/castkit/types/string.rb +2 -2
  30. data/lib/castkit/types.rb +1 -1
  31. data/lib/castkit/validators/base.rb +59 -0
  32. data/lib/castkit/validators/boolean_validator.rb +39 -0
  33. data/lib/castkit/validators/collection_validator.rb +29 -0
  34. data/lib/castkit/validators/float_validator.rb +31 -0
  35. data/lib/castkit/validators/integer_validator.rb +31 -0
  36. data/lib/castkit/validators/numeric_validator.rb +2 -2
  37. data/lib/castkit/validators/string_validator.rb +3 -4
  38. data/lib/castkit/version.rb +1 -1
  39. data/lib/generators/base.rb +97 -0
  40. data/lib/generators/contract.rb +68 -0
  41. data/lib/generators/data_object.rb +48 -0
  42. data/lib/generators/plugin.rb +25 -0
  43. data/lib/generators/serializer.rb +28 -0
  44. data/lib/generators/templates/contract.rb.tt +24 -0
  45. data/lib/generators/templates/contract_spec.rb.tt +76 -0
  46. data/lib/generators/templates/data_object.rb.tt +15 -0
  47. data/lib/generators/templates/data_object_spec.rb.tt +36 -0
  48. data/lib/generators/templates/plugin.rb.tt +37 -0
  49. data/lib/generators/templates/plugin_spec.rb.tt +18 -0
  50. data/lib/generators/templates/serializer.rb.tt +24 -0
  51. data/lib/generators/templates/serializer_spec.rb.tt +14 -0
  52. data/lib/generators/templates/type.rb.tt +55 -0
  53. data/lib/generators/templates/type_spec.rb.tt +42 -0
  54. data/lib/generators/templates/validator.rb.tt +26 -0
  55. data/lib/generators/templates/validator_spec.rb.tt +23 -0
  56. data/lib/generators/type.rb +29 -0
  57. data/lib/generators/validator.rb +41 -0
  58. metadata +50 -7
  59. data/lib/castkit/default_serializer.rb +0 -154
  60. data/lib/castkit/serializer.rb +0 -92
  61. data/lib/castkit/validators/base_validator.rb +0 -39
data/README.md CHANGED
@@ -21,6 +21,8 @@ Castkit is designed to work seamlessly in service-oriented and API-driven archit
21
21
  - [DataObjects](#dataobjects)
22
22
  - [Contracts](#contracts)
23
23
  - [Advance Usage](#advanced-usage-coming-soon)
24
+ - [Plugins](#plugins)
25
+ - [Castkit CLI](#castkit-cli)
24
26
  - [Testing](#testing)
25
27
  - [Compatibility](#compatibility)
26
28
  - [License](#license)
@@ -58,14 +60,14 @@ Castkit comes with built-in support for primitive types and allows registration
58
60
 
59
61
  ```ruby
60
62
  {
61
- array: Castkit::Types::Collection,
62
- boolean: Castkit::Types::Boolean,
63
- date: Castkit::Types::Date,
64
- datetime: Castkit::Types::DateTime,
65
- float: Castkit::Types::Float,
66
- hash: Castkit::Types::Generic,
67
- integer: Castkit::Types::Integer,
68
- string: Castkit::Types::String
63
+ array: Castkit::Types::Collection,
64
+ boolean: Castkit::Types::Boolean,
65
+ date: Castkit::Types::Date,
66
+ datetime: Castkit::Types::DateTime,
67
+ float: Castkit::Types::Float,
68
+ hash: Castkit::Types::Base,
69
+ integer: Castkit::Types::Integer,
70
+ string: Castkit::Types::String
69
71
  }
70
72
  ```
71
73
 
@@ -374,7 +376,8 @@ from_contract = Castkit::DataObject.from_contract(contract)
374
376
  To override default serialization behavior:
375
377
 
376
378
  ```ruby
377
- class CustomSerializer < Castkit::Serializer
379
+
380
+ class CustomSerializer < Castkit::Serializers::Base
378
381
  def call
379
382
  { payload: object.to_h }
380
383
  end
@@ -427,7 +430,8 @@ end
427
430
  Or subclass directly:
428
431
 
429
432
  ```ruby
430
- class MyContract < Castkit::Contract::Generic
433
+
434
+ class MyContract < Castkit::Contract::Base
431
435
  string :id
432
436
  integer :count, required: false
433
437
  end
@@ -532,7 +536,7 @@ UserContract.validate!(id: "abc", address: { city: "Boston" })
532
536
 
533
537
  Castkit is designed to be modular and extendable. Future guides will cover:
534
538
 
535
- - Custom serializers (`Castkit::Serializer`)
539
+ - Custom serializers (`Castkit::Serializers::Base`)
536
540
  - Integration layers:
537
541
  - `castkit-activerecord` for syncing with ActiveRecord models
538
542
  - `castkit-msgpack` for binary encoding
@@ -543,6 +547,278 @@ Castkit is designed to be modular and extendable. Future guides will cover:
543
547
 
544
548
  ---
545
549
 
550
+ ## Plugins
551
+
552
+ Castkit supports modular extensions through a lightweight plugin system. Plugins can modify or extend the behavior of `Castkit::DataObject` classes, such as adding serialization support, transformation helpers, or framework integrations.
553
+
554
+ Plugins are just Ruby modules and can be registered and activated globally or per-class.
555
+
556
+ ---
557
+
558
+ ### 📦 Activating Plugins
559
+
560
+ Plugins can be activated on any DataObject or at runtime:
561
+
562
+ ```ruby
563
+ module MyPlugin
564
+ def self.setup!(klass)
565
+ # Optional: called after inclusion
566
+ klass.string :plugin_id
567
+ end
568
+
569
+ def plugin_feature
570
+ "Enabled!"
571
+ end
572
+ end
573
+
574
+ Castkit.configure do |config|
575
+ config.register_plugin(:my_plugin, MyPlugin)
576
+ end
577
+
578
+ class MyDto < Castkit::DataObject
579
+ Castkit::Plugins.activate(self, :my_plugin)
580
+ end
581
+ ```
582
+
583
+ This includes the `MyPlugin` module into `MyDto` and calls `MyPlugin.setup!(MyDto)` if defined.
584
+
585
+ ---
586
+
587
+ ### 🧩 Registering Plugins
588
+
589
+ Plugins must be registered before use:
590
+
591
+ ```ruby
592
+ Castkit.configure do |config|
593
+ config.register_plugin(:oj, Castkit::Plugins::Oj)
594
+ end
595
+ ```
596
+
597
+ You can then activate them:
598
+
599
+ ```ruby
600
+ Castkit::Plugins.activate(MyDto, :oj)
601
+ ```
602
+
603
+ ---
604
+
605
+ ### 🧰 Plugin API
606
+
607
+ | Method | Description |
608
+ |------------------------------|-------------|
609
+ | `Castkit::Plugins.register(:name, mod)` | Registers a plugin under a custom name. |
610
+ | `Castkit::Plugins.activate(klass, *names)` | Includes one or more plugins into a class. |
611
+ | `Castkit::Plugins.lookup!(:name)` | Looks up the plugin by name or constant. |
612
+
613
+ ---
614
+
615
+ ### 📁 Plugin Structure
616
+
617
+ Castkit looks for plugins under the `Castkit::Plugins` namespace by default:
618
+
619
+ ```ruby
620
+ module Castkit
621
+ module Plugins
622
+ module Oj
623
+ def self.setup!(klass)
624
+ klass.include SerializationSupport
625
+ end
626
+ end
627
+ end
628
+ end
629
+ ```
630
+
631
+ To activate this:
632
+
633
+ ```ruby
634
+ Castkit::Plugins.activate(MyDto, :oj)
635
+ ```
636
+
637
+ You can also manually register plugins not under this namespace.
638
+
639
+ ---
640
+
641
+ ### ✅ Example Use Case
642
+
643
+ ```ruby
644
+ module Castkit
645
+ module Plugins
646
+ module Timestamps
647
+ def self.setup!(klass)
648
+ klass.datetime :created_at
649
+ klass.datetime :updated_at
650
+ end
651
+ end
652
+ end
653
+ end
654
+
655
+ Castkit::Plugins.activate(UserDto, :timestamps)
656
+ ```
657
+
658
+ This approach allows reusable, modular feature sets across DTOs with clean setup behavior.
659
+
660
+ ---
661
+
662
+ ## Castkit CLI
663
+
664
+ Castkit includes a command-line interface to help scaffold and inspect DTO components with ease.
665
+
666
+ The CLI is structured around two primary commands:
667
+
668
+ - `castkit generate` — scaffolds boilerplate for Castkit components.
669
+ - `castkit list` — introspects and displays registered or defined components.
670
+
671
+ ---
672
+
673
+ ## ✨ Generate Commands
674
+
675
+ The `castkit generate` command provides subcommands for creating files for all core Castkit component types.
676
+
677
+ ### 🧱 DataObject
678
+
679
+ ```bash
680
+ castkit generate dataobject User name:string age:integer
681
+ ```
682
+
683
+ Creates:
684
+
685
+ - `lib/castkit/data_objects/user.rb`
686
+ - `spec/castkit/data_objects/user_spec.rb`
687
+
688
+ ### 📄 Contract
689
+
690
+ ```bash
691
+ castkit generate contract UserInput id:string email:string
692
+ ```
693
+
694
+ Creates:
695
+
696
+ - `lib/castkit/contracts/user_input.rb`
697
+ - `spec/castkit/contracts/user_input_spec.rb`
698
+
699
+ ### 🔌 Plugin
700
+
701
+ ```bash
702
+ castkit generate plugin Oj
703
+ ```
704
+
705
+ Creates:
706
+
707
+ - `lib/castkit/plugins/oj.rb`
708
+ - `spec/castkit/plugins/oj_spec.rb`
709
+
710
+ ### 🧪 Validator
711
+
712
+ ```bash
713
+ castkit generate validator Money
714
+ ```
715
+
716
+ Creates:
717
+
718
+ - `lib/castkit/validators/money.rb`
719
+ - `spec/castkit/validators/money_spec.rb`
720
+
721
+ ### 🧬 Type
722
+
723
+ ```bash
724
+ castkit generate type money
725
+ ```
726
+
727
+ Creates:
728
+
729
+ - `lib/castkit/types/money.rb`
730
+ - `spec/castkit/types/money_spec.rb`
731
+
732
+ ### 📦 Serializer
733
+
734
+ ```bash
735
+ castkit generate serializer Json
736
+ ```
737
+
738
+ Creates:
739
+
740
+ - `lib/castkit/serializers/json.rb`
741
+ - `spec/castkit/serializers/json_spec.rb`
742
+
743
+ You can disable test generation with `--no-spec`.
744
+
745
+ ---
746
+
747
+ ## 📋 List Commands
748
+
749
+ The `castkit list` command provides an interface to view internal Castkit definitions or project-registered components.
750
+
751
+ ### 🧾 List Types
752
+
753
+ ```bash
754
+ castkit list types
755
+ ```
756
+
757
+ Displays a grouped list of:
758
+
759
+ - Native types (defined by Castkit)
760
+ - Custom types (registered via `Castkit.configure`)
761
+
762
+ Example:
763
+
764
+ ```bash
765
+ Native Types:
766
+ Castkit::Types::String - :string, :str, :uuid
767
+
768
+ Custom Types:
769
+ MyApp::Types::Money - :money
770
+ ```
771
+
772
+ ### 🔍 List Validators
773
+
774
+ ```bash
775
+ castkit list validators
776
+ ```
777
+
778
+ Displays all validator classes defined in `lib/castkit/validators` or custom-defined under `Castkit::Validators`.
779
+
780
+ Castkit validators are tagged `[Castkit]`, and others as `[Custom]`.
781
+
782
+ ### 📑 List Contracts
783
+
784
+ ```bash
785
+ castkit list contracts
786
+ ```
787
+
788
+ Lists all contracts in the `Castkit::Contracts` namespace and related files.
789
+
790
+ ### 📦 List DataObjects
791
+
792
+ ```bash
793
+ castkit list dataobjects
794
+ ```
795
+
796
+ Lists all DTOs in the `Castkit::DataObjects` namespace.
797
+
798
+ ### 🧪 List Serializers
799
+
800
+ ```bash
801
+ castkit list serializers
802
+ ```
803
+
804
+ Lists all serializer classes and their source origin.
805
+
806
+ ---
807
+
808
+ ## 🧰 Example Usage
809
+
810
+ ```bash
811
+ castkit generate dataobject Product name:string price:float
812
+ castkit generate contract ProductInput name:string
813
+
814
+ castkit list types
815
+ castkit list validators
816
+ ```
817
+
818
+ The CLI is designed to provide a familiar Rails-like generator experience, tailored for Castkit’s data-first architecture.
819
+
820
+ ---
821
+
546
822
  ## Testing
547
823
 
548
824
  You can test DTOs and Contracts by treating them like plain Ruby objects:
data/castkit.gemspec CHANGED
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.require_paths = ["lib"]
34
34
 
35
35
  # Runtime dependencies
36
+ spec.add_dependency "thor"
36
37
 
37
38
  # Development dependencies
38
39
  spec.add_development_dependency "rspec"
@@ -20,12 +20,15 @@ require_relative "inflector"
20
20
  # @see Castkit::Contract
21
21
  # @see Castkit::DataObject
22
22
  module Castkit
23
- # Namespace used for registering DataObjects generated from contracts.
23
+ # Namespace used for registering generated DataObjects.
24
24
  module DataObjects; end
25
25
 
26
- # Namespace used for registering contracts generated from DataObjects.
26
+ # Namespace used for registering generated contracts.
27
27
  module Contracts; end
28
28
 
29
+ # Namespace used for registering generated plugins.
30
+ module Plugins; end
31
+
29
32
  class << self
30
33
  # Yields the global configuration object for customization.
31
34
  #
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "../../generators/contract"
5
+ require_relative "../../generators/data_object"
6
+ require_relative "../../generators/plugin"
7
+ require_relative "../../generators/serializer"
8
+ require_relative "../../generators/type"
9
+ require_relative "../../generators/validator"
10
+
11
+ module Castkit
12
+ module CLI
13
+ # Thor CLI class for generating Castkit components.
14
+ #
15
+ # Provides `castkit generate` commands for each major Castkit component, including types,
16
+ # data objects, contracts, validators, serializers, and plugins.
17
+ #
18
+ # All generators support the `--no-spec` flag to skip spec file creation.
19
+ class Generate < Thor
20
+ desc "contract NAME", "Generates a new Castkit contract"
21
+ method_option :spec, type: :boolean, default: true
22
+ # Generates a new contract class.
23
+ #
24
+ # @param name [String] the class name for the contract
25
+ # @param fields [Array<String>] optional attribute definitions
26
+ # @return [void]
27
+ def contract(name, *fields)
28
+ args = [Castkit::Inflector.pascalize(name), fields]
29
+ args << "--no-spec" unless options[:spec]
30
+ Castkit::Generators::Contract.start(args)
31
+ end
32
+
33
+ desc "dataobject NAME", "Generates a new Castkit DataObject"
34
+ method_option :spec, type: :boolean, default: true
35
+ # Generates a new DataObject class.
36
+ #
37
+ # @param name [String] the class name for the data object
38
+ # @param fields [Array<String>] optional attribute definitions
39
+ # @return [void]
40
+ def dataobject(name, *fields)
41
+ args = [Castkit::Inflector.pascalize(name), fields]
42
+ args << "--no-spec" unless options[:spec]
43
+ Castkit::Generators::DataObject.start(args)
44
+ end
45
+
46
+ desc "plugin NAME", "Generates a new Castkit plugin"
47
+ method_option :spec, type: :boolean, default: true
48
+ # Generates a new plugin module.
49
+ #
50
+ # @param name [String] the module name for the plugin
51
+ # @param fields [Array<String>] optional stub fields
52
+ # @return [void]
53
+ def plugin(name, *fields)
54
+ args = [Castkit::Inflector.pascalize(name), fields]
55
+ args << "--no-spec" unless options[:spec]
56
+ Castkit::Generators::Plugin.start(args)
57
+ end
58
+
59
+ desc "serializer NAME", "Generates a new Castkit serializer"
60
+ method_option :spec, type: :boolean, default: true
61
+ # Generates a new custom serializer class.
62
+ #
63
+ # @param name [String] the class name for the serializer
64
+ # @param fields [Array<String>] optional stub fields
65
+ # @return [void]
66
+ def serializer(name, *fields)
67
+ args = [Castkit::Inflector.pascalize(name), fields]
68
+ args << "--no-spec" unless options[:spec]
69
+ Castkit::Generators::Serializer.start(args)
70
+ end
71
+
72
+ desc "type NAME", "Generates a new Castkit type"
73
+ method_option :spec, type: :boolean, default: true
74
+ # Generates a new custom type.
75
+ #
76
+ # @param name [String] the class name for the type
77
+ # @return [void]
78
+ def type(name)
79
+ args = [Castkit::Inflector.pascalize(name)]
80
+ args << "--no-spec" unless options[:spec]
81
+ Castkit::Generators::Type.start(args)
82
+ end
83
+
84
+ desc "validator NAME", "Generates a new Castkit validator"
85
+ method_option :spec, type: :boolean, default: true
86
+ # Generates a new validator class.
87
+ #
88
+ # @param name [String] the class name for the validator
89
+ # @param fields [Array<String>] optional stub fields
90
+ # @return [void]
91
+ def validator(name, *fields)
92
+ args = [Castkit::Inflector.pascalize(name), fields]
93
+ args << "--no-spec" unless options[:spec]
94
+ Castkit::Generators::Validator.start(args)
95
+ end
96
+ end
97
+ end
98
+ 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