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.
@@ -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