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 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 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.
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
- Currently, this library is under development and has the following to offer:
11
+ Toward that end, this gem provides:
12
12
 
13
- * Bootstrapping a new CLI app
14
- * Lightweight DSL to structure your bin file
15
- * Simple wrapper for running external commands with good logging
16
- * Utility Classes
17
- * Methadone::CLILogger - a logger subclass that sends messages to standard error standard out as appropriate
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 aruba; aruba just doesn't work on JRuby for some reason, though this library *is* being used in production under JRuby 1.6.6
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
- == DSL for your `bin` file
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
- A canonical `OptionParser` driven app has a few problems with it structurally that methadone can solve
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
- +go!+ runs the block you gave to +main+, passing it the unparsed +ARGV+ as parameters to the block. It will
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
- include Methadone::SH
138
-
139
- sh 'cp foo.txt /tmp'
140
- # => logs the command to DEBUG, executes the command, logs its output to DEBUG and its
141
- # error output to WARN, returns 0
142
-
143
- sh 'cp non_existent_file.txt /nowhere_good'
144
- # => logs the command to DEBUG, executes thecommand, logs its output to INFO and
145
- # its error output to WARN, returns the nonzero exit status of the underlying command
146
-
147
- sh! 'cp non_existent_file.txt /nowhere_good'
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
- sh 'ls -l /tmp/' do |stdout|
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
- == Utility Classes
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
- === Examples
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
- ==== Using STDOUT as a log, respecting STDERR
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
- require 'methadone'
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
- include Methadone::CLILogging
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
- ==== Using a log file, but respecting STDERR
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 methadone provides additional, more opinionated, steps.
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
- === Steps Provided
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
- See {the roadmap}[https://github.com/davetron5000/methadone/wiki/Roadmap]
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
- if license
36
- if license == 'NONE'
37
- license = nil
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
 
@@ -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:
@@ -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
- # #info as a replacement for +puts+ and #error as a replacement
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
- # * #debug messages do not appear anywhere
13
- # * #info messages appear on the standard output
14
- # * #warn, #error, and #fata messagse appear on the standard error
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 = Logger.new(error_device)
87
- @stderr_logger.level = Logger::Severity::WARN
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
@@ -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.
@@ -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 -w
26
+ # #!/usr/bin/env ruby
24
27
  #
25
28
  # require 'methadone'
26
29
  #
27
- # include Methadone::Main
28
- #
29
- # main do |needed, maybe|
30
- # options[:switch] => true or false, based on command line
31
- # options[:flag] => value of flag passed on command line
32
- # end
33
- #
34
- # # Proxy to an OptionParser instance's on method
35
- # on("--[no]-switch")
36
- # on("--flag VALUE")
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
- # arg :needed
39
- # arg :maybe, :optional
40
- #
41
- # go!
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
- # This also includes Methadone::CLILogging to give you access to simple logging
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
- include Methadone::CLILogging
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. If you *don't*, you must provide a logger
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
- # This is not intended to be a complete replacement for Open3, but instead of make common cases
22
- # and good practice easy to accomplish.
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.
@@ -1,3 +1,3 @@
1
1
  module Methadone
2
- VERSION = "1.0.0.rc1"
2
+ VERSION = "1.0.0.rc2"
3
3
  end
@@ -4,35 +4,40 @@ require 'optparse'
4
4
  require 'methadone'
5
5
  require '<%= gemname %>'
6
6
 
7
- include Methadone::Main
8
-
9
- main do # Add args you want: |like,so|
10
- # your program code here
11
- # You can access CLI options via
12
- # the options Hash
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!
@@ -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
@@ -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 error so we can capture what's being logged at this level
17
- def error(string)
18
- @logged << string
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
- private
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
- hash: 1276957477555352091
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
- date: 2012-01-22 00:00:00 -05:00
21
- default_executable:
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
- prerelease: false
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
- hash: 3
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
- requirement: &id002 !ruby/object:Gem::Requirement
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
- hash: 15
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
- requirement: &id003 !ruby/object:Gem::Requirement
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
- hash: 3
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
- requirement: &id004 !ruby/object:Gem::Requirement
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
- hash: 21
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
- requirement: &id005 !ruby/object:Gem::Requirement
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
- requirement: &id006 !ruby/object:Gem::Requirement
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
- hash: 3
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
- requirement: &id007 !ruby/object:Gem::Requirement
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
- hash: 1
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
- requirement: &id008 !ruby/object:Gem::Requirement
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
- hash: 31
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
- requirement: &id009 !ruby/object:Gem::Requirement
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
- hash: 3
150
- segments:
151
- - 0
152
- version: "0"
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
153
110
  type: :development
154
- version_requirements: *id009
155
- description: Methadone provides a lot of small but useful features for developing a command-line app, including an opinionated bootstrapping process, some helpful cucumber steps, and some classes to bridge logging and output into a simple, unified, interface
156
- email:
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
- hash: 3
236
- segments:
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.5.2
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