hanami-cli 2.1.1 → 2.2.0.beta1
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 +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
|