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