travis-cl 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
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.