hanami-cli 2.2.0 → 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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -0
  3. data/CHANGELOG.md +43 -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/install.rb +12 -0
  21. data/lib/hanami/cli/commands/app/server.rb +0 -1
  22. data/lib/hanami/cli/commands/app.rb +1 -0
  23. data/lib/hanami/cli/commands/gem/new.rb +37 -7
  24. data/lib/hanami/cli/errors.rb +26 -0
  25. data/lib/hanami/cli/files.rb +20 -6
  26. data/lib/hanami/cli/generators/app/action.rb +78 -101
  27. data/lib/hanami/cli/generators/app/component.rb +11 -33
  28. data/lib/hanami/cli/generators/app/migration.rb +1 -1
  29. data/lib/hanami/cli/generators/app/operation.rb +4 -5
  30. data/lib/hanami/cli/generators/app/part.rb +42 -65
  31. data/lib/hanami/cli/generators/app/relation.rb +4 -5
  32. data/lib/hanami/cli/generators/app/repo.rb +3 -5
  33. data/lib/hanami/cli/generators/app/ruby_class_file.rb +32 -0
  34. data/lib/hanami/cli/generators/app/ruby_file.rb +128 -0
  35. data/lib/hanami/cli/generators/app/ruby_module_file.rb +28 -0
  36. data/lib/hanami/cli/generators/app/slice.rb +130 -37
  37. data/lib/hanami/cli/generators/app/struct.rb +3 -4
  38. data/lib/hanami/cli/generators/app/view.rb +40 -45
  39. data/lib/hanami/cli/generators/context.rb +6 -0
  40. data/lib/hanami/cli/generators/gem/app/assets.js +14 -13
  41. data/lib/hanami/cli/generators/gem/app/dev +1 -1
  42. data/lib/hanami/cli/generators/gem/app/gemfile.erb +5 -0
  43. data/lib/hanami/cli/generators/gem/app/gitignore.erb +3 -1
  44. data/lib/hanami/cli/generators/gem/app/rakefile.erb +3 -0
  45. data/lib/hanami/cli/generators/gem/app/readme.erb +14 -0
  46. data/lib/hanami/cli/generators/gem/app.rb +40 -37
  47. data/lib/hanami/cli/ruby_file_generator.rb +17 -8
  48. data/lib/hanami/cli/server.rb +15 -1
  49. data/lib/hanami/cli/version.rb +1 -1
  50. data/lib/hanami/console/context.rb +5 -0
  51. metadata +35 -41
  52. data/lib/hanami/cli/generators/app/action/action.erb +0 -17
  53. data/lib/hanami/cli/generators/app/action/slice_action.erb +0 -17
  54. data/lib/hanami/cli/generators/app/action/slice_template.html.erb +0 -1
  55. data/lib/hanami/cli/generators/app/action/slice_view.erb +0 -10
  56. data/lib/hanami/cli/generators/app/action/template.erb +0 -0
  57. data/lib/hanami/cli/generators/app/action/template.html.erb +0 -1
  58. data/lib/hanami/cli/generators/app/action/view.erb +0 -10
  59. data/lib/hanami/cli/generators/app/action_context.rb +0 -90
  60. data/lib/hanami/cli/generators/app/component/component.erb +0 -8
  61. data/lib/hanami/cli/generators/app/component/slice_component.erb +0 -8
  62. data/lib/hanami/cli/generators/app/component_context.rb +0 -82
  63. data/lib/hanami/cli/generators/app/part/app_base_part.erb +0 -9
  64. data/lib/hanami/cli/generators/app/part/app_part.erb +0 -13
  65. data/lib/hanami/cli/generators/app/part/slice_base_part.erb +0 -9
  66. data/lib/hanami/cli/generators/app/part/slice_part.erb +0 -13
  67. data/lib/hanami/cli/generators/app/part_context.rb +0 -82
  68. data/lib/hanami/cli/generators/app/ruby_file_writer.rb +0 -151
  69. data/lib/hanami/cli/generators/app/slice/action.erb +0 -7
  70. data/lib/hanami/cli/generators/app/slice/app_css.erb +0 -5
  71. data/lib/hanami/cli/generators/app/slice/app_js.erb +0 -1
  72. data/lib/hanami/cli/generators/app/slice/app_layout.erb +0 -18
  73. data/lib/hanami/cli/generators/app/slice/helpers.erb +0 -10
  74. data/lib/hanami/cli/generators/app/slice/keep.erb +0 -0
  75. data/lib/hanami/cli/generators/app/slice/operation.erb +0 -7
  76. data/lib/hanami/cli/generators/app/slice/relation.erb +0 -8
  77. data/lib/hanami/cli/generators/app/slice/repo.erb +0 -8
  78. data/lib/hanami/cli/generators/app/slice/routes.erb +0 -3
  79. data/lib/hanami/cli/generators/app/slice/struct.erb +0 -8
  80. data/lib/hanami/cli/generators/app/slice/view.erb +0 -7
  81. data/lib/hanami/cli/generators/app/slice_context.rb +0 -72
  82. data/lib/hanami/cli/generators/app/view/app_template.html.erb +0 -1
  83. data/lib/hanami/cli/generators/app/view/app_view.erb +0 -10
  84. data/lib/hanami/cli/generators/app/view/slice_template.html.erb +0 -1
  85. data/lib/hanami/cli/generators/app/view/slice_view.erb +0 -10
  86. data/lib/hanami/cli/generators/app/view_context.rb +0 -88
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../constants"
4
+
5
+ module Hanami
6
+ module CLI
7
+ module Generators
8
+ module App
9
+ # @api private
10
+ class RubyModuleFile < RubyFile
11
+ private
12
+
13
+ def modules
14
+ namespace_modules + [constant_name]
15
+ end
16
+
17
+ def class_name
18
+ nil
19
+ end
20
+
21
+ def parent_class_name
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -19,44 +19,127 @@ module Hanami
19
19
 
20
20
  # @since 2.0.0
21
21
  # @api private
22
- def call(app, slice, url, context: nil, **opts)
23
- context ||= SliceContext.new(inflector, app, slice, url, **opts)
22
+ def call(app, slice, url, **opts)
23
+ skip_route = opts.fetch(:skip_route, false)
24
24
 
25
- if context.generate_route?
25
+ unless skip_route
26
26
  fs.inject_line_at_class_bottom(
27
- fs.join("config", "routes.rb"), "class Routes", t("routes.erb", context).chomp
27
+ fs.join("config", "routes.rb"),
28
+ "class Routes",
29
+ <<~ROUTES.chomp
30
+
31
+ slice :#{inflector.underscore(slice)}, at: "#{url}" do
32
+ end
33
+ ROUTES
28
34
  )
29
35
  end
30
36
 
31
37
  fs.mkdir(directory = "slices/#{slice}")
32
38
 
33
- fs.write(fs.join(directory, "action.rb"), t("action.erb", context))
34
- fs.write(fs.join(directory, "view.rb"), t("view.erb", context))
35
- fs.write(fs.join(directory, "views", "helpers.rb"), t("helpers.erb", context))
36
- fs.write(fs.join(directory, "templates", "layouts", "app.html.erb"), t("app_layout.erb", context))
37
- fs.write(fs.join(directory, "operation.rb"), t("operation.erb", context))
38
-
39
- if context.bundled_assets?
40
- fs.write(fs.join(directory, "assets", "js", "app.js"), t("app_js.erb", context))
41
- fs.write(fs.join(directory, "assets", "css", "app.css"), t("app_css.erb", context))
42
- fs.write(fs.join(directory, "assets", "images", "favicon.ico"), file("favicon.ico"))
39
+ RubyClassFile.new(
40
+ fs: fs,
41
+ inflector: inflector,
42
+ namespace: slice,
43
+ key: "action",
44
+ base_path: directory,
45
+ parent_class_name: "#{Hanami.app.namespace}::Action",
46
+ auto_register: false
47
+ ).create
48
+
49
+ RubyClassFile.new(
50
+ fs: fs,
51
+ inflector: inflector,
52
+ namespace: slice,
53
+ key: "view",
54
+ base_path: directory,
55
+ parent_class_name: "#{Hanami.app.namespace}::View",
56
+ auto_register: false
57
+ ).create
58
+
59
+ RubyModuleFile.new(
60
+ fs: fs,
61
+ inflector: inflector,
62
+ namespace: slice,
63
+ key: "views.helpers",
64
+ base_path: directory,
65
+ auto_register: false,
66
+ body: ["# Add your view helpers here"]
67
+ ).create
68
+
69
+ fs.create(
70
+ fs.join(directory, "templates", "layouts", "app.html.erb"),
71
+ app_layout_template(
72
+ page_title: "#{inflector.humanize(app)} - #{inflector.humanize(slice)}"
73
+ )
74
+ )
75
+
76
+ if Hanami.bundled?("dry-operation")
77
+ RubyClassFile.new(
78
+ fs: fs,
79
+ inflector: inflector,
80
+ namespace: slice,
81
+ key: "operation",
82
+ base_path: directory,
83
+ parent_class_name: "#{Hanami.app.namespace}::Operation",
84
+ auto_register: false
85
+ ).create
43
86
  end
44
87
 
45
- if context.generate_db?
46
- fs.write(fs.join(directory, "db", "relation.rb"), t("relation.erb", context))
47
- fs.write(fs.join(directory, "relations", ".keep"), t("keep.erb", context))
48
-
49
- fs.write(fs.join(directory, "db", "repo.rb"), t("repo.erb", context))
50
- fs.write(fs.join(directory, "repos", ".keep"), t("keep.erb", context))
88
+ if Hanami.bundled?("hanami-assets")
89
+ fs.create(
90
+ fs.join(directory, "assets", "js", "app.js"),
91
+ %(import "../css/app.css";\n)
92
+ )
93
+ fs.create(
94
+ fs.join(directory, "assets", "css", "app.css"),
95
+ <<~CSS
96
+ body {
97
+ background-color: #fff;
98
+ color: #000;
99
+ font-family: sans-serif;
100
+ }
101
+ CSS
102
+ )
103
+ fs.create(fs.join(directory, "assets", "images", "favicon.ico"), file("favicon.ico"))
104
+ end
51
105
 
52
- fs.write(fs.join(directory, "db", "struct.rb"), t("struct.erb", context))
53
- fs.write(fs.join(directory, "structs", ".keep"), t("keep.erb", context))
106
+ if Hanami.bundled?("hanami-db") && !opts.fetch(:skip_db, false)
107
+ RubyClassFile.new(
108
+ fs: fs,
109
+ inflector: inflector,
110
+ namespace: slice,
111
+ key: "db.relation",
112
+ base_path: directory,
113
+ parent_class_name: "#{Hanami.app.namespace}::DB::Relation",
114
+ ).create
115
+
116
+ RubyClassFile.new(
117
+ fs: fs,
118
+ inflector: inflector,
119
+ namespace: slice,
120
+ key: "db.repo",
121
+ base_path: directory,
122
+ parent_class_name: "#{Hanami.app.namespace}::DB::Repo",
123
+ ).create
124
+
125
+ RubyClassFile.new(
126
+ fs: fs,
127
+ inflector: inflector,
128
+ namespace: slice,
129
+ key: "db.struct",
130
+ base_path: directory,
131
+ parent_class_name: "#{Hanami.app.namespace}::DB::Struct",
132
+ ).create
133
+
134
+ fs.touch(fs.join(directory, "relations", ".keep"))
135
+ fs.touch(fs.join(directory, "repos", ".keep"))
136
+ fs.touch(fs.join(directory, "structs", ".keep"))
54
137
  end
55
138
 
56
- fs.write(fs.join(directory, "actions/.keep"), t("keep.erb", context))
57
- fs.write(fs.join(directory, "views/.keep"), t("keep.erb", context))
58
- fs.write(fs.join(directory, "templates/.keep"), t("keep.erb", context))
59
- fs.write(fs.join(directory, "templates/layouts/.keep"), t("keep.erb", context))
139
+ fs.touch(fs.join(directory, "actions/.keep"))
140
+ fs.touch(fs.join(directory, "views/.keep"))
141
+ fs.touch(fs.join(directory, "templates/.keep"))
142
+ fs.touch(fs.join(directory, "templates/layouts/.keep"))
60
143
  end
61
144
 
62
145
  private
@@ -65,20 +148,30 @@ module Hanami
65
148
 
66
149
  attr_reader :inflector
67
150
 
68
- def template(path, context)
69
- require "erb"
70
-
71
- ERB.new(
72
- File.read(__dir__ + "/slice/#{path}"),
73
- trim_mode: "-"
74
- ).result(context.ctx)
75
- end
76
-
77
- alias_method :t, :template
78
-
79
151
  def file(path)
80
152
  File.read(File.join(__dir__, "slice", path))
81
153
  end
154
+
155
+ def app_layout_template(page_title:)
156
+ bundled_assets = Hanami.bundled?("hanami-assets")
157
+
158
+ <<~LAYOUT
159
+ <!DOCTYPE html>
160
+ <html lang="en">
161
+ <head>
162
+ <meta charset="UTF-8">
163
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
164
+ <title>#{ page_title }</title>
165
+ #{'<%= favicon_tag %>' if bundled_assets }
166
+ #{'<%= stylesheet_tag "app" %>' if bundled_assets }
167
+ </head>
168
+ <body>
169
+ <%= yield %>
170
+ #{'<%= javascript_tag "app" %>' if bundled_assets}
171
+ </body>
172
+ </html>
173
+ LAYOUT
174
+ end
82
175
  end
83
176
  end
84
177
  end
@@ -18,16 +18,15 @@ module Hanami
18
18
  # @since 2.2.0
19
19
  # @api private
20
20
  def call(key:, namespace:, base_path:)
21
- RubyFileWriter.new(
21
+ RubyClassFile.new(
22
22
  fs: fs,
23
23
  inflector: inflector,
24
- ).call(
25
24
  key: key,
26
25
  namespace: namespace,
27
26
  base_path: base_path,
28
27
  extra_namespace: "Structs",
29
- relative_parent_class: "DB::Struct",
30
- )
28
+ parent_class_name: "#{inflector.camelize(namespace)}::DB::Struct",
29
+ ).create
31
30
  end
32
31
 
33
32
  private
@@ -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
  module Hanami
@@ -11,57 +11,62 @@ module Hanami
11
11
  # @since 2.0.0
12
12
  # @api private
13
13
  class View
14
+ DEFAULT_FORMAT = "html"
15
+ private_constant :DEFAULT_FORMAT
16
+
17
+ TEMPLATES_FOLDER = "templates"
18
+ private_constant :TEMPLATES_FOLDER
19
+
14
20
  # @since 2.0.0
15
21
  # @api private
16
- def initialize(fs:, inflector:)
22
+ def initialize(fs:, inflector:, out: $stdout)
17
23
  @fs = fs
18
24
  @inflector = inflector
25
+ @out = out
19
26
  end
20
27
 
21
28
  # @since 2.0.0
22
29
  # @api private
23
- def call(app, key, format, slice)
24
- context = ViewContext.new(inflector, app, slice, key)
25
-
26
- if slice
27
- generate_for_slice(context, format, slice)
28
- else
29
- generate_for_app(context, format, slice)
30
+ def call(key:, namespace:, base_path:)
31
+ view_class_file(key:, namespace:, base_path:).then do |view_class|
32
+ view_class.create
33
+ view_class_name = view_class.fully_qualified_name
34
+ create_template_file(key:, base_path:, view_class_name:)
30
35
  end
31
36
  end
32
37
 
33
38
  private
34
39
 
35
- attr_reader :fs
36
-
37
- attr_reader :inflector
38
-
39
- # rubocop:disable Metrics/AbcSize
40
-
41
- def generate_for_slice(context, format, slice)
42
- slice_directory = fs.join("slices", slice)
43
- raise MissingSliceError.new(slice) unless fs.directory?(slice_directory)
44
-
45
- fs.mkdir(directory = fs.join(slice_directory, "views", context.namespaces))
46
- fs.write(fs.join(directory, "#{context.name}.rb"), t("slice_view.erb", context))
47
-
48
- fs.mkdir(directory = fs.join(slice_directory, "templates", context.namespaces))
49
- fs.write(fs.join(directory, "#{context.name}.#{format}.erb"),
50
- t(template_with_format_ext("slice_template", format), context))
40
+ attr_reader :fs, :inflector, :out
41
+
42
+ def view_class_file(key:, namespace:, base_path:)
43
+ RubyClassFile.new(
44
+ fs: fs,
45
+ inflector: inflector,
46
+ namespace: namespace,
47
+ key: inflector.underscore(key),
48
+ base_path: base_path,
49
+ parent_class_name: "#{inflector.camelize(namespace)}::View",
50
+ extra_namespace: "Views",
51
+ )
51
52
  end
52
53
 
53
- def generate_for_app(context, format, _slice)
54
- fs.mkdir(directory = fs.join("app", "views", context.namespaces))
55
- fs.write(fs.join(directory, "#{context.name}.rb"), t("app_view.erb", context))
56
-
57
- fs.mkdir(directory = fs.join("app", "templates", context.namespaces))
58
- fs.write(fs.join(directory, "#{context.name}.#{format}.erb"),
59
- t(template_with_format_ext("app_template", format), context))
54
+ def create_template_file(key:, base_path:, view_class_name:)
55
+ key_parts = key.split(KEY_SEPARATOR)
56
+ class_name_from_key = key_parts.pop # takes last segment as the class name
57
+ module_names_from_key = key_parts # the rest of the segments are the module names
58
+
59
+ file_path = fs.join(
60
+ base_path,
61
+ TEMPLATES_FOLDER,
62
+ module_names_from_key,
63
+ template_file_name(class_name_from_key, DEFAULT_FORMAT),
64
+ )
65
+ body = "<h1>#{view_class_name}</h1>\n"
66
+ fs.create(file_path, body)
60
67
  end
61
68
 
62
- # rubocop:enable Metrics/AbcSize
63
-
64
- def template_with_format_ext(name, format)
69
+ def template_file_name(name, format)
65
70
  ext =
66
71
  case format.to_sym
67
72
  when :html
@@ -72,16 +77,6 @@ module Hanami
72
77
 
73
78
  "#{name}#{ext}"
74
79
  end
75
-
76
- def template(path, context)
77
- require "erb"
78
-
79
- ERB.new(
80
- File.read(__dir__ + "/view/#{path}")
81
- ).result(context.ctx)
82
- end
83
-
84
- alias_method :t, :template
85
80
  end
86
81
  end
87
82
  end
@@ -86,6 +86,12 @@ module Hanami
86
86
  !options.fetch(:skip_db, false)
87
87
  end
88
88
 
89
+ # @since 2.2.0
90
+ # @api private
91
+ def generate_view?
92
+ !options.fetch(:skip_view, false)
93
+ end
94
+
89
95
  # @since 2.2.0
90
96
  # @api private
91
97
  def generate_sqlite?
@@ -1,16 +1,17 @@
1
1
  import * as assets from "hanami-assets";
2
2
 
3
- await assets.run();
4
-
5
- // To provide additional esbuild (https://esbuild.github.io) options, use the following:
6
- //
7
- // Read more at: https://guides.hanamirb.org/assets/customization/
8
- //
9
- // await assets.run({
10
- // esbuildOptionsFn: (args, esbuildOptions) => {
11
- // // Add to esbuildOptions here. Use `args.watch` as a condition for different options for
12
- // // compile vs watch.
3
+ // Assets are managed by esbuild (https://esbuild.github.io), and can be
4
+ // customized below.
13
5
  //
14
- // return esbuildOptions;
15
- // }
16
- // });
6
+ // Learn more at https://guides.hanamirb.org/assets/customization/.
7
+
8
+ await assets.run({
9
+ esbuildOptionsFn: (args, esbuildOptions) => {
10
+ // Customize your `esbuildOptions` here.
11
+ //
12
+ // Use the `args.watch` boolean as a condition to apply diffierent options
13
+ // when running `hanami assets watch` vs `hanami assets compile`.
14
+
15
+ return esbuildOptions;
16
+ },
17
+ });
@@ -5,4 +5,4 @@ if ! gem list foreman -i --silent; then
5
5
  gem install foreman
6
6
  fi
7
7
 
8
- exec foreman start -f Procfile.dev "$@"
8
+ exec foreman start -f Procfile.dev --env=/dev/null "$@"
@@ -3,6 +3,9 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  <%= hanami_gem("hanami") %>
6
+ <%- if hanami_head? -%>
7
+ <%= hanami_gem("cli") %>
8
+ <%- end -%>
6
9
  <%- if generate_assets? -%>
7
10
  <%= hanami_gem("assets") %>
8
11
  <%- end -%>
@@ -12,7 +15,9 @@ source "https://rubygems.org"
12
15
  <%- end -%>
13
16
  <%= hanami_gem("router") %>
14
17
  <%= hanami_gem("validations") %>
18
+ <%- if generate_view? -%>
15
19
  <%= hanami_gem("view") %>
20
+ <%- end -%>
16
21
 
17
22
  gem "dry-types", "~> 1.7"
18
23
  gem "dry-operation"
@@ -1,7 +1,9 @@
1
1
  .env*.local
2
2
  log/*
3
3
  <%- if generate_assets? -%>
4
- public/
4
+ public/*
5
+ !public/404.html
6
+ !public/500.html
5
7
  node_modules/
6
8
  <%- end -%>
7
9
  <%- if generate_sqlite? -%>
@@ -1,3 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hanami/rake_tasks"
4
+
5
+ # Add your custom rake tasks to the lib/tasks directory
6
+ Rake.add_rakelib "lib/tasks"
@@ -1 +1,15 @@
1
1
  # <%= camelized_app_name %>
2
+
3
+ Welcome to your Hanami app!
4
+
5
+ ## Getting Started
6
+
7
+ - Run the server with `bin/dev`
8
+ - View the app at [http://localhost:2300](http://localhost:2300)
9
+ - Run the tests with `bundle exec rake`
10
+
11
+ ## Useful Links
12
+
13
+ - [Hanami Home](http://hanamirb.org)
14
+ - [Hanami Guides](https://guides.hanamirb.org/)
15
+ - [Hanami API Doc](https://gemdocs.org/gems/hanami/latest)
@@ -34,62 +34,65 @@ module Hanami
34
34
  attr_reader :inflector
35
35
 
36
36
  def generate_app(app, context) # rubocop:disable Metrics/AbcSize
37
- fs.write(".gitignore", t("gitignore.erb", context))
38
- fs.write(".env", t("env.erb", context))
37
+ fs.create(".gitignore", t("gitignore.erb", context))
38
+ fs.create(".env", t("env.erb", context))
39
39
 
40
- fs.write("README.md", t("readme.erb", context))
41
- fs.write("Gemfile", t("gemfile.erb", context))
42
- fs.write("Rakefile", t("rakefile.erb", context))
43
- fs.write("Procfile.dev", t("procfile.erb", context))
44
- fs.write("config.ru", t("config_ru.erb", context))
40
+ fs.create("README.md", t("readme.erb", context))
41
+ fs.create("Gemfile", t("gemfile.erb", context))
42
+ fs.create("Rakefile", t("rakefile.erb", context))
43
+ fs.create("Procfile.dev", t("procfile.erb", context))
44
+ fs.create("config.ru", t("config_ru.erb", context))
45
45
 
46
- fs.write("bin/dev", file("dev"))
46
+ fs.create("bin/dev", file("dev"))
47
47
  fs.chmod("bin/dev", 0o755)
48
48
 
49
- fs.write("config/app.rb", t("app.erb", context))
50
- fs.write("config/settings.rb", t("settings.erb", context))
51
- fs.write("config/routes.rb", t("routes.erb", context))
52
- fs.write("config/puma.rb", t("puma.erb", context))
49
+ fs.create("config/app.rb", t("app.erb", context))
50
+ fs.create("config/settings.rb", t("settings.erb", context))
51
+ fs.create("config/routes.rb", t("routes.erb", context))
52
+ fs.create("config/puma.rb", t("puma.erb", context))
53
53
 
54
- fs.write("lib/tasks/.keep", t("keep.erb", context))
55
- fs.write("lib/#{app}/types.rb", t("types.erb", context))
54
+ fs.create("lib/tasks/.keep", t("keep.erb", context))
55
+ fs.create("lib/#{app}/types.rb", t("types.erb", context))
56
56
 
57
- fs.write("app/actions/.keep", t("keep.erb", context))
58
- fs.write("app/action.rb", t("action.erb", context))
59
- fs.write("app/view.rb", t("view.erb", context))
60
- fs.write("app/views/helpers.rb", t("helpers.erb", context))
61
- fs.write("app/templates/layouts/app.html.erb", t("app_layout.erb", context))
57
+ fs.create("app/actions/.keep", t("keep.erb", context))
58
+ fs.create("app/action.rb", t("action.erb", context))
59
+
60
+ if context.generate_view?
61
+ fs.create("app/view.rb", t("view.erb", context))
62
+ fs.create("app/views/helpers.rb", t("helpers.erb", context))
63
+ fs.create("app/templates/layouts/app.html.erb", t("app_layout.erb", context))
64
+
65
+ fs.create("public/404.html", file("404.html"))
66
+ fs.create("public/500.html", file("500.html"))
67
+ end
62
68
 
63
69
  if context.generate_assets?
64
- fs.write("package.json", t("package.json.erb", context))
65
- fs.write("config/assets.js", file("assets.js"))
66
- fs.write("app/assets/js/app.js", t("app_js.erb", context))
67
- fs.write("app/assets/css/app.css", t("app_css.erb", context))
68
- fs.write("app/assets/images/favicon.ico", file("favicon.ico"))
70
+ fs.create("package.json", t("package.json.erb", context))
71
+ fs.create("config/assets.js", file("assets.js"))
72
+ fs.create("app/assets/js/app.js", t("app_js.erb", context))
73
+ fs.create("app/assets/css/app.css", t("app_css.erb", context))
74
+ fs.create("app/assets/images/favicon.ico", file("favicon.ico"))
69
75
  end
70
76
 
71
77
  if context.generate_db?
72
- fs.write("app/db/relation.rb", t("relation.erb", context))
73
- fs.write("app/relations/.keep", t("keep.erb", context))
78
+ fs.create("app/db/relation.rb", t("relation.erb", context))
79
+ fs.create("app/relations/.keep", t("keep.erb", context))
74
80
 
75
- fs.write("app/db/repo.rb", t("repo.erb", context))
76
- fs.write("app/repos/.keep", t("keep.erb", context))
81
+ fs.create("app/db/repo.rb", t("repo.erb", context))
82
+ fs.create("app/repos/.keep", t("keep.erb", context))
77
83
 
78
- fs.write("app/db/struct.rb", t("struct.erb", context))
79
- fs.write("app/structs/.keep", t("keep.erb", context))
84
+ fs.create("app/db/struct.rb", t("struct.erb", context))
85
+ fs.create("app/structs/.keep", t("keep.erb", context))
80
86
 
81
- fs.write("config/db/seeds.rb", t("seeds.erb", context))
82
- fs.write("config/db/migrate/.keep", t("keep.erb", context))
87
+ fs.create("config/db/seeds.rb", t("seeds.erb", context))
88
+ fs.create("config/db/migrate/.keep", t("keep.erb", context))
83
89
 
84
90
  if context.generate_sqlite?
85
- fs.write("db/.keep", t("keep.erb", context))
91
+ fs.create("db/.keep", t("keep.erb", context))
86
92
  end
87
93
  end
88
94
 
89
- fs.write("app/operation.rb", t("operation.erb", context))
90
-
91
- fs.write("public/404.html", file("404.html"))
92
- fs.write("public/500.html", file("500.html"))
95
+ fs.create("app/operation.rb", t("operation.erb", context))
93
96
  end
94
97
 
95
98
  def template(path, context)
@@ -26,7 +26,7 @@ module Hanami
26
26
  INDENT = " "
27
27
 
28
28
  def self.class(class_name, **args)
29
- new(class_name: class_name, **args).to_s
29
+ new(class_name: class_name, **args).call
30
30
  end
31
31
 
32
32
  def self.module(*names, **args)
@@ -36,24 +36,33 @@ module Hanami
36
36
  names
37
37
  end
38
38
 
39
- new(modules: module_names, class_name: nil, parent_class: nil, **args).to_s
39
+ new(
40
+ modules: module_names,
41
+ class_name: nil,
42
+ parent_class_name: nil,
43
+ **args,
44
+ ).call
40
45
  end
41
46
 
42
47
  def initialize(
43
48
  class_name: nil,
44
- parent_class: nil,
49
+ parent_class_name: nil,
45
50
  modules: [],
46
51
  header: [],
47
52
  body: []
48
53
  )
49
54
  @class_name = class_name
50
- @parent_class = parent_class
55
+ @parent_class_name = parent_class_name
51
56
  @modules = modules
52
57
  @header = header.any? ? (header + [""]) : []
53
58
  @body = body
59
+
60
+ if parent_class_name && !class_name
61
+ raise ArgumentError, "class_name is required when parent_class_name is specified"
62
+ end
54
63
  end
55
64
 
56
- def to_s
65
+ def call
57
66
  definition = lines(modules).map { |line| "#{line}\n" }.join
58
67
  source_code = [header, definition].flatten.join("\n")
59
68
  ensure_parseable!(source_code)
@@ -64,7 +73,7 @@ module Hanami
64
73
 
65
74
  attr_reader(
66
75
  :class_name,
67
- :parent_class,
76
+ :parent_class_name,
68
77
  :modules,
69
78
  :header,
70
79
  :body
@@ -98,8 +107,8 @@ module Hanami
98
107
  end
99
108
 
100
109
  def class_definition
101
- if parent_class
102
- "class #{class_name} < #{parent_class}"
110
+ if parent_class_name
111
+ "class #{class_name} < #{parent_class_name}"
103
112
  else
104
113
  "class #{class_name}"
105
114
  end