hanami 1.0.0 → 1.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/FEATURES.md +12 -0
  4. data/README.md +3 -0
  5. data/bin/hanami +3 -3
  6. data/hanami.gemspec +9 -9
  7. data/lib/hanami.rb +27 -0
  8. data/lib/hanami/action/routing_helpers.rb +1 -1
  9. data/lib/hanami/application_configuration.rb +0 -8
  10. data/lib/hanami/application_name.rb +1 -3
  11. data/lib/hanami/application_namespace.rb +1 -1
  12. data/lib/hanami/cli/commands.rb +101 -0
  13. data/lib/hanami/cli/commands/assets.rb +16 -0
  14. data/lib/hanami/cli/commands/assets/precompile.rb +42 -0
  15. data/lib/hanami/cli/commands/command.rb +180 -0
  16. data/lib/hanami/cli/commands/console.rb +95 -0
  17. data/lib/hanami/cli/commands/db.rb +30 -0
  18. data/lib/hanami/cli/commands/db/apply.rb +32 -0
  19. data/lib/hanami/cli/commands/db/console.rb +44 -0
  20. data/lib/hanami/cli/commands/db/create.rb +32 -0
  21. data/lib/hanami/cli/commands/db/drop.rb +32 -0
  22. data/lib/hanami/cli/commands/db/migrate.rb +39 -0
  23. data/lib/hanami/cli/commands/db/prepare.rb +32 -0
  24. data/lib/hanami/cli/commands/db/version.rb +32 -0
  25. data/lib/hanami/cli/commands/destroy.rb +26 -0
  26. data/lib/hanami/cli/commands/destroy/action.rb +131 -0
  27. data/lib/hanami/cli/commands/destroy/app.rb +159 -0
  28. data/lib/hanami/cli/commands/destroy/mailer.rb +74 -0
  29. data/lib/hanami/cli/commands/destroy/migration.rb +51 -0
  30. data/lib/hanami/cli/commands/destroy/model.rb +84 -0
  31. data/lib/hanami/cli/commands/generate.rb +28 -0
  32. data/lib/hanami/cli/commands/generate/action.rb +224 -0
  33. data/lib/hanami/cli/commands/generate/action/action.erb +8 -0
  34. data/lib/hanami/cli/commands/generate/action/action_spec.minitest.erb +11 -0
  35. data/lib/hanami/{generators/action/action_spec.rspec.tt → cli/commands/generate/action/action_spec.rspec.erb} +1 -3
  36. data/lib/hanami/cli/commands/generate/action/action_without_view.erb +9 -0
  37. data/lib/hanami/cli/commands/generate/action/view.erb +5 -0
  38. data/lib/hanami/cli/commands/generate/action/view_spec.minitest.erb +15 -0
  39. data/lib/hanami/{generators/action/view_spec.rspec.tt → cli/commands/generate/action/view_spec.rspec.erb} +2 -4
  40. data/lib/hanami/cli/commands/generate/app.rb +231 -0
  41. data/lib/hanami/{generators/app/application.rb.tt → cli/commands/generate/app/application.erb} +6 -6
  42. data/lib/hanami/{generators → cli/commands/generate}/app/favicon.ico +0 -0
  43. data/lib/hanami/{generators/action/template.tt → cli/commands/generate/app/gitkeep.erb} +0 -0
  44. data/lib/hanami/cli/commands/generate/app/layout.erb +7 -0
  45. data/lib/hanami/cli/commands/generate/app/layout_spec.minitest.erb +11 -0
  46. data/lib/hanami/cli/commands/generate/app/layout_spec.rspec.erb +11 -0
  47. data/lib/hanami/{generators/app/config/routes.rb.tt → cli/commands/generate/app/routes.erb} +0 -0
  48. data/lib/hanami/{generators/app/templates/application.html.erb.tt → cli/commands/generate/app/template.erb.erb} +1 -1
  49. data/lib/hanami/{generators/app/templates/application.html.haml.tt → cli/commands/generate/app/template.haml.erb} +1 -1
  50. data/lib/hanami/{generators/app/templates/application.html.slim.tt → cli/commands/generate/app/template.slim.erb} +1 -1
  51. data/lib/hanami/cli/commands/generate/mailer.rb +94 -0
  52. data/lib/hanami/cli/commands/generate/mailer/mailer.erb +7 -0
  53. data/lib/hanami/cli/commands/generate/mailer/mailer_spec.minitest.erb +7 -0
  54. data/lib/hanami/cli/commands/generate/mailer/mailer_spec.rspec.erb +5 -0
  55. data/lib/hanami/cli/commands/generate/migration.rb +41 -0
  56. data/lib/hanami/{generators/migration/migration.rb.tt → cli/commands/generate/migration/migration.erb} +0 -0
  57. data/lib/hanami/cli/commands/generate/model.rb +98 -0
  58. data/lib/hanami/cli/commands/generate/model/entity.erb +2 -0
  59. data/lib/hanami/cli/commands/generate/model/entity_spec.minitest.erb +5 -0
  60. data/lib/hanami/cli/commands/generate/model/entity_spec.rspec.erb +3 -0
  61. data/lib/hanami/{generators/model/migration.rb.tt → cli/commands/generate/model/migration.erb} +1 -1
  62. data/lib/hanami/cli/commands/generate/model/repository.erb +2 -0
  63. data/lib/hanami/cli/commands/generate/model/repository_spec.minitest.erb +5 -0
  64. data/lib/hanami/cli/commands/generate/model/repository_spec.rspec.erb +3 -0
  65. data/lib/hanami/cli/commands/generate/secret.rb +48 -0
  66. data/lib/hanami/cli/commands/new.rb +575 -0
  67. data/lib/hanami/{generators/application/container/.env.development.tt → cli/commands/new/.env.development.erb} +1 -1
  68. data/lib/hanami/{generators/application/container/.env.test.tt → cli/commands/new/.env.test.erb} +1 -1
  69. data/lib/hanami/{generators/app/config/initializers/.gitkeep → cli/commands/new/.gitkeep.erb} +0 -0
  70. data/lib/hanami/cli/commands/new/Gemfile.erb +57 -0
  71. data/lib/hanami/cli/commands/new/README.md.erb +35 -0
  72. data/lib/hanami/{generators/application/container/config.ru.tt → cli/commands/new/config.ru.erb} +0 -0
  73. data/lib/hanami/{generators/application/container/config/boot.rb.tt → cli/commands/new/config/boot.erb} +0 -0
  74. data/lib/hanami/{generators/application/container/config/environment.rb.tt → cli/commands/new/config/environment.erb} +8 -8
  75. data/lib/hanami/{generators/application/container/gitignore.tt → cli/commands/new/gitignore.erb} +0 -0
  76. data/lib/hanami/{generators/application/container/gitignore_with_sqlite.tt → cli/commands/new/gitignore_with_sqlite.erb} +0 -0
  77. data/lib/hanami/cli/commands/new/hanamirc.erb +3 -0
  78. data/lib/hanami/cli/commands/new/lib/project.erb +2 -0
  79. data/lib/hanami/{generators/application/container/Rakefile.minitest.tt → cli/commands/new/minitest/Rakefile.erb} +0 -0
  80. data/lib/hanami/{generators/application/container/features_helper.rb.minitest.tt → cli/commands/new/minitest/features_helper.erb} +0 -0
  81. data/lib/hanami/{generators/application/container/spec_helper.rb.minitest.tt → cli/commands/new/minitest/spec_helper.erb} +0 -0
  82. data/lib/hanami/{generators/application/container/Rakefile.rspec.tt → cli/commands/new/rspec/Rakefile.erb} +0 -0
  83. data/lib/hanami/{generators/application/container/capybara.rb.rspec.tt → cli/commands/new/rspec/capybara.erb} +0 -0
  84. data/lib/hanami/{generators/application/container/features_helper.rb.rspec.tt → cli/commands/new/rspec/features_helper.erb} +0 -0
  85. data/lib/hanami/{generators/application/container/rspec.rspec.tt → cli/commands/new/rspec/rspec.erb} +0 -0
  86. data/lib/hanami/{generators/application/container/spec_helper.rb.rspec.tt → cli/commands/new/rspec/spec_helper.erb} +0 -0
  87. data/lib/hanami/{generators/application/container/.gitignore → cli/commands/new/schema.sql.erb} +0 -0
  88. data/lib/hanami/cli/commands/project.rb +421 -0
  89. data/lib/hanami/cli/commands/routes.rb +21 -0
  90. data/lib/hanami/cli/commands/server.rb +39 -0
  91. data/lib/hanami/cli/commands/templates.rb +31 -0
  92. data/lib/hanami/cli/commands/version.rb +19 -0
  93. data/lib/hanami/common_logger.rb +14 -4
  94. data/lib/hanami/components.rb +39 -1
  95. data/lib/hanami/components/components.rb +36 -1
  96. data/lib/hanami/config/sessions.rb +1 -1
  97. data/lib/hanami/environment.rb +20 -50
  98. data/lib/hanami/hanamirc.rb +7 -30
  99. data/lib/hanami/middleware.rb +0 -26
  100. data/lib/hanami/static.rb +1 -1
  101. data/lib/hanami/templates/welcome.html.erb +1 -1
  102. data/lib/hanami/version.rb +1 -1
  103. data/lib/hanami/welcome.rb +1 -11
  104. metadata +125 -135
  105. data/lib/hanami/cli.rb +0 -154
  106. data/lib/hanami/cli_base.rb +0 -49
  107. data/lib/hanami/cli_sub_commands/assets.rb +0 -24
  108. data/lib/hanami/cli_sub_commands/db.rb +0 -138
  109. data/lib/hanami/cli_sub_commands/destroy.rb +0 -115
  110. data/lib/hanami/cli_sub_commands/generate.rb +0 -150
  111. data/lib/hanami/commands/apps.rb +0 -4
  112. data/lib/hanami/commands/assets/precompile.rb +0 -26
  113. data/lib/hanami/commands/command.rb +0 -77
  114. data/lib/hanami/commands/console.rb +0 -107
  115. data/lib/hanami/commands/db/apply.rb +0 -19
  116. data/lib/hanami/commands/db/console.rb +0 -41
  117. data/lib/hanami/commands/db/create.rb +0 -19
  118. data/lib/hanami/commands/db/drop.rb +0 -19
  119. data/lib/hanami/commands/db/migrate.rb +0 -30
  120. data/lib/hanami/commands/db/prepare.rb +0 -19
  121. data/lib/hanami/commands/db/version.rb +0 -19
  122. data/lib/hanami/commands/generate/abstract.rb +0 -76
  123. data/lib/hanami/commands/generate/action.rb +0 -322
  124. data/lib/hanami/commands/generate/app.rb +0 -154
  125. data/lib/hanami/commands/generate/mailer.rb +0 -131
  126. data/lib/hanami/commands/generate/migration.rb +0 -75
  127. data/lib/hanami/commands/generate/model.rb +0 -132
  128. data/lib/hanami/commands/generate/secret_token.rb +0 -37
  129. data/lib/hanami/commands/new/abstract.rb +0 -178
  130. data/lib/hanami/commands/new/app.rb +0 -129
  131. data/lib/hanami/commands/new/container.rb +0 -119
  132. data/lib/hanami/commands/routes.rb +0 -26
  133. data/lib/hanami/commands/server.rb +0 -32
  134. data/lib/hanami/generators/action/action.rb.tt +0 -8
  135. data/lib/hanami/generators/action/action_spec.minitest.tt +0 -12
  136. data/lib/hanami/generators/action/action_without_view.rb.tt +0 -9
  137. data/lib/hanami/generators/action/view.rb.tt +0 -5
  138. data/lib/hanami/generators/action/view_spec.minitest.tt +0 -16
  139. data/lib/hanami/generators/app/.gitkeep.tt +0 -1
  140. data/lib/hanami/generators/app/views/application_layout.rb.tt +0 -7
  141. data/lib/hanami/generators/application/container/.gitkeep +0 -1
  142. data/lib/hanami/generators/application/container/Gemfile.tt +0 -56
  143. data/lib/hanami/generators/application/container/config/initializers/.gitkeep +0 -0
  144. data/lib/hanami/generators/application/container/db/.gitkeep +0 -1
  145. data/lib/hanami/generators/application/container/hanamirc.tt +0 -4
  146. data/lib/hanami/generators/application/container/lib/chirp/entities/.gitkeep +0 -1
  147. data/lib/hanami/generators/application/container/lib/chirp/mailers/.gitkeep +0 -0
  148. data/lib/hanami/generators/application/container/lib/chirp/mailers/templates/.gitkeep +0 -0
  149. data/lib/hanami/generators/application/container/lib/chirp/repositories/.gitkeep +0 -1
  150. data/lib/hanami/generators/application/container/lib/project.rb.tt +0 -2
  151. data/lib/hanami/generators/application/container/schema.sql.tt +0 -0
  152. data/lib/hanami/generators/database_config.rb +0 -114
  153. data/lib/hanami/generators/generatable.rb +0 -69
  154. data/lib/hanami/generators/generator.rb +0 -52
  155. data/lib/hanami/generators/mailer/mailer.rb.tt +0 -7
  156. data/lib/hanami/generators/mailer/mailer_spec.rb.minitest.tt +0 -7
  157. data/lib/hanami/generators/mailer/mailer_spec.rb.rspec.tt +0 -5
  158. data/lib/hanami/generators/mailer/template.html.tt +0 -0
  159. data/lib/hanami/generators/mailer/template.txt.tt +0 -0
  160. data/lib/hanami/generators/model/entity.rb.tt +0 -2
  161. data/lib/hanami/generators/model/entity_spec.minitest.tt +0 -5
  162. data/lib/hanami/generators/model/entity_spec.rspec.tt +0 -3
  163. data/lib/hanami/generators/model/repository.rb.tt +0 -2
  164. data/lib/hanami/generators/model/repository_spec.minitest.tt +0 -5
  165. data/lib/hanami/generators/model/repository_spec.rspec.tt +0 -3
  166. data/lib/hanami/generators/template_engine.rb +0 -44
  167. data/lib/hanami/generators/test_framework.rb +0 -55
@@ -0,0 +1,159 @@
1
+ module Hanami
2
+ class CLI
3
+ module Commands
4
+ module Destroy
5
+ # @since 1.1.0
6
+ # @api private
7
+ class App < Command
8
+ desc "Destroy an app"
9
+
10
+ argument :app, required: true, desc: "The application name (eg. `web`)"
11
+
12
+ example [
13
+ "admin # Destroy `admin` app"
14
+ ]
15
+
16
+ # @since 1.1.0
17
+ # @api private
18
+ def call(app:, **options) # rubocop:disable Metrics/MethodLength
19
+ app = Utils::String.underscore(app)
20
+ context = Context.new(app: app, options: options)
21
+
22
+ assert_valid_app!(context)
23
+ context = context.with(base_url: base_url(context))
24
+
25
+ remove_test_http_session_secret(context)
26
+ remove_development_http_session_secret(context)
27
+
28
+ remove_mount_app(context)
29
+ remove_require_app(context)
30
+
31
+ recursively_destroy_precompiled_assets(context)
32
+ destroy_assets_manifest(context)
33
+
34
+ recursively_destroy_specs(context)
35
+ recursively_destroy_app(context)
36
+ end
37
+
38
+ private
39
+
40
+ # @since 1.1.0
41
+ # @api private
42
+ def assert_valid_app!(context)
43
+ return if project.app?(context)
44
+
45
+ existing_apps = project.apps.map { |name| "`#{name}'" }.join(' ')
46
+ warn "`#{context.app}' is not a valid APP. Please specify one of: #{existing_apps}"
47
+ exit(1)
48
+ end
49
+
50
+ # @since 1.1.0
51
+ # @api private
52
+ def remove_test_http_session_secret(context)
53
+ content = "#{context.app.upcase}_SESSIONS_SECRET"
54
+ destination = project.env(context, "test")
55
+
56
+ files.remove_line(destination, content)
57
+ say(:subtract, destination)
58
+ end
59
+
60
+ # @since 1.1.0
61
+ # @api private
62
+ def remove_development_http_session_secret(context)
63
+ content = "#{context.app.upcase}_SESSIONS_SECRET"
64
+ destination = project.env(context, "development")
65
+
66
+ files.remove_line(destination, content)
67
+ say(:subtract, destination)
68
+ end
69
+
70
+ # @since 1.1.0
71
+ # @api private
72
+ def remove_mount_app(context)
73
+ content = "mount #{context.app.classify}::Application"
74
+ destination = project.environment(context)
75
+
76
+ files.remove_line(destination, content)
77
+ say(:subtract, destination)
78
+ end
79
+
80
+ # @since 1.1.0
81
+ # @api private
82
+ def remove_require_app(context)
83
+ content = "require_relative '../apps/#{context.app}/application'"
84
+ destination = project.environment(context)
85
+
86
+ files.remove_line(destination, content)
87
+ say(:subtract, destination)
88
+ end
89
+
90
+ # @since 1.1.0
91
+ # @api private
92
+ def recursively_destroy_precompiled_assets(context)
93
+ destination = project.public_app_assets(context)
94
+ return unless files.directory?(destination)
95
+
96
+ files.delete_directory(destination)
97
+ say(:remove, destination)
98
+ end
99
+
100
+ # @since 1.1.0
101
+ # @api private
102
+ def destroy_assets_manifest(context)
103
+ destination = project.assets_manifest(context)
104
+ return unless files.exist?(destination)
105
+
106
+ files.delete(destination)
107
+ say(:remove, destination)
108
+ end
109
+
110
+ # @since 1.1.0
111
+ # @api private
112
+ def recursively_destroy_specs(context)
113
+ destination = project.app_spec(context)
114
+
115
+ files.delete_directory(destination)
116
+ say(:remove, destination)
117
+ end
118
+
119
+ # @since 1.1.0
120
+ # @api private
121
+ def recursively_destroy_app(context)
122
+ destination = project.app(context)
123
+
124
+ files.delete_directory(destination)
125
+ say(:remove, destination)
126
+ end
127
+
128
+ # @since 1.1.0
129
+ # @api private
130
+ def base_url(context)
131
+ content = "mount #{context.app.classify}::Application"
132
+ destination = project.environment(context)
133
+
134
+ line = read_matching_line(destination, content)
135
+ *, at = line.split(/at\:[[:space:]]*/)
136
+
137
+ at.strip.gsub(/["']*/, "")
138
+ end
139
+
140
+ # @since 1.1.0
141
+ # @api private
142
+ def read_matching_line(path, target)
143
+ content = ::File.readlines(path)
144
+ line = content.find do |l|
145
+ case target
146
+ when String
147
+ l.include?(target)
148
+ when Regexp
149
+ l =~ target
150
+ end
151
+ end
152
+
153
+ line or raise ArgumentError.new("Cannot find `#{target}' inside `#{path}'.")
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,74 @@
1
+ module Hanami
2
+ class CLI
3
+ module Commands
4
+ module Destroy
5
+ # @since 1.1.0
6
+ # @api private
7
+ class Mailer < Command
8
+ requires "environment"
9
+ desc "Destroy a mailer"
10
+
11
+ argument :mailer, required: true, desc: "The mailer name (eg. `welcome`)"
12
+
13
+ example [
14
+ "welcome # Destroy `WelcomeMailer` mailer"
15
+ ]
16
+
17
+ # @since 1.1.0
18
+ # @api private
19
+ def call(mailer:, **options)
20
+ mailer = Utils::String.transform(mailer, :underscore, :singularize)
21
+ context = Context.new(mailer: mailer, options: options)
22
+
23
+ assert_valid_mailer!(context)
24
+
25
+ destroy_mailer_spec(context)
26
+ destroy_templates(context)
27
+ destroy_mailer(context)
28
+ end
29
+
30
+ private
31
+
32
+ # @since 1.1.0
33
+ # @api private
34
+ def assert_valid_mailer!(context)
35
+ destination = project.mailer(context)
36
+ return if files.exist?(destination)
37
+
38
+ destination = project.mailers(context)
39
+ warn "cannot find `#{context.mailer}' mailer. Please have a look at `#{destination}' directory to find an existing mailer."
40
+ exit(1)
41
+ end
42
+
43
+ # @since 1.1.0
44
+ # @api private
45
+ def destroy_mailer_spec(context)
46
+ destination = project.mailer_spec(context)
47
+
48
+ files.delete(destination)
49
+ say(:remove, destination)
50
+ end
51
+
52
+ # @since 1.1.0
53
+ # @api private
54
+ def destroy_templates(context)
55
+ destinations = project.mailer_templates(context)
56
+ destinations.each do |destination|
57
+ files.delete(destination)
58
+ say(:remove, destination)
59
+ end
60
+ end
61
+
62
+ # @since 1.1.0
63
+ # @api private
64
+ def destroy_mailer(context)
65
+ destination = project.mailer(context)
66
+
67
+ files.delete(destination)
68
+ say(:remove, destination)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,51 @@
1
+ module Hanami
2
+ class CLI
3
+ module Commands
4
+ module Destroy
5
+ # @since 1.1.0
6
+ # @api private
7
+ class Migration < Command
8
+ desc "Destroy a migration"
9
+
10
+ argument :migration, required: true, desc: "The migration name (eg. `create_users`)"
11
+
12
+ example [
13
+ "create_users # Destroy `db/migrations/#{Project.migration_timestamp}_create_users.rb`"
14
+ ]
15
+
16
+ # @since 1.1.0
17
+ # @api private
18
+ def call(migration:, **options)
19
+ migration = Utils::String.underscore(migration)
20
+ context = Context.new(migration: migration, options: options)
21
+ context = context.with(destination: project.find_migration(context))
22
+
23
+ assert_valid_migration!(context)
24
+
25
+ destroy_migration(context)
26
+ true
27
+ end
28
+
29
+ private
30
+
31
+ # @since 1.1.0
32
+ # @api private
33
+ def assert_valid_migration!(context)
34
+ return if !context.destination.nil? && files.exist?(context.destination)
35
+
36
+ destination = project.migrations(context)
37
+ warn "cannot find `#{context.migration}'. Please have a look at `#{destination}' directory to find an existing migration"
38
+ exit(1)
39
+ end
40
+
41
+ # @since 1.1.0
42
+ # @api private
43
+ def destroy_migration(context)
44
+ files.delete(context.destination)
45
+ say(:remove, context.destination)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,84 @@
1
+ module Hanami
2
+ class CLI
3
+ module Commands
4
+ module Destroy
5
+ # @since 1.1.0
6
+ # @api private
7
+ class Model < Command
8
+ requires "environment"
9
+
10
+ desc "Destroy a model"
11
+
12
+ argument :model, required: true, desc: "The model name (eg. `user`)"
13
+
14
+ example [
15
+ "user # Destroy `User` entity and `UserRepository` repository"
16
+ ]
17
+
18
+ # @since 1.1.0
19
+ # @api private
20
+ def call(model:, **options)
21
+ model = Utils::String.transform(model, :underscore, :singularize)
22
+ context = Context.new(model: model, options: options)
23
+
24
+ assert_valid_model!(context)
25
+
26
+ destroy_repository_spec(context)
27
+ destroy_entity_spec(context)
28
+ destroy_repository(context)
29
+ destroy_entity(context)
30
+ end
31
+
32
+ private
33
+
34
+ # @since 1.1.0
35
+ # @api private
36
+ def assert_valid_model!(context)
37
+ destination = project.entity(context)
38
+ return if files.exist?(destination)
39
+
40
+ destination = project.entities(context)
41
+ warn "cannot find `#{context.model}' model. Please have a look at `#{destination}' directory to find an existing model."
42
+ exit(1)
43
+ end
44
+
45
+ # @since 1.1.0
46
+ # @api private
47
+ def destroy_repository_spec(context)
48
+ destination = project.repository_spec(context)
49
+
50
+ files.delete(destination)
51
+ say(:remove, destination)
52
+ end
53
+
54
+ # @since 1.1.0
55
+ # @api private
56
+ def destroy_entity_spec(context)
57
+ destination = project.entity_spec(context)
58
+
59
+ files.delete(destination)
60
+ say(:remove, destination)
61
+ end
62
+
63
+ # @since 1.1.0
64
+ # @api private
65
+ def destroy_repository(context)
66
+ destination = project.repository(context)
67
+
68
+ files.delete(destination)
69
+ say(:remove, destination)
70
+ end
71
+
72
+ # @since 1.1.0
73
+ # @api private
74
+ def destroy_entity(context)
75
+ destination = project.entity(context)
76
+
77
+ files.delete(destination)
78
+ say(:remove, destination)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,28 @@
1
+ module Hanami
2
+ # Hanami CLI
3
+ #
4
+ # @since 1.1.0
5
+ class CLI
6
+ module Commands
7
+ # @since 1.1.0
8
+ # @api private
9
+ module Generate
10
+ require "hanami/cli/commands/generate/app"
11
+ require "hanami/cli/commands/generate/action"
12
+ require "hanami/cli/commands/generate/mailer"
13
+ require "hanami/cli/commands/generate/migration"
14
+ require "hanami/cli/commands/generate/model"
15
+ require "hanami/cli/commands/generate/secret"
16
+ end
17
+ end
18
+
19
+ register "generate", aliases: ["g"] do |prefix|
20
+ prefix.register "app", Commands::Generate::App
21
+ prefix.register "action", Commands::Generate::Action
22
+ prefix.register "mailer", Commands::Generate::Mailer
23
+ prefix.register "migration", Commands::Generate::Migration
24
+ prefix.register "model", Commands::Generate::Model
25
+ prefix.register "secret", Commands::Generate::Secret
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,224 @@
1
+ module Hanami
2
+ class CLI
3
+ module Commands
4
+ module Generate
5
+ # @since 1.1.0
6
+ # @api private
7
+ class Action < Command
8
+ requires "environment"
9
+
10
+ desc "Generate an action for app"
11
+
12
+ example [
13
+ "web home#index # Basic usage",
14
+ "admin home#index # Generate for `admin` app",
15
+ "web home#index --url=/ # Specify URL",
16
+ "web sessions#destroy --method=GET # Specify HTTP method",
17
+ "web books#create --skip-view # Skip view and template"
18
+ ]
19
+
20
+ argument :app, required: true, desc: "The application name (eg. `web`)"
21
+ argument :action, required: true, desc: "The action name (eg. `home#index`)"
22
+
23
+ option :url, desc: "The action URL"
24
+ option :method, desc: "The action HTTP method"
25
+ option :skip_view, type: :boolean, default: false, desc: "Skip view and template"
26
+
27
+ # @since 1.1.0
28
+ # @api private
29
+ #
30
+ # rubocop:disable Metrics/AbcSize
31
+ # rubocop:disable Metrics/MethodLength
32
+ def call(app:, action:, **options)
33
+ controller, action = controller_and_action_name(action)
34
+ http_method = route_http_method(action, options)
35
+ context = Context.new(app: app, controller: controller, action: action, test: options.fetch(:test), http_method: http_method, options: options)
36
+ context = context.with(template: project.template(context))
37
+
38
+ assert_valid_app!(context)
39
+ assert_valid_route_url!(context)
40
+ assert_valid_route_http_method!(context)
41
+
42
+ generate_action(context)
43
+ generate_view(context)
44
+ generate_template(context)
45
+ generate_action_spec(context)
46
+ generate_view_spec(context)
47
+ insert_route(context)
48
+ end
49
+ # rubocop:enable Metrics/MethodLength
50
+ # rubocop:enable Metrics/AbcSize
51
+
52
+ private
53
+
54
+ # @since 1.1.0
55
+ # @api private
56
+ def controller_and_action_name(name)
57
+ # FIXME: extract this regexp
58
+ name.split(/#|\//)
59
+ end
60
+
61
+ # @since 1.1.0
62
+ # @api private
63
+ def assert_valid_app!(context)
64
+ return if project.app?(context)
65
+
66
+ existing_apps = project.apps.map { |name| "`#{name}'" }.join(' ')
67
+ warn "`#{context.app}' is not a valid APP. Please specify one of: #{existing_apps}"
68
+ exit(1)
69
+ end
70
+
71
+ # @since 1.1.0
72
+ # @api private
73
+ def assert_valid_route_url!(context)
74
+ if context.options.key?(:url) && Utils::Blank.blank?(context.options[:url]) # rubocop:disable Style/GuardClause
75
+ warn "`#{context.options[:url]}' is not a valid URL"
76
+ exit(1)
77
+ end
78
+ end
79
+
80
+ # @since 1.1.0
81
+ # @api private
82
+ def assert_valid_route_http_method!(context)
83
+ unless Hanami::Routing::Route::VALID_HTTP_VERBS.include?(context.http_method.upcase) # rubocop:disable Style/GuardClause
84
+ warn "`#{context.http_method.upcase}' is not a valid HTTP method. Please use one of: #{Hanami::Routing::Route::VALID_HTTP_VERBS.map { |verb| "`#{verb}'" }.join(' ')}"
85
+ exit(1)
86
+ end
87
+ end
88
+
89
+ # @since 1.1.0
90
+ # @api private
91
+ def generate_action(context)
92
+ source = if skip_view?(context)
93
+ templates.find("action_without_view.erb")
94
+ else
95
+ templates.find("action.erb")
96
+ end
97
+ destination = project.action(context)
98
+
99
+ generate_file(source, destination, context)
100
+ say(:create, destination)
101
+ end
102
+
103
+ # @since 1.1.0
104
+ # @api private
105
+ def generate_view(context)
106
+ return if skip_view?(context)
107
+
108
+ source = templates.find("view.erb")
109
+ destination = project.view(context)
110
+
111
+ generate_file(source, destination, context)
112
+ say(:create, destination)
113
+ end
114
+
115
+ # @since 1.1.0
116
+ # @api private
117
+ def generate_template(context)
118
+ return if skip_view?(context)
119
+ destination = project.template(context)
120
+
121
+ files.touch(destination)
122
+ say(:create, destination)
123
+ end
124
+
125
+ # @since 1.1.0
126
+ # @api private
127
+ def generate_action_spec(context)
128
+ source = templates.find("action_spec.#{context.test}.erb")
129
+ destination = project.action_spec(context)
130
+
131
+ generate_file(source, destination, context)
132
+ say(:create, destination)
133
+ end
134
+
135
+ # @since 1.1.0
136
+ # @api private
137
+ def generate_view_spec(context)
138
+ return if skip_view?(context)
139
+
140
+ source = templates.find("view_spec.#{context.test}.erb")
141
+ destination = project.view_spec(context)
142
+
143
+ generate_file(source, destination, context)
144
+ say(:create, destination)
145
+ end
146
+
147
+ # @since 1.1.0
148
+ # @api private
149
+ def insert_route(context)
150
+ content = "#{context.http_method} '#{route_url(context)}', to: '#{route_endpoint(context)}'".downcase
151
+ destination = project.app_routes(context)
152
+
153
+ files.append(destination, content)
154
+ say(:insert, destination)
155
+ end
156
+
157
+ # @since 1.1.0
158
+ # @api private
159
+ def route_http_method(action, options)
160
+ options.fetch(:method) { route_resourceful_http_method(action) }
161
+ end
162
+
163
+ # @since 1.1.0
164
+ # @api private
165
+ def skip_view?(context)
166
+ context.options.fetch(:skip_view, false)
167
+ end
168
+
169
+ # @since 1.1.0
170
+ # @api private
171
+ DEFAULT_HTTP_METHOD = 'GET'.freeze
172
+
173
+ # @since 1.1.0
174
+ # @api private
175
+ RESOURCEFUL_HTTP_METHODS = {
176
+ 'create' => 'POST',
177
+ 'update' => 'PATCH',
178
+ 'destroy' => 'DELETE'
179
+ }.freeze
180
+
181
+ # @since 1.1.0
182
+ # @api private
183
+ def route_resourceful_http_method(action)
184
+ RESOURCEFUL_HTTP_METHODS.fetch(action, DEFAULT_HTTP_METHOD)
185
+ end
186
+
187
+ # @since 1.1.0
188
+ # @api private
189
+ def route_url(context)
190
+ context.options.fetch(:url) { route_resourceful_url(context) }
191
+ end
192
+
193
+ # @since 1.1.0
194
+ # @api private
195
+ def route_resourceful_url(context)
196
+ "/#{context.controller}#{route_resourceful_url_suffix(context)}"
197
+ end
198
+
199
+ # @since 1.1.0
200
+ # @api private
201
+ RESOURCEFUL_ROUTE_URL_SUFFIXES = {
202
+ 'show' => '/:id',
203
+ 'update' => '/:id',
204
+ 'destroy' => '/:id',
205
+ 'new' => '/new',
206
+ 'edit' => '/:id/edit'
207
+ }.freeze
208
+
209
+ # @since 1.1.0
210
+ # @api private
211
+ def route_resourceful_url_suffix(context)
212
+ RESOURCEFUL_ROUTE_URL_SUFFIXES.fetch(context.action) { "" }
213
+ end
214
+
215
+ # @since 1.1.0
216
+ # @api private
217
+ def route_endpoint(context)
218
+ "#{context.controller}##{context.action}"
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end