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.
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