tty-option 0.2.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.
@@ -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