tty-option 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,24 +10,26 @@ module TTY
10
10
  #
11
11
  # @example
12
12
  # param = Parameter::Argument.create(:foo, convert: :int)
13
- # ParamConversion[param, "12"] # => 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) unless param.convert?
18
+ return Result.success(value) if !param.convert? || value.nil?
18
19
 
19
- case cast = param.convert
20
- when Proc
21
- Result.success(cast.(value))
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(Conversions[cast].(value))
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 :[] :call
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) unless param.permit?
16
+ return Result.success(value) if !param.permit? || value.nil?
17
17
 
18
- if param.permit.include?(value)
18
+ unpermitted = Array(value) - Array(param.permit)
19
+
20
+ if unpermitted.empty?
19
21
  Result.success(value)
20
22
  else
21
- Result.failure(UnpermittedArgument.new(param, value))
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 :[] :call
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: '\d+')
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, values)
16
- return Result.success(values) unless param.validate?
25
+ def call(param, value)
26
+ return Result.success(value) if !param.validate? || value.nil?
17
27
 
18
28
  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
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.size <= 1 ? result.first : 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 :[] :call
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
@@ -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 |key, val|
36
- case key.to_sym
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
- key, val = :required, check_required(!val)
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[key.to_sym] = val
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) { false }
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) { false }
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 %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"
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 an Array"
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
- !self.public_send(name).empty?
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
@@ -109,7 +109,7 @@ module TTY
109
109
  values = []
110
110
  arity = arg.arity.abs - 1
111
111
 
112
- arity.times do |i|
112
+ arity.times do
113
113
  break if @argv.empty?
114
114
  value = @argv.shift
115
115
  if argument?(value)
@@ -12,7 +12,7 @@ module TTY
12
12
  def add(param)
13
13
  @multiplies << param
14
14
  end
15
- alias :<< :add
15
+ alias << add
16
16
 
17
17
  # Check if parameter matches arity
18
18
  #
@@ -150,10 +150,10 @@ module TTY
150
150
  if allowed
151
151
  case value
152
152
  when Hash
153
- (@parsed[env_arg.key] ||= {}).merge!(value)
153
+ (@parsed[env_arg.key] ||= {}).merge!(value)
154
154
  else
155
155
  Array(value).each do |v|
156
- (@parsed[env_arg.key] ||= []) << v
156
+ (@parsed[env_arg.key] ||= []) << v
157
157
  end
158
158
  end
159
159
  else
@@ -142,7 +142,7 @@ module TTY
142
142
  (@parsed[kwarg.key] ||= {}).merge!(value)
143
143
  else
144
144
  Array(value).each do |v|
145
- (@parsed[kwarg.key] ||= []) << v
145
+ (@parsed[kwarg.key] ||= []) << v
146
146
  end
147
147
  end
148
148
  else
@@ -175,7 +175,12 @@ module TTY
175
175
  @remaining << long
176
176
  end
177
177
  elsif matching_options == 1
178
- value = long[opt.long_name.size..-1]
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(/^[^-][^=]*\z/).nil?
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(/^[\p{Lu}_\-\d]+=/).nil?
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(/^([^-=][\p{Ll}_\-\d]*)=([^=]+)/).nil?
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(/^-./).nil?
58
+ !value.match(OPTION_PARAMETER).nil?
47
59
  end
48
60
  end # ParamTypes
49
61
  end # Parser
@@ -12,7 +12,7 @@ module TTY
12
12
  def add(param)
13
13
  @required << param
14
14
  end
15
- alias :<< :add
15
+ alias << add
16
16
 
17
17
  def delete(param)
18
18
  @required.delete(param)
@@ -15,7 +15,7 @@ module TTY
15
15
  keywords: TTY::Option::Parser::Keywords,
16
16
  arguments: TTY::Option::Parser::Arguments,
17
17
  environments: TTY::Option::Parser::Environments
18
- }
18
+ }.freeze
19
19
 
20
20
  ARGUMENT_SEPARATOR = /^-{2,}$/.freeze
21
21
 
@@ -11,7 +11,7 @@ module TTY
11
11
  ParamConversion,
12
12
  ParamPermitted,
13
13
  ParamValidation
14
- ]
14
+ ].freeze
15
15
 
16
16
  # Create a param processing pipeline
17
17
  #
@@ -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