tty-option 0.1.0 → 0.3.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 +41 -1
- data/LICENSE.txt +1 -1
- data/README.md +1204 -492
- data/lib/tty/option/aggregate_errors.rb +3 -3
- data/lib/tty/option/const.rb +17 -0
- data/lib/tty/option/conversions.rb +52 -34
- data/lib/tty/option/converter.rb +2 -2
- data/lib/tty/option/deep_dup.rb +43 -20
- data/lib/tty/option/dsl.rb +5 -5
- data/lib/tty/option/errors.rb +33 -3
- data/lib/tty/option/formatter.rb +369 -74
- data/lib/tty/option/param_conversion.rb +11 -9
- data/lib/tty/option/param_permitted.rb +8 -4
- data/lib/tty/option/param_validation.rb +125 -21
- data/lib/tty/option/parameter.rb +23 -15
- data/lib/tty/option/parameters.rb +15 -1
- data/lib/tty/option/parser/arguments.rb +1 -1
- data/lib/tty/option/parser/arity_check.rb +1 -1
- data/lib/tty/option/parser/environments.rb +2 -2
- data/lib/tty/option/parser/keywords.rb +1 -1
- data/lib/tty/option/parser/options.rb +6 -1
- data/lib/tty/option/parser/param_types.rb +16 -4
- data/lib/tty/option/parser/required_check.rb +1 -1
- data/lib/tty/option/parser.rb +1 -1
- data/lib/tty/option/pipeline.rb +1 -1
- data/lib/tty/option/result.rb +25 -3
- data/lib/tty/option/usage.rb +58 -1
- data/lib/tty/option/usage_wrapper.rb +3 -2
- data/lib/tty/option/version.rb +1 -1
- metadata +8 -6
@@ -10,24 +10,26 @@ module TTY
|
|
10
10
|
#
|
11
11
|
# @example
|
12
12
|
# param = Parameter::Argument.create(:foo, convert: :int)
|
13
|
-
# ParamConversion[param, "12"]
|
13
|
+
# result = ParamConversion[param, "12"]
|
14
|
+
# result.value # => 12
|
14
15
|
#
|
15
16
|
# @api public
|
16
17
|
def call(param, value)
|
17
|
-
return Result.success(value)
|
18
|
+
return Result.success(value) if !param.convert? || value.nil?
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
cast = param.convert
|
21
|
+
cast = cast.is_a?(Proc) ? cast : Conversions[cast]
|
22
|
+
converted = cast.(value)
|
23
|
+
|
24
|
+
if converted == Const::Undefined
|
25
|
+
Result.failure(InvalidConversionArgument.new(param, value))
|
22
26
|
else
|
23
|
-
Result.success(
|
27
|
+
Result.success(converted)
|
24
28
|
end
|
25
|
-
rescue ConversionError
|
26
|
-
Result.failure(InvalidConversionArgument.new(param, value))
|
27
29
|
end
|
28
30
|
module_function :call
|
29
31
|
|
30
|
-
alias
|
32
|
+
alias [] call
|
31
33
|
module_function :[]
|
32
34
|
end # ParamConversion
|
33
35
|
end # Option
|
@@ -13,17 +13,21 @@ module TTY
|
|
13
13
|
#
|
14
14
|
# @api public
|
15
15
|
def call(param, value)
|
16
|
-
return Result.success(value)
|
16
|
+
return Result.success(value) if !param.permit? || value.nil?
|
17
17
|
|
18
|
-
|
18
|
+
unpermitted = Array(value) - Array(param.permit)
|
19
|
+
|
20
|
+
if unpermitted.empty?
|
19
21
|
Result.success(value)
|
20
22
|
else
|
21
|
-
Result.failure(
|
23
|
+
Result.failure(unpermitted.map do |val|
|
24
|
+
UnpermittedArgument.new(param, val)
|
25
|
+
end)
|
22
26
|
end
|
23
27
|
end
|
24
28
|
module_function :call
|
25
29
|
|
26
|
-
alias
|
30
|
+
alias [] call
|
27
31
|
module_function :[]
|
28
32
|
end # ParamPermitted
|
29
33
|
end # Option
|
@@ -4,45 +4,149 @@ require_relative "result"
|
|
4
4
|
|
5
5
|
module TTY
|
6
6
|
module Option
|
7
|
+
# Responsible for parameter validation
|
8
|
+
#
|
9
|
+
# @api private
|
7
10
|
module ParamValidation
|
8
11
|
# Validate parameter value against validation rule
|
9
12
|
#
|
10
13
|
# @example
|
11
|
-
# param = Parameter::Option.create(:foo, validate:
|
12
|
-
# ParamValidation[param, "12"] # => "12"
|
14
|
+
# param = TTY::Option::Parameter::Option.create(:foo, validate: "\d+")
|
15
|
+
# TTY::Option::ParamValidation[param, "12"] # => "12"
|
16
|
+
#
|
17
|
+
# @param [TTY::Option::Parameter] param
|
18
|
+
# the parameter with a validation rule
|
19
|
+
# @param [Object] value
|
20
|
+
# the value to validate
|
21
|
+
#
|
22
|
+
# @return [TTY::Option::Result]
|
13
23
|
#
|
14
24
|
# @api public
|
15
|
-
def call(param,
|
16
|
-
return Result.success(
|
25
|
+
def call(param, value)
|
26
|
+
return Result.success(value) if !param.validate? || value.nil?
|
17
27
|
|
18
28
|
errors = []
|
19
|
-
|
20
|
-
|
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
|
29
|
+
result = validate_object(param, value) do |error|
|
30
|
+
errors << error
|
34
31
|
end
|
35
32
|
|
36
33
|
if errors.empty?
|
37
|
-
Result.success(result
|
34
|
+
Result.success(result)
|
38
35
|
else
|
39
36
|
Result.failure(errors)
|
40
37
|
end
|
41
38
|
end
|
42
39
|
module_function :call
|
43
40
|
|
44
|
-
alias
|
41
|
+
alias [] call
|
45
42
|
module_function :[]
|
43
|
+
|
44
|
+
# Validate an object
|
45
|
+
#
|
46
|
+
# @param [TTY::Option::Parameter] param
|
47
|
+
# the parameter with a validation rule
|
48
|
+
# @param [Object] value
|
49
|
+
# the value to validate
|
50
|
+
#
|
51
|
+
# @yield [TTY::Option::InvalidArgument]
|
52
|
+
#
|
53
|
+
# @return [Object, nil]
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
def validate_object(param, value, &block)
|
57
|
+
case value
|
58
|
+
when Array
|
59
|
+
validate_array(param, value, &block)
|
60
|
+
when Hash
|
61
|
+
validate_hash(param, value, &block)
|
62
|
+
else
|
63
|
+
error = valid_or_error(param, value)
|
64
|
+
error ? block.(error) && return : value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
module_function :validate_object
|
68
|
+
private_class_method :validate_object
|
69
|
+
|
70
|
+
# Validate array values
|
71
|
+
#
|
72
|
+
# @param [TTY::Option::Parameter] param
|
73
|
+
# the parameter with a validation rule
|
74
|
+
# @param [Object] values
|
75
|
+
# the values in an array to validate
|
76
|
+
#
|
77
|
+
# @yield [TTY::Option::InvalidArgument]
|
78
|
+
#
|
79
|
+
# @return [Array]
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
def validate_array(param, values)
|
83
|
+
values.each_with_object([]) do |value, acc|
|
84
|
+
error = valid_or_error(param, value)
|
85
|
+
error ? yield(error) : acc << value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
module_function :validate_array
|
89
|
+
private_class_method :validate_array
|
90
|
+
|
91
|
+
# Validate hash values
|
92
|
+
#
|
93
|
+
# @param [TTY::Option::Parameter] param
|
94
|
+
# the parameter with a validation rule
|
95
|
+
# @param [Object] values
|
96
|
+
# the values in a hash to validate
|
97
|
+
#
|
98
|
+
# @yield [TTY::Option::InvalidArgument]
|
99
|
+
#
|
100
|
+
# @return [Hash]
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
def validate_hash(param, values)
|
104
|
+
values.each_with_object({}) do |value, acc|
|
105
|
+
error = valid_or_error(param, value)
|
106
|
+
error ? yield(error) : acc[value[0]] = value[1]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
module_function :validate_hash
|
110
|
+
private_class_method :validate_hash
|
111
|
+
|
112
|
+
# Create an error for an invalid parameter value
|
113
|
+
#
|
114
|
+
# @param [TTY::Option::Parameter] param
|
115
|
+
# the parameter with a validation rule
|
116
|
+
# @param [Object] value
|
117
|
+
# the value to validate
|
118
|
+
#
|
119
|
+
# @return [TTY::Option::InvalidArgument, nil]
|
120
|
+
#
|
121
|
+
# @api private
|
122
|
+
def valid_or_error(param, value)
|
123
|
+
return if valid?(param, value)
|
124
|
+
|
125
|
+
TTY::Option::InvalidArgument.new(param, value)
|
126
|
+
end
|
127
|
+
module_function :valid_or_error
|
128
|
+
private_class_method :valid_or_error
|
129
|
+
|
130
|
+
# Check whether a parameter value is valid or not
|
131
|
+
#
|
132
|
+
# @param [TTY::Option::Parameter] param
|
133
|
+
# the parameter with a validation rule
|
134
|
+
# @param [Object] value
|
135
|
+
# the value to validate
|
136
|
+
#
|
137
|
+
# @return [Boolean]
|
138
|
+
#
|
139
|
+
# @api private
|
140
|
+
def valid?(param, value)
|
141
|
+
case param.validate
|
142
|
+
when Proc
|
143
|
+
param.validate.(value)
|
144
|
+
when Regexp
|
145
|
+
!param.validate.match(value.to_s).nil?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
module_function :valid?
|
149
|
+
private_class_method :valid?
|
46
150
|
end # ParamValidation
|
47
151
|
end # Option
|
48
152
|
end # TTY
|
data/lib/tty/option/parameter.rb
CHANGED
@@ -11,6 +11,12 @@ module TTY
|
|
11
11
|
include DSL::Arity
|
12
12
|
include DSL::Conversion
|
13
13
|
|
14
|
+
# Zero or more parameter arity pattern
|
15
|
+
ZERO_OR_MORE_ARITY = /\*|any/.freeze
|
16
|
+
|
17
|
+
# One or more parameter arity pattern
|
18
|
+
ONE_OR_MORE_ARITY = /\+/.freeze
|
19
|
+
|
14
20
|
# A parameter factory
|
15
21
|
#
|
16
22
|
# @api public
|
@@ -32,20 +38,20 @@ module TTY
|
|
32
38
|
def initialize(key, **settings, &block)
|
33
39
|
@key = key
|
34
40
|
@settings = {}
|
35
|
-
settings.each do |
|
36
|
-
case
|
41
|
+
settings.each do |name, val|
|
42
|
+
case name.to_sym
|
37
43
|
when :arity
|
38
44
|
val = check_arity(val)
|
39
45
|
when :default
|
40
46
|
val = check_default(val)
|
41
47
|
when :optional
|
42
|
-
|
48
|
+
name, val = :required, check_required(!val)
|
43
49
|
when :permit
|
44
50
|
val = check_permitted(val)
|
45
51
|
when :validate
|
46
52
|
val = check_validation(val)
|
47
53
|
end
|
48
|
-
@settings[
|
54
|
+
@settings[name.to_sym] = val
|
49
55
|
end
|
50
56
|
|
51
57
|
instance_eval(&block) if block_given?
|
@@ -136,7 +142,7 @@ module TTY
|
|
136
142
|
end
|
137
143
|
|
138
144
|
def required?
|
139
|
-
@settings.fetch(:required
|
145
|
+
@settings.fetch(:required, false)
|
140
146
|
end
|
141
147
|
|
142
148
|
def hidden
|
@@ -144,7 +150,7 @@ module TTY
|
|
144
150
|
end
|
145
151
|
|
146
152
|
def hidden?
|
147
|
-
@settings.fetch(:hidden
|
153
|
+
@settings.fetch(:hidden, false)
|
148
154
|
end
|
149
155
|
|
150
156
|
def display?
|
@@ -203,6 +209,7 @@ module TTY
|
|
203
209
|
# @api public
|
204
210
|
def ==(other)
|
205
211
|
return false unless instance_of?(other.class)
|
212
|
+
|
206
213
|
name == other.name && to_h == other.to_h
|
207
214
|
end
|
208
215
|
|
@@ -211,6 +218,7 @@ module TTY
|
|
211
218
|
# @api public
|
212
219
|
def eql?(other)
|
213
220
|
return false unless instance_of?(other.class)
|
221
|
+
|
214
222
|
name.eql?(other.name) && to_h.eql?(other.to_h)
|
215
223
|
end
|
216
224
|
|
@@ -250,15 +258,14 @@ module TTY
|
|
250
258
|
end
|
251
259
|
|
252
260
|
case value.to_s
|
253
|
-
when
|
254
|
-
when
|
255
|
-
else value
|
256
|
-
end
|
257
|
-
|
258
|
-
|
259
|
-
|
261
|
+
when ZERO_OR_MORE_ARITY then -1
|
262
|
+
when ONE_OR_MORE_ARITY then -2
|
263
|
+
else value.to_i
|
264
|
+
end.tap do |val|
|
265
|
+
if val.zero?
|
266
|
+
raise ConfigurationError, "#{to_sym} '#{name}' arity cannot be zero"
|
267
|
+
end
|
260
268
|
end
|
261
|
-
value
|
262
269
|
end
|
263
270
|
|
264
271
|
# @api private
|
@@ -267,7 +274,8 @@ module TTY
|
|
267
274
|
value
|
268
275
|
else
|
269
276
|
raise ConfigurationError,
|
270
|
-
"#{to_sym} '#{name}' permitted value needs to be
|
277
|
+
"#{to_sym} '#{name}' permitted value needs to be " \
|
278
|
+
"an Array or a Hash"
|
271
279
|
end
|
272
280
|
end
|
273
281
|
|
@@ -13,7 +13,16 @@ module TTY
|
|
13
13
|
# @api private
|
14
14
|
def self.define_query(name)
|
15
15
|
define_method(:"#{name}?") do
|
16
|
-
!
|
16
|
+
!public_send(name).empty?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Define a predicate method to check if a parameter is supported
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def self.define_param_query(name)
|
24
|
+
define_method(:"#{name}?") do |param|
|
25
|
+
public_send(:"#{name}s").map(&:key).include?(param)
|
17
26
|
end
|
18
27
|
end
|
19
28
|
|
@@ -37,6 +46,11 @@ module TTY
|
|
37
46
|
define_query :options
|
38
47
|
define_query :environments
|
39
48
|
|
49
|
+
define_param_query :argument
|
50
|
+
define_param_query :keyword
|
51
|
+
define_param_query :option
|
52
|
+
define_param_query :environment
|
53
|
+
|
40
54
|
# A parameters list
|
41
55
|
#
|
42
56
|
# @api private
|
@@ -150,10 +150,10 @@ module TTY
|
|
150
150
|
if allowed
|
151
151
|
case value
|
152
152
|
when Hash
|
153
|
-
(@parsed[env_arg.key] ||=
|
153
|
+
(@parsed[env_arg.key] ||= {}).merge!(value)
|
154
154
|
else
|
155
155
|
Array(value).each do |v|
|
156
|
-
(@parsed[env_arg.key] ||=
|
156
|
+
(@parsed[env_arg.key] ||= []) << v
|
157
157
|
end
|
158
158
|
end
|
159
159
|
else
|
@@ -175,7 +175,12 @@ module TTY
|
|
175
175
|
@remaining << long
|
176
176
|
end
|
177
177
|
elsif matching_options == 1
|
178
|
-
|
178
|
+
# option stuck together with the argument
|
179
|
+
if sep.nil? && rest.empty?
|
180
|
+
value = long[opt.long_name.size..-1]
|
181
|
+
else
|
182
|
+
value = rest
|
183
|
+
end
|
179
184
|
else
|
180
185
|
@error_aggregator.(AmbiguousOption.new("option '#{long}' is ambiguous"))
|
181
186
|
end
|
@@ -4,6 +4,18 @@ module TTY
|
|
4
4
|
module Option
|
5
5
|
class Parser
|
6
6
|
module ParamTypes
|
7
|
+
# Positional argument pattern
|
8
|
+
ARGUMENT_PARAMETER = /^[^-][^=]*\z/.freeze
|
9
|
+
|
10
|
+
# Environment variable pattern
|
11
|
+
ENV_VAR_PARAMETER = /^[\p{Lu}_\-\d]+=/.freeze
|
12
|
+
|
13
|
+
# Keyword pattern
|
14
|
+
KEYWORD_PARAMETER = /^([^-=][\p{Ll}_\-\d]*)=([^=]+)/.freeze
|
15
|
+
|
16
|
+
# Option and flag pattern
|
17
|
+
OPTION_PARAMETER = /^-./.freeze
|
18
|
+
|
7
19
|
# Check if value looks like an argument
|
8
20
|
#
|
9
21
|
# @param [String] value
|
@@ -12,7 +24,7 @@ module TTY
|
|
12
24
|
#
|
13
25
|
# @api public
|
14
26
|
def argument?(value)
|
15
|
-
!value.match(
|
27
|
+
!value.match(ARGUMENT_PARAMETER).nil?
|
16
28
|
end
|
17
29
|
|
18
30
|
# Check if value is an environment variable
|
@@ -23,7 +35,7 @@ module TTY
|
|
23
35
|
#
|
24
36
|
# @api public
|
25
37
|
def env_var?(value)
|
26
|
-
!value.match(
|
38
|
+
!value.match(ENV_VAR_PARAMETER).nil?
|
27
39
|
end
|
28
40
|
|
29
41
|
# Check to see if value is a keyword
|
@@ -32,7 +44,7 @@ module TTY
|
|
32
44
|
#
|
33
45
|
# @api public
|
34
46
|
def keyword?(value)
|
35
|
-
!value.to_s.match(
|
47
|
+
!value.to_s.match(KEYWORD_PARAMETER).nil?
|
36
48
|
end
|
37
49
|
|
38
50
|
# Check if value looks like an option
|
@@ -43,7 +55,7 @@ module TTY
|
|
43
55
|
#
|
44
56
|
# @api public
|
45
57
|
def option?(value)
|
46
|
-
!value.match(
|
58
|
+
!value.match(OPTION_PARAMETER).nil?
|
47
59
|
end
|
48
60
|
end # ParamTypes
|
49
61
|
end # Parser
|
data/lib/tty/option/parser.rb
CHANGED
data/lib/tty/option/pipeline.rb
CHANGED
data/lib/tty/option/result.rb
CHANGED
@@ -18,29 +18,51 @@ module TTY
|
|
18
18
|
Failure.new(value)
|
19
19
|
end
|
20
20
|
|
21
|
+
# Wrapped value
|
22
|
+
#
|
23
|
+
# @api public
|
21
24
|
attr_reader :value
|
22
25
|
|
26
|
+
# Reason for failure
|
27
|
+
#
|
28
|
+
# @api public
|
23
29
|
attr_reader :error
|
24
30
|
|
31
|
+
# Check whether or not a result is a success monad
|
32
|
+
#
|
33
|
+
# @return [Boolean]
|
34
|
+
#
|
35
|
+
# @api public
|
25
36
|
def success?
|
26
37
|
is_a?(Success)
|
27
38
|
end
|
28
39
|
|
40
|
+
# Check whether or not a result is a failure class
|
41
|
+
#
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
# @api public
|
29
45
|
def failure?
|
30
46
|
is_a?(Failure)
|
31
47
|
end
|
32
48
|
|
49
|
+
# Success monad containing a value
|
50
|
+
#
|
51
|
+
# @api private
|
33
52
|
class Success < Result
|
34
53
|
def initialize(value)
|
35
54
|
@value = value
|
36
55
|
end
|
37
56
|
end
|
38
57
|
|
58
|
+
# Failure monad containing an error
|
59
|
+
#
|
60
|
+
# @api private
|
39
61
|
class Failure < Result
|
40
62
|
def initialize(error)
|
41
63
|
@error = error
|
42
64
|
end
|
43
65
|
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
66
|
+
end # Result
|
67
|
+
end # Option
|
68
|
+
end # TTY
|