Skip to content

Commit

Permalink
Add Hanami::CLI::RubyFileGenerator, convert Operation to use it inste…
Browse files Browse the repository at this point in the history
…ad of ERB (#186)

* Add dry-operation to default Gemfile

* Add base Operation class, based on dry-operation

* Fix view spec

* Add Operation generators

* Add empty `call` method definition

* Remove ostruct

* Allow slash separator for generator

* Allow slash separator for generator

* Rename module to admin

* Remove newlines in generated files

By adding new templates for un-nested operations

* Remove input as default args

* Remove Operations namespace, generate in app/ or slices/SLICE_NAME/

* Prevent generating operation without namespace

* Revert "Prevent generating operation without namespace"

This reverts commit a5bd2f3.

* Add recommendation to add namespace to operations

* Change examples

* Switch to outputting directly, remove Files#recommend

* Add Hanami::CLI::RubyFileGenerator

* x.x.x => 2.2.0

* x.x.x => 2.2.0

* Include Dry::Monads[:result] in base Action

* Add .module tests

* Convert top-level app operation to use RubyFileGenerator

* Convert nested app operation to use RubyFileGenerator

* Support slash separators

* Convert top-level slice operation to use RubyFileGenerator

* Remove OperationContext

* Remove namespaces instance variable

* Refactor to variables

* Remove last temporary instance variable

* Refactor

* More refactoring, for clarity

* Rename variable for clarity

* Rename helper method

* Simplify RubyFileGenerator, support older versions

* Convert Operation generator to use simplified RubyFileGenerator

* Remove un-used errors

* Refactor

* Older kwargs forwarding style

* Refactor

* Rename variable

* Add explanatory comment

Add dry-monads include for slice base action

* Fix base slice action

* Remove un-used ERB templates

* Remove OperationContext

* Ternary over and/or

* Fix missing 'end' from bad merge

* Fix namespace recommendation

* Extract App::Generate::Command

* Specify full name, to use App::Command

* Use constants file

* Move class methods above initialize

* Use constants file

* Add yard comments

* Revert "Use constants file"

This reverts commit 303f502.

Would need to namespace it and we may want to this to standalone so
keeping it here. It's just two little spaces anyway

* Fix indent to be two spaces
  • Loading branch information
cllns authored Jul 14, 2024
1 parent 1409ad9 commit d8837ec
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 163 deletions.
2 changes: 1 addition & 1 deletion lib/hanami/cli/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def self.new(
inflector: Dry::Inflector.new,
**opts
)
super(out: out, err: err, fs: fs, inflector: inflector, **opts)
super
end

# Returns a new command.
Expand Down
48 changes: 48 additions & 0 deletions lib/hanami/cli/commands/app/generate/command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 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 2.2.0
# @api private
class Command < App::Command
argument :name, required: true, desc: "Name"
option :slice, required: false, desc: "Slice name"

attr_reader :generator
private :generator

# @since 2.2.0
# @api private
def initialize(
fs:,
inflector:,
generator_class: nil,
**opts
)
raise "Provide a generator class (that takes fs and inflector)" if generator_class.nil?

super(fs: fs, inflector: inflector, **opts)
@generator = generator_class.new(fs: fs, inflector: inflector, out: out)
end

# @since 2.2.0
# @api private
def call(name:, slice: nil, **)
normalized_slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
generator.call(app.namespace, name, normalized_slice)
end
end
end
end
end
end
end
24 changes: 3 additions & 21 deletions lib/hanami/cli/commands/app/generate/operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,16 @@ module App
module Generate
# @since 2.2.0
# @api private
class Operation < App::Command
argument :name, required: true, desc: "Operation name"
option :slice, required: false, desc: "Slice name"

class Operation < Generate::Command
example [
%(books.add (MyApp::Books::Add)),
%(books.add --slice=admin (Admin::Books::Add)),
]
attr_reader :generator
private :generator

# @since 2.2.0
# @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 2.2.0
# @api private
def call(name:, slice: nil, **)
slice = inflector.underscore(Shellwords.shellescape(slice)) if slice

generator.call(app.namespace, name, slice)
def initialize(**opts)
super(generator_class: Generators::App::Operation, **opts)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/hanami/cli/commands/app/generate/slice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module App
module Generate
# @since 2.0.0
# @api private
class Slice < Command
class Slice < App::Command
argument :name, required: true, desc: "The slice name"
option :url, required: false, type: :string, desc: "The slice URL prefix"

Expand Down
89 changes: 58 additions & 31 deletions lib/hanami/cli/generators/app/operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require "erb"
require "dry/files"
require_relative "../constants"
require_relative "../../errors"

module Hanami
Expand All @@ -21,54 +22,80 @@ def initialize(fs:, inflector:, out: $stdout)

# @since 2.2.0
# @api private
def call(app, key, slice)
context = OperationContext.new(inflector, app, slice, key)
def call(app_namespace, key, slice)
operation_name = key.split(KEY_SEPARATOR)[-1]
local_namespaces = key.split(KEY_SEPARATOR)[..-2]
container_namespace = slice || app_namespace

if slice
generate_for_slice(context, slice)
else
generate_for_app(context)
end
raise_missing_slice_error_if_missing(slice) if slice
print_namespace_recommendation(operation_name) if local_namespaces.none?

directory = directory(slice, local_namespaces: local_namespaces)
path = fs.join(directory, "#{operation_name}.rb")
fs.mkdir(directory)

file_contents = class_definition(
operation_name: operation_name,
container_namespace: container_namespace,
local_namespaces: local_namespaces,
)
fs.write(path, file_contents)
end

private

attr_reader :fs, :inflector, :out

def generate_for_slice(context, slice)
slice_directory = fs.join("slices", slice)
raise MissingSliceError.new(slice) unless fs.directory?(slice_directory)
def directory(slice = nil, local_namespaces:)
base = if slice
fs.join("slices", slice)
else
fs.join("app")
end

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))
if local_namespaces.any?
fs.join(base, local_namespaces)
else
fs.mkdir(directory = fs.join(slice_directory))
fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_slice_operation.erb", context))
out.puts(" Note: We generated a top-level operation. To generate into a directory, add a namespace: `my_namespace.#{context.name}`")
fs.join(base)
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"))
out.puts(" Note: We generated a top-level operation. To generate into a directory, add a namespace: `my_namespace.#{context.name}`")
fs.write(fs.join(directory, "#{context.name}.rb"), t("top_level_app_operation.erb", context))
end
def class_definition(operation_name:, container_namespace:, local_namespaces:)
container_module = normalize(container_namespace)

modules = local_namespaces
.map { normalize(_1) }
.compact
.prepend(container_module)

parent_class = [container_module, "Operation"].join("::")

RubyFileGenerator.class(
normalize(operation_name),
parent_class: parent_class,
modules: modules,
body: ["def call", "end"],
header: ["# frozen_string_literal: true"],
)
end

def template(path, context)
require "erb"
def normalize(name)
inflector.camelize(name).gsub(/[^\p{Alnum}]/, "")
end

ERB.new(
File.read(__dir__ + "/operation/#{path}")
).result(context.ctx)
def print_namespace_recommendation(operation_name)
out.puts(
" Note: We generated a top-level operation. " \
"To generate into a directory, add a namespace: `my_namespace.#{operation_name}`"
)
end

alias_method :t, :template
def raise_missing_slice_error_if_missing(slice)
if slice
slice_directory = fs.join("slices", slice)
raise MissingSliceError.new(slice) unless fs.directory?(slice_directory)
end
end
end
end
end
Expand Down
10 changes: 0 additions & 10 deletions lib/hanami/cli/generators/app/operation/nested_app_operation.erb

This file was deleted.

10 changes: 0 additions & 10 deletions lib/hanami/cli/generators/app/operation/nested_slice_operation.erb

This file was deleted.

This file was deleted.

This file was deleted.

71 changes: 0 additions & 71 deletions lib/hanami/cli/generators/app/operation_context.rb

This file was deleted.

Loading

0 comments on commit d8837ec

Please sign in to comment.