hanami-cli 2.1.1 → 2.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +18 -13
- data/CHANGELOG.md +15 -0
- data/Gemfile +4 -2
- data/README.md +11 -7
- data/docker-compose.yml +9 -0
- data/hanami-cli.gemspec +2 -2
- data/lib/hanami/cli/command.rb +1 -1
- data/lib/hanami/cli/commands/app/command.rb +2 -16
- data/lib/hanami/cli/commands/app/db/command.rb +116 -0
- data/lib/hanami/cli/commands/app/db/create.rb +19 -11
- data/lib/hanami/cli/commands/app/db/drop.rb +19 -10
- data/lib/hanami/cli/commands/app/db/migrate.rb +19 -13
- data/lib/hanami/cli/commands/app/db/prepare.rb +42 -0
- data/lib/hanami/cli/commands/app/db/seed.rb +11 -22
- data/lib/hanami/cli/commands/app/db/structure/dump.rb +30 -7
- data/lib/hanami/cli/commands/app/db/structure/load.rb +52 -0
- data/lib/hanami/cli/commands/app/db/utils/database.rb +68 -73
- data/lib/hanami/cli/commands/app/db/utils/mysql.rb +2 -2
- data/lib/hanami/cli/commands/app/db/utils/postgres.rb +38 -19
- data/lib/hanami/cli/commands/app/db/utils/sqlite.rb +58 -10
- data/lib/hanami/cli/commands/app/db/version.rb +12 -9
- data/lib/hanami/cli/commands/app/generate/action.rb +4 -3
- data/lib/hanami/cli/commands/app/generate/command.rb +49 -0
- data/lib/hanami/cli/commands/app/generate/component.rb +49 -0
- data/lib/hanami/cli/commands/app/generate/migration.rb +27 -0
- data/lib/hanami/cli/commands/app/generate/operation.rb +26 -0
- data/lib/hanami/cli/commands/app/generate/part.rb +1 -1
- data/lib/hanami/cli/commands/app/generate/relation.rb +35 -0
- data/lib/hanami/cli/commands/app/generate/repo.rb +46 -0
- data/lib/hanami/cli/commands/app/generate/slice.rb +20 -3
- data/lib/hanami/cli/commands/app/generate/struct.rb +27 -0
- data/lib/hanami/cli/commands/app/install.rb +1 -1
- data/lib/hanami/cli/commands/app/middleware.rb +1 -1
- data/lib/hanami/cli/commands/app/server.rb +2 -2
- data/lib/hanami/cli/commands/app.rb +21 -2
- data/lib/hanami/cli/commands/gem/new.rb +78 -14
- data/lib/hanami/cli/errors.rb +28 -0
- data/lib/hanami/cli/generators/app/action_context.rb +5 -13
- data/lib/hanami/cli/generators/app/component/component.erb +8 -0
- data/lib/hanami/cli/generators/app/component/slice_component.erb +8 -0
- data/lib/hanami/cli/generators/app/component.rb +61 -0
- data/lib/hanami/cli/generators/app/component_context.rb +82 -0
- data/lib/hanami/cli/generators/app/migration.rb +69 -0
- data/lib/hanami/cli/generators/app/operation.rb +48 -0
- data/lib/hanami/cli/generators/app/part_context.rb +5 -21
- data/lib/hanami/cli/generators/app/relation.rb +44 -0
- data/lib/hanami/cli/generators/app/repo.rb +40 -0
- data/lib/hanami/cli/generators/app/ruby_file_writer.rb +149 -0
- data/lib/hanami/cli/generators/app/slice/operation.erb +7 -0
- data/lib/hanami/cli/generators/app/slice/relation.erb +8 -0
- data/lib/hanami/cli/generators/app/slice/{slice.erb → repo.erb} +3 -1
- data/lib/hanami/cli/generators/app/slice/struct.erb +8 -0
- data/lib/hanami/cli/generators/app/slice.rb +14 -6
- data/lib/hanami/cli/generators/app/slice_context.rb +9 -2
- data/lib/hanami/cli/generators/app/struct.rb +39 -0
- data/lib/hanami/cli/generators/app/view_context.rb +4 -16
- data/lib/hanami/cli/generators/constants.rb +39 -0
- data/lib/hanami/cli/generators/context.rb +48 -0
- data/lib/hanami/cli/generators/gem/app/action.erb +3 -0
- data/lib/hanami/cli/generators/gem/app/env.erb +4 -0
- data/lib/hanami/cli/generators/gem/app/gemfile.erb +11 -0
- data/lib/hanami/cli/generators/gem/app/gitignore.erb +1 -1
- data/lib/hanami/cli/generators/gem/app/operation.erb +13 -0
- data/lib/hanami/cli/generators/gem/app/relation.erb +10 -0
- data/lib/hanami/cli/generators/gem/app/repo.erb +10 -0
- data/lib/hanami/cli/generators/gem/app/struct.erb +10 -0
- data/lib/hanami/cli/generators/gem/app.rb +19 -0
- data/lib/hanami/cli/ruby_file_generator.rb +123 -0
- data/lib/hanami/cli/version.rb +1 -1
- metadata +39 -16
- data/lib/hanami/cli/commands/app/db/create_migration.rb +0 -32
- data/lib/hanami/cli/commands/app/db/reset.rb +0 -28
- data/lib/hanami/cli/commands/app/db/rollback.rb +0 -81
- data/lib/hanami/cli/commands/app/db/sample_data.rb +0 -42
- data/lib/hanami/cli/commands/app/db/setup.rb +0 -26
- data/lib/hanami/cli/commands/app/db/utils/database_config.rb +0 -60
- data/lib/hanami/cli/generators/app/slice/repository.erb +0 -10
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/inflector"
|
4
|
+
require "dry/files"
|
5
|
+
require "shellwords"
|
6
|
+
require_relative "../../../naming"
|
7
|
+
require_relative "../../../errors"
|
8
|
+
|
9
|
+
module Hanami
|
10
|
+
module CLI
|
11
|
+
module Commands
|
12
|
+
module App
|
13
|
+
module Generate
|
14
|
+
# @since 2.2.0
|
15
|
+
# @api private
|
16
|
+
class Repo < Command
|
17
|
+
argument :name, required: true, desc: "Repo name"
|
18
|
+
|
19
|
+
example [
|
20
|
+
%(books (MyApp::Repos::BooksRepo)),
|
21
|
+
%(books/drafts_repo (MyApp::Repos::Books::DraftsRepo)),
|
22
|
+
%(books --slice=admin (Admin::Repos::BooksRepo)),
|
23
|
+
]
|
24
|
+
|
25
|
+
# @since 2.2.0
|
26
|
+
# @api private
|
27
|
+
def generator_class
|
28
|
+
Generators::App::Repo
|
29
|
+
end
|
30
|
+
|
31
|
+
# @since 2.2.0
|
32
|
+
# @api private
|
33
|
+
def call(name:, slice: nil, **opts)
|
34
|
+
normalized_name = if name.end_with?("_repo")
|
35
|
+
name
|
36
|
+
else
|
37
|
+
"#{inflector.singularize(name)}_repo"
|
38
|
+
end
|
39
|
+
super(name: normalized_name, slice: slice, **opts)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -12,10 +12,23 @@ module Hanami
|
|
12
12
|
module Generate
|
13
13
|
# @since 2.0.0
|
14
14
|
# @api private
|
15
|
-
class Slice < Command
|
15
|
+
class Slice < App::Command
|
16
16
|
argument :name, required: true, desc: "The slice name"
|
17
17
|
option :url, required: false, type: :string, desc: "The slice URL prefix"
|
18
18
|
|
19
|
+
# @since 2.2.0
|
20
|
+
# @api private
|
21
|
+
SKIP_DB_DEFAULT = false
|
22
|
+
private_constant :SKIP_DB_DEFAULT
|
23
|
+
|
24
|
+
# @since 2.2.0
|
25
|
+
# @api private
|
26
|
+
option :skip_db,
|
27
|
+
type: :boolean,
|
28
|
+
required: false,
|
29
|
+
default: SKIP_DB_DEFAULT,
|
30
|
+
desc: "Skip database"
|
31
|
+
|
19
32
|
example [
|
20
33
|
"admin # Admin slice (/admin URL prefix)",
|
21
34
|
"users --url=/u # Users slice (/u URL prefix)",
|
@@ -34,14 +47,18 @@ module Hanami
|
|
34
47
|
|
35
48
|
# @since 2.0.0
|
36
49
|
# @api private
|
37
|
-
def call(
|
50
|
+
def call(
|
51
|
+
name:,
|
52
|
+
url: nil,
|
53
|
+
skip_db: SKIP_DB_DEFAULT
|
54
|
+
)
|
38
55
|
require "hanami/setup"
|
39
56
|
|
40
57
|
app = inflector.underscore(Hanami.app.namespace)
|
41
58
|
name = inflector.underscore(Shellwords.shellescape(name))
|
42
59
|
url = sanitize_url_prefix(name, url)
|
43
60
|
|
44
|
-
generator.call(app, name, url)
|
61
|
+
generator.call(app, name, url, skip_db: skip_db)
|
45
62
|
end
|
46
63
|
|
47
64
|
private
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
module App
|
7
|
+
module Generate
|
8
|
+
# @since 2.2.0
|
9
|
+
# @api private
|
10
|
+
class Struct < Command
|
11
|
+
argument :name, required: true, desc: "Struct name"
|
12
|
+
|
13
|
+
example [
|
14
|
+
%(book (MyApp::Structs::Book)),
|
15
|
+
%(book/published_book (MyApp::Structs::Book::PublishedBook)),
|
16
|
+
%(book --slice=admin (Admin::Structs::Book)),
|
17
|
+
]
|
18
|
+
|
19
|
+
def generator_class
|
20
|
+
Generators::App::Struct
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -34,7 +34,7 @@ module Hanami
|
|
34
34
|
private_constant :DEFAULT_WITH_ARGUMENTS
|
35
35
|
|
36
36
|
option :with_arguments, default: DEFAULT_WITH_ARGUMENTS, required: false,
|
37
|
-
desc: "Include inspected arguments", type: :
|
37
|
+
desc: "Include inspected arguments", type: :flag
|
38
38
|
|
39
39
|
example [
|
40
40
|
"middleware # Print app Rack middleware stack",
|
@@ -41,8 +41,8 @@ module Hanami
|
|
41
41
|
option :port, default: Hanami::Port::DEFAULT, required: false,
|
42
42
|
desc: "The port to run the server on (falls back to the rack handler)"
|
43
43
|
option :config, default: DEFAULT_CONFIG_PATH, required: false, desc: "Rack configuration file"
|
44
|
-
option :debug, default: false, required: false, desc: "Turn on/off debug output", type: :
|
45
|
-
option :warn, default: false, required: false, desc: "Turn on/off warnings", type: :
|
44
|
+
option :debug, default: false, required: false, desc: "Turn on/off debug output", type: :flag
|
45
|
+
option :warn, default: false, required: false, desc: "Turn on/off warnings", type: :flag
|
46
46
|
|
47
47
|
# @since 2.0.0
|
48
48
|
# @api private
|
@@ -27,11 +27,30 @@ module Hanami
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
if Hanami.bundled?("hanami-db")
|
31
|
+
register "db" do |db|
|
32
|
+
db.register "create", DB::Create
|
33
|
+
db.register "drop", DB::Drop
|
34
|
+
db.register "migrate", DB::Migrate
|
35
|
+
db.register "structure dump", DB::Structure::Dump
|
36
|
+
db.register "structure load", DB::Structure::Load
|
37
|
+
db.register "seed", DB::Seed
|
38
|
+
db.register "prepare", DB::Prepare
|
39
|
+
db.register "version", DB::Version
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
30
43
|
register "generate", aliases: ["g"] do |prefix|
|
31
|
-
prefix.register "slice", Generate::Slice
|
32
44
|
prefix.register "action", Generate::Action
|
33
|
-
prefix.register "
|
45
|
+
prefix.register "component", Generate::Component
|
46
|
+
prefix.register "migration", Generate::Migration
|
47
|
+
prefix.register "operation", Generate::Operation
|
34
48
|
prefix.register "part", Generate::Part
|
49
|
+
prefix.register "relation", Generate::Relation
|
50
|
+
prefix.register "repo", Generate::Repo
|
51
|
+
prefix.register "slice", Generate::Slice
|
52
|
+
prefix.register "struct", Generate::Struct
|
53
|
+
prefix.register "view", Generate::View
|
35
54
|
end
|
36
55
|
end
|
37
56
|
end
|
@@ -25,6 +25,27 @@ module Hanami
|
|
25
25
|
SKIP_ASSETS_DEFAULT = false
|
26
26
|
private_constant :SKIP_ASSETS_DEFAULT
|
27
27
|
|
28
|
+
# @since 2.2.0
|
29
|
+
# @api private
|
30
|
+
SKIP_DB_DEFAULT = false
|
31
|
+
private_constant :SKIP_DB_DEFAULT
|
32
|
+
|
33
|
+
# @since 2.2.0
|
34
|
+
# @api private
|
35
|
+
DATABASE_SQLITE = "sqlite"
|
36
|
+
|
37
|
+
# @since 2.2.0
|
38
|
+
# @api private
|
39
|
+
DATABASE_POSTGRES = "postgres"
|
40
|
+
|
41
|
+
# @since 2.2.0
|
42
|
+
# @api private
|
43
|
+
DATABASE_MYSQL = "mysql"
|
44
|
+
|
45
|
+
# @since 2.2.0
|
46
|
+
# @api private
|
47
|
+
SUPPORTED_DATABASES = [DATABASE_SQLITE, DATABASE_POSTGRES, DATABASE_MYSQL].freeze
|
48
|
+
|
28
49
|
desc "Generate a new Hanami app"
|
29
50
|
|
30
51
|
# @since 2.0.0
|
@@ -33,28 +54,42 @@ module Hanami
|
|
33
54
|
|
34
55
|
# @since 2.0.0
|
35
56
|
# @api private
|
36
|
-
option :skip_install, type: :
|
57
|
+
option :skip_install, type: :flag, required: false,
|
37
58
|
default: SKIP_INSTALL_DEFAULT,
|
38
59
|
desc: "Skip app installation (Bundler, third-party Hanami plugins)"
|
39
60
|
|
40
61
|
# @since 2.1.0
|
41
62
|
# @api private
|
42
|
-
option :head, type: :
|
63
|
+
option :head, type: :flag, required: false,
|
43
64
|
default: HEAD_DEFAULT,
|
44
65
|
desc: "Use Hanami HEAD version (from GitHub `main` branches)"
|
45
66
|
|
46
67
|
# @since 2.1.0
|
47
68
|
# @api private
|
48
|
-
option :skip_assets, type: :
|
69
|
+
option :skip_assets, type: :flag, required: false,
|
49
70
|
default: SKIP_ASSETS_DEFAULT,
|
50
|
-
desc: "Skip assets"
|
71
|
+
desc: "Skip including hanami-assets"
|
72
|
+
|
73
|
+
# @since 2.2.0
|
74
|
+
# @api private
|
75
|
+
option :skip_db, type: :flag, required: false,
|
76
|
+
default: SKIP_DB_DEFAULT,
|
77
|
+
desc: "Skip including hanami-db"
|
78
|
+
|
79
|
+
# @since 2.2.0
|
80
|
+
# @api private
|
81
|
+
option :database, type: :string, required: false,
|
82
|
+
default: DATABASE_SQLITE,
|
83
|
+
desc: "Database adapter (supported: sqlite, mysql, postgres)"
|
51
84
|
|
52
85
|
# rubocop:disable Layout/LineLength
|
53
86
|
example [
|
54
|
-
"bookshelf
|
55
|
-
"bookshelf --head
|
56
|
-
"bookshelf --skip-install
|
57
|
-
"bookshelf --skip-assets
|
87
|
+
"bookshelf # Generate a new Hanami app in `bookshelf/' directory, using `Bookshelf' namespace",
|
88
|
+
"bookshelf --head # Generate a new Hanami app, using Hanami HEAD version from GitHub `main' branches",
|
89
|
+
"bookshelf --skip-install # Generate a new Hanami app, but it skips Hanami installation",
|
90
|
+
"bookshelf --skip-assets # Generate a new Hanami app without hanmai-assets",
|
91
|
+
"bookshelf --skip-db # Generate a new Hanami app without hanami-db",
|
92
|
+
"bookshelf --database={sqlite|postgres|mysql} # Generate a new Hanami app with a specified database (default: sqlite)",
|
58
93
|
]
|
59
94
|
# rubocop:enable Layout/LineLength
|
60
95
|
|
@@ -75,20 +110,36 @@ module Hanami
|
|
75
110
|
@system_call = system_call
|
76
111
|
end
|
77
112
|
|
78
|
-
# rubocop:
|
79
|
-
|
80
|
-
# rubocop:disable Metrics/AbcSize
|
113
|
+
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
81
114
|
|
82
115
|
# @since 2.0.0
|
83
116
|
# @api private
|
84
|
-
def call(
|
117
|
+
def call(
|
118
|
+
app:,
|
119
|
+
head: HEAD_DEFAULT,
|
120
|
+
skip_install: SKIP_INSTALL_DEFAULT,
|
121
|
+
skip_assets: SKIP_ASSETS_DEFAULT,
|
122
|
+
skip_db: SKIP_DB_DEFAULT,
|
123
|
+
database: nil
|
124
|
+
)
|
125
|
+
# rubocop:enable Metrics/ParameterLists
|
85
126
|
app = inflector.underscore(app)
|
86
127
|
|
87
128
|
raise PathAlreadyExistsError.new(app) if fs.exist?(app)
|
129
|
+
raise ConflictingOptionsError.new("--skip-db", "--database") if skip_db && database
|
130
|
+
|
131
|
+
normalized_database ||= normalize_database(database)
|
88
132
|
|
89
133
|
fs.mkdir(app)
|
90
134
|
fs.chdir(app) do
|
91
|
-
context = Generators::Context.new(
|
135
|
+
context = Generators::Context.new(
|
136
|
+
inflector,
|
137
|
+
app,
|
138
|
+
head: head,
|
139
|
+
skip_assets: skip_assets,
|
140
|
+
skip_db: skip_db,
|
141
|
+
database: normalized_database
|
142
|
+
)
|
92
143
|
generator.call(app, context: context) do
|
93
144
|
if skip_install
|
94
145
|
out.puts "Skipping installation, please enter `#{app}' directory and run `bundle exec hanami install'"
|
@@ -112,7 +163,7 @@ module Hanami
|
|
112
163
|
end
|
113
164
|
end
|
114
165
|
end
|
115
|
-
# rubocop:enable Metrics/AbcSize
|
166
|
+
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
|
116
167
|
|
117
168
|
private
|
118
169
|
|
@@ -120,6 +171,19 @@ module Hanami
|
|
120
171
|
attr_reader :generator
|
121
172
|
attr_reader :system_call
|
122
173
|
|
174
|
+
def normalize_database(database)
|
175
|
+
case database
|
176
|
+
when nil, "sqlite", "sqlite3"
|
177
|
+
DATABASE_SQLITE
|
178
|
+
when "mysql", "mysql2"
|
179
|
+
DATABASE_MYSQL
|
180
|
+
when "postgres", "postgresql", "pg"
|
181
|
+
DATABASE_POSTGRES
|
182
|
+
else
|
183
|
+
raise DatabaseNotSupportedError.new(database, SUPPORTED_DATABASES)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
123
187
|
def run_install_command!(head:)
|
124
188
|
head_flag = head ? " --head" : ""
|
125
189
|
bundler.exec("hanami install#{head_flag}").tap do |result|
|
data/lib/hanami/cli/errors.rb
CHANGED
@@ -91,5 +91,33 @@ module Hanami
|
|
91
91
|
super("`#{scheme}' is not a supported db scheme")
|
92
92
|
end
|
93
93
|
end
|
94
|
+
|
95
|
+
# @since 2.2.0
|
96
|
+
# @api public
|
97
|
+
class DatabaseNotSupportedError < Error
|
98
|
+
def initialize(invalid_database, supported_databases)
|
99
|
+
super("`#{invalid_database}' is not a supported database. Supported databases are: #{supported_databases.join(', ')}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @since 2.2.0
|
104
|
+
# @api public
|
105
|
+
class ConflictingOptionsError < Error
|
106
|
+
def initialize(option1, option2)
|
107
|
+
super("`#{option1}' and `#{option2}' cannot be used together")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# @since 2.2.0
|
112
|
+
# @api public
|
113
|
+
class InvalidMigrationNameError < Error
|
114
|
+
def initialize(name)
|
115
|
+
super(<<~TEXT)
|
116
|
+
Invalid migration name: #{name}
|
117
|
+
|
118
|
+
Name must contain only letters, numbers, and underscores.
|
119
|
+
TEXT
|
120
|
+
end
|
121
|
+
end
|
94
122
|
end
|
95
123
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "slice_context"
|
4
3
|
require "dry/files/path"
|
4
|
+
require_relative "slice_context"
|
5
|
+
require_relative "../constants"
|
5
6
|
|
6
7
|
module Hanami
|
7
8
|
module CLI
|
@@ -52,7 +53,7 @@ module Hanami
|
|
52
53
|
# @api private
|
53
54
|
def module_controller_declaration
|
54
55
|
controller.each_with_index.map do |token, i|
|
55
|
-
"#{
|
56
|
+
"#{NESTED_OFFSET}#{INDENTATION * i}module #{inflector.camelize(token)}"
|
56
57
|
end.join($/)
|
57
58
|
end
|
58
59
|
|
@@ -60,14 +61,14 @@ module Hanami
|
|
60
61
|
# @api private
|
61
62
|
def module_controller_end
|
62
63
|
controller.each_with_index.map do |_, i|
|
63
|
-
"#{
|
64
|
+
"#{NESTED_OFFSET}#{INDENTATION * i}end"
|
64
65
|
end.reverse.join($/)
|
65
66
|
end
|
66
67
|
|
67
68
|
# @since 2.0.0
|
68
69
|
# @api private
|
69
70
|
def module_controller_offset
|
70
|
-
"#{
|
71
|
+
"#{NESTED_OFFSET}#{INDENTATION * controller.count}"
|
71
72
|
end
|
72
73
|
|
73
74
|
# @since 2.0.0
|
@@ -79,15 +80,6 @@ module Hanami
|
|
79
80
|
|
80
81
|
private
|
81
82
|
|
82
|
-
NAMESPACE_SEPARATOR = "::"
|
83
|
-
private_constant :NAMESPACE_SEPARATOR
|
84
|
-
|
85
|
-
INDENTATION = " "
|
86
|
-
private_constant :INDENTATION
|
87
|
-
|
88
|
-
OFFSET = INDENTATION * 2
|
89
|
-
private_constant :OFFSET
|
90
|
-
|
91
83
|
attr_reader :controller
|
92
84
|
|
93
85
|
attr_reader :action
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
require "dry/files"
|
5
|
+
module Hanami
|
6
|
+
module CLI
|
7
|
+
module Generators
|
8
|
+
module App
|
9
|
+
# @api private
|
10
|
+
# @since 2.2.0
|
11
|
+
class Component
|
12
|
+
# @api private
|
13
|
+
# @since 2.2.0
|
14
|
+
def initialize(fs:, inflector:)
|
15
|
+
@fs = fs
|
16
|
+
@inflector = inflector
|
17
|
+
end
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
# @since 2.2.0
|
21
|
+
def call(app, key, slice)
|
22
|
+
context = ComponentContext.new(inflector, app, slice, key)
|
23
|
+
|
24
|
+
if slice
|
25
|
+
generate_for_slice(context, slice)
|
26
|
+
else
|
27
|
+
generate_for_app(context)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :fs
|
34
|
+
|
35
|
+
attr_reader :inflector
|
36
|
+
|
37
|
+
def generate_for_slice(context, slice)
|
38
|
+
slice_directory = fs.join("slices", slice)
|
39
|
+
raise MissingSliceError.new(slice) unless fs.directory?(slice_directory)
|
40
|
+
|
41
|
+
fs.mkdir(directory = fs.join(slice_directory, context.namespaces))
|
42
|
+
fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("slice_component.erb", context))
|
43
|
+
end
|
44
|
+
|
45
|
+
def generate_for_app(context)
|
46
|
+
fs.mkdir(directory = fs.join("app", context.namespaces))
|
47
|
+
fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("component.erb", context))
|
48
|
+
end
|
49
|
+
|
50
|
+
def template(path, context)
|
51
|
+
ERB.new(
|
52
|
+
File.read(__dir__ + "/component/#{path}")
|
53
|
+
).result(context.ctx)
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :t, :template
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../constants"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module CLI
|
7
|
+
module Generators
|
8
|
+
module App
|
9
|
+
class ComponentContext < SliceContext
|
10
|
+
# @api private
|
11
|
+
# @since 2.2.0
|
12
|
+
attr_reader :key
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
# @since 2.2.0
|
16
|
+
def initialize(inflector, app, slice, key)
|
17
|
+
@key = key
|
18
|
+
super(inflector, app, slice, nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
# @since 2.2.0
|
23
|
+
def namespaces
|
24
|
+
@namespaces ||= key.split(MATCHER_PATTERN)[..-2].map { inflector.underscore(_1) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
# @since 2.2.0
|
29
|
+
def name
|
30
|
+
@name ||= key.split(MATCHER_PATTERN)[-1]
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
# @since 2.2.0
|
35
|
+
def camelized_namespace
|
36
|
+
namespaces.map { inflector.camelize(_1) }.join(NAMESPACE_SEPARATOR)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @api private
|
40
|
+
# @since 2.2.0
|
41
|
+
def camelized_name
|
42
|
+
inflector.camelize(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @api private
|
46
|
+
# @since 2.2.0
|
47
|
+
def underscored_namespace
|
48
|
+
namespaces.map { inflector.underscore(_1) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
# @since 2.2.0
|
53
|
+
def underscored_name
|
54
|
+
inflector.underscore(name)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @api private
|
58
|
+
# @since 2.2.0
|
59
|
+
def module_namespace_declaration
|
60
|
+
namespaces.each_with_index.map { |token, i|
|
61
|
+
"#{OFFSET}#{INDENTATION * i}module #{inflector.camelize(token)}"
|
62
|
+
}.join($/)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
# @since 2.2.0
|
67
|
+
def module_namespace_end
|
68
|
+
namespaces.each_with_index.map { |_, i|
|
69
|
+
"#{OFFSET}#{INDENTATION * i}end"
|
70
|
+
}.reverse.join($/)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @api private
|
74
|
+
# @since 2.2.0
|
75
|
+
def module_namespace_offset
|
76
|
+
"#{OFFSET}#{INDENTATION * namespaces.count}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module CLI
|
5
|
+
module Generators
|
6
|
+
module App
|
7
|
+
# @since 2.2.0
|
8
|
+
# @api private
|
9
|
+
class Migration
|
10
|
+
# @since 2.2.0
|
11
|
+
# @api private
|
12
|
+
def initialize(fs:, inflector:, out: $stdout)
|
13
|
+
@fs = fs
|
14
|
+
@inflector = inflector
|
15
|
+
@out = out
|
16
|
+
end
|
17
|
+
|
18
|
+
# @since 2.2.0
|
19
|
+
# @api private
|
20
|
+
def call(_app_namespace, name, slice, **_opts)
|
21
|
+
normalized_name = inflector.underscore(name)
|
22
|
+
ensure_valid_name(normalized_name)
|
23
|
+
|
24
|
+
base = if slice
|
25
|
+
fs.join("slices", slice, "config", "db", "migrate")
|
26
|
+
else
|
27
|
+
fs.join("config", "db", "migrate")
|
28
|
+
end
|
29
|
+
|
30
|
+
path = fs.join(base, file_name(normalized_name))
|
31
|
+
|
32
|
+
fs.write(path, FILE_CONTENTS)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :fs, :inflector, :out
|
38
|
+
|
39
|
+
VALID_NAME_REGEX = /^[_a-z0-9]+$/
|
40
|
+
private_constant :VALID_NAME_REGEX
|
41
|
+
|
42
|
+
def ensure_valid_name(name)
|
43
|
+
unless VALID_NAME_REGEX.match?(name.downcase)
|
44
|
+
raise InvalidMigrationNameError.new(name)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def file_name(name)
|
49
|
+
"#{Time.now.strftime(VERSION_FORMAT)}_#{name}.rb"
|
50
|
+
end
|
51
|
+
|
52
|
+
VERSION_FORMAT = "%Y%m%d%H%M%S"
|
53
|
+
private_constant :VERSION_FORMAT
|
54
|
+
|
55
|
+
FILE_CONTENTS = <<~RUBY
|
56
|
+
# frozen_string_literal: true
|
57
|
+
|
58
|
+
ROM::SQL.migration do
|
59
|
+
# Add your migration here.
|
60
|
+
#
|
61
|
+
# See https://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html for details.
|
62
|
+
end
|
63
|
+
RUBY
|
64
|
+
private_constant :FILE_CONTENTS
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|