tty-option 0.0.0 → 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 +4 -4
- data/CHANGELOG.md +1 -1
- data/README.md +1653 -1
- data/lib/tty/option.rb +63 -4
- data/lib/tty/option/aggregate_errors.rb +95 -0
- data/lib/tty/option/conversions.rb +126 -0
- data/lib/tty/option/converter.rb +63 -0
- data/lib/tty/option/deep_dup.rb +48 -0
- data/lib/tty/option/dsl.rb +105 -0
- data/lib/tty/option/dsl/arity.rb +49 -0
- data/lib/tty/option/dsl/conversion.rb +17 -0
- data/lib/tty/option/error_aggregator.rb +35 -0
- data/lib/tty/option/errors.rb +144 -0
- data/lib/tty/option/formatter.rb +389 -0
- data/lib/tty/option/inflection.rb +50 -0
- data/lib/tty/option/param_conversion.rb +34 -0
- data/lib/tty/option/param_permitted.rb +30 -0
- data/lib/tty/option/param_validation.rb +48 -0
- data/lib/tty/option/parameter.rb +310 -0
- data/lib/tty/option/parameter/argument.rb +18 -0
- data/lib/tty/option/parameter/environment.rb +20 -0
- data/lib/tty/option/parameter/keyword.rb +15 -0
- data/lib/tty/option/parameter/option.rb +99 -0
- data/lib/tty/option/parameters.rb +157 -0
- data/lib/tty/option/params.rb +122 -0
- data/lib/tty/option/parser.rb +57 -3
- data/lib/tty/option/parser/arguments.rb +166 -0
- data/lib/tty/option/parser/arity_check.rb +34 -0
- data/lib/tty/option/parser/environments.rb +169 -0
- data/lib/tty/option/parser/keywords.rb +158 -0
- data/lib/tty/option/parser/options.rb +273 -0
- data/lib/tty/option/parser/param_types.rb +51 -0
- data/lib/tty/option/parser/required_check.rb +36 -0
- data/lib/tty/option/pipeline.rb +38 -0
- data/lib/tty/option/result.rb +46 -0
- data/lib/tty/option/section.rb +26 -0
- data/lib/tty/option/sections.rb +56 -0
- data/lib/tty/option/usage.rb +166 -0
- data/lib/tty/option/usage_wrapper.rb +58 -0
- data/lib/tty/option/version.rb +3 -3
- metadata +37 -3
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
module Option
|
5
|
+
module Inflection
|
6
|
+
# Remove all modules/class names
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# demodulize("TTY::Option::ErrorAggregator")
|
10
|
+
# # => "ErrorAggregator"
|
11
|
+
#
|
12
|
+
# @return [String]
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
def demodulize(name)
|
16
|
+
name.to_s.split("::").last
|
17
|
+
end
|
18
|
+
module_function :demodulize
|
19
|
+
|
20
|
+
# Convert class name to underscore
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# underscore("ErrorAggregator")
|
24
|
+
# # => "error_aggregator"
|
25
|
+
#
|
26
|
+
# @return [String]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def underscore(name)
|
30
|
+
name.to_s
|
31
|
+
.gsub(/([A-Z\d]+)([A-Z][a-z])/, "\\1_\\2")
|
32
|
+
.gsub(/([a-z\d]+)([A-Z])/, "\\1_\\2")
|
33
|
+
.downcase
|
34
|
+
end
|
35
|
+
module_function :underscore
|
36
|
+
|
37
|
+
# Convert class name to dashed case
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# dasherize("ErrorAggregator")
|
41
|
+
# # => "error-aggregator"
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def dasherize(name)
|
45
|
+
underscore(name).tr("_", "-")
|
46
|
+
end
|
47
|
+
module_function :dasherize
|
48
|
+
end # Inflection
|
49
|
+
end # Option
|
50
|
+
end # TTY
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "conversions"
|
4
|
+
require_relative "result"
|
5
|
+
|
6
|
+
module TTY
|
7
|
+
module Option
|
8
|
+
module ParamConversion
|
9
|
+
# Convert parameter value to another type
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# param = Parameter::Argument.create(:foo, convert: :int)
|
13
|
+
# ParamConversion[param, "12"] # => 12
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def call(param, value)
|
17
|
+
return Result.success(value) unless param.convert?
|
18
|
+
|
19
|
+
case cast = param.convert
|
20
|
+
when Proc
|
21
|
+
Result.success(cast.(value))
|
22
|
+
else
|
23
|
+
Result.success(Conversions[cast].(value))
|
24
|
+
end
|
25
|
+
rescue ConversionError
|
26
|
+
Result.failure(InvalidConversionArgument.new(param, value))
|
27
|
+
end
|
28
|
+
module_function :call
|
29
|
+
|
30
|
+
alias :[] :call
|
31
|
+
module_function :[]
|
32
|
+
end # ParamConversion
|
33
|
+
end # Option
|
34
|
+
end # TTY
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "result"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
module Option
|
7
|
+
module ParamPermitted
|
8
|
+
# Convert parameter value to another type
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# param = Parameter::Argument.create(:foo, permit: %w[11 12 13])
|
12
|
+
# ParamPermitted[param, "12"] # => 12
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
def call(param, value)
|
16
|
+
return Result.success(value) unless param.permit?
|
17
|
+
|
18
|
+
if param.permit.include?(value)
|
19
|
+
Result.success(value)
|
20
|
+
else
|
21
|
+
Result.failure(UnpermittedArgument.new(param, value))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
module_function :call
|
25
|
+
|
26
|
+
alias :[] :call
|
27
|
+
module_function :[]
|
28
|
+
end # ParamPermitted
|
29
|
+
end # Option
|
30
|
+
end # TTY
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "result"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
module Option
|
7
|
+
module ParamValidation
|
8
|
+
# Validate parameter value against validation rule
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# param = Parameter::Option.create(:foo, validate: '\d+')
|
12
|
+
# ParamValidation[param, "12"] # => "12"
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
def call(param, values)
|
16
|
+
return Result.success(values) unless param.validate?
|
17
|
+
|
18
|
+
errors = []
|
19
|
+
|
20
|
+
result = Array(values).reduce([]) do |acc, value|
|
21
|
+
valid = case param.validate
|
22
|
+
when Proc
|
23
|
+
param.validate.(value)
|
24
|
+
when Regexp
|
25
|
+
!param.validate.match(value.to_s).nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
if valid
|
29
|
+
acc << value
|
30
|
+
else
|
31
|
+
errors << TTY::Option::InvalidArgument.new(param, value)
|
32
|
+
end
|
33
|
+
acc
|
34
|
+
end
|
35
|
+
|
36
|
+
if errors.empty?
|
37
|
+
Result.success(result.size <= 1 ? result.first : result)
|
38
|
+
else
|
39
|
+
Result.failure(errors)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
module_function :call
|
43
|
+
|
44
|
+
alias :[] :call
|
45
|
+
module_function :[]
|
46
|
+
end # ParamValidation
|
47
|
+
end # Option
|
48
|
+
end # TTY
|
@@ -0,0 +1,310 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "deep_dup"
|
4
|
+
require_relative "dsl/arity"
|
5
|
+
require_relative "dsl/conversion"
|
6
|
+
|
7
|
+
module TTY
|
8
|
+
module Option
|
9
|
+
class Parameter
|
10
|
+
include Comparable
|
11
|
+
include DSL::Arity
|
12
|
+
include DSL::Conversion
|
13
|
+
|
14
|
+
# A parameter factory
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
def self.create(key, **settings, &block)
|
18
|
+
new(key, **settings, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# The key under which this parameter is registered
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
attr_reader :key
|
25
|
+
|
26
|
+
# Create a parameter
|
27
|
+
#
|
28
|
+
# @param [Symbol] key
|
29
|
+
# the key to register this param under
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
def initialize(key, **settings, &block)
|
33
|
+
@key = key
|
34
|
+
@settings = {}
|
35
|
+
settings.each do |key, val|
|
36
|
+
case key.to_sym
|
37
|
+
when :arity
|
38
|
+
val = check_arity(val)
|
39
|
+
when :default
|
40
|
+
val = check_default(val)
|
41
|
+
when :optional
|
42
|
+
key, val = :required, check_required(!val)
|
43
|
+
when :permit
|
44
|
+
val = check_permitted(val)
|
45
|
+
when :validate
|
46
|
+
val = check_validation(val)
|
47
|
+
end
|
48
|
+
@settings[key.to_sym] = val
|
49
|
+
end
|
50
|
+
|
51
|
+
instance_eval(&block) if block_given?
|
52
|
+
end
|
53
|
+
|
54
|
+
def arity(value = (not_set = true))
|
55
|
+
if not_set
|
56
|
+
@settings.fetch(:arity) { default_arity }
|
57
|
+
else
|
58
|
+
@settings[:arity] = check_arity(value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_arity
|
63
|
+
1
|
64
|
+
end
|
65
|
+
|
66
|
+
# Determine minimum boundary for arity parameter
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
def min_arity
|
70
|
+
arity < 0 ? arity.abs - 1 : arity
|
71
|
+
end
|
72
|
+
|
73
|
+
# Check if multiple occurrences are allowed
|
74
|
+
#
|
75
|
+
# @return [Boolean]
|
76
|
+
#
|
77
|
+
# @api public
|
78
|
+
def multiple?
|
79
|
+
arity < 0 || 1 < arity.abs
|
80
|
+
end
|
81
|
+
|
82
|
+
def convert(value = (not_set = true))
|
83
|
+
if not_set
|
84
|
+
@settings[:convert]
|
85
|
+
else
|
86
|
+
@settings[:convert] = value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def convert?
|
91
|
+
@settings.key?(:convert) && !@settings[:convert].nil?
|
92
|
+
end
|
93
|
+
|
94
|
+
def default(value = (not_set = true))
|
95
|
+
if not_set
|
96
|
+
@settings[:default]
|
97
|
+
else
|
98
|
+
@settings[:default] = check_default(value)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
alias defaults default
|
102
|
+
|
103
|
+
def default?
|
104
|
+
@settings.key?(:default) && !@settings[:default].nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
def desc(value = (not_set = true))
|
108
|
+
if not_set
|
109
|
+
@settings[:desc]
|
110
|
+
else
|
111
|
+
@settings[:desc] = value
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def desc?
|
116
|
+
@settings.key?(:desc) && !@settings[:desc].nil?
|
117
|
+
end
|
118
|
+
|
119
|
+
# Check if this options is multi argument
|
120
|
+
#
|
121
|
+
# @api public
|
122
|
+
def multi_argument?
|
123
|
+
!convert.to_s.match(/list|array|map|hash|\w+s/).nil?
|
124
|
+
end
|
125
|
+
|
126
|
+
def optional
|
127
|
+
@settings[:required] = false
|
128
|
+
end
|
129
|
+
|
130
|
+
def optional?
|
131
|
+
!required?
|
132
|
+
end
|
133
|
+
|
134
|
+
def required
|
135
|
+
@settings[:required] = check_required(true)
|
136
|
+
end
|
137
|
+
|
138
|
+
def required?
|
139
|
+
@settings.fetch(:required) { false }
|
140
|
+
end
|
141
|
+
|
142
|
+
def hidden
|
143
|
+
@settings[:hidden] = true
|
144
|
+
end
|
145
|
+
|
146
|
+
def hidden?
|
147
|
+
@settings.fetch(:hidden) { false }
|
148
|
+
end
|
149
|
+
|
150
|
+
def display?
|
151
|
+
desc? && !hidden?
|
152
|
+
end
|
153
|
+
|
154
|
+
def permit(value = (not_set = true))
|
155
|
+
if not_set
|
156
|
+
@settings[:permit]
|
157
|
+
else
|
158
|
+
@settings[:permit] = check_permitted(value)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def permit?
|
163
|
+
@settings.key?(:permit) && !@settings[:permit].nil?
|
164
|
+
end
|
165
|
+
|
166
|
+
def name(value = (not_set = true))
|
167
|
+
if not_set
|
168
|
+
@settings.fetch(:name) { default_name }
|
169
|
+
else
|
170
|
+
@settings[:name] = value
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def default_name
|
175
|
+
key.to_s.tr("_", "-")
|
176
|
+
end
|
177
|
+
|
178
|
+
def validate(value = (not_set = true))
|
179
|
+
if not_set
|
180
|
+
@settings[:validate]
|
181
|
+
else
|
182
|
+
@settings[:validate] = check_validation(value)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def validate?
|
187
|
+
@settings.key?(:validate) && !@settings[:validate].nil?
|
188
|
+
end
|
189
|
+
|
190
|
+
def to_sym
|
191
|
+
self.class.name.to_s.split(/::/).last.downcase.to_sym
|
192
|
+
end
|
193
|
+
|
194
|
+
# Compare this parameter name with the other
|
195
|
+
#
|
196
|
+
# @api public
|
197
|
+
def <=>(other)
|
198
|
+
name <=> other.name
|
199
|
+
end
|
200
|
+
|
201
|
+
# Compare parameters for equality based on type and name
|
202
|
+
#
|
203
|
+
# @api public
|
204
|
+
def ==(other)
|
205
|
+
return false unless instance_of?(other.class)
|
206
|
+
name == other.name && to_h == other.to_h
|
207
|
+
end
|
208
|
+
|
209
|
+
# Compare parameters for equality based on type and name
|
210
|
+
#
|
211
|
+
# @api public
|
212
|
+
def eql?(other)
|
213
|
+
return false unless instance_of?(other.class)
|
214
|
+
name.eql?(other.name) && to_h.eql?(other.to_h)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Return a hash of this parameter settings
|
218
|
+
#
|
219
|
+
# @return [Hash] the names and values of this parameter
|
220
|
+
#
|
221
|
+
# @api public
|
222
|
+
def to_h(&block)
|
223
|
+
if block_given?
|
224
|
+
@settings.each_with_object({}) do |(key, val), acc|
|
225
|
+
k, v = *block.(key, val)
|
226
|
+
acc[k] = v
|
227
|
+
end
|
228
|
+
else
|
229
|
+
DeepDup.deep_dup(@settings)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Make a duplicate of this parameter
|
234
|
+
#
|
235
|
+
# @api public
|
236
|
+
def dup
|
237
|
+
super.tap do |param|
|
238
|
+
param.instance_variable_set(:@key, DeepDup.deep_dup(@key))
|
239
|
+
param.instance_variable_set(:@settings, DeepDup.deep_dup(@settings))
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
# @api private
|
246
|
+
def check_arity(value)
|
247
|
+
if value.nil?
|
248
|
+
raise ConfigurationError,
|
249
|
+
"#{to_sym} '#{name}' arity needs to be an Integer"
|
250
|
+
end
|
251
|
+
|
252
|
+
case value.to_s
|
253
|
+
when %r{\*|any} then value = -1
|
254
|
+
when %r{\+} then value = -2
|
255
|
+
else value = value.to_i
|
256
|
+
end
|
257
|
+
|
258
|
+
if value.zero?
|
259
|
+
raise ConfigurationError, "#{to_sym} '#{name}' arity cannot be zero"
|
260
|
+
end
|
261
|
+
value
|
262
|
+
end
|
263
|
+
|
264
|
+
# @api private
|
265
|
+
def check_permitted(value)
|
266
|
+
if value.respond_to?(:include?)
|
267
|
+
value
|
268
|
+
else
|
269
|
+
raise ConfigurationError,
|
270
|
+
"#{to_sym} '#{name}' permitted value needs to be an Array"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def check_default(value)
|
275
|
+
if !value.nil? && required?
|
276
|
+
raise ConfigurationError,
|
277
|
+
"#{to_sym} '#{name}' cannot have default value and be required"
|
278
|
+
else
|
279
|
+
value
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# @api private
|
284
|
+
def check_required(value)
|
285
|
+
if value && default?
|
286
|
+
raise ConfigurationError,
|
287
|
+
"#{to_sym} '#{name}' cannot be required and have default value"
|
288
|
+
else
|
289
|
+
value
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# @api private
|
294
|
+
def check_validation(value)
|
295
|
+
case value
|
296
|
+
when NilClass
|
297
|
+
raise ConfigurationError,
|
298
|
+
"#{to_sym} '#{name}' validation needs to be a Proc or a Regexp"
|
299
|
+
when Proc
|
300
|
+
value
|
301
|
+
when Regexp, String
|
302
|
+
Regexp.new(value.to_s)
|
303
|
+
else
|
304
|
+
raise ConfigurationError,
|
305
|
+
"#{to_sym} '#{name}' validation can only be a Proc or a Regexp"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end # Parameter
|
309
|
+
end # Option
|
310
|
+
end # TTY
|