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