methadone 1.0.0.rc1 → 1.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +50 -180
- data/bin/methadone +9 -13
- data/features/bootstrap.feature +1 -0
- data/lib/methadone/cli_logger.rb +23 -10
- data/lib/methadone/cli_logging.rb +34 -0
- data/lib/methadone/cucumber.rb +49 -0
- data/lib/methadone/execution_strategy/base.rb +0 -6
- data/lib/methadone/main.rb +48 -20
- data/lib/methadone/sh.rb +40 -4
- data/lib/methadone/version.rb +1 -1
- data/templates/full/bin/executable.erb +36 -31
- data/test/test_cli_logger.rb +21 -0
- data/test/test_cli_logging.rb +50 -0
- data/test/test_main.rb +81 -15
- metadata +91 -144
data/README.rdoc
CHANGED
@@ -6,17 +6,15 @@ 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
|
-
The
|
9
|
+
The goal of this project is to make it as easy as possible to write awesome and powerful command-line applications.
|
10
10
|
|
11
|
-
|
11
|
+
Toward that end, this gem provides:
|
12
12
|
|
13
|
-
*
|
14
|
-
*
|
15
|
-
*
|
16
|
-
*
|
17
|
-
|
18
|
-
* Methadone::CLILogging - a module that, when included in any class, provides easy access to a shared logger
|
19
|
-
* Cucumber Steps
|
13
|
+
* A command-line app to bootstrap a new command-line app.
|
14
|
+
* A lightweight DSL to create your command-line interface, that loses none of <tt>OptionParser</tt>'s power.
|
15
|
+
* A simplified means of running external commands that has better error handling and diagnostics.
|
16
|
+
* Simplified zero-config logging that is a better-than-<tt>puts</tt> <tt>puts</tt>.
|
17
|
+
* Cucumber Steps, built on Aruba, to allow you to test-drive your command-line app development.
|
20
18
|
|
21
19
|
== Links
|
22
20
|
|
@@ -26,13 +24,13 @@ Currently, this library is under development and has the following to offer:
|
|
26
24
|
== Platforms
|
27
25
|
|
28
26
|
* Works completely on:
|
27
|
+
* MRI Ruby 1.8.7
|
29
28
|
* MRI Ruby 1.9.2
|
30
29
|
* MRI Ruby 1.9.3
|
31
30
|
* MRI Ruby Head
|
32
|
-
* MRI Ruby 1.8.7
|
33
31
|
* RBX
|
34
32
|
* REE
|
35
|
-
* For JRuby, everything works but
|
33
|
+
* For JRuby, everything works but Aruba; Aruba just doesn't work on JRuby for some reason, though this library *is* being used in production under JRuby 1.6.6
|
36
34
|
|
37
35
|
== Bootstrapping a new CLI App
|
38
36
|
|
@@ -61,68 +59,24 @@ The +methadone+ command-line app will bootstrap a new command-line app, setting
|
|
61
59
|
Usage: newgem [options]
|
62
60
|
"""
|
63
61
|
|
64
|
-
Basically, this sets you up with all the boilerplate that you *should* be using to write a command-line app.
|
62
|
+
Basically, this sets you up with all the boilerplate that you *should* be using to write a command-line app. Specifically, you get:
|
65
63
|
|
66
|
-
|
64
|
+
* Gemified project (based on <tt>bundle gem</tt>)
|
65
|
+
* An executable using Methadone::Main to outline your new app
|
66
|
+
* <tt>Test::Unit</tt> test task set up and an empty unit test.
|
67
|
+
* Aruba/Cucumber set up with a basic feature that exercise your executable
|
68
|
+
* The outline of a README
|
69
|
+
* An optional license included
|
67
70
|
|
68
|
-
|
71
|
+
== DSL for your <tt>bin</tt> file
|
72
|
+
|
73
|
+
A canonical <tt>OptionParser</tt> driven app has a few problems with it structurally that methadone can solve
|
69
74
|
|
70
75
|
* Backwards organization - main logic is at the bottom of the file, not the top
|
71
76
|
* Verbose to use +opts.on+ just to set a value in a +Hash+
|
72
|
-
* No exception handling
|
73
|
-
|
74
|
-
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
|
75
|
-
|
76
|
-
#!/usr/bin/env ruby
|
77
|
-
|
78
|
-
require 'optparse'
|
79
|
-
require 'methadone'
|
80
|
-
|
81
|
-
include Methadone::Main
|
82
|
-
|
83
|
-
main do |name,password|
|
84
|
-
name # => guaranteed to be non-nil
|
85
|
-
password # => nil if user omitted on command line
|
86
|
-
options[:switch] # => true if user used --switch or -s
|
87
|
-
options[:s] # => ALSO true if user used --switch or -s
|
88
|
-
options[:f] # => value of FILE if used on command-line
|
89
|
-
options[:flag] # => ALSO value of FILE if used on command-line
|
90
|
-
|
91
|
-
# If something goes wrong, you can just raise an exception
|
92
|
-
# or call exit_now! if you want to control the exit status
|
93
|
-
#
|
94
|
-
# Note that if you set DEBUG in the environment, the exception
|
95
|
-
# will leak through; this can be handy to figure out why
|
96
|
-
# your app might be failing
|
97
|
-
end
|
98
|
-
|
99
|
-
description "One line summary of your awesome app"
|
100
|
-
|
101
|
-
on("--[no-]switch","-s","Some switch")
|
102
|
-
on("-f FILE","--flag","Some flag")
|
103
|
-
on("-x FOO") do |foo|
|
104
|
-
# something more complex; this is exactly OptionParser opts.on
|
105
|
-
end
|
106
|
-
|
107
|
-
arg :name
|
108
|
-
arg :password, :optional
|
109
|
-
|
110
|
-
# If you want to avoid automatic exception catching/logging do:
|
111
|
-
#
|
112
|
-
# leak_exceptions true
|
113
|
-
#
|
114
|
-
# Note that Methadone::Error exceptions are always caught and logged
|
115
|
-
|
116
|
-
go!
|
77
|
+
* No exception handling - you have to explicitly call <tt>exit</tt> and/or let exceptions' stack traces leak through.
|
117
78
|
|
118
|
-
|
119
|
-
also parse the command-line via +OptionParser+ and do a check on the remaining arguments to see
|
120
|
-
if there's enough to satisfy the <tt>:required</tt> args you've specified.
|
121
|
-
|
122
|
-
Finally, the banner/help string will be full constructed based on the interface you've declared. If you
|
123
|
-
don't accept options, <tt>[options]</tt> won't appear in the help. The names of your arguments
|
124
|
-
will appear in proper order and <tt>:optional</tt> ones will be in square brackets. You don't have to
|
125
|
-
touch a thing.
|
79
|
+
Methadone provides Methadone::Main to help make a clean and easy-to-maintain <tt>bin</tt> file. See the {rdoc}[http://rubydoc.info/github/davetron5000/methadone/master/Methadone/Main] for an example, and see {my blog}[http://www.naildrivin5.com/blog/2011/12/19/methadone-the-awesome-cli-library.html] on the derivation of this module.
|
126
80
|
|
127
81
|
== Wrapper for running external commands with good logging
|
128
82
|
|
@@ -132,87 +86,44 @@ While backtick and <tt>%x[]</tt> are nice for compact, bash-like scripting, they
|
|
132
86
|
* You have no access to the standard error
|
133
87
|
* You really want to log: the command, the output, and the error so that for cron-like tasks, you can sort out what happened
|
134
88
|
|
135
|
-
Enter Methadone::SH
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
#
|
141
|
-
#
|
142
|
-
|
143
|
-
|
144
|
-
#
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
# => same as above, EXCEPT, raises a Methadone::FailedCommandError
|
149
|
-
|
150
|
-
With this, you can easily script external commands in *almost* as expedient a fashion as with +bash+, however you get sensible logging along the way. By default, this uses the logger provided by Methadone::CLILogging (which is *not* mixed in when you mix in Methadone::SH). If you want to use a different logger, or don't want to mix in Methadone::CLILogging, simply call +set_sh_logger+ with your preferred logger.
|
151
|
-
|
152
|
-
But that's not all! You can run code when the command succeed by passing a block:
|
153
|
-
|
154
|
-
sh 'cp foo.txt /tmp' do
|
155
|
-
# Behaves exactly as before, but this block is called after
|
156
|
-
end
|
157
|
-
|
158
|
-
sh 'cp non_existent_file.txt /nowhere_good' do
|
159
|
-
# This block isn't called, since the command failed
|
160
|
-
end
|
161
|
-
|
162
|
-
The <tt>sh!</tt> form works this way as well. The block form is also how you can access the standard output or error of the command that ran. Simply have your block accept one or two aguments:
|
89
|
+
Enter Methadone::SH
|
90
|
+
|
91
|
+
sh "cp foo.txt /tmp"
|
92
|
+
# => logs command at DEBUG level
|
93
|
+
# if the command exited zero:
|
94
|
+
# logs the standard output at DEBUG
|
95
|
+
# logs the standard error at WARN
|
96
|
+
# if the command exited nonzero:
|
97
|
+
# logs the standard output at INFO
|
98
|
+
# logs the standard error at WARN
|
99
|
+
# returns the exit code for your examination
|
100
|
+
#
|
101
|
+
# there's a LOT MORE
|
163
102
|
|
164
|
-
|
165
|
-
# stdout contains the output of the command
|
166
|
-
end
|
167
|
-
sh 'ls -l /tmp/ /non_existent_dir' do |stdout,stderr|
|
168
|
-
# stdout contains the output of the command,
|
169
|
-
# stderr contains the standard error output.
|
170
|
-
end
|
103
|
+
See the {rdoc}[http://rubydoc.info/github/davetron5000/methadone/master/Methadone/SH] for more detailed examples and usage.
|
171
104
|
|
172
105
|
This isn't a replacement for Open3 or ChildProcess, but a way to easily "do the right thing" for most cases.
|
173
106
|
|
174
|
-
==
|
175
|
-
|
176
|
-
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
|
107
|
+
== Zero-Config Logging
|
177
108
|
|
178
|
-
|
109
|
+
Chances are, your code is littered with <tt>STDERR.puts</tt> on a good day, and nothing on a bad day. You probably also have a bunch of debug <tt>puts</tt> calls that you have commented out. Logging is extremely helpful in understand how your app is behaving (or how it behaved in the past). Logging can be a pain to set up, and can also make it hard to give the user at the command-prompt a good experience.
|
179
110
|
|
180
|
-
|
111
|
+
Methadone::CLILogger is designed to handle this. It's a proper subclass of Ruby's built-in <tt>Logger</tt> with a few enhancements:
|
181
112
|
|
182
|
-
|
113
|
+
* Messages don't get formatting if they are destined for a TTY (e.g. the user sitting at her terminal)
|
114
|
+
* Errors and warnings go to the standard error.
|
115
|
+
* Debug and info messages go to the standard output.
|
116
|
+
* When these are redirected to a file, the log messages are properly date/time stamped as you'd expect
|
117
|
+
* You can mix-in Methadone::CLILogging to get access to a global logger instances without resorting to an explicit global variable
|
183
118
|
|
184
|
-
|
119
|
+
See {CLILogger's rdoc}[http://rubydoc.info/github/davetron5000/methadone/master/Methadone/CLILogger] and then {CLILogging's}[http://rubydoc.info/github/davetron5000/methadone/master/Methadone/CLILogging] for more.
|
185
120
|
|
186
|
-
command = "rm -rf /tmp/*"
|
187
|
-
debug("About to run #{command}") # => goes only to STDOUT, no logging format
|
188
|
-
if system(command)
|
189
|
-
info("Succesfully ran #{command}") # => goes only to STDOUT, no logging format
|
190
|
-
else
|
191
|
-
error("There was a problem running #{command}") # => goes only to STDERR, no logging format
|
192
|
-
end
|
193
121
|
|
194
|
-
|
195
|
-
|
196
|
-
Here, since we have a logfile, that logfile gets ALL messages and they have the default logger format.
|
197
|
-
|
198
|
-
require 'methadone'
|
199
|
-
|
200
|
-
include Methadone::CLILogging
|
201
|
-
|
202
|
-
self.logger = CLILogger.new("logfile.txt")
|
203
|
-
command = "rm -rf /tmp/*"
|
204
|
-
debug("About to run #{command}") # => goes only to logfile.txt, in the logger-style format
|
205
|
-
if system(command)
|
206
|
-
info("Succesfully ran #{command}") # => goes only to logfile.txt, in the logger-style format
|
207
|
-
else
|
208
|
-
error("There was a problem running #{command}")
|
209
|
-
# => goes to logfile.txt in the logger-style format, and
|
210
|
-
# to STDERR in a plain format
|
211
|
-
end
|
122
|
+
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
|
212
123
|
|
213
124
|
== Cucumber Steps
|
214
125
|
|
215
|
-
Methadone uses aruba[http://www.github.com/cucumber/aruba] for BDD-style testing with cucumber. This library has some awesome steps, and
|
126
|
+
Methadone uses aruba[http://www.github.com/cucumber/aruba] for BDD-style testing with cucumber. This library has some awesome steps, and Methadone provides additional, more opinionated, steps.
|
216
127
|
|
217
128
|
=== Example
|
218
129
|
|
@@ -230,50 +141,9 @@ Here's an example from methadone's own tests:
|
|
230
141
|
|app_name|which is required|
|
231
142
|
|dir_name|which is optional|
|
232
143
|
|
233
|
-
|
234
|
-
* Run <tt>command_to_run --help</tt> using aruba
|
235
|
-
|
236
|
-
When I get help for "command_to_run"
|
237
|
-
|
238
|
-
* Make sure that each option shows up in the help and has *some* sort of documentation
|
239
|
-
|
240
|
-
Then the following options should be documented:
|
241
|
-
|--force|
|
242
|
-
|-x |
|
243
|
-
|
244
|
-
* Check an individual option for documentation:
|
245
|
-
|
246
|
-
Then the option "--force" should be documented
|
247
|
-
|
248
|
-
* Checks that the help has a proper usage banner
|
249
|
-
|
250
|
-
Then the banner should be present
|
251
|
-
|
252
|
-
* Checks that the banner includes the version
|
253
|
-
|
254
|
-
Then the banner should include the version
|
255
|
-
|
256
|
-
* Checks that the usage banner indicates it takes options via <tt>[options]</tt>
|
257
|
-
|
258
|
-
Then the banner should document that this app takes options
|
259
|
-
|
260
|
-
* Do the opposite; check that you don't indicate options are accepted
|
261
|
-
|
262
|
-
Then the banner should document that this app takes no options
|
263
|
-
|
264
|
-
* Checks that the app's usage banner documents that its arguments are <tt>args</tt>
|
265
|
-
|
266
|
-
Then the banner should document that this app's arguments are "args"
|
267
|
-
|
268
|
-
* Do the opposite; check that your app doesn't take any arguments
|
269
|
-
|
270
|
-
Then the banner should document that this app takes no arguments
|
271
|
-
|
272
|
-
* Check for a usage description which occurs after the banner and a blank line
|
273
|
-
|
274
|
-
Then there should be a one-line summary of what the app does
|
275
|
-
|
276
|
-
== What might be
|
144
|
+
See Methadone::Cucumber or its {rdoc}[http://rubydoc.info/github/davetron5000/methadone/master/Methadone/Cucumber] for a list of all the steps provided.
|
277
145
|
|
278
|
-
|
146
|
+
== Contributing
|
279
147
|
|
148
|
+
* Feel free to file an issue, even if you don't have time to submit a patch
|
149
|
+
* Please try to include a test for any patch you submit. If you don't include a test, I'll have to write one, and it'll take longer to get your code in.
|
data/bin/methadone
CHANGED
@@ -7,6 +7,7 @@ require 'methadone/cli'
|
|
7
7
|
|
8
8
|
include FileUtils
|
9
9
|
include Methadone::Main
|
10
|
+
include Methadone::CLILogging
|
10
11
|
include Methadone::CLI
|
11
12
|
include Methadone::SH
|
12
13
|
|
@@ -32,20 +33,11 @@ main do |app_name|
|
|
32
33
|
end
|
33
34
|
|
34
35
|
license = options[:license]
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
else
|
39
|
-
copy_file "#{options[:license]}_LICENSE.txt", :as => "LICENSE.txt"
|
40
|
-
end
|
41
|
-
else
|
42
|
-
warn "warning: your app has no license"
|
43
|
-
end
|
44
|
-
|
36
|
+
warn "warning: your app has no license" unless license
|
37
|
+
license = nil if license == 'NONE'
|
38
|
+
copy_file "#{options[:license]}_LICENSE.txt", :as => "LICENSE.txt" if license
|
45
39
|
|
46
|
-
if using_readme
|
47
|
-
copy_file "README.rdoc", :binding => binding
|
48
|
-
end
|
40
|
+
copy_file "README.rdoc", :binding => binding if using_readme
|
49
41
|
|
50
42
|
copy_file "features/executable.feature", :as => "#{gemname}.feature", :binding => binding
|
51
43
|
copy_file "features/step_definitions/executable_steps.rb", :as => "#{gemname}_steps.rb"
|
@@ -70,9 +62,13 @@ on("--[no-]readme","[Do not ]produce a README file")
|
|
70
62
|
licenses = %w(mit apache custom NONE)
|
71
63
|
on("-l LICENSE","--license",licenses,"Specify the license for your project (#{licenses.join('|')})")
|
72
64
|
|
65
|
+
use_log_level_option
|
66
|
+
|
73
67
|
arg :app_name, :required
|
74
68
|
|
75
69
|
version Methadone::VERSION
|
76
70
|
|
71
|
+
defaults_from_env_var 'METHODONE_OPTS'
|
72
|
+
|
77
73
|
go!
|
78
74
|
|
data/features/bootstrap.feature
CHANGED
@@ -43,6 +43,7 @@ Feature: Bootstrap a new command-line app
|
|
43
43
|
And the banner should document that this app takes options
|
44
44
|
And the following options should be documented:
|
45
45
|
|--version|
|
46
|
+
|--log-level|
|
46
47
|
And the banner should document that this app takes no arguments
|
47
48
|
When I successfully run `rake -T -I../../lib`
|
48
49
|
Then the output should contain:
|
data/lib/methadone/cli_logger.rb
CHANGED
@@ -2,17 +2,18 @@ require 'logger'
|
|
2
2
|
|
3
3
|
module Methadone
|
4
4
|
# A Logger instance that gives better control of messaging the user
|
5
|
-
# and logging app activity. At it's most basic, you would use
|
6
|
-
#
|
5
|
+
# and logging app activity. At it's most basic, you would use <tt>info</tt>
|
6
|
+
# as a replacement for +puts+ and <tt>error</tt> as a replacement
|
7
7
|
# for <tt>STDERR.puts</tt>. Since this is a logger, however, you
|
8
8
|
# can also use #debug, #warn, and #fatal, and you can control
|
9
9
|
# the format and "logging level" as such.
|
10
10
|
#
|
11
11
|
# So, by default:
|
12
|
-
# *
|
13
|
-
# *
|
14
|
-
# *
|
15
|
-
# * The default format of messages is simply the message, no logging cruft
|
12
|
+
# * debug messages do not appear anywhere
|
13
|
+
# * info messages appear on the standard output
|
14
|
+
# * warn, error, and fatal messagse appear on the standard error
|
15
|
+
# * The default format of messages is simply the message, no logging cruft, however if your output
|
16
|
+
# is redirected to a file, a better timestamped logging format is used
|
16
17
|
#
|
17
18
|
# You can customize this in several ways:
|
18
19
|
#
|
@@ -66,7 +67,8 @@ module Methadone
|
|
66
67
|
end
|
67
68
|
@stderr_logger.add(severity,message,progname,&block)
|
68
69
|
end
|
69
|
-
|
70
|
+
|
71
|
+
DEFAULT_ERROR_LEVEL = Logger::Severity::WARN
|
70
72
|
|
71
73
|
# A logger that logs error-type messages to a second device; useful
|
72
74
|
# for ensuring that error messages go to standard error. This should be
|
@@ -81,16 +83,27 @@ module Methadone
|
|
81
83
|
# +error_device+:: device where all error messages should go. By default, this is Logger::Severity::WARN
|
82
84
|
def initialize(log_device=$stdout,error_device=$stderr)
|
83
85
|
super(log_device)
|
86
|
+
@stderr_logger = Logger.new(error_device)
|
87
|
+
|
84
88
|
@split_logs = log_device.tty? && error_device.tty?
|
89
|
+
|
85
90
|
self.level = Logger::Severity::INFO
|
86
|
-
@stderr_logger =
|
87
|
-
|
91
|
+
@stderr_logger.level = DEFAULT_ERROR_LEVEL
|
92
|
+
|
88
93
|
self.formatter = BLANK_FORMAT if log_device.tty?
|
89
94
|
@stderr_logger.formatter = BLANK_FORMAT if error_device.tty?
|
90
95
|
end
|
91
96
|
|
97
|
+
def level=(level)
|
98
|
+
super(level)
|
99
|
+
current_error_level = @stderr_logger.level
|
100
|
+
if (level > DEFAULT_ERROR_LEVEL) && @split_logs
|
101
|
+
@stderr_logger.level = level
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
92
105
|
# Set the threshold for what messages go to the error device. Note that calling
|
93
|
-
# #level= will *not* affect the error logger
|
106
|
+
# #level= will *not* affect the error logger *unless* both devices are TTYs.
|
94
107
|
#
|
95
108
|
# +level+:: a constant from Logger::Severity for the level of messages that should go
|
96
109
|
# to the error logger
|
@@ -23,6 +23,11 @@ module Methadone
|
|
23
23
|
# end
|
24
24
|
# end
|
25
25
|
module CLILogging
|
26
|
+
|
27
|
+
def self.included(k)
|
28
|
+
k.extend(self)
|
29
|
+
end
|
30
|
+
|
26
31
|
# Access the shared logger. All classes that include this module
|
27
32
|
# will get the same logger via this method.
|
28
33
|
def logger
|
@@ -40,6 +45,7 @@ module Methadone
|
|
40
45
|
def change_logger(new_logger)
|
41
46
|
raise ArgumentError,"Logger may not be nil" if new_logger.nil?
|
42
47
|
@@logger = new_logger
|
48
|
+
@@logger.level = @log_level if @log_level
|
43
49
|
end
|
44
50
|
|
45
51
|
alias logger= change_logger
|
@@ -55,5 +61,33 @@ module Methadone
|
|
55
61
|
def error(progname = nil, &block); logger.error(progname,&block); end
|
56
62
|
# pass-through to <tt>logger.fatal(progname,&block)</tt>
|
57
63
|
def fatal(progname = nil, &block); logger.fatal(progname,&block); end
|
64
|
+
|
65
|
+
LOG_LEVELS = {
|
66
|
+
'debug' => Logger::DEBUG,
|
67
|
+
'info' => Logger::INFO,
|
68
|
+
'warn' => Logger::WARN,
|
69
|
+
'error' => Logger::ERROR,
|
70
|
+
'fatal' => Logger::FATAL,
|
71
|
+
}
|
72
|
+
|
73
|
+
# Call this *if* you've included Methadone::Main to set up a <tt>--log-level</tt> option for your app
|
74
|
+
# that will allow the user to configure the logging level.
|
75
|
+
#
|
76
|
+
# Example:
|
77
|
+
#
|
78
|
+
# main do
|
79
|
+
# # your app
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# use_log_level_option
|
83
|
+
#
|
84
|
+
# go!
|
85
|
+
#
|
86
|
+
def use_log_level_option
|
87
|
+
on("--log-level LEVEL",LOG_LEVELS,"Set the logging level (#{LOG_LEVELS.keys.join('|')})","(Default: info)") do |level|
|
88
|
+
@log_level = level
|
89
|
+
logger.level = level
|
90
|
+
end
|
91
|
+
end
|
58
92
|
end
|
59
93
|
end
|
data/lib/methadone/cucumber.rb
CHANGED
@@ -1,3 +1,52 @@
|
|
1
|
+
module Methadone
|
2
|
+
# By <tt>require</tt>'ing <tt>methadone/cucumber</tt> in your Cucumber setup (e.g. in <tt>env.rb</tt>), you
|
3
|
+
# gain access to the steps defined in this file. They provide you with the following:
|
4
|
+
#
|
5
|
+
# * Run <tt>command_to_run --help</tt> using aruba
|
6
|
+
#
|
7
|
+
# When I get help for "command_to_run"
|
8
|
+
#
|
9
|
+
# * Make sure that each option shows up in the help and has *some* sort of documentation
|
10
|
+
#
|
11
|
+
# Then the following options should be documented:
|
12
|
+
# |--force|
|
13
|
+
# |-x |
|
14
|
+
#
|
15
|
+
# * Check an individual option for documentation:
|
16
|
+
#
|
17
|
+
# Then the option "--force" should be documented
|
18
|
+
#
|
19
|
+
# * Checks that the help has a proper usage banner
|
20
|
+
#
|
21
|
+
# Then the banner should be present
|
22
|
+
#
|
23
|
+
# * Checks that the banner includes the version
|
24
|
+
#
|
25
|
+
# Then the banner should include the version
|
26
|
+
#
|
27
|
+
# * Checks that the usage banner indicates it takes options via <tt>[options]</tt>
|
28
|
+
#
|
29
|
+
# Then the banner should document that this app takes options
|
30
|
+
#
|
31
|
+
# * Do the opposite; check that you don't indicate options are accepted
|
32
|
+
#
|
33
|
+
# Then the banner should document that this app takes no options
|
34
|
+
#
|
35
|
+
# * Checks that the app's usage banner documents that its arguments are <tt>args</tt>
|
36
|
+
#
|
37
|
+
# Then the banner should document that this app's arguments are "args"
|
38
|
+
#
|
39
|
+
# * Do the opposite; check that your app doesn't take any arguments
|
40
|
+
#
|
41
|
+
# Then the banner should document that this app takes no arguments
|
42
|
+
#
|
43
|
+
# * Check for a usage description which occurs after the banner and a blank line
|
44
|
+
#
|
45
|
+
# Then there should be a one-line summary of what the app does
|
46
|
+
#
|
47
|
+
module Cucumber
|
48
|
+
end
|
49
|
+
end
|
1
50
|
When /^I get help for "([^"]*)"$/ do |app_name|
|
2
51
|
@app_name = app_name
|
3
52
|
step %(I run `#{app_name} --help`)
|
@@ -1,10 +1,4 @@
|
|
1
1
|
module Methadone
|
2
|
-
# Module to contain ExecutionStrategy implementations.
|
3
|
-
# To build your own simply implement two methods:
|
4
|
-
#
|
5
|
-
# <tt>exception_meaning_command_not_found</tt>:: return the class that, if caught, means that the underlying command
|
6
|
-
# couldn't be found. This is needed because currently impelmentations
|
7
|
-
# throw an exception, but they don't all throw the same one.
|
8
2
|
module ExecutionStrategy
|
9
3
|
# Base for any ExecutionStrategy implementation. Currently, this is nothing more than an interface
|
10
4
|
# specification.
|
data/lib/methadone/main.rb
CHANGED
@@ -14,31 +14,37 @@ module Methadone
|
|
14
14
|
# in a sensible way. You can use as much or as little as you want, though
|
15
15
|
# you must at least use #main to get any benefits.
|
16
16
|
#
|
17
|
+
# Further, you must provide access to a logger via a method named
|
18
|
+
# #logger. If you include Methadone::CLILogging, this will be done for you
|
19
|
+
#
|
17
20
|
# You also get a more expedient interface to OptionParser as well
|
18
21
|
# as checking for required arguments to your app. For example, if
|
19
22
|
# we want our app to accept a negatable switch named "switch", a flag
|
20
23
|
# named "flag", and two arguments "needed" (which is required)
|
21
24
|
# and "maybe" which is optional, we can do the following:
|
22
25
|
#
|
23
|
-
# #!/usr/bin/env ruby
|
26
|
+
# #!/usr/bin/env ruby
|
24
27
|
#
|
25
28
|
# require 'methadone'
|
26
29
|
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
30
|
+
# class App
|
31
|
+
# include Methadone::Main
|
32
|
+
# include Methadone::CLILogging
|
33
|
+
#
|
34
|
+
# main do |needed, maybe|
|
35
|
+
# options[:switch] => true or false, based on command line
|
36
|
+
# options[:flag] => value of flag passed on command line
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# # Proxy to an OptionParser instance's on method
|
40
|
+
# on("--[no]-switch")
|
41
|
+
# on("--flag VALUE")
|
37
42
|
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
43
|
+
# arg :needed
|
44
|
+
# arg :maybe, :optional
|
45
|
+
#
|
46
|
+
# go!
|
47
|
+
# end
|
42
48
|
#
|
43
49
|
# Our app then acts as follows:
|
44
50
|
#
|
@@ -47,9 +53,18 @@ module Methadone
|
|
47
53
|
# $ our_app foo
|
48
54
|
# # => succeeds; "maybe" in main is nil
|
49
55
|
#
|
50
|
-
#
|
56
|
+
# Note that we've done all of this inside a class that we called +App+. This isn't strictly
|
57
|
+
# necessary, and you can just +include+ Methadone::Main and Methadone::CLILogging at the root
|
58
|
+
# of your +bin+ file if you like. This is somewhat unsafe, because +self+ inside the +bin+
|
59
|
+
# file is Object, and any methods you create (or cause to be created via +include+) will be
|
60
|
+
# present on *every* object. This can cause odd problems, so it's recommended that you
|
61
|
+
# *not* do this.
|
62
|
+
#
|
51
63
|
module Main
|
52
|
-
|
64
|
+
def self.included(k)
|
65
|
+
k.extend(self)
|
66
|
+
end
|
67
|
+
|
53
68
|
# Declare the main method for your app.
|
54
69
|
# This allows you to specify the general logic of your
|
55
70
|
# app at the top of your bin file, but can rely on any methods
|
@@ -97,6 +112,14 @@ module Methadone
|
|
97
112
|
@leak_exceptions = leak
|
98
113
|
end
|
99
114
|
|
115
|
+
# Set the name of the environment variable where users can place default
|
116
|
+
# options for your app. Omit this to disable the feature.
|
117
|
+
def defaults_from_env_var(env_var)
|
118
|
+
@env_var = env_var
|
119
|
+
opts.separator ''
|
120
|
+
opts.separator "Default values can be placed in the #{env_var} environment variable"
|
121
|
+
opts.separator ''
|
122
|
+
end
|
100
123
|
|
101
124
|
# Start your command-line app, exiting appropriately when
|
102
125
|
# complete.
|
@@ -113,6 +136,11 @@ module Methadone
|
|
113
136
|
#
|
114
137
|
def go!
|
115
138
|
normalize_defaults
|
139
|
+
if @env_var
|
140
|
+
String(ENV[@env_var]).split(/\s+/).each do |arg|
|
141
|
+
::ARGV.unshift(arg)
|
142
|
+
end
|
143
|
+
end
|
116
144
|
opts.parse!
|
117
145
|
opts.check_args!
|
118
146
|
result = call_main
|
@@ -122,7 +150,7 @@ module Methadone
|
|
122
150
|
exit 0
|
123
151
|
end
|
124
152
|
rescue OptionParser::ParseError => ex
|
125
|
-
error ex.message
|
153
|
+
logger.error ex.message
|
126
154
|
puts
|
127
155
|
puts opts.help
|
128
156
|
exit 64 # Linux standard for bad command line
|
@@ -253,12 +281,12 @@ module Methadone
|
|
253
281
|
@main_block.call(*ARGV)
|
254
282
|
rescue Methadone::Error => ex
|
255
283
|
raise ex if ENV['DEBUG']
|
256
|
-
error ex.message unless no_message? ex
|
284
|
+
logger.error ex.message unless no_message? ex
|
257
285
|
ex.exit_code
|
258
286
|
rescue => ex
|
259
287
|
raise ex if ENV['DEBUG']
|
260
288
|
raise ex if @leak_exceptions
|
261
|
-
error ex.message unless no_message? ex
|
289
|
+
logger.error ex.message unless no_message? ex
|
262
290
|
70 # Linux sysexit code for internal software error
|
263
291
|
end
|
264
292
|
|
data/lib/methadone/sh.rb
CHANGED
@@ -11,15 +11,51 @@ module Methadone
|
|
11
11
|
# Module with various helper methods for executing external commands.
|
12
12
|
# In most cases, you can use #sh to run commands and have decent logging
|
13
13
|
# done. You will likely use this in a class that also mixes-in
|
14
|
-
# Methadone::CLILogging
|
15
|
-
# via #set_sh_logger.
|
14
|
+
# Methadone::CLILogging (remembering that Methadone::Main mixes this in for you).
|
15
|
+
# If you <b>don't</b>, you must provide a logger via #set_sh_logger.
|
16
|
+
#
|
17
|
+
# == Examples
|
18
|
+
#
|
19
|
+
# include Methadone::SH
|
20
|
+
#
|
21
|
+
# sh 'cp foo.txt /tmp'
|
22
|
+
# # => logs the command to DEBUG, executes the command, logs its output to DEBUG and its
|
23
|
+
# # error output to WARN, returns 0
|
24
|
+
#
|
25
|
+
# sh 'cp non_existent_file.txt /nowhere_good'
|
26
|
+
# # => logs the command to DEBUG, executes the command, logs its output to INFO and
|
27
|
+
# # its error output to WARN, returns the nonzero exit status of the underlying command
|
28
|
+
#
|
29
|
+
# sh! 'cp non_existent_file.txt /nowhere_good'
|
30
|
+
# # => same as above, EXCEPT, raises a Methadone::FailedCommandError
|
31
|
+
#
|
32
|
+
# sh 'cp foo.txt /tmp' do
|
33
|
+
# # Behaves exactly as before, but this block is called after
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# sh 'cp non_existent_file.txt /nowhere_good' do
|
37
|
+
# # This block isn't called, since the command failed
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# sh 'ls -l /tmp/' do |stdout|
|
41
|
+
# # stdout contains the output of the command
|
42
|
+
# end
|
43
|
+
# sh 'ls -l /tmp/ /non_existent_dir' do |stdout,stderr|
|
44
|
+
# # stdout contains the output of the command,
|
45
|
+
# # stderr contains the standard error output.
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# == Handling remote execution
|
16
49
|
#
|
17
50
|
# In order to work on as many Rubies as possible, this class defers the actual execution
|
18
51
|
# to an execution strategy. See #set_execution_strategy if you think you'd like to override
|
19
52
|
# that, or just want to know how it works.
|
20
53
|
#
|
21
|
-
#
|
22
|
-
#
|
54
|
+
# == More complex execution and subprocess management
|
55
|
+
#
|
56
|
+
# This is not intended to be a complete replacement for Open3 or an enhanced means of managing subprocesses.
|
57
|
+
# This is to make it easy for you to shell-out to external commands and have your app be robust and
|
58
|
+
# easy to maintain.
|
23
59
|
module SH
|
24
60
|
# Run a shell command, capturing and logging its output.
|
25
61
|
# If the command completed successfully, it's output is logged at DEBUG.
|
data/lib/methadone/version.rb
CHANGED
@@ -4,35 +4,40 @@ require 'optparse'
|
|
4
4
|
require 'methadone'
|
5
5
|
require '<%= gemname %>'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
|
7
|
+
class App
|
8
|
+
include Methadone::Main
|
9
|
+
include Methadone::CLILogging
|
10
|
+
|
11
|
+
main do # Add args you want: |like,so|
|
12
|
+
# your program code here
|
13
|
+
# You can access CLI options via
|
14
|
+
# the options Hash
|
15
|
+
end
|
16
|
+
|
17
|
+
# supplemental methods here
|
18
|
+
|
19
|
+
# Declare command-line interface here
|
20
|
+
|
21
|
+
# description "one line description of your app"
|
22
|
+
#
|
23
|
+
# Accept flags via:
|
24
|
+
# on("--flag VAL","Some flag")
|
25
|
+
# options[flag] will contain VAL
|
26
|
+
#
|
27
|
+
# Specify switches via:
|
28
|
+
# on("--[no-]switch","Some switch")
|
29
|
+
#
|
30
|
+
# Or, just call OptionParser methods on opts
|
31
|
+
#
|
32
|
+
# Require an argument
|
33
|
+
# arg :some_arg
|
34
|
+
#
|
35
|
+
# # Make an argument optional
|
36
|
+
# arg :optional_arg, :optional
|
37
|
+
|
38
|
+
version <%= module_name %>::VERSION
|
39
|
+
|
40
|
+
use_log_level_option
|
41
|
+
|
42
|
+
go!
|
13
43
|
end
|
14
|
-
|
15
|
-
# supplemental methods here
|
16
|
-
|
17
|
-
# Declare command-line interface here
|
18
|
-
|
19
|
-
# description "one line description of your app"
|
20
|
-
#
|
21
|
-
# Accept flags via:
|
22
|
-
# on("--flag VAL","Some flag")
|
23
|
-
# options[flag] will contain VAL
|
24
|
-
#
|
25
|
-
# Specify switches via:
|
26
|
-
# on("--[no-]switch","Some switch")
|
27
|
-
#
|
28
|
-
# Or, just call OptionParser methods on opts
|
29
|
-
#
|
30
|
-
# Require an argument
|
31
|
-
# arg :some_arg
|
32
|
-
#
|
33
|
-
# # Make an argument optional
|
34
|
-
# arg :optional_arg, :optional
|
35
|
-
|
36
|
-
version <%= module_name %>::VERSION
|
37
|
-
|
38
|
-
go!
|
data/test/test_cli_logger.rb
CHANGED
@@ -41,6 +41,27 @@ class TestCLILogger < BaseTest
|
|
41
41
|
}
|
42
42
|
end
|
43
43
|
|
44
|
+
test_that "when both stderr and stdin are ttys, setting the level higher than WARN should affect the error logger" do
|
45
|
+
Given {
|
46
|
+
class << $stderr
|
47
|
+
def tty?; true; end
|
48
|
+
end
|
49
|
+
class << $stdout
|
50
|
+
def tty?; true; end
|
51
|
+
end
|
52
|
+
|
53
|
+
@logger = CLILogger.new
|
54
|
+
@logger.level = Logger::ERROR
|
55
|
+
}
|
56
|
+
|
57
|
+
When log_all_levels
|
58
|
+
|
59
|
+
Then {
|
60
|
+
$stdout.string.should == ""
|
61
|
+
$stderr.string.should == "error\nfatal\n"
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
44
65
|
test_that "logger sends debug and info to stdout, and warns, errors, and fatals to stderr" do
|
45
66
|
Given a_logger_with_blank_format
|
46
67
|
When log_all_levels
|
data/test/test_cli_logging.rb
CHANGED
@@ -92,6 +92,56 @@ class TestCLILogging < BaseTest
|
|
92
92
|
}
|
93
93
|
end
|
94
94
|
|
95
|
+
test_that "when we call use_log_level_option, it sets up logging level CLI options" do
|
96
|
+
Given {
|
97
|
+
@app = MyAppThatActsLikeItUsesMain.new
|
98
|
+
@app.call_use_log_level_option
|
99
|
+
@level = any_int
|
100
|
+
}
|
101
|
+
When {
|
102
|
+
@app.use_option(@level)
|
103
|
+
}
|
104
|
+
Then {
|
105
|
+
@app.logger.level.should == @level
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
test_that "when we call use_log_level_option, then later change the logger, that logger gets the proper level set" do
|
110
|
+
Given {
|
111
|
+
@app = MyAppThatActsLikeItUsesMain.new
|
112
|
+
@app.call_use_log_level_option
|
113
|
+
@level = any_int
|
114
|
+
}
|
115
|
+
When {
|
116
|
+
@app.use_option(@level)
|
117
|
+
@other_logger = OpenStruct.new
|
118
|
+
@app.change_logger(@other_logger)
|
119
|
+
}
|
120
|
+
Then {
|
121
|
+
@other_logger.level.should == @level
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
class MyAppThatActsLikeItUsesMain
|
126
|
+
include Methadone::CLILogging
|
127
|
+
|
128
|
+
def call_use_log_level_option
|
129
|
+
use_log_level_option
|
130
|
+
end
|
131
|
+
|
132
|
+
def use_option(level)
|
133
|
+
@block.call(level)
|
134
|
+
end
|
135
|
+
|
136
|
+
def on(*args,&block)
|
137
|
+
@block = block
|
138
|
+
end
|
139
|
+
|
140
|
+
def logger
|
141
|
+
@logger ||= OpenStruct.new
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
95
145
|
class MyClassThatLogsToStdout
|
96
146
|
include Methadone::CLILogging
|
97
147
|
|
data/test/test_main.rb
CHANGED
@@ -6,21 +6,23 @@ class TestMain < BaseTest
|
|
6
6
|
include Methadone::Main
|
7
7
|
|
8
8
|
def setup
|
9
|
-
@logged = []
|
10
9
|
@original_argv = ARGV.clone
|
11
10
|
ARGV.clear
|
12
11
|
@old_stdout = $stdout
|
13
12
|
$stdout = StringIO.new
|
13
|
+
@logged = StringIO.new
|
14
|
+
@custom_logger = Logger.new(@logged)
|
14
15
|
end
|
15
16
|
|
16
|
-
# Override
|
17
|
-
def
|
18
|
-
@
|
17
|
+
# Override the built-in logger so we can capture it
|
18
|
+
def logger
|
19
|
+
@custom_logger
|
19
20
|
end
|
20
21
|
|
21
22
|
def teardown
|
22
23
|
set_argv @original_argv
|
23
24
|
ENV.delete('DEBUG')
|
25
|
+
ENV.delete('APP_OPTS')
|
24
26
|
$stdout = @old_stdout
|
25
27
|
end
|
26
28
|
|
@@ -29,11 +31,11 @@ class TestMain < BaseTest
|
|
29
31
|
@called = false
|
30
32
|
main do
|
31
33
|
begin
|
32
|
-
debug "debug"
|
33
|
-
info "info"
|
34
|
-
warn "warn"
|
35
|
-
error "error"
|
36
|
-
fatal "fatal"
|
34
|
+
logger.debug "debug"
|
35
|
+
logger.info "info"
|
36
|
+
logger.warn "warn"
|
37
|
+
logger.error "error"
|
38
|
+
logger.fatal "fatal"
|
37
39
|
@called = true
|
38
40
|
rescue => ex
|
39
41
|
puts ex.message
|
@@ -106,10 +108,6 @@ class TestMain < BaseTest
|
|
106
108
|
end
|
107
109
|
end
|
108
110
|
|
109
|
-
def main_that_exits(exit_status)
|
110
|
-
proc { main { exit_status } }
|
111
|
-
end
|
112
|
-
|
113
111
|
test_that "go exits with 70, which is the Linux sysexits.h code for this sort of thing, if there's an exception" do
|
114
112
|
Given {
|
115
113
|
main do
|
@@ -497,7 +495,75 @@ class TestMain < BaseTest
|
|
497
495
|
}
|
498
496
|
end
|
499
497
|
|
500
|
-
|
498
|
+
test_that "when getting defaults from an environment variable, show it in the help output" do
|
499
|
+
Given app_to_use_environment
|
500
|
+
When {
|
501
|
+
@help_string = opts.to_s
|
502
|
+
}
|
503
|
+
Then {
|
504
|
+
@help_string.should match /Default values can be placed in the APP_OPTS environment variable/
|
505
|
+
}
|
506
|
+
end
|
507
|
+
|
508
|
+
test_that "when we want to get opts from the environment, we can" do
|
509
|
+
Given app_to_use_environment
|
510
|
+
And {
|
511
|
+
@flag_value = '56'
|
512
|
+
@some_arg = any_string
|
513
|
+
set_argv([])
|
514
|
+
ENV['APP_OPTS'] = "--switch --flag=#{@flag_value} #{@some_arg}"
|
515
|
+
}
|
516
|
+
When {
|
517
|
+
@code = lambda { go! }
|
518
|
+
}
|
519
|
+
Then {
|
520
|
+
assert_exits(0,'',&@code)
|
521
|
+
@switch.should == true
|
522
|
+
@flag.should == @flag_value
|
523
|
+
@args.should == [@some_arg]
|
524
|
+
}
|
525
|
+
end
|
526
|
+
|
527
|
+
test_that "environment args are overridden by the command line" do
|
528
|
+
Given app_to_use_environment
|
529
|
+
And {
|
530
|
+
@flag_value = any_string
|
531
|
+
ENV['APP_OPTS'] = "--switch --flag=#{any_string}"
|
532
|
+
set_argv(['--flag',@flag_value])
|
533
|
+
}
|
534
|
+
When {
|
535
|
+
@code = lambda { go! }
|
536
|
+
}
|
537
|
+
Then {
|
538
|
+
assert_exits(0,'',&@code)
|
539
|
+
@switch.should == true
|
540
|
+
@flag.should == @flag_value
|
541
|
+
}
|
542
|
+
end
|
543
|
+
|
544
|
+
private
|
545
|
+
|
546
|
+
def main_that_exits(exit_status)
|
547
|
+
proc { main { exit_status } }
|
548
|
+
end
|
549
|
+
|
550
|
+
def app_to_use_environment
|
551
|
+
lambda {
|
552
|
+
@switch = nil
|
553
|
+
@flag = nil
|
554
|
+
@args = nil
|
555
|
+
main do |*args|
|
556
|
+
@switch = options[:switch]
|
557
|
+
@flag = options[:flag]
|
558
|
+
@args = args
|
559
|
+
end
|
560
|
+
|
561
|
+
defaults_from_env_var 'APP_OPTS'
|
562
|
+
|
563
|
+
on('--switch','Some Switch')
|
564
|
+
on('--flag FOO','Some Flag')
|
565
|
+
}
|
566
|
+
end
|
501
567
|
|
502
568
|
def main_shouldve_been_called
|
503
569
|
Proc.new { assert @called,"Main block wasn't called?!" }
|
@@ -516,7 +582,7 @@ class TestMain < BaseTest
|
|
516
582
|
def run_go!; proc { go! }; end
|
517
583
|
|
518
584
|
def assert_logged_at_error(expected_message)
|
519
|
-
@logged.should include expected_message
|
585
|
+
@logged.string.should include expected_message
|
520
586
|
end
|
521
587
|
|
522
588
|
def assert_exits(exit_code,message='',&block)
|
metadata
CHANGED
@@ -1,167 +1,126 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: methadone
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.rc2
|
5
5
|
prerelease: 6
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 0
|
9
|
-
- 0
|
10
|
-
- rc
|
11
|
-
- 1
|
12
|
-
version: 1.0.0.rc1
|
13
6
|
platform: ruby
|
14
|
-
authors:
|
7
|
+
authors:
|
15
8
|
- davetron5000
|
16
9
|
autorequire:
|
17
10
|
bindir: bin
|
18
11
|
cert_chain: []
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
dependencies:
|
23
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-02-04 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
24
15
|
name: bundler
|
25
|
-
|
26
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &70291259020440 !ruby/object:Gem::Requirement
|
27
17
|
none: false
|
28
|
-
requirements:
|
29
|
-
- -
|
30
|
-
- !ruby/object:Gem::Version
|
31
|
-
|
32
|
-
segments:
|
33
|
-
- 0
|
34
|
-
version: "0"
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
35
22
|
type: :runtime
|
36
|
-
version_requirements: *id001
|
37
|
-
- !ruby/object:Gem::Dependency
|
38
|
-
name: rspec-expectations
|
39
23
|
prerelease: false
|
40
|
-
|
24
|
+
version_requirements: *70291259020440
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec-expectations
|
27
|
+
requirement: &70291259018880 !ruby/object:Gem::Requirement
|
41
28
|
none: false
|
42
|
-
requirements:
|
29
|
+
requirements:
|
43
30
|
- - ~>
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
|
46
|
-
segments:
|
47
|
-
- 2
|
48
|
-
- 6
|
49
|
-
version: "2.6"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.6'
|
50
33
|
type: :development
|
51
|
-
version_requirements: *id002
|
52
|
-
- !ruby/object:Gem::Dependency
|
53
|
-
name: rake
|
54
34
|
prerelease: false
|
55
|
-
|
35
|
+
version_requirements: *70291259018880
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70291259018280 !ruby/object:Gem::Requirement
|
56
39
|
none: false
|
57
|
-
requirements:
|
58
|
-
- -
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
|
61
|
-
segments:
|
62
|
-
- 0
|
63
|
-
version: "0"
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
64
44
|
type: :development
|
65
|
-
version_requirements: *id003
|
66
|
-
- !ruby/object:Gem::Dependency
|
67
|
-
name: rdoc
|
68
45
|
prerelease: false
|
69
|
-
|
46
|
+
version_requirements: *70291259018280
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rdoc
|
49
|
+
requirement: &70291259017480 !ruby/object:Gem::Requirement
|
70
50
|
none: false
|
71
|
-
requirements:
|
51
|
+
requirements:
|
72
52
|
- - ~>
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
|
75
|
-
segments:
|
76
|
-
- 3
|
77
|
-
- 9
|
78
|
-
version: "3.9"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.9'
|
79
55
|
type: :development
|
80
|
-
version_requirements: *id004
|
81
|
-
- !ruby/object:Gem::Dependency
|
82
|
-
name: cucumber
|
83
56
|
prerelease: false
|
84
|
-
|
57
|
+
version_requirements: *70291259017480
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: cucumber
|
60
|
+
requirement: &70291259016720 !ruby/object:Gem::Requirement
|
85
61
|
none: false
|
86
|
-
requirements:
|
62
|
+
requirements:
|
87
63
|
- - ~>
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
hash: 17
|
90
|
-
segments:
|
91
|
-
- 1
|
92
|
-
- 1
|
93
|
-
- 1
|
64
|
+
- !ruby/object:Gem::Version
|
94
65
|
version: 1.1.1
|
95
66
|
type: :development
|
96
|
-
version_requirements: *id005
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: aruba
|
99
67
|
prerelease: false
|
100
|
-
|
68
|
+
version_requirements: *70291259016720
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: aruba
|
71
|
+
requirement: &70291259016160 !ruby/object:Gem::Requirement
|
101
72
|
none: false
|
102
|
-
requirements:
|
103
|
-
- -
|
104
|
-
- !ruby/object:Gem::Version
|
105
|
-
|
106
|
-
segments:
|
107
|
-
- 0
|
108
|
-
version: "0"
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
109
77
|
type: :development
|
110
|
-
version_requirements: *id006
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: simplecov
|
113
78
|
prerelease: false
|
114
|
-
|
79
|
+
version_requirements: *70291259016160
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: simplecov
|
82
|
+
requirement: &70291259015380 !ruby/object:Gem::Requirement
|
115
83
|
none: false
|
116
|
-
requirements:
|
84
|
+
requirements:
|
117
85
|
- - ~>
|
118
|
-
- !ruby/object:Gem::Version
|
119
|
-
|
120
|
-
segments:
|
121
|
-
- 0
|
122
|
-
- 5
|
123
|
-
version: "0.5"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0.5'
|
124
88
|
type: :development
|
125
|
-
version_requirements: *id007
|
126
|
-
- !ruby/object:Gem::Dependency
|
127
|
-
name: clean_test
|
128
89
|
prerelease: false
|
129
|
-
|
90
|
+
version_requirements: *70291259015380
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: clean_test
|
93
|
+
requirement: &70291259014700 !ruby/object:Gem::Requirement
|
130
94
|
none: false
|
131
|
-
requirements:
|
95
|
+
requirements:
|
132
96
|
- - ~>
|
133
|
-
- !ruby/object:Gem::Version
|
134
|
-
|
135
|
-
segments:
|
136
|
-
- 0
|
137
|
-
- 10
|
138
|
-
version: "0.10"
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0.10'
|
139
99
|
type: :development
|
140
|
-
version_requirements: *id008
|
141
|
-
- !ruby/object:Gem::Dependency
|
142
|
-
name: mocha
|
143
100
|
prerelease: false
|
144
|
-
|
101
|
+
version_requirements: *70291259014700
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: mocha
|
104
|
+
requirement: &70291259014060 !ruby/object:Gem::Requirement
|
145
105
|
none: false
|
146
|
-
requirements:
|
147
|
-
- -
|
148
|
-
- !ruby/object:Gem::Version
|
149
|
-
|
150
|
-
segments:
|
151
|
-
- 0
|
152
|
-
version: "0"
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
153
110
|
type: :development
|
154
|
-
|
155
|
-
|
156
|
-
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *70291259014060
|
113
|
+
description: Methadone provides a lot of small but useful features for developing
|
114
|
+
a command-line app, including an opinionated bootstrapping process, some helpful
|
115
|
+
cucumber steps, and some classes to bridge logging and output into a simple, unified,
|
116
|
+
interface
|
117
|
+
email:
|
157
118
|
- davetron5000 at gmail.com
|
158
|
-
executables:
|
119
|
+
executables:
|
159
120
|
- methadone
|
160
121
|
extensions: []
|
161
|
-
|
162
122
|
extra_rdoc_files: []
|
163
|
-
|
164
|
-
files:
|
123
|
+
files:
|
165
124
|
- .gitignore
|
166
125
|
- .rvmrc
|
167
126
|
- .travis.yml
|
@@ -218,43 +177,31 @@ files:
|
|
218
177
|
- test/test_cli_logging.rb
|
219
178
|
- test/test_main.rb
|
220
179
|
- test/test_sh.rb
|
221
|
-
has_rdoc: true
|
222
180
|
homepage: http://github.com/davetron5000/methadone
|
223
181
|
licenses: []
|
224
|
-
|
225
182
|
post_install_message:
|
226
183
|
rdoc_options: []
|
227
|
-
|
228
|
-
require_paths:
|
184
|
+
require_paths:
|
229
185
|
- lib
|
230
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
231
187
|
none: false
|
232
|
-
requirements:
|
233
|
-
- -
|
234
|
-
- !ruby/object:Gem::Version
|
235
|
-
|
236
|
-
|
237
|
-
- 0
|
238
|
-
version: "0"
|
239
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
188
|
+
requirements:
|
189
|
+
- - ! '>='
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
version: '0'
|
192
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
240
193
|
none: false
|
241
|
-
requirements:
|
242
|
-
- -
|
243
|
-
- !ruby/object:Gem::Version
|
244
|
-
hash: 25
|
245
|
-
segments:
|
246
|
-
- 1
|
247
|
-
- 3
|
248
|
-
- 1
|
194
|
+
requirements:
|
195
|
+
- - ! '>'
|
196
|
+
- !ruby/object:Gem::Version
|
249
197
|
version: 1.3.1
|
250
198
|
requirements: []
|
251
|
-
|
252
199
|
rubyforge_project: methadone
|
253
|
-
rubygems_version: 1.
|
200
|
+
rubygems_version: 1.8.10
|
254
201
|
signing_key:
|
255
202
|
specification_version: 3
|
256
203
|
summary: Kick the bash habit and start your command-line apps off right
|
257
|
-
test_files:
|
204
|
+
test_files:
|
258
205
|
- features/bootstrap.feature
|
259
206
|
- features/license.feature
|
260
207
|
- features/readme.feature
|