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,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files/generators'
4
+
5
+ module Cuprum::Cli::Files::Generators
6
+ # Generator for creating a Ruby source file.
7
+ class RubyGenerator < Cuprum::Cli::Files::Generator
8
+ match_file(/\.rb\z/)
9
+
10
+ option :parent_class
11
+
12
+ option :ruby, type: :boolean, default: true
13
+ option :rspec, type: :boolean, default: true, aliases: %i[spec]
14
+
15
+ option :ruby_template
16
+ option :rspec_template
17
+
18
+ output '%<file_path>s',
19
+ key: :ruby,
20
+ template: File.join(
21
+ Cuprum::Cli::Files::Generators::TEMPLATES_PATH,
22
+ 'ruby.rb.erb'
23
+ )
24
+
25
+ output File.join('spec', '%<relative_path>s', '%<short_name>s_spec.rb'),
26
+ key: :rspec,
27
+ template: File.join(
28
+ Cuprum::Cli::Files::Generators::TEMPLATES_PATH,
29
+ 'rspec.rb.erb'
30
+ )
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files'
4
+
5
+ module Cuprum::Cli::Files
6
+ # Namespace for pre-defined file generators.
7
+ module Generators
8
+ autoload :BasicGenerator, 'cuprum/cli/files/generators/basic_generator'
9
+ autoload :RSpecGenerator, 'cuprum/cli/files/generators/rspec_generator'
10
+ autoload :RubyGenerator, 'cuprum/cli/files/generators/ruby_generator'
11
+
12
+ # The path for template files, used when defining generators.
13
+ TEMPLATES_PATH =
14
+ File
15
+ .join(
16
+ Cuprum::Cli.gem_path,
17
+ 'lib',
18
+ 'cuprum',
19
+ 'cli',
20
+ 'files',
21
+ 'templates'
22
+ )
23
+ .freeze
24
+ end
25
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/processing'
4
+ require 'sleeping_king_studios/tools/toolbox/heritable_data'
5
+
6
+ require 'cuprum/cli/files'
7
+
8
+ module Cuprum::Cli::Files
9
+ # Data class representing a file generator template.
10
+ Template =
11
+ SleepingKingStudios::Tools::Toolbox::HeritableData.define(:engine) do # rubocop:disable Metrics/BlockLength
12
+ include Cuprum::Processing
13
+ include Cuprum::ResultHelpers
14
+ include Cuprum::Steps
15
+
16
+ # @param engine [Symbol] the engine used to generate the template
17
+ # contents.
18
+ def initialize(engine: nil, **)
19
+ super
20
+ end
21
+
22
+ # (see Cuprum::Processing#call)
23
+ def call(*args, **kwargs, &)
24
+ steps { super }
25
+ end
26
+
27
+ private
28
+
29
+ def apply_engine(template, **parameters)
30
+ return template unless engine
31
+
32
+ engine_class = Cuprum::Cli::Files::Engines.fetch(engine) do
33
+ return failure(unknown_engine_error)
34
+ end
35
+
36
+ engine_class.new(template_name:).call(template, **parameters)
37
+ end
38
+
39
+ def process(**parameters)
40
+ template = step { raw_value }
41
+
42
+ apply_engine(template, **parameters)
43
+ end
44
+
45
+ def raw_value
46
+ error = Cuprum::Errors::CommandNotImplemented.new(command: self)
47
+
48
+ failure(error)
49
+ end
50
+
51
+ def template_name = nil
52
+
53
+ def unknown_engine_error
54
+ details = "unknown template engine #{engine.inspect}"
55
+
56
+ Cuprum::Cli::Files::Errors::TemplateError.new(
57
+ details:,
58
+ message: "unable to render template - #{details}"
59
+ )
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files/templates'
4
+
5
+ module Cuprum::Cli::Files::Templates
6
+ # Data class representing a template that returns the input contents.
7
+ BasicTemplate = Cuprum::Cli::Files::Template.define do
8
+ private
9
+
10
+ def process(contents: nil, **parameters)
11
+ step { validate_contents(contents) }
12
+
13
+ apply_engine(contents, **parameters)
14
+ end
15
+
16
+ def tools
17
+ SleepingKingStudios::Tools::Toolbelt.instance
18
+ end
19
+
20
+ def validate_contents(contents) # rubocop:disable Metrics/MethodLength
21
+ validator = tools.assertions.aggregator_class.new
22
+
23
+ validator.validate_presence(contents, as: 'contents')
24
+ validator.validate_instance_of(
25
+ contents,
26
+ as: 'contents',
27
+ expected: String
28
+ )
29
+
30
+ return Cuprum::Result.new if validator.empty?
31
+
32
+ error = Cuprum::Errors::InvalidParameters.new(
33
+ command_class: self.class,
34
+ failures: validator.each.to_a
35
+ )
36
+ failure(error)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files/template'
4
+ require 'cuprum/cli/files/templates'
5
+
6
+ module Cuprum::Cli::Files::Templates
7
+ # rubocop:disable Metrics/BlockLength
8
+
9
+ # Data class representing a template defined on a file.
10
+ FileTemplate = Cuprum::Cli::Files::Template.define(:file_path, :file_system) \
11
+ do
12
+ class_methods = Module.new do
13
+ # Converts a raw file path to a template object.
14
+ #
15
+ # @param file_path [String] the input file path.
16
+ #
17
+ # @return [FileTemplate] the generated template.
18
+ def build(file_path)
19
+ engine =
20
+ case File.extname(file_path)
21
+ when '.erb' then Cuprum::Cli::Files::Engines::ERB
22
+ end
23
+
24
+ new(engine:, file_path:)
25
+ end
26
+ end
27
+ const_set(:ClassMethods, class_methods)
28
+
29
+ def self.included(other)
30
+ super
31
+
32
+ other.extend(const_get(:ClassMethods))
33
+ end
34
+
35
+ # @overload initialize(file_path:, engine: nil)
36
+ # @param engine [Symbol] the engine used to generate the template
37
+ # contents.
38
+ # @param file_path [String] the path to the template file.
39
+ def initialize(file_path:, engine: nil, file_system: nil, **)
40
+ file_system ||= Cuprum::Cli::Dependencies.provider.get('file_system')
41
+
42
+ super(engine:, file_path:, file_system:, **)
43
+ end
44
+
45
+ private
46
+
47
+ def raw_value
48
+ success(file_system.read_file(file_path))
49
+ rescue Cuprum::Cli::Dependencies::FileSystem::FileNotFoundError
50
+ error = Cuprum::Cli::Files::Errors::MissingTemplate.new(
51
+ message: 'unable to generate file',
52
+ template_path: file_path
53
+ )
54
+ failure(error)
55
+ end
56
+
57
+ def template_name = file_path
58
+ end
59
+ # rubocop:enable Metrics/BlockLength
60
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files/template'
4
+ require 'cuprum/cli/files/templates'
5
+
6
+ module Cuprum::Cli::Files::Templates
7
+ # Data class representing a string literal template.
8
+ StringTemplate = Cuprum::Cli::Files::Template.define(:raw_template) do
9
+ class_methods = Module.new do
10
+ # Converts a raw template string to a template object.
11
+ #
12
+ # @param raw_template [String] the unprocessed template string.
13
+ #
14
+ # @return [StringTemplate] the generated template.
15
+ def build(raw_template) = new(engine: nil, raw_template:)
16
+ end
17
+ const_set(:ClassMethods, class_methods)
18
+
19
+ def self.included(other)
20
+ super
21
+
22
+ other.extend(const_get(:ClassMethods))
23
+ end
24
+
25
+ private
26
+
27
+ def raw_value = raw_template
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/files'
4
+
5
+ module Cuprum::Cli::Files
6
+ # Namespace for template files and implementations.
7
+ module Templates
8
+ autoload :BasicTemplate, 'cuprum/cli/files/templates/basic_template'
9
+ autoload :FileTemplate, 'cuprum/cli/files/templates/file_template'
10
+ autoload :StringTemplate, 'cuprum/cli/files/templates/string_template'
11
+
12
+ class << self
13
+ # Converts a raw input string to a template object.
14
+ #
15
+ # - If the input is a Template, returns the input.
16
+ # - If the input is a single-line String, generates and returns a
17
+ # FileTemplate with the input as the file path.
18
+ # - If the input is a multi-line String, generates and returns a
19
+ # StringTemplate with the input as the raw template value.
20
+ # - For all other values, raises an ArgumentError.
21
+ #
22
+ # @param maybe_template [Cuprum::Cli::Files::Template, String] the
23
+ # template or unprocessed template string.
24
+ #
25
+ # @return [Cuprum::Cli::Files::Template] the generated template.
26
+ def resolve(maybe_template)
27
+ if maybe_template.is_a?(Cuprum::Cli::Files::Template)
28
+ return maybe_template
29
+ end
30
+
31
+ if maybe_template.is_a?(String)
32
+ return build_template_from_string(maybe_template)
33
+ end
34
+
35
+ tools.assertions.validate_presence(maybe_template, as: 'template')
36
+
37
+ raise ArgumentError, 'template must be a Template or file path'
38
+ end
39
+
40
+ private
41
+
42
+ def build_template_from_string(maybe_template)
43
+ tools.assertions.validate_presence(maybe_template, as: 'template')
44
+
45
+ if file_path?(maybe_template)
46
+ Cuprum::Cli::Files::Templates::FileTemplate.build(maybe_template)
47
+ else
48
+ Cuprum::Cli::Files::Templates::StringTemplate.build(maybe_template)
49
+ end
50
+ end
51
+
52
+ def file_path?(value)
53
+ !value.include?("\n")
54
+ end
55
+
56
+ def tools = SleepingKingStudios::Tools::Toolbelt.instance
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli'
4
+
5
+ module Cuprum::Cli
6
+ # Namespace for shared functionality for manipulating files.
7
+ module Files
8
+ autoload :CreateFile, 'cuprum/cli/files/create_file'
9
+ autoload :Engines, 'cuprum/cli/files/engines'
10
+ autoload :Errors, 'cuprum/cli/files/errors'
11
+ autoload :GenerateFile, 'cuprum/cli/files/generate_file'
12
+ autoload :Generator, 'cuprum/cli/files/generator'
13
+ autoload :Generators, 'cuprum/cli/files/generators'
14
+ autoload :RenderErb, 'cuprum/cli/files/render_erb'
15
+ autoload :Template, 'cuprum/cli/files/template'
16
+ autoload :Templates, 'cuprum/cli/files/templates'
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'async'
4
+ require 'async/semaphore'
5
+
6
+ require 'cuprum/cli/commands/ci/rspec_each_command'
7
+ require 'cuprum/cli/integrations/async/commands'
8
+
9
+ module Cuprum::Cli::Integrations::Async::Commands
10
+ # Command for running each RSpec file in its own process with parallelism.
11
+ class RSpecEachCommand < Cuprum::Cli::Commands::Ci::RSpecEachCommand
12
+ description 'Runs each RSpec file in an isolated process.'
13
+
14
+ full_name 'ci:rspec_each'
15
+
16
+ option :max_jobs,
17
+ type: :integer,
18
+ default: Cuprum::Cli::Integrations::Async.max_concurrent_tasks
19
+
20
+ private
21
+
22
+ def run_matching_files
23
+ Sync do
24
+ semaphore = ::Async::Semaphore.new(8)
25
+
26
+ matching_files.each do |filename|
27
+ semaphore.async { run_file(filename) }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/integrations/async'
4
+
5
+ module Cuprum::Cli::Integrations::Async
6
+ # Namespace for commands that rely on the Async integration.
7
+ module Commands
8
+ autoload :RSpecEachCommand,
9
+ 'cuprum/cli/integrations/async/commands/rspec_each_command'
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/integrations'
4
+
5
+ module Cuprum::Cli::Integrations
6
+ # Integration with the Async library.
7
+ #
8
+ # @see https://socketry.github.io/async/
9
+ module Async
10
+ autoload :Commands, 'cuprum/cli/integrations/async/commands'
11
+
12
+ class << self
13
+ # @return [Integer] The recommended maximum number of concurrent Async
14
+ # tasks. This value can be configured by setting an
15
+ # ASYNC_CONCURRENT_TASKS environment variable. The default value is 8.
16
+ def max_concurrent_tasks
17
+ [ENV.fetch('ASYNC_CONCURRENT_TASKS', '8').to_i, 1].max
18
+ end
19
+ end
20
+ end
21
+ end
@@ -7,36 +7,14 @@ require 'cuprum/cli/registry'
7
7
  module Cuprum::Cli::Integrations::Thor
8
8
  # Registers CLI commands by name and adds as Thor tasks.
9
9
  class Registry < Cuprum::Cli::Registry
10
- # Registers the command with the registry.
11
- #
12
- # Also registers a Thor task with compatible parameters and metadata.
13
- #
14
- # @param command [Class] the command class to register.
15
- # @param config [Hash] options for configuring the command.
16
- #
17
- # @option config arguments [Array] arguments to pass to the command on
18
- # initialization.
19
- # @option config description [String] the description for the command.
20
- # @option config full_description [String] the full description for the
21
- # command.
22
- # @option config full_name [String] the name under which to register the
23
- # command. Defaults to the value of command.full_name.
24
- # @option config options [Hash] options to pass to the command on
25
- # initialization.
26
- #
27
- # @raise [NameError] if a command is already registered with that name.
28
- #
29
- # @return [self]
30
- def register(command, **config)
31
- super.tap do
32
- name = config.fetch(:full_name, command.full_name)
33
- command = commands[name]
10
+ private
34
11
 
35
- Cuprum::Cli::Integrations::Thor::Task::Builder
36
- .new(command)
37
- .build(full_name: name)
38
- end
12
+ def add_registered_command(command)
13
+ super
14
+
15
+ Cuprum::Cli::Integrations::Thor::Task::Builder
16
+ .new(command)
17
+ .build(full_name: command.full_name)
39
18
  end
40
- alias add register
41
19
  end
42
20
  end
@@ -110,6 +110,10 @@ module Cuprum::Cli::Integrations::Thor
110
110
 
111
111
  return :numeric if NUMERIC_TYPES.include?(type)
112
112
 
113
+ # Thor can't handle heterogenous parameters, but it *can* pass them
114
+ # through as Strings.
115
+ return :string if type == 'object'
116
+
113
117
  type.to_sym
114
118
  end
115
119
 
@@ -183,9 +187,10 @@ module Cuprum::Cli::Integrations::Thor
183
187
  command_class.new(**command_dependencies).call(*args, **opts)
184
188
 
185
189
  handle_failure(result) if result.failure?
186
- rescue Cuprum::Cli::Options::UnknownOptionError,
187
- Cuprum::Cli::Arguments::ExtraArgumentsError => exception
188
- abort(exception.message)
190
+ rescue StandardError => exception
191
+ message = failure_message_for_exception(exception)
192
+
193
+ abort(message)
189
194
  end
190
195
  end
191
196
 
@@ -195,11 +200,36 @@ module Cuprum::Cli::Integrations::Thor
195
200
 
196
201
  def abort(*) = Kernel.abort(*)
197
202
 
203
+ def empty?(value)
204
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
205
+ end
206
+
207
+ def failure_message_for_error(error)
208
+ if error
209
+ message = "#{command_class} failed with error #{error.class}"
210
+
211
+ message << ": #{error.message}" unless empty?(error.message)
212
+ message << '.' unless message.end_with?('.')
213
+
214
+ return message
215
+ end
216
+
217
+ "#{command_class} failed but did not return an error message."
218
+ end
219
+
220
+ def failure_message_for_exception(exception)
221
+ message = "#{command_class} failed with exception: #{exception.message}"
222
+ message << " (#{exception.class})"
223
+
224
+ exception.backtrace.each do |line|
225
+ message << "\n\t" << line
226
+ end
227
+
228
+ message
229
+ end
230
+
198
231
  def handle_failure(result)
199
- message =
200
- result
201
- .error
202
- &.then { |err| "#{err.class.name}: #{err.message}" } || false
232
+ message = failure_message_for_error(result.error)
203
233
 
204
234
  abort(message)
205
235
  end
@@ -19,7 +19,7 @@ module Cuprum::Cli
19
19
  class AbstractCommandError < StandardError; end
20
20
 
21
21
  # Class methods to extend when including Metadata.
22
- module ClassMethods
22
+ module ClassMethods # rubocop:disable Metrics/ModuleLength
23
23
  # Marks the command as abstract.
24
24
  def abstract = @abstract = true
25
25
 
@@ -102,7 +102,7 @@ module Cuprum::Cli
102
102
  #
103
103
  # @return [String] the set full name.
104
104
  def full_name(value = UNDEFINED) # rubocop:disable Metrics/MethodLength
105
- return defined_full_name if value == UNDEFINED
105
+ return @full_name || defined_full_name if value == UNDEFINED
106
106
 
107
107
  if abstract?
108
108
  raise AbstractCommandError, abstract_command_message('set full_name')
@@ -119,6 +119,12 @@ module Cuprum::Cli
119
119
  @full_name = value
120
120
  end
121
121
 
122
+ # @return [true, false] true if the command defines a custom name;
123
+ # otherwise false.
124
+ def full_name?
125
+ !@full_name.nil?
126
+ end
127
+
122
128
  # The namespace for the command.
123
129
  #
124
130
  # A command's namespace is defined as the part of the full name prior to
@@ -152,7 +158,50 @@ module Cuprum::Cli
152
158
  # short_name will be "do_something".
153
159
  #
154
160
  # @return [String] the short name for the command.
155
- def short_name = full_name&.split(':')&.last
161
+ def short_name
162
+ full_name&.split(':')&.last
163
+ end
164
+
165
+ # Sets the description for the command.
166
+ #
167
+ # @param value [String] the description to set.
168
+ #
169
+ # @return [self]
170
+ #
171
+ # @see #description
172
+ def with_description(value)
173
+ description(value)
174
+
175
+ self
176
+ end
177
+
178
+ # Sets the full description for the command and returns the command.
179
+ #
180
+ # @param value [String] the full description to set.
181
+ #
182
+ # @return [self]
183
+ #
184
+ # @see #full_description
185
+ def with_full_description(value)
186
+ full_description(value)
187
+
188
+ self
189
+ end
190
+
191
+ # Sets the full name for the command and returns the command.
192
+ #
193
+ # The full name must be in snake_case format joined by ":".
194
+ #
195
+ # @param value [String] the full name to set.
196
+ #
197
+ # @return [self]
198
+ #
199
+ # @see #full_name
200
+ def with_full_name(value)
201
+ full_name(value)
202
+
203
+ self
204
+ end
156
205
 
157
206
  protected
158
207
 
@@ -182,13 +231,13 @@ module Cuprum::Cli
182
231
  end
183
232
 
184
233
  def defined_full_name
185
- return @full_name if @full_name ||= default_name
234
+ return @defined_full_name if @defined_full_name ||= default_name
186
235
 
187
236
  return unless superclass.respond_to?(:defined_full_name, true)
188
237
 
189
238
  return if superclass.name == 'Cuprum::Cli::Command'
190
239
 
191
- superclass.defined_full_name
240
+ superclass.full_name
192
241
  end
193
242
 
194
243
  private
@@ -39,9 +39,9 @@ module Cuprum::Cli # rubocop:disable Metrics/ModuleLength
39
39
  default: nil,
40
40
  description: nil,
41
41
  parameter_name: nil,
42
- required: false,
42
+ required: false, # rubocop:disable Layout/ExtraSpacing
43
43
  type: :string,
44
- variadic: false
44
+ variadic: false # rubocop:disable Layout/ExtraSpacing
45
45
  )
46
46
  name = name.to_sym
47
47
  aliases = Array(aliases).compact.map { |obj| obj.to_s.tr('_', '-') }
@@ -81,8 +81,8 @@ module Cuprum::Cli # rubocop:disable Metrics/ModuleLength
81
81
  value = default_value if blank?(value)
82
82
  value = value.to_s if value.is_a?(Symbol)
83
83
 
84
- return default_value_for_type if value.nil? && !required?
85
- return value if valid_option?(value)
84
+ return default_value_for_type if value.nil? && !required?
85
+ return normalize_variadic_keys(value) if valid_option?(value)
86
86
 
87
87
  raise Cuprum::Cli::Options::InvalidOptionError,
88
88
  invalid_option_message(original_value)
@@ -135,6 +135,12 @@ module Cuprum::Cli # rubocop:disable Metrics/ModuleLength
135
135
  "received #{value.inspect}"
136
136
  end
137
137
 
138
+ def normalize_variadic_keys(value)
139
+ return value unless variadic?
140
+
141
+ value.transform_keys { |key| tools.string_tools.underscore(key).to_sym }
142
+ end
143
+
138
144
  def tools
139
145
  SleepingKingStudios::Tools::Toolbelt.instance
140
146
  end
@@ -131,7 +131,7 @@ module Cuprum::Cli::Options
131
131
  # @raise [Cuprum::Cli::Options::InvalidOptionError] if any value does not
132
132
  # match the expected option type, or any required value is missing.
133
133
  def resolve_options(**values)
134
- values = option_values.merge(values)
134
+ values = option_values.merge(resolve_aliases(values))
135
135
  defined_options = options
136
136
  values, unknown_options = resolve_variadic_values(values)
137
137
 
@@ -188,6 +188,24 @@ module Cuprum::Cli::Options
188
188
  # :nocov:
189
189
  end
190
190
 
191
+ def resolve_aliases(values)
192
+ options.each do |key, option|
193
+ option.aliases.each do |item|
194
+ alias_key = item.to_sym
195
+
196
+ next unless values.key?(alias_key)
197
+
198
+ # Always remove the alias from the options.
199
+ alias_value = values.delete(alias_key)
200
+
201
+ # If the original value is present, it takes precedence.
202
+ values[key] ||= alias_value
203
+ end
204
+ end
205
+
206
+ values
207
+ end
208
+
191
209
  def resolve_variadic_values(values) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
192
210
  defined_options = options
193
211
  unknown_options = values.keys - options.keys