cuprum-cli 0.1.0 → 0.2.0

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +10 -0
  4. data/lib/cuprum/cli/argument.rb +2 -2
  5. data/lib/cuprum/cli/command.rb +2 -4
  6. data/lib/cuprum/cli/commands/ci/rspec_each_command.rb +34 -8
  7. data/lib/cuprum/cli/commands/ci.rb +8 -0
  8. data/lib/cuprum/cli/commands/file/new_command.rb +42 -49
  9. data/lib/cuprum/cli/commands/file.rb +8 -5
  10. data/lib/cuprum/cli/dependencies/clock/mock.rb +28 -0
  11. data/lib/cuprum/cli/dependencies/clock.rb +29 -0
  12. data/lib/cuprum/cli/dependencies/file_system/mock.rb +150 -18
  13. data/lib/cuprum/cli/dependencies/file_system.rb +145 -10
  14. data/lib/cuprum/cli/dependencies.rb +2 -0
  15. data/lib/cuprum/cli/errors.rb +0 -1
  16. data/lib/cuprum/cli/{commands/file/generate_file.rb → files/create_file.rb} +6 -48
  17. data/lib/cuprum/cli/{commands/file → files/engines}/render_erb.rb +6 -6
  18. data/lib/cuprum/cli/files/engines.rb +76 -0
  19. data/lib/cuprum/cli/{errors/files → files/errors}/file_not_writeable.rb +3 -3
  20. data/lib/cuprum/cli/files/errors/generator_error.rb +46 -0
  21. data/lib/cuprum/cli/{errors/files → files/errors}/missing_parameter.rb +5 -5
  22. data/lib/cuprum/cli/{errors/files → files/errors}/missing_template.rb +3 -3
  23. data/lib/cuprum/cli/{errors/files → files/errors}/template_error.rb +3 -3
  24. data/lib/cuprum/cli/files/errors.rb +19 -0
  25. data/lib/cuprum/cli/files/generator.rb +431 -0
  26. data/lib/cuprum/cli/files/generators/basic_generator.rb +15 -0
  27. data/lib/cuprum/cli/files/generators/rspec_generator.rb +19 -0
  28. data/lib/cuprum/cli/files/generators/ruby_generator.rb +32 -0
  29. data/lib/cuprum/cli/files/generators.rb +25 -0
  30. data/lib/cuprum/cli/files/template.rb +62 -0
  31. data/lib/cuprum/cli/files/templates/basic_template.rb +39 -0
  32. data/lib/cuprum/cli/files/templates/file_template.rb +60 -0
  33. data/lib/cuprum/cli/files/templates/string_template.rb +29 -0
  34. data/lib/cuprum/cli/files/templates.rb +59 -0
  35. data/lib/cuprum/cli/files.rb +18 -0
  36. data/lib/cuprum/cli/integrations/async/commands/rspec_each_command.rb +32 -0
  37. data/lib/cuprum/cli/integrations/async/commands.rb +11 -0
  38. data/lib/cuprum/cli/integrations/async.rb +21 -0
  39. data/lib/cuprum/cli/integrations/thor/registry.rb +7 -29
  40. data/lib/cuprum/cli/integrations/thor/task.rb +37 -7
  41. data/lib/cuprum/cli/metadata.rb +54 -5
  42. data/lib/cuprum/cli/option.rb +10 -4
  43. data/lib/cuprum/cli/options/class_methods.rb +19 -1
  44. data/lib/cuprum/cli/registry.rb +131 -55
  45. data/lib/cuprum/cli/rspec/deferred/dependencies/file_system_examples.rb +1303 -1001
  46. data/lib/cuprum/cli/rspec/deferred/generators_examples.rb +112 -0
  47. data/lib/cuprum/cli/rspec/deferred/metadata_examples.rb +300 -0
  48. data/lib/cuprum/cli/rspec/deferred/options_examples.rb +8 -2
  49. data/lib/cuprum/cli/rspec/deferred/registry_examples.rb +343 -108
  50. data/lib/cuprum/cli/rspec/deferred/templates_examples.rb +183 -0
  51. data/lib/cuprum/cli/version.rb +1 -1
  52. data/lib/cuprum/cli.rb +1 -0
  53. metadata +31 -14
  54. data/lib/cuprum/cli/commands/file/resolve_template.rb +0 -136
  55. data/lib/cuprum/cli/commands/file/templates.rb +0 -71
  56. data/lib/cuprum/cli/errors/files/template_not_resolved.rb +0 -54
  57. data/lib/cuprum/cli/errors/files.rb +0 -19
  58. /data/lib/cuprum/cli/{commands/file → files}/templates/rspec.rb.erb +0 -0
  59. /data/lib/cuprum/cli/{commands/file → files}/templates/ruby.rb.erb +0 -0
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files'
4
+
5
+ module Cuprum::Cli::Files
6
+ # Namespace for commands that process raw templates into file contents.
7
+ module Engines
8
+ class << self
9
+ # @overload fetch(engine)
10
+ # Retrieves the render command for the given engine.
11
+ #
12
+ # @param engine [String] the engine identifier.
13
+ #
14
+ # @return [Cuprum::Command] the render command for the engine.
15
+ #
16
+ # @raise [KeyError] if there is no matching engine.
17
+ #
18
+ # @overload fetch(engine, default)
19
+ # Retrieves the render command for the given engine.
20
+ #
21
+ # @param engine [String] the engine identifier.
22
+ # @param default [Cuprum::Command] the command to return if there is no
23
+ # matching engine.
24
+ #
25
+ # @return [Cuprum::Command] the render command for the engine.
26
+ #
27
+ # @overload fetch(engine) { |engine| }
28
+ # Retrieves the render command for the given engine.
29
+ #
30
+ # If there is no matching engine. calls the block with the given engine.
31
+ #
32
+ # @param engine [String] the engine identifier.
33
+ #
34
+ # @return [Cuprum::Command] the render command for the engine.
35
+ #
36
+ # @yieldparam engine [String] the engine identifier.
37
+ #
38
+ # @yieldreturn [Cuprum::Command] the command to return if there is no
39
+ # matching engine.
40
+ def fetch(engine, default = UNDEFINED, &)
41
+ return engines.fetch(engine, &) if default == UNDEFINED
42
+
43
+ engines.fetch(engine, default)
44
+ end
45
+
46
+ # Registers a render command for the specified engine.
47
+ #
48
+ # @param engine [String] the engine identifier.
49
+ # @param render_command [Cuprum::Command] the render command for the
50
+ # engine.
51
+ #
52
+ # @return [void]
53
+ def register(engine, render_command)
54
+ engines[engine] = render_command
55
+
56
+ nil
57
+ end
58
+
59
+ private
60
+
61
+ def default_engines
62
+ { ERB => Cuprum::Cli::Files::Engines::RenderErb }
63
+ end
64
+
65
+ def engines = @engines ||= default_engines
66
+ end
67
+
68
+ autoload :RenderErb, 'cuprum/cli/files/engines/render_erb'
69
+
70
+ # Identifier for ERB files.
71
+ ERB = 'cuprum.cli.files.engines.erb'
72
+
73
+ UNDEFINED = SleepingKingStudios::Tools::UNDEFINED
74
+ private_constant :UNDEFINED
75
+ end
76
+ end
@@ -2,13 +2,13 @@
2
2
 
3
3
  require 'cuprum/error'
4
4
 
5
- require 'cuprum/cli/errors/files'
5
+ require 'cuprum/cli/files/errors'
6
6
 
7
- module Cuprum::Cli::Errors::Files
7
+ module Cuprum::Cli::Files::Errors
8
8
  # Error returned when attempting to generate a file in an invalid location.
9
9
  class FileNotWriteable < Cuprum::Error
10
10
  # Short string used to identify the type of error.
11
- TYPE = 'cuprum.cli.errors.files.file_not_writeable'
11
+ TYPE = 'cuprum.cli.files.errors.file_not_writeable'
12
12
 
13
13
  # @param file_path [String] the path to the expected file.
14
14
  # @param message [String] message describing the nature of the error.
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files/errors'
4
+
5
+ module Cuprum::Cli::Files::Errors
6
+ # Error returned when an error occurs when calling a generator.
7
+ class GeneratorError < Cuprum::Error
8
+ # Short string used to identify the type of error.
9
+ TYPE = 'cuprum.cli.files.errors.generator_error'
10
+
11
+ # @param message [String] message describing the nature of the error.
12
+ # @param details [String] additional information about the error.
13
+ # @param file_path [String] the file path passed to the generator.
14
+ # @param options [Hash] additional options passed to the generator.
15
+ def initialize(message:, details: nil, file_path: nil, options: {}, **)
16
+ @details = details
17
+ @file_path = file_path
18
+ @options = options
19
+
20
+ super
21
+ end
22
+
23
+ # @return [String] additional information about the error.
24
+ attr_reader :details
25
+
26
+ # @return [String] the file path passed to the generator.
27
+ attr_reader :file_path
28
+
29
+ # @return [Hash] additional options passed to the generator.
30
+ attr_reader :options
31
+
32
+ private
33
+
34
+ def as_json_data
35
+ options = self.options.empty? ? nil : self.options.transform_keys(&:to_s)
36
+
37
+ super
38
+ .merge(
39
+ 'details' => details,
40
+ 'file_path' => file_path,
41
+ 'options' => options
42
+ )
43
+ .compact
44
+ end
45
+ end
46
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cuprum/cli/errors/files'
4
- require 'cuprum/cli/errors/files/template_error'
3
+ require 'cuprum/cli/files/errors'
4
+ require 'cuprum/cli/files/errors/template_error'
5
5
 
6
- module Cuprum::Cli::Errors::Files
6
+ module Cuprum::Cli::Files::Errors
7
7
  # Error returned when a required parameter is missing when rendering content.
8
- class MissingParameter < Cuprum::Cli::Errors::Files::TemplateError
8
+ class MissingParameter < Cuprum::Cli::Files::Errors::TemplateError
9
9
  # Short string used to identify the type of error.
10
- TYPE = 'cuprum.cli.errors.files.missing_parameter'
10
+ TYPE = 'cuprum.cli.files.errors.missing_parameter'
11
11
 
12
12
  # @param parameter_name [String, Symbol] the name of the missing parameter.
13
13
  # @param details [String] additional information about the error.
@@ -2,13 +2,13 @@
2
2
 
3
3
  require 'cuprum/error'
4
4
 
5
- require 'cuprum/cli/errors/files'
5
+ require 'cuprum/cli/files/errors'
6
6
 
7
- module Cuprum::Cli::Errors::Files
7
+ module Cuprum::Cli::Files::Errors
8
8
  # Error returned when unable to load a template.
9
9
  class MissingTemplate < Cuprum::Error
10
10
  # Short string used to identify the type of error.
11
- TYPE = 'cuprum.cli.errors.files.missing_template'
11
+ TYPE = 'cuprum.cli.files.errors.missing_template'
12
12
 
13
13
  # @param template_path [String] the expected template path.
14
14
  # @param message [String] an optional message to display.
@@ -2,13 +2,13 @@
2
2
 
3
3
  require 'cuprum/error'
4
4
 
5
- require 'cuprum/cli/errors/files'
5
+ require 'cuprum/cli/files/errors'
6
6
 
7
- module Cuprum::Cli::Errors::Files
7
+ module Cuprum::Cli::Files::Errors
8
8
  # Error returned when an error occurs when generating templated content.
9
9
  class TemplateError < Cuprum::Error
10
10
  # Short string used to identify the type of error.
11
- TYPE = 'cuprum.cli.errors.files.template_error'
11
+ TYPE = 'cuprum.cli.files.errors.template_error'
12
12
 
13
13
  # @param message [String] message describing the nature of the error.
14
14
  # @param details [String] additional information about the error.
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files'
4
+
5
+ module Cuprum::Cli::Files
6
+ # Namespace for errors returned when creating or managing files.
7
+ module Errors
8
+ autoload :FileNotWriteable,
9
+ 'cuprum/cli/files/errors/file_not_writeable'
10
+ autoload :GeneratorError,
11
+ 'cuprum/cli/files/errors/generator_error'
12
+ autoload :MissingParameter,
13
+ 'cuprum/cli/files/errors/missing_parameter'
14
+ autoload :MissingTemplate,
15
+ 'cuprum/cli/files/errors/missing_template'
16
+ autoload :TemplateError,
17
+ 'cuprum/cli/files/errors/template_error'
18
+ end
19
+ end
@@ -0,0 +1,431 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum'
4
+ require 'plumbum'
5
+
6
+ require 'cuprum/cli/files'
7
+
8
+ module Cuprum::Cli::Files
9
+ # Command for generating templated files.
10
+ class Generator < Cuprum::Command # rubocop:disable Metrics/ClassLength
11
+ include Plumbum::Consumer
12
+ prepend Plumbum::Parameters
13
+ include Cuprum::Cli::Dependencies::StandardIo::Helpers
14
+ extend Cuprum::Cli::Options::ClassMethods
15
+ include Cuprum::Cli::Options::Quiet
16
+ include Cuprum::Cli::Options::Verbose
17
+
18
+ # Raised when performing a protected operation on an abstract Generator.
19
+ class AbstractGeneratorError < StandardError; end
20
+
21
+ # Raised when performing a protected operation on an abstract Generator.
22
+ class OutputAlreadyExistsError < KeyError; end
23
+
24
+ # Data class representing a configured output file.
25
+ Output = Data.define(:key, :path, :template)
26
+
27
+ class << self
28
+ # Marks the generator as abstract.
29
+ def abstract = @abstract = true
30
+
31
+ # @return [true, false] true if the generator is abstract and should not
32
+ # be assigned matchers or outputs; otherwise false.
33
+ def abstract? = @abstract.nil? ? false : @abstract
34
+
35
+ # @overload match_file(pattern)
36
+ # Registers the pattern as a valid match for the generator.
37
+ #
38
+ # The registered matches are checked when the generator checks whether a
39
+ # file path can be generated by the generator.
40
+ #
41
+ # - If the given pattern is a regular expression, the generator will
42
+ # match the pattern against the file path.
43
+ # - If the pattern is a string, the generator will check if the file
44
+ # path ends with the pattern string.
45
+ #
46
+ # @param pattern [Regexp, String] the pattern to register.
47
+ #
48
+ # @return [void]
49
+ #
50
+ # @overload match_file(&block)
51
+ # Registers the block as a valid match for the generator.
52
+ #
53
+ # The registered matches are checked when the generator checks whether a
54
+ # file path can be generated by the generator. The given block will be
55
+ # called with the file path and generator options; if the block returns
56
+ # a truthy value, the match check returns true.
57
+ #
58
+ # @yieldparam file_path [String] the input file path provided by the
59
+ # user.
60
+ # @yieldparam options [Hash] additional options for the generator.
61
+ #
62
+ # @return [void]
63
+ def match_file(pattern = nil, &block)
64
+ if abstract?
65
+ raise AbstractGeneratorError,
66
+ abstract_generator_message('define matcher')
67
+ end
68
+
69
+ own_matchers << (block_given? ? block : pattern)
70
+
71
+ nil
72
+ end
73
+
74
+ # @overload matches?(file_path, **options)
75
+ # Checks if the generator matches the given file path and options.
76
+ #
77
+ # @param file_path [String] the input file path provided by the user.
78
+ # @param options [Hash] additional options for the generator.
79
+ #
80
+ # @return [true, false] true if the generator can generate files with
81
+ # the given file type, path, and options; otherwise false.
82
+ def matches?(file_path, **)
83
+ matchers.any? do |matcher|
84
+ case matcher
85
+ when Proc then matcher.call(file_path, **)
86
+ when Regexp then matcher.match?(file_path)
87
+ when String then file_path.end_with?(matcher)
88
+ else
89
+ # :nocov:
90
+ false
91
+ # :nocov:
92
+ end
93
+ end
94
+ end
95
+ alias match? matches?
96
+
97
+ # Registers an output file for the generator.
98
+ #
99
+ # The output path parameter is a String that can include format strings,
100
+ # which will be resolved from the options passed to the generator. In
101
+ # addition, the format strings can reference the following parameters
102
+ # derived from the input file_path. The following examples use an input
103
+ # file path of "lib/path/to/file.rb":
104
+ #
105
+ # - base_name: The last segment of the file path, including the file
106
+ # extension. Example: "file.rb".
107
+ # - dir_name: The directory path from the file path, relative to the
108
+ # working directory. Example: "lib/path/to".
109
+ # - ext_name: The extension of the file path, including the leading
110
+ # period character. Example: ".rb".
111
+ # - file_path: The full file path. Example: "lib/path/to/file.rb".
112
+ # - relative_path: The *second and later* segments of the directory path,
113
+ # or an empty String if the path is too short. Example: "path/to".
114
+ # - root_path: The *first* segment of the directory path, or an empty
115
+ # String if the path is too short. Example: "lib".
116
+ # - short_name: The last segment of the file path, excluding the file
117
+ # extension. Example: "file".
118
+ #
119
+ # @param path [String] the file path for the generated file.
120
+ # @param key [String, Symbol] a unique key used to identify the output.
121
+ # Defaults to :default.
122
+ # @param template [String] the path to the template file for the output.
123
+ # If not provided, the user must provide a template for that output.
124
+ #
125
+ # @return [void]
126
+ def output(path, key: :default, template: nil) # rubocop:disable Metrics/MethodLength
127
+ key = key.to_sym
128
+
129
+ if abstract?
130
+ raise AbstractGeneratorError,
131
+ abstract_generator_message("define output #{key.inspect}")
132
+ end
133
+
134
+ if own_outputs.key?(key)
135
+ raise OutputAlreadyExistsError, output_already_exists_message(key)
136
+ end
137
+
138
+ template = resolve_template(template)
139
+
140
+ own_outputs[key] = Output.new(key:, path:, template:)
141
+
142
+ nil
143
+ end
144
+
145
+ # @return [Hash{Symbol => Output}] the configured outputs for the
146
+ # generator.
147
+ def outputs
148
+ return {} if abstract?
149
+
150
+ superclass.outputs.merge(own_outputs)
151
+ end
152
+
153
+ protected
154
+
155
+ def matchers
156
+ return [] if abstract?
157
+
158
+ superclass.matchers.concat(own_matchers)
159
+ end
160
+
161
+ def own_matchers = @own_matchers ||= []
162
+
163
+ def own_outputs = @own_outputs ||= {}
164
+
165
+ private
166
+
167
+ def abstract_generator_message(short_message)
168
+ class_name =
169
+ ancestors
170
+ .find { |ancestor| ancestor.is_a?(Class) && ancestor.name }
171
+ .name
172
+
173
+ "unable to #{short_message} - #{class_name} is an abstract class"
174
+ end
175
+
176
+ def output_already_exists_message(key)
177
+ class_name =
178
+ ancestors
179
+ .find { |ancestor| ancestor.is_a?(Class) && ancestor.name }
180
+ .name
181
+
182
+ "unable to define output #{key.inspect} - #{class_name} already " \
183
+ "defines output #{key.inspect}"
184
+ end
185
+
186
+ def resolve_template(maybe_template)
187
+ return if maybe_template.nil?
188
+
189
+ Cuprum::Cli::Files::Templates.resolve(maybe_template)
190
+ end
191
+ end
192
+
193
+ abstract
194
+
195
+ provider Cuprum::Cli::Dependencies.provider
196
+
197
+ dependency :file_system
198
+ dependency :standard_io
199
+
200
+ option :directories, type: :boolean, default: true
201
+ option :dry_run, type: :boolean
202
+
203
+ # @overload initialize(file_path, **options)
204
+ # @param file_path [String, nil] the input file path provided by the user.
205
+ # @param options [Hash] additional options for the generator.
206
+ #
207
+ # @option options dry_run [true, false] if true, simulates file generation
208
+ # but does not perform the actual file system operations. Defaults to
209
+ # false.
210
+ # @option options quiet [true, false] if true, does not print generated
211
+ # file names status to STDOUT. Defaults to false.
212
+ # @option options verbose [true, false] if true, prints the contents of
213
+ # generated files to STDOUT. Defaults to false.
214
+ def initialize(file_path = nil, **)
215
+ super()
216
+
217
+ @file_path = file_path
218
+ @options = self.class.resolve_options(**)
219
+ end
220
+
221
+ # @return [String] the input file path provided by the user.
222
+ attr_reader :file_path
223
+
224
+ # @return [Hash] additional options for the generator.
225
+ attr_reader :options
226
+
227
+ # @return [Hash] parameters extracted from the input file name.
228
+ def file_parameters
229
+ @file_parameters ||=
230
+ if file_path
231
+ extract_file_parameters(file_path)
232
+ else
233
+ {}
234
+ end
235
+ end
236
+
237
+ # @return [Hash] parameters used to resolve output file paths and contents.
238
+ def parameters
239
+ file_parameters.merge(options)
240
+ end
241
+
242
+ private
243
+
244
+ def build_file_template(template_path)
245
+ Cuprum::Cli::Files::Templates::FileTemplate.build(template_path)
246
+ end
247
+
248
+ def check_if_file_already_exists(file_path:)
249
+ return unless file_system.file?(file_path)
250
+
251
+ error =
252
+ file_not_writeable_error(file_path:, reason: 'file already exists')
253
+
254
+ failure(error)
255
+ end
256
+
257
+ def check_if_file_is_directory(file_path:)
258
+ return unless file_system.directory?(file_path)
259
+
260
+ error =
261
+ file_not_writeable_error(file_path:, reason: 'file is a directory')
262
+
263
+ failure(error)
264
+ end
265
+
266
+ def create_file(contents:, file_path:)
267
+ Cuprum::Cli::Files::CreateFile
268
+ .new(
269
+ directories: directories?,
270
+ dry_run: dry_run?,
271
+ file_system:
272
+ )
273
+ .call(contents:, file_path:)
274
+ end
275
+
276
+ def duplicate_file_error(file_path:, key:)
277
+ details = "multiple outputs writing to output path #{file_path.inspect}"
278
+
279
+ generator_error(details:, key:)
280
+ end
281
+
282
+ def extract_file_parameters(file_path) # rubocop:disable Metrics/MethodLength
283
+ base_name = File.basename(file_path)
284
+ segments = file_path.split(File::SEPARATOR)
285
+
286
+ {
287
+ base_name:,
288
+ dir_name: File.dirname(file_path),
289
+ ext_name: File.extname(file_path),
290
+ file_path:,
291
+ relative_path: segments[1...-1].join(File::SEPARATOR),
292
+ root_path: segments[0...-1].first || '',
293
+ short_name: base_name.split('.').first
294
+ }
295
+ end
296
+
297
+ def file_not_writeable_error(**)
298
+ Cuprum::Cli::Files::Errors::FileNotWriteable.new(**)
299
+ end
300
+
301
+ def filter_outputs # rubocop:disable Metrics/MethodLength
302
+ outputs = self.class.outputs
303
+
304
+ if outputs.empty?
305
+ error = generator_error(
306
+ details: 'generator does not define any outputs'
307
+ )
308
+ return failure(error)
309
+ end
310
+
311
+ outputs = outputs.reject { |key, _| options[key] == false }
312
+
313
+ return outputs unless outputs.empty?
314
+
315
+ error = generator_error(details: 'all outputs have been disabled')
316
+ failure(error)
317
+ end
318
+
319
+ def generate_file(contents:, file_path:)
320
+ say "Generating file #{file_path}..."
321
+
322
+ report_file_contents(contents)
323
+
324
+ step { create_file(contents:, file_path:) }
325
+
326
+ file_path
327
+ end
328
+
329
+ def generate_contents(outputs) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
330
+ outputs.each_value.with_object({}) do |output, hsh|
331
+ file_path = step { resolve_output_path(output) }
332
+
333
+ if hsh.key?(file_path)
334
+ return failure(duplicate_file_error(file_path:, key: output.key))
335
+ end
336
+
337
+ template = step { template_for(output) }
338
+ contents = step { render_template(template:) }
339
+
340
+ step { check_if_file_is_directory(file_path:) }
341
+ step { check_if_file_already_exists(file_path:) }
342
+
343
+ hsh[file_path] = contents
344
+ end
345
+ end
346
+
347
+ def generator_error(details: nil, key: nil)
348
+ message = 'unable to generate output file'
349
+ message = "#{message}#{key ? " #{key.inspect}" : 's'}"
350
+ message = "#{message} - #{details}" if details
351
+
352
+ Cuprum::Cli::Files::Errors::GeneratorError.new(
353
+ details:,
354
+ file_path:,
355
+ message:,
356
+ options:
357
+ )
358
+ end
359
+
360
+ def process
361
+ outputs = step { filter_outputs }
362
+
363
+ generated_files = step { generate_contents(outputs) }
364
+
365
+ generated_files.each do |file_path, contents|
366
+ step { generate_file(contents:, file_path:) }
367
+ end
368
+
369
+ generated_files.keys
370
+ end
371
+
372
+ def render_template(template:)
373
+ if template.members.include?(:file_system)
374
+ template = template.with(file_system:)
375
+ end
376
+
377
+ template.call(**parameters)
378
+ end
379
+
380
+ def report_file_contents(contents)
381
+ say "\n", verbose: true
382
+ say(
383
+ contents
384
+ .each_line
385
+ .map { |line| line == "\n" ? "\n" : " #{line}" }.join,
386
+ verbose: true
387
+ )
388
+ say "\n", verbose: true
389
+ end
390
+
391
+ def resolve_output_path(output) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
392
+ format(output.path, parameters.compact).gsub(%r{//+}, '/')
393
+ rescue ArgumentError => exception
394
+ raise unless exception.message.include?('malformed format string')
395
+
396
+ details = "#{exception.message} for output path #{output.path}"
397
+ error = generator_error(details:, key: output.key)
398
+ failure(error)
399
+ rescue KeyError => exception
400
+ details =
401
+ "missing parameter #{exception.key.inspect} for output path " \
402
+ "#{output.path}"
403
+ error = generator_error(details:, key: output.key)
404
+ failure(error)
405
+ end
406
+
407
+ def template_for(output)
408
+ template_path = template_path_from_options(output)
409
+
410
+ unless template_path.nil? || template_path.empty?
411
+ return build_file_template(template_path)
412
+ end
413
+
414
+ return output.template if output.template
415
+
416
+ error = generator_error(
417
+ details: 'output does not define a template path',
418
+ key: output.key
419
+ )
420
+ failure(error)
421
+ end
422
+
423
+ def template_path_from_options(output)
424
+ if output.key == :default && options.key?(:template)
425
+ return options[:template]
426
+ end
427
+
428
+ options[:"#{output.key}_template"]
429
+ end
430
+ end
431
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files/generators'
4
+
5
+ module Cuprum::Cli::Files::Generators
6
+ # Generator for creating a file with specified file path and contents.
7
+ class BasicGenerator < Cuprum::Cli::Files::Generator
8
+ option :contents, type: :string, required: true
9
+
10
+ option :file_path, type: :string, required: true
11
+
12
+ output '%<file_path>s',
13
+ template: Cuprum::Cli::Files::Templates::BasicTemplate.new
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files/generators'
4
+
5
+ module Cuprum::Cli::Files::Generators
6
+ # Generator for creating an RSpec source file.
7
+ class RSpecGenerator < Cuprum::Cli::Files::Generator
8
+ match_file(/_spec\.rb\z/)
9
+
10
+ option :rspec_template
11
+
12
+ output '%<file_path>s',
13
+ key: :rspec,
14
+ template: File.join(
15
+ Cuprum::Cli::Files::Generators::TEMPLATES_PATH,
16
+ 'rspec.rb.erb'
17
+ )
18
+ end
19
+ end