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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +10 -0
- data/lib/cuprum/cli/argument.rb +2 -2
- data/lib/cuprum/cli/command.rb +2 -4
- data/lib/cuprum/cli/commands/ci/rspec_each_command.rb +34 -8
- data/lib/cuprum/cli/commands/ci.rb +8 -0
- data/lib/cuprum/cli/commands/file/new_command.rb +42 -49
- data/lib/cuprum/cli/commands/file.rb +8 -5
- data/lib/cuprum/cli/dependencies/clock/mock.rb +28 -0
- data/lib/cuprum/cli/dependencies/clock.rb +29 -0
- data/lib/cuprum/cli/dependencies/file_system/mock.rb +150 -18
- data/lib/cuprum/cli/dependencies/file_system.rb +145 -10
- data/lib/cuprum/cli/dependencies.rb +2 -0
- data/lib/cuprum/cli/errors.rb +0 -1
- data/lib/cuprum/cli/{commands/file/generate_file.rb → files/create_file.rb} +6 -48
- data/lib/cuprum/cli/{commands/file → files/engines}/render_erb.rb +6 -6
- data/lib/cuprum/cli/files/engines.rb +76 -0
- data/lib/cuprum/cli/{errors/files → files/errors}/file_not_writeable.rb +3 -3
- data/lib/cuprum/cli/files/errors/generator_error.rb +46 -0
- data/lib/cuprum/cli/{errors/files → files/errors}/missing_parameter.rb +5 -5
- data/lib/cuprum/cli/{errors/files → files/errors}/missing_template.rb +3 -3
- data/lib/cuprum/cli/{errors/files → files/errors}/template_error.rb +3 -3
- data/lib/cuprum/cli/files/errors.rb +19 -0
- data/lib/cuprum/cli/files/generator.rb +431 -0
- data/lib/cuprum/cli/files/generators/basic_generator.rb +15 -0
- data/lib/cuprum/cli/files/generators/rspec_generator.rb +19 -0
- data/lib/cuprum/cli/files/generators/ruby_generator.rb +32 -0
- data/lib/cuprum/cli/files/generators.rb +25 -0
- data/lib/cuprum/cli/files/template.rb +62 -0
- data/lib/cuprum/cli/files/templates/basic_template.rb +39 -0
- data/lib/cuprum/cli/files/templates/file_template.rb +60 -0
- data/lib/cuprum/cli/files/templates/string_template.rb +29 -0
- data/lib/cuprum/cli/files/templates.rb +59 -0
- data/lib/cuprum/cli/files.rb +18 -0
- data/lib/cuprum/cli/integrations/async/commands/rspec_each_command.rb +32 -0
- data/lib/cuprum/cli/integrations/async/commands.rb +11 -0
- data/lib/cuprum/cli/integrations/async.rb +21 -0
- data/lib/cuprum/cli/integrations/thor/registry.rb +7 -29
- data/lib/cuprum/cli/integrations/thor/task.rb +37 -7
- data/lib/cuprum/cli/metadata.rb +54 -5
- data/lib/cuprum/cli/option.rb +10 -4
- data/lib/cuprum/cli/options/class_methods.rb +19 -1
- data/lib/cuprum/cli/registry.rb +131 -55
- data/lib/cuprum/cli/rspec/deferred/dependencies/file_system_examples.rb +1303 -1001
- data/lib/cuprum/cli/rspec/deferred/generators_examples.rb +112 -0
- data/lib/cuprum/cli/rspec/deferred/metadata_examples.rb +300 -0
- data/lib/cuprum/cli/rspec/deferred/options_examples.rb +8 -2
- data/lib/cuprum/cli/rspec/deferred/registry_examples.rb +343 -108
- data/lib/cuprum/cli/rspec/deferred/templates_examples.rb +183 -0
- data/lib/cuprum/cli/version.rb +1 -1
- data/lib/cuprum/cli.rb +1 -0
- metadata +31 -14
- data/lib/cuprum/cli/commands/file/resolve_template.rb +0 -136
- data/lib/cuprum/cli/commands/file/templates.rb +0 -71
- data/lib/cuprum/cli/errors/files/template_not_resolved.rb +0 -54
- data/lib/cuprum/cli/errors/files.rb +0 -19
- /data/lib/cuprum/cli/{commands/file → files}/templates/rspec.rb.erb +0 -0
- /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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
187
|
-
|
|
188
|
-
|
|
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
|
data/lib/cuprum/cli/metadata.rb
CHANGED
|
@@ -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
|
|
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 @
|
|
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.
|
|
240
|
+
superclass.full_name
|
|
192
241
|
end
|
|
193
242
|
|
|
194
243
|
private
|
data/lib/cuprum/cli/option.rb
CHANGED
|
@@ -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
|
|
85
|
-
return 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
|