hanami-cli 2.2.1 → 2.3.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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -0
  3. data/CHANGELOG.md +33 -0
  4. data/Gemfile +3 -1
  5. data/hanami-cli.gemspec +2 -0
  6. data/lib/hanami/cli/command.rb +5 -11
  7. data/lib/hanami/cli/commands/app/command.rb +5 -5
  8. data/lib/hanami/cli/commands/app/console.rb +1 -0
  9. data/lib/hanami/cli/commands/app/db/command.rb +0 -1
  10. data/lib/hanami/cli/commands/app/db/drop.rb +2 -2
  11. data/lib/hanami/cli/commands/app/db/rollback.rb +204 -0
  12. data/lib/hanami/cli/commands/app/db/utils/mysql.rb +3 -2
  13. data/lib/hanami/cli/commands/app/db/utils/postgres.rb +3 -1
  14. data/lib/hanami/cli/commands/app/generate/action.rb +32 -40
  15. data/lib/hanami/cli/commands/app/generate/command.rb +54 -17
  16. data/lib/hanami/cli/commands/app/generate/component.rb +4 -19
  17. data/lib/hanami/cli/commands/app/generate/part.rb +4 -21
  18. data/lib/hanami/cli/commands/app/generate/slice.rb +2 -2
  19. data/lib/hanami/cli/commands/app/generate/view.rb +5 -24
  20. data/lib/hanami/cli/commands/app/server.rb +0 -1
  21. data/lib/hanami/cli/commands/app.rb +1 -0
  22. data/lib/hanami/cli/commands/gem/new.rb +34 -3
  23. data/lib/hanami/cli/errors.rb +26 -0
  24. data/lib/hanami/cli/files.rb +20 -6
  25. data/lib/hanami/cli/generators/app/action.rb +78 -101
  26. data/lib/hanami/cli/generators/app/component.rb +11 -33
  27. data/lib/hanami/cli/generators/app/migration.rb +1 -1
  28. data/lib/hanami/cli/generators/app/operation.rb +4 -5
  29. data/lib/hanami/cli/generators/app/part.rb +42 -65
  30. data/lib/hanami/cli/generators/app/relation.rb +4 -5
  31. data/lib/hanami/cli/generators/app/repo.rb +3 -5
  32. data/lib/hanami/cli/generators/app/ruby_class_file.rb +32 -0
  33. data/lib/hanami/cli/generators/app/ruby_file.rb +128 -0
  34. data/lib/hanami/cli/generators/app/ruby_module_file.rb +28 -0
  35. data/lib/hanami/cli/generators/app/slice.rb +130 -37
  36. data/lib/hanami/cli/generators/app/struct.rb +3 -4
  37. data/lib/hanami/cli/generators/app/view.rb +40 -45
  38. data/lib/hanami/cli/generators/context.rb +6 -0
  39. data/lib/hanami/cli/generators/gem/app/assets.js +14 -13
  40. data/lib/hanami/cli/generators/gem/app/dev +1 -1
  41. data/lib/hanami/cli/generators/gem/app/gemfile.erb +5 -0
  42. data/lib/hanami/cli/generators/gem/app/gitignore.erb +3 -1
  43. data/lib/hanami/cli/generators/gem/app/rakefile.erb +3 -0
  44. data/lib/hanami/cli/generators/gem/app/readme.erb +14 -0
  45. data/lib/hanami/cli/generators/gem/app.rb +40 -37
  46. data/lib/hanami/cli/ruby_file_generator.rb +17 -8
  47. data/lib/hanami/cli/server.rb +15 -1
  48. data/lib/hanami/cli/version.rb +1 -1
  49. data/lib/hanami/console/context.rb +5 -0
  50. metadata +35 -41
  51. data/lib/hanami/cli/generators/app/action/action.erb +0 -17
  52. data/lib/hanami/cli/generators/app/action/slice_action.erb +0 -17
  53. data/lib/hanami/cli/generators/app/action/slice_template.html.erb +0 -1
  54. data/lib/hanami/cli/generators/app/action/slice_view.erb +0 -10
  55. data/lib/hanami/cli/generators/app/action/template.erb +0 -0
  56. data/lib/hanami/cli/generators/app/action/template.html.erb +0 -1
  57. data/lib/hanami/cli/generators/app/action/view.erb +0 -10
  58. data/lib/hanami/cli/generators/app/action_context.rb +0 -90
  59. data/lib/hanami/cli/generators/app/component/component.erb +0 -8
  60. data/lib/hanami/cli/generators/app/component/slice_component.erb +0 -8
  61. data/lib/hanami/cli/generators/app/component_context.rb +0 -82
  62. data/lib/hanami/cli/generators/app/part/app_base_part.erb +0 -9
  63. data/lib/hanami/cli/generators/app/part/app_part.erb +0 -13
  64. data/lib/hanami/cli/generators/app/part/slice_base_part.erb +0 -9
  65. data/lib/hanami/cli/generators/app/part/slice_part.erb +0 -13
  66. data/lib/hanami/cli/generators/app/part_context.rb +0 -82
  67. data/lib/hanami/cli/generators/app/ruby_file_writer.rb +0 -151
  68. data/lib/hanami/cli/generators/app/slice/action.erb +0 -7
  69. data/lib/hanami/cli/generators/app/slice/app_css.erb +0 -5
  70. data/lib/hanami/cli/generators/app/slice/app_js.erb +0 -1
  71. data/lib/hanami/cli/generators/app/slice/app_layout.erb +0 -18
  72. data/lib/hanami/cli/generators/app/slice/helpers.erb +0 -10
  73. data/lib/hanami/cli/generators/app/slice/keep.erb +0 -0
  74. data/lib/hanami/cli/generators/app/slice/operation.erb +0 -7
  75. data/lib/hanami/cli/generators/app/slice/relation.erb +0 -8
  76. data/lib/hanami/cli/generators/app/slice/repo.erb +0 -8
  77. data/lib/hanami/cli/generators/app/slice/routes.erb +0 -3
  78. data/lib/hanami/cli/generators/app/slice/struct.erb +0 -8
  79. data/lib/hanami/cli/generators/app/slice/view.erb +0 -7
  80. data/lib/hanami/cli/generators/app/slice_context.rb +0 -72
  81. data/lib/hanami/cli/generators/app/view/app_template.html.erb +0 -1
  82. data/lib/hanami/cli/generators/app/view/app_view.erb +0 -10
  83. data/lib/hanami/cli/generators/app/view/slice_template.html.erb +0 -1
  84. data/lib/hanami/cli/generators/app/view/slice_view.erb +0 -10
  85. data/lib/hanami/cli/generators/app/view_context.rb +0 -88
@@ -3,6 +3,7 @@
3
3
  require "dry/inflector"
4
4
  require "dry/files"
5
5
  require "shellwords"
6
+
6
7
  module Hanami
7
8
  module CLI
8
9
  module Commands
@@ -10,9 +11,8 @@ module Hanami
10
11
  module Generate
11
12
  # @api private
12
13
  # @since 2.2.0
13
- class Component < App::Command
14
+ class Component < Command
14
15
  argument :name, required: true, desc: "Component name"
15
- option :slice, required: false, desc: "Slice name"
16
16
 
17
17
  example [
18
18
  %(isbn_decoder (MyApp::IsbnDecoder)),
@@ -20,26 +20,11 @@ module Hanami
20
20
  %(isbn_decoder --slice=admin (Admin::IsbnDecoder)),
21
21
  %(Exporters::Complete::CSV (MyApp::Exporters::Complete::CSV)),
22
22
  ]
23
- attr_reader :generator
24
- private :generator
25
23
 
26
- # @api private
27
24
  # @since 2.2.0
28
- def initialize(
29
- fs:, inflector:,
30
- generator: Generators::App::Component.new(fs: fs, inflector: inflector),
31
- **opts
32
- )
33
- @generator = generator
34
- super(fs: fs, inflector: inflector, **opts)
35
- end
36
-
37
25
  # @api private
38
- # @since 2.2.0
39
- def call(name:, slice: nil, **)
40
- slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
41
-
42
- generator.call(app.namespace, name, slice)
26
+ def generator_class
27
+ Generators::App::Component
43
28
  end
44
29
  end
45
30
  end
@@ -11,12 +11,12 @@ module Hanami
11
11
  module Generate
12
12
  # @since 2.1.0
13
13
  # @api private
14
- class Part < App::Command
14
+ class Part < Command
15
15
  DEFAULT_SKIP_TESTS = false
16
16
  private_constant :DEFAULT_SKIP_TESTS
17
17
 
18
18
  argument :name, required: true, desc: "Part name"
19
- option :slice, required: false, desc: "Slice name"
19
+
20
20
  option \
21
21
  :skip_tests,
22
22
  required: false,
@@ -28,26 +28,9 @@ module Hanami
28
28
  %(book (MyApp::Views::Parts::Book)),
29
29
  %(book --slice=admin (Admin::Views::Parts::Book)),
30
30
  ]
31
- attr_reader :generator
32
- private :generator
33
-
34
- # @since 2.0.0
35
- # @api private
36
- def initialize(
37
- fs:, inflector:,
38
- generator: Generators::App::Part.new(fs: fs, inflector: inflector),
39
- **opts
40
- )
41
- super(fs: fs, inflector: inflector, **opts)
42
- @generator = generator
43
- end
44
-
45
- # @since 2.0.0
46
- # @api private
47
- def call(name:, slice: nil, skip_tests: DEFAULT_SKIP_TESTS, **) # rubocop:disable Lint/UnusedMethodArgument
48
- slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
49
31
 
50
- generator.call(app.namespace, name, slice)
32
+ def generator_class
33
+ Generators::App::Part
51
34
  end
52
35
  end
53
36
  end
@@ -49,11 +49,11 @@ module Hanami
49
49
  # @since 2.0.0
50
50
  # @api private
51
51
  def initialize(
52
- fs:, inflector:,
52
+ fs:,
53
53
  generator: Generators::App::Slice.new(fs: fs, inflector: inflector),
54
54
  **opts
55
55
  )
56
- super(fs: fs, inflector: inflector, **opts)
56
+ super(fs: fs, **opts)
57
57
  @generator = generator
58
58
  end
59
59
 
@@ -13,40 +13,21 @@ module Hanami
13
13
  module Generate
14
14
  # @since 2.0.0
15
15
  # @api private
16
- class View < App::Command
17
- # TODO: make this configurable
18
- DEFAULT_FORMAT = "html"
19
- private_constant :DEFAULT_FORMAT
20
-
16
+ class View < Command
17
+ # TODO: make format configurable
21
18
  # TODO: make engine configurable
22
19
 
23
20
  argument :name, required: true, desc: "View name"
24
- option :slice, required: false, desc: "Slice name"
25
21
 
26
22
  example [
27
23
  %(books.index (MyApp::Actions::Books::Index)),
28
24
  %(books.index --slice=admin (Admin::Actions::Books::Index)),
29
25
  ]
30
- attr_reader :generator
31
- private :generator
32
26
 
33
- # @since 2.0.0
27
+ # @since 2.2.0
34
28
  # @api private
35
- def initialize(
36
- fs:, inflector:,
37
- generator: Generators::App::View.new(fs: fs, inflector: inflector),
38
- **opts
39
- )
40
- super(fs: fs, inflector: inflector, **opts)
41
- @generator = generator
42
- end
43
-
44
- # @since 2.0.0
45
- # @api private
46
- def call(name:, format: DEFAULT_FORMAT, slice: nil, **)
47
- slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
48
-
49
- generator.call(app.namespace, name, format, slice)
29
+ def generator_class
30
+ Generators::App::View
50
31
  end
51
32
  end
52
33
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rack"
4
3
  require "hanami/port"
5
4
  require_relative "../app"
6
5
  require_relative "../../server"
@@ -32,6 +32,7 @@ module Hanami
32
32
  db.register "create", DB::Create
33
33
  db.register "drop", DB::Drop
34
34
  db.register "migrate", DB::Migrate
35
+ db.register "rollback", DB::Rollback
35
36
  db.register "structure dump", DB::Structure::Dump
36
37
  db.register "structure load", DB::Structure::Load
37
38
  db.register "seed", DB::Seed
@@ -30,6 +30,11 @@ module Hanami
30
30
  SKIP_DB_DEFAULT = false
31
31
  private_constant :SKIP_DB_DEFAULT
32
32
 
33
+ # @since 2.2.0
34
+ # @api private
35
+ SKIP_VIEW_DEFAULT = false
36
+ private_constant :SKIP_VIEW_DEFAULT
37
+
33
38
  # @since 2.2.0
34
39
  # @api private
35
40
  DATABASE_SQLITE = "sqlite"
@@ -46,6 +51,9 @@ module Hanami
46
51
  # @api private
47
52
  SUPPORTED_DATABASES = [DATABASE_SQLITE, DATABASE_POSTGRES, DATABASE_MYSQL].freeze
48
53
 
54
+ # @api private
55
+ FORBIDDEN_APP_NAMES = %w[app slice].freeze
56
+
49
57
  desc "Generate a new Hanami app"
50
58
 
51
59
  # @since 2.0.0
@@ -76,6 +84,12 @@ module Hanami
76
84
  default: SKIP_DB_DEFAULT,
77
85
  desc: "Skip including hanami-db"
78
86
 
87
+ # @since 2.2.0
88
+ # @api private
89
+ option :skip_view, type: :flag, required: false,
90
+ default: SKIP_VIEW_DEFAULT,
91
+ desc: "Skip including hanami-view"
92
+
79
93
  # @since 2.2.0
80
94
  # @api private
81
95
  option :database, type: :string, required: false,
@@ -87,8 +101,9 @@ module Hanami
87
101
  "bookshelf # Generate a new Hanami app in `bookshelf/' directory, using `Bookshelf' namespace",
88
102
  "bookshelf --head # Generate a new Hanami app, using Hanami HEAD version from GitHub `main' branches",
89
103
  "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",
104
+ "bookshelf --skip-assets # Generate a new Hanami app without hanami-assets",
91
105
  "bookshelf --skip-db # Generate a new Hanami app without hanami-db",
106
+ "bookshelf --skip-view # Generate a new Hanami app without hanami-view",
92
107
  "bookshelf --database={sqlite|postgres|mysql} # Generate a new Hanami app with a specified database (default: sqlite)",
93
108
  ]
94
109
  # rubocop:enable Layout/LineLength
@@ -98,13 +113,13 @@ module Hanami
98
113
  # @since 2.0.0
99
114
  # @api private
100
115
  def initialize(
101
- fs:, inflector:,
116
+ fs:,
102
117
  bundler: CLI::Bundler.new(fs: fs),
103
118
  generator: Generators::Gem::App.new(fs: fs, inflector: inflector),
104
119
  system_call: SystemCall.new,
105
120
  **opts
106
121
  )
107
- super(fs: fs, inflector: inflector, **opts)
122
+ super(fs: fs, **opts)
108
123
  @bundler = bundler
109
124
  @generator = generator
110
125
  @system_call = system_call
@@ -120,12 +135,14 @@ module Hanami
120
135
  skip_install: SKIP_INSTALL_DEFAULT,
121
136
  skip_assets: SKIP_ASSETS_DEFAULT,
122
137
  skip_db: SKIP_DB_DEFAULT,
138
+ skip_view: SKIP_VIEW_DEFAULT,
123
139
  database: nil
124
140
  )
125
141
  # rubocop:enable Metrics/ParameterLists
126
142
  app = inflector.underscore(app)
127
143
 
128
144
  raise PathAlreadyExistsError.new(app) if fs.exist?(app)
145
+ raise ForbiddenAppNameError.new(app) if FORBIDDEN_APP_NAMES.include?(app)
129
146
 
130
147
  normalized_database ||= normalize_database(database)
131
148
 
@@ -137,6 +154,7 @@ module Hanami
137
154
  head: head,
138
155
  skip_assets: skip_assets,
139
156
  skip_db: skip_db,
157
+ skip_view: skip_view,
140
158
  database: normalized_database
141
159
  )
142
160
  generator.call(app, context: context) do
@@ -158,6 +176,9 @@ module Hanami
158
176
 
159
177
  out.puts "Running hanami install..."
160
178
  run_install_command!(head: head)
179
+
180
+ out.puts "Initializing git repository..."
181
+ init_git_repository
161
182
  end
162
183
  end
163
184
  end
@@ -193,6 +214,16 @@ module Hanami
193
214
  end
194
215
  end
195
216
  end
217
+
218
+ # @api private
219
+ def init_git_repository
220
+ system_call.call("git", ["init"]).tap do |result|
221
+ unless result.successful?
222
+ out.puts "WARNING: Failed to initialize git repository"
223
+ out.puts(result.err.lines.map { |line| line.prepend(" ") })
224
+ end
225
+ end
226
+ end
196
227
  end
197
228
  end
198
229
  end
@@ -44,6 +44,24 @@ module Hanami
44
44
  end
45
45
  end
46
46
 
47
+ # @api public
48
+ class FileAlreadyExistsError < Error
49
+ ERROR_MESSAGE = <<~ERROR.chomp
50
+ The file `%{file_path}` could not be generated because it already exists.
51
+ ERROR
52
+
53
+ def initialize(file_path)
54
+ super(ERROR_MESSAGE % {file_path:})
55
+ end
56
+ end
57
+
58
+ # @api public
59
+ class ForbiddenAppNameError < Error
60
+ def initialize(name)
61
+ super("Cannot create new Hanami app with the name: `#{name}'")
62
+ end
63
+ end
64
+
47
65
  # @since 2.0.0
48
66
  # @api public
49
67
  class MissingSliceError < Error
@@ -100,6 +118,14 @@ module Hanami
100
118
  end
101
119
  end
102
120
 
121
+ # @since 2.2.0
122
+ # @api public
123
+ class DatabaseExistenceCheckError < Error
124
+ def initialize(original_message)
125
+ super("Could not check if the database exists. Error message:\n#{original_message}")
126
+ end
127
+ end
128
+
103
129
  # @since 2.2.0
104
130
  # @api public
105
131
  class ConflictingOptionsError < Error
@@ -14,6 +14,13 @@ module Hanami
14
14
  @out = out
15
15
  end
16
16
 
17
+ # @api private
18
+ def create(path, *content)
19
+ raise FileAlreadyExistsError.new(path) if exist?(path)
20
+
21
+ write(path, *content)
22
+ end
23
+
17
24
  # @since 2.0.0
18
25
  # @api private
19
26
  def write(path, *content)
@@ -33,10 +40,10 @@ module Hanami
33
40
  # @since 2.0.0
34
41
  # @api private
35
42
  def mkdir(path)
36
- unless exist?(path)
37
- super
38
- created(_path(path))
39
- end
43
+ return if exist?(path)
44
+
45
+ super
46
+ created(dir_path(path))
40
47
  end
41
48
 
42
49
  # @since 2.0.0
@@ -46,6 +53,13 @@ module Hanami
46
53
  super
47
54
  end
48
55
 
56
+ def touch(path)
57
+ return if exist?(path)
58
+
59
+ super
60
+ created(path)
61
+ end
62
+
49
63
  private
50
64
 
51
65
  attr_reader :out
@@ -77,10 +91,10 @@ module Hanami
77
91
  end
78
92
 
79
93
  def within_folder(path)
80
- out.puts "-> Within #{_path(path)}"
94
+ out.puts "-> Within #{dir_path(path)}"
81
95
  end
82
96
 
83
- def _path(path)
97
+ def dir_path(path)
84
98
  path + ::File::SEPARATOR
85
99
  end
86
100
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
3
  require "dry/files"
4
+ require_relative "../constants"
5
5
  require_relative "../../errors"
6
6
 
7
7
  # rubocop:disable Metrics/ParameterLists
@@ -14,20 +14,25 @@ module Hanami
14
14
  class Action
15
15
  # @since 2.0.0
16
16
  # @api private
17
- def initialize(fs:, inflector:)
17
+ def initialize(fs:, inflector:, out: $stdout)
18
18
  @fs = fs
19
19
  @inflector = inflector
20
+ @out = out
21
+ @view_generator = Generators::App::View.new(
22
+ fs: fs,
23
+ inflector: inflector,
24
+ out: out
25
+ )
20
26
  end
21
27
 
22
28
  # @since 2.0.0
23
29
  # @api private
24
- def call(app, controller, action, url, http, format, skip_view, skip_route, slice, context: nil)
25
- context ||= ActionContext.new(inflector, app, slice, controller, action)
26
- if slice
27
- generate_for_slice(controller, action, url, http, format, skip_view, skip_route, slice, context)
28
- else
29
- generate_for_app(controller, action, url, http, format, skip_view, skip_route, context)
30
- end
30
+ def call(key:, namespace:, base_path:, url_path:, http_method:, skip_view:, skip_route:)
31
+ insert_route(key:, namespace:, url_path:, http_method:) unless skip_route
32
+
33
+ generate_action(key:, namespace:, base_path:, include_placeholder_body: skip_view)
34
+
35
+ generate_view(key:, namespace:, base_path:) unless skip_view
31
36
  end
32
37
 
33
38
  private
@@ -67,86 +72,76 @@ module Hanami
67
72
  PATH_SEPARATOR = "/"
68
73
  private_constant :PATH_SEPARATOR
69
74
 
70
- attr_reader :fs
71
-
72
- attr_reader :inflector
73
-
74
- # rubocop:disable Metrics/AbcSize
75
- def generate_for_slice(controller, action, url, http, format, skip_view, skip_route, slice, context)
76
- slice_directory = fs.join("slices", slice)
77
- raise MissingSliceError.new(slice) unless fs.directory?(slice_directory)
78
-
79
- if generate_route?(skip_route)
80
- fs.inject_line_at_block_bottom(
81
- fs.join("config", "routes.rb"),
82
- slice_matcher(slice),
83
- route(controller, action, url, http)
84
- )
85
- end
86
-
87
- fs.mkdir(directory = fs.join(slice_directory, "actions", controller))
88
- fs.write(fs.join(directory, "#{action}.rb"), t("slice_action.erb", context))
75
+ attr_reader :fs, :inflector, :out, :view_generator
89
76
 
90
- if generate_view?(skip_view, action, directory)
91
- fs.mkdir(directory = fs.join(slice_directory, "views", controller))
92
- fs.write(fs.join(directory, "#{action}.rb"), t("slice_view.erb", context))
77
+ # @api private
78
+ # @since 2.2.2
79
+ def insert_route(key:, namespace:, url_path:, http_method:)
80
+ routes_location = fs.join("config", "routes.rb")
81
+ route = route_definition(key:, url_path:, http_method:)
93
82
 
94
- fs.mkdir(directory = fs.join(slice_directory, "templates", controller))
95
- fs.write(fs.join(directory, "#{action}.#{format}.erb"),
96
- t(template_with_format_ext("slice_template", format), context))
83
+ if namespace == Hanami.app.namespace
84
+ fs.inject_line_at_class_bottom(routes_location, "class Routes", route)
85
+ else
86
+ slice_matcher = /slice[[:space:]]*:#{namespace}/
87
+ fs.inject_line_at_block_bottom(routes_location, slice_matcher, route)
97
88
  end
98
89
  end
99
90
 
100
- def generate_for_app(controller, action, url, http, format, skip_view, skip_route, context)
101
- if generate_route?(skip_route)
102
- fs.inject_line_at_class_bottom(
103
- fs.join("config", "routes.rb"),
104
- "class Routes",
105
- route(controller, action, url, http)
106
- )
107
- end
108
-
109
- fs.mkdir(directory = fs.join("app", "actions", controller))
110
- fs.write(fs.join(directory, "#{action}.rb"), t("action.erb", context))
91
+ # @api private
92
+ # @since 2.2.2
93
+ def generate_action(key:, namespace:, base_path:, include_placeholder_body:)
94
+ RubyClassFile.new(
95
+ fs: fs,
96
+ inflector: inflector,
97
+ namespace: namespace,
98
+ key: inflector.underscore(key),
99
+ base_path: base_path,
100
+ parent_class_name: "#{inflector.camelize(namespace)}::Action",
101
+ extra_namespace: "Actions",
102
+ body: [
103
+ "def handle(request, response)",
104
+ (" response.body = self.class.name" if include_placeholder_body),
105
+ "end"
106
+ ].compact
107
+ ).create
108
+ end
111
109
 
112
- view = action
113
- view_directory = fs.join("app", "views", controller)
110
+ # @api private
111
+ # @since 2.2.2
112
+ def generate_view(key:, namespace:, base_path:)
113
+ *controller_name_parts, action_name = key.split(KEY_SEPARATOR)
114
114
 
115
- if generate_view?(skip_view, view, view_directory)
116
- fs.mkdir(view_directory)
117
- fs.write(fs.join(view_directory, "#{view}.rb"), t("view.erb", context))
115
+ view_directory = fs.join(base_path, "views", controller_name_parts)
118
116
 
119
- fs.mkdir(template_directory = fs.join("app", "templates", controller))
120
- fs.write(fs.join(template_directory, "#{view}.#{format}.erb"),
121
- t(template_with_format_ext("template", format), context))
117
+ if generate_view?(action_name, view_directory)
118
+ view_generator.call(
119
+ key: key,
120
+ namespace: namespace,
121
+ base_path: base_path,
122
+ )
122
123
  end
123
124
  end
124
- # rubocop:enable Metrics/AbcSize
125
-
126
- def slice_matcher(slice)
127
- /slice[[:space:]]*:#{slice}/
128
- end
129
-
130
- def route(controller, action, url, http)
131
- %(#{route_http(action,
132
- http)} "#{route_url(controller, action, url)}", to: "#{controller.join('.')}.#{action}")
133
- end
134
125
 
135
126
  # @api private
136
- # @since 2.1.0
137
- def generate_view?(skip_view, view, directory)
138
- return false if skip_view
139
- return generate_restful_view?(view, directory) if rest_view?(view)
127
+ # @since 2.2.2
128
+ def route_definition(key:, url_path:, http_method:)
129
+ *controller_name_parts, action_name = key.split(KEY_SEPARATOR)
130
+
131
+ method = route_http(action_name, http_method)
132
+ path = route_url(controller_name_parts, action_name, url_path)
140
133
 
141
- true
134
+ %(#{method} "#{path}", to: "#{key}")
142
135
  end
143
136
 
144
137
  # @api private
145
- # @since 2.2.0
146
- def generate_route?(skip_route)
147
- return false if skip_route
148
-
149
- true
138
+ # @since 2.1.0
139
+ def generate_view?(action_name, directory)
140
+ if rest_view?(action_name)
141
+ generate_restful_view?(action_name, directory)
142
+ else
143
+ true
144
+ end
150
145
  end
151
146
 
152
147
  # @api private
@@ -169,40 +164,22 @@ module Hanami
169
164
  RESTFUL_COUNTERPART_VIEWS.fetch(view, nil)
170
165
  end
171
166
 
172
- def template_with_format_ext(name, format)
173
- ext =
174
- case format.to_sym
175
- when :html
176
- ".html.erb"
177
- else
178
- ".erb"
179
- end
180
-
181
- "#{name}#{ext}"
182
- end
183
-
184
- def template(path, context)
185
- require "erb"
186
-
187
- ERB.new(
188
- File.read(__dir__ + "/action/#{path}"), trim_mode: "-",
189
- ).result(context.ctx)
190
- end
191
-
192
- alias_method :t, :template
193
-
194
- def route_url(controller, action, url)
167
+ # @api private
168
+ # @since 2.1.0
169
+ def route_url(controller, action, url_path)
195
170
  action = ROUTE_RESTFUL_URL_SUFFIXES.fetch(action) { [action] }
196
- url ||= "#{PATH_SEPARATOR}#{(controller + action).join(PATH_SEPARATOR)}"
171
+ url_path ||= "#{PATH_SEPARATOR}#{(controller + action).join(PATH_SEPARATOR)}"
197
172
 
198
- CLI::URL.call(url)
173
+ CLI::URL.call(url_path)
199
174
  end
200
175
 
201
- def route_http(action, http)
202
- result = (http ||= ROUTE_RESTFUL_HTTP_METHODS.fetch(action, ROUTE_DEFAULT_HTTP_METHOD)).downcase
176
+ # @api private
177
+ # @since 2.1.0
178
+ def route_http(action, http_method)
179
+ result = (http_method ||= ROUTE_RESTFUL_HTTP_METHODS.fetch(action, ROUTE_DEFAULT_HTTP_METHOD)).downcase
203
180
 
204
181
  unless ROUTE_HTTP_METHODS.include?(result)
205
- raise UnknownHTTPMethodError.new(http)
182
+ raise UnknownHTTPMethodError.new(http_method)
206
183
  end
207
184
 
208
185
  result
@@ -12,49 +12,27 @@ module Hanami
12
12
  class Component
13
13
  # @api private
14
14
  # @since 2.2.0
15
- def initialize(fs:, inflector:)
15
+ def initialize(fs:, inflector:, out: $stdout)
16
16
  @fs = fs
17
17
  @inflector = inflector
18
+ @out = out
18
19
  end
19
20
 
20
21
  # @api private
21
22
  # @since 2.2.0
22
- def call(app, key, slice)
23
- context = ComponentContext.new(inflector, app, slice, key)
24
-
25
- if slice
26
- generate_for_slice(context, slice)
27
- else
28
- generate_for_app(context)
29
- end
23
+ def call(key:, namespace:, base_path:)
24
+ RubyClassFile.new(
25
+ fs: fs,
26
+ inflector: inflector,
27
+ key: inflector.underscore(key),
28
+ namespace: namespace,
29
+ base_path: base_path,
30
+ ).create
30
31
  end
31
32
 
32
33
  private
33
34
 
34
- attr_reader :fs
35
-
36
- attr_reader :inflector
37
-
38
- def generate_for_slice(context, slice)
39
- slice_directory = fs.join("slices", slice)
40
- raise MissingSliceError.new(slice) unless fs.directory?(slice_directory)
41
-
42
- fs.mkdir(directory = fs.join(slice_directory, context.namespaces))
43
- fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("slice_component.erb", context))
44
- end
45
-
46
- def generate_for_app(context)
47
- fs.mkdir(directory = fs.join("app", context.namespaces))
48
- fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("component.erb", context))
49
- end
50
-
51
- def template(path, context)
52
- ERB.new(
53
- File.read(__dir__ + "/component/#{path}")
54
- ).result(context.ctx)
55
- end
56
-
57
- alias_method :t, :template
35
+ attr_reader :fs, :inflector, :out
58
36
  end
59
37
  end
60
38
  end