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,603 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
begin
|
5
|
+
Module.const_get('BasicObject')
|
6
|
+
# We are 1.9.x
|
7
|
+
rescue NameError
|
8
|
+
BasicObject = Object
|
9
|
+
end
|
10
|
+
|
11
|
+
module OptparsePlus
|
12
|
+
# Include this module to gain access to the "canonical command-line app structure"
|
13
|
+
# DSL. This is a *very* lightweight layer on top of what you might
|
14
|
+
# normally write that gives you just a bit of help to keep your code structured
|
15
|
+
# in a sensible way. You can use as much or as little as you want, though
|
16
|
+
# you must at least use #main to get any benefits.
|
17
|
+
#
|
18
|
+
# Further, you must provide access to a logger via a method named
|
19
|
+
# #logger. If you include OptparsePlus::CLILogging, this will be done for you
|
20
|
+
#
|
21
|
+
# You also get a more expedient interface to OptionParser as well
|
22
|
+
# as checking for required arguments to your app. For example, if
|
23
|
+
# we want our app to accept a negatable switch named "switch", a flag
|
24
|
+
# named "flag", and two arguments "needed" (which is required)
|
25
|
+
# and "maybe" which is optional, we can do the following:
|
26
|
+
#
|
27
|
+
# #!/usr/bin/env ruby
|
28
|
+
#
|
29
|
+
# require 'optparse_plus'
|
30
|
+
#
|
31
|
+
# class App
|
32
|
+
# include OptparsePlus::Main
|
33
|
+
# include OptparsePlus::CLILogging
|
34
|
+
#
|
35
|
+
# main do |needed, maybe|
|
36
|
+
# options[:switch] => true or false, based on command line
|
37
|
+
# options[:flag] => value of flag passed on command line
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Proxy to an OptionParser instance's on method
|
41
|
+
# on("--[no]-switch")
|
42
|
+
# on("--flag VALUE")
|
43
|
+
#
|
44
|
+
# arg :needed
|
45
|
+
# arg :maybe, :optional
|
46
|
+
#
|
47
|
+
# defaults_from_env_var SOME_VAR
|
48
|
+
# defaults_from_config_file '.my_app.rc'
|
49
|
+
#
|
50
|
+
# go!
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# Our app then acts as follows:
|
54
|
+
#
|
55
|
+
# $ our_app
|
56
|
+
# # => parse error: 'needed' is required
|
57
|
+
# $ our_app foo
|
58
|
+
# # => succeeds; "maybe" in main is nil
|
59
|
+
# $ our_app --flag foo
|
60
|
+
# # => options[:flag] has the value "foo"
|
61
|
+
# $ SOME_VAR='--flag foo' our_app
|
62
|
+
# # => options[:flag] has the value "foo"
|
63
|
+
# $ SOME_VAR='--flag foo' our_app --flag bar
|
64
|
+
# # => options[:flag] has the value "bar"
|
65
|
+
#
|
66
|
+
# Note that we've done all of this inside a class that we called +App+. This isn't strictly
|
67
|
+
# necessary, and you can just +include+ OptparsePlus::Main and OptparsePlus::CLILogging at the root
|
68
|
+
# of your +bin+ file if you like. This is somewhat unsafe, because +self+ inside the +bin+
|
69
|
+
# file is Object, and any methods you create (or cause to be created via +include+) will be
|
70
|
+
# present on *every* object. This can cause odd problems, so it's recommended that you
|
71
|
+
# *not* do this.
|
72
|
+
#
|
73
|
+
module Main
|
74
|
+
include OptparsePlus::ExitNow
|
75
|
+
include OptparsePlus::ARGVParser
|
76
|
+
|
77
|
+
def self.included(k)
|
78
|
+
k.extend(self)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Declare the main method for your app.
|
82
|
+
# This allows you to specify the general logic of your
|
83
|
+
# app at the top of your bin file, but can rely on any methods
|
84
|
+
# or other code that you define later.
|
85
|
+
#
|
86
|
+
# For example, suppose you want to process a set of files, but
|
87
|
+
# wish to determine that list from another method to keep your
|
88
|
+
# code clean.
|
89
|
+
#
|
90
|
+
# #!/usr/bin/env ruby -w
|
91
|
+
#
|
92
|
+
# require 'optparse_plus'
|
93
|
+
#
|
94
|
+
# include OptparsePlus::Main
|
95
|
+
#
|
96
|
+
# main do
|
97
|
+
# files_to_process.each do |file|
|
98
|
+
# # process file
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# def files_to_process
|
103
|
+
# # return list of files
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# go!
|
107
|
+
#
|
108
|
+
# The block can accept any parameters, and unparsed arguments
|
109
|
+
# from the command line will be passed.
|
110
|
+
#
|
111
|
+
# *Note*: #go! will modify +ARGV+ so any unparsed arguments that you do *not* declare as arguments
|
112
|
+
# to #main will essentially be unavailable. I consider this a bug, and it should be changed/fixed in
|
113
|
+
# a future version.
|
114
|
+
#
|
115
|
+
# To run this method, call #go!
|
116
|
+
def main(&block)
|
117
|
+
@main_block = block
|
118
|
+
end
|
119
|
+
|
120
|
+
# Configure the auto-handling of StandardError exceptions caught
|
121
|
+
# from calling go!.
|
122
|
+
#
|
123
|
+
# leak:: if true, go! will *not* catch StandardError exceptions, but instead
|
124
|
+
# allow them to bubble up. If false, they will be caught and handled as normal.
|
125
|
+
# This does *not* affect OptparsePlus::Error exceptions; those will NOT leak through.
|
126
|
+
def leak_exceptions(leak)
|
127
|
+
@leak_exceptions = leak
|
128
|
+
end
|
129
|
+
|
130
|
+
# Set the name of the environment variable where users can place default
|
131
|
+
# options for your app. Omit this to disable the feature.
|
132
|
+
def defaults_from_env_var(env_var)
|
133
|
+
@env_var = env_var
|
134
|
+
end
|
135
|
+
|
136
|
+
# Set the path to the file where defaults can be configured.
|
137
|
+
#
|
138
|
+
# The format of this file can be either a simple string of options, like what goes
|
139
|
+
# in the environment variable (see #defaults_from_env_var), or YAML, in which case
|
140
|
+
# it should be a hash where keys are the option names, and values their defaults.
|
141
|
+
#
|
142
|
+
# Relative paths will be expanded relative to the user's home directory.
|
143
|
+
#
|
144
|
+
# filename:: path to the file. If relative, will look in user's HOME directory.
|
145
|
+
# If absolute, this is the absolute path to where the file should be.
|
146
|
+
def defaults_from_config_file(filename,options={})
|
147
|
+
@rc_file = File.expand_path(filename, ENV['HOME'])
|
148
|
+
end
|
149
|
+
|
150
|
+
# Start your command-line app, exiting appropriately when
|
151
|
+
# complete.
|
152
|
+
#
|
153
|
+
# This *will* exit your program when it completes. If your
|
154
|
+
# #main block evaluates to an integer, that value will be sent
|
155
|
+
# to Kernel#exit, otherwise, this will exit with 0
|
156
|
+
#
|
157
|
+
# If the command-line options couldn't be parsed, this
|
158
|
+
# will exit with 64 and whatever message OptionParser provided.
|
159
|
+
#
|
160
|
+
# If a required argument (see #arg) is not found, this exits with
|
161
|
+
# 64 and a message about that missing argument.
|
162
|
+
def go!
|
163
|
+
setup_defaults
|
164
|
+
opts.post_setup
|
165
|
+
opts.parse!
|
166
|
+
opts.check_args!
|
167
|
+
result = call_main
|
168
|
+
if result.kind_of? Integer
|
169
|
+
exit result
|
170
|
+
else
|
171
|
+
exit 0
|
172
|
+
end
|
173
|
+
rescue OptionParser::ParseError => ex
|
174
|
+
logger.error ex.message
|
175
|
+
puts
|
176
|
+
puts opts.help
|
177
|
+
exit 64 # Linux standard for bad command line
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns an OptionParser that you can use
|
181
|
+
# to declare your command-line interface. Generally, you
|
182
|
+
# won't use this and will use #on directly, but this allows
|
183
|
+
# you to have complete control of option parsing.
|
184
|
+
#
|
185
|
+
# The object returned has
|
186
|
+
# an additional feature that implements typical use of OptionParser.
|
187
|
+
#
|
188
|
+
# opts.on("--flag VALUE")
|
189
|
+
#
|
190
|
+
# Does this under the covers:
|
191
|
+
#
|
192
|
+
# opts.on("--flag VALUE") do |value|
|
193
|
+
# options[:flag] = value
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# Since, most of the time, this is all you want to do,
|
197
|
+
# this makes it more expedient to do so. The key that is
|
198
|
+
# is set in #options will be a symbol <i>and string</i> of the option name, without
|
199
|
+
# the leading dashes. Note that if you use multiple option names, a key
|
200
|
+
# will be generated for each. Further, if you use the negatable form,
|
201
|
+
# only the positive key will be set, e.g. for <tt>--[no-]verbose</tt>,
|
202
|
+
# only <tt>:verbose</tt> will be set (to true or false).
|
203
|
+
#
|
204
|
+
# As an example, this declaration:
|
205
|
+
#
|
206
|
+
# opts.on("-f VALUE", "--flag")
|
207
|
+
#
|
208
|
+
# And this command-line invocation:
|
209
|
+
#
|
210
|
+
# $ my_app -f foo
|
211
|
+
#
|
212
|
+
# Will result in all of these forms returning the String "foo":
|
213
|
+
# * <tt>options['f']</tt>
|
214
|
+
# * <tt>options[:f]</tt>
|
215
|
+
# * <tt>options['flag']</tt>
|
216
|
+
# * <tt>options[:flag]</tt>
|
217
|
+
#
|
218
|
+
# Further, any one of those keys can be used to determine the default value for the option.
|
219
|
+
def opts
|
220
|
+
@option_parser ||= OptionParserProxy.new(OptionParser.new,options)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Calls the +on+ method of #opts with the given arguments (see RDoc for #opts for the additional
|
224
|
+
# help provided).
|
225
|
+
def on(*args,&block)
|
226
|
+
opts.on(*args,&block)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Sets the name of an arguments your app accepts. Note
|
230
|
+
# that no sanity checking is done on the configuration
|
231
|
+
# of your arguments you create via multiple calls to this method.
|
232
|
+
# Namely, the last argument should be the only one that is
|
233
|
+
# a :many or a :any, but the system here won't sanity check that.
|
234
|
+
#
|
235
|
+
# +arg_name+:: name of the argument to appear in documentation
|
236
|
+
# This will be converted into a String and used to create
|
237
|
+
# the banner (unless you have overridden the banner)
|
238
|
+
# +options+:: list (not Hash) of options:
|
239
|
+
# <tt>:required</tt>:: this arg is required (this is the default)
|
240
|
+
# <tt>:optional</tt>:: this arg is optional
|
241
|
+
# <tt>:one</tt>:: only one of this arg should be supplied (default)
|
242
|
+
# <tt>:many</tt>:: many of this arg may be supplied, but at least one is required
|
243
|
+
# <tt>:any</tt>:: any number, include zero, may be supplied
|
244
|
+
# A string:: if present, this will be documentation for the argument and appear in the help
|
245
|
+
def arg(arg_name,*options)
|
246
|
+
opts.arg(arg_name,*options)
|
247
|
+
end
|
248
|
+
|
249
|
+
# Set the description of your app for inclusion in the help output.
|
250
|
+
# +desc+:: a short, one-line description of your app
|
251
|
+
def description(desc)
|
252
|
+
opts.description(desc)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns a Hash that you can use to store or retrieve options
|
256
|
+
# parsed from the command line. When you put values in here, if you do so
|
257
|
+
# *before* you've declared your command-line interface via #on, the value
|
258
|
+
# will be used in the docstring to indicate it is the default.
|
259
|
+
# You can use either a String or a Symbol and, after #go! is called and
|
260
|
+
# the command-line is parsed, the values will be available as both
|
261
|
+
# a String and a Symbol.
|
262
|
+
#
|
263
|
+
# Example
|
264
|
+
#
|
265
|
+
# main do
|
266
|
+
# puts options[:foo] # put the value of --foo that the user provided
|
267
|
+
# end
|
268
|
+
#
|
269
|
+
# options[:foo] = "bar" # set "bar" as the default value for --foo, which
|
270
|
+
# # will cause us to include "(default: bar)" in the
|
271
|
+
# # docstring
|
272
|
+
#
|
273
|
+
# on("--foo FOO","Sets the foo")
|
274
|
+
# go!
|
275
|
+
#
|
276
|
+
def options
|
277
|
+
@options ||= {}
|
278
|
+
end
|
279
|
+
|
280
|
+
# Set the version of your app so it appears in the
|
281
|
+
# banner. This also adds --version as an option to your app which,
|
282
|
+
# when used, will act just like --help (see version_options to control this)
|
283
|
+
#
|
284
|
+
# version:: the current version of your app. Should almost always be
|
285
|
+
# YourApp::VERSION, where the module YourApp should've been generated
|
286
|
+
# by the bootstrap script
|
287
|
+
# version_options:: controls how the version option behaves. If this is a string,
|
288
|
+
# then the string will be used as documentation for the --version flag.
|
289
|
+
# If a Hash, more configuration is available:
|
290
|
+
# custom_docs:: the string to document the --version flag if you don't like the default
|
291
|
+
# compact:: if true, --version will just show the app name and version - no help
|
292
|
+
# format:: if provided, this can give limited control over the format of the compact
|
293
|
+
# version string. It should be a printf-style string and will be given
|
294
|
+
# two options: the first is the CLI app name, and the second is the version string
|
295
|
+
def version(version,version_options={})
|
296
|
+
opts.version(version)
|
297
|
+
if version_options.kind_of?(String)
|
298
|
+
version_options = { :custom_docs => version_options }
|
299
|
+
end
|
300
|
+
version_options[:custom_docs] ||= "Show help/version info"
|
301
|
+
version_options[:format] ||= "%s version %s"
|
302
|
+
opts.on("--version",version_options[:custom_docs]) do
|
303
|
+
if version_options[:compact]
|
304
|
+
puts version_options[:format] % [::File.basename($0),version]
|
305
|
+
else
|
306
|
+
puts opts.to_s
|
307
|
+
end
|
308
|
+
exit 0
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
private
|
313
|
+
|
314
|
+
# Reset internal state - mostly useful for tests
|
315
|
+
def reset!
|
316
|
+
@options = nil
|
317
|
+
@option_parser = nil
|
318
|
+
end
|
319
|
+
|
320
|
+
def setup_defaults
|
321
|
+
add_defaults_to_docs
|
322
|
+
set_defaults_from_rc_file
|
323
|
+
normalize_defaults
|
324
|
+
set_defaults_from_env_var
|
325
|
+
end
|
326
|
+
|
327
|
+
def add_defaults_to_docs
|
328
|
+
@env_var = nil unless defined? @env_var
|
329
|
+
@rc_file = nil unless defined? @rc_file
|
330
|
+
if @env_var && @rc_file
|
331
|
+
opts.separator ''
|
332
|
+
opts.separator 'Default values can be placed in:'
|
333
|
+
opts.separator ''
|
334
|
+
opts.separator " #{@env_var} environment variable, as a String of options"
|
335
|
+
opts.separator " #{@rc_file} with contents either a String of options "
|
336
|
+
spaces = (0..@rc_file.length).reduce('') { |a,_| a << ' ' }
|
337
|
+
opts.separator " #{spaces}or a YAML-encoded Hash"
|
338
|
+
elsif @env_var
|
339
|
+
opts.separator ''
|
340
|
+
opts.separator "Default values can be placed in the #{@env_var} environment variable"
|
341
|
+
elsif @rc_file
|
342
|
+
opts.separator ''
|
343
|
+
opts.separator "Default values can be placed in #{@rc_file}"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def set_defaults_from_env_var
|
348
|
+
if @env_var
|
349
|
+
parse_string_for_argv(ENV[@env_var]).each do |arg|
|
350
|
+
::ARGV.unshift(arg)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def set_defaults_from_rc_file
|
356
|
+
if @rc_file && File.exist?(@rc_file)
|
357
|
+
File.open(@rc_file) do |file|
|
358
|
+
parsed = begin
|
359
|
+
YAML::load(file)
|
360
|
+
rescue => ex
|
361
|
+
logger.error ex.message unless no_message? ex
|
362
|
+
nil
|
363
|
+
end
|
364
|
+
if parsed.kind_of? String
|
365
|
+
parse_string_for_argv(parsed).each do |arg|
|
366
|
+
::ARGV.unshift(arg)
|
367
|
+
end
|
368
|
+
elsif parsed.kind_of? Hash
|
369
|
+
parsed.each do |option,value|
|
370
|
+
options[option] = value
|
371
|
+
end
|
372
|
+
else
|
373
|
+
raise OptionParser::ParseError,
|
374
|
+
"rc file #{@rc_file} is not parseable, should be a string or YAML-encoded Hash"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
|
381
|
+
# Normalized all defaults to both string and symbol forms, so
|
382
|
+
# the user can access them via either means just as they would for
|
383
|
+
# non-defaulted options
|
384
|
+
def normalize_defaults
|
385
|
+
new_options = {}
|
386
|
+
options.each do |key,value|
|
387
|
+
unless value.nil?
|
388
|
+
new_options[key.to_s] = value
|
389
|
+
new_options[key.to_sym] = value
|
390
|
+
end
|
391
|
+
end
|
392
|
+
options.merge!(new_options)
|
393
|
+
end
|
394
|
+
|
395
|
+
# Handle calling main and trapping any exceptions thrown
|
396
|
+
def call_main
|
397
|
+
@leak_exceptions = nil unless defined? @leak_exceptions
|
398
|
+
@main_block.call(*ARGV)
|
399
|
+
rescue OptparsePlus::Error => ex
|
400
|
+
raise ex if ENV['DEBUG']
|
401
|
+
logger.error ex.message unless no_message? ex
|
402
|
+
ex.exit_code
|
403
|
+
rescue OptionParser::ParseError
|
404
|
+
raise
|
405
|
+
rescue => ex
|
406
|
+
raise ex if ENV['DEBUG']
|
407
|
+
raise ex if @leak_exceptions
|
408
|
+
logger.error ex.message unless no_message? ex
|
409
|
+
70 # Linux sysexit code for internal software error
|
410
|
+
end
|
411
|
+
|
412
|
+
def no_message?(exception)
|
413
|
+
exception.message.nil? || exception.message.strip.empty?
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# <b>OptparsePlus Internal - treat as private</b>
|
418
|
+
#
|
419
|
+
# A proxy to OptionParser that intercepts #on
|
420
|
+
# so that we can allow a simpler interface
|
421
|
+
class OptionParserProxy < BasicObject
|
422
|
+
# Create the proxy
|
423
|
+
#
|
424
|
+
# +option_parser+:: An OptionParser instance
|
425
|
+
# +options+:: a hash that will store the options
|
426
|
+
# set via automatic setting. The caller should
|
427
|
+
# retain a reference to this
|
428
|
+
def initialize(option_parser,options)
|
429
|
+
@option_parser = option_parser
|
430
|
+
@options = options
|
431
|
+
@user_specified_banner = false
|
432
|
+
@accept_options = false
|
433
|
+
@args = []
|
434
|
+
@arg_options = {}
|
435
|
+
@arg_documentation = {}
|
436
|
+
@description = nil
|
437
|
+
@version = nil
|
438
|
+
set_banner
|
439
|
+
document_help
|
440
|
+
end
|
441
|
+
|
442
|
+
def check_args!
|
443
|
+
::Hash[@args.zip(::ARGV)].each do |arg_name,arg_value|
|
444
|
+
if @arg_options[arg_name].include? :required
|
445
|
+
if arg_value.nil?
|
446
|
+
message = "'#{arg_name.to_s}' is required"
|
447
|
+
message = "at least one " + message if @arg_options[arg_name].include? :many
|
448
|
+
raise ::OptionParser::ParseError,message
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
# If invoked as with OptionParser, behaves the exact same way.
|
455
|
+
# If invoked without a block, however, the options hash given
|
456
|
+
# to the constructor will be used to store
|
457
|
+
# the parsed command-line value. See #opts in the Main module
|
458
|
+
# for how that works.
|
459
|
+
def on(*args,&block)
|
460
|
+
@accept_options = true
|
461
|
+
args = add_default_value_to_docstring(*args)
|
462
|
+
if block
|
463
|
+
@option_parser.on(*args,&block)
|
464
|
+
else
|
465
|
+
opt_names = option_names(*args)
|
466
|
+
@option_parser.on(*args) do |value|
|
467
|
+
opt_names.each do |name|
|
468
|
+
@options[name] = value
|
469
|
+
@options[name.to_s] = value
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
set_banner
|
474
|
+
end
|
475
|
+
|
476
|
+
# Proxies to underlying OptionParser
|
477
|
+
def banner=(new_banner)
|
478
|
+
@option_parser.banner=new_banner
|
479
|
+
@user_specified_banner = true
|
480
|
+
end
|
481
|
+
|
482
|
+
# Sets the banner to include these arg names
|
483
|
+
def arg(arg_name,*options)
|
484
|
+
options << :optional if options.include?(:any) && !options.include?(:optional)
|
485
|
+
options << :required unless options.include? :optional
|
486
|
+
options << :one unless options.include?(:any) || options.include?(:many)
|
487
|
+
@args << arg_name
|
488
|
+
@arg_options[arg_name] = options
|
489
|
+
options.select(&STRINGS_ONLY).each do |doc|
|
490
|
+
@arg_documentation[arg_name] = doc + (options.include?(:optional) ? " (optional)" : "")
|
491
|
+
end
|
492
|
+
set_banner
|
493
|
+
end
|
494
|
+
|
495
|
+
def description(desc)
|
496
|
+
@description = desc
|
497
|
+
set_banner
|
498
|
+
end
|
499
|
+
|
500
|
+
# Defers all calls save #on to
|
501
|
+
# the underlying OptionParser instance
|
502
|
+
def method_missing(sym,*args,&block)
|
503
|
+
@option_parser.send(sym,*args,&block)
|
504
|
+
end
|
505
|
+
|
506
|
+
# Since we extend Object on 1.8.x, to_s is defined and thus not proxied by method_missing
|
507
|
+
def to_s #::nodoc::
|
508
|
+
@option_parser.to_s
|
509
|
+
end
|
510
|
+
|
511
|
+
# Sets the version for the banner
|
512
|
+
def version(version)
|
513
|
+
@version = version
|
514
|
+
set_banner
|
515
|
+
end
|
516
|
+
|
517
|
+
# We need some documentation to appear at the end, after all OptionParser setup
|
518
|
+
# has occured, but before we actually start. This method serves that purpose
|
519
|
+
def post_setup
|
520
|
+
unless @arg_documentation.empty?
|
521
|
+
@option_parser.separator ''
|
522
|
+
@option_parser.separator "Arguments:"
|
523
|
+
@option_parser.separator ''
|
524
|
+
@args.each do |arg|
|
525
|
+
@option_parser.separator " #{arg}"
|
526
|
+
@option_parser.separator " #{@arg_documentation[arg]}"
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
private
|
532
|
+
|
533
|
+
def document_help
|
534
|
+
@option_parser.on("-h","--help","Show command line help") do
|
535
|
+
puts @option_parser.to_s
|
536
|
+
exit 0
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
def add_default_value_to_docstring(*args)
|
541
|
+
default_value = nil
|
542
|
+
option_names_from(args).each do |option|
|
543
|
+
default_value = (@options[option.to_s] || @options[option.to_sym]) if default_value.nil?
|
544
|
+
end
|
545
|
+
if default_value.nil?
|
546
|
+
args
|
547
|
+
else
|
548
|
+
args + ["(default: #{default_value})"]
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
def option_names_from(args)
|
553
|
+
args.select(&STRINGS_ONLY).select { |_|
|
554
|
+
_ =~ /^\-/
|
555
|
+
}.map { |_|
|
556
|
+
_.gsub(/^\-+/,'').gsub(/\s.*$/,'')
|
557
|
+
}
|
558
|
+
end
|
559
|
+
|
560
|
+
def set_banner
|
561
|
+
unless @user_specified_banner
|
562
|
+
new_banner="Usage: #{::File.basename($0)}"
|
563
|
+
new_banner += " [options]" if @accept_options
|
564
|
+
unless @args.empty?
|
565
|
+
new_banner += " "
|
566
|
+
new_banner += @args.map { |arg|
|
567
|
+
if @arg_options[arg].include? :any
|
568
|
+
"[#{arg.to_s}...]"
|
569
|
+
elsif @arg_options[arg].include? :optional
|
570
|
+
"[#{arg.to_s}]"
|
571
|
+
elsif @arg_options[arg].include? :many
|
572
|
+
"#{arg.to_s}..."
|
573
|
+
else
|
574
|
+
arg.to_s
|
575
|
+
end
|
576
|
+
}.join(' ')
|
577
|
+
end
|
578
|
+
new_banner += "\n\n#{@description}" if @description
|
579
|
+
new_banner += "\n\nv#{@version}" if @version
|
580
|
+
new_banner += "\n\nOptions:" if @accept_options
|
581
|
+
|
582
|
+
@option_parser.banner=new_banner
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
def option_names(*opts_on_args,&block)
|
587
|
+
opts_on_args.select(&STRINGS_ONLY).map { |arg|
|
588
|
+
if arg =~ /^--\[no-\]([^-\s][^\s]*)/
|
589
|
+
$1.to_sym
|
590
|
+
elsif arg =~ /^--([^-\s][^\s]*)/
|
591
|
+
$1.to_sym
|
592
|
+
elsif arg =~ /^-([^-\s][^\s]*)/
|
593
|
+
$1.to_sym
|
594
|
+
else
|
595
|
+
nil
|
596
|
+
end
|
597
|
+
}.reject(&:nil?)
|
598
|
+
end
|
599
|
+
|
600
|
+
STRINGS_ONLY = lambda { |o| o.kind_of?(::String) }
|
601
|
+
|
602
|
+
end
|
603
|
+
end
|