tty-option 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/README.md +1653 -1
  4. data/lib/tty/option.rb +63 -4
  5. data/lib/tty/option/aggregate_errors.rb +95 -0
  6. data/lib/tty/option/conversions.rb +126 -0
  7. data/lib/tty/option/converter.rb +63 -0
  8. data/lib/tty/option/deep_dup.rb +48 -0
  9. data/lib/tty/option/dsl.rb +105 -0
  10. data/lib/tty/option/dsl/arity.rb +49 -0
  11. data/lib/tty/option/dsl/conversion.rb +17 -0
  12. data/lib/tty/option/error_aggregator.rb +35 -0
  13. data/lib/tty/option/errors.rb +144 -0
  14. data/lib/tty/option/formatter.rb +389 -0
  15. data/lib/tty/option/inflection.rb +50 -0
  16. data/lib/tty/option/param_conversion.rb +34 -0
  17. data/lib/tty/option/param_permitted.rb +30 -0
  18. data/lib/tty/option/param_validation.rb +48 -0
  19. data/lib/tty/option/parameter.rb +310 -0
  20. data/lib/tty/option/parameter/argument.rb +18 -0
  21. data/lib/tty/option/parameter/environment.rb +20 -0
  22. data/lib/tty/option/parameter/keyword.rb +15 -0
  23. data/lib/tty/option/parameter/option.rb +99 -0
  24. data/lib/tty/option/parameters.rb +157 -0
  25. data/lib/tty/option/params.rb +122 -0
  26. data/lib/tty/option/parser.rb +57 -3
  27. data/lib/tty/option/parser/arguments.rb +166 -0
  28. data/lib/tty/option/parser/arity_check.rb +34 -0
  29. data/lib/tty/option/parser/environments.rb +169 -0
  30. data/lib/tty/option/parser/keywords.rb +158 -0
  31. data/lib/tty/option/parser/options.rb +273 -0
  32. data/lib/tty/option/parser/param_types.rb +51 -0
  33. data/lib/tty/option/parser/required_check.rb +36 -0
  34. data/lib/tty/option/pipeline.rb +38 -0
  35. data/lib/tty/option/result.rb +46 -0
  36. data/lib/tty/option/section.rb +26 -0
  37. data/lib/tty/option/sections.rb +56 -0
  38. data/lib/tty/option/usage.rb +166 -0
  39. data/lib/tty/option/usage_wrapper.rb +58 -0
  40. data/lib/tty/option/version.rb +3 -3
  41. 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