tty-option 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,30 +5,55 @@ require_relative "usage_wrapper"
5
5
 
6
6
  module TTY
7
7
  module Option
8
+ # Responsible for formatting help display
9
+ #
10
+ # @api private
8
11
  class Formatter
9
12
  include UsageWrapper
10
13
 
11
- SHORT_OPT_LENGTH = 4
14
+ BOOLEANS = [true, false].freeze
12
15
  DEFAULT_WIDTH = 80
13
- NEWLINE = "\n"
16
+ DOUBLE_SPACE = " "
14
17
  ELLIPSIS = "..."
18
+ EMPTY = ""
19
+ LIST_SEPARATOR = ", "
20
+ MAP_SEPARATOR = ":"
21
+ NEWLINE = "\n"
15
22
  SPACE = " "
16
23
 
17
- DEFAULT_PARAM_DISPLAY = ->(str) { str.to_s.upcase }
24
+ DEFAULT_NAME_SELECTOR = ->(param) { param.name }
18
25
  DEFAULT_ORDER = ->(params) { params.sort }
26
+ DEFAULT_PARAM_DISPLAY = ->(str) { str.to_s.upcase }
19
27
  NOOP_PROC = ->(param) { param }
20
- DEFAULT_NAME_SELECTOR = ->(param) { param.name }
21
28
 
29
+ # Generate help for parameters and usage
30
+ #
31
+ # @param [TTY::Option::Parameters] parameters
32
+ # the parameters to format
33
+ # @param [TTY::Option::Usage] usage
34
+ # the usage to format
35
+ #
36
+ # @return [String]
37
+ #
22
38
  # @api public
23
39
  def self.help(parameters, usage, **config, &block)
24
40
  new(parameters, usage, **config).help(&block)
25
41
  end
26
42
 
27
- attr_reader :width
28
-
29
- # Create a help formatter
43
+ # Create a Formatter instance
30
44
  #
31
- # @param [Parameters]
45
+ # @param [TTY::Option::Parameters] parameters
46
+ # the parameters to format
47
+ # @param [TTY::Option::Usage] usage
48
+ # the usage to format
49
+ # @param [Proc] param_display
50
+ # the parameter display formatter, by default, uppercases all chars
51
+ # @param [Integer] width
52
+ # the width at which to wrap the help display, by default 80 columns
53
+ # @param [Proc] order
54
+ # the order for displaying parameters, by default alphabetical
55
+ # @param [Integer] indent
56
+ # the indent for help display
32
57
  #
33
58
  # @api public
34
59
  def initialize(parameters, usage, param_display: DEFAULT_PARAM_DISPLAY,
@@ -51,7 +76,10 @@ module TTY
51
76
  }
52
77
  end
53
78
 
54
- # A formatted help usage information
79
+ # Generate help display
80
+ #
81
+ # @example
82
+ # formatter.help
55
83
  #
56
84
  # @yieldparam [TTY::Option::Sections] sections
57
85
  #
@@ -90,18 +118,50 @@ module TTY
90
118
  formatted.end_with?(NEWLINE) ? formatted : formatted + NEWLINE
91
119
  end
92
120
 
121
+ # Generate help header
122
+ #
123
+ # @example
124
+ # formatter.help_header
125
+ #
126
+ # @return [String]
127
+ #
128
+ # @api public
93
129
  def help_header
94
130
  "#{format_multiline(@usage.header, @indent)}#{NEWLINE}"
95
131
  end
96
132
 
133
+ # Generate help banner
134
+ #
135
+ # @example
136
+ # formatter.help_banner
137
+ #
138
+ # @return [String]
139
+ #
140
+ # @api public
97
141
  def help_banner
98
142
  (@usage.banner? ? @usage.banner : format_usage)
99
143
  end
100
144
 
145
+ # Generate help description
146
+ #
147
+ # @example
148
+ # formatter.help_description
149
+ #
150
+ # @return [String]
151
+ #
152
+ # @api public
101
153
  def help_description
102
154
  "#{NEWLINE}#{format_description}"
103
155
  end
104
156
 
157
+ # Generate help arguments
158
+ #
159
+ # @example
160
+ # formatter.help_arguments
161
+ #
162
+ # @return [String]
163
+ #
164
+ # @api public
105
165
  def help_arguments
106
166
  "#{NEWLINE}#{@space_indent}#{@section_names[:arguments]}#{NEWLINE}" +
107
167
  format_section(@parameters.arguments, ->(param) do
@@ -109,6 +169,14 @@ module TTY
109
169
  end)
110
170
  end
111
171
 
172
+ # Generate help keywords
173
+ #
174
+ # @example
175
+ # formatter.help_keywords
176
+ #
177
+ # @return [String]
178
+ #
179
+ # @api public
112
180
  def help_keywords
113
181
  "#{NEWLINE}#{@space_indent}#{@section_names[:keywords]}#{NEWLINE}" +
114
182
  format_section(@parameters.keywords, ->(param) do
@@ -116,28 +184,62 @@ module TTY
116
184
  end)
117
185
  end
118
186
 
187
+ # Generate help options
188
+ #
189
+ # @example
190
+ # formatter.help_options
191
+ #
192
+ # @return [String]
193
+ #
194
+ # @api public
119
195
  def help_options
120
196
  "#{NEWLINE}#{@space_indent}#{@section_names[:options]}#{NEWLINE}" +
121
197
  format_options
122
198
  end
123
199
 
200
+ # Generate help environment variables
201
+ #
202
+ # @example
203
+ # formatter.help_environments
204
+ #
205
+ # @return [String]
206
+ #
207
+ # @api public
124
208
  def help_environments
125
209
  "#{NEWLINE}#{@space_indent}#{@section_names[:env]}#{NEWLINE}" +
126
210
  format_section(@order.(@parameters.environments))
127
211
  end
128
212
 
213
+ # Generate help examples
214
+ #
215
+ # @example
216
+ # formatter.help_examples
217
+ #
218
+ # @return [String]
219
+ #
220
+ # @api public
129
221
  def help_examples
130
222
  "#{NEWLINE}#{@space_indent}#{@section_names[:examples]}#{NEWLINE}" +
131
223
  format_examples
132
224
  end
133
225
 
226
+ # Generate help footer
227
+ #
228
+ # @example
229
+ # formatter.help_footer
230
+ #
231
+ # @return [String]
232
+ #
233
+ # @api public
134
234
  def help_footer
135
235
  "#{NEWLINE}#{format_multiline(@usage.footer, @indent)}"
136
236
  end
137
237
 
138
238
  private
139
239
 
140
- # Provide a default usage banner
240
+ # Format default usage banner
241
+ #
242
+ # @return [String]
141
243
  #
142
244
  # @api private
143
245
  def format_usage
@@ -149,10 +251,12 @@ module TTY
149
251
  output << " [#{@param_display.("environment")}]" if @parameters.environments?
150
252
  output << " #{format_arguments_usage}" if @parameters.arguments?
151
253
  output << " #{format_keywords_usage}" if @parameters.keywords?
152
- usage + wrap(output.join, indent: usage.length, width: width)
254
+ usage + wrap(output.join, indent: usage.length, width: @width)
153
255
  end
154
256
 
155
- # Format arguments
257
+ # Format arguments usage
258
+ #
259
+ # @return [String]
156
260
  #
157
261
  # @api private
158
262
  def format_arguments_usage
@@ -165,7 +269,12 @@ module TTY
165
269
  end.join(SPACE)
166
270
  end
167
271
 
168
- # Provide an argument summary
272
+ # Format argument usage
273
+ #
274
+ # @param [TTY::Option::Parameter::Argument] arg
275
+ # the argument to format
276
+ #
277
+ # @return [String]
169
278
  #
170
279
  # @api private
171
280
  def format_argument_usage(arg)
@@ -175,6 +284,13 @@ module TTY
175
284
 
176
285
  # Format parameter usage
177
286
  #
287
+ # @param [TTY::Option::Parameter] param
288
+ # the parameter to format
289
+ # @param [String] param_name
290
+ # the parameter name
291
+ #
292
+ # @return [String]
293
+ #
178
294
  # @api private
179
295
  def format_parameter_usage(param, param_name)
180
296
  args = []
@@ -193,6 +309,8 @@ module TTY
193
309
 
194
310
  # Format keywords usage
195
311
  #
312
+ # @return [String]
313
+ #
196
314
  # @api private
197
315
  def format_keywords_usage
198
316
  return "" unless @parameters.keywords?
@@ -204,7 +322,12 @@ module TTY
204
322
  end.join(SPACE)
205
323
  end
206
324
 
207
- # Provide a keyword summary
325
+ # Format keyword usage
326
+ #
327
+ # @param [TTY::Option::Parameter::Keyword] kwarg
328
+ # the keyword to format
329
+ #
330
+ # @return [String]
208
331
  #
209
332
  # @api private
210
333
  def format_keyword_usage(kwarg)
@@ -212,7 +335,14 @@ module TTY
212
335
  format_parameter_usage(kwarg, param_name)
213
336
  end
214
337
 
215
- # Provide a keyword argument display format
338
+ # Format keyword name
339
+ #
340
+ # @param [TTY::Option::Parameter::Keyword] kwarg
341
+ # the keyword to format
342
+ # @param [Proc] param_display
343
+ # the parameter display formatter, by default, uppercases all chars
344
+ #
345
+ # @return [String]
216
346
  #
217
347
  # @api private
218
348
  def kwarg_param_display(kwarg, param_display = NOOP_PROC)
@@ -227,19 +357,18 @@ module TTY
227
357
  "#{kwarg_name}=#{conv_name}"
228
358
  end
229
359
 
230
- # Format a parameter section in the help display
231
- #
232
- # @param [String] parameters_name
233
- # the name of parameter type
360
+ # Format section parameters
234
361
  #
362
+ # @param [Array<TTY::Option::Parameter>] params
363
+ # the parameters to format
235
364
  # @param [Proc] name_selector
236
- # selects a name from the parameter, by defeault the name
365
+ # the parameter name selector, by default, calls the name
237
366
  #
238
367
  # @return [String]
239
368
  #
240
369
  # @api private
241
370
  def format_section(params, name_selector = DEFAULT_NAME_SELECTOR)
242
- longest_param = params.map(&name_selector).compact.max_by(&:length).length
371
+ longest_param = find_longest_parameter(params, &name_selector)
243
372
 
244
373
  params.reduce([]) do |acc, param|
245
374
  next acc if param.hidden?
@@ -248,44 +377,45 @@ module TTY
248
377
  end.join(NEWLINE)
249
378
  end
250
379
 
251
- # Format a section parameter line
380
+ # Format section parameter
381
+ #
382
+ # @param [TTY::Option::Parameter] param
383
+ # the parameter to format
384
+ # @param [Integer] longest_param
385
+ # the longest parameter length
386
+ # @param [Proc] name_selector
387
+ # the parameter name selector, by default, calls the name
252
388
  #
253
389
  # @return [String]
254
390
  #
255
391
  # @api private
256
392
  def format_section_parameter(param, longest_param, name_selector)
257
393
  line = []
258
- desc = []
259
- indent = @param_indent + longest_param + 2
260
394
  param_name = name_selector.(param)
395
+ description = parameter_description?(param)
396
+ template = description ? "%s%-#{longest_param}s" : "%s%s"
261
397
 
262
- if param.desc?
263
- line << format("%s%-#{longest_param}s", SPACE * @param_indent, param_name)
264
- desc << " #{param.desc}"
265
- else
266
- line << format("%s%s", SPACE * @param_indent, param_name)
267
- end
398
+ line << format(template, SPACE * @param_indent, param_name)
268
399
 
269
- if param.permit?
270
- desc << format(" (permitted: %s)", param.permit.join(", "))
400
+ if description
401
+ desc = format_parameter_description(param)
402
+ indent = @param_indent + longest_param + 2
403
+ line << wrap(desc, indent: indent, width: @width)
271
404
  end
272
405
 
273
- if (default = format_default(param))
274
- desc << default
275
- end
276
-
277
- line << wrap(desc.join, indent: indent, width: width)
278
406
  line.join
279
407
  end
280
408
 
281
409
  # Format multiline description
282
410
  #
411
+ # @return [String]
412
+ #
283
413
  # @api private
284
414
  def format_description
285
415
  format_multiline(@usage.desc, @indent)
286
416
  end
287
417
 
288
- # Returns all the options formatted to fit 80 columns
418
+ # Format options
289
419
  #
290
420
  # @return [String]
291
421
  #
@@ -293,69 +423,225 @@ module TTY
293
423
  def format_options
294
424
  return "" if @parameters.options.empty?
295
425
 
296
- longest_option = @parameters.options.map(&:long)
297
- .compact.max_by(&:length).length
298
- any_short = @parameters.options.map(&:short).compact.any?
299
426
  ordered_options = @order.(@parameters.options)
427
+ longest_short = find_longest_short_option
428
+ longest_long = find_longest_long_option
300
429
 
301
430
  ordered_options.reduce([]) do |acc, option|
302
431
  next acc if option.hidden?
303
- acc << format_option(option, longest_option, any_short)
432
+
433
+ acc << format_option(option, longest_short, longest_long)
304
434
  end.join(NEWLINE)
305
435
  end
306
436
 
437
+ # Find the longest short option
438
+ #
439
+ # @return [Integer, nil]
440
+ #
441
+ # @api private
442
+ def find_longest_short_option
443
+ short_options = @parameters.options.select(&:short?)
444
+ find_longest_parameter(short_options) do |option|
445
+ option.long? ? option.short_name : option.short
446
+ end
447
+ end
448
+
449
+ # Find the longest long option
450
+ #
451
+ # @return [Integer, nil]
452
+ #
453
+ # @api private
454
+ def find_longest_long_option
455
+ long_options = @parameters.options.select(&:long?)
456
+ find_longest_parameter(long_options, &:long)
457
+ end
458
+
459
+ # Find the longest parameter
460
+ #
461
+ # @param [Array<TTY::Option::Parameter>] params
462
+ # the parameters to search
463
+ #
464
+ # @yield [TTY::Option::Parameter]
465
+ #
466
+ # @return [Integer, nil]
467
+ #
468
+ # @api private
469
+ def find_longest_parameter(params, &name_selector)
470
+ params = params.reject(&:hidden?).map(&name_selector)
471
+
472
+ params.max_by(&:length).length if params.any?
473
+ end
474
+
307
475
  # Format an option
308
476
  #
477
+ # @param [TTY::Option::Parameter::Option] option
478
+ # the option to format
479
+ # @param [Integer, nil] longest_short
480
+ # the longest short option length or nil
481
+ # @param [Integer, nil] longest_long
482
+ # the longest long option length or nil
483
+ #
484
+ # @return [String]
485
+ #
309
486
  # @api private
310
- def format_option(option, longest_length, any_short)
487
+ def format_option(option, longest_short, longest_long)
311
488
  line = [@space_indent]
312
- desc = []
313
489
  indent = @indent
314
490
 
315
- if any_short
316
- short_option = option.short? ? option.short_name : SPACE
317
- line << format("%#{SHORT_OPT_LENGTH}s", short_option)
318
- indent += SHORT_OPT_LENGTH
491
+ if longest_short
492
+ line << " #{format_short_option(option, longest_short)}"
493
+ indent += line.last.length
319
494
  end
320
495
 
321
- # short & long option separator
322
- line << ((option.short? && option.long?) ? ", " : " ")
323
- indent += 2
496
+ if longest_long
497
+ separator = short_and_long_option_separator(option)
498
+ line << "#{separator}#{format_long_option(option, longest_long)}"
499
+ indent += line.last.length
500
+ end
324
501
 
502
+ if parameter_description?(option)
503
+ indent += 2
504
+ desc = format_parameter_description(option)
505
+ line << wrap(desc, indent: indent, width: @width)
506
+ end
507
+
508
+ line.join
509
+ end
510
+
511
+ # Format a short option
512
+ #
513
+ # @param [TTY::Option::Parameter::Option] option
514
+ # the option to format
515
+ # @param [Integer] longest
516
+ # the longest short option length
517
+ #
518
+ # @return [String]
519
+ #
520
+ # @api private
521
+ def format_short_option(option, longest)
522
+ if option.long?
523
+ format("%-#{longest}s", option.short_name)
524
+ elsif parameter_description?(option)
525
+ format("%-#{longest}s", option.short)
526
+ else
527
+ option.short
528
+ end
529
+ end
530
+
531
+ # Format a long option
532
+ #
533
+ # @param [TTY::Option::Parameter::Option] option
534
+ # the option to format
535
+ # @param [Integer] longest
536
+ # the longest long option length
537
+ #
538
+ # @return [String]
539
+ #
540
+ # @api private
541
+ def format_long_option(option, longest)
325
542
  if option.long?
326
- if option.desc?
327
- line << format("%-#{longest_length}s", option.long)
543
+ if parameter_description?(option)
544
+ format("%-#{longest}s", option.long)
328
545
  else
329
- line << option.long
546
+ option.long
330
547
  end
331
- else
332
- line << format("%-#{longest_length}s", SPACE)
548
+ elsif parameter_description?(option)
549
+ format("%-#{longest}s", SPACE)
333
550
  end
334
- indent += longest_length
551
+ end
335
552
 
336
- if option.desc?
337
- desc << " #{option.desc}"
553
+ # Short and long option separator
554
+ #
555
+ # @param [TTY::Option::Parameter::Option] option
556
+ # the option to separate short and long names
557
+ #
558
+ # @return [String]
559
+ #
560
+ # @api private
561
+ def short_and_long_option_separator(option)
562
+ if option.short? && option.long?
563
+ LIST_SEPARATOR
564
+ elsif option.long? || parameter_description?(option)
565
+ DOUBLE_SPACE
566
+ else
567
+ EMPTY
338
568
  end
339
- indent += 2
569
+ end
570
+
571
+ # Format a parameter description
572
+ #
573
+ # @param [TTY::Option::Parameter] param
574
+ # the parameter to format
575
+ #
576
+ # @return [String]
577
+ #
578
+ # @api private
579
+ def format_parameter_description(param)
580
+ desc = []
340
581
 
341
- if option.permit?
342
- desc << format(" (permitted: %s)", option.permit.join(","))
582
+ desc << " #{param.desc}" if param.desc?
583
+
584
+ if param.permit?
585
+ desc << SPACE unless param.desc?
586
+ desc << format_permitted(param.permit)
343
587
  end
344
588
 
345
- if (default = format_default(option))
589
+ if (default = format_default(param))
590
+ desc << SPACE unless param.desc?
346
591
  desc << default
347
592
  end
348
593
 
349
- line << wrap(desc.join, indent: indent, width: width)
594
+ desc.join
595
+ end
350
596
 
351
- line.join
597
+ # Check whether or not parameter has description
598
+ #
599
+ # @param [TTY::Option::Parameter] param
600
+ # the parameter to check for description
601
+ #
602
+ # @return [Boolean]
603
+ #
604
+ # @api private
605
+ def parameter_description?(param)
606
+ param.desc? || param.permit? || parameter_default?(param)
607
+ end
608
+
609
+ # Check whether or not parameter has default
610
+ #
611
+ # @param [TTY::Option::Parameter] param
612
+ # the parameter to check for default
613
+ #
614
+ # @return [Boolean]
615
+ #
616
+ # @api private
617
+ def parameter_default?(param)
618
+ param.default? && !BOOLEANS.include?(param.default)
619
+ end
620
+
621
+ # Format permitted values
622
+ #
623
+ # @param [Parameter] values
624
+ # the permitted values to format
625
+ #
626
+ # @return [String]
627
+ #
628
+ # @api private
629
+ def format_permitted(values)
630
+ format(" (permitted: %s)", values.map do |val|
631
+ val.respond_to?(:to_ary) ? val.join(MAP_SEPARATOR) : val
632
+ end.join(LIST_SEPARATOR))
352
633
  end
353
634
 
354
- # Format default value
635
+ # Format a default value
636
+ #
637
+ # @param [TTY::Option::Parameter] param
638
+ # the parameter to format
639
+ #
640
+ # @return [String]
355
641
  #
356
642
  # @api private
357
643
  def format_default(param)
358
- return if !param.default? || [true, false].include?(param.default)
644
+ return unless parameter_default?(param)
359
645
 
360
646
  if param.default.is_a?(String)
361
647
  format(" (default %p)", param.default)
@@ -366,6 +652,8 @@ module TTY
366
652
 
367
653
  # Format examples section
368
654
  #
655
+ # @return [String]
656
+ #
369
657
  # @api private
370
658
  def format_examples
371
659
  format_multiline(@usage.example, @param_indent)
@@ -373,15 +661,22 @@ module TTY
373
661
 
374
662
  # Format multiline content
375
663
  #
664
+ # @param [Array<Array<String>>] lines
665
+ # the lines to format
666
+ # @param [Integer] indent
667
+ # the indent for the lines
668
+ #
669
+ # @return [String]
670
+ #
376
671
  # @api private
377
672
  def format_multiline(lines, indent)
378
673
  last_index = lines.size - 1
379
674
  lines.map.with_index do |line, i|
380
675
  line.map do |part|
381
676
  part.split(NEWLINE).map do |p|
382
- wrap(p, indent: indent, width: width, indent_first: true)
677
+ wrap(p, indent: indent, width: @width, indent_first: true)
383
678
  end.join(NEWLINE)
384
- end.join(NEWLINE) + (last_index != i ? NEWLINE : "")
679
+ end.join(NEWLINE) + (last_index == i ? EMPTY : NEWLINE)
385
680
  end.join(NEWLINE)
386
681
  end
387
682
  end # Formatter
@@ -13,12 +13,16 @@ 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