optparse-plus 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +7 -0
  6. data/CHANGES.md +66 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE.txt +201 -0
  9. data/README.rdoc +173 -0
  10. data/Rakefile +94 -0
  11. data/bin/optparse_plus +130 -0
  12. data/fix.rb +29 -0
  13. data/lib/optparse-plus.rb +1 -0
  14. data/lib/optparse_plus.rb +15 -0
  15. data/lib/optparse_plus/argv_parser.rb +50 -0
  16. data/lib/optparse_plus/cli.rb +116 -0
  17. data/lib/optparse_plus/cli_logger.rb +133 -0
  18. data/lib/optparse_plus/cli_logging.rb +138 -0
  19. data/lib/optparse_plus/cucumber.rb +119 -0
  20. data/lib/optparse_plus/error.rb +32 -0
  21. data/lib/optparse_plus/execution_strategy/base.rb +34 -0
  22. data/lib/optparse_plus/execution_strategy/jvm.rb +37 -0
  23. data/lib/optparse_plus/execution_strategy/mri.rb +16 -0
  24. data/lib/optparse_plus/execution_strategy/open_3.rb +16 -0
  25. data/lib/optparse_plus/execution_strategy/open_4.rb +22 -0
  26. data/lib/optparse_plus/execution_strategy/rbx_open_4.rb +12 -0
  27. data/lib/optparse_plus/exit_now.rb +40 -0
  28. data/lib/optparse_plus/main.rb +603 -0
  29. data/lib/optparse_plus/process_status.rb +45 -0
  30. data/lib/optparse_plus/sh.rb +223 -0
  31. data/lib/optparse_plus/test/base_integration_test.rb +31 -0
  32. data/lib/optparse_plus/test/integration_test_assertions.rb +65 -0
  33. data/lib/optparse_plus/version.rb +3 -0
  34. data/optparse_plus.gemspec +28 -0
  35. data/templates/full/.gitignore.erb +4 -0
  36. data/templates/full/README.rdoc.erb +24 -0
  37. data/templates/full/Rakefile.erb +71 -0
  38. data/templates/full/_license_head.txt.erb +2 -0
  39. data/templates/full/apache_LICENSE.txt.erb +203 -0
  40. data/templates/full/bin/executable.erb +45 -0
  41. data/templates/full/custom_LICENSE.txt.erb +0 -0
  42. data/templates/full/gplv2_LICENSE.txt.erb +14 -0
  43. data/templates/full/gplv3_LICENSE.txt.erb +14 -0
  44. data/templates/full/mit_LICENSE.txt.erb +7 -0
  45. data/templates/rspec/spec/something_spec.rb.erb +5 -0
  46. data/templates/test_unit/test/integration/test_cli.rb.erb +11 -0
  47. data/templates/test_unit/test/unit/test_something.rb.erb +7 -0
  48. data/test/integration/base_integration_test.rb +60 -0
  49. data/test/integration/test_bootstrap.rb +150 -0
  50. data/test/integration/test_cli.rb +21 -0
  51. data/test/integration/test_license.rb +56 -0
  52. data/test/integration/test_readme.rb +53 -0
  53. data/test/integration/test_rspec.rb +28 -0
  54. data/test/integration/test_version.rb +21 -0
  55. data/test/unit/base_test.rb +19 -0
  56. data/test/unit/command_for_tests.sh +7 -0
  57. data/test/unit/execution_strategy/test_base.rb +24 -0
  58. data/test/unit/execution_strategy/test_jvm.rb +77 -0
  59. data/test/unit/execution_strategy/test_mri.rb +32 -0
  60. data/test/unit/execution_strategy/test_open_3.rb +70 -0
  61. data/test/unit/execution_strategy/test_open_4.rb +86 -0
  62. data/test/unit/execution_strategy/test_rbx_open_4.rb +25 -0
  63. data/test/unit/test/test_integration_test_assertions.rb +211 -0
  64. data/test/unit/test_cli_logger.rb +219 -0
  65. data/test/unit/test_cli_logging.rb +243 -0
  66. data/test/unit/test_exit_now.rb +37 -0
  67. data/test/unit/test_main.rb +840 -0
  68. data/test/unit/test_sh.rb +404 -0
  69. metadata +260 -0
@@ -0,0 +1,243 @@
1
+ require 'base_test'
2
+ require 'optparse_plus'
3
+ require 'stringio'
4
+
5
+ class TestCLILogging < BaseTest
6
+ include OptparsePlus
7
+
8
+ SLEEP_TIME = 0.1
9
+
10
+ def setup
11
+ @blank_format = proc do |severity,datetime,progname,msg|
12
+ msg + "\n"
13
+ end
14
+ @real_stderr = $stderr
15
+ @real_stdout = $stdout
16
+ $stderr = StringIO.new
17
+ $stdout = StringIO.new
18
+ end
19
+
20
+ def teardown
21
+ $stderr = @real_stderr
22
+ $stdout = @real_stdout
23
+ end
24
+
25
+ test_that "a class can include CLILogging and get terser logging" do
26
+ Given {
27
+ @class_with_logger = MyClassThatLogsToStdout.new
28
+ }
29
+
30
+ When {
31
+ @class_with_logger.doit
32
+ }
33
+
34
+ Then {
35
+ $stdout.string.should be == "debug\ninfo\nwarn\nerror\nfatal\n"
36
+ $stderr.string.should be == "warn\nerror\nfatal\n"
37
+ }
38
+ end
39
+
40
+ test_that "another class using CLILogging gets the same logger instance" do
41
+ Given {
42
+ @first = MyClassThatLogsToStdout.new
43
+ @second = MyOtherClassThatLogsToStdout.new
44
+ }
45
+ Then {
46
+ @first.logger_id.should be == @second.logger_id
47
+ }
48
+ end
49
+
50
+ test_that "we can change the global logger via self." do
51
+ Given {
52
+ @first = MyClassThatLogsToStdout.new
53
+ @second = MyOtherClassThatLogsToStdout.new
54
+ @logger_id = @second.logger_id
55
+ }
56
+ When {
57
+ @second.instance_eval do
58
+ self.logger=(OptparsePlus::CLILogger.new)
59
+ end
60
+ }
61
+ Then {
62
+ @logger_id.should_not be == @second.logger_id
63
+ @first.logger_id.should be == @second.logger_id
64
+ }
65
+ end
66
+
67
+ test_that "we can change the global logger change_logger()" do
68
+ Given {
69
+ @first = MyClassThatLogsToStdout.new
70
+ @second = MyOtherClassThatLogsToStdout.new
71
+ @logger_id = @second.logger_id
72
+ }
73
+ When {
74
+ @second.instance_eval do
75
+ change_logger(Logger.new(STDERR))
76
+ end
77
+ }
78
+ Then {
79
+ @logger_id.should_not be == @second.logger_id
80
+ @first.logger_id.should be == @second.logger_id
81
+ }
82
+ end
83
+
84
+ test_that "we cannot use a nil logger" do
85
+ Given {
86
+ @other_class = MyOtherClassThatLogsToStdout.new
87
+ }
88
+ Then {
89
+ lambda {
90
+ MyOtherClassThatLogsToStdout.new.instance_eval do
91
+ self.logger=(nil)
92
+ end
93
+ }.should raise_error(ArgumentError)
94
+ }
95
+ end
96
+
97
+ test_that "when we call use_log_level_option, it sets up logging level CLI options" do
98
+ Given {
99
+ @app = MyAppThatActsLikeItUsesMain.new
100
+ @app.call_use_log_level_option
101
+ @level = any_int
102
+ }
103
+ When {
104
+ @app.use_option(@level)
105
+ }
106
+ Then {
107
+ @app.logger.level.should be == @level
108
+ }
109
+ end
110
+
111
+ test_that "when we call use_log_level_option, then later change the logger, that logger gets the proper level set" do
112
+ Given {
113
+ @app = MyAppThatActsLikeItUsesMain.new
114
+ @app.call_use_log_level_option
115
+ @level = any_int
116
+ }
117
+ When {
118
+ @app.use_option(@level)
119
+ @other_logger = OpenStruct.new
120
+ @app.change_logger(@other_logger)
121
+ }
122
+ Then {
123
+ @other_logger.level.should be == @level
124
+ }
125
+ end
126
+
127
+
128
+ test_that "when we enable runtime log level toggling, it toggles the log level on receiving the set signal" do
129
+ Given {
130
+ @app = MyAppThatActsLikeItUsesMain.new
131
+ @app.call_use_log_level_option( :toggle_debug_on_signal => 'USR2' )
132
+ @level = Logger::INFO
133
+ @app.use_option(@level)
134
+ }
135
+ When {
136
+ send_signal_and_wait_a_moment('USR2')
137
+ }
138
+ Then {
139
+ @app.logger.level.should be == Logger::DEBUG
140
+ }
141
+ end
142
+
143
+ test_that "when we toggle the log level and change the logger, the new logger has also it's log level increased" do
144
+ Given {
145
+ @app = MyAppThatActsLikeItUsesMain.new
146
+ @app.call_use_log_level_option( :toggle_debug_on_signal => 'USR2' )
147
+ @level = Logger::INFO
148
+ @app.use_option(@level)
149
+ }
150
+ When {
151
+ send_signal_and_wait_a_moment('USR2')
152
+ @other_logger = OpenStruct.new
153
+ @app.change_logger(@other_logger)
154
+ }
155
+ Then {
156
+ @other_logger.level.should be == Logger::DEBUG
157
+ }
158
+ end
159
+
160
+ test_that "when we enable runtime log level toggling and send the signal twice, the original log level is restored" do
161
+ Given {
162
+ @app = MyAppThatActsLikeItUsesMain.new
163
+ @app.call_use_log_level_option( :toggle_debug_on_signal => 'USR2' )
164
+ @level = Logger::INFO
165
+ @app.use_option(@level)
166
+ }
167
+ When {
168
+ send_signal_and_wait_a_moment('USR2')
169
+ send_signal_and_wait_a_moment('USR2')
170
+ }
171
+ Then {
172
+ @app.logger.level.should be == Logger::INFO
173
+ }
174
+ end
175
+
176
+
177
+ class MyAppThatActsLikeItUsesMain
178
+ include OptparsePlus::CLILogging
179
+
180
+ def call_use_log_level_option(args = {})
181
+ use_log_level_option(args)
182
+ end
183
+
184
+ def use_option(level)
185
+ @block.call(level)
186
+ end
187
+
188
+ def on(*args,&block)
189
+ @block = block
190
+ end
191
+
192
+ def logger
193
+ @logger ||= OpenStruct.new
194
+ end
195
+ end
196
+
197
+ class MyClassThatLogsToStdout
198
+ include OptparsePlus::CLILogging
199
+
200
+ def initialize
201
+ logger.formatter = proc do |severity,datetime,progname,msg|
202
+ msg + "\n"
203
+ end
204
+ logger.level = Logger::DEBUG
205
+ end
206
+
207
+ def doit
208
+ debug("debug")
209
+ info("info")
210
+ warn("warn")
211
+ error("error")
212
+ fatal("fatal")
213
+ end
214
+
215
+ def logger_id; logger.object_id; end
216
+ end
217
+
218
+
219
+ class MyOtherClassThatLogsToStdout
220
+ include OptparsePlus::CLILogging
221
+
222
+ def initialize
223
+ logger.formatter = proc do |severity,datetime,progname,msg|
224
+ msg + "\n"
225
+ end
226
+ end
227
+
228
+ def doit
229
+ debug("debug")
230
+ info("info")
231
+ warn("warn")
232
+ error("error")
233
+ fatal("fatal")
234
+ end
235
+
236
+ def logger_id; logger.object_id; end
237
+ end
238
+
239
+ def send_signal_and_wait_a_moment(signal)
240
+ Process.kill(signal, $$)
241
+ sleep(SLEEP_TIME) # call sleep to give the trap handler a chance to kick in
242
+ end
243
+ end
@@ -0,0 +1,37 @@
1
+ require 'base_test'
2
+ require 'optparse_plus'
3
+ require 'stringio'
4
+
5
+ class TestExitNow < BaseTest
6
+ include OptparsePlus
7
+ include OptparsePlus::ExitNow
8
+
9
+ test_that "exit_now raises the proper error" do
10
+ Given {
11
+ @exit_code = any_int :min => 1
12
+ @message = any_string
13
+ }
14
+ When {
15
+ @code = lambda { exit_now!(@exit_code,@message) }
16
+ }
17
+ Then {
18
+ exception = assert_raises(OptparsePlus::Error,&@code)
19
+ exception.exit_code.should be == @exit_code
20
+ exception.message.should be == @message
21
+ }
22
+ end
23
+
24
+ test_that "exit_now without an exit code uses 1 as the exti code" do
25
+ Given {
26
+ @message = any_string
27
+ }
28
+ When {
29
+ @code = lambda { exit_now!(@message) }
30
+ }
31
+ Then {
32
+ exception = assert_raises(OptparsePlus::Error,&@code)
33
+ exception.exit_code.should be == 1
34
+ exception.message.should be == @message
35
+ }
36
+ end
37
+ end
@@ -0,0 +1,840 @@
1
+ require 'base_test'
2
+ require 'optparse_plus'
3
+ require 'stringio'
4
+ require 'fileutils'
5
+
6
+ class TestMain < BaseTest
7
+ include OptparsePlus::Main
8
+
9
+ def setup
10
+ @original_argv = ARGV.clone
11
+ ARGV.clear
12
+ @old_stdout = $stdout
13
+ $stdout = StringIO.new
14
+ @logged = StringIO.new
15
+ @custom_logger = Logger.new(@logged)
16
+
17
+ @original_home = ENV['HOME']
18
+ fake_home = '/tmp/fake-home'
19
+ FileUtils.rm_rf(fake_home)
20
+ FileUtils.mkdir(fake_home)
21
+ ENV['HOME'] = fake_home
22
+ end
23
+
24
+ # Override the built-in logger so we can capture it
25
+ def logger
26
+ @custom_logger
27
+ end
28
+
29
+ def teardown
30
+ set_argv @original_argv
31
+ ENV.delete('DEBUG')
32
+ ENV.delete('APP_OPTS')
33
+ $stdout = @old_stdout
34
+ ENV['HOME'] = @original_home
35
+ end
36
+
37
+ test_that "my main block gets called by run and has access to CLILogging" do
38
+ Given {
39
+ @called = false
40
+ main do
41
+ begin
42
+ logger.debug "debug"
43
+ logger.info "info"
44
+ logger.warn "warn"
45
+ logger.error "error"
46
+ logger.fatal "fatal"
47
+ @called = true
48
+ rescue => ex
49
+ puts ex.message
50
+ end
51
+ end
52
+ }
53
+ When run_go_safely
54
+ Then main_shouldve_been_called
55
+ end
56
+
57
+ test_that "my main block gets the command-line parameters" do
58
+ Given {
59
+ @params = []
60
+ main do |param1,param2,param3|
61
+ @params << param1
62
+ @params << param2
63
+ @params << param3
64
+ end
65
+ set_argv %w(one two three)
66
+ }
67
+ When run_go_safely
68
+ Then {
69
+ @params.should be == %w(one two three)
70
+ }
71
+ end
72
+
73
+ test_that "my main block can freely ignore arguments given" do
74
+ Given {
75
+ @called = false
76
+ main do
77
+ @called = true
78
+ end
79
+ set_argv %w(one two three)
80
+ }
81
+ When run_go_safely
82
+ Then main_shouldve_been_called
83
+ end
84
+
85
+ test_that "my main block can ask for arguments that it might not receive" do
86
+ Given {
87
+ @params = []
88
+ main do |param1,param2,param3|
89
+ @params << param1
90
+ @params << param2
91
+ @params << param3
92
+ end
93
+ set_argv %w(one two)
94
+ }
95
+ When run_go_safely
96
+ Then {
97
+ @params.should be == ['one','two',nil]
98
+ }
99
+ end
100
+
101
+ test_that "go exits zero when main evaluates to nil or some other non number" do
102
+ [nil,'some string',Object.new,[],4.5].each do |non_number|
103
+ Given main_that_exits non_number
104
+ Then {
105
+ assert_exits(0,"for value #{non_number}") { When run_go! }
106
+ }
107
+ end
108
+ end
109
+
110
+ test_that "go exits with the numeric value that main evaluated to" do
111
+ [0,1,2,3].each do |exit_status|
112
+ Given main_that_exits exit_status
113
+ Then {
114
+ assert_exits(exit_status) { When run_go! }
115
+ }
116
+ end
117
+ end
118
+
119
+ test_that "go exits with 70, which is the Linux sysexits.h code for this sort of thing, if there's an exception" do
120
+ Given {
121
+ main do
122
+ raise "oh noes"
123
+ end
124
+ }
125
+ Then {
126
+ assert_exits(70) { When run_go! }
127
+ assert_logged_at_error "oh noes"
128
+ }
129
+ end
130
+
131
+ test_that "go allows the exception raised to leak through if DEBUG is set in the environment" do
132
+ Given {
133
+ ENV['DEBUG'] = 'true'
134
+ main do
135
+ raise ArgumentError,"oh noes"
136
+ end
137
+ }
138
+ Then {
139
+ assert_raises ArgumentError do
140
+ When run_go!
141
+ end
142
+ }
143
+ end
144
+
145
+ test_that "An exception that's not a StandardError causes the excepteion to break through and raise" do
146
+ Given {
147
+ main do
148
+ raise Exception,"oh noes"
149
+ end
150
+ }
151
+ Then {
152
+ ex = assert_raises Exception do
153
+ When run_go!
154
+ end
155
+ assert_equal "oh noes",ex.message
156
+ }
157
+ end
158
+
159
+ test_that "Non-optparse_plus exceptions leak through if we configure it that way" do
160
+ Given {
161
+ main do
162
+ raise StandardError,"oh noes"
163
+ end
164
+ leak_exceptions true
165
+ }
166
+ Then {
167
+ ex = assert_raises StandardError do
168
+ When run_go!
169
+ end
170
+ assert_equal "oh noes",ex.message
171
+ }
172
+ end
173
+
174
+ test_that "go exits with the exit status included in the special-purpose excepiton" do
175
+ Given {
176
+ main do
177
+ raise OptparsePlus::Error.new(4,"oh noes")
178
+ end
179
+ }
180
+ Then {
181
+ assert_exits(4) { When run_go! }
182
+ assert_logged_at_error "oh noes"
183
+ }
184
+ end
185
+
186
+ test_that "go allows the special optparse_plus exception to leak through if DEBUG is set in the environment" do
187
+ Given {
188
+ ENV['DEBUG'] = 'true'
189
+ main do
190
+ raise OptparsePlus::Error.new(4,"oh noes")
191
+ end
192
+ }
193
+ Then {
194
+ assert_raises OptparsePlus::Error do
195
+ When run_go!
196
+ end
197
+ }
198
+ end
199
+
200
+ test_that "can exit with a specific status by using the helper method instead of making a new exception" do
201
+ Given {
202
+ main do
203
+ exit_now!(4,"oh noes")
204
+ end
205
+ }
206
+ Then {
207
+ assert_exits(4) { When run_go! }
208
+ assert_logged_at_error "oh noes"
209
+ }
210
+ end
211
+
212
+ test_that "when we help_now! we exit and show help" do
213
+ Given {
214
+ @message = any_sentence
215
+ main do
216
+ help_now!(@message)
217
+ end
218
+
219
+ opts.on("--switch") { options[:switch] = true }
220
+ opts.on("--flag FLAG") { |value| options[:flag] = value }
221
+
222
+ set_argv []
223
+ }
224
+
225
+ Then {
226
+ assert_exits(64) { When run_go! }
227
+ assert $stdout.string.include?(opts.to_s),"Expected #{$stdout.string} to contain #{opts.to_s}"
228
+ assert_logged_at_error @message
229
+ }
230
+ end
231
+
232
+ test_that "opts allows us to more expediently set up OptionParser" do
233
+ Given {
234
+ @switch = nil
235
+ @flag = nil
236
+ main do
237
+ @switch = options[:switch]
238
+ @flag = options[:flag]
239
+ end
240
+
241
+ opts.on("--switch") { options[:switch] = true }
242
+ opts.on("--flag FLAG") { |value| options[:flag] = value }
243
+
244
+ set_argv %w(--switch --flag value)
245
+ }
246
+
247
+ When run_go_safely
248
+
249
+ Then {
250
+ @switch.should be true
251
+ @flag.should be == 'value'
252
+ }
253
+ end
254
+
255
+ test_that "when the command line is invalid, we exit with 64 and print the CLI help" do
256
+ Given {
257
+ main do
258
+ end
259
+
260
+ opts.on("--switch") { options[:switch] = true }
261
+ opts.on("--flag FLAG") { |value| options[:flag] = value }
262
+
263
+ set_argv %w(--invalid --flag value)
264
+ }
265
+
266
+ Then {
267
+ assert_exits(64) { When run_go! }
268
+ assert $stdout.string.include?(opts.to_s),"Expected #{$stdout.string} to contain #{opts.to_s}"
269
+ }
270
+ end
271
+
272
+ test_that "when setting defualts they get copied to strings/symbols as well" do
273
+ Given {
274
+ @flag_with_string_key_defalt = nil
275
+ @flag_with_symbol_key_defalt = nil
276
+ reset!
277
+ main do
278
+ @flag_with_string_key_defalt = options[:foo]
279
+ @flag_with_symbol_key_defalt = options['bar']
280
+ end
281
+ options['foo'] = 'FOO'
282
+ options[:bar] = 'BAR'
283
+ on("--foo")
284
+ on("--bar")
285
+ }
286
+ When run_go_safely
287
+ Then {
288
+ assert_equal 'FOO',@flag_with_string_key_defalt
289
+ assert_equal 'BAR',@flag_with_symbol_key_defalt
290
+ }
291
+ end
292
+
293
+ test_that "passing non-strings wont' break automagic stuff" do
294
+ Given {
295
+ @foo = nil
296
+ @bar = nil
297
+ main do
298
+ @foo = options[:foo]
299
+ @bar = options[:bar]
300
+ end
301
+ on("--foo ARG",OptionParser::DecimalInteger)
302
+ on("--bar ARG",/^\d/)
303
+ set_argv %w(--foo 88 --bar 4)
304
+ }
305
+ When run_go_safely
306
+ Then {
307
+ assert_equal 88,@foo,@logged.string + $stdout.string
308
+ assert_equal '4',@bar,@logged.string + $stdout.string
309
+ }
310
+ end
311
+
312
+ test_that "omitting the block to opts simply sets the value in the options hash and returns itself" do
313
+ Given {
314
+ @switch = nil
315
+ @negatable = nil
316
+ @flag = nil
317
+ @f = nil
318
+ @other = nil
319
+ @some_other = nil
320
+ @with_dashes = nil
321
+ main do
322
+ @switch = [options[:switch],options['switch']]
323
+ @flag = [options[:flag],options['flag']]
324
+ @f = [options[:f],options['f']]
325
+ @negatable = [options[:negatable],options['negatable']]
326
+ @other = [options[:other],options['other']]
327
+ @some_other = [options[:some_other],options['some_other']]
328
+ @with_dashes = [options[:'flag-with-dashes'],options['flag-with-dashes']]
329
+ end
330
+
331
+ on("--switch")
332
+ on("--[no-]negatable")
333
+ on("--flag FLAG","-f","Some documentation string")
334
+ on("--flag-with-dashes FOO")
335
+ on("--other") do
336
+ options[:some_other] = true
337
+ end
338
+
339
+ set_argv %w(--switch --flag value --negatable --other --flag-with-dashes=BAR)
340
+ }
341
+
342
+ When run_go_safely
343
+
344
+ Then {
345
+ @switch[0].should be true
346
+ @some_other[0].should be true
347
+ @other[0].should_not be true
348
+ @flag[0].should be == 'value'
349
+ @f[0].should be == 'value'
350
+ @with_dashes[0].should be == 'BAR'
351
+
352
+ @switch[1].should be true
353
+ @some_other[1].should be nil # ** this is set manually
354
+ @other[1].should_not be true
355
+ @flag[1].should be == 'value'
356
+ @f[1].should be == 'value'
357
+ @with_dashes[1].should be == 'BAR'
358
+
359
+ opts.to_s.should match(/Some documentation string/)
360
+ }
361
+ end
362
+
363
+ test_that "without specifying options, options in brackets doesn't show up in our banner" do
364
+ Given {
365
+ main {}
366
+ }
367
+
368
+ Then {
369
+ opts.banner.should_not match(/\[options\]/)
370
+ }
371
+ end
372
+
373
+ test_that "when specifying an option, [options] shows up in the banner" do
374
+ Given {
375
+ main {}
376
+ on("-s")
377
+ }
378
+
379
+ Then {
380
+ opts.banner.should match(/\[options\]/)
381
+ }
382
+
383
+ end
384
+
385
+ test_that "I can specify which arguments my app takes and if they are required as well as document them" do
386
+ Given {
387
+ main {}
388
+ @db_name_desc = any_string
389
+ @user_desc = any_string
390
+ @password_desc = any_string
391
+
392
+ arg :db_name, @db_name_desc
393
+ arg :user, :required, @user_desc
394
+ arg :password, :optional, @password_desc
395
+ }
396
+ When run_go_safely
397
+ Then {
398
+ opts.banner.should match(/db_name user \[password\]$/)
399
+ opts.to_s.should match(/#{@db_name_desc}/)
400
+ opts.to_s.should match(/#{@user_desc}/)
401
+ opts.to_s.should match(/#{@password_desc}/)
402
+ }
403
+ end
404
+
405
+ test_that "I can specify which arguments my app takes and if they are singular or plural" do
406
+ Given {
407
+ main {}
408
+
409
+ arg :db_name
410
+ arg :user, :required, :one
411
+ arg :tables, :many
412
+ }
413
+
414
+ Then {
415
+ opts.banner.should match(/db_name user tables...$/)
416
+ }
417
+ end
418
+
419
+ test_that "I can specify which arguments my app takes and if they are singular or optional plural" do
420
+ Given {
421
+ main {}
422
+
423
+ arg :db_name
424
+ arg :user, :required, :one
425
+ arg :tables, :any
426
+ }
427
+
428
+ Then {
429
+ opts.banner.should match(/db_name user \[tables...\]$/)
430
+ }
431
+ end
432
+
433
+ test_that "I can set a description for my app" do
434
+ Given {
435
+ main {}
436
+ description "An app of total awesome"
437
+
438
+ }
439
+ Then {
440
+ opts.banner.should match(/^An app of total awesome$/)
441
+ }
442
+ end
443
+
444
+ test_that "when I override the banner, we don't automatically do anything" do
445
+ Given {
446
+ main {}
447
+ opts.banner = "FOOBAR"
448
+
449
+ on("-s")
450
+ }
451
+
452
+ Then {
453
+ opts.banner.should be == 'FOOBAR'
454
+ }
455
+ end
456
+
457
+ test_that "when I say an argument is required and its omitted, I get an error" do
458
+ Given {
459
+ main {}
460
+ arg :foo
461
+ arg :bar
462
+
463
+ set_argv %w(blah)
464
+ }
465
+
466
+ Then {
467
+ assert_exits(64) { When run_go! }
468
+ assert_logged_at_error("parse error: 'bar' is required")
469
+ }
470
+ end
471
+
472
+ test_that "when I say an argument is many and its omitted, I get an error" do
473
+ Given {
474
+ main {}
475
+ arg :foo
476
+ arg :bar, :many
477
+
478
+ set_argv %w(blah)
479
+ }
480
+
481
+ Then {
482
+ assert_exits(64) { When run_go! }
483
+ assert_logged_at_error("parse error: at least one 'bar' is required")
484
+ }
485
+ end
486
+
487
+ test_that "when I specify a version, it shows up in the banner" do
488
+ Given {
489
+ main{}
490
+ version "0.0.1"
491
+ }
492
+
493
+ Then {
494
+ opts.banner.should match(/^v0.0.1/m)
495
+ }
496
+ end
497
+
498
+ test_that "when I specify a version, I can get help via --version" do
499
+ Given {
500
+ main{}
501
+ version "0.0.1"
502
+ set_argv(['--verison'])
503
+ }
504
+ Then run_go_safely
505
+ And {
506
+ opts.to_s.should match(/Show help\/version info/m)
507
+ }
508
+ end
509
+
510
+ test_that "when I specify a version with custom help, it shows up" do
511
+ @version_message = "SHOW ME VERSIONS"
512
+ Given {
513
+ main{}
514
+ version "0.0.1",@version_message
515
+ set_argv(['--verison'])
516
+ }
517
+ Then run_go_safely
518
+ And {
519
+ opts.to_s.should match(/#{@version_message}/)
520
+ }
521
+ end
522
+
523
+ test_that "default values for options are put into the docstring" do
524
+ Given {
525
+ main {}
526
+ options[:foo] = "bar"
527
+ on("--foo ARG","Indicate the type of foo")
528
+ }
529
+ When {
530
+ @help_string = opts.to_s
531
+ }
532
+ When {
533
+ @help_string.should match(/\(default: bar\)/)
534
+ }
535
+
536
+ end
537
+
538
+ test_that "default values for options with several names are put into the docstring" do
539
+ Given {
540
+ main {}
541
+ options[:foo] = "bar"
542
+ on("-f ARG","--foo","Indicate the type of foo")
543
+ }
544
+ When {
545
+ @help_string = opts.to_s
546
+ }
547
+ When {
548
+ @help_string.should match(/\(default: bar\)/)
549
+ }
550
+ end
551
+
552
+ test_that "when getting defaults from an environment variable, show it in the help output" do
553
+ Given app_to_use_environment
554
+ When run_go_safely
555
+ And {
556
+ @help_string = opts.to_s
557
+ }
558
+ Then {
559
+ @help_string.should match(/Default values can be placed in the APP_OPTS environment variable/)
560
+ }
561
+ end
562
+
563
+ test_that "when we want to get opts from the environment, we can" do
564
+ Given app_to_use_environment
565
+ And {
566
+ @flag_value = '56'
567
+ @some_arg = any_string
568
+ set_argv([])
569
+ ENV['APP_OPTS'] = "--switch --flag=#{@flag_value} #{@some_arg}"
570
+ }
571
+ When {
572
+ @code = lambda { go! }
573
+ }
574
+ Then {
575
+ assert_exits(0,'',&@code)
576
+ @switch.should be == true
577
+ @flag.should be == @flag_value
578
+ @args.should be == [@some_arg]
579
+ }
580
+ end
581
+
582
+ test_that "environment args are overridden by the command line" do
583
+ Given app_to_use_environment
584
+ And {
585
+ @flag_value = any_string
586
+ ENV['APP_OPTS'] = "--switch --flag=#{any_string}"
587
+ set_argv(['--flag',@flag_value])
588
+ }
589
+ When {
590
+ @code = lambda { go! }
591
+ }
592
+ Then {
593
+ assert_exits(0,'',&@code)
594
+ @switch.should be == true
595
+ @flag.should be == @flag_value
596
+ }
597
+ end
598
+
599
+ test_that "environment args correctly handle spaces" do
600
+ Given app_to_use_environment
601
+ And {
602
+ @flag_value = any_string + ' ' + any_string
603
+ ENV['APP_OPTS'] = "--switch --flag='#{@flag_value}'"
604
+ }
605
+ When {
606
+ @code = lambda { go! }
607
+ }
608
+ Then {
609
+ assert_exits(0,'',&@code)
610
+ @switch.should be == true
611
+ @flag.should be == @flag_value
612
+ }
613
+ end
614
+
615
+ test_that "environment args correctly handle spaces via backslash stuff" do
616
+ Given app_to_use_environment
617
+ And {
618
+ cli_flag_value = any_string(:max => 4) + "\\ " + any_string(:max => 4)
619
+ @flag_value = cli_flag_value.gsub("\\ "," ")
620
+ ENV['APP_OPTS'] = "--switch --flag=#{cli_flag_value}"
621
+ }
622
+ When {
623
+ @code = lambda { go! }
624
+ }
625
+ Then {
626
+ assert_exits(0,'',&@code)
627
+ @switch.should be == true
628
+ @flag.should be == @flag_value
629
+ }
630
+ end
631
+
632
+ test_that "we can get defaults from a YAML config file if it's specified" do
633
+ Given app_to_use_rc_file
634
+ And {
635
+ @flag_value = any_string
636
+ rc_file = File.join(ENV['HOME'],'.my_app.rc')
637
+ File.open(rc_file,'w') do |file|
638
+ file.puts({
639
+ 'switch' => true,
640
+ 'flag' => @flag_value,
641
+ }.to_yaml)
642
+ end
643
+ }
644
+ When {
645
+ @code = lambda { go! }
646
+ }
647
+ Then {
648
+ assert_exits(0,&@code)
649
+ @switch.should be == true
650
+ @flag.should be == @flag_value
651
+ }
652
+
653
+ end
654
+
655
+ test_that "we can get defaults from an absolute config filename" do
656
+ tempfile = Tempfile.new('optparse_plus_test.rc')
657
+ Given app_to_use_rc_file(tempfile.path)
658
+ And {
659
+ @flag_value = any_string
660
+ rc_file = File.join(tempfile.path)
661
+ File.open(rc_file,'w') do |file|
662
+ file.puts({
663
+ 'switch' => true,
664
+ 'flag' => @flag_value,
665
+ }.to_yaml)
666
+ end
667
+ }
668
+ When {
669
+ @code = lambda { go! }
670
+ }
671
+ Then {
672
+ assert_exits(0,&@code)
673
+ @switch.should be == true
674
+ @flag.should be == @flag_value
675
+ }
676
+
677
+ end
678
+
679
+
680
+ test_that "we can specify an rc file even if it doesn't exist" do
681
+ Given app_to_use_rc_file
682
+ And {
683
+ @flag_value = any_string
684
+ rc_file = File.join(ENV['HOME'],'.my_app.rc')
685
+ raise "Something's wrong, expection rc file not to exist" if File.exist?(rc_file)
686
+ }
687
+ When {
688
+ @code = lambda { go! }
689
+ }
690
+ Then {
691
+ assert_exits(0,&@code)
692
+ @switch.should be == nil
693
+ @flag.should be == nil
694
+ }
695
+ end
696
+
697
+ test_that "we can use a simpler, text format for the rc file" do
698
+ Given app_to_use_rc_file
699
+ And {
700
+ @flag_value = any_string
701
+ rc_file = File.join(ENV['HOME'],'.my_app.rc')
702
+ File.open(rc_file,'w') do |file|
703
+ file.puts "--switch --flag=#{@flag_value}"
704
+ end
705
+ }
706
+ When {
707
+ @code = lambda { go! }
708
+ }
709
+ Then {
710
+ assert_exits(0,&@code)
711
+ @switch.should be == true
712
+ @flag.should be == @flag_value
713
+ }
714
+
715
+ end
716
+
717
+ test_that "the text format for the rc file attempts to respect quoted arguments" do
718
+ Given app_to_use_rc_file
719
+ And {
720
+ @flag_value = any_string(:max => 10) + " " + any_string(:max => 10)
721
+ rc_file = File.join(ENV['HOME'],'.my_app.rc')
722
+ File.open(rc_file,'w') do |file|
723
+ file.puts "--switch --flag='#{@flag_value}'"
724
+ end
725
+ }
726
+ When {
727
+ @code = lambda { go! }
728
+ }
729
+ Then {
730
+ assert_exits(0,&@code)
731
+ @switch.should be == true
732
+ @flag.should be == @flag_value
733
+ }
734
+ end
735
+
736
+ test_that "with an ill-formed rc file, we get a reasonable error message" do
737
+ Given app_to_use_rc_file
738
+ And {
739
+ @flag_value = any_string
740
+ rc_file = File.join(ENV['HOME'],'.my_app.rc')
741
+ File.open(rc_file,'w') do |file|
742
+ file.puts OpenStruct.new(:foo => :bar).to_yaml
743
+ end
744
+ }
745
+ When {
746
+ @code = lambda { go! }
747
+ }
748
+ Then {
749
+ assert_exits(64,&@code)
750
+ }
751
+
752
+ end
753
+
754
+ test_that "we can call methods prior to calling main without a problem" do
755
+ Given {
756
+ description "An app of total awesome"
757
+ opts.on("--switch") { options[:switch] = true }
758
+ main {}
759
+ }
760
+ Then {
761
+ opts.banner.should match(/^An app of total awesome$/)
762
+ opts.to_s.should match(/--switch/)
763
+ }
764
+ end
765
+
766
+ private
767
+
768
+ def app_to_use_rc_file(rc_file = '.my_app.rc')
769
+ lambda {
770
+ reset!
771
+ @switch = nil
772
+ @flag = nil
773
+ @args = nil
774
+ main do |*args|
775
+ @switch = options[:switch]
776
+ @flag = options[:flag]
777
+ @args = args
778
+ end
779
+
780
+ defaults_from_config_file rc_file
781
+
782
+ on('--switch','Some Switch')
783
+ on('--flag FOO','Some Flag')
784
+ }
785
+ end
786
+
787
+ def main_that_exits(exit_status)
788
+ proc { main { exit_status } }
789
+ end
790
+
791
+ def app_to_use_environment
792
+ lambda {
793
+ @switch = nil
794
+ @flag = nil
795
+ @args = nil
796
+ main do |*args|
797
+ @switch = options[:switch]
798
+ @flag = options[:flag]
799
+ @args = args
800
+ end
801
+
802
+ defaults_from_env_var 'APP_OPTS'
803
+
804
+ on('--switch','Some Switch')
805
+ on('--flag FOO','Some Flag')
806
+ }
807
+ end
808
+
809
+ def main_shouldve_been_called
810
+ Proc.new { assert @called,"Main block wasn't called?!" }
811
+ end
812
+
813
+ def run_go_safely
814
+ Proc.new { safe_go! }
815
+ end
816
+
817
+ # Calls go!, but traps the exit
818
+ def safe_go!
819
+ go!
820
+ rescue SystemExit
821
+ end
822
+
823
+ def run_go!; proc { go! }; end
824
+
825
+ def assert_logged_at_error(expected_message)
826
+ @logged.string.should include expected_message
827
+ end
828
+
829
+ def assert_exits(exit_code,message='',&block)
830
+ block.call
831
+ fail "Expected an exit of #{exit_code}, but we didn't even exit!"
832
+ rescue SystemExit => ex
833
+ assert_equal exit_code,ex.status,@logged.string
834
+ end
835
+
836
+ def set_argv(args)
837
+ ARGV.clear
838
+ args.each { |arg| ARGV << arg }
839
+ end
840
+ end