rivulet-rb 0.1.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.
- checksums.yaml +7 -0
- data/bin/rivulet +4 -0
- data/lib/rivulet/application.rb +90 -0
- data/lib/rivulet/cli/console.rb +15 -0
- data/lib/rivulet/cli/db/migrate.rb +17 -0
- data/lib/rivulet/cli/generate/handler/operation.rb +75 -0
- data/lib/rivulet/cli/generate/handler/step.rb +91 -0
- data/lib/rivulet/cli/generate/handler.rb +135 -0
- data/lib/rivulet/cli/generate/migration.rb +31 -0
- data/lib/rivulet/cli/generate/operation.rb +111 -0
- data/lib/rivulet/cli/generate/resource.rb +25 -0
- data/lib/rivulet/cli/generate/service/operation.rb +118 -0
- data/lib/rivulet/cli/generate/service/projection.rb +89 -0
- data/lib/rivulet/cli/generate/service/step.rb +91 -0
- data/lib/rivulet/cli/generate/service.rb +143 -0
- data/lib/rivulet/cli/new.rb +191 -0
- data/lib/rivulet/cli/routes.rb +15 -0
- data/lib/rivulet/cli.rb +43 -0
- data/lib/rivulet/container.rb +28 -0
- data/lib/rivulet/operation.rb +21 -0
- data/lib/rivulet/operations/dispatch_request.rb +21 -0
- data/lib/rivulet/operations/migrate.rb +23 -0
- data/lib/rivulet/operations/print_routes.rb +17 -0
- data/lib/rivulet/operations/run_console.rb +25 -0
- data/lib/rivulet/operations/startup.rb +23 -0
- data/lib/rivulet/projection.rb +6 -0
- data/lib/rivulet/request.rb +18 -0
- data/lib/rivulet/response.rb +12 -0
- data/lib/rivulet/routing/mapper.rb +78 -0
- data/lib/rivulet/routing/route.rb +14 -0
- data/lib/rivulet/step.rb +22 -0
- data/lib/rivulet/steps/build_config.rb +26 -0
- data/lib/rivulet/steps/build_context.rb +76 -0
- data/lib/rivulet/steps/compile_response.rb +113 -0
- data/lib/rivulet/steps/dispatch.rb +17 -0
- data/lib/rivulet/steps/load_app.rb +20 -0
- data/lib/rivulet/steps/load_db.rb +24 -0
- data/lib/rivulet/steps/load_routes.rb +28 -0
- data/lib/rivulet/steps/load_settings.rb +42 -0
- data/lib/rivulet/steps/print_routes.rb +123 -0
- data/lib/rivulet/steps/run_console.rb +26 -0
- data/lib/rivulet/steps/run_migrations.rb +50 -0
- data/lib/rivulet/steps/validate_response.rb +42 -0
- data/lib/rivulet/telemetry/node.rb +8 -0
- data/lib/rivulet/telemetry/sequel_extension.rb +19 -0
- data/lib/rivulet/telemetry/timing_wrapper.rb +12 -0
- data/lib/rivulet/telemetry.rb +62 -0
- data/lib/rivulet/version.rb +3 -0
- data/lib/rivulet.rb +66 -0
- metadata +342 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5ae318924632d21dfc60983838454a230011b5c6a762446c52b927870074980c
|
|
4
|
+
data.tar.gz: eb65be1dcabda72c0ce3d590e6952d8929f1b945f23344de7fcc2403188a94eb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0c6433c7c4814dd1d763d1d16d01ce903a9b84b308451766e1f6f64625c8195235526faf95c78fa43a2f1bfe9d4f4c9d9dfdbf69454c6ca0a92d6ec3f8a3c05a
|
|
7
|
+
data.tar.gz: fdb3fe00dd3280bce1844cd9ef71ae24c3c30f398d4aaea45cfe755442c6c2a4413c7d183f04938c5770f1b9c5910a61ff549377b304b63851c66c40f12e6b11
|
data/bin/rivulet
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module Rivulet
|
|
2
|
+
class Application
|
|
3
|
+
include Dry::Configurable
|
|
4
|
+
include Dry::Monads[:result]
|
|
5
|
+
include Dry::AutoInject(Rivulet::Container)[
|
|
6
|
+
dispatch_request: 'operations.dispatch_request',
|
|
7
|
+
startup_operation: 'operations.startup',
|
|
8
|
+
migrate_operation: 'operations.migrate',
|
|
9
|
+
run_console_operation: 'operations.run_console',
|
|
10
|
+
print_routes_operation: 'operations.print_routes'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
attr_accessor :db
|
|
14
|
+
|
|
15
|
+
def call(env)
|
|
16
|
+
result = with_telemetry do
|
|
17
|
+
dispatch_request.(resource: self, env: env)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
return result.success[:response] if result.success?
|
|
21
|
+
|
|
22
|
+
case result
|
|
23
|
+
in Failure[:route_not_found]
|
|
24
|
+
[404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
|
|
25
|
+
in Failure[:wrong_response_type, message]
|
|
26
|
+
logger.error(message)
|
|
27
|
+
[500, {}, []]
|
|
28
|
+
in Failure[:conflicting_response, message]
|
|
29
|
+
logger.error(message)
|
|
30
|
+
[500, {}, []]
|
|
31
|
+
in Failure[:file_not_found, message]
|
|
32
|
+
logger.error(message)
|
|
33
|
+
[500, {}, []]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def routes
|
|
38
|
+
@routes ||= Routing::Mapper.new([])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def startup
|
|
42
|
+
result = startup_operation.(resource: self)
|
|
43
|
+
if result.failure?
|
|
44
|
+
warn "Startup failed: #{result.failure}"
|
|
45
|
+
exit 1
|
|
46
|
+
end
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def migrate!
|
|
51
|
+
result = migrate_operation.(resource: self)
|
|
52
|
+
if result.failure?
|
|
53
|
+
warn "Migration failed: #{result.failure}"
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def run_console
|
|
59
|
+
result = run_console_operation.(resource: self)
|
|
60
|
+
if result.failure?
|
|
61
|
+
warn "Cannot start console: #{result.failure}"
|
|
62
|
+
exit 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def print_routes
|
|
67
|
+
result = print_routes_operation.(resource: self)
|
|
68
|
+
if result.failure?
|
|
69
|
+
warn "Cannot print routes: #{result.failure}"
|
|
70
|
+
exit 1
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def with_telemetry
|
|
77
|
+
t = Telemetry.new
|
|
78
|
+
Fiber[:rivulet_telemetry] = t
|
|
79
|
+
|
|
80
|
+
result = yield
|
|
81
|
+
|
|
82
|
+
logger.info(
|
|
83
|
+
"Completed total_ms=#{t.total_ms} db_ms=#{t.db_ms} flow:\n#{t.print_flow}"
|
|
84
|
+
)
|
|
85
|
+
result
|
|
86
|
+
ensure
|
|
87
|
+
Fiber[:rivulet_telemetry] = nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Rivulet
|
|
4
|
+
module CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Generate
|
|
7
|
+
class Handler
|
|
8
|
+
class Operation < Dry::CLI::Command
|
|
9
|
+
desc "Generate an operation"
|
|
10
|
+
argument :name, required: true, desc: "Operation name in handler.operation format (e.g. users.create)"
|
|
11
|
+
|
|
12
|
+
def call(name:, **)
|
|
13
|
+
handler_name, operation_name = name.split('.')
|
|
14
|
+
handler_dir = underscore(handler_name)
|
|
15
|
+
operation_dir = underscore(operation_name)
|
|
16
|
+
handler_module = camelize(handler_dir)
|
|
17
|
+
operation_module = camelize(operation_dir)
|
|
18
|
+
base = "app/handlers/#{handler_dir}"
|
|
19
|
+
|
|
20
|
+
write "#{base}/operations/#{operation_dir}.rb", operation_template(handler_module, operation_module)
|
|
21
|
+
register_operation(base, handler_dir, handler_module, operation_dir, operation_module)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def underscore(str)
|
|
27
|
+
str
|
|
28
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
29
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
30
|
+
.downcase
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def camelize(str)
|
|
34
|
+
str.split('_').map(&:capitalize).join
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def write(path, content)
|
|
38
|
+
File.write(path, content)
|
|
39
|
+
puts " create #{path}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def operation_template(handler_module, operation_module)
|
|
43
|
+
<<~RUBY
|
|
44
|
+
module Handlers
|
|
45
|
+
module #{handler_module}
|
|
46
|
+
module Operations
|
|
47
|
+
class #{operation_module} < Rivulet::Operation
|
|
48
|
+
def call(input = {})
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
RUBY
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def register_operation(base, handler_dir, handler_module, operation_dir, operation_module)
|
|
58
|
+
container_file = "#{base}/container.rb"
|
|
59
|
+
op_registration = " register('#{operation_dir}') { Handlers::#{handler_module}::Operations::#{operation_module}.new }\n"
|
|
60
|
+
|
|
61
|
+
if File.exist?(container_file)
|
|
62
|
+
content = File.read(container_file)
|
|
63
|
+
content.sub!(/( namespace\('operations'\) do\n)/, "\\1#{op_registration}")
|
|
64
|
+
File.write(container_file, content)
|
|
65
|
+
puts " update #{container_file}"
|
|
66
|
+
else
|
|
67
|
+
write container_file, container_template(handler_module, operation_dir, operation_module)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Rivulet
|
|
4
|
+
module CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Generate
|
|
7
|
+
class Handler
|
|
8
|
+
class Step < Dry::CLI::Command
|
|
9
|
+
desc "Generate a step"
|
|
10
|
+
argument :name, required: true, desc: "Step name in handler.step format (e.g. users.create)"
|
|
11
|
+
|
|
12
|
+
def call(name:, **)
|
|
13
|
+
handler_name, step_name = name.split('.')
|
|
14
|
+
handler_dir = underscore(handler_name)
|
|
15
|
+
step_dir = underscore(step_name)
|
|
16
|
+
handler_module = camelize(handler_dir)
|
|
17
|
+
step_class = camelize(step_dir)
|
|
18
|
+
base = "app/handlers/#{handler_dir}"
|
|
19
|
+
|
|
20
|
+
write "#{base}/steps/#{step_dir}.rb", step_template(handler_module, step_class)
|
|
21
|
+
register_step(base, handler_module, step_dir, step_class)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def underscore(str)
|
|
27
|
+
str
|
|
28
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
29
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
30
|
+
.downcase
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def camelize(str)
|
|
34
|
+
str.split('_').map(&:capitalize).join
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def write(path, content)
|
|
38
|
+
File.write(path, content)
|
|
39
|
+
puts " create #{path}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def step_template(handler_module, step_class)
|
|
43
|
+
<<~RUBY
|
|
44
|
+
module Handlers
|
|
45
|
+
module #{handler_module}
|
|
46
|
+
module Steps
|
|
47
|
+
class #{step_class} < Rivulet::Step
|
|
48
|
+
def call(input)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
RUBY
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def register_step(base, handler_module, step_dir, step_module)
|
|
58
|
+
container_file = "#{base}/container.rb"
|
|
59
|
+
step_registration = " register('#{step_dir}') { Handlers::#{handler_module}::Steps::#{step_module}.new }\n"
|
|
60
|
+
|
|
61
|
+
if File.exist?(container_file)
|
|
62
|
+
content = File.read(container_file)
|
|
63
|
+
content.sub!(/( namespace\('steps'\) do\n)/, "\\1#{step_registration}")
|
|
64
|
+
File.write(container_file, content)
|
|
65
|
+
puts " update #{container_file}"
|
|
66
|
+
else
|
|
67
|
+
write container_file, container_template(handler_module, step_dir, step_module)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def container_template(handler_module, step_name, step_class)
|
|
72
|
+
<<~RUBY
|
|
73
|
+
module Handlers
|
|
74
|
+
module #{handler_module}
|
|
75
|
+
class Container
|
|
76
|
+
extend Dry::Core::Container::Mixin
|
|
77
|
+
|
|
78
|
+
namespace('steps') do
|
|
79
|
+
register('#{step_name}') { Handlers::#{service_module}::Steps::#{step_class}.new }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
RUBY
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Rivulet
|
|
4
|
+
module CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Generate
|
|
7
|
+
class Handler < Dry::CLI::Command
|
|
8
|
+
desc "Generate a handler"
|
|
9
|
+
argument :name, required: true, desc: "Handler name (e.g. Users or users)"
|
|
10
|
+
|
|
11
|
+
option :create, type: :boolean, aliases: ['-c'], desc: 'Add "create" operation'
|
|
12
|
+
option :read, type: :boolean, aliases: ['-r'], desc: 'Add "show" operation'
|
|
13
|
+
option :update, type: :boolean, aliases: ['-u'], desc: 'Add "update" operation'
|
|
14
|
+
option :delete, type: :boolean, aliases: ['-d'], desc: 'Add "delete" operation'
|
|
15
|
+
option :list, type: :boolean, aliases: ['-l'], desc: 'Add "index" operation'
|
|
16
|
+
|
|
17
|
+
SUBDIRS = %w[operations steps].freeze
|
|
18
|
+
|
|
19
|
+
def call(name:, **options)
|
|
20
|
+
dir_name = underscore(name)
|
|
21
|
+
module_name = camelize(dir_name)
|
|
22
|
+
base = "app/handlers/#{dir_name}"
|
|
23
|
+
|
|
24
|
+
SUBDIRS.each { |d| create_dir "#{base}/#{d}" }
|
|
25
|
+
write "#{base}/handler.rb", handler_template(module_name)
|
|
26
|
+
create_container(base, module_name) unless File.exist?("#{base}/container.rb")
|
|
27
|
+
register_handler(dir_name, module_name)
|
|
28
|
+
|
|
29
|
+
if options[:create]
|
|
30
|
+
Operation.new.call(name: "#{name}.create")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if options[:read]
|
|
34
|
+
Operation.new.call(name: "#{name}.show")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if options[:update]
|
|
38
|
+
Operation.new.call(name: "#{name}.update")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if options[:delete]
|
|
42
|
+
Operation.new.call(name: "#{name}.delete")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if options[:list]
|
|
46
|
+
Operation.new.call(name: "#{name}.index")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def underscore(str)
|
|
53
|
+
str
|
|
54
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
55
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
56
|
+
.downcase
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def camelize(str)
|
|
60
|
+
str.split('_').map(&:capitalize).join
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def create_container(base, module_name)
|
|
64
|
+
write "#{base}/container.rb", container_template(module_name)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def create_dir(path)
|
|
68
|
+
FileUtils.mkdir_p(path)
|
|
69
|
+
puts " create #{path}/"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def write(path, content)
|
|
73
|
+
File.write(path, content)
|
|
74
|
+
puts " create #{path}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def handler_template(module_name)
|
|
78
|
+
<<~RUBY
|
|
79
|
+
module Handlers
|
|
80
|
+
module #{module_name}
|
|
81
|
+
class Handler
|
|
82
|
+
NAMESPACE = 'operations'
|
|
83
|
+
|
|
84
|
+
def method_missing(name, input = {}, options = {}, &block)
|
|
85
|
+
key = [NAMESPACE, name].join('.')
|
|
86
|
+
super unless Container.key?(key)
|
|
87
|
+
|
|
88
|
+
Container[key].call(input, **options, &block)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def respond_to_missing?(name, include_private = false)
|
|
92
|
+
key = [NAMESPACE, name].join('.')
|
|
93
|
+
Container.key?(key) || super
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
RUBY
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def container_template(module_name)
|
|
102
|
+
<<~RUBY
|
|
103
|
+
module Handlers
|
|
104
|
+
module #{module_name}
|
|
105
|
+
class Container
|
|
106
|
+
extend Dry::Core::Container::Mixin
|
|
107
|
+
import Handlers::Shared::Namespace
|
|
108
|
+
|
|
109
|
+
namespace('operations') do
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
namespace('steps') do
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
RUBY
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def register_handler(dir_name, module_name)
|
|
121
|
+
handlers_file = 'app/handlers.rb'
|
|
122
|
+
return unless File.exist?(handlers_file)
|
|
123
|
+
|
|
124
|
+
content = File.read(handlers_file)
|
|
125
|
+
registration = " register('#{dir_name}') { Handlers::#{module_name}::Handler.new }\n"
|
|
126
|
+
|
|
127
|
+
updated = content.sub(/^end\s*\z/, "#{registration}end\n")
|
|
128
|
+
File.write(handlers_file, updated)
|
|
129
|
+
puts " update #{handlers_file}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Rivulet
|
|
4
|
+
module CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Generate
|
|
7
|
+
class Migration < Dry::CLI::Command
|
|
8
|
+
desc "Generate a migration"
|
|
9
|
+
argument :name, required: true, desc: "Migration name (e.g. create_users)"
|
|
10
|
+
|
|
11
|
+
def call(name:, **)
|
|
12
|
+
FileUtils.mkdir_p('db/migrations')
|
|
13
|
+
filename = "#{Time.now.strftime('%Y%m%d%H%M%S')}_#{underscore(name)}.sql"
|
|
14
|
+
path = File.join('db/migrations', filename)
|
|
15
|
+
File.write(path, "")
|
|
16
|
+
puts " create #{path}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def underscore(str)
|
|
22
|
+
str
|
|
23
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
24
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
25
|
+
.downcase
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Rivulet
|
|
4
|
+
module CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Generate
|
|
7
|
+
class Operation < Dry::CLI::Command
|
|
8
|
+
desc "Generate an operation"
|
|
9
|
+
argument :name, required: true, desc: "Operation name in service.operation format (e.g. users.create)"
|
|
10
|
+
|
|
11
|
+
def call(name:, **)
|
|
12
|
+
service_name, operation_name = name.split('.')
|
|
13
|
+
service_dir = underscore(service_name)
|
|
14
|
+
operation_dir = underscore(operation_name)
|
|
15
|
+
service_module = camelize(service_dir)
|
|
16
|
+
operation_module = camelize(operation_dir)
|
|
17
|
+
base = "app/services/#{service_dir}"
|
|
18
|
+
|
|
19
|
+
write "#{base}/operations/#{operation_dir}.rb", operation_template(service_module, operation_module)
|
|
20
|
+
write "#{base}/contracts/#{operation_dir}.rb", contract_template(service_module, operation_module)
|
|
21
|
+
register_operation(base, service_dir, service_module, operation_dir, operation_module)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def underscore(str)
|
|
27
|
+
str
|
|
28
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
29
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
30
|
+
.downcase
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def camelize(str)
|
|
34
|
+
str.split('_').map(&:capitalize).join
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def write(path, content)
|
|
38
|
+
File.write(path, content)
|
|
39
|
+
puts " create #{path}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def operation_template(service_module, operation_module)
|
|
43
|
+
<<~RUBY
|
|
44
|
+
module Services
|
|
45
|
+
module #{service_module}
|
|
46
|
+
module Operations
|
|
47
|
+
class #{operation_module} < Rivulet::Operation
|
|
48
|
+
def call(input = {})
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
RUBY
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def contract_template(service_module, operation_module)
|
|
58
|
+
<<~RUBY
|
|
59
|
+
module Services
|
|
60
|
+
module #{service_module}
|
|
61
|
+
module Contracts
|
|
62
|
+
class #{operation_module}
|
|
63
|
+
params do
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
RUBY
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def register_operation(base, service_dir, service_module, operation_dir, operation_module)
|
|
73
|
+
container_file = "#{base}/container.rb"
|
|
74
|
+
op_registration = " register('#{operation_dir}') { Services::#{service_module}::Operations::#{operation_module}.new }\n"
|
|
75
|
+
contract_registration = " register('#{operation_dir}') { Services::#{service_module}::Contracts::#{operation_module}.new }\n"
|
|
76
|
+
|
|
77
|
+
if File.exist?(container_file)
|
|
78
|
+
content = File.read(container_file)
|
|
79
|
+
content.sub!(/( namespace\('operations'\) do\n)/, "\\1#{op_registration}")
|
|
80
|
+
content.sub!(/( namespace\('contracts'\) do\n)/, "\\1#{contract_registration}")
|
|
81
|
+
File.write(container_file, content)
|
|
82
|
+
puts " update #{container_file}"
|
|
83
|
+
else
|
|
84
|
+
write container_file, container_template(service_module, operation_dir, operation_module)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def container_template(service_module, operation_dir, operation_module)
|
|
89
|
+
<<~RUBY
|
|
90
|
+
module Services
|
|
91
|
+
module #{service_module}
|
|
92
|
+
class Container
|
|
93
|
+
extend Dry::Core::Container::Mixin
|
|
94
|
+
|
|
95
|
+
namespace('operations') do
|
|
96
|
+
register('#{operation_dir}') { Services::#{service_module}::Operations::#{operation_module}.new }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
namespace('contracts') do
|
|
100
|
+
register('#{operation_dir}') { Services::#{service_module}::Contracts::#{operation_module}.new }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
RUBY
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Rivulet
|
|
4
|
+
module CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Generate
|
|
7
|
+
class Resource < Dry::CLI::Command
|
|
8
|
+
desc "Generate a resource (handler + service)"
|
|
9
|
+
argument :name, required: true, desc: "Resource name (e.g. Users or users)"
|
|
10
|
+
|
|
11
|
+
option :create, type: :boolean, aliases: ['-c'], desc: 'Add operations to create resource'
|
|
12
|
+
option :read, type: :boolean, aliases: ['-r'], desc: 'Add operations to read resource'
|
|
13
|
+
option :update, type: :boolean, aliases: ['-u'], desc: 'Add operations to update resource'
|
|
14
|
+
option :delete, type: :boolean, aliases: ['-d'], desc: 'Add operations to delete resource'
|
|
15
|
+
option :list, type: :boolean, aliases: ['-l'], desc: 'Add operations to list resource'
|
|
16
|
+
|
|
17
|
+
def call(name:, **options)
|
|
18
|
+
Handler.new.call(name: name, **options)
|
|
19
|
+
Service.new.call(name: name, **options)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|