choosy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -0
- data/Gemfile.lock +25 -0
- data/LICENSE +23 -0
- data/README.markdown +393 -0
- data/Rakefile +57 -0
- data/lib/VERSION +1 -0
- data/lib/choosy/base_command.rb +59 -0
- data/lib/choosy/command.rb +32 -0
- data/lib/choosy/converter.rb +115 -0
- data/lib/choosy/dsl/base_command_builder.rb +172 -0
- data/lib/choosy/dsl/command_builder.rb +43 -0
- data/lib/choosy/dsl/option_builder.rb +155 -0
- data/lib/choosy/dsl/super_command_builder.rb +41 -0
- data/lib/choosy/errors.rb +11 -0
- data/lib/choosy/option.rb +22 -0
- data/lib/choosy/parse_result.rb +64 -0
- data/lib/choosy/parser.rb +184 -0
- data/lib/choosy/printing/color.rb +101 -0
- data/lib/choosy/printing/erb_printer.rb +23 -0
- data/lib/choosy/printing/help_printer.rb +174 -0
- data/lib/choosy/super_command.rb +77 -0
- data/lib/choosy/super_parser.rb +81 -0
- data/lib/choosy/verifier.rb +62 -0
- data/lib/choosy/version.rb +12 -0
- data/lib/choosy.rb +16 -0
- data/spec/choosy/base_command_spec.rb +11 -0
- data/spec/choosy/command_spec.rb +51 -0
- data/spec/choosy/converter_spec.rb +145 -0
- data/spec/choosy/dsl/base_command_builder_spec.rb +328 -0
- data/spec/choosy/dsl/commmand_builder_spec.rb +80 -0
- data/spec/choosy/dsl/option_builder_spec.rb +386 -0
- data/spec/choosy/dsl/super_command_builder_spec.rb +83 -0
- data/spec/choosy/parser_spec.rb +275 -0
- data/spec/choosy/printing/color_spec.rb +74 -0
- data/spec/choosy/printing/help_printer_spec.rb +117 -0
- data/spec/choosy/super_command_spec.rb +80 -0
- data/spec/choosy/super_parser_spec.rb +106 -0
- data/spec/choosy/verifier_spec.rb +180 -0
- data/spec/integration/command-A_spec.rb +37 -0
- data/spec/integration/supercommand-A_spec.rb +61 -0
- data/spec/spec_helpers.rb +30 -0
- metadata +150 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
ZenTest (4.4.2)
|
5
|
+
autotest (4.4.6)
|
6
|
+
ZenTest (>= 4.4.1)
|
7
|
+
autotest-notification (2.3.1)
|
8
|
+
autotest (~> 4.3)
|
9
|
+
diff-lcs (1.1.2)
|
10
|
+
rspec (2.5.0)
|
11
|
+
rspec-core (~> 2.5.0)
|
12
|
+
rspec-expectations (~> 2.5.0)
|
13
|
+
rspec-mocks (~> 2.5.0)
|
14
|
+
rspec-core (2.5.1)
|
15
|
+
rspec-expectations (2.5.0)
|
16
|
+
diff-lcs (~> 1.1.2)
|
17
|
+
rspec-mocks (2.5.0)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
autotest
|
24
|
+
autotest-notification
|
25
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
The MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2011 Gabe McArthur
|
4
|
+
Copyright (c) 2010 Gabriel Horner
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
# Choosy: Picking your arguments carefully
|
2
|
+
|
3
|
+
This is a small DSL library for creating command line clients in Ruby. It is largely inspired by the <a href="https://github.com/defunkt/choice">choice</a>, <a href="https://github.com/visionmedia/commander">commander</a>, and <a href="http://furius.ca/optcomplete/">optcomplete.py</a> libraries, though it makes some different design decisions than they do. It is opinionated software.
|
4
|
+
|
5
|
+
This library should:
|
6
|
+
|
7
|
+
- Make creating command line clients relatively easy.
|
8
|
+
- Make creating supercommands like git, subversion, and gem easier.
|
9
|
+
- Allow you to add validation logic for your arguments within the parsing phase.
|
10
|
+
- Allowing for dependencies between options, so that you can more easily validate related options (i.e. if the<code>--bold</code> flag requires the <code>--font Arial</code> flag, then you should be able to ask for the <code>--font</code> option to be validated first, and then the <code>--bold</code> option.
|
11
|
+
- Allow you to customize its output using your own formatting system.
|
12
|
+
- Allow you to customize the output to your specifications.
|
13
|
+
|
14
|
+
This library should never:
|
15
|
+
|
16
|
+
- Interact with your execution logic. You can attach executors to commands for convenience, but the execution phase should be delegated to you, not the parsing library. Separation of concerns, people.
|
17
|
+
- Rely on display or user interface libraries like Highline, since this is only for parsing command lines.
|
18
|
+
- Pollute your namespaces with my DSL function names. (I really, really hate it when libraries do this.)
|
19
|
+
|
20
|
+
# Examples
|
21
|
+
|
22
|
+
#!/usr/bin/env ruby
|
23
|
+
# foo.rb
|
24
|
+
|
25
|
+
require 'rubygems'
|
26
|
+
require 'choosy'
|
27
|
+
|
28
|
+
FOO_VERSION = 1.0.1
|
29
|
+
|
30
|
+
class FooExecutor
|
31
|
+
def execute!(options, args)
|
32
|
+
puts "BOLDED!!" if options[:bold]
|
33
|
+
options[:count].times do
|
34
|
+
puts "#{options[:prefix]}#{options[:words].push('foo').join(',')#{options[:suffix}}"
|
35
|
+
end
|
36
|
+
puts "and #{args.join ' '}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
foo_cmd = Choosy::Command.new :foo do |foo|
|
41
|
+
# Add a class to do the execution when you call foo_cmd.execute!
|
42
|
+
# You can also use a proc that takes the options and the args, like:
|
43
|
+
# foo.executor { |opts, args| puts 'Hi!' }
|
44
|
+
foo.executor FooExecutor.new
|
45
|
+
|
46
|
+
# You can add your custom printer by giving the
|
47
|
+
# full path to an ERB template file here.
|
48
|
+
# The default printer is :standard, but you can
|
49
|
+
# also use the builtin printer :compact. The
|
50
|
+
# output can be colored or uncolored, though the
|
51
|
+
# default is colored.
|
52
|
+
foo.printer :standard, :colored => true
|
53
|
+
|
54
|
+
foo.summary 'Prints out "foo" to the console"
|
55
|
+
foo.desc <<HERE
|
56
|
+
This is a long description about what 'foo' is
|
57
|
+
and how it works. Don't worry your pretty little head
|
58
|
+
about the details.
|
59
|
+
HERE
|
60
|
+
|
61
|
+
foo.separator 'Required Options:'
|
62
|
+
|
63
|
+
# A shorthand for a common option type.
|
64
|
+
# It adds the '-p/--prefix PREFIX' infomation for you.
|
65
|
+
foo.single :prefix, "A prefix for 'foo'" do |p|
|
66
|
+
p.default '<'
|
67
|
+
p.required
|
68
|
+
end
|
69
|
+
|
70
|
+
# The long way to do the same thing as above, except with
|
71
|
+
# explicitly named dependencies
|
72
|
+
foo.option :suffix => [:prefix] do |o|
|
73
|
+
o.short '-s'
|
74
|
+
o.long '--suffix', 'SUFFIX'
|
75
|
+
o.desc 'A suffix for "foo"'
|
76
|
+
o.required
|
77
|
+
|
78
|
+
o.validate do |suffix|
|
79
|
+
if suffix == foo[:prefix]
|
80
|
+
o.fail "You can't matching prefixes and suffixes, you heathen!"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Just like the 'single' method above, except now it automatically
|
86
|
+
# requires/casts the argument to this flag into an integer. These commands
|
87
|
+
# also take an optional hash as the last argument, which can be used instead
|
88
|
+
# of a block.
|
89
|
+
foo.integer :count, 'The number of times to repeat "foo"', :required => true
|
90
|
+
|
91
|
+
foo.separator
|
92
|
+
foo.separator 'Options:'
|
93
|
+
|
94
|
+
foo.option :words do |o|
|
95
|
+
o.short '-w'
|
96
|
+
o.long '--words', 'WORDS+' # By default, the '+' at the end
|
97
|
+
# means that this takes multiple
|
98
|
+
# arguments. You put a '-' at
|
99
|
+
# the end of the argument list
|
100
|
+
# to stop parsing this option
|
101
|
+
# and allow for regular args.
|
102
|
+
o.desc "Other fun words to put in quotes"
|
103
|
+
o.default [] # The default anyway on multple arg options
|
104
|
+
|
105
|
+
# Sets the exact count of the number of arguments it accepts.
|
106
|
+
# also allowable are the single selectors :zero and :one.
|
107
|
+
# By default, the option 'WORDS+' sets the range to be
|
108
|
+
# {:at_least => 1, :at_most => 1000 }
|
109
|
+
o.count {:at_least => 2, :at_most => 10 }
|
110
|
+
|
111
|
+
o.validate do |words|
|
112
|
+
words.each do |word|
|
113
|
+
if word !~ /\w+/
|
114
|
+
o.fail "I can't print that: #{word}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Alternatively, we could have done the following:
|
121
|
+
foo.strings :words, "Other fun words to put in quotes" do |w|
|
122
|
+
w.count {:at_least => 2, :at_most => 10 }
|
123
|
+
w.validate do |words|
|
124
|
+
words.each do |word|
|
125
|
+
if word !~ /\w+/
|
126
|
+
w.fail "I can't print that: #{word}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Yet another shorthand notation for options, since they
|
133
|
+
# are boolean by default
|
134
|
+
foo.option :bold => {:long => '--bold', :default => false}
|
135
|
+
|
136
|
+
options.separator
|
137
|
+
# Tail options
|
138
|
+
|
139
|
+
# When any of the simpler notations are suffixed with a '_'
|
140
|
+
# character, the short option is always suppressed.
|
141
|
+
foo.boolean_ :debug, "Prints out extra debugging output."
|
142
|
+
|
143
|
+
# The '_' characters are replaced with '-' in flags, so the
|
144
|
+
# following creates a '--no-color' flag.
|
145
|
+
foo.boolean_ :no_color, "Turns off coloring in the output" do |o|
|
146
|
+
o.validate do
|
147
|
+
foo.printer :standard, :colored => false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Adds the standard -h/--help option.
|
152
|
+
# Should skip the '-h' flag if already set.
|
153
|
+
foo.help
|
154
|
+
|
155
|
+
# Adds the --version option.
|
156
|
+
foo.version "Foo: #{FOO_VERSION}"
|
157
|
+
|
158
|
+
# Now, add some validation for any addtional arguments
|
159
|
+
# that are left over after the parsing.
|
160
|
+
foo.arguments do |args|
|
161
|
+
if args.empty?
|
162
|
+
a.fail "You have to pass in empty arguments that do nothing!"
|
163
|
+
end
|
164
|
+
if args.count >= 3
|
165
|
+
a.fail "Whoa there! You're going argument crazy!"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
if __FILE__ == $0
|
171
|
+
# Parses and validates the options.
|
172
|
+
args = ['--prefix', '{',
|
173
|
+
'--suffix', '}',
|
174
|
+
'--words', 'high', 'there', 'you', '-',
|
175
|
+
# The '-' stops parsing this option, so that:
|
176
|
+
'handsom', 'devil',
|
177
|
+
'http://not.posting.here', # will be regular arguments
|
178
|
+
'-c, '3', # Count
|
179
|
+
'--', # Stops parsing all arguments
|
180
|
+
'-h', '--help', '-v', '--version' # Ignored
|
181
|
+
]
|
182
|
+
result = foo_cmd.parse!(args)
|
183
|
+
|
184
|
+
require 'pp'
|
185
|
+
pp result[:prefix] # => '{'
|
186
|
+
pp result[:suffix] # => '}'
|
187
|
+
pp reuslt[:count] # => 3
|
188
|
+
pp result[:bold] # => false
|
189
|
+
pp reuslt[:words] # => ['high', 'there', 'you']
|
190
|
+
pp reuslt.args # => ['handsom', 'devil',
|
191
|
+
# 'http://not.posting.here',
|
192
|
+
# '-h', '--help', '-v', '--version']
|
193
|
+
pp result.options # => {:prefix => '{', :suffix => '}'
|
194
|
+
# :count => 3, :bold => false,
|
195
|
+
# :words => ['high', 'there', 'you']}
|
196
|
+
|
197
|
+
# Now, call the command that does the actual work.
|
198
|
+
# This passes the foo_cmd.options and the foo_cmd.args
|
199
|
+
# as arguments to the executors 'execute!' method.
|
200
|
+
#
|
201
|
+
# This allows you to easily associate command classes with
|
202
|
+
# commands, without resorting to a hash or combining
|
203
|
+
# execution logic with command parsing logic.
|
204
|
+
foo_cmd.execute!(args) # {high,there,you,foo}
|
205
|
+
# {high,there,you,foo}
|
206
|
+
# {high,there,you,foo}
|
207
|
+
# and handsom devil http://not.posting.here -h --help -v --verbose
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
### Super Commands
|
212
|
+
|
213
|
+
You can also combine multiple choices into an uber-choice, creating
|
214
|
+
commands that look a lot like git or subversion.
|
215
|
+
|
216
|
+
First, we create another command.
|
217
|
+
|
218
|
+
#!/usr/bin/env ruby
|
219
|
+
# bar.rb
|
220
|
+
|
221
|
+
require 'rubygems'
|
222
|
+
require 'choosy'
|
223
|
+
|
224
|
+
class BarExecutor
|
225
|
+
def execute!(options, args)
|
226
|
+
if options[:bold]
|
227
|
+
puts "BOLDED BAR"
|
228
|
+
else
|
229
|
+
puts "bar"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Create a new command
|
235
|
+
bar_cmd = Choosy::Command.new :bar do |bar|
|
236
|
+
bar.executor BarExecutor.new
|
237
|
+
bar.summary "Just prints 'bar'"
|
238
|
+
bar.desc "A truly unremarkable command"
|
239
|
+
|
240
|
+
bar.boolean :bold, "Bolds something"
|
241
|
+
|
242
|
+
# Because there is no bar.arguments call,
|
243
|
+
# it is now an error if there are extra
|
244
|
+
# command line arguments to this command.
|
245
|
+
end
|
246
|
+
|
247
|
+
We can now create our super command.
|
248
|
+
|
249
|
+
#!/usr/bin/env ruby
|
250
|
+
# superfoo.rb
|
251
|
+
|
252
|
+
require 'rubygems'
|
253
|
+
require 'choosy'
|
254
|
+
require 'foo.rb'
|
255
|
+
require 'bar.rb'
|
256
|
+
|
257
|
+
SUPERFOO_VERSION = "1.0.1"
|
258
|
+
|
259
|
+
superfoo = Choosy::SuperCommand.new :superfoo do |superfoo|
|
260
|
+
superfoo.summary "This is a superfoo command."
|
261
|
+
superfoo.desc "Say something, dammit!"
|
262
|
+
|
263
|
+
# You can also add commands after instantiation.
|
264
|
+
# Note that, when added, these commands have their
|
265
|
+
# -h/--help/--version flags suppressed, so you'll
|
266
|
+
# need to add those flags here.
|
267
|
+
superfoo.commands bar_cmd
|
268
|
+
|
269
|
+
# Creates a 'help' command
|
270
|
+
superfoo.help do |help|
|
271
|
+
help.summary "Prints this help message"
|
272
|
+
end
|
273
|
+
|
274
|
+
# Create some global options that are parsed
|
275
|
+
# defore subcommand options
|
276
|
+
|
277
|
+
superfoo.option :config do |o|
|
278
|
+
o.long '--config', 'FILE'
|
279
|
+
o.desc "Configure your superfoo with a configuration file."
|
280
|
+
|
281
|
+
o.validate do |config|
|
282
|
+
if !File.exist? config
|
283
|
+
o.fail "Unable to find configuration file!"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Adds a global --version flag.
|
289
|
+
superfoo.version do
|
290
|
+
puts "#{SUPERFOO_VERSION}"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Add a command after the fact.
|
295
|
+
superfoo.commands foo_cmd
|
296
|
+
|
297
|
+
if __FILE__ == $0
|
298
|
+
superfoo.parse! ['-c', '5',
|
299
|
+
'foo',
|
300
|
+
'--config', '~/.superfoo',
|
301
|
+
'--prefix', '{',
|
302
|
+
'--suffix', '}',
|
303
|
+
'cruft',
|
304
|
+
'bar',
|
305
|
+
'--bold']
|
306
|
+
|
307
|
+
require 'pp'
|
308
|
+
pp superfoo[:config] # => '~/.superfoo'
|
309
|
+
pp superfoo.subcommand.name # => :foo
|
310
|
+
pp superfoo.subcommand[:prefix] # => '{'
|
311
|
+
pp superfoo.subcommand[:suffix] # => '}'
|
312
|
+
pp superfoo.subcommand[:count] # => 2
|
313
|
+
pp superfoo.subcommand[:bold] # => true
|
314
|
+
pp superfoo.subcommand.options # => {:prefix => '{', :suffix => '}'
|
315
|
+
# :count => 2,
|
316
|
+
# :bold => true,
|
317
|
+
# :words => [],
|
318
|
+
# :config => '~/.superfoo' }
|
319
|
+
pp superfoo.subcommand.args # => ['cruft', 'bar']
|
320
|
+
|
321
|
+
pp superfoo.options # => {:prefix => '{', :suffix => '}'
|
322
|
+
# :count => 2,
|
323
|
+
# :bold => true,
|
324
|
+
# :words => [],
|
325
|
+
# :config => '~/.superfoo' }
|
326
|
+
pp superfoo.args # => ['cruft', 'bar']
|
327
|
+
|
328
|
+
# Now, we can call the subcommand
|
329
|
+
superfoo.execute! ## Calls superfoo.subcommand.execute!
|
330
|
+
## Prints:
|
331
|
+
# BOLDED!!
|
332
|
+
# {foo}
|
333
|
+
# {foo}
|
334
|
+
# and cruft bar
|
335
|
+
|
336
|
+
# We got what we wanted, so reset the parser.
|
337
|
+
superfoo.reset!
|
338
|
+
|
339
|
+
# Instead of parsing the 'bar' parameter as an argument to
|
340
|
+
# the foo command, so that when the first argument that matches
|
341
|
+
# another command name is encountered, it stops parsing the
|
342
|
+
# current command and passes the rest of the arguments to the
|
343
|
+
# next command.
|
344
|
+
#
|
345
|
+
# You can also set this inside a SuperChoosy.new {|s| ... }
|
346
|
+
# block.
|
347
|
+
superfoo.parsimonious
|
348
|
+
|
349
|
+
superfoo.parse! ['-c', '5',
|
350
|
+
'foo',
|
351
|
+
'--config', '~/.superfoo',
|
352
|
+
'--prefix', '{',
|
353
|
+
'--suffix', '}',
|
354
|
+
'cruft',
|
355
|
+
'bar',
|
356
|
+
'--bold']
|
357
|
+
|
358
|
+
pp superfoo[:config] # => '~/.superfoo'
|
359
|
+
pp superfoo.subcommand.name # => :foo
|
360
|
+
pp superfoo.subcommands[0].name # => :foo
|
361
|
+
pp superfoo.subcommands[0][:prefix] # => '{'
|
362
|
+
pp superfoo.subcommands[0][:suffix] # => '}'
|
363
|
+
pp superfoo.subcommands[0][:count] # => 2
|
364
|
+
pp superfoo.subcommands[0][:bold] # => true
|
365
|
+
pp superfoo.subcommands[0].options # => {:prefix => '{', :suffix => '}'
|
366
|
+
# :count => 2,
|
367
|
+
# :bold => false,
|
368
|
+
# :words => [],
|
369
|
+
# :config => '~/.superfoo' }
|
370
|
+
pp superfoo.subcommands[0].args # => ['cruft']
|
371
|
+
|
372
|
+
pp superfoo.subcommands[1].name # => :bar
|
373
|
+
pp superfoo.subcommands[1][:bold] # => true
|
374
|
+
pp superfoo.subcommands[1].options # => {:bold => true,
|
375
|
+
# :config => '~/.superfoo'}
|
376
|
+
pp superfoo.subcommands[1].args # => []
|
377
|
+
|
378
|
+
pp superfoo.options # => {:config => '~/.superfoo'}
|
379
|
+
pp superfoo.args # => []
|
380
|
+
|
381
|
+
# Now, execute the subcommands in order
|
382
|
+
superfoo.execute! ## Same as:
|
383
|
+
# superfoo.subcommands.each do |subcommand|
|
384
|
+
# command.execute!
|
385
|
+
# end
|
386
|
+
## Prints:
|
387
|
+
# {foo}
|
388
|
+
# {foo}
|
389
|
+
# and cruft
|
390
|
+
# BOLDED BAR
|
391
|
+
end
|
392
|
+
|
393
|
+
### TODO: Output Printing
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
# $LOAD_PATH.unshift File.expand_path("../spec", __FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rake'
|
6
|
+
#require 'rake/rdoctask'
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
require './lib/choosy/version'
|
9
|
+
|
10
|
+
PACKAGE_NAME = "choosy"
|
11
|
+
PACKAGE_VERSION = Choosy::Version
|
12
|
+
|
13
|
+
desc "Default task"
|
14
|
+
task :default => [ :spec ]
|
15
|
+
|
16
|
+
desc "Build documentation"
|
17
|
+
task :doc => [ :rdoc ]
|
18
|
+
|
19
|
+
#task :rdoc => SOURCE_FILES
|
20
|
+
|
21
|
+
desc "Run the RSpec tests"
|
22
|
+
RSpec::Core::RakeTask.new :spec
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'jeweler'
|
26
|
+
Jeweler::Tasks.new do |gem|
|
27
|
+
gem.name = PACKAGE_NAME
|
28
|
+
gem.version = PACKAGE_VERSION
|
29
|
+
gem.summary = 'Yet another option parsing library.'
|
30
|
+
gem.description = 'This is a DSL for creating more complicated command line tools.'
|
31
|
+
gem.email = ['madeonamac@gmail.com']
|
32
|
+
gem.authors = ['Gabe McArthur']
|
33
|
+
gem.homepage = 'http://github.com/gabemc/choosy'
|
34
|
+
gem.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
|
35
|
+
|
36
|
+
gem.add_development_dependency 'rspec', '~> 2.5'
|
37
|
+
end
|
38
|
+
rescue LoadError
|
39
|
+
puts "Jeweler or dependencies are not available. Install it with: gem install jeweler"
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Cleans the generated files."
|
43
|
+
task :clean do
|
44
|
+
rm Dir.glob('*.gemspec')
|
45
|
+
rm Dir.glob('*.gem')
|
46
|
+
rm_rf 'pkg'
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Deploys the gem to rubygems.org"
|
50
|
+
task :gem => :release do
|
51
|
+
system("gem build #{PACKAGE_NAME}.gemspec")
|
52
|
+
system("gem push #{PACKAGE_NAME}-#{PACKAGE_VERSION}.gem")
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Does the full release cycle."
|
56
|
+
task :deploy => [:gem, :clean] do
|
57
|
+
end
|
data/lib/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'choosy/errors'
|
2
|
+
|
3
|
+
module Choosy
|
4
|
+
class BaseCommand
|
5
|
+
attr_accessor :name, :summary, :description, :printer
|
6
|
+
attr_reader :builder, :listing, :option_builders
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
@listing = []
|
11
|
+
@option_builders = {}
|
12
|
+
|
13
|
+
@builder = create_builder
|
14
|
+
yield @builder if block_given?
|
15
|
+
@builder.finalize!
|
16
|
+
end
|
17
|
+
|
18
|
+
def alter(&block)
|
19
|
+
yield @builder if block_given?
|
20
|
+
@builder.finalize!
|
21
|
+
end
|
22
|
+
|
23
|
+
def options
|
24
|
+
@option_builders.values.map {|b| b.option}
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse!(args, propagate=false)
|
28
|
+
if propagate
|
29
|
+
return parse(args)
|
30
|
+
else
|
31
|
+
begin
|
32
|
+
return parse(args)
|
33
|
+
rescue Choosy::ValidationError, Choosy::ConversionError, Choosy::ParseError => e
|
34
|
+
$stderr << "#{@name}: #{e.message}\n"
|
35
|
+
exit 1
|
36
|
+
rescue Choosy::HelpCalled => e
|
37
|
+
handle_help(e)
|
38
|
+
exit 0
|
39
|
+
rescue Choosy::VersionCalled => e
|
40
|
+
$stdout << "#{e.message}\n"
|
41
|
+
exit 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
def create_builder
|
48
|
+
# Override in subclasses
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse(args)
|
52
|
+
# Override in subclasses
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_help(hc)
|
56
|
+
# Override in subclasses
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'choosy/errors'
|
2
|
+
require 'choosy/base_command'
|
3
|
+
require 'choosy/dsl/command_builder'
|
4
|
+
require 'choosy/parser'
|
5
|
+
require 'choosy/verifier'
|
6
|
+
|
7
|
+
module Choosy
|
8
|
+
class Command < BaseCommand
|
9
|
+
attr_accessor :executor, :argument_validation
|
10
|
+
|
11
|
+
def execute!(args)
|
12
|
+
raise Choosy::ConfigurationError.new("No executor given for: #{name}") unless executor
|
13
|
+
result = parse!(args)
|
14
|
+
executor.call(result.options, result.args)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
def create_builder
|
19
|
+
Choosy::DSL::CommandBuilder.new(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle_help(hc)
|
23
|
+
printer.print!(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse(args)
|
27
|
+
parser = Parser.new(self)
|
28
|
+
result = parser.parse!(args)
|
29
|
+
result.verify!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|