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