hyraft-rule 0.1.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/hyraft/rule/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "hyraft-rule"
7
+ spec.version = Hyraft::Rule::VERSION
8
+ spec.authors = ["Demjhon Silver"]
9
+
10
+
11
+ spec.summary = "Hyraft Rule - Command system for Hyraft applications"
12
+ spec.description = "A standalone command system with migrations for Hyraft applications"
13
+ spec.homepage = "https://github.com/demjhonsilver/hyraft-rule"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.4.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ # Manual file listing without git dependency
22
+ # In hyraft-rule.gemspec - update the files section
23
+ spec.files = [
24
+ "exe/hyraft-rule",
25
+ "exe/hyraft-rule-db",
26
+ "exe/hyr-rule",
27
+ "exe/hyr-rule-db",
28
+ "lib/hyraft/rule.rb",
29
+ "lib/hyraft/rule/version.rb",
30
+ "lib/hyraft/rule/command.rb",
31
+ "lib/hyraft/rule/engine/source_command.rb",
32
+ "lib/hyraft/rule/engine/circuit_command.rb",
33
+ "lib/hyraft/rule/engine/port_command.rb",
34
+ "lib/hyraft/rule/adapter_request/web_adapter_command.rb",
35
+ "lib/hyraft/rule/adapter_request/remove_adapter_command.rb",
36
+ "lib/hyraft/rule/adapter_exhaust/data_gateway_command.rb",
37
+ "lib/hyraft/rule/assemble_command.rb",
38
+ "lib/hyraft/rule/disassemble_command.rb",
39
+ "lib/hyraft/rule/template_command.rb",
40
+ "LICENSE.txt",
41
+ "README.md",
42
+ "CHANGELOG.md",
43
+ "hyraft-rule.gemspec"
44
+ ]
45
+
46
+ spec.bindir = "exe"
47
+ spec.executables = ["hyraft-rule", "hyraft-rule-db", "hyr-rule", "hyr-rule-db"]
48
+ spec.require_paths = ["lib"]
49
+
50
+
51
+
52
+ spec.add_development_dependency "minitest", "~> 5.0"
53
+ spec.add_development_dependency "rake", "~> 13.0"
54
+ end
@@ -0,0 +1,132 @@
1
+ # lib/hyraft/rule/adapter_exhaust/data_gateway_command.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'fileutils'
5
+
6
+ module Hyraft
7
+ module Rule
8
+ module AdapterExhaust
9
+ class DataGatewayCommand
10
+ def self.start(args)
11
+ new(args).execute
12
+ end
13
+
14
+ def initialize(args)
15
+ @gateway_name = args[0]
16
+ @table_name = extract_table_name(@gateway_name)
17
+ @target_dir = args[1] || "."
18
+ end
19
+
20
+ def execute
21
+ return show_usage unless @gateway_name
22
+
23
+ filename = "sequel_#{@table_name}_gateway.rb"
24
+ gateway_dir = File.join(@target_dir, "adapter-exhaust/data-gateway")
25
+ full_path = File.join(gateway_dir, filename)
26
+
27
+ FileUtils.mkdir_p(gateway_dir)
28
+ File.write(full_path, gateway_template)
29
+
30
+ puts "✓ Created gateway: #{full_path}"
31
+ puts "Table name: #{@table_name}"
32
+ puts "Port Adapter: #{port_class_name}"
33
+ puts "Resource: #{source_class_name}"
34
+ end
35
+
36
+ private
37
+
38
+ def extract_table_name(gateway_name)
39
+ gateway_name.sub(/_(gateway)?$/, '')
40
+ end
41
+
42
+ def port_class_name
43
+ "#{@table_name.capitalize}GatewayPort"
44
+ end
45
+
46
+ def gateway_class_name
47
+ "Sequel#{@table_name.capitalize}Gateway"
48
+ end
49
+
50
+ def source_class_name
51
+ @table_name.end_with?('s') ? @table_name[0..-2].capitalize : @table_name.capitalize
52
+ end
53
+
54
+ def source_variable_name
55
+ @table_name.end_with?('s') ? @table_name[0..-2] : @table_name
56
+ end
57
+
58
+ def show_usage
59
+ puts "Usage: hyraft-rule data-gateway <gateway_name> [target_dir]"
60
+ puts ""
61
+ puts "Examples:"
62
+ puts " hyraft-rule data-gateway articles"
63
+ puts " hyraft-rule data-gateway users"
64
+ puts " hyraft-rule data-gateway products"
65
+ puts ""
66
+ puts "This creates: adapter-exhaust/data-gateway/sequel_<name>_gateway.rb"
67
+ end
68
+
69
+ def gateway_template
70
+ port_name = port_class_name
71
+ gateway_name = gateway_class_name
72
+ source_name = source_class_name
73
+ source_var = source_variable_name
74
+
75
+ <<~RUBY
76
+ require_root 'engine/port/#{@table_name}_gateway_port'
77
+ require_root 'engine/source/#{source_var}'
78
+ require_root 'infra/database/sequel_connection'
79
+
80
+ class #{gateway_name} < #{port_name}
81
+ def initialize
82
+ @db = SequelConnection.db
83
+ @#{@table_name} = @db[:#{@table_name}]
84
+ end
85
+
86
+ def save(#{source_var})
87
+ if #{source_var}.id && find(#{source_var}.id)
88
+ @#{@table_name}.where(id: #{source_var}.id.to_i).update(
89
+ # Add update fields here
90
+ updated_at: Time.now
91
+ )
92
+ else
93
+ id = @#{@table_name}.insert(
94
+ # Add insert fields here
95
+ created_at: Time.now,
96
+ updated_at: Time.now
97
+ )
98
+ #{source_var}.id = id.to_s
99
+ end
100
+ #{source_var}
101
+ end
102
+
103
+ def all
104
+ @#{@table_name}.order(Sequel.desc(:created_at)).all.map { |row| map_row_to_#{source_var}(row) }
105
+ end
106
+
107
+ def find(id)
108
+ row = @#{@table_name}.where(id: id.to_i).first
109
+ row && map_row_to_#{source_var}(row)
110
+ end
111
+
112
+ def delete(id)
113
+ @#{@table_name}.where(id: id.to_i).delete
114
+ end
115
+
116
+ private
117
+
118
+ def map_row_to_#{source_var}(row)
119
+ #{source_name}.new(
120
+ id: row[:id].to_s,
121
+ # Add entity attributes here
122
+ created_at: row[:created_at],
123
+ updated_at: row[:updated_at]
124
+ )
125
+ end
126
+ end
127
+ RUBY
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Hyraft
6
+ module Rule
7
+ class RemoveAdapterCommand
8
+ def self.start(args)
9
+ new(args).execute
10
+ end
11
+
12
+ def initialize(args)
13
+ @adapter_input = args[0] # "admin-app/users" or "api-app/products"
14
+ @target_dir = args[1] || "."
15
+ parse_adapter_input
16
+ end
17
+
18
+ def execute
19
+ return show_manifest unless @adapter_name
20
+
21
+ puts "\e[33m🗑️ Removing adapter: '#{@app_folder}/#{@adapter_name}'\e[0m"
22
+
23
+ remove_adapter_only
24
+
25
+ puts "\e[32m✅ Adapter removed: '#{@app_folder}/#{@adapter_name}'\e[0m"
26
+ puts "\e[36mNote: Engine layer and other adapters preserved\e[0m"
27
+ end
28
+
29
+ private
30
+
31
+ def parse_adapter_input
32
+ return unless @adapter_input
33
+
34
+ if @adapter_input.include?('/')
35
+ @app_folder, @adapter_name = @adapter_input.split('/', 2)
36
+ else
37
+ puts "❌ Error: Please specify app folder (e.g., admin-app/users)"
38
+ exit 1
39
+ end
40
+ end
41
+
42
+ def remove_adapter_only
43
+ # Only remove the specific web adapter
44
+ adapter_path = "adapter-intake/#{@app_folder}/request/#{@adapter_name.downcase}_web_adapter.rb"
45
+ delete_file(adapter_path)
46
+ end
47
+
48
+ def delete_file(relative_path)
49
+ full_path = File.join(@target_dir, relative_path)
50
+ if File.exist?(full_path)
51
+ File.delete(full_path)
52
+ puts " ✓ Deleted: #{relative_path}"
53
+ else
54
+ puts " ⚠️ Not found: #{relative_path}"
55
+ end
56
+ end
57
+
58
+ def show_manifest
59
+ puts "Hyraft Remove Adapter"
60
+ puts "Usage: hyraft-rule remove-adapter <folder>/<adapter_name> [target_dir]"
61
+ puts ""
62
+ puts "Examples:"
63
+ puts " hyraft-rule remove-adapter admin-app/users"
64
+ puts " hyraft-rule remove-adapter api-app/products"
65
+ puts " hyraft-rule remove-adapter mobile-app/categories"
66
+ puts ""
67
+ puts "This command only removes the web adapter, preserving:"
68
+ puts " • Engine layer (sources, circuits, ports)"
69
+ puts " • Data gateway"
70
+ puts " • Other app adapters"
71
+ puts ""
72
+ puts "Use 'hyraft-rule disassemble' to remove entire resource"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,211 @@
1
+ # lib/hyraft/rule/adapter_request/web_adapter_command.rb
2
+
3
+ require 'fileutils'
4
+
5
+ module Hyraft
6
+ module Rule
7
+ module AdapterRequest
8
+ class WebAdapterCommand
9
+ def self.start(args)
10
+ input = args[0]
11
+ return show_usage unless input
12
+
13
+ if input.include?('/')
14
+ folder_name, adapter_name = input.split('/', 2)
15
+ else
16
+ folder_name = "web-app"
17
+ adapter_name = input
18
+ end
19
+
20
+ target_dir = args[1] || "."
21
+ adapters_dir = File.join(target_dir, "adapter-intake", folder_name, "request")
22
+ full_path = File.join(adapters_dir, "#{adapter_name.downcase}_web_adapter.rb")
23
+
24
+ FileUtils.mkdir_p(adapters_dir)
25
+ File.write(full_path, web_adapter_template(adapter_name, folder_name))
26
+
27
+ puts "\e[94m✓ Created web adapter: #{full_path}\e[0m"
28
+ puts "\e[38;5;214mApp folder name: #{folder_name}, Adapter: #{adapter_name}\e[0m"
29
+ end
30
+
31
+ private
32
+
33
+ def self.show_usage
34
+ puts "Usage: hyraft-rule web-adapter [folder_name/]<AdapterName> [target_dir]"
35
+ puts ""
36
+ puts "Examples:"
37
+ puts " hyraft-rule web-adapter Articles"
38
+ puts " hyraft-rule web-adapter admin/Users"
39
+ puts " hyraft-rule web-adapter api-app/Products"
40
+ puts " hyraft-rule web-adapter mobile/Categories"
41
+ puts ""
42
+ puts "Default folder: web-app"
43
+ end
44
+
45
+ def self.web_adapter_template(adapter_name, folder_name = "web-app")
46
+ singular_name = adapter_name.downcase.chomp('s')
47
+ adapter_class_name = adapter_name.split('_').map(&:capitalize).join # "articles" → "Articles"
48
+ plural_name = singular_name + 's'
49
+ circuit_name = "#{adapter_class_name}Circuit"
50
+ gateway_name = "Sequel#{adapter_class_name}Gateway"
51
+
52
+ <<~RUBY
53
+ require_root 'engine/circuit/#{plural_name}_circuit'
54
+ require_root 'adapter-exhaust/data-gateway/sequel_#{plural_name}_gateway'
55
+
56
+ class #{adapter_class_name}WebAdapter
57
+ def initialize
58
+ gateway = #{gateway_name}.new
59
+ @#{plural_name} = #{circuit_name}.new(gateway)
60
+ end
61
+
62
+ # GET /#{plural_name}
63
+ def index(request)
64
+ #{plural_name} = @#{plural_name}.list || []
65
+
66
+ {
67
+ status: 200,
68
+ locals: {
69
+ #{plural_name}: #{plural_name}
70
+ },
71
+ display: 'pages/#{plural_name}/index.hyr'
72
+ }
73
+ end
74
+
75
+ # GET /#{plural_name}/:id
76
+ def show(request)
77
+ id = request.params['route_params'].first
78
+ #{singular_name} = @#{plural_name}.find(id)
79
+
80
+ if #{singular_name}
81
+ {
82
+ status: 200,
83
+ locals: {
84
+ #{singular_name}: #{singular_name}
85
+ },
86
+ display: 'pages/#{plural_name}/show.hyr'
87
+ }
88
+ else
89
+ not_found_response(request)
90
+ end
91
+ end
92
+
93
+ # GET /#{plural_name}/new - Show create form
94
+ def new(request)
95
+ {
96
+ status: 200,
97
+ locals: {},
98
+ display: 'pages/#{plural_name}/new.hyr'
99
+ }
100
+ end
101
+
102
+ # POST /#{plural_name} - Create #{singular_name}
103
+ def create(request)
104
+ data = request.params['data'] || {}
105
+
106
+ # TODO: Add validation for required fields
107
+ # Example:
108
+ # if data['title'] && data['content']
109
+ # article = @articles.create(
110
+ # title: data['title'],
111
+ # content: data['content']
112
+ # )
113
+ #
114
+ # {
115
+ # status: 303,
116
+ # headers: { 'Location' => "/articles" },
117
+ # locals: {}
118
+ # }
119
+ # else
120
+ # {
121
+ # status: 422,
122
+ # locals: {
123
+ # error: "Title and content are required"
124
+ # },
125
+ # display: 'pages/articles/new.hyr'
126
+ # }
127
+ # end
128
+
129
+ end
130
+
131
+ # GET /#{plural_name}/:id/edit - Show edit form
132
+ def edit(request)
133
+ id = request.params['route_params'].first
134
+ #{singular_name} = @#{plural_name}.find(id)
135
+
136
+ if #{singular_name}
137
+ {
138
+ status: 200,
139
+ locals: {
140
+ #{singular_name}: #{singular_name}
141
+ },
142
+ display: 'pages/#{plural_name}/edit.hyr'
143
+ }
144
+ else
145
+ not_found_response(request)
146
+ end
147
+ end
148
+
149
+ # PUT /#{plural_name}/:id - Update #{singular_name}
150
+ def update(request)
151
+ id = request.params['route_params'].first
152
+ data = request.params['data'] || {}
153
+
154
+ # TODO: Implement update logic
155
+ # Example:
156
+ # updated_#{singular_name} = @#{plural_name}.update(
157
+ # id: id,
158
+ # title: data['title'],
159
+ # content: data['content']
160
+ # )
161
+
162
+ if updated_#{singular_name}
163
+ {
164
+ status: 303,
165
+ headers: { 'Location' => "/#{plural_name}" },
166
+ locals: {}
167
+ }
168
+ else
169
+ {
170
+ status: 422,
171
+ locals: {
172
+ #{singular_name}: @#{plural_name}.find(id),
173
+ error: "Failed to update #{singular_name}"
174
+ },
175
+ display: 'pages/#{plural_name}/edit.hyr'
176
+ }
177
+ end
178
+ end
179
+
180
+ # DELETE /#{plural_name}/:id - Delete #{singular_name}
181
+ def delete(request)
182
+ id = request.params['route_params'].first
183
+ @#{plural_name}.delete(id)
184
+
185
+ {
186
+ status: 303,
187
+ headers: { 'Location' => "/#{plural_name}" },
188
+ locals: {}
189
+ }
190
+ end
191
+
192
+ private
193
+
194
+ def not_found_response(request)
195
+ {
196
+ status: 404,
197
+ locals: {
198
+ error: "#{singular_name.capitalize} not found",
199
+ back_url: '/#{plural_name}',
200
+ back_text: 'Back to #{adapter_name}'
201
+ },
202
+ display: 'pages/#{plural_name}/404.hyr'
203
+ }
204
+ end
205
+ end
206
+ RUBY
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Hyraft
6
+ module Rule
7
+ class AssembleCommand
8
+ def self.start(args)
9
+ new(args).execute
10
+ end
11
+
12
+ def initialize(args)
13
+ @resource_input = args[0] # Can be "users" or "admin-app/users"
14
+ @target_dir = args[1] || "."
15
+ parse_resource_input
16
+ end
17
+
18
+ def execute
19
+ return show_manifest unless @resource_name
20
+
21
+ puts "\e[35m🔧 Hyraft Assembly: Assembling '#{@resource_name}' resource\e[0m"
22
+ puts "\e[36mApp Folder: #{@app_folder}\e[0m" if @app_folder != "web-app"
23
+
24
+ assemble_engine_layer
25
+ assemble_adapter_layer
26
+
27
+ puts "\e[32m✅ Assembly Complete: '#{@resource_name}' resource assembled\e[0m"
28
+ puts "\e[36mNext steps:\e[0m"
29
+ puts " ⚡ Implement business logic in circuits"
30
+ puts " 🌐 Connect web adapters to router"
31
+ puts " 📊 Create database table manually"
32
+ end
33
+
34
+ private
35
+
36
+ def parse_resource_input
37
+ return unless @resource_input
38
+
39
+ if @resource_input.include?('/')
40
+ @app_folder, @resource_name = @resource_input.split('/', 2)
41
+ else
42
+ @app_folder = "web-app"
43
+ @resource_name = @resource_input
44
+ end
45
+ end
46
+
47
+ def assemble_engine_layer
48
+ puts "\e[34m⚡ Assembling Engine Layer...\e[0m"
49
+
50
+ # Check if engine files already exist
51
+ singular_name = @resource_name.end_with?('s') ? @resource_name[0..-2].capitalize : @resource_name.capitalize
52
+ source_path = File.join(@target_dir, "engine/source/#{singular_name.downcase}.rb")
53
+ circuit_path = File.join(@target_dir, "engine/circuit/#{@resource_name.downcase}_circuit.rb")
54
+ port_path = File.join(@target_dir, "engine/port/#{@resource_name.downcase}_gateway_port.rb")
55
+
56
+ if File.exist?(source_path) || File.exist?(circuit_path) || File.exist?(port_path)
57
+ puts " ⚠️ Engine files already exist - skipping engine layer"
58
+ return
59
+ end
60
+
61
+ # Pass singular name for source, plural for others
62
+ SourceCommand.start([singular_name, @target_dir])
63
+ CircuitCommand.start(["#{@resource_name.capitalize}Circuit", @target_dir])
64
+ PortCommand.start(["#{@resource_name.capitalize}GatewayPort", @target_dir])
65
+ end
66
+
67
+ def assemble_adapter_layer
68
+ puts "\e[34m🔌 Assembling Adapter Layer...\e[0m"
69
+ # Pass folder/resource format to web adapter command
70
+ AdapterRequest::WebAdapterCommand.start(["#{@app_folder}/#{@resource_name}", @target_dir])
71
+ AdapterExhaust::DataGatewayCommand.start([@resource_name, @target_dir])
72
+
73
+ # Generate templates as well
74
+ puts "\e[34m📝 Generating Templates...\e[0m"
75
+ TemplateCommand.start(["#{@app_folder}/#{@resource_name}", @target_dir])
76
+
77
+ end
78
+
79
+ def show_manifest
80
+ puts "Hyraft Assembly Manifest"
81
+ puts "Usage: hyraft-rule assemble [folder/]<resource_name> [target_dir]"
82
+ puts ""
83
+ puts "Examples:"
84
+ puts " hyraft-rule assemble articles # Default: web-app/articles"
85
+ puts " hyraft-rule assemble users # Default: web-app/users"
86
+ puts " hyraft-rule assemble admin-app/users # Creates: admin-app/users"
87
+ puts " hyraft-rule assemble api/products # Creates: api/products"
88
+ puts " hyraft-rule assemble mobile/categories # Creates: mobile/categories"
89
+ puts ""
90
+ puts "Assembly Components:"
91
+ puts " ⚡ Engine Layer → Sources + Circuits + Ports"
92
+ puts " 🔌 Adapter Layer → Web Adapters + Data Gateways"
93
+ puts ""
94
+ puts "Note: Database migrations must be created separately"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyraft
4
+ module Rule
5
+ class Command
6
+ def self.start(argv)
7
+ new(argv).execute
8
+ end
9
+
10
+ def initialize(argv)
11
+ @argv = argv
12
+ @command = argv[0]
13
+ @args = argv[1..-1]
14
+ end
15
+
16
+ def execute
17
+ case @command
18
+ when "migrate"
19
+ MigrationCommand.start(@args)
20
+ when "source"
21
+ SourceCommand.start(@args)
22
+ when "circuit"
23
+ CircuitCommand.start(@args)
24
+ when "port"
25
+ PortCommand.start(@args)
26
+ when "web-adapter"
27
+ AdapterRequest::WebAdapterCommand.start(@args)
28
+ when "data-gateway"
29
+ AdapterExhaust::DataGatewayCommand.start(@args)
30
+ when "assemble"
31
+ AssembleCommand.start(@args)
32
+ when "disassemble"
33
+ DisassembleCommand.start(@args)
34
+ when "template"
35
+ TemplateCommand.start(@args)
36
+ when "remove-adapter", "rm-adapter"
37
+ require 'hyraft/rule/adapter_request/remove_adapter_command'
38
+ RemoveAdapterCommand.start(@args)
39
+ when "version", "-v", "--version"
40
+ puts "Hyraft Rule version #{VERSION}"
41
+ when "help", "-h", "h", "--help", nil
42
+ show_help
43
+ else
44
+ puts "Unknown command: #{@command}"
45
+ show_help
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def show_help
52
+ puts "Hyraft Rule - Command System for Hyraft Applications"
53
+ puts ""
54
+ puts "Commands:"
55
+ puts " source Generate source files"
56
+ puts " circuit Generate circuit files"
57
+ puts " port Generate port files"
58
+ puts " web-adapter Generate web adapter files"
59
+ puts " data-gateway Generate data gateway files"
60
+ puts " template Generate .hyr template files"
61
+ puts " assemble Assemble Engine + Adapters for a resource"
62
+ puts " disassemble Remove assembled Engine + Adapters for a resource"
63
+ puts " remove-adapter Remove specific adapter only (preserves engine)"
64
+ puts " version Show version information"
65
+ puts " help Show this help message"
66
+ puts ""
67
+ puts "Run 'hyr-rule <command>"
68
+ puts "Run 'hyraft-rule <command>"
69
+ puts ""
70
+ puts ""
71
+ puts "Database Commands:"
72
+ puts "Run 'hyr-rule-db <command>"
73
+ puts "Run 'hyraft-rule-db <command>"
74
+ puts ""
75
+ puts ""
76
+ end
77
+ end
78
+ end
79
+ end