-
-
Notifications
You must be signed in to change notification settings - Fork 38
Add dry-operation generators #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
d3e5e78
888c388
24d3d6b
bc2670a
1c37df3
a3f6a03
d93dfc8
649bdcb
f74519f
0f9f814
663abc6
3b72feb
0f81a5c
a5bd2f3
eb391ca
1023225
6a3c32a
8dc3de4
8f90b33
8e62aa3
c2c54c9
9933c8a
9fc5e31
ad0431d
0095497
29b02b9
e60248d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# frozen_string_literal: true | ||
|
||
require "dry/inflector" | ||
require "dry/files" | ||
require "shellwords" | ||
require_relative "../../../naming" | ||
require_relative "../../../errors" | ||
|
||
module Hanami | ||
module CLI | ||
module Commands | ||
module App | ||
module Generate | ||
# @since x.x.x | ||
# @api private | ||
class Operation < App::Command | ||
argument :name, required: true, desc: "Operation name" | ||
option :slice, required: false, desc: "Slice name" | ||
|
||
example [ | ||
%(books.add (MyApp::Books::Add)), | ||
%(books.add --slice=admin (Admin::Books::Add)), | ||
] | ||
attr_reader :generator | ||
private :generator | ||
|
||
# @since x.x.x | ||
# @api private | ||
def initialize( | ||
fs:, inflector:, | ||
generator: Generators::App::Operation.new(fs: fs, inflector: inflector), | ||
**opts | ||
) | ||
super(fs: fs, inflector: inflector, **opts) | ||
@generator = generator | ||
end | ||
|
||
# @since x.x.x | ||
# @api private | ||
def call(name:, slice: nil, **) | ||
slice = inflector.underscore(Shellwords.shellescape(slice)) if slice | ||
|
||
generator.call(app.namespace, name, slice) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,77 @@ | ||||||
# frozen_string_literal: true | ||||||
|
||||||
require "erb" | ||||||
require "dry/files" | ||||||
require_relative "../../errors" | ||||||
|
||||||
module Hanami | ||||||
module CLI | ||||||
module Generators | ||||||
module App | ||||||
# @since x.x.x | ||||||
# @api private | ||||||
class Operation | ||||||
# @since x.x.x | ||||||
# @api private | ||||||
def initialize(fs:, inflector:) | ||||||
@fs = fs | ||||||
@inflector = inflector | ||||||
end | ||||||
|
||||||
# @since x.x.x | ||||||
# @api private | ||||||
def call(app, key, slice) | ||||||
context = OperationContext.new(inflector, app, slice, key) | ||||||
|
||||||
if slice | ||||||
generate_for_slice(context, slice) | ||||||
else | ||||||
generate_for_app(context) | ||||||
end | ||||||
end | ||||||
|
||||||
private | ||||||
|
||||||
attr_reader :fs | ||||||
|
||||||
attr_reader :inflector | ||||||
|
||||||
def generate_for_slice(context, slice) | ||||||
slice_directory = fs.join("slices", slice) | ||||||
raise MissingSliceError.new(slice) unless fs.directory?(slice_directory) | ||||||
|
||||||
if context.namespaces.any? | ||||||
fs.mkdir(directory = fs.join(slice_directory, context.namespaces)) | ||||||
fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_slice_operation.erb", context)) | ||||||
else | ||||||
fs.mkdir(directory = fs.join(slice_directory)) | ||||||
fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_slice_operation.erb", context)) | ||||||
fs.recommend("Add a namespace to operation names, so they go into a folder within #{directory}/.") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about this for wording?
Suggested change
That first sentence is there to explain why we're offering the recommendation in the first place. The second sentence is there to give a bit more guidance as to what the "recommended" args might look like. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sweet, switched it to this. I added the indentation too so it's more noticeable. |
||||||
end | ||||||
end | ||||||
|
||||||
def generate_for_app(context) | ||||||
if context.namespaces.any? | ||||||
fs.mkdir(directory = fs.join("app", context.namespaces)) | ||||||
fs.write(fs.join(directory, "#{context.name}.rb"), t("nested_app_operation.erb", context)) | ||||||
else | ||||||
fs.mkdir(directory = fs.join("app")) | ||||||
fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_app_operation.erb", context)) | ||||||
fs.recommend("Add a namespace to operation names, so they go into a folder within #{directory}/.") | ||||||
end | ||||||
end | ||||||
|
||||||
def template(path, context) | ||||||
require "erb" | ||||||
|
||||||
ERB.new( | ||||||
File.read(__dir__ + "/operation/#{path}") | ||||||
).result(context.ctx) | ||||||
end | ||||||
|
||||||
alias_method :t, :template | ||||||
end | ||||||
end | ||||||
end | ||||||
end | ||||||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# frozen_string_literal: true | ||
|
||
module <%= camelized_app_name %> | ||
<%= module_namespace_declaration %> | ||
<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation | ||
<%= module_namespace_offset %> def call | ||
<%= module_namespace_offset %> end | ||
<%= module_namespace_offset %>end | ||
<%= module_namespace_end %> | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# frozen_string_literal: true | ||
|
||
module <%= camelized_slice_name %> | ||
<%= module_namespace_declaration %> | ||
<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation | ||
<%= module_namespace_offset %> def call | ||
<%= module_namespace_offset %> end | ||
<%= module_namespace_offset %>end | ||
<%= module_namespace_end %> | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# frozen_string_literal: true | ||
|
||
module <%= camelized_app_name %> | ||
<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Operation | ||
<%= module_namespace_offset %> def call | ||
<%= module_namespace_offset %> end | ||
<%= module_namespace_offset %>end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# frozen_string_literal: true | ||
|
||
module <%= camelized_slice_name %> | ||
<%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Operation | ||
<%= module_namespace_offset %> def call | ||
<%= module_namespace_offset %> end | ||
<%= module_namespace_offset %>end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative "slice_context" | ||
require "dry/files/path" | ||
|
||
module Hanami | ||
module CLI | ||
module Generators | ||
# @since x.x.x | ||
# @api private | ||
module App | ||
# @since x.x.x | ||
# @api private | ||
class OperationContext < SliceContext | ||
# TODO: move these constants somewhere that will let us reuse them | ||
KEY_SEPARATOR = %r{\.|/} | ||
private_constant :KEY_SEPARATOR | ||
|
||
NAMESPACE_SEPARATOR = "::" | ||
private_constant :NAMESPACE_SEPARATOR | ||
|
||
INDENTATION = " " | ||
private_constant :INDENTATION | ||
|
||
OFFSET = INDENTATION | ||
private_constant :OFFSET | ||
|
||
# @since x.x.x | ||
# @api private | ||
attr_reader :key | ||
|
||
# @since x.x.x | ||
# @api private | ||
def initialize(inflector, app, slice, key) | ||
@key = key | ||
super(inflector, app, slice, nil) | ||
end | ||
|
||
# @since x.x.x | ||
# @api private | ||
def namespaces | ||
@namespaces ||= key.split(KEY_SEPARATOR)[..-2] | ||
end | ||
|
||
# @since x.x.x | ||
# @api private | ||
def name | ||
@name ||= key.split(KEY_SEPARATOR)[-1] | ||
end | ||
|
||
# @api private | ||
# @since x.x.x | ||
# @api private | ||
def camelized_name | ||
inflector.camelize(name) | ||
end | ||
|
||
# @since x.x.x | ||
# @api private | ||
def module_namespace_declaration | ||
namespaces.each_with_index.map { |token, i| | ||
"#{OFFSET}#{INDENTATION * i}module #{inflector.camelize(token)}" | ||
}.join($/) | ||
end | ||
|
||
# @since x.x.x | ||
# @api private | ||
def module_namespace_end | ||
namespaces.each_with_index.map { |_, i| | ||
"#{OFFSET}#{INDENTATION * i}end" | ||
}.reverse.join($/) | ||
end | ||
|
||
# @since x.x.x | ||
# @api private | ||
def module_namespace_offset | ||
"#{OFFSET}#{INDENTATION * namespaces.count}" | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# auto_register: false | ||
# frozen_string_literal: true | ||
|
||
module <%= camelized_slice_name %> | ||
class Operation < <%= camelized_app_name %>::Operation | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# auto_register: false | ||
# frozen_string_literal: true | ||
|
||
require "dry/operation" | ||
|
||
module <%= camelized_app_name %> | ||
class Operation < Dry::Operation | ||
end | ||
end |
Uh oh!
There was an error while loading. Please reload this page.