optparse-plus 3.0.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 (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