optparse-plus 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CHANGES.md +66 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +201 -0
- data/README.rdoc +173 -0
- data/Rakefile +94 -0
- data/bin/optparse_plus +130 -0
- data/fix.rb +29 -0
- data/lib/optparse-plus.rb +1 -0
- data/lib/optparse_plus.rb +15 -0
- data/lib/optparse_plus/argv_parser.rb +50 -0
- data/lib/optparse_plus/cli.rb +116 -0
- data/lib/optparse_plus/cli_logger.rb +133 -0
- data/lib/optparse_plus/cli_logging.rb +138 -0
- data/lib/optparse_plus/cucumber.rb +119 -0
- data/lib/optparse_plus/error.rb +32 -0
- data/lib/optparse_plus/execution_strategy/base.rb +34 -0
- data/lib/optparse_plus/execution_strategy/jvm.rb +37 -0
- data/lib/optparse_plus/execution_strategy/mri.rb +16 -0
- data/lib/optparse_plus/execution_strategy/open_3.rb +16 -0
- data/lib/optparse_plus/execution_strategy/open_4.rb +22 -0
- data/lib/optparse_plus/execution_strategy/rbx_open_4.rb +12 -0
- data/lib/optparse_plus/exit_now.rb +40 -0
- data/lib/optparse_plus/main.rb +603 -0
- data/lib/optparse_plus/process_status.rb +45 -0
- data/lib/optparse_plus/sh.rb +223 -0
- data/lib/optparse_plus/test/base_integration_test.rb +31 -0
- data/lib/optparse_plus/test/integration_test_assertions.rb +65 -0
- data/lib/optparse_plus/version.rb +3 -0
- data/optparse_plus.gemspec +28 -0
- data/templates/full/.gitignore.erb +4 -0
- data/templates/full/README.rdoc.erb +24 -0
- data/templates/full/Rakefile.erb +71 -0
- data/templates/full/_license_head.txt.erb +2 -0
- data/templates/full/apache_LICENSE.txt.erb +203 -0
- data/templates/full/bin/executable.erb +45 -0
- data/templates/full/custom_LICENSE.txt.erb +0 -0
- data/templates/full/gplv2_LICENSE.txt.erb +14 -0
- data/templates/full/gplv3_LICENSE.txt.erb +14 -0
- data/templates/full/mit_LICENSE.txt.erb +7 -0
- data/templates/rspec/spec/something_spec.rb.erb +5 -0
- data/templates/test_unit/test/integration/test_cli.rb.erb +11 -0
- data/templates/test_unit/test/unit/test_something.rb.erb +7 -0
- data/test/integration/base_integration_test.rb +60 -0
- data/test/integration/test_bootstrap.rb +150 -0
- data/test/integration/test_cli.rb +21 -0
- data/test/integration/test_license.rb +56 -0
- data/test/integration/test_readme.rb +53 -0
- data/test/integration/test_rspec.rb +28 -0
- data/test/integration/test_version.rb +21 -0
- data/test/unit/base_test.rb +19 -0
- data/test/unit/command_for_tests.sh +7 -0
- data/test/unit/execution_strategy/test_base.rb +24 -0
- data/test/unit/execution_strategy/test_jvm.rb +77 -0
- data/test/unit/execution_strategy/test_mri.rb +32 -0
- data/test/unit/execution_strategy/test_open_3.rb +70 -0
- data/test/unit/execution_strategy/test_open_4.rb +86 -0
- data/test/unit/execution_strategy/test_rbx_open_4.rb +25 -0
- data/test/unit/test/test_integration_test_assertions.rb +211 -0
- data/test/unit/test_cli_logger.rb +219 -0
- data/test/unit/test_cli_logging.rb +243 -0
- data/test/unit/test_exit_now.rb +37 -0
- data/test/unit/test_main.rb +840 -0
- data/test/unit/test_sh.rb +404 -0
- 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
|