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