methadone 0.2.0 → 0.3.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.
- data/README.rdoc +60 -1
- data/bin/methadone +7 -22
- data/features/bootstrap.feature +1 -4
- data/features/step_definitions/bootstrap_steps.rb +4 -0
- data/lib/methadone.rb +1 -0
- data/lib/methadone/cli.rb +1 -1
- data/lib/methadone/main.rb +232 -2
- data/lib/methadone/version.rb +1 -1
- data/templates/full/bin/executable.erb +21 -21
- data/test/test_main.rb +147 -0
- metadata +12 -12
data/README.rdoc
CHANGED
@@ -6,9 +6,12 @@ License:: Distributes under the Apache License, see LICENSE.txt in the source di
|
|
6
6
|
|
7
7
|
A smattering of tools to make your command-line apps easily awesome; kick the bash habit without sacrificing any of the power.
|
8
8
|
|
9
|
-
|
9
|
+
The overall goal of this project is to allow you to write a command-line app in Ruby that is as close as possible to the expedience and concisenes of bash, but with all the power of Ruby available when you need it.
|
10
|
+
|
11
|
+
Currently, this library is under development and has the following to offer:
|
10
12
|
|
11
13
|
* Bootstrapping a new CLI app
|
14
|
+
* Lightweight DSL to structure your bin file
|
12
15
|
* Utility Classes
|
13
16
|
* Methadone::CLILogger - a logger subclass that sends messages to standard error standard out as appropriate
|
14
17
|
* Methadone::CLILogging - a module that, when included in any class, provides easy access to a shared logger
|
@@ -48,6 +51,62 @@ The +methadone+ command-line app will bootstrap a new command-line app, setting
|
|
48
51
|
|
49
52
|
Basically, this sets you up with all the boilerplate that you *should* be using to write a command-line app.
|
50
53
|
|
54
|
+
== DSL for your `bin` file
|
55
|
+
|
56
|
+
A canonical `OptionParser` driven app has a few problems with it structurally that methadone can solve
|
57
|
+
|
58
|
+
* Backwards organization - main logic is at the bottom of the file, not the top
|
59
|
+
* Verbose to use +opts.on+ just to set a value in a +Hash+
|
60
|
+
* No exception handling
|
61
|
+
|
62
|
+
Methadone gives you a simple,lightweight DSL to help. It's important to note that we're taking a light touch here; this is all a thin wrapper around +OptionParser+ and you still have complete access to it if you'd like. We're basically wrapping up some canonical boilerplate into more expedient code
|
63
|
+
|
64
|
+
#!/usr/bin/env ruby
|
65
|
+
|
66
|
+
require 'optparse'
|
67
|
+
require 'methadone'
|
68
|
+
|
69
|
+
include Methadone::Main
|
70
|
+
|
71
|
+
main do |name,password|
|
72
|
+
name # => guaranteed to be non-nil
|
73
|
+
password # => nil if user omitted on command line
|
74
|
+
options[:switch] # => true if user used --switch or -s
|
75
|
+
options[:s] # => ALSO true if user used --switch or -s
|
76
|
+
options[:f] # => value of FILE if used on command-line
|
77
|
+
options[:flag] # => ALSO value of FILE if used on command-line
|
78
|
+
|
79
|
+
# If something goes wrong, you can just raise an exception
|
80
|
+
# or call exit_now! if you want to control the exit status
|
81
|
+
end
|
82
|
+
|
83
|
+
description "One line summary of your awesome app"
|
84
|
+
|
85
|
+
on("--[no-]switch","-s","Some switch")
|
86
|
+
on("-f FILE","--flag","Some flag")
|
87
|
+
on("-x FOO") do |foo|
|
88
|
+
# something more complex; this is exactly OptionParser opts.on
|
89
|
+
end
|
90
|
+
|
91
|
+
arg :name
|
92
|
+
arg :password, :optional
|
93
|
+
|
94
|
+
go!
|
95
|
+
|
96
|
+
+go!+ runs the block you gave to +main+, passing it the unparsed +ARGV+ as parameters to the block. It will
|
97
|
+
also parse the command-line via +OptionParser+ and do a check on the remaining arguments to see
|
98
|
+
if there's enough to satisfy the <tt>:required</tt> args you've specified.
|
99
|
+
|
100
|
+
Finally, the banner/help string will be full constructed based on the interface you've declared. If you
|
101
|
+
don't accept options, <tt>[options]</tt> won't appear in the help. The names of your arguments
|
102
|
+
will appear in proper order and <tt>:optional</tt> ones will be in square brackets. You don't have to
|
103
|
+
touch a thing.
|
104
|
+
|
105
|
+
Why not just <tt>def main</tt> and call that method? +go!+ also handles exceptions:
|
106
|
+
|
107
|
+
* Any uncaught exception that isn't a subclass of <tt>Methadone::Error</tt> will cause the app to exit with a 70 (standard Linux "internal error" exit status) and print the exception's messages to standard error, no backtrace
|
108
|
+
* An instance of <tt>Methadone::Error</tt>, if caught, will have similar behavior, but the +exit_code+ method will be called on the exception to determine the exit status. You can trigger this exception via <tt>exit_now! exit_stats,message</tt>
|
109
|
+
|
51
110
|
== Utility Classes
|
52
111
|
|
53
112
|
Currently, there are classes the assist in directing output logger-style to the right place; basically ensuring that errors go to +STDERR+ and everything else goes to +STDOUT+. All of this is, of course, configurable
|
data/bin/methadone
CHANGED
@@ -11,13 +11,13 @@ include FileUtils
|
|
11
11
|
include Methadone::Main
|
12
12
|
include Methadone::CLI
|
13
13
|
|
14
|
-
main do |
|
15
|
-
check_and_prepare_basedir!(
|
14
|
+
main do |app_name|
|
15
|
+
check_and_prepare_basedir!(app_name,options[:force])
|
16
16
|
|
17
|
-
gemname = File.basename(
|
17
|
+
gemname = File.basename(app_name)
|
18
18
|
debug "Creating project for gem #{gemname}"
|
19
19
|
|
20
|
-
chdir File.dirname(
|
20
|
+
chdir File.dirname(app_name)
|
21
21
|
|
22
22
|
%x[bundle gem #{gemname}]
|
23
23
|
|
@@ -41,26 +41,11 @@ main do |basedir,force|
|
|
41
41
|
], :before => /^end\s*$/
|
42
42
|
end
|
43
43
|
|
44
|
-
|
45
|
-
option_parser = OptionParser.new do |opts|
|
46
|
-
executable = File.basename(__FILE__)
|
47
|
-
opts.banner = "Usage: #{executable} [options] app_name\n\n" +
|
48
|
-
"Kick the bash habit by bootstrapping your Ruby command-line apps\n\nOptions:"
|
44
|
+
description "Kick the bash habit by bootstrapping your Ruby command-line apps"
|
49
45
|
|
50
|
-
|
51
|
-
options[:force] = true
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
option_parser.parse!
|
46
|
+
on("--force","Overwrite files if they exist")
|
56
47
|
|
57
|
-
|
58
|
-
|
59
|
-
if ARGV.empty?
|
60
|
-
STDERR.puts("error: app_dir required")
|
61
|
-
exit 2
|
62
|
-
end
|
48
|
+
arg :app_name, :required
|
63
49
|
|
64
|
-
ARGV << options[:force]
|
65
50
|
go!
|
66
51
|
|
data/features/bootstrap.feature
CHANGED
@@ -79,10 +79,7 @@ Feature: Bootstrap a new command-line app
|
|
79
79
|
Scenario: We must supply a dirname
|
80
80
|
When I run `methadone`
|
81
81
|
Then the exit status should not be 0
|
82
|
-
And the stderr should
|
83
|
-
"""
|
84
|
-
error: app_dir required
|
85
|
-
"""
|
82
|
+
And the stderr should match /'app_name' is required/
|
86
83
|
|
87
84
|
@debug
|
88
85
|
Scenario: Help is properly documented
|
data/lib/methadone.rb
CHANGED
data/lib/methadone/cli.rb
CHANGED
@@ -83,7 +83,7 @@ module Methadone
|
|
83
83
|
|
84
84
|
# Get the location of the templates for profile "from"
|
85
85
|
def template_dir(from)
|
86
|
-
File.join(File.dirname(
|
86
|
+
File.join(File.dirname(__FILE__),'..','..','templates',from.to_s)
|
87
87
|
end
|
88
88
|
|
89
89
|
def template_dirs_in(profile)
|
data/lib/methadone/main.rb
CHANGED
@@ -1,8 +1,44 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
1
3
|
module Methadone
|
2
4
|
# Include this module to gain access to the "canonical command-line app structure"
|
3
5
|
# DSL. This is a *very* lightweight layer on top of what you might
|
4
6
|
# normally write that gives you just a bit of help to keep your code structured
|
5
|
-
# in a sensible way.
|
7
|
+
# in a sensible way. You can use as much or as little as you want, though
|
8
|
+
# you must at least use #main to get any benefits.
|
9
|
+
#
|
10
|
+
# You also get a more expedient interface to OptionParser as well
|
11
|
+
# as checking for required arguments to your app. For example, if
|
12
|
+
# we want our app to accept a negatable switch named "switch", a flag
|
13
|
+
# named "flag", and two arguments "needed" (which is required)
|
14
|
+
# and "maybe" which optional, we can do the following:
|
15
|
+
#
|
16
|
+
# #!/usr/bin/env ruby -w
|
17
|
+
#
|
18
|
+
# require 'methadone'
|
19
|
+
#
|
20
|
+
# include Methadone::Main
|
21
|
+
#
|
22
|
+
# main do |needed, maybe|
|
23
|
+
# options[:switch] => true or false, based on command line
|
24
|
+
# options[:flag] => value of flag passed on command line
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # Proxy to an OptionParser instance's on method
|
28
|
+
# on("--[no]-switch")
|
29
|
+
# on("--flag VALUE")
|
30
|
+
#
|
31
|
+
# arg :needed
|
32
|
+
# arg :maybe, :optional
|
33
|
+
#
|
34
|
+
# go!
|
35
|
+
#
|
36
|
+
# Our app then acts as follows:
|
37
|
+
#
|
38
|
+
# $ our_app
|
39
|
+
# # => parse error: 'needed' is required
|
40
|
+
# $ our_app foo
|
41
|
+
# # => succeeds; "maybe" in main is nil
|
6
42
|
#
|
7
43
|
# This also includes Methadone::CLILogging to give you access to simple logging
|
8
44
|
module Main
|
@@ -40,21 +76,36 @@ module Methadone
|
|
40
76
|
# To run this method, call #go!
|
41
77
|
def main(&block)
|
42
78
|
@main_block = block
|
79
|
+
@options = {}
|
80
|
+
@option_parser = OptionParserProxy.new(OptionParser.new,@options)
|
43
81
|
end
|
44
82
|
|
83
|
+
|
45
84
|
# Start your command-line app, exiting appropriately when
|
46
|
-
# complete
|
85
|
+
# complete.
|
47
86
|
#
|
48
87
|
# This *will* exit your program when it completes. If your
|
49
88
|
# #main block evaluates to an integer, that value will be sent
|
50
89
|
# to Kernel#exit, otherwise, this will exit with 0
|
90
|
+
#
|
91
|
+
# If the command-line options couldn't be parsed, this
|
92
|
+
# will exit with 64 and whatever message OptionParser provided.
|
93
|
+
#
|
94
|
+
# If a required argument (see #arg) is not found, this exits with
|
95
|
+
# 64 and a message about that missing argument.
|
96
|
+
#
|
51
97
|
def go!
|
98
|
+
opts.parse!
|
99
|
+
opts.check_args!
|
52
100
|
result = call_main
|
53
101
|
if result.kind_of? Fixnum
|
54
102
|
exit result
|
55
103
|
else
|
56
104
|
exit 0
|
57
105
|
end
|
106
|
+
rescue OptionParser::ParseError => ex
|
107
|
+
error ex.message
|
108
|
+
exit 64 # Linux standard for bad command line
|
58
109
|
end
|
59
110
|
|
60
111
|
# Call this to exit the program immediately
|
@@ -66,6 +117,65 @@ module Methadone
|
|
66
117
|
raise Methadone::Error.new(exit_code,message)
|
67
118
|
end
|
68
119
|
|
120
|
+
# Returns an OptionParser that you can use
|
121
|
+
# to declare your command-line interface. The object returned as
|
122
|
+
# an additional feature that implements typical use of OptionParser.
|
123
|
+
#
|
124
|
+
# opts.on("--flag VALUE")
|
125
|
+
#
|
126
|
+
# Does this under the covers:
|
127
|
+
#
|
128
|
+
# opts.on("--flag VALUE") do |value|
|
129
|
+
# options[:flag] = value
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# Since, most of the time, this is all you want to do,
|
133
|
+
# this makes it more expedient to do so. The key that is
|
134
|
+
# is set in #options will be a symbol of the option name, without
|
135
|
+
# the dashes. Note that if you use multiple option names, a key
|
136
|
+
# will be generated for each. Further, if you use the negatable form,
|
137
|
+
# only the positive key will be set, e.g. for <tt>--[no-]verbose</tt>,
|
138
|
+
# only <tt>:verbose</tt> will be set (to true or false).
|
139
|
+
def opts
|
140
|
+
@option_parser
|
141
|
+
end
|
142
|
+
|
143
|
+
# Calls <tt>opts.on</tt> with the given arguments
|
144
|
+
def on(*args,&block)
|
145
|
+
opts.on(*args,&block)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Sets the name of an arguments your app accepts. Note
|
149
|
+
# that no sanity checking is done on the configuration
|
150
|
+
# of your arguments you create via multiple calls to this method.
|
151
|
+
# Namely, the last argument should be the only one that is
|
152
|
+
# a :many or a :any, but the system here won't sanity check that.
|
153
|
+
#
|
154
|
+
# +arg_name+:: name of the argument to appear in documentation
|
155
|
+
# This will be converted into a String and used to create
|
156
|
+
# the banner (unless you have overridden the banner)
|
157
|
+
# +options+:: list (not Hash) of options:
|
158
|
+
# <tt>:required</tt> - this arg is required (this is the default)
|
159
|
+
# <tt>:optional</tt> - this arg is optional
|
160
|
+
# <tt>:one</tt> - only one of this arg should be supplied (default)
|
161
|
+
# <tt>:many</tt> - many of this arg may be supplied, but at least one is required
|
162
|
+
# <tt>:any</tt> - any number, include zero, may be supplied
|
163
|
+
def arg(arg_name,*options)
|
164
|
+
opts.arg(arg_name,*options)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Set the description of your app for inclusion in the help output.
|
168
|
+
# +desc+:: a short, one-line description of your app
|
169
|
+
def description(desc)
|
170
|
+
opts.description(desc)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Returns a Hash that you can use to store or retrieve options
|
174
|
+
# parsed from the command line
|
175
|
+
def options
|
176
|
+
@options
|
177
|
+
end
|
178
|
+
|
69
179
|
private
|
70
180
|
|
71
181
|
# Handle calling main and trapping any exceptions thrown
|
@@ -83,4 +193,124 @@ module Methadone
|
|
83
193
|
exception.message.nil? || exception.message.strip.empty?
|
84
194
|
end
|
85
195
|
end
|
196
|
+
|
197
|
+
# A proxy to OptionParser that intercepts #on
|
198
|
+
# so that we can allow a simpler interface
|
199
|
+
class OptionParserProxy < BasicObject
|
200
|
+
# Create the proxy
|
201
|
+
#
|
202
|
+
# +option_parser+:: An OptionParser instance
|
203
|
+
# +options+:: a hash that will store the options
|
204
|
+
# set via automatic setting. The caller should
|
205
|
+
# retain a reference to this
|
206
|
+
def initialize(option_parser,options)
|
207
|
+
@option_parser = option_parser
|
208
|
+
@options = options
|
209
|
+
@user_specified_banner = false
|
210
|
+
@accept_options = false
|
211
|
+
@args = []
|
212
|
+
@arg_options = {}
|
213
|
+
@description = nil
|
214
|
+
set_banner
|
215
|
+
end
|
216
|
+
|
217
|
+
def check_args!
|
218
|
+
::Hash[@args.zip(::ARGV)].each do |arg_name,arg_value|
|
219
|
+
if @arg_options[arg_name].include? :required
|
220
|
+
if arg_value.nil?
|
221
|
+
message = "'#{arg_name.to_s}' is required"
|
222
|
+
message = "at least one " + message if @arg_options[arg_name].include? :many
|
223
|
+
raise ::OptionParser::ParseError,message
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# If invoked as with OptionParser, behaves the exact same way.
|
230
|
+
# If invoked without a block, however, the options hash given
|
231
|
+
# to the constructor will be used to store
|
232
|
+
# the parsed command-line value. See #opts in the Main module
|
233
|
+
# for how that works.
|
234
|
+
def on(*args,&block)
|
235
|
+
@accept_options = true
|
236
|
+
if block
|
237
|
+
@option_parser.on(*args,&block)
|
238
|
+
else
|
239
|
+
opt_names = option_names(*args)
|
240
|
+
@option_parser.on(*args) do |value|
|
241
|
+
opt_names.each { |name| @options[name] = value }
|
242
|
+
end
|
243
|
+
end
|
244
|
+
set_banner
|
245
|
+
end
|
246
|
+
|
247
|
+
# Proxies to underlying OptionParser
|
248
|
+
def banner=(new_banner)
|
249
|
+
@option_parser.banner=new_banner
|
250
|
+
@user_specified_banner = true
|
251
|
+
end
|
252
|
+
|
253
|
+
# Sets the banner to include these arg names
|
254
|
+
def arg(arg_name,*options)
|
255
|
+
options << :optional if options.include?(:any) && !options.include?(:optional)
|
256
|
+
options << :required unless options.include? :optional
|
257
|
+
options << :one unless options.include?(:any) || options.include?(:many)
|
258
|
+
@args << arg_name
|
259
|
+
@arg_options[arg_name] = options
|
260
|
+
set_banner
|
261
|
+
end
|
262
|
+
|
263
|
+
def description(desc)
|
264
|
+
@description = desc
|
265
|
+
set_banner
|
266
|
+
end
|
267
|
+
|
268
|
+
# Defers all calls save #on to
|
269
|
+
# the underlying OptionParser instance
|
270
|
+
def method_missing(sym,*args,&block)
|
271
|
+
@option_parser.send(sym,*args,&block)
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
|
276
|
+
def set_banner
|
277
|
+
unless @user_specified_banner
|
278
|
+
new_banner="Usage: #{::File.basename($0)}"
|
279
|
+
new_banner += " [options]" if @accept_options
|
280
|
+
unless @args.empty?
|
281
|
+
new_banner += " "
|
282
|
+
new_banner += @args.map { |arg|
|
283
|
+
if @arg_options[arg].include? :any
|
284
|
+
"[#{arg.to_s}...]"
|
285
|
+
elsif @arg_options[arg].include? :optional
|
286
|
+
"[#{arg.to_s}]"
|
287
|
+
elsif @arg_options[arg].include? :many
|
288
|
+
"#{arg.to_s}..."
|
289
|
+
else
|
290
|
+
arg.to_s
|
291
|
+
end
|
292
|
+
}.join(' ')
|
293
|
+
end
|
294
|
+
new_banner += "\n\n#{@description}" if @description
|
295
|
+
new_banner += "\n\nOptions:" if @accept_options
|
296
|
+
|
297
|
+
@option_parser.banner=new_banner
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def option_names(*opts_on_args,&block)
|
302
|
+
opts_on_args.map { |arg|
|
303
|
+
if arg =~ /^--\[no-\]([^-\s]*)/
|
304
|
+
$1.to_sym
|
305
|
+
elsif arg =~ /^--([^-\s]*)/
|
306
|
+
$1.to_sym
|
307
|
+
elsif arg =~ /^-([^-\s]*)/
|
308
|
+
$1.to_sym
|
309
|
+
else
|
310
|
+
nil
|
311
|
+
end
|
312
|
+
}.reject(&:nil?)
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
86
316
|
end
|
data/lib/methadone/version.rb
CHANGED
@@ -5,31 +5,31 @@ require 'methadone'
|
|
5
5
|
|
6
6
|
include Methadone::Main
|
7
7
|
|
8
|
-
main do |
|
8
|
+
main do # Add args you want: |like,so|
|
9
9
|
# your program code here
|
10
|
+
# You can access CLI options via
|
11
|
+
# the options Hash
|
10
12
|
end
|
11
13
|
|
12
14
|
# supplemental methods here
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
# Declare command-line interface here
|
17
|
+
|
18
|
+
# description "one line description of your app"
|
19
|
+
#
|
20
|
+
# Accept flags via:
|
21
|
+
# on("--flag VAL","Some flag")
|
22
|
+
# options[flag] will contain VAL
|
23
|
+
#
|
24
|
+
# Specify switches via:
|
25
|
+
# on("--[no-]switch","Some switch")
|
26
|
+
#
|
27
|
+
# Or, just call OptionParser methods on opts
|
28
|
+
#
|
29
|
+
# Require an argument
|
30
|
+
# arg :some_arg
|
31
|
+
#
|
32
|
+
# # Make an argument optional
|
33
|
+
# arg :optional_arg, :optional
|
22
34
|
|
23
|
-
# opts.on("--flag VAL","Some flag") do |val|
|
24
|
-
# options[:flag] = val
|
25
|
-
# end
|
26
|
-
#
|
27
|
-
# opts.on("--[no-]switch","Some switch") do |switch|
|
28
|
-
# options[:switch] = switch
|
29
|
-
# end
|
30
|
-
end
|
31
|
-
|
32
|
-
option_parser.parse!
|
33
|
-
|
34
|
-
ARGV << options
|
35
35
|
go!
|
data/test/test_main.rb
CHANGED
@@ -110,6 +110,153 @@ class TestMain < BaseTest
|
|
110
110
|
assert_logged_at_error "oh noes"
|
111
111
|
end
|
112
112
|
|
113
|
+
test "opts allows us to more expediently set up OptionParser" do
|
114
|
+
switch = nil
|
115
|
+
flag = nil
|
116
|
+
main do
|
117
|
+
switch = options[:switch]
|
118
|
+
flag = options[:flag]
|
119
|
+
end
|
120
|
+
|
121
|
+
opts.on("--switch") { options[:switch] = true }
|
122
|
+
opts.on("--flag FLAG") { |value| options[:flag] = value }
|
123
|
+
|
124
|
+
set_argv %w(--switch --flag value)
|
125
|
+
|
126
|
+
safe_go!
|
127
|
+
|
128
|
+
assert switch
|
129
|
+
assert_equal 'value',flag
|
130
|
+
end
|
131
|
+
|
132
|
+
test "when the command line is invalid, we exit with 64" do
|
133
|
+
main do
|
134
|
+
end
|
135
|
+
|
136
|
+
opts.on("--switch") { options[:switch] = true }
|
137
|
+
opts.on("--flag FLAG") { |value| options[:flag] = value }
|
138
|
+
|
139
|
+
set_argv %w(--invalid --flag value)
|
140
|
+
|
141
|
+
assert_exits(64) { go! }
|
142
|
+
end
|
143
|
+
|
144
|
+
test "omitting the block to opts simply sets the value in the options hash and returns itself" do
|
145
|
+
switch = nil
|
146
|
+
negatable = nil
|
147
|
+
flag = nil
|
148
|
+
f = nil
|
149
|
+
other = nil
|
150
|
+
some_other = nil
|
151
|
+
main do
|
152
|
+
switch = options[:switch]
|
153
|
+
flag = options[:flag]
|
154
|
+
f = options[:f]
|
155
|
+
negatable = options[:negatable]
|
156
|
+
other = options[:other]
|
157
|
+
some_other = options[:some_other]
|
158
|
+
end
|
159
|
+
|
160
|
+
on("--switch")
|
161
|
+
on("--[no-]negatable")
|
162
|
+
on("--flag FLAG","-f","Some documentation string")
|
163
|
+
on("--other") do
|
164
|
+
options[:some_other] = true
|
165
|
+
end
|
166
|
+
|
167
|
+
set_argv %w(--switch --flag value --negatable --other)
|
168
|
+
|
169
|
+
safe_go!
|
170
|
+
|
171
|
+
assert switch
|
172
|
+
assert some_other
|
173
|
+
refute other
|
174
|
+
assert_equal 'value',flag
|
175
|
+
assert_equal 'value',f,opts.to_s
|
176
|
+
assert_match /Some documentation string/,opts.to_s
|
177
|
+
end
|
178
|
+
|
179
|
+
test "without specifying options, [options] doesn't show up in our banner" do
|
180
|
+
main {}
|
181
|
+
|
182
|
+
refute_match /\[options\]/,opts.banner
|
183
|
+
end
|
184
|
+
|
185
|
+
test "when specifying an option, [options] shows up in the banner" do
|
186
|
+
main {}
|
187
|
+
on("-s")
|
188
|
+
|
189
|
+
assert_match /\[options\]/,opts.banner
|
190
|
+
end
|
191
|
+
|
192
|
+
test "I can specify which arguments my app takes and if they are required" do
|
193
|
+
main {}
|
194
|
+
|
195
|
+
arg :db_name
|
196
|
+
arg :user, :required
|
197
|
+
arg :password, :optional
|
198
|
+
|
199
|
+
assert_match /db_name user \[password\]$/,opts.banner
|
200
|
+
end
|
201
|
+
|
202
|
+
test "I can specify which arguments my app takes and if they are singular or plural" do
|
203
|
+
main {}
|
204
|
+
|
205
|
+
arg :db_name
|
206
|
+
arg :user, :required, :one
|
207
|
+
arg :tables, :many
|
208
|
+
|
209
|
+
assert_match /db_name user tables...$/,opts.banner
|
210
|
+
end
|
211
|
+
|
212
|
+
test "I can specify which arguments my app takes and if they are singular or optional plural" do
|
213
|
+
main {}
|
214
|
+
|
215
|
+
arg :db_name
|
216
|
+
arg :user, :required, :one
|
217
|
+
arg :tables, :any
|
218
|
+
|
219
|
+
assert_match /db_name user \[tables...\]$/,opts.banner
|
220
|
+
end
|
221
|
+
|
222
|
+
test "I can set a description for my app" do
|
223
|
+
main {}
|
224
|
+
description "An app of total awesome"
|
225
|
+
|
226
|
+
assert_match /^An app of total awesome$/,opts.banner
|
227
|
+
end
|
228
|
+
|
229
|
+
test "when I override the banner, we don't automatically do anything" do
|
230
|
+
main {}
|
231
|
+
opts.banner = "FOOBAR"
|
232
|
+
|
233
|
+
on("-s")
|
234
|
+
|
235
|
+
assert_equal "FOOBAR",opts.banner
|
236
|
+
end
|
237
|
+
|
238
|
+
test "when I say an argument is required and its omitted, I get an error" do
|
239
|
+
main {}
|
240
|
+
arg :foo
|
241
|
+
arg :bar
|
242
|
+
|
243
|
+
set_argv %w(blah)
|
244
|
+
|
245
|
+
assert_exits(64) { go! }
|
246
|
+
assert_logged_at_error("parse error: 'bar' is required")
|
247
|
+
end
|
248
|
+
|
249
|
+
test "when I say an argument is many and its omitted, I get an error" do
|
250
|
+
main {}
|
251
|
+
arg :foo
|
252
|
+
arg :bar, :many
|
253
|
+
|
254
|
+
set_argv %w(blah)
|
255
|
+
|
256
|
+
assert_exits(64) { go! }
|
257
|
+
assert_logged_at_error("parse error: at least one 'bar' is required")
|
258
|
+
end
|
259
|
+
|
113
260
|
private
|
114
261
|
|
115
262
|
# Calls go!, but traps the exit
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: methadone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-10-
|
12
|
+
date: 2011-10-02 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec-expectations
|
16
|
-
requirement: &
|
16
|
+
requirement: &70140138849040 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '2.6'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70140138849040
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70140138848500 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70140138848500
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rdoc
|
38
|
-
requirement: &
|
38
|
+
requirement: &70140138847780 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '3.9'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70140138847780
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: aruba
|
49
|
-
requirement: &
|
49
|
+
requirement: &70140138847360 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70140138847360
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: simplecov
|
60
|
-
requirement: &
|
60
|
+
requirement: &70140138846780 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: '0.5'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70140138846780
|
69
69
|
description: Methadone provides a lot of small but useful features for developing
|
70
70
|
a command-line app, including an opinionated bootstrapping process, some helpful
|
71
71
|
cucumber steps, and some classes to bridge logging and output into a simple, unified,
|