choosy 0.1.0
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/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
|