optix 1.0.1
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +431 -0
- data/Rakefile +30 -0
- data/examples/filetool.rb +188 -0
- data/examples/printer.rb +43 -0
- data/lib/optix/trollop.rb +814 -0
- data/lib/optix/version.rb +3 -0
- data/lib/optix.rb +276 -0
- data/optix.gemspec +20 -0
- data/spec/optix_spec.rb +988 -0
- data/spec/spec_helper.rb +30 -0
- metadata +83 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (C) 2012, moe@busyloop.net
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
copy of this software and associated documentation files (the "Software"),
|
5
|
+
to deal in the Software without restriction, including without limitation
|
6
|
+
the rights to use, copy, modify, merge, publish, pulverize, distribute,
|
7
|
+
synergize, compost, defenestrate, sublicense, and/or sell copies of the
|
8
|
+
Software, and to permit persons to whom the Software is furnished to do
|
9
|
+
so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included
|
12
|
+
in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
If the Author of the Software (the "Author") needs a place to crash and
|
15
|
+
you have a sofa available, you should maybe give the Author a break and
|
16
|
+
let him sleep on your couch.
|
17
|
+
|
18
|
+
If you are caught in a dire situation wherein you only have enough time
|
19
|
+
to save one person out of a group, and the Author is a member of that
|
20
|
+
group, you must save the Author.
|
21
|
+
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
23
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO BLAH BLAH BLAH ISN'T IT FUNNY
|
24
|
+
HOW UPPER-CASE MAKES IT SOUND LIKE THE LICENSE IS ANGRY AND SHOUTING AT YOU.
|
25
|
+
|
data/README.md
ADDED
@@ -0,0 +1,431 @@
|
|
1
|
+
# Optix
|
2
|
+
|
3
|
+
Optix is an unobtrusive, composable command line parser based on Trollop.
|
4
|
+
|
5
|
+
|
6
|
+
## Features
|
7
|
+
|
8
|
+
* Lightweight, unobtrusive syntax.
|
9
|
+
No subclassing or introduction of dependencies.
|
10
|
+
|
11
|
+
* Supports subcommands such as `git remote show origin` with arbitrary nesting.
|
12
|
+
|
13
|
+
* Subcommands inherit from their parent. Common options (such as '--debug' or '--loglevel')
|
14
|
+
need to be declared only once to make them available to an entire branch.
|
15
|
+
|
16
|
+
* Stands on the shoulders of [Trollop](http://trollop.rubyforge.org) (by William Morgan), one of the most complete and robust
|
17
|
+
option-parser implementations ever created.
|
18
|
+
|
19
|
+
* Automatic validation and help-screens.
|
20
|
+
|
21
|
+
* Strong test-suite.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
$ gem install optix
|
26
|
+
|
27
|
+
## Example
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
#!/usr/bin/env ruby
|
31
|
+
require 'optix'
|
32
|
+
|
33
|
+
module Example
|
34
|
+
module Printer
|
35
|
+
# Declare the "root"-command
|
36
|
+
Optix::command do
|
37
|
+
text "I am printer. I print strings to the screen."
|
38
|
+
text "Please invoke one of my not so many sub-commands."
|
39
|
+
|
40
|
+
# Declare a global option (all subcommands inherit this)
|
41
|
+
opt :debug, "Enable debugging", :default => false
|
42
|
+
end
|
43
|
+
|
44
|
+
# Declare sub-command
|
45
|
+
Optix::command 'print' do
|
46
|
+
desc "Print a string"
|
47
|
+
text "Print a string to the screen"
|
48
|
+
params "<string>"
|
49
|
+
|
50
|
+
opt :count, "Print how many times?", :default => 1
|
51
|
+
|
52
|
+
# This block is invoked when validations pass.
|
53
|
+
exec do |cmd, opts, argv|
|
54
|
+
if argv.length < 1
|
55
|
+
raise Optix::HelpNeeded
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "DEBUGGING IS ENABLED!" if opts[:debug]
|
59
|
+
(1..opts[:count]).each do
|
60
|
+
puts argv.join(' ')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if __FILE__ == $0
|
68
|
+
# Perform the actual parsing and execution.
|
69
|
+
Optix.invoke!(ARGV)
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
The above code in action:
|
74
|
+
|
75
|
+
```
|
76
|
+
$ ./printer.rb --help
|
77
|
+
|
78
|
+
Usage: ./printer.rb <subcommand>
|
79
|
+
|
80
|
+
I am printer. I print strings to the screen.
|
81
|
+
Please invoke one of my not so many sub-commands.
|
82
|
+
|
83
|
+
Options:
|
84
|
+
--debug, -d: Enable debugging
|
85
|
+
--help, -h: Show this message
|
86
|
+
|
87
|
+
Commands:
|
88
|
+
print Print a string
|
89
|
+
|
90
|
+
```
|
91
|
+
|
92
|
+
See the `examples/`-folder for more elaborate examples.
|
93
|
+
|
94
|
+
## Documentation
|
95
|
+
|
96
|
+
### Commands and Sub-Commands
|
97
|
+
|
98
|
+
Commands may be declared anywhere, at any time, in any order.
|
99
|
+
Declaring the "root"-command looks like this:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
require 'optix'
|
103
|
+
|
104
|
+
Optix::command do
|
105
|
+
# opts, triggers, etc
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
Now, let's add a sub-command:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
Optix::command 'sub' do
|
113
|
+
# opts, triggers, etc
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
And finally, a sub-sub command:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
Optix::command 'sub sub' do
|
121
|
+
# opts, triggers, etc
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
Remember: Optix doesn't care about the order of declarations.
|
126
|
+
The `sub sub` command may be declared prior to the `sub` command.
|
127
|
+
|
128
|
+
A common pattern is to insert your `Optix::command` blocks directly
|
129
|
+
at the module level so they get invoked during class-loading.
|
130
|
+
This way your CLI assembles itself automatically and the
|
131
|
+
command-hierarchy mirrors the modules/classes that
|
132
|
+
are actually loaded.
|
133
|
+
|
134
|
+
|
135
|
+
### Optix::command DSL
|
136
|
+
|
137
|
+
Within `Optix::command` the following directives are available:
|
138
|
+
|
139
|
+
### desc
|
140
|
+
|
141
|
+
Short description, displayed in the subcommand-list on the help-screen of the *parent* command.
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
Optix::command "frobnitz" do
|
145
|
+
desc "Frobnicates the gizmo"
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
### text
|
150
|
+
|
151
|
+
Long description, displayed on the help-screen for this command.
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
Optix::command "frobnitz" do
|
155
|
+
text "Frobnicate the gizmo by subtle twiddling."
|
156
|
+
text "Please only apply this to 2-state devices or you might bork it."
|
157
|
+
end
|
158
|
+
```
|
159
|
+
|
160
|
+
* May be called multiple times for a multi-line description.
|
161
|
+
|
162
|
+
|
163
|
+
### opt
|
164
|
+
|
165
|
+
Declares an option.
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
Optix::command do
|
169
|
+
opt :some_name, "some description", :default => 'some_default'
|
170
|
+
end
|
171
|
+
```
|
172
|
+
|
173
|
+
Takes the following optional arguments:
|
174
|
+
|
175
|
+
* `:long` Specify the long form of the argument, i.e. the form with two dashes.
|
176
|
+
If unspecified, will be automatically derived based on the argument name by turning the name
|
177
|
+
option into a string, and replacing any _'s by -'s.
|
178
|
+
|
179
|
+
* `:short` Specify the short form of the argument, i.e. the form with one dash.
|
180
|
+
If unspecified, will be automatically derived from argument name.
|
181
|
+
|
182
|
+
* `:type` Require that the argument take a parameter or parameters of a given type. For a *single parameter*,
|
183
|
+
the value can be one of **:int**, **:integer**, **:string**, **:double**, **:float**, **:io**, **:date**,
|
184
|
+
or a corresponding Ruby class (e.g. **Integer** for **:int**).
|
185
|
+
For *multiple-argument parameters*, the value can be one of **:ints**, **:integers**, **:strings**, **:doubles**,
|
186
|
+
**:floats**, **:ios** or **:dates**. If unset, the default argument type is **:flag**, meaning that the argument
|
187
|
+
does not take a parameter. The specification of `:type` is not necessary if a `:default` is given.
|
188
|
+
|
189
|
+
* `:default` Set the default value for an argument. Without a default value, the opts-hash passed to `exec{}`
|
190
|
+
and `filter{}` will have a *nil* value for this key unless the argument is given on the commandline.
|
191
|
+
The argument type is derived automatically from the class of the default value given, so specifying a `:type`
|
192
|
+
is not necessary if a `:default` is given. (But see below for an important caveat when `:multi` is specified too.)
|
193
|
+
If the argument is a flag, and the default is set to **true**, then if it is specified on the the commandline
|
194
|
+
the value will be **false**.
|
195
|
+
|
196
|
+
* `:required` If set to **true**, the argument must be provided on the commandline.
|
197
|
+
|
198
|
+
* `:multi` If set to **true**, allows multiple occurrences of the option on the commandline.
|
199
|
+
Otherwise, only a single instance of the option is allowed.
|
200
|
+
|
201
|
+
Note that there are two types of argument multiplicity: an argument
|
202
|
+
can take multiple values, e.g. "--arg 1 2 3". An argument can also
|
203
|
+
be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
|
204
|
+
|
205
|
+
Arguments that take multiple values should have a `:type` parameter
|
206
|
+
or a `:default` value of an array of the correct type (e.g. [String]).
|
207
|
+
|
208
|
+
The value of this argument will be an array of the parameters on the
|
209
|
+
commandline.
|
210
|
+
|
211
|
+
Arguments that can occur multiple times should be marked with
|
212
|
+
`:multi => true`. The value of this argument will also be an array.
|
213
|
+
In contrast with regular non-multi options, if not specified on
|
214
|
+
the commandline, the default value will be [], not nil.
|
215
|
+
|
216
|
+
These two attributes can be combined (e.g. **:type => :strings**,
|
217
|
+
**:multi => true**), in which case the value of the argument will be
|
218
|
+
an array of arrays.
|
219
|
+
|
220
|
+
There's one ambiguous case to be aware of: when `:multi` is **true** and a
|
221
|
+
`:default` is set to an array (of something), it's ambiguous whether this
|
222
|
+
is a multi-value argument as well as a multi-occurrence argument.
|
223
|
+
In thise case, we assume that it's not a multi-value argument.
|
224
|
+
If you want a multi-value, multi-occurrence argument with a default
|
225
|
+
value, you must specify `:type` as well.
|
226
|
+
|
227
|
+
### params
|
228
|
+
|
229
|
+
Describes positional parameters that this command accepts.
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
Optix::command do
|
233
|
+
params "<foo> [bar]"
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
* Note: Optix does **not** validate or inspect positional parameters at all (this is your job, inside exec{}).
|
238
|
+
The value of this command is only used to display a proper synopsis in the help-screen.
|
239
|
+
|
240
|
+
### depends
|
241
|
+
|
242
|
+
Marks two (or more!) options as requiring each other. Only handles
|
243
|
+
undirected (i.e., mutual) dependencies.
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
Optix::command do
|
247
|
+
opt :we, ""
|
248
|
+
opt :are, ""
|
249
|
+
opt :family, ""
|
250
|
+
depends :we, :are, :family
|
251
|
+
end
|
252
|
+
```
|
253
|
+
|
254
|
+
### conflicts
|
255
|
+
|
256
|
+
Marks two (or more!) options as conflicting.
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
Optix::command do
|
260
|
+
opt :force, "Force this operation"
|
261
|
+
opt :no_op, "Dry run, don't actually do anything"
|
262
|
+
conflict :force, :no_op
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
### trigger
|
267
|
+
|
268
|
+
Triggers allow to short-circuit argument parsing for "action-options"
|
269
|
+
(options that directly trigger an action) such as `--version`.
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
Optix::command do
|
273
|
+
opt :version, "Print version and exit"
|
274
|
+
trigger :version do
|
275
|
+
puts "Version 1.0"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
```
|
279
|
+
|
280
|
+
* Triggers fire *before* validation.
|
281
|
+
|
282
|
+
* Parsing stops after a trigger has fired.
|
283
|
+
|
284
|
+
* A trigger can only be bound to an existing `opt`. I.e. you must first
|
285
|
+
declare `opt :version` before you can bind a trigger with `trigger
|
286
|
+
:version`.
|
287
|
+
|
288
|
+
* A trigger may be bound to multiple options like so: `trigger [:version,
|
289
|
+
:other, :etc] do ...`
|
290
|
+
|
291
|
+
* You may raise `Optix::HelpNeeded` inside your trigger to abort
|
292
|
+
parsing and display the help-screen.
|
293
|
+
|
294
|
+
|
295
|
+
### filter
|
296
|
+
|
297
|
+
Filters allow to group functionality that is common to a branch of subcommands.
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
Optix::command do
|
301
|
+
opt :debug, "Enable debugging"
|
302
|
+
filter do |cmd, opts, argv|
|
303
|
+
if opts[:debug]
|
304
|
+
# .. enable debugging ..
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
```
|
309
|
+
|
310
|
+
* Filters fire *after* validation, for each command in the chain.
|
311
|
+
|
312
|
+
* Parsing continues normally after a filter has fired.
|
313
|
+
|
314
|
+
* Your block receives three arguments:
|
315
|
+
* `cmd` (Array) The full command that was executed, e.g.: ['foo', 'bar', 'baz']
|
316
|
+
* `opts` (Hash) The options-hash, e.g.: { :debug => true }
|
317
|
+
* `argv` (Array) Positional parameters that your command may have received, e.g.: ['a','b','c']
|
318
|
+
|
319
|
+
* You may raise `Optix::HelpNeeded` inside your filter to abort
|
320
|
+
parsing and display the help-screen.
|
321
|
+
|
322
|
+
|
323
|
+
### exec
|
324
|
+
|
325
|
+
The exec-block is called when your command is invoked, after validation
|
326
|
+
passed. It should contain (or invoke) your actual business logic.
|
327
|
+
|
328
|
+
```ruby
|
329
|
+
Optix::command do
|
330
|
+
exec do |cmd, opts, argv|
|
331
|
+
if opts[:debug]
|
332
|
+
# .. enable debugging ..
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
```
|
337
|
+
|
338
|
+
* Your block receives three arguments:
|
339
|
+
* `cmd` (Array) The full command that was executed, e.g.: ['foo', 'bar', 'baz']
|
340
|
+
* `opts` (Hash) The options-hash, e.g.: { :debug => true }
|
341
|
+
* `argv` (Array) Positional parameters that your command may have received, e.g.: ['a','b','c']
|
342
|
+
|
343
|
+
* You may raise `Optix::HelpNeeded` inside your exec-block to abort
|
344
|
+
parsing and display the help-screen.
|
345
|
+
|
346
|
+
|
347
|
+
## Chain of execution
|
348
|
+
|
349
|
+
This is the chain of execution when you pass ['foo', 'bar', 'batz'] to `Optix.invoke!`:
|
350
|
+
|
351
|
+
1. Triggers for `foo` (if any, execution stops if a trigger fires)
|
352
|
+
1. Triggers for `bar` (if any, execution stops if a trigger fires)
|
353
|
+
1. Triggers for `batz` (if any, execution stops if a trigger fires)
|
354
|
+
1. Validation
|
355
|
+
1. Filters for `foo` (if any)
|
356
|
+
1. Filters for `bar` (if any)
|
357
|
+
1. Filters for `batz` (if any)
|
358
|
+
1. Exec{}-block for `batz`
|
359
|
+
|
360
|
+
|
361
|
+
|
362
|
+
## Scopes
|
363
|
+
|
364
|
+
In rare cases you may want to have multiple independent Optix command-trees in a single app.
|
365
|
+
This can be achieved by passing a scope-name to your command-declarations, like so:
|
366
|
+
|
367
|
+
```ruby
|
368
|
+
# Declare root-command in the :default scope
|
369
|
+
Optix::command '', :default do
|
370
|
+
# opts, triggers, etc
|
371
|
+
end
|
372
|
+
|
373
|
+
# Declare root-command in another, independent scope
|
374
|
+
Optix::command '', :other_scope do
|
375
|
+
end
|
376
|
+
|
377
|
+
# Declare a sub-command in the other scope
|
378
|
+
Optix::command 'sub', :other_scope do
|
379
|
+
end
|
380
|
+
|
381
|
+
# Then either invoke the :default scope
|
382
|
+
Optix.invoke!(ARGV)
|
383
|
+
# ...or...
|
384
|
+
Optix.invoke!(ARGV, :other_scope)
|
385
|
+
```
|
386
|
+
|
387
|
+
## Re-initialization
|
388
|
+
|
389
|
+
In even rarer cases you may need to reset Optix at runtime.
|
390
|
+
To make Optix forget all scopes, configuration and commands, invoke:
|
391
|
+
|
392
|
+
`Optix.reset!`
|
393
|
+
|
394
|
+
|
395
|
+
## Contributing
|
396
|
+
|
397
|
+
Patches are welcome, especially bugfixes.
|
398
|
+
|
399
|
+
1. Fork it
|
400
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
401
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
402
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
403
|
+
5. Create new Pull Request
|
404
|
+
|
405
|
+
## License
|
406
|
+
|
407
|
+
Copyright (C) 2012, moe@busyloop.net
|
408
|
+
|
409
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
410
|
+
copy of this software and associated documentation files (the "Software"),
|
411
|
+
to deal in the Software without restriction, including without limitation
|
412
|
+
the rights to use, copy, modify, merge, publish, pulverize, distribute,
|
413
|
+
synergize, compost, defenestrate, sublicense, and/or sell copies of the
|
414
|
+
Software, and to permit persons to whom the Software is furnished to do
|
415
|
+
so, subject to the following conditions:
|
416
|
+
|
417
|
+
The above copyright notice and this permission notice shall be included
|
418
|
+
in all copies or substantial portions of the Software.
|
419
|
+
|
420
|
+
If the Author of the Software (the "Author") needs a place to crash and
|
421
|
+
you have a sofa available, you should maybe give the Author a break and
|
422
|
+
let him sleep on your couch.
|
423
|
+
|
424
|
+
If you are caught in a dire situation wherein you only have enough time
|
425
|
+
to save one person out of a group, and the Author is a member of that
|
426
|
+
group, you must save the Author.
|
427
|
+
|
428
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
429
|
+
INCLUDING BUT NOT LIMITED TO BLAH BLAH BLAH ISN'T IT FUNNY HOW UPPER-CASE MAKES IT
|
430
|
+
SOUND LIKE THE LICENSE IS ANGRY AND SHOUTING AT YOU.
|
431
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new("test:spec") do |t|
|
9
|
+
t.pattern = 'spec/*_spec.rb'
|
10
|
+
t.rcov = false
|
11
|
+
#t.rspec_opts = '-b -c -f progress --tag ~benchmark'
|
12
|
+
t.rspec_opts = '-b -c -f documentation --tag ~benchmark'
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec::Core::RakeTask.new("test:benchmark") do |t|
|
16
|
+
t.pattern = 'spec/*.rb'
|
17
|
+
t.rcov = false
|
18
|
+
t.rspec_opts = '-b -c -f documentation --tag benchmark'
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
namespace :test do
|
23
|
+
task :coverage do
|
24
|
+
#require 'cover_me'
|
25
|
+
#CoverMe.complete!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'Run full test suite'
|
30
|
+
task :test => [ 'test:spec', 'test:coverage' ]
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'optix'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Example application to demonstrate Optix.
|
5
|
+
#
|
6
|
+
# The easiest way to get started is to download this file
|
7
|
+
# and actually run it. Play around, try invoking '--help',
|
8
|
+
# '-v', '--version' and the subcommands.
|
9
|
+
#
|
10
|
+
# Then come back here to learn how it's done. :)
|
11
|
+
#
|
12
|
+
module Example
|
13
|
+
class FileTool
|
14
|
+
# Declare the "root" command
|
15
|
+
# Also declaring the first-level sub-commands right here.
|
16
|
+
# Just like the root-command these commands can not be invoked
|
17
|
+
# directly (because they have sub-commands). Their declaration
|
18
|
+
# is not mandatory, but by declaring them explicitly we can add
|
19
|
+
# some useful help-texts to aid the user.
|
20
|
+
Optix::command do
|
21
|
+
# Let's have a nice description
|
22
|
+
text "This is FileTool, a little example application to demonstrate Optix."
|
23
|
+
text "It's safe to play around with. All operations are no-ops."
|
24
|
+
text "Your filesystem is never actually modified!"
|
25
|
+
text ""
|
26
|
+
text "Invoke me with one of the sub-commands to perform a dummy-operation."
|
27
|
+
|
28
|
+
# Note: Options are recursively inherited by sub-commands.
|
29
|
+
# Thus, any option we declare here will also be available
|
30
|
+
# in all sub-commands.
|
31
|
+
opt :debug, "Enable debugging", :default => false
|
32
|
+
opt :version, "Print version and exit"
|
33
|
+
|
34
|
+
# Triggers fire immediately, before validation.
|
35
|
+
#
|
36
|
+
# This one short-circuits the '--version' and '-v' opts.
|
37
|
+
# We want to print/exit immediately when we encounter
|
38
|
+
# them, regardless of other opts or subcommands.
|
39
|
+
#
|
40
|
+
# NOTE: Triggers can only bind to existing opts.
|
41
|
+
# Make sure to always declare the 'opt' before
|
42
|
+
# creating a 'trigger' (just like in this example).
|
43
|
+
trigger [:version] do
|
44
|
+
puts "Version 1.0"
|
45
|
+
# parsing stops after a trigger has fired.
|
46
|
+
end
|
47
|
+
|
48
|
+
# Filters are "pass-through", they fire for
|
49
|
+
# every intermediate sub-command. We use it here
|
50
|
+
# to set up a common debug-mechanism before the
|
51
|
+
# subcommand is invoked.
|
52
|
+
filter do |cmd, opts, argv|
|
53
|
+
if opts[:debug]
|
54
|
+
FileTool.enable_debug!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Note this command has no exec{}-block.
|
59
|
+
# It would never fire because this command has sub-commands.
|
60
|
+
# When a command has sub-commands then it can no longer be
|
61
|
+
# invoked directly (that would only lead to confusion and
|
62
|
+
# bad accidents).
|
63
|
+
end
|
64
|
+
|
65
|
+
Optix::command 'file' do
|
66
|
+
desc "Operations on files"
|
67
|
+
text "Please invoke one of the sub-commands to perform a file-operation."
|
68
|
+
end
|
69
|
+
|
70
|
+
Optix::command 'dir' do
|
71
|
+
desc "Operations on directories"
|
72
|
+
text "Please invoke one of the sub-commands to perform a directory-operation."
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.enable_debug!
|
76
|
+
# enable debugging...
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module Example
|
82
|
+
class FileTool
|
83
|
+
class Move
|
84
|
+
# Here we declare the subcommand 'file move'
|
85
|
+
Optix::command 'file move' do
|
86
|
+
# Short one-liner for the sub-command list in parent help-screen
|
87
|
+
desc "Move a file"
|
88
|
+
|
89
|
+
# Verbose description for the help-screen
|
90
|
+
text "Move a file from <source> to <dest>"
|
91
|
+
text "The destination will not be overwritten unless --force is applied."
|
92
|
+
|
93
|
+
opt :force, "Force overwrite", :default => false
|
94
|
+
|
95
|
+
# This text is only used in the help-screen. Positional arguments are
|
96
|
+
# *not* checked by Optix, you have to do that yourself in the exec{}-block.
|
97
|
+
params "<source> <dest>"
|
98
|
+
|
99
|
+
exec do |cmd, opts, argv|
|
100
|
+
# bail if we didn't receive enough parameters
|
101
|
+
if argv.length < 2
|
102
|
+
puts "Error: #{cmd.join(' ')} must be invoked with 2 parameters."
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
|
106
|
+
puts "#{cmd.join(' ')} called with #{opts}, #{argv}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Here we declare the subcommand 'dir move'
|
111
|
+
Optix::command 'dir move' do
|
112
|
+
desc "Move directory from A to B"
|
113
|
+
|
114
|
+
text "Move a directory from <source> to <dest>"
|
115
|
+
text "The destination will not be overwritten unless --force is applied."
|
116
|
+
opt :force, "Force overwrite", :default => false
|
117
|
+
params "<source> <dest>"
|
118
|
+
|
119
|
+
exec do |cmd, opts, argv|
|
120
|
+
if argv.length < 2
|
121
|
+
puts "Error: #{cmd.join(' ')} must be invoked with 2 parameters."
|
122
|
+
exit 1
|
123
|
+
end
|
124
|
+
puts "#{cmd.join(' ')} called with #{opts}, #{argv}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Copy
|
130
|
+
# Here we declare the subcommand 'file copy'
|
131
|
+
Optix::command 'file copy' do
|
132
|
+
# Short one-liner for the sub-command list in parent help-screen
|
133
|
+
desc "Copy a file"
|
134
|
+
|
135
|
+
# Verbose description for the help-screen
|
136
|
+
text "Copy a file from <source> to <dest>"
|
137
|
+
text "The destination will not be overwritten unless --force is applied."
|
138
|
+
|
139
|
+
opt :force, "Force overwrite", :default => false
|
140
|
+
|
141
|
+
# This text is only used in the help-screen. Positional arguments are
|
142
|
+
# *not* checked by Optix, you have to do that yourself in the exec{}-block.
|
143
|
+
params "<source> <dest>"
|
144
|
+
|
145
|
+
exec do |cmd, opts, argv|
|
146
|
+
# bail if we didn't receive enough parameters
|
147
|
+
if argv.length < 2
|
148
|
+
puts "Error: #{cmd.join(' ')} must be invoked with 2 parameters."
|
149
|
+
exit 1
|
150
|
+
end
|
151
|
+
|
152
|
+
puts "#{cmd.join(' ')} called with #{opts}, #{argv}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Here we declare the subcommand 'dir copy'
|
157
|
+
Optix::command 'dir copy' do
|
158
|
+
desc "Copy directory from A to B"
|
159
|
+
|
160
|
+
text "Copy a directory from <source> to <dest>"
|
161
|
+
text "The destination will not be overwritten unless --force is applied."
|
162
|
+
opt :force, "Force overwrite", :default => false
|
163
|
+
params "<source> <dest>"
|
164
|
+
|
165
|
+
exec do |cmd, opts, argv|
|
166
|
+
if argv.length < 2
|
167
|
+
puts "Error: #{cmd.join(' ')} must be invoked with 2 parameters."
|
168
|
+
exit 1
|
169
|
+
end
|
170
|
+
puts "#{cmd.join(' ')} called with #{opts}, #{argv}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if __FILE__ == $0
|
178
|
+
# Perform some configuration (optional!)
|
179
|
+
Optix.configure do
|
180
|
+
# override a help-text template
|
181
|
+
# see the source-code for full list of available config keys
|
182
|
+
text_header_usage 'Syntax: %0 %command %params'
|
183
|
+
end
|
184
|
+
|
185
|
+
# Perform the actual parsing and execution.
|
186
|
+
Optix.invoke!(ARGV)
|
187
|
+
end
|
188
|
+
|