Skip to content

Commit

Permalink
Generate repo (#200)
Browse files Browse the repository at this point in the history
* 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

* Add struct generator

* Extract App::Generate::Command

* Extract App::Generate::Command

* Specify full name, to use App::Command

* Specify full name, to use App::Command

* Use App::Generate::Command for Struct

* Use KEY_SEPARATOR from constants file

* Use constants file

* Provide generator class to command

* Extract Helper, use for Struct generator

* Fix specs with Helper

* kwargs

* Rename helper to RubyFileWriter

* Fix examples

* Remove optional kwarg

* Reorder args

* Rename to relative_parent_class

* Add repo generator

* Remove duplicate implementation

* Add api doc comments

* Reorder methods

* Refactor initialize

* Refactor to use method instead of arg

* Refactor to move logic into generator

* Reorder assignments

* Adapt to new API for RubyFileWriter

* Use super

* Remove space

* Name kwargs for Ruby 3.1

* Switch to singularize from pluralize

* Remove shellescape
  • Loading branch information
cllns authored Jul 14, 2024
1 parent 0c8fdd6 commit 20f2407
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/hanami/cli/commands/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def self.extended(base)
prefix.register "component", Generate::Component
prefix.register "operation", Generate::Operation
prefix.register "part", Generate::Part
prefix.register "repo", Generate::Repo
prefix.register "slice", Generate::Slice
prefix.register "struct", Generate::Struct
prefix.register "view", Generate::View
Expand Down
49 changes: 49 additions & 0 deletions lib/hanami/cli/commands/app/generate/repo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 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 Repo < Command
argument :name, required: true, desc: "Repo name"
option :slice, required: false, desc: "Slice name"

example [
%(books (MyApp::Repos::BooksRepo)),
%(books/drafts_repo (MyApp::Repos::Books::DraftsRepo)),
%(books --slice=admin (Admin::Repos::BooksRepo)),
]

# @since 2.2.0
# @api private
def generator_class
Generators::App::Repo
end

# @since 2.2.0
# @api private
def call(name:, slice: nil, **opts)
normalized_name = if name.end_with?("_repo")
name
else
"#{inflector.singularize(name)}_repo"
end
slice = inflector.underscore(slice) if slice

super(name: normalized_name, slice: slice, **opts)
end
end
end
end
end
end
end
45 changes: 45 additions & 0 deletions lib/hanami/cli/generators/app/repo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

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

module Hanami
module CLI
module Generators
module App
# @since 2.2.0
# @api private
class Repo
# @since 2.2.0
# @api private
def initialize(fs:, inflector:, out: $stdout)
@fs = fs
@inflector = inflector
@out = out
end

# @since 2.2.0
# @api private
def call(app_namespace, key, slice)
RubyFileWriter.new(
fs: fs,
inflector: inflector,
app_namespace: app_namespace,
key: key,
slice: slice,
extra_namespace: "Repos",
relative_parent_class: "DB::Repo",
body: [],
).call
end

private

attr_reader :fs, :inflector, :out
end
end
end
end
end
137 changes: 137 additions & 0 deletions spec/unit/hanami/cli/commands/app/generate/repo_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# frozen_string_literal: true

RSpec.describe Hanami::CLI::Commands::App::Generate::Repo, :app do
subject { described_class.new(fs: fs, inflector: inflector, out: out) }

let(:out) { StringIO.new }
let(:fs) { Hanami::CLI::Files.new(memory: true, out: out) }
let(:inflector) { Dry::Inflector.new }
let(:app) { Hanami.app.namespace }
let(:dir) { inflector.underscore(app) }

def output
out.rewind && out.read.chomp
end

context "generating for app" do
describe "without namespace" do
it "generates a repo and singularizes name properly" do
subject.call(name: "books")

repo_file = <<~EXPECTED
# frozen_string_literal: true
module Test
module Repos
class BookRepo < Test::DB::Repo
end
end
end
EXPECTED

expect(fs.read("app/repos/book_repo.rb")).to eq(repo_file)
expect(output).to include("Created app/repos/book_repo.rb")
end

it "passed through repo name if repo_ suffix is preent" do
subject.call(name: "books_repo")

repo_file = <<~EXPECTED
# frozen_string_literal: true
module Test
module Repos
class BooksRepo < Test::DB::Repo
end
end
end
EXPECTED

expect(fs.read("app/repos/books_repo.rb")).to eq(repo_file)
expect(output).to include("Created app/repos/books_repo.rb")
end
end

it "generates a repo in a deep namespace with default separator" do
subject.call(name: "books.drafts_repo")

repo_file = <<~EXPECTED
# frozen_string_literal: true
module Test
module Repos
module Books
class DraftsRepo < Test::DB::Repo
end
end
end
end
EXPECTED

expect(fs.read("app/repos/books/drafts_repo.rb")).to eq(repo_file)
expect(output).to include("Created app/repos/books/drafts_repo.rb")
end

it "generates an repo in a deep namespace with slash separators" do
subject.call(name: "books/published_repo")

repo_file = <<~EXPECTED
# frozen_string_literal: true
module Test
module Repos
module Books
class PublishedRepo < Test::DB::Repo
end
end
end
end
EXPECTED

expect(fs.read("app/repos/books/published_repo.rb")).to eq(repo_file)
expect(output).to include("Created app/repos/books/published_repo.rb")
end
end

context "generating for a slice" do
it "generates a repo and singularizes name properly" do
fs.mkdir("slices/main")
subject.call(name: "books", slice: "main")

repo_file = <<~EXPECTED
# frozen_string_literal: true
module Main
module Repos
class BookRepo < Main::DB::Repo
end
end
end
EXPECTED

expect(fs.read("slices/main/repos/book_repo.rb")).to eq(repo_file)
expect(output).to include("Created slices/main/repos/book_repo.rb")
end

it "generates a repo in a nested namespace" do
fs.mkdir("slices/main")
subject.call(name: "book.draft", slice: "main")

repo_file = <<~EXPECTED
# frozen_string_literal: true
module Main
module Repos
module Book
class DraftRepo < Main::DB::Repo
end
end
end
end
EXPECTED

expect(fs.read("slices/main/repos/book/draft_repo.rb")).to eq(repo_file)
expect(output).to include("Created slices/main/repos/book/draft_repo.rb")
end
end
end

0 comments on commit 20f2407

Please sign in to comment.