cuprum-cli 0.1.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 +7 -0
- data/CHANGELOG.md +34 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE +21 -0
- data/README.md +163 -0
- data/lib/cuprum/cli/argument.rb +172 -0
- data/lib/cuprum/cli/arguments/class_methods.rb +283 -0
- data/lib/cuprum/cli/arguments.rb +16 -0
- data/lib/cuprum/cli/coercion.rb +131 -0
- data/lib/cuprum/cli/command.rb +102 -0
- data/lib/cuprum/cli/commands/ci/report.rb +121 -0
- data/lib/cuprum/cli/commands/ci/rspec_command.rb +108 -0
- data/lib/cuprum/cli/commands/ci/rspec_each_command.rb +185 -0
- data/lib/cuprum/cli/commands/ci.rb +12 -0
- data/lib/cuprum/cli/commands/echo_command.rb +76 -0
- data/lib/cuprum/cli/commands/file/generate_file.rb +141 -0
- data/lib/cuprum/cli/commands/file/new_command.rb +86 -0
- data/lib/cuprum/cli/commands/file/render_erb.rb +88 -0
- data/lib/cuprum/cli/commands/file/resolve_template.rb +136 -0
- data/lib/cuprum/cli/commands/file/templates/rspec.rb.erb +14 -0
- data/lib/cuprum/cli/commands/file/templates/ruby.rb.erb +29 -0
- data/lib/cuprum/cli/commands/file/templates.rb +71 -0
- data/lib/cuprum/cli/commands/file.rb +14 -0
- data/lib/cuprum/cli/commands.rb +12 -0
- data/lib/cuprum/cli/dependencies/file_system/mock.rb +297 -0
- data/lib/cuprum/cli/dependencies/file_system.rb +247 -0
- data/lib/cuprum/cli/dependencies/standard_io/helpers.rb +138 -0
- data/lib/cuprum/cli/dependencies/standard_io/mock.rb +85 -0
- data/lib/cuprum/cli/dependencies/standard_io.rb +110 -0
- data/lib/cuprum/cli/dependencies/system_command/mock.rb +57 -0
- data/lib/cuprum/cli/dependencies/system_command.rb +147 -0
- data/lib/cuprum/cli/dependencies.rb +25 -0
- data/lib/cuprum/cli/errors/files/file_not_writeable.rb +42 -0
- data/lib/cuprum/cli/errors/files/missing_parameter.rb +71 -0
- data/lib/cuprum/cli/errors/files/missing_template.rb +36 -0
- data/lib/cuprum/cli/errors/files/template_error.rb +37 -0
- data/lib/cuprum/cli/errors/files/template_not_resolved.rb +54 -0
- data/lib/cuprum/cli/errors/files.rb +19 -0
- data/lib/cuprum/cli/errors/system_command_failure.rb +44 -0
- data/lib/cuprum/cli/errors.rb +11 -0
- data/lib/cuprum/cli/integrations/thor/arguments_parser.rb +99 -0
- data/lib/cuprum/cli/integrations/thor/registry.rb +42 -0
- data/lib/cuprum/cli/integrations/thor/task.rb +211 -0
- data/lib/cuprum/cli/integrations/thor.rb +14 -0
- data/lib/cuprum/cli/integrations.rb +8 -0
- data/lib/cuprum/cli/metadata.rb +215 -0
- data/lib/cuprum/cli/option.rb +165 -0
- data/lib/cuprum/cli/options/class_methods.rb +232 -0
- data/lib/cuprum/cli/options/quiet.rb +32 -0
- data/lib/cuprum/cli/options/verbose.rb +32 -0
- data/lib/cuprum/cli/options.rb +18 -0
- data/lib/cuprum/cli/registry.rb +141 -0
- data/lib/cuprum/cli/rspec/deferred/arguments_examples.rb +203 -0
- data/lib/cuprum/cli/rspec/deferred/ci/report_examples.rb +450 -0
- data/lib/cuprum/cli/rspec/deferred/ci.rb +8 -0
- data/lib/cuprum/cli/rspec/deferred/dependencies/file_system_examples.rb +1469 -0
- data/lib/cuprum/cli/rspec/deferred/dependencies.rb +8 -0
- data/lib/cuprum/cli/rspec/deferred/metadata_examples.rb +856 -0
- data/lib/cuprum/cli/rspec/deferred/options_examples.rb +234 -0
- data/lib/cuprum/cli/rspec/deferred/registry_examples.rb +451 -0
- data/lib/cuprum/cli/rspec/deferred.rb +8 -0
- data/lib/cuprum/cli/rspec.rb +8 -0
- data/lib/cuprum/cli/version.rb +59 -0
- data/lib/cuprum/cli.rb +47 -0
- metadata +173 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cuprum/cli'
|
|
4
|
+
require 'cuprum/cli/options'
|
|
5
|
+
|
|
6
|
+
module Cuprum::Cli # rubocop:disable Metrics/ModuleLength
|
|
7
|
+
# Data object representing a command option.
|
|
8
|
+
Option = Data.define(
|
|
9
|
+
:aliases,
|
|
10
|
+
:default,
|
|
11
|
+
:description,
|
|
12
|
+
:name,
|
|
13
|
+
:parameter_name,
|
|
14
|
+
:required,
|
|
15
|
+
:type,
|
|
16
|
+
:variadic
|
|
17
|
+
) do
|
|
18
|
+
# @param name [String, Symbol] the name of the option.
|
|
19
|
+
# @param aliases [Array<String, Symbol>] aliases for the option when parsing
|
|
20
|
+
# options from the command line.
|
|
21
|
+
# @param default [Object, Proc] the default value for the option. If given
|
|
22
|
+
# and the value of the option is nil, sets the option value to the default
|
|
23
|
+
# value.
|
|
24
|
+
# @param description [String] a short, human-readable description of the
|
|
25
|
+
# option.
|
|
26
|
+
# @param parameter_name [String] a representation of the possible values for
|
|
27
|
+
# the option.
|
|
28
|
+
# @param required [true, false] if true, raises an exception if the option
|
|
29
|
+
# is not provided to the command.
|
|
30
|
+
# @param type [Class, String, Symbol] the expected type of the option value
|
|
31
|
+
# as a Class or class name. If given, raises an exception if the option
|
|
32
|
+
# value is not an instance of the type. Defaults to :string.
|
|
33
|
+
# @param variadic [true, false] if true, the option is variadic and
|
|
34
|
+
# represents an hash of options provided to the command. Defaults to
|
|
35
|
+
# false.
|
|
36
|
+
def initialize( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
37
|
+
name:,
|
|
38
|
+
aliases: [],
|
|
39
|
+
default: nil,
|
|
40
|
+
description: nil,
|
|
41
|
+
parameter_name: nil,
|
|
42
|
+
required: false,
|
|
43
|
+
type: :string,
|
|
44
|
+
variadic: false
|
|
45
|
+
)
|
|
46
|
+
name = name.to_sym
|
|
47
|
+
aliases = Array(aliases).compact.map { |obj| obj.to_s.tr('_', '-') }
|
|
48
|
+
required = required ? true : false
|
|
49
|
+
type = type.to_sym if type.is_a?(String)
|
|
50
|
+
variadic = variadic ? true : false
|
|
51
|
+
|
|
52
|
+
super(
|
|
53
|
+
aliases:,
|
|
54
|
+
default:,
|
|
55
|
+
description:,
|
|
56
|
+
name:,
|
|
57
|
+
parameter_name:,
|
|
58
|
+
required:,
|
|
59
|
+
type:,
|
|
60
|
+
variadic:
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
alias_method :required?, :required
|
|
65
|
+
|
|
66
|
+
alias_method :variadic?, :variadic
|
|
67
|
+
|
|
68
|
+
# @overload def resolve(value)
|
|
69
|
+
# Validates the value for the current option.
|
|
70
|
+
#
|
|
71
|
+
# If the value is nil, applies the option default (if any).
|
|
72
|
+
#
|
|
73
|
+
# @param value [Object] the value to validate.
|
|
74
|
+
#
|
|
75
|
+
# @return [Object] the validated option value.
|
|
76
|
+
#
|
|
77
|
+
# @raise [Cuprum::Cli::Options::InvalidOptionError] if the value is
|
|
78
|
+
# missing (for a required option) or invalid.
|
|
79
|
+
def resolve(original_value)
|
|
80
|
+
value = original_value
|
|
81
|
+
value = default_value if blank?(value)
|
|
82
|
+
value = value.to_s if value.is_a?(Symbol)
|
|
83
|
+
|
|
84
|
+
return default_value_for_type if value.nil? && !required?
|
|
85
|
+
return value if valid_option?(value)
|
|
86
|
+
|
|
87
|
+
raise Cuprum::Cli::Options::InvalidOptionError,
|
|
88
|
+
invalid_option_message(original_value)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def blank?(value)
|
|
94
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def default_value
|
|
98
|
+
default.is_a?(Proc) ? default.call : default
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def default_value_for_type
|
|
102
|
+
return {} if variadic?
|
|
103
|
+
|
|
104
|
+
return false if type == :boolean && !required?
|
|
105
|
+
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def expected_hash_type
|
|
110
|
+
message = required? ? 'a non-empty Hash of' : 'a Hash of'
|
|
111
|
+
|
|
112
|
+
return "#{message} true or false values" if type == :boolean
|
|
113
|
+
|
|
114
|
+
plural_type =
|
|
115
|
+
tools.string_tools.pluralize(type.is_a?(Class) ? type.name : type)
|
|
116
|
+
|
|
117
|
+
"#{message} #{tools.string_tools.camelize(plural_type)}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def expected_type
|
|
121
|
+
return expected_hash_type if variadic?
|
|
122
|
+
|
|
123
|
+
case type
|
|
124
|
+
when :boolean
|
|
125
|
+
'true or false'
|
|
126
|
+
when Class
|
|
127
|
+
"an instance of #{type.name}"
|
|
128
|
+
else
|
|
129
|
+
"an instance of #{tools.string_tools.camelize(type.to_s)}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def invalid_option_message(value)
|
|
134
|
+
"invalid value for option :#{name} - expected #{expected_type}, " \
|
|
135
|
+
"received #{value.inspect}"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def tools
|
|
139
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def valid_option?(value)
|
|
143
|
+
return validate_option(value) unless variadic?
|
|
144
|
+
|
|
145
|
+
return false unless value.is_a?(Hash)
|
|
146
|
+
return false if required? && value.empty?
|
|
147
|
+
|
|
148
|
+
value.each_value.all? { |item| validate_option(item) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def validate_option(value) # rubocop:disable Naming/PredicateMethod
|
|
152
|
+
case type
|
|
153
|
+
when :boolean
|
|
154
|
+
value == true || value == false # rubocop:disable Style/MultipleComparison
|
|
155
|
+
when Class
|
|
156
|
+
value.is_a?(type)
|
|
157
|
+
else
|
|
158
|
+
expected = tools.string_tools.camelize(type.to_s)
|
|
159
|
+
expected = Object.const_get(expected)
|
|
160
|
+
|
|
161
|
+
value.is_a?(expected)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'weakref'
|
|
4
|
+
|
|
5
|
+
require 'cuprum/cli/option'
|
|
6
|
+
require 'cuprum/cli/options'
|
|
7
|
+
|
|
8
|
+
module Cuprum::Cli::Options
|
|
9
|
+
# Methods used to extend command class functionality for defining options.
|
|
10
|
+
module ClassMethods
|
|
11
|
+
# Helper class for defining command options.
|
|
12
|
+
class Builder
|
|
13
|
+
# @param command_class [Class] the command class.
|
|
14
|
+
# @param defined_options [Hash{Symbol => Cuprum::Cli::Option}] the options
|
|
15
|
+
# defined for the command.
|
|
16
|
+
def initialize(command_class:, defined_options:)
|
|
17
|
+
@command_class = command_class
|
|
18
|
+
@defined_options = defined_options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Class] the command class.
|
|
22
|
+
attr_reader :command_class
|
|
23
|
+
|
|
24
|
+
# @return [Hash{Symbol => Cuprum::Cli::Option}] the options
|
|
25
|
+
# defined for the command.
|
|
26
|
+
attr_reader :defined_options
|
|
27
|
+
|
|
28
|
+
# (see Cuprum::Cli::Options::ClassMethods#option)
|
|
29
|
+
def call(name, define_method: nil, define_predicate: nil, **options)
|
|
30
|
+
option = Cuprum::Cli::Option.new(name:, **options)
|
|
31
|
+
|
|
32
|
+
defined_options[option.name] = option
|
|
33
|
+
|
|
34
|
+
boolean_option = options[:type] == :boolean && !options[:variadic]
|
|
35
|
+
|
|
36
|
+
define_method = !boolean_option if define_method.nil?
|
|
37
|
+
define_predicate = boolean_option if define_predicate.nil?
|
|
38
|
+
|
|
39
|
+
define_method_for(option) if define_method
|
|
40
|
+
define_predicate_for(option) if define_predicate
|
|
41
|
+
|
|
42
|
+
option.name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def define_method_for(option)
|
|
48
|
+
command_class.define_method(option.name) { @options[option.name] }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def define_predicate_for(option)
|
|
52
|
+
command_class.define_method(:"#{option.name}?") do
|
|
53
|
+
value = @options[option.name]
|
|
54
|
+
|
|
55
|
+
return false if value.nil? || value == false
|
|
56
|
+
|
|
57
|
+
!(value.respond_to?(:empty?) && value.empty?)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @overload option(name, aliases: [], default: nil, description: nil, required: false, type: :string, **options)
|
|
63
|
+
# Defines an option for the command class.
|
|
64
|
+
#
|
|
65
|
+
# @param name [String, Symbol] the name of the option.
|
|
66
|
+
# @param aliases [Array<String, Symbol>] aliases for the option when
|
|
67
|
+
# parsing options from the command line.
|
|
68
|
+
# @param default [Object, Proc] the default value for the option. If given
|
|
69
|
+
# and the value of the option is nil, sets the option value to the
|
|
70
|
+
# default value.
|
|
71
|
+
# @param description [String] a short, human-readable description of the
|
|
72
|
+
# option.
|
|
73
|
+
# @param required [true, false] if true, raises an exception if the option
|
|
74
|
+
# is not provided to the command.
|
|
75
|
+
# @param type [Class, String, Symbol] the expected type of the option
|
|
76
|
+
# value as a Class or class name. If given, raises an exception if the
|
|
77
|
+
# option value is not an instance of the type. Defaults to :string.
|
|
78
|
+
# @param options [Hash] additional options for defining the option.
|
|
79
|
+
#
|
|
80
|
+
# @option options define_method [true, false] if true, defines a reader
|
|
81
|
+
# method for the option. Defaults to false for boolean options and true
|
|
82
|
+
# for all other options.
|
|
83
|
+
# @option options define_predicate [true, false] if true, defines a
|
|
84
|
+
# predicate method for the option, which returns true if the option is
|
|
85
|
+
# not nil and not empty. Defaults to true for boolean options and false
|
|
86
|
+
# for all other options.
|
|
87
|
+
def option(name, define_method: nil, define_predicate: nil, **)
|
|
88
|
+
handle_multiple_variadic_options(**)
|
|
89
|
+
|
|
90
|
+
options_builder
|
|
91
|
+
.call(name, define_method:, define_predicate:, **)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Assigns a predefined option value for the command.
|
|
95
|
+
#
|
|
96
|
+
# @param option [String, Symbol] the option to set.
|
|
97
|
+
# @param value [Object] the option value to append.
|
|
98
|
+
#
|
|
99
|
+
# @return [void]
|
|
100
|
+
def option_value(option, value)
|
|
101
|
+
defined_option_values[option.to_sym] = value
|
|
102
|
+
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @return [Hash{Symbol => Object}] predefined option values for the command.
|
|
107
|
+
def option_values = inherited_option_values.merge(defined_option_values)
|
|
108
|
+
|
|
109
|
+
# The defined options, including options defined on ancestor classes.
|
|
110
|
+
#
|
|
111
|
+
# @return [Hash{Symbol => Cuprum::Cli::Option}] the defined options.
|
|
112
|
+
def options
|
|
113
|
+
ancestors.reduce({}) do |hsh, ancestor|
|
|
114
|
+
return hsh if ancestor == Cuprum::Cli::Command
|
|
115
|
+
next hsh unless ancestor.respond_to?(:defined_options, true)
|
|
116
|
+
|
|
117
|
+
hsh.merge(ancestor.defined_options)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Validates the given option values against the defined class options.
|
|
122
|
+
#
|
|
123
|
+
# Also applies any default values from the defined options.
|
|
124
|
+
#
|
|
125
|
+
# @param values [Hash] the option values to resolve.
|
|
126
|
+
#
|
|
127
|
+
# @return [Hash] the option values with applied defaults.
|
|
128
|
+
#
|
|
129
|
+
# @raise [Cuprum::Cli::Options::UnknownOptionError] if any value does not
|
|
130
|
+
# have a corresponding defined option.
|
|
131
|
+
# @raise [Cuprum::Cli::Options::InvalidOptionError] if any value does not
|
|
132
|
+
# match the expected option type, or any required value is missing.
|
|
133
|
+
def resolve_options(**values)
|
|
134
|
+
values = option_values.merge(values)
|
|
135
|
+
defined_options = options
|
|
136
|
+
values, unknown_options = resolve_variadic_values(values)
|
|
137
|
+
|
|
138
|
+
unless unknown_options.empty?
|
|
139
|
+
raise Cuprum::Cli::Options::UnknownOptionError,
|
|
140
|
+
unknown_options_message(defined_options:, unknown_options:)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
defined_options.to_h do |key, option|
|
|
144
|
+
[key, option.resolve(values[key])]
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
protected
|
|
149
|
+
|
|
150
|
+
def defined_options
|
|
151
|
+
@defined_options ||= {}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def defined_option_values = @defined_option_values ||= {}
|
|
155
|
+
|
|
156
|
+
def inherited_option_values
|
|
157
|
+
return {} unless superclass.respond_to?(:inherited_option_values, true)
|
|
158
|
+
|
|
159
|
+
superclass.inherited_option_values.merge(superclass.defined_option_values)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
def handle_multiple_variadic_options(variadic: false, **)
|
|
165
|
+
return unless variadic
|
|
166
|
+
|
|
167
|
+
matching = defined_options.each_value.find(&:variadic?)
|
|
168
|
+
|
|
169
|
+
return unless matching
|
|
170
|
+
|
|
171
|
+
message = "command already defines variadic option :#{matching.name}"
|
|
172
|
+
|
|
173
|
+
raise ArgumentError, message
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def options_builder
|
|
177
|
+
(
|
|
178
|
+
@options_builder ||= WeakRef.new(
|
|
179
|
+
Builder.new(command_class: self, defined_options:)
|
|
180
|
+
)
|
|
181
|
+
).__getobj__
|
|
182
|
+
rescue RefError
|
|
183
|
+
# :nocov:
|
|
184
|
+
@options_builder = WeakRef.new(
|
|
185
|
+
Builder.new(command_class: self, defined_options:)
|
|
186
|
+
)
|
|
187
|
+
@arguments_builder.__getobj__
|
|
188
|
+
# :nocov:
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def resolve_variadic_values(values) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
192
|
+
defined_options = options
|
|
193
|
+
unknown_options = values.keys - options.keys
|
|
194
|
+
|
|
195
|
+
return [values, unknown_options] if unknown_options.empty?
|
|
196
|
+
|
|
197
|
+
variadic_key = defined_options.find { |_, value| value.variadic? }&.first
|
|
198
|
+
|
|
199
|
+
return [values, unknown_options] unless variadic_key
|
|
200
|
+
|
|
201
|
+
variadic_value = values.fetch(variadic_key, {})
|
|
202
|
+
|
|
203
|
+
unless variadic_value.is_a?(Hash)
|
|
204
|
+
return [values.except(*unknown_options), []]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
variadic_value = values.slice(*unknown_options).merge(variadic_value)
|
|
208
|
+
|
|
209
|
+
values =
|
|
210
|
+
values.except(*unknown_options).merge(variadic_key => variadic_value)
|
|
211
|
+
|
|
212
|
+
[values, []]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def tools
|
|
216
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def unknown_options_message(defined_options:, unknown_options:)
|
|
220
|
+
counted =
|
|
221
|
+
tools.integer_tools.pluralize(unknown_options.size, 'option')
|
|
222
|
+
unknown = unknown_options.map(&:inspect).join(', ')
|
|
223
|
+
message = "unrecognized #{counted} #{unknown} for #{name}"
|
|
224
|
+
|
|
225
|
+
return message if defined_options.empty?
|
|
226
|
+
|
|
227
|
+
valid_options = defined_options.keys.sort.map(&:inspect).join(', ')
|
|
228
|
+
|
|
229
|
+
"#{message} - valid options are #{valid_options}"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cuprum/cli/options'
|
|
4
|
+
|
|
5
|
+
module Cuprum::Cli::Options
|
|
6
|
+
# Defines --quiet option, which silences non-essential output.
|
|
7
|
+
module Quiet
|
|
8
|
+
DESCRIPTION = 'Silences non-essential console outputs.'
|
|
9
|
+
private_constant :DESCRIPTION
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def included(other)
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
other.option :quiet,
|
|
18
|
+
type: :boolean,
|
|
19
|
+
aliases: :q,
|
|
20
|
+
default: false,
|
|
21
|
+
description: DESCRIPTION
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# (see Cuprum::Cli::Dependencies::StandardIo::Helpers#say)
|
|
26
|
+
def say(message, quiet: false, **)
|
|
27
|
+
return if options.fetch(:quiet, false) && !quiet
|
|
28
|
+
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cuprum/cli/options'
|
|
4
|
+
|
|
5
|
+
module Cuprum::Cli::Options
|
|
6
|
+
# Defines --verbose option, which enables optional console outputs.
|
|
7
|
+
module Verbose
|
|
8
|
+
DESCRIPTION = 'Enables optional console outputs.'
|
|
9
|
+
private_constant :DESCRIPTION
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def included(other)
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
other.option :verbose,
|
|
18
|
+
type: :boolean,
|
|
19
|
+
aliases: :v,
|
|
20
|
+
default: false,
|
|
21
|
+
description: DESCRIPTION
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# (see Cuprum::Cli::Dependencies::StandardIo::Helpers#say)
|
|
26
|
+
def say(message, verbose: false, **)
|
|
27
|
+
return if verbose && !options.fetch(:verbose, false)
|
|
28
|
+
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cuprum/cli'
|
|
4
|
+
|
|
5
|
+
module Cuprum::Cli
|
|
6
|
+
# Namespace for functionality that implements command options.
|
|
7
|
+
module Options
|
|
8
|
+
autoload :ClassMethods, 'cuprum/cli/options/class_methods'
|
|
9
|
+
autoload :Quiet, 'cuprum/cli/options/quiet'
|
|
10
|
+
autoload :Verbose, 'cuprum/cli/options/verbose'
|
|
11
|
+
|
|
12
|
+
# Exception raised when a command receives an invalid value for an option.
|
|
13
|
+
class InvalidOptionError < StandardError; end
|
|
14
|
+
|
|
15
|
+
# Exception raised when a command receives an unrecognized option.
|
|
16
|
+
class UnknownOptionError < StandardError; end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cuprum/cli'
|
|
4
|
+
|
|
5
|
+
module Cuprum::Cli
|
|
6
|
+
# Registers CLI commands by name.
|
|
7
|
+
class Registry
|
|
8
|
+
# @return [Class, nil] the command registered with the given name, if any.
|
|
9
|
+
def [](name)
|
|
10
|
+
tools.assertions.validate_name(name, as: 'full_name')
|
|
11
|
+
|
|
12
|
+
registered_commands[name]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns a copy of the commands registered with the registry.
|
|
16
|
+
#
|
|
17
|
+
# @return [Hash{String => Class}] the registered commands.
|
|
18
|
+
def commands
|
|
19
|
+
registered_commands.dup.freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Registers the command with the registry.
|
|
23
|
+
#
|
|
24
|
+
# @param command [Class] the command class to register.
|
|
25
|
+
# @param config [Hash] options for configuring the command.
|
|
26
|
+
#
|
|
27
|
+
# @option config arguments [Array] arguments to pass to the command on
|
|
28
|
+
# initialization.
|
|
29
|
+
# @option config description [String] the description for the command.
|
|
30
|
+
# @option config full_description [String] the full description for the
|
|
31
|
+
# command.
|
|
32
|
+
# @option config full_name [String] the name under which to register the
|
|
33
|
+
# command. Defaults to the value of command.full_name.
|
|
34
|
+
# @option config options [Hash] options to pass to the command on
|
|
35
|
+
# initialization.
|
|
36
|
+
#
|
|
37
|
+
# @raise [NameError] if a command is already registered with that name.
|
|
38
|
+
#
|
|
39
|
+
# @return [self]
|
|
40
|
+
def register(command, **config)
|
|
41
|
+
validate_command(command)
|
|
42
|
+
|
|
43
|
+
name = config.fetch(:full_name, command.full_name)
|
|
44
|
+
|
|
45
|
+
validate_name(name)
|
|
46
|
+
|
|
47
|
+
command = build_command(command, **config) if any_present?(config)
|
|
48
|
+
|
|
49
|
+
registered_commands[name] = command
|
|
50
|
+
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
alias add register
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def any_present?(config)
|
|
58
|
+
return false if config.empty?
|
|
59
|
+
|
|
60
|
+
config.each_value.any? { |value| present?(value) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def build_command( # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/ParameterLists
|
|
64
|
+
command,
|
|
65
|
+
arguments: nil,
|
|
66
|
+
description: nil,
|
|
67
|
+
full_description: nil,
|
|
68
|
+
full_name: nil,
|
|
69
|
+
options: nil,
|
|
70
|
+
**
|
|
71
|
+
)
|
|
72
|
+
Class.new(command).tap do |command_class|
|
|
73
|
+
command_class.description(description) if present?(description)
|
|
74
|
+
|
|
75
|
+
command_class.full_name(full_name) if present?(full_name)
|
|
76
|
+
|
|
77
|
+
if present?(full_description)
|
|
78
|
+
command_class.full_description(full_description)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
arguments&.each do |argument|
|
|
82
|
+
command_class.argument_value(argument)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
options&.each do |option, value|
|
|
86
|
+
command_class.option_value(option, value)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def command_name(command)
|
|
92
|
+
command
|
|
93
|
+
.ancestors
|
|
94
|
+
.find { |ancestor| ancestor.is_a?(Class) && ancestor.name }
|
|
95
|
+
.name
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def invalid_name_format_message
|
|
99
|
+
'full_name does not match format category:sub_category:do_something'
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def present?(value)
|
|
103
|
+
return false if value.nil?
|
|
104
|
+
|
|
105
|
+
return false unless value.respond_to?(:empty?) && !value.empty?
|
|
106
|
+
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def registered_commands = @registered_commands ||= {}
|
|
111
|
+
|
|
112
|
+
def tools = SleepingKingStudios::Tools::Toolbelt.instance
|
|
113
|
+
|
|
114
|
+
def validate_command(command)
|
|
115
|
+
tools.assertions.validate_class(command, as: 'command')
|
|
116
|
+
tools.assertions.validate_inherits_from(
|
|
117
|
+
command,
|
|
118
|
+
as: 'command',
|
|
119
|
+
expected: Cuprum::Cli::Command
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def validate_name(name) # rubocop:disable Metrics/MethodLength
|
|
124
|
+
tools.assertions.validate_name(name, as: 'full_name')
|
|
125
|
+
tools.assertions.validate_matches(
|
|
126
|
+
name,
|
|
127
|
+
as: 'full_name',
|
|
128
|
+
expected: Cuprum::Cli::Metadata::FULL_NAME_FORMAT,
|
|
129
|
+
message: invalid_name_format_message
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return unless registered_commands.key?(name)
|
|
133
|
+
|
|
134
|
+
command = registered_commands[name]
|
|
135
|
+
message =
|
|
136
|
+
"command already registered as #{name} - #{command_name(command)}"
|
|
137
|
+
|
|
138
|
+
raise NameError, message
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|