travis-cl 1.2.4

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 (114) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +134 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +59 -0
  5. data/MIT_LICENSE.md +21 -0
  6. data/README.md +1283 -0
  7. data/cl.gemspec +30 -0
  8. data/examples/README.md +22 -0
  9. data/examples/_src/args/cast.erb.rb +100 -0
  10. data/examples/_src/args/opts.erb.rb +100 -0
  11. data/examples/_src/args/required.erb.rb +63 -0
  12. data/examples/_src/args/splat.erb.rb +55 -0
  13. data/examples/_src/gem.erb.rb +99 -0
  14. data/examples/_src/heroku.erb.rb +47 -0
  15. data/examples/_src/rakeish.erb.rb +54 -0
  16. data/examples/_src/readme/abstract.erb.rb +27 -0
  17. data/examples/_src/readme/alias.erb.rb +22 -0
  18. data/examples/_src/readme/arg.erb.rb +21 -0
  19. data/examples/_src/readme/arg_array.erb.rb +21 -0
  20. data/examples/_src/readme/arg_type.erb.rb +23 -0
  21. data/examples/_src/readme/args_splat.erb.rb +55 -0
  22. data/examples/_src/readme/array.erb.rb +21 -0
  23. data/examples/_src/readme/basic.erb.rb +72 -0
  24. data/examples/_src/readme/default.erb.rb +21 -0
  25. data/examples/_src/readme/deprecated.erb.rb +21 -0
  26. data/examples/_src/readme/deprecated_alias.erb.rb +21 -0
  27. data/examples/_src/readme/description.erb.rb +60 -0
  28. data/examples/_src/readme/downcase.erb.rb +21 -0
  29. data/examples/_src/readme/enum.erb.rb +35 -0
  30. data/examples/_src/readme/example.erb.rb +25 -0
  31. data/examples/_src/readme/format.erb.rb +35 -0
  32. data/examples/_src/readme/internal.erb.rb +28 -0
  33. data/examples/_src/readme/negate.erb.rb +37 -0
  34. data/examples/_src/readme/note.erb.rb +25 -0
  35. data/examples/_src/readme/opts.erb.rb +33 -0
  36. data/examples/_src/readme/opts_block.erb.rb +30 -0
  37. data/examples/_src/readme/range.erb.rb +35 -0
  38. data/examples/_src/readme/registry.erb.rb +18 -0
  39. data/examples/_src/readme/required.erb.rb +35 -0
  40. data/examples/_src/readme/requireds.erb.rb +46 -0
  41. data/examples/_src/readme/requires.erb.rb +35 -0
  42. data/examples/_src/readme/runner.erb.rb +29 -0
  43. data/examples/_src/readme/runner_custom.erb.rb +25 -0
  44. data/examples/_src/readme/secret.erb.rb +22 -0
  45. data/examples/_src/readme/see.erb.rb +25 -0
  46. data/examples/_src/readme/type.erb.rb +21 -0
  47. data/examples/args/cast +98 -0
  48. data/examples/args/opts +98 -0
  49. data/examples/args/required +62 -0
  50. data/examples/args/splat +58 -0
  51. data/examples/gem +97 -0
  52. data/examples/heroku +48 -0
  53. data/examples/rakeish +50 -0
  54. data/examples/readme/abstract +28 -0
  55. data/examples/readme/alias +21 -0
  56. data/examples/readme/arg +20 -0
  57. data/examples/readme/arg_array +20 -0
  58. data/examples/readme/arg_type +22 -0
  59. data/examples/readme/args_splat +58 -0
  60. data/examples/readme/array +20 -0
  61. data/examples/readme/basic +67 -0
  62. data/examples/readme/default +20 -0
  63. data/examples/readme/deprecated +20 -0
  64. data/examples/readme/deprecated_alias +20 -0
  65. data/examples/readme/description +56 -0
  66. data/examples/readme/downcase +20 -0
  67. data/examples/readme/enum +33 -0
  68. data/examples/readme/example +21 -0
  69. data/examples/readme/format +33 -0
  70. data/examples/readme/internal +24 -0
  71. data/examples/readme/negate +44 -0
  72. data/examples/readme/note +21 -0
  73. data/examples/readme/opts +31 -0
  74. data/examples/readme/opts_block +29 -0
  75. data/examples/readme/range +33 -0
  76. data/examples/readme/registry +15 -0
  77. data/examples/readme/required +33 -0
  78. data/examples/readme/requireds +46 -0
  79. data/examples/readme/requires +33 -0
  80. data/examples/readme/runner +30 -0
  81. data/examples/readme/runner_custom +22 -0
  82. data/examples/readme/secret +21 -0
  83. data/examples/readme/see +21 -0
  84. data/examples/readme/type +20 -0
  85. data/lib/cl/arg.rb +79 -0
  86. data/lib/cl/args.rb +92 -0
  87. data/lib/cl/cast.rb +55 -0
  88. data/lib/cl/cmd.rb +74 -0
  89. data/lib/cl/config/env.rb +52 -0
  90. data/lib/cl/config/files.rb +34 -0
  91. data/lib/cl/config.rb +30 -0
  92. data/lib/cl/ctx.rb +36 -0
  93. data/lib/cl/dsl.rb +182 -0
  94. data/lib/cl/errors.rb +119 -0
  95. data/lib/cl/help/cmd.rb +118 -0
  96. data/lib/cl/help/cmds.rb +26 -0
  97. data/lib/cl/help/format.rb +69 -0
  98. data/lib/cl/help/table.rb +58 -0
  99. data/lib/cl/help/usage.rb +26 -0
  100. data/lib/cl/help.rb +37 -0
  101. data/lib/cl/helper/suggest.rb +10 -0
  102. data/lib/cl/helper.rb +47 -0
  103. data/lib/cl/opt.rb +276 -0
  104. data/lib/cl/opts/validate.rb +117 -0
  105. data/lib/cl/opts.rb +114 -0
  106. data/lib/cl/parser/format.rb +63 -0
  107. data/lib/cl/parser.rb +70 -0
  108. data/lib/cl/runner/default.rb +86 -0
  109. data/lib/cl/runner/multi.rb +34 -0
  110. data/lib/cl/runner.rb +10 -0
  111. data/lib/cl/ui.rb +146 -0
  112. data/lib/cl/version.rb +3 -0
  113. data/lib/cl.rb +62 -0
  114. metadata +177 -0
data/README.md ADDED
@@ -0,0 +1,1283 @@
1
+ # Cl [![Build Status](https://travis-ci.org/svenfuchs/cl.svg?branch=master)](https://travis-ci.org/svenfuchs/cl) [![Code Climate](https://api.codeclimate.com/v1/badges/870e448eb8162d3e1ed7/maintainability)](https://codeclimate.com/github/svenfuchs/cl) [![Code Coverage](https://coveralls.io/repos/github/svenfuchs/cl/badge.svg?branch=master)](https://coveralls.io/github/svenfuchs/cl?branch=master) [![Gem Version](https://img.shields.io/gem/v/cl?cache=2019-08-10)](http://rubygems.org/gems/cl) [![Rubydocs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/svenfuchs/cl)
2
+
3
+ OptionParser based CLI support for rapid CLI development in an object-oriented
4
+ context.
5
+
6
+ This library wraps Ruby's OptionParser for parsing your options under the hood,
7
+ so you get all the goodness that the Ruby standard library provides.
8
+
9
+ On top of that it adds a rich and powerful DSL for defining, validating, and
10
+ normalizing options, as well as automatic and gorgeous help output (modeled
11
+ after `gem --help`).
12
+
13
+ Further documentation is available on [rubydoc.info](https://www.rubydoc.info/github/svenfuchs/cl)
14
+
15
+ Examples in this README are included from [examples/readme](https://github.com/svenfuchs/cl/tree/master/examples/readme).
16
+ More examples can be found in [examples](https://github.com/svenfuchs/cl/tree/master/examples).
17
+ All examples are guaranteed to be up to date by the way of being [verified](https://github.com/svenfuchs/cl/blob/master/.travis.yml#L14)
18
+ on CI.
19
+
20
+ ## Table of Contents
21
+
22
+ * [Basic Usage](#basic-usage)
23
+ * [Command Registry](#command-registry)
24
+ * [Runners](#runners)
25
+ * [Command DSL](#command-dsl)
26
+ * [Commands](#commands)
27
+ * [Description, Summary, Examples](#description-summary-examples)
28
+ * [Abstract](#abstract)
29
+ * [Arguments](#arguments)
30
+ * [Types](#types)
31
+ * [Splat](#splat)
32
+ * [Options](#options)
33
+ * [Aliases](#aliases)
34
+ * [Defaults](#defaults)
35
+ * [Deprecations](#deprecations)
36
+ * [Downcase](#downcase)
37
+ * [Enum](#enum)
38
+ * [Example](#example)
39
+ * [Format](#format)
40
+ * [Internal](#internal)
41
+ * [Min and Max](#min-and-max)
42
+ * [Negations](#negations)
43
+ * [Note](#note)
44
+ * [Secret](#secret)
45
+ * [See Also](#see-also)
46
+ * [Types](#types)
47
+ * [Required Options](#required-options)
48
+ * [Config Files](#config-files)
49
+ * [Environment Variables](#environment-variables)
50
+
51
+ ## Basic Usage
52
+
53
+ ```ruby
54
+ module Owners
55
+ class Add < Cl::Cmd
56
+ register :add
57
+
58
+ summary 'Add one or more owners to an existing owner group'
59
+
60
+ description <<~str
61
+ Use this command to add one or more owners to an existing
62
+ owner group.
63
+
64
+ [...]
65
+ str
66
+
67
+ args :owner
68
+
69
+ opt '-t', '--to TO', 'An existing owner group'
70
+
71
+ def run
72
+ # implement adding the owner as given in `owner` (as well as `args`)
73
+ # to the group given in `to` (as well as `opts[:to]`).
74
+ p owner: owner, to: to, to?: to?, args: args, opts: opts
75
+ end
76
+ end
77
+ end
78
+
79
+ # Running this, e.g. using `bin/owners add one,two --to group` will instantiate the
80
+ # class `Owners::Add`, and call the method `run` on it.
81
+
82
+ # e.g. bin/owners
83
+ #
84
+ # args normally would be ARGV
85
+ args = %w(add one --to group)
86
+
87
+ Cl.new('owners').run(args)
88
+
89
+ # Output:
90
+ #
91
+ # {:owner=>"one", :to=>"group", :to?=>true, :args=>["one"], :opts=>{:to=>"group"}}
92
+
93
+ Cl.new('owners').run(%w(add --help))
94
+
95
+ # Output:
96
+ #
97
+ # Usage: owners add [owner] [options]
98
+ #
99
+ # Summary:
100
+ #
101
+ # Add one or more owners to an existing owner group
102
+ #
103
+ # Description:
104
+ #
105
+ # Use this command to add one or more owners to an existing
106
+ # owner group.
107
+ #
108
+ # [...]
109
+ #
110
+ # Arguments:
111
+ #
112
+ # owner type: string
113
+ #
114
+ # Options:
115
+ #
116
+ # -t --to TO An existing owner group (type: string)
117
+ # --help Get help on this command
118
+
119
+ ```
120
+
121
+ ### Command Registry
122
+
123
+ Commands are Ruby classes that extend the class `Cl::Cmd`.
124
+
125
+ They register to a [Ruby class registry](https://github.com/svenfuchs/registry) in order
126
+ to decouple looking up command classes from their Ruby namespace.
127
+
128
+ For example:
129
+
130
+ ```ruby
131
+ module Cmd
132
+ class One < Cl::Cmd
133
+ register :one
134
+ end
135
+
136
+ class Two < Cl::Cmd
137
+ register :two
138
+ end
139
+ end
140
+
141
+ p Cl::Cmd[:one] # => Cmd::One
142
+ p Cl::Cmd[:two] # => Cmd::Two
143
+
144
+ ```
145
+
146
+ Commands can be registered like so:
147
+
148
+ ```ruby
149
+ module One
150
+ class Cmd < Cl::Cmd
151
+ register :'cmd:one'
152
+ end
153
+ end
154
+ ```
155
+
156
+ By default commands auto register themselves with the underscored name of the
157
+ last part of their class name (as seen in the example above). It is possible to
158
+ turn this off using:
159
+
160
+ ```ruby
161
+ Cl::Cmd.auto_register = false
162
+ ```
163
+
164
+ Command auto-registration can cause name clashes when namespaced commands have
165
+ the same demodulized class name. For example:
166
+
167
+ ```ruby
168
+ class Git < Cl::Cmd
169
+ # auto-registers as :git
170
+ end
171
+
172
+ module Heroku
173
+ class Git < Cl::Cmd
174
+ # also auto-registers as :git
175
+ end
176
+ end
177
+ ```
178
+
179
+ It is recommended to turn auto-registration off when using such module
180
+ structures.
181
+
182
+
183
+ ### Runners
184
+
185
+ Runners lookup the command to execute from the registry, by checking the
186
+ arguments given by the user for registered command keys.
187
+
188
+ With the two command classes `One` and `Two` from the example above (and
189
+ assuming that the executable that calls `Cl` is `bin/run`) the default runner
190
+ would recognize and run the following commands:
191
+
192
+ ```
193
+ $ bin/run one something else
194
+ # instantiates One, passing the args array `["something", "else"]`, and calls the instance method `run`
195
+
196
+ $ bin/run two
197
+ # instantiates Two, passing an empty args arry `[]`, and calls the instance method `run`
198
+ ```
199
+
200
+ The default runner also supports nested namespaces, and checks for command classes
201
+ with keys separated by colons. For instance:
202
+
203
+ ```ruby
204
+ module Git
205
+ class Pull < Cl::Cmd
206
+ register :'git:pull'
207
+
208
+ arg :branch
209
+
210
+ def run
211
+ p cmd: registry_key, args: args
212
+ end
213
+ end
214
+ end
215
+
216
+ # With this class registered (and assuming the executable that calls `Cl` is
217
+ # `bin/run`) the default runner would recognize and run it:
218
+ #
219
+ # $ bin/run git:pull master # instantiates Git::Pull, and passes ["master"] as args
220
+ # $ bin/run git pull master # does the same
221
+
222
+ Cl.new('run').run(%w(git:pull master))
223
+ # Output:
224
+ #
225
+ # {:cmd=>:"git:pull", :args=>["master"]}
226
+
227
+ Cl.new('run').run(%w(git pull master))
228
+ # Output:
229
+ #
230
+ # {:cmd=>:"git:pull", :args=>["master"]}
231
+
232
+ ```
233
+
234
+ Runners are registered on the module `Cl::Runner`. It is possible to register custom
235
+ runners, and use them by passing the option `runner` to `Cl.new`:
236
+
237
+ ```ruby
238
+ module Git
239
+ class Pull < Cl::Cmd
240
+ register :'git:pull'
241
+
242
+ arg :branch
243
+
244
+ def run
245
+ p cmd: registry_key, args: args
246
+ end
247
+ end
248
+ end
249
+
250
+ # With this class registered (and assuming the executable that calls `Cl` is
251
+ # `bin/run`) the default runner would recognize and run it:
252
+ #
253
+ # $ bin/run git:pull master # instantiates Git::Pull, and passes ["master"] as args
254
+ # $ bin/run git pull master # does the same
255
+
256
+ Cl.new('run').run(%w(git:pull master))
257
+ # Output:
258
+ #
259
+ # {:cmd=>:"git:pull", :args=>["master"]}
260
+
261
+ Cl.new('run').run(%w(git pull master))
262
+ # Output:
263
+ #
264
+ # {:cmd=>:"git:pull", :args=>["master"]}
265
+
266
+ ```
267
+
268
+ See `Cl::Runner::Default` for more details.
269
+
270
+ There also is an experimental runner `:multi`, which supports rake-style
271
+ execution of multiple commands at once, like so:
272
+
273
+ ```
274
+ bin/rake db:drop production -f db:create db:migrate production -v 1
275
+ ```
276
+
277
+ See the example [rakeish](blob/master/examples/rakeish) for more details.
278
+
279
+ ## Command DSL
280
+
281
+ The DSL is defined on the class body.
282
+
283
+ ### Commands
284
+
285
+ Commands are classes that are derived from the base class `Cl::Cmd`.
286
+
287
+ #### Description, Summary, Examples
288
+
289
+ The description, summary, and examples are used in the help output.
290
+
291
+ ```ruby
292
+ module Owners
293
+ class Add < Cl::Cmd
294
+ register :add
295
+
296
+ summary 'Add one or more owners to an existing owner group'
297
+
298
+ description <<~str
299
+ Use this command to add one or more owners to an existing
300
+ owner group.
301
+ str
302
+
303
+ examples <<~str
304
+ Adding a single user to the group admins:
305
+
306
+ owners add user --to admins
307
+
308
+ Adding a several users at once:
309
+
310
+ owners add one two three --to admins
311
+ str
312
+ end
313
+ end
314
+
315
+ Cl.new('owners').run(%w(add --help))
316
+
317
+ # Output:
318
+ #
319
+ # Usage: owners add [options]
320
+ #
321
+ # Summary:
322
+ #
323
+ # Add one or more owners to an existing owner group
324
+ #
325
+ # Description:
326
+ #
327
+ # Use this command to add one or more owners to an existing
328
+ # owner group.
329
+ #
330
+ # Options:
331
+ #
332
+ # --help Get help on this command
333
+ #
334
+ # Examples:
335
+ #
336
+ # Adding a single user to the group admins:
337
+ #
338
+ # owners add user --to admins
339
+ #
340
+ # Adding a several users at once:
341
+ #
342
+ # owners add one two three --to admins
343
+
344
+ ```
345
+
346
+ #### Abstract
347
+
348
+ Command base classes can be declared abstract in order to prevent them from
349
+ being identified as a runnable command and to omit them from help output.
350
+
351
+ This is only relevant if a command base class is registered. See [Command
352
+ Registry](#command-registry) for details.
353
+
354
+ ```ruby
355
+ class Base < Cl::Cmd
356
+ abstract
357
+ end
358
+
359
+ class Add < Base
360
+ register :add
361
+
362
+ def run
363
+ puts 'Success'
364
+ end
365
+ end
366
+
367
+ Cl.new('owners').run(%w(add))
368
+
369
+ # Output:
370
+ #
371
+ # Success
372
+
373
+ Cl.new('owners').run(%w(base))
374
+
375
+ # Output:
376
+ #
377
+ # Unknown command: base
378
+
379
+ ```
380
+
381
+ ### Arguments
382
+
383
+ Arguments can be declared like so:
384
+
385
+ ```ruby
386
+ arg :arg_name, description: 'arg description', type: :[array|string|integer|float|boolean]
387
+ ```
388
+
389
+ This will define an `attr_accessor` on the `Cmd` class. I.e. in the following
390
+ example the method `ownsers` will be available on the `Cmd` instance:
391
+
392
+ ```ruby
393
+ class Add < Cl::Cmd
394
+ register :add
395
+
396
+ arg :owner
397
+
398
+ def run
399
+ p owner: owner
400
+ end
401
+ end
402
+
403
+ Cl.new('owners').run(%w(add one))
404
+
405
+ # Output:
406
+ #
407
+ # {:owner=>"one"}
408
+
409
+ ```
410
+
411
+ #### Types
412
+
413
+ Arguments can have a type. Known types are: `:array`, `:string`, `:integer`,
414
+ `:float`, `:boolean`.
415
+
416
+ The type `:array` makes sure the argument accessible on the `Cmd` instance is a
417
+ Ruby Array. (This currently only supports arrays of strings).
418
+
419
+ If the option `sep` is given on the argument, then the argument value is split
420
+ using this separator.
421
+
422
+ ```ruby
423
+ class Add < Cl::Cmd
424
+ register :add
425
+
426
+ arg :owners, type: :array, sep: ','
427
+
428
+ def run
429
+ p owners: owners
430
+ end
431
+ end
432
+
433
+ Cl.new('owners').run(%w(add one,two))
434
+
435
+ # Output:
436
+ #
437
+ # {:owners=>["one", "two"]}
438
+
439
+ ```
440
+
441
+ Other types cast the given argument to the expected Ruby type.
442
+
443
+ ```ruby
444
+ class Cmd < Cl::Cmd
445
+ register :cmd
446
+
447
+ arg :one, type: :integer
448
+ arg :two, type: :float
449
+ arg :three, type: :boolean
450
+
451
+ def run
452
+ p [one.class, two.class, three.class]
453
+ end
454
+ end
455
+
456
+ Cl.new('owners').run(%w(cmd 1 2.1 yes))
457
+
458
+ # Output:
459
+ #
460
+ # [Integer, Float, TrueClass]
461
+
462
+ ```
463
+
464
+ #### Splat
465
+
466
+ Array arguments support splats, modeled after Ruby argument splats.
467
+
468
+ For example:
469
+
470
+ ```ruby
471
+ class Lft < Cl::Cmd
472
+ register :lft
473
+
474
+ arg :a, type: :array, splat: true
475
+ arg :b
476
+ arg :c
477
+
478
+ def run
479
+ p [a, b, c]
480
+ end
481
+ end
482
+
483
+ class Mid < Cl::Cmd
484
+ register :mid
485
+
486
+ arg :a
487
+ arg :b, type: :array, splat: true
488
+ arg :c
489
+
490
+ def run
491
+ p [a, b, c]
492
+ end
493
+ end
494
+
495
+ class Rgt < Cl::Cmd
496
+ register :rgt
497
+
498
+ arg :a
499
+ arg :b
500
+ arg :c, type: :array, splat: true
501
+
502
+ def run
503
+ p [a, b, c]
504
+ end
505
+ end
506
+
507
+ Cl.new('splat').run(%w(lft 1 2 3 4 5))
508
+
509
+ # Output:
510
+ #
511
+ # [["1", "2", "3"], "4", "5"]
512
+
513
+ Cl.new('splat').run(%w(mid 1 2 3 4 5))
514
+
515
+ # Output:
516
+ #
517
+ # ["1", ["2", "3", "4"], "5"]
518
+
519
+ Cl.new('splat').run(%w(rgt 1 2 3 4 5))
520
+
521
+ # Output:
522
+ #
523
+ # ["1", "2", ["3", "4", "5"]]
524
+
525
+ ```
526
+
527
+ ### Options
528
+
529
+ Declaring options can be done by calling the method `opt` on the class body.
530
+
531
+ This will add the option, if given by the user, to the hash `opts` on the `Cmd` instance.
532
+ It also defines a reader method that returns the respective value from the `opts` hash,
533
+ and a predicate that will be true if the option has been given.
534
+
535
+ For example:
536
+
537
+ ```ruby
538
+ class Add < Cl::Cmd
539
+ register :add
540
+
541
+ opt '--to GROUP', 'Target group to add owners to'
542
+
543
+ def run
544
+ p opts: opts, to: to, to?: to?
545
+ end
546
+ end
547
+
548
+ Cl.new('owners').run(%w(add --to one))
549
+
550
+ # Output:
551
+ #
552
+ # {:opts=>{:to=>"one"}, :to=>"one", :to?=>true}
553
+
554
+ Cl.new('owners').run(%w(add --help))
555
+
556
+ # Output:
557
+ #
558
+ # Usage: owners add [options]
559
+ #
560
+ # Options:
561
+ #
562
+ # --to GROUP Target group to add owners to (type: string)
563
+ # --help Get help on this command
564
+
565
+ ```
566
+
567
+ Options optionally accept a block in case custom normalization is needed.
568
+
569
+ Depending on the block's arity the following arguments are passed to the block:
570
+ option value, option name, option type, collection of all options defined on
571
+ the command.
572
+
573
+ ```ruby
574
+ class Add < Cl::Cmd
575
+ register :add
576
+
577
+ # depending on its arity the block can receive:
578
+ #
579
+ # * value
580
+ # * value, name
581
+ # * value, name, type
582
+ # * value, name, type, opts
583
+ opt '--to GROUP' do |value|
584
+ opts[:to] = "group-#{value}"
585
+ end
586
+
587
+ def run
588
+ p to: to
589
+ end
590
+ end
591
+
592
+ Cl.new('owners').run(%w(add --to one))
593
+
594
+ # Output:
595
+ #
596
+ # {:to=>"group-one"}
597
+
598
+
599
+ ```
600
+
601
+ #### Aliases
602
+
603
+ Options can have one or many alias names, given as a Symbol or Array of Symbols:
604
+
605
+ ```ruby
606
+ class Add < Cl::Cmd
607
+ register :add
608
+
609
+ opt '--to GROUP', alias: :group
610
+
611
+ def run
612
+ # p opts: opts, to: to, to?: to?, group: group, group?: group?
613
+ p opts: opts, to: to, to?: to?
614
+ end
615
+ end
616
+
617
+ Cl.new('owners').run(%w(add --group one))
618
+
619
+ # Output:
620
+ #
621
+ # {:opts=>{:to=>"one", :group=>"one"}, :to=>"one", :to?=>true}
622
+
623
+ ```
624
+
625
+ #### Defaults
626
+
627
+ Options can have a default value.
628
+
629
+ I.e. this value is going to be used if the user does not provide the option:
630
+
631
+ ```ruby
632
+ class Add < Cl::Cmd
633
+ register :add
634
+
635
+ opt '--to GROUP', default: 'default'
636
+
637
+ def run
638
+ p to: to
639
+ end
640
+ end
641
+
642
+ Cl.new('owners').run(%w(add))
643
+
644
+ # Output:
645
+ #
646
+ # {:to=>"default"}
647
+
648
+ ```
649
+
650
+ #### Deprecations
651
+
652
+ Options, and option alias name can be deprecated.
653
+
654
+ For a deprecated option:
655
+
656
+ ```ruby
657
+ class Add < Cl::Cmd
658
+ register :add
659
+
660
+ opt '--target GROUP', deprecated: 'Deprecated.'
661
+
662
+ def run
663
+ p target: target, deprecations: deprecations
664
+ end
665
+ end
666
+
667
+ Cl.new('owners').run(%w(add --target one))
668
+
669
+ # Output:
670
+ #
671
+ # {:target=>"one", :deprecations=>{:target=>"Deprecated."}}
672
+
673
+ ```
674
+
675
+ For a deprecated option alias name:
676
+
677
+ ```ruby
678
+ class Add < Cl::Cmd
679
+ register :add
680
+
681
+ opt '--to GROUP', alias: :target, deprecated: :target
682
+
683
+ def run
684
+ p to: to, deprecations: deprecations
685
+ end
686
+ end
687
+
688
+ Cl.new('owners').run(%w(add --target one))
689
+
690
+ # Output:
691
+ #
692
+ # {:to=>"one", :deprecations=>{:target=>:to}}
693
+
694
+ ```
695
+
696
+ #### Downcase
697
+
698
+ Options can be declared to be downcased.
699
+
700
+ For example:
701
+
702
+ ```ruby
703
+ class Add < Cl::Cmd
704
+ register :add
705
+
706
+ opt '--to GROUP', downcase: true
707
+
708
+ def run
709
+ p to: to
710
+ end
711
+ end
712
+
713
+ Cl.new('owners').run(%w(add --to ONE))
714
+
715
+ # Output:
716
+ #
717
+ # {:to=>"one"}
718
+
719
+ ```
720
+
721
+ #### Enum
722
+
723
+ Options can be enums (i.e. have known values).
724
+
725
+ If an unknown values is given by the user the parser will reject the option,
726
+ and print the help output for this command.
727
+
728
+ For example:
729
+
730
+ ```ruby
731
+ class Add < Cl::Cmd
732
+ register :add
733
+
734
+ opt '--to GROUP', enum: %w(one two)
735
+
736
+ def run
737
+ p to: to
738
+ end
739
+ end
740
+
741
+ Cl.new('owners').run(%w(add --to one))
742
+
743
+ # Output:
744
+ #
745
+ # {:to=>"one"}
746
+
747
+ Cl.new('owners').run(%w(add --to unknown))
748
+
749
+ # Output:
750
+ #
751
+ # Unknown value: to=unknown (known values: one, two)
752
+ #
753
+ # Usage: owners add [options]
754
+ #
755
+ # Options:
756
+ #
757
+ # --to GROUP type: string, known values: one, two
758
+ # --help Get help on this command
759
+
760
+ ```
761
+
762
+ #### Example
763
+
764
+ Options can have examples that will be printed in the help output.
765
+
766
+ ```ruby
767
+ class Add < Cl::Cmd
768
+ register :add
769
+
770
+ opt '--to GROUP', example: 'group-one'
771
+ end
772
+
773
+ Cl.new('owners').run(%w(add --help))
774
+
775
+ # Output:
776
+ #
777
+ # Usage: owners add [options]
778
+ #
779
+ # Options:
780
+ #
781
+ # --to GROUP type: string, e.g.: group-one
782
+ # --help Get help on this command
783
+
784
+ ```
785
+
786
+ #### Format
787
+
788
+ Options can have a required format.
789
+
790
+ If a value is given by the user that does not match the format then the parser
791
+ will reject the option, and print the help output for this command.
792
+
793
+ For example:
794
+
795
+ ```ruby
796
+ class Add < Cl::Cmd
797
+ register :add
798
+
799
+ opt '--to GROUP', format: /^\w+$/
800
+
801
+ def run
802
+ p to: to
803
+ end
804
+ end
805
+
806
+ Cl.new('owners').run(%w(add --to one))
807
+
808
+ # Output:
809
+ #
810
+ # {:to=>"one"}
811
+
812
+ Cl.new('owners').run(['add', '--to', 'does not match!'])
813
+
814
+ # Output:
815
+ #
816
+ # Invalid format: to (format: /^\w+$/)
817
+ #
818
+ # Usage: owners add [options]
819
+ #
820
+ # Options:
821
+ #
822
+ # --to GROUP type: string, format: /^\w+$/
823
+ # --help Get help on this command
824
+
825
+ ```
826
+
827
+ #### Internal
828
+
829
+ Options can be declared to be internal, hiding the option from the help output.
830
+
831
+ For example:
832
+
833
+ ```ruby
834
+ class Add < Cl::Cmd
835
+ register :add
836
+
837
+ opt '--to GROUP'
838
+ opt '--hidden', internal: true
839
+ end
840
+
841
+ Cl.new('owners').run(%w(add --help))
842
+
843
+ # Output:
844
+ #
845
+ # Usage: owners add [options]
846
+ #
847
+ # Options:
848
+ #
849
+ # --to GROUP type: string
850
+ # --help Get help on this command
851
+
852
+ ```
853
+
854
+ #### Min and Max
855
+
856
+ Options can have mininum and/or maximum values.
857
+
858
+ If a value is given by the user that does not match the required min and/or max
859
+ values then the parser will reject the option, and print the help output for
860
+ this command.
861
+
862
+ For example:
863
+
864
+ ```ruby
865
+ class Add < Cl::Cmd
866
+ register :add
867
+
868
+ opt '--retries COUNT', type: :integer, min: 1, max: 5
869
+
870
+ def run
871
+ p retries: retries
872
+ end
873
+ end
874
+
875
+ Cl.new('owners').run(%w(add --retries 1))
876
+
877
+ # Output:
878
+ #
879
+ # {:retries=>1}
880
+
881
+ Cl.new('owners').run(%w(add --retries 10))
882
+
883
+ # Output:
884
+ #
885
+ # Out of range: retries (min: 1, max: 5)
886
+ #
887
+ # Usage: owners add [options]
888
+ #
889
+ # Options:
890
+ #
891
+ # --retries COUNT type: integer, min: 1, max: 5
892
+ # --help Get help on this command
893
+
894
+ ```
895
+
896
+ #### Negations
897
+
898
+ Flags (boolean options) automatically allow negation using `--no-*` and
899
+ `--no_*` using OptionParser's support for these. However, sometimes it can be
900
+ convenient to allow other terms for negating an option. Flags therefore accept
901
+ an option `negate` like so:
902
+
903
+ ```ruby
904
+ class Add < Cl::Cmd
905
+ register :add
906
+
907
+ opt '--notifications', 'Send out notifications to the team', negate: %w(skip)
908
+
909
+ def run
910
+ p notifications?
911
+ end
912
+ end
913
+
914
+ Cl.new('owners').run(%w(add --notifications))
915
+
916
+ # Output:
917
+ #
918
+ # true
919
+
920
+ Cl.new('owners').run(%w(add --no_notifications))
921
+
922
+ # Output:
923
+ #
924
+ # false
925
+
926
+ Cl.new('owners').run(%w(add --no-notifications))
927
+
928
+ # Output:
929
+ #
930
+ # false
931
+
932
+ Cl.new('owners').run(%w(add --skip_notifications))
933
+
934
+ # Output:
935
+ #
936
+ # false
937
+
938
+ Cl.new('owners').run(%w(add --skip-notifications))
939
+
940
+ # Output:
941
+ #
942
+ # false
943
+
944
+ ```
945
+
946
+ #### Note
947
+
948
+ Options can have a note that will be printed in the help output.
949
+
950
+ ```ruby
951
+ class Add < Cl::Cmd
952
+ register :add
953
+
954
+ opt '--to GROUP', note: 'needs to be a group'
955
+ end
956
+
957
+ Cl.new('owners').run(%w(add --help))
958
+
959
+ # Output:
960
+ #
961
+ # Usage: owners add [options]
962
+ #
963
+ # Options:
964
+ #
965
+ # --to GROUP type: string, note: needs to be a group
966
+ # --help Get help on this command
967
+
968
+ ```
969
+
970
+ #### Secret
971
+
972
+ Options can be declared as secret.
973
+
974
+ This makes it possible for client code to inspect if a given option is secret.
975
+ Also, option values given by the user will be tainted, so client code can rely
976
+ on this in order to, for example, obfuscate values from log output.
977
+
978
+ ```ruby
979
+ class Add < Cl::Cmd
980
+ register :add
981
+
982
+ opt '--pass PASS', secret: true
983
+
984
+ def run
985
+ p(
986
+ secret?: self.class.opts[:pass].secret?,
987
+ tainted?: pass
988
+ )
989
+ end
990
+ end
991
+
992
+ Cl.new('owners').run(%w(add --pass pass))
993
+
994
+ # Output:
995
+ #
996
+ # {:secret?=>true, :tainted?=>true}
997
+
998
+ ```
999
+
1000
+ #### See Also
1001
+
1002
+ Options can refer to documentation using the `see` option. This will be printed
1003
+ in the help output.
1004
+
1005
+ For example:
1006
+
1007
+ ```ruby
1008
+ class Add < Cl::Cmd
1009
+ register :add
1010
+
1011
+ opt '--to GROUP', see: 'https://docs.io/cli/owners/add'
1012
+ end
1013
+
1014
+ Cl.new('owners').run(%w(add --help))
1015
+
1016
+ # Output:
1017
+ #
1018
+ # Usage: owners add [options]
1019
+ #
1020
+ # Options:
1021
+ #
1022
+ # --to GROUP type: string, see: https://docs.io/cli/owners/add
1023
+ # --help Get help on this command
1024
+
1025
+ ```
1026
+
1027
+ #### Types
1028
+
1029
+ Options can have a type. Known types are: `:array`, `:string`, `:integer`,
1030
+ `:float`, `:boolean`.
1031
+
1032
+ The type `:array` allows an option to be given multiple times, and makes sure
1033
+ the value accessible on the `Cmd` instance is a Ruby Array. (This currently
1034
+ only supports arrays of strings).
1035
+
1036
+ ```ruby
1037
+ class Add < Cl::Cmd
1038
+ register :add
1039
+
1040
+ opt '--to GROUP', type: :array
1041
+
1042
+ def run
1043
+ p to
1044
+ end
1045
+ end
1046
+
1047
+ Cl.new('owners').run(%w(add --to one --to two))
1048
+
1049
+ # Output:
1050
+ #
1051
+ # ["one", "two"]
1052
+
1053
+ ```
1054
+
1055
+ Other types cast the given value to the expected Ruby type.
1056
+
1057
+ ```ruby
1058
+ class Add < Cl::Cmd
1059
+ register :add
1060
+
1061
+ opt '--active BOOL', type: :boolean
1062
+ opt '--retries INT', type: :integer
1063
+ opt '--sleep FLOAT', type: :float
1064
+
1065
+ def run
1066
+ p active: active.class, retries: retries.class, sleep: sleep.class
1067
+ end
1068
+ end
1069
+
1070
+ Cl.new('owners').run(%w(add --active yes --retries 1 --sleep 0.1))
1071
+
1072
+ # Output:
1073
+ #
1074
+ # {:active=>TrueClass, :retries=>Integer, :sleep=>Float}
1075
+
1076
+ ```
1077
+
1078
+ #### Required Options
1079
+
1080
+ There are three ways options can be required:
1081
+
1082
+ * using `required: true` on the option: the option itself is required to be given
1083
+ * using `requires: :other`on the option: the option requires another option to be given
1084
+ * using `required :one, [:two, :three]` on the class: either `one` or both `two` and `three` must be given
1085
+
1086
+ For example, this simply requires the option `--to`:
1087
+
1088
+ ```ruby
1089
+ class Add < Cl::Cmd
1090
+ register :add
1091
+
1092
+ opt '--to GROUP', required: true
1093
+
1094
+ def run
1095
+ p to: to
1096
+ end
1097
+ end
1098
+
1099
+ Cl.new('owners').run(%w(add --to one))
1100
+
1101
+ # Output:
1102
+ #
1103
+ # {:to=>"one"}
1104
+
1105
+ Cl.new('owners').run(%w(add))
1106
+
1107
+ # Output:
1108
+ #
1109
+ # Missing required option: to
1110
+ #
1111
+ # Usage: owners add [options]
1112
+ #
1113
+ # Options:
1114
+ #
1115
+ # --to GROUP type: string, required
1116
+ # --help Get help on this command
1117
+
1118
+ ```
1119
+
1120
+ This will make the option `--retries` depend on the option `--to`:
1121
+
1122
+ ```ruby
1123
+ class Add < Cl::Cmd
1124
+ register :add
1125
+
1126
+ opt '--to GROUP'
1127
+ opt '--other GROUP', requires: :to
1128
+
1129
+ def run
1130
+ p to: to, other: other
1131
+ end
1132
+ end
1133
+
1134
+ Cl.new('owners').run(%w(add --to one --other two))
1135
+
1136
+ # Output:
1137
+ #
1138
+ # {:to=>"one", :other=>"two"}
1139
+
1140
+ Cl.new('owners').run(%w(add --other two))
1141
+
1142
+ # Output:
1143
+ #
1144
+ # Missing option: to (required by other)
1145
+ #
1146
+ # Usage: owners add [options]
1147
+ #
1148
+ # Options:
1149
+ #
1150
+ # --to GROUP type: string
1151
+ # --other GROUP type: string, requires: to
1152
+ # --help Get help on this command
1153
+
1154
+ ```
1155
+
1156
+ This requires either the option `--api_key` or both options `--username` and
1157
+ `--password` to be given:
1158
+
1159
+ ```ruby
1160
+ class Add < Cl::Cmd
1161
+ register :add
1162
+
1163
+ # read DNF, i.e. "token OR user AND pass
1164
+ required :token, [:user, :pass]
1165
+
1166
+ opt '--token TOKEN'
1167
+ opt '--user NAME'
1168
+ opt '--pass PASS'
1169
+
1170
+ def run
1171
+ p token: token, user: user, pass: pass
1172
+ end
1173
+ end
1174
+
1175
+ Cl.new('owners').run(%w(add --token token))
1176
+
1177
+ # Output:
1178
+ #
1179
+ # {:token=>"token", :user=>nil, :pass=>nil}
1180
+
1181
+ Cl.new('owners').run(%w(add --user user --pass pass))
1182
+
1183
+ # Output:
1184
+ #
1185
+ # {:token=>nil, :user=>"user", :pass=>"pass"}
1186
+
1187
+ Cl.new('owners').run(%w(add))
1188
+
1189
+ # Output:
1190
+ #
1191
+ # Missing options: token, or user and pass
1192
+ #
1193
+ # Usage: owners add [options]
1194
+ #
1195
+ # Options:
1196
+ #
1197
+ # Either token, or user and pass are required.
1198
+ #
1199
+ # --token TOKEN type: string
1200
+ # --user NAME type: string
1201
+ # --pass PASS type: string
1202
+ # --help Get help on this command
1203
+
1204
+ ```
1205
+
1206
+ ### Config Files
1207
+
1208
+ Cl automatically reads config files that match the given executable name (inspired by
1209
+ [gem-release](https://github.com/svenfuchs/gem-release)), stored either in the
1210
+ user directory or the current working directory.
1211
+
1212
+ For example:
1213
+
1214
+ ```ruby
1215
+ module Api
1216
+ class Login < Cl::Cmd
1217
+ opt '--username USER'
1218
+ opt '--password PASS'
1219
+ end
1220
+ end
1221
+
1222
+ # bin/api
1223
+ CL.new('api').run(ARGV)
1224
+
1225
+ # ~/api.yml
1226
+ login:
1227
+ username: 'someone'
1228
+ password: 'password'
1229
+
1230
+ # ./api.yml
1231
+ login:
1232
+ username: 'someone else'
1233
+ ```
1234
+
1235
+ then running
1236
+
1237
+ ```
1238
+ $ bin/api login
1239
+ ```
1240
+
1241
+ instantiates `Api::Login`, and passes the hash
1242
+
1243
+ ```ruby
1244
+ { username: 'someone else', password: 'password' }
1245
+ ```
1246
+
1247
+ as `opts`.
1248
+
1249
+ Options passed by the user take precedence over defaults defined in config
1250
+ files.
1251
+
1252
+ ### Environment Variables
1253
+
1254
+ Cl automatically defaults options to environment variables that are prefixed
1255
+ with the given executable name (inspired by [gem-release](https://github.com/svenfuchs/gem-release)).
1256
+
1257
+ ```ruby
1258
+ module Api
1259
+ class Login < Cl::Cmd
1260
+ opt '--username USER'
1261
+ opt '--password PASS'
1262
+ end
1263
+ end
1264
+
1265
+ # bin/api
1266
+ CL.new('api').run(ARGV)
1267
+ ```
1268
+
1269
+ then running
1270
+
1271
+ ```
1272
+ $ API_USERNAME=someone API_PASSWORD=password bin/api login
1273
+ ```
1274
+
1275
+ instantiates `Api::Login`, and passes the hash
1276
+
1277
+ ```ruby
1278
+ { username: 'someone', password: 'password' }
1279
+ ```
1280
+
1281
+ Options passed by the user take precedence over defaults given as environment
1282
+ variables, and environment variables take precedence over defaults defined in
1283
+ config files.