choosy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/Gemfile +7 -0
  2. data/Gemfile.lock +25 -0
  3. data/LICENSE +23 -0
  4. data/README.markdown +393 -0
  5. data/Rakefile +57 -0
  6. data/lib/VERSION +1 -0
  7. data/lib/choosy/base_command.rb +59 -0
  8. data/lib/choosy/command.rb +32 -0
  9. data/lib/choosy/converter.rb +115 -0
  10. data/lib/choosy/dsl/base_command_builder.rb +172 -0
  11. data/lib/choosy/dsl/command_builder.rb +43 -0
  12. data/lib/choosy/dsl/option_builder.rb +155 -0
  13. data/lib/choosy/dsl/super_command_builder.rb +41 -0
  14. data/lib/choosy/errors.rb +11 -0
  15. data/lib/choosy/option.rb +22 -0
  16. data/lib/choosy/parse_result.rb +64 -0
  17. data/lib/choosy/parser.rb +184 -0
  18. data/lib/choosy/printing/color.rb +101 -0
  19. data/lib/choosy/printing/erb_printer.rb +23 -0
  20. data/lib/choosy/printing/help_printer.rb +174 -0
  21. data/lib/choosy/super_command.rb +77 -0
  22. data/lib/choosy/super_parser.rb +81 -0
  23. data/lib/choosy/verifier.rb +62 -0
  24. data/lib/choosy/version.rb +12 -0
  25. data/lib/choosy.rb +16 -0
  26. data/spec/choosy/base_command_spec.rb +11 -0
  27. data/spec/choosy/command_spec.rb +51 -0
  28. data/spec/choosy/converter_spec.rb +145 -0
  29. data/spec/choosy/dsl/base_command_builder_spec.rb +328 -0
  30. data/spec/choosy/dsl/commmand_builder_spec.rb +80 -0
  31. data/spec/choosy/dsl/option_builder_spec.rb +386 -0
  32. data/spec/choosy/dsl/super_command_builder_spec.rb +83 -0
  33. data/spec/choosy/parser_spec.rb +275 -0
  34. data/spec/choosy/printing/color_spec.rb +74 -0
  35. data/spec/choosy/printing/help_printer_spec.rb +117 -0
  36. data/spec/choosy/super_command_spec.rb +80 -0
  37. data/spec/choosy/super_parser_spec.rb +106 -0
  38. data/spec/choosy/verifier_spec.rb +180 -0
  39. data/spec/integration/command-A_spec.rb +37 -0
  40. data/spec/integration/supercommand-A_spec.rb +61 -0
  41. data/spec/spec_helpers.rb +30 -0
  42. metadata +150 -0
data/lib/choosy.rb ADDED
@@ -0,0 +1,16 @@
1
+ # $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'choosy/command'
4
+ require 'choosy/super_command'
5
+ require 'choosy/converter'
6
+ require 'choosy/errors'
7
+ require 'choosy/option'
8
+ require 'choosy/version'
9
+ require 'choosy/parser'
10
+ require 'choosy/verifier'
11
+ require 'choosy/dsl/command_builder'
12
+ require 'choosy/dsl/super_command_builder'
13
+ require 'choosy/dsl/option_builder'
14
+ require 'choosy/printing/color'
15
+ require 'choosy/printing/erb_printer'
16
+ require 'choosy/printing/help_printer'
@@ -0,0 +1,11 @@
1
+ require 'spec_helpers'
2
+ require 'choosy/command'
3
+
4
+ module Choosy
5
+ describe BaseCommand do
6
+ it "should finalize the builder" do
7
+ cmd = Command.new :cmd
8
+ cmd.printer.should be_a(Choosy::Printing::HelpPrinter)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helpers'
2
+ require 'choosy/command'
3
+ require 'choosy/printing/help_printer'
4
+
5
+ module Choosy
6
+ describe Command do
7
+ before :each do
8
+ @c = Command.new :foo
9
+ end
10
+
11
+ describe :parse! do
12
+ it "should print out the version number" do
13
+ @c.alter do |c|
14
+ c.version "blah"
15
+ end
16
+
17
+ o = capture :stdout do
18
+ attempting {
19
+ @c.parse!(['--version'])
20
+ }.should raise_error(SystemExit)
21
+ end
22
+
23
+ o.should eql("blah\n")
24
+ end
25
+
26
+ it "should print out the help info" do
27
+ @c.alter do |c|
28
+ c.summary "Summary"
29
+ c.desc "this is a description"
30
+ c.help
31
+ end
32
+
33
+ o = capture :stdout do
34
+ attempting {
35
+ @c.parse!(['--help'])
36
+ }.should raise_error(SystemExit)
37
+ end
38
+
39
+ o.should match(/-h, --help/)
40
+ end
41
+ end
42
+
43
+ describe :execute! do
44
+ it "should fail when no executor is given" do
45
+ attempting {
46
+ @c.execute!(['a', 'b'])
47
+ }.should raise_error(Choosy::ConfigurationError, /No executor/)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,145 @@
1
+ require 'spec_helpers'
2
+ require 'choosy/version'
3
+ require 'choosy/converter'
4
+
5
+ module Choosy
6
+ describe Converter do
7
+
8
+ describe :for do
9
+ describe "symbols" do
10
+ it "knows about strings" do
11
+ Converter.for(:string).should eql(:string)
12
+ end
13
+
14
+ it "knows about files" do
15
+ Converter.for(:file).should eql(:file)
16
+ end
17
+
18
+ it "knows about ints" do
19
+ Converter.for(:int).should eql(:integer)
20
+ end
21
+
22
+ it "knows about integers" do
23
+ Converter.for(:integer).should eql(:integer)
24
+ end
25
+
26
+ it "knows about floats" do
27
+ Converter.for(:float).should eql(:float)
28
+ end
29
+
30
+ it "knows about Dates" do
31
+ Converter.for(:date).should eql(:date)
32
+ end
33
+
34
+ it "knows about Time" do
35
+ Converter.for(:time).should eql(:time)
36
+ end
37
+
38
+ it "knows about DateTime" do
39
+ Converter.for(:datetime).should eql(:datetime)
40
+ end
41
+ end
42
+
43
+ describe "unknown types" do
44
+ it "returns nil" do
45
+ Converter.for(Hash).should be(nil)
46
+ end
47
+ end
48
+
49
+ describe "unknown symbols" do
50
+ it "returns nil" do
51
+ Converter.for(:tada).should be(nil)
52
+ end
53
+ end
54
+
55
+ describe "booleans" do
56
+ it "returns :boolean on :boolean" do
57
+ Converter.for(:boolean).should eql(:boolean)
58
+ end
59
+
60
+ it "returns :boolean on :bool" do
61
+ Converter.for(:bool).should eql(:boolean)
62
+ end
63
+ end
64
+ end#for
65
+
66
+ describe "converting" do
67
+ describe :integer do
68
+ it "should return an integer value" do
69
+ Converter.convert(:integer, "42").should eql(42)
70
+ end
71
+ end
72
+
73
+ describe :float do
74
+ it "should return an float value" do
75
+ Converter.convert(:float, "3.2").should eql(3.2)
76
+ end
77
+ end
78
+
79
+ describe :symbol do
80
+ it "should return a symbol" do
81
+ Converter.convert(:symbol, "this").should eql(:this)
82
+ end
83
+ end
84
+
85
+ describe :file do
86
+ it "should return a file" do
87
+ Converter.convert(:file, __FILE__).path.should eql(__FILE__)
88
+ end
89
+
90
+ it "should succeed if the file is present" do
91
+ Converter.convert(:file, __FILE__).path.should eql(__FILE__)
92
+ end
93
+
94
+ it "should fail if the file is not present" do
95
+ attempting {
96
+ Converter.convert(:file, __FILE__ + "nothere")
97
+ }.should raise_error(Choosy::ValidationError, /Unable to locate file:/)
98
+ end
99
+ end
100
+
101
+ describe :yaml do
102
+ it "should raise an error if the file doesn't exist" do
103
+ attempting {
104
+ Converter.convert(:yaml, __FILE__ + "non")
105
+ }.should raise_error(Choosy::ValidationError, /Unable to locate/)
106
+ end
107
+
108
+ it "should be able to parse a good yaml file" do
109
+ y = Converter.convert(:yaml, File.join(File.dirname(__FILE__), '..', '..', 'lib', 'VERSION'))
110
+ y.should eql(Choosy::Version.to_s.chomp)
111
+ end
112
+ end
113
+
114
+ describe :date do
115
+ it "should return a Date" do
116
+ date = Converter.convert(:date, "July 17, 2010")
117
+ date.month.should eql(7)
118
+ date.day.should eql(17)
119
+ date.year.should eql(2010)
120
+ end
121
+ end
122
+
123
+ describe :time do
124
+ it "should return a Time" do
125
+ time = Converter.convert(:time, "12:30:15")
126
+ time.hour.should eql(12)
127
+ time.min.should eql(30)
128
+ time.sec.should eql(15)
129
+ end
130
+ end
131
+
132
+ describe :datetime do
133
+ it "should return a DateTime" do
134
+ datetime = Converter.convert(:datetime, "July 17, 2010 12:30:15")
135
+ datetime.month.should eql(7)
136
+ datetime.day.should eql(17)
137
+ datetime.year.should eql(2010)
138
+ datetime.hour.should eql(12)
139
+ datetime.min.should eql(30)
140
+ datetime.sec.should eql(15)
141
+ end
142
+ end
143
+ end#converting
144
+ end
145
+ end
@@ -0,0 +1,328 @@
1
+ require 'spec_helpers'
2
+ require 'choosy/dsl/base_command_builder'
3
+ require 'choosy/command'
4
+
5
+ module Choosy::DSL
6
+ describe BaseCommandBuilder do
7
+ before :each do
8
+ @command = Choosy::Command.new(:cmd)
9
+ @builder = @command.builder
10
+ end
11
+
12
+ describe :printer do
13
+ it "should raise an error on a non-standard printer" do
14
+ attempting {
15
+ @builder.printer :non_standard
16
+ }.should raise_error(Choosy::ConfigurationError, /Unknown printing/)
17
+ end
18
+
19
+ it "should know how to set the default printer" do
20
+ @builder.printer :standard
21
+ @command.printer.should be_a(Choosy::Printing::HelpPrinter)
22
+ end
23
+
24
+ it "should know how to turn off color" do
25
+ @builder.printer :standard, :color => false
26
+ @command.printer.color.disabled?.should be(true)
27
+ end
28
+
29
+ class TestPrinter
30
+ def print!(cmd)
31
+ end
32
+ end
33
+
34
+ it "should understand how to set custom printers" do
35
+ attempting {
36
+ @builder.printer TestPrinter.new
37
+ }.should_not raise_error
38
+ end
39
+
40
+ it "should fail when the printer doesn't implement 'print!'" do
41
+ attempting {
42
+ @builder.printer "this"
43
+ }.should raise_error(Choosy::ConfigurationError, /Unknown printing/)
44
+ end
45
+
46
+ describe "for :erb printing" do
47
+ it "should be able to handle a given template" do
48
+ @builder.printer :erb, :template => __FILE__
49
+ @command.printer.should be_a(Choosy::Printing::ERBPrinter)
50
+ end
51
+
52
+ it "should fail when the tempate file doesn't exist" do
53
+ attempting {
54
+ @builder.printer :erb, :template => "non"
55
+ }.should raise_error(Choosy::ConfigurationError, /template file doesn't exist/)
56
+ end
57
+
58
+ it "should fail when no template option is specified" do
59
+ attempting {
60
+ @builder.printer :erb
61
+ }.should raise_error(Choosy::ConfigurationError, /no template/)
62
+ end
63
+ end
64
+ end#printer
65
+
66
+ describe :summary do
67
+ it "should set the summary for this command" do
68
+ @builder.summary "This is a summary"
69
+ @command.summary.should eql("This is a summary")
70
+ end
71
+ end#summary
72
+
73
+ describe :desc do
74
+ it "should set the summary for this command" do
75
+ @builder.desc "This is a description"
76
+ @command.description.should match(/This is/)
77
+ end
78
+ end#desc
79
+
80
+ describe :separator do
81
+ it "should add a separator to the list of options for printing" do
82
+ @builder.separator 'Required arguments'
83
+ @command.listing[0].should eql('Required arguments')
84
+ end
85
+
86
+ it "should add an empty string to the listing when called with no arguments" do
87
+ @builder.separator
88
+ @command.listing[0].should eql('')
89
+ end
90
+ end#separator
91
+
92
+ describe :option do
93
+ describe "when using just a name" do
94
+ it "should set the name of the option" do
95
+ o = @builder.option :opto do |o|
96
+ o.short '-o'
97
+ end
98
+ o.name.should eql(:opto)
99
+ end
100
+
101
+ it "should handle a CommandBuilder block" do
102
+ @builder.option :opto do |o|
103
+ o.short '-s'
104
+ o.should be_a(OptionBuilder)
105
+ end
106
+ end
107
+
108
+ it "should fail when no block is given" do
109
+ attempting {
110
+ @builder.option "blah"
111
+ }.should raise_error(Choosy::ConfigurationError, /No configuration block/)
112
+ end
113
+
114
+ it "should fail when the symbol is nil" do
115
+ attempting {
116
+ @builder.option nil do |o|
117
+ o.short = '-o'
118
+ end
119
+ }.should raise_error(Choosy::ConfigurationError, /The option name was nil/)
120
+ end
121
+ end
122
+
123
+ describe "when using a hash" do
124
+ describe "for dependencies" do
125
+ it "should set the dependencies of that option" do
126
+ o = @builder.option :o => [:s] do |o|
127
+ o.short '-o'
128
+ end
129
+ o.dependent_options.should have(1).items
130
+ o.dependent_options[0].should eql(:s)
131
+ end
132
+ end
133
+
134
+ describe "for options" do
135
+ it "should set the name of the option" do
136
+ o = @builder.option :o => {:short => '-o'}
137
+ o.name.should eql(:o)
138
+ end
139
+
140
+ it "should set the properties via the hash" do
141
+ o = @builder.option :o => {:short => '-o'}
142
+ o.short_flag.should eql('-o')
143
+ end
144
+
145
+ it "should still accept the block" do
146
+ o = @builder.option :o => {:short => '-o'} do |s|
147
+ s.desc "short"
148
+ end
149
+ o.description.should eql('short')
150
+ end
151
+ end
152
+
153
+ describe "whose arguments are invalid" do
154
+ it "should fail when more than one key is present" do
155
+ attempting {
156
+ @builder.option({:o => nil, :p => nil})
157
+ }.should raise_error(Choosy::ConfigurationError, /Malformed option hash/)
158
+ end
159
+
160
+ it "should fail when more than the hash is empty" do
161
+ attempting {
162
+ @builder.option({})
163
+ }.should raise_error(Choosy::ConfigurationError, /Malformed option hash/)
164
+ end
165
+
166
+ it "should fail when the option value is not an Array or a Hash" do
167
+ attempting {
168
+ @builder.option({:o => :noop})
169
+ }.should raise_error(Choosy::ConfigurationError, "Unable to process option hash")
170
+ end
171
+ end
172
+
173
+ describe "after the option block has been processed" do
174
+ it "should call the 'finalize!' method on the builder" do
175
+ o = @builder.option :o do |o|
176
+ o.short '-o'
177
+ end
178
+ o.cast_to.should eql(:boolean)
179
+ end
180
+
181
+ it "should add the builder with the given name to the command option_builders hash" do
182
+ @builder.option :o => {:short => '-l'}
183
+ @builder.command.option_builders.should have(1).item
184
+ end
185
+
186
+ it "adds the option to the listing" do
187
+ @builder.option :o => {:short => '-l'}
188
+ @builder.command.listing.should have(1).item
189
+ @builder.command.listing[0].name.should eql(:o)
190
+ end
191
+ end
192
+ end
193
+ end#option
194
+
195
+ describe :boolean do
196
+ it "should be able to set short flag" do
197
+ o = @builder.boolean :Debug, "Show the debug output"
198
+ o.short_flag.should eql('-D')
199
+ end
200
+
201
+ it "should be able to set the long flag" do
202
+ o = @builder.boolean :Debug, "show debug"
203
+ o.long_flag.should eql('--debug')
204
+ end
205
+
206
+ it "should be able to fix the naming convention for long names" do
207
+ o = @builder.boolean :no_COLOR, "No color output"
208
+ o.long_flag.should eql('--no-color')
209
+ end
210
+
211
+ it "should set the description" do
212
+ o = @builder.boolean :no_color, "No color output"
213
+ o.description.should eql("No color output")
214
+ end
215
+
216
+ it "should handle optional configuration" do
217
+ o = @builder.boolean :debug, "Debug", {:short => '-D'}
218
+ o.short_flag.should eql('-D')
219
+ end
220
+
221
+ it "should be able to capture block level data" do
222
+ o = @builder.boolean :debug, "Show debug output" do |d|
223
+ d.short '-D'
224
+ end
225
+ o.short_flag.should eql("-D")
226
+ end
227
+
228
+ it "should suppress the short flag for boolean_" do
229
+ o = @builder.boolean_ :debug, "Show debug output"
230
+ o.short_flag.should be(nil)
231
+ end
232
+ end#boolean
233
+
234
+ describe :single do
235
+ it "should be able to set the short flag" do
236
+ o = @builder.single :count, "Show the count"
237
+ o.short_flag.should eql('-c')
238
+ end
239
+
240
+ it "should be able to set the parameter name" do
241
+ o = @builder.single :count, "Show the count"
242
+ o.flag_parameter.should eql('COUNT')
243
+ end
244
+ end#single
245
+
246
+ describe :multiple do
247
+ it "should be able to set the short flag" do
248
+ o = @builder.multiple :file_names, "The file names"
249
+ o.short_flag.should eql('-f')
250
+ end
251
+
252
+ it "should be able to set the parameter name" do
253
+ o = @builder.multiple :file_names, "The file names"
254
+ o.flag_parameter.should eql('FILE_NAMES+')
255
+ end
256
+
257
+ it "should be able to yield a block" do
258
+ o = @builder.multiple :file_names, "The file names" do |f|
259
+ f.long '--files'
260
+ end
261
+ o.long_flag.should eql('--files')
262
+ end
263
+ end#multiple
264
+
265
+ describe "dynamically generated method for" do
266
+ Choosy::Converter::CONVERSIONS.keys.each do |method|
267
+ it "#{method}" do
268
+ o = @builder.send(method, method, "Desc of #{method}")
269
+ o.cast_to.should eql(Choosy::Converter.for(method))
270
+ if o.cast_to == :boolean
271
+ o.flag_parameter.should be(nil)
272
+ else
273
+ o.flag_parameter.should eql(method.to_s.upcase)
274
+ end
275
+ end
276
+
277
+ it "#{method}_" do
278
+ underscore = "#{method}_".to_sym
279
+ o = @builder.send(underscore, method, "Desc of #{method}_")
280
+ o.short_flag.should be(nil)
281
+ end
282
+
283
+ next if method == :boolean || method == :bool
284
+
285
+ it "#{method}s" do
286
+ plural = "#{method}s".to_sym
287
+ o = @builder.send(plural, plural, "Desc of #{plural}")
288
+ o.cast_to.should eql(Choosy::Converter.for(method))
289
+ o.flag_parameter.should eql("#{plural.to_s.upcase}+")
290
+ end
291
+
292
+ it "#{method}s_" do
293
+ underscore = "#{method}s_".to_sym
294
+ o = @builder.send(underscore, method, "Desc of #{method}s_")
295
+ o.short_flag.should be(nil)
296
+ end
297
+ end
298
+ end
299
+
300
+ describe :version do
301
+ it "should allow you to set the message" do
302
+ v = @builder.version 'blah'
303
+ attempting {
304
+ v.validation_step.call
305
+ }.should raise_error(Choosy::VersionCalled, 'blah')
306
+ end
307
+
308
+ it "should allow you to use a block to alter the help message" do
309
+ v = @builder.version 'blah' do |v|
310
+ v.desc "Version"
311
+ end
312
+ v.description.should eql("Version")
313
+ end
314
+
315
+ it "should set the message automatically" do
316
+ v = @builder.version "blah"
317
+ v.description.should eql("The version number")
318
+ end
319
+ end#version
320
+
321
+ describe :finalize! do
322
+ it "should set the printer if not already set" do
323
+ @builder.finalize!
324
+ @command.printer.should be_a(Choosy::Printing::HelpPrinter)
325
+ end
326
+ end#finalize!
327
+ end
328
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helpers'
2
+ require 'choosy/dsl/command_builder'
3
+ require 'choosy/command'
4
+ require 'choosy/converter'
5
+ require 'choosy/errors'
6
+
7
+ module Choosy::DSL
8
+ class FakeExecutor
9
+ attr_reader :options, :args
10
+ def execute!(options, args)
11
+ @options = options
12
+ @args = args
13
+ end
14
+ end
15
+
16
+ describe CommandBuilder do
17
+ before :each do
18
+ @command = Choosy::Command.new(:cmd)
19
+ @builder = @command.builder
20
+ end
21
+
22
+ describe :executor do
23
+ it "should set the executor in the command" do
24
+ @builder.executor FakeExecutor.new
25
+ @command.executor.should be_a(FakeExecutor)
26
+ end
27
+
28
+ it "should handle proc arguments" do
29
+ @builder.executor {|opts, args| puts "hi"}
30
+ @command.executor.should_not be(nil)
31
+ end
32
+
33
+ it "should raise an error if the executor is nil" do
34
+ attempting {
35
+ @builder.executor nil
36
+ }.should raise_error(Choosy::ConfigurationError, /executor was nil/)
37
+ end
38
+
39
+ it "should raise an error if the executor class doesn't have an 'execute!' method" do
40
+ attempting {
41
+ @builder.executor Array.new
42
+ }.should raise_error(Choosy::ConfigurationError, /'execute!'/)
43
+ end
44
+ end#executor
45
+
46
+ describe :help do
47
+ it "should allow for a no arg" do
48
+ h = @builder.help
49
+ h.description.should eql("Show this help message")
50
+ end
51
+
52
+ it "should allow you to set the message" do
53
+ h = @builder.help 'Help message'
54
+ h.description.should eql('Help message')
55
+ end
56
+
57
+ it "should throw a HelpCalled upon validation" do
58
+ h = @builder.help
59
+ attempting {
60
+ h.validation_step.call
61
+ }.should raise_error(Choosy::HelpCalled)
62
+ end
63
+ end#help
64
+
65
+ describe :arguments do
66
+ it "should fail if there is no block given" do
67
+ attempting {
68
+ @builder.arguments
69
+ }.should raise_error(Choosy::ConfigurationError, /arguments/)
70
+ end
71
+
72
+ it "should pass in the arguments to validate" do
73
+ @builder.arguments do |args|
74
+ args.should have(3).items
75
+ end
76
+ @command.argument_validation.call([1, 2, 3])
77
+ end
78
+ end#arguments
79
+ end
80
+ end