methadone-rehab 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CHANGES.md +66 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +201 -0
- data/README.rdoc +179 -0
- data/Rakefile +98 -0
- data/TODO.md +3 -0
- data/bin/methadone +157 -0
- data/features/bootstrap.feature +169 -0
- data/features/license.feature +43 -0
- data/features/multilevel_commands.feature +125 -0
- data/features/readme.feature +26 -0
- data/features/rspec_support.feature +27 -0
- data/features/step_definitions/bootstrap_steps.rb +47 -0
- data/features/step_definitions/license_steps.rb +30 -0
- data/features/step_definitions/readme_steps.rb +26 -0
- data/features/step_definitions/version_steps.rb +4 -0
- data/features/support/env.rb +26 -0
- data/features/version.feature +17 -0
- data/lib/methadone.rb +15 -0
- data/lib/methadone/argv_parser.rb +50 -0
- data/lib/methadone/cli.rb +124 -0
- data/lib/methadone/cli_logger.rb +133 -0
- data/lib/methadone/cli_logging.rb +138 -0
- data/lib/methadone/cucumber.rb +174 -0
- data/lib/methadone/error.rb +32 -0
- data/lib/methadone/execution_strategy/base.rb +34 -0
- data/lib/methadone/execution_strategy/jvm.rb +37 -0
- data/lib/methadone/execution_strategy/mri.rb +16 -0
- data/lib/methadone/execution_strategy/open_3.rb +16 -0
- data/lib/methadone/execution_strategy/open_4.rb +22 -0
- data/lib/methadone/execution_strategy/rbx_open_4.rb +12 -0
- data/lib/methadone/exit_now.rb +40 -0
- data/lib/methadone/main.rb +1039 -0
- data/lib/methadone/process_status.rb +45 -0
- data/lib/methadone/sh.rb +223 -0
- data/lib/methadone/version.rb +3 -0
- data/methadone-rehab.gemspec +32 -0
- data/templates/full/.gitignore.erb +4 -0
- data/templates/full/README.rdoc.erb +25 -0
- data/templates/full/Rakefile.erb +74 -0
- data/templates/full/_license_head.txt.erb +2 -0
- data/templates/full/apache_LICENSE.txt.erb +203 -0
- data/templates/full/bin/executable.erb +47 -0
- data/templates/full/custom_LICENSE.txt.erb +0 -0
- data/templates/full/features/executable.feature.erb +13 -0
- data/templates/full/features/step_definitions/executable_steps.rb.erb +1 -0
- data/templates/full/features/support/env.rb.erb +16 -0
- data/templates/full/gplv2_LICENSE.txt.erb +14 -0
- data/templates/full/gplv3_LICENSE.txt.erb +15 -0
- data/templates/full/mit_LICENSE.txt.erb +7 -0
- data/templates/multicommand/bin/executable.erb +52 -0
- data/templates/multicommand/lib/command.rb.erb +40 -0
- data/templates/multicommand/lib/commands.rb.erb +7 -0
- data/templates/rspec/spec/something_spec.rb.erb +5 -0
- data/templates/test_unit/test/tc_something.rb.erb +7 -0
- data/test/base_test.rb +20 -0
- data/test/command_for_tests.sh +7 -0
- data/test/execution_strategy/test_base.rb +24 -0
- data/test/execution_strategy/test_jvm.rb +77 -0
- data/test/execution_strategy/test_mri.rb +32 -0
- data/test/execution_strategy/test_open_3.rb +70 -0
- data/test/execution_strategy/test_open_4.rb +86 -0
- data/test/execution_strategy/test_rbx_open_4.rb +25 -0
- data/test/test_cli_logger.rb +219 -0
- data/test/test_cli_logging.rb +243 -0
- data/test/test_exit_now.rb +37 -0
- data/test/test_main.rb +1213 -0
- data/test/test_multi.rb +405 -0
- data/test/test_sh.rb +404 -0
- metadata +321 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
module Methadone
|
|
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 <tt>info</tt>
|
|
6
|
+
# as a replacement for +puts+ and <tt>error</tt> as a replacement
|
|
7
|
+
# for <tt>STDERR.puts</tt>. Since this is a logger, however, you
|
|
8
|
+
# can also use #debug, #warn, and #fatal, and you can control
|
|
9
|
+
# the format and "logging level" as such.
|
|
10
|
+
#
|
|
11
|
+
# So, by default:
|
|
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
|
|
17
|
+
#
|
|
18
|
+
# You can customize this in several ways:
|
|
19
|
+
#
|
|
20
|
+
# * You can override the devices used by passing different devices to the constructor
|
|
21
|
+
# * You can adjust the level of message that goes to the error logger via error_level=
|
|
22
|
+
# * You can adjust the format for messages to the error logger separately via error_formatter=
|
|
23
|
+
#
|
|
24
|
+
# === Example
|
|
25
|
+
#
|
|
26
|
+
# logger = CLILogger.new
|
|
27
|
+
# logger.debug("Starting up") # => only the standard output gets this
|
|
28
|
+
# logger.warn("careful!") # => only the standard error gets this
|
|
29
|
+
# logger.error("Something went wrong!") # => only the standard error gets this
|
|
30
|
+
#
|
|
31
|
+
# logger = CLILogger.new
|
|
32
|
+
# logger.error_level = Logger::ERROR
|
|
33
|
+
# logger.debug("Starting up") # => only the standard output gets this
|
|
34
|
+
# logger.warn("careful!") # => only the standard OUTPUT gets this
|
|
35
|
+
# logger.error("Something went wrong!") # => only the standard error gets this
|
|
36
|
+
#
|
|
37
|
+
# logger = CLILogger.new('logfile.txt')
|
|
38
|
+
# logger.debug("Starting up") # => logfile.txt gets this
|
|
39
|
+
# logger.error("Something went wrong!") # => BOTH logfile.txt AND the standard error get this
|
|
40
|
+
class CLILogger < Logger
|
|
41
|
+
BLANK_FORMAT = proc { |severity,datetime,progname,msg|
|
|
42
|
+
msg + "\n"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Helper to proxy methods to the super class AND to the internal error logger
|
|
46
|
+
#
|
|
47
|
+
# +symbol+:: Symbol for name of the method to proxy
|
|
48
|
+
def self.proxy_method(symbol) #:nodoc:
|
|
49
|
+
old_name = "old_#{symbol}".to_sym
|
|
50
|
+
alias_method old_name,symbol
|
|
51
|
+
define_method symbol do |*args,&block|
|
|
52
|
+
send(old_name,*args,&block)
|
|
53
|
+
@stderr_logger.send(symbol,*args,&block)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
proxy_method :'formatter='
|
|
58
|
+
proxy_method :'datetime_format='
|
|
59
|
+
|
|
60
|
+
def add(severity, message = nil, progname = nil, &block) #:nodoc:
|
|
61
|
+
if @split_logs
|
|
62
|
+
unless severity >= @stderr_logger.level
|
|
63
|
+
super(severity,message,progname,&block)
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
super(severity,message,progname,&block)
|
|
67
|
+
end
|
|
68
|
+
@stderr_logger.add(severity,message,progname,&block)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
DEFAULT_ERROR_LEVEL = Logger::Severity::WARN
|
|
72
|
+
|
|
73
|
+
# A logger that logs error-type messages to a second device; useful
|
|
74
|
+
# for ensuring that error messages go to standard error. This should be
|
|
75
|
+
# pretty smart about doing the right thing. If both log devices are
|
|
76
|
+
# ttys, e.g. one is going to standard error and the other to the standard output,
|
|
77
|
+
# messages only appear once in the overall output stream. In other words,
|
|
78
|
+
# an ERROR logged will show up *only* in the standard error. If either
|
|
79
|
+
# log device is NOT a tty, then all messages go to +log_device+ and only
|
|
80
|
+
# errors go to +error_device+
|
|
81
|
+
#
|
|
82
|
+
# +log_device+:: device where all log messages should go, based on level
|
|
83
|
+
# +error_device+:: device where all error messages should go. By default, this is Logger::Severity::WARN
|
|
84
|
+
def initialize(log_device=$stdout,error_device=$stderr)
|
|
85
|
+
super(log_device)
|
|
86
|
+
@stderr_logger = Logger.new(error_device)
|
|
87
|
+
|
|
88
|
+
log_device_tty = tty?(log_device)
|
|
89
|
+
error_device_tty = tty?(error_device)
|
|
90
|
+
|
|
91
|
+
@split_logs = log_device_tty && error_device_tty
|
|
92
|
+
|
|
93
|
+
self.level = Logger::Severity::INFO
|
|
94
|
+
@stderr_logger.level = DEFAULT_ERROR_LEVEL
|
|
95
|
+
|
|
96
|
+
self.formatter = BLANK_FORMAT if log_device_tty
|
|
97
|
+
@stderr_logger.formatter = BLANK_FORMAT if error_device_tty
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def level=(level)
|
|
101
|
+
super(level)
|
|
102
|
+
current_error_level = @stderr_logger.level
|
|
103
|
+
if (level > DEFAULT_ERROR_LEVEL) && @split_logs
|
|
104
|
+
@stderr_logger.level = level
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Set the threshold for what messages go to the error device. Note that calling
|
|
109
|
+
# #level= will *not* affect the error logger *unless* both devices are TTYs.
|
|
110
|
+
#
|
|
111
|
+
# +level+:: a constant from Logger::Severity for the level of messages that should go
|
|
112
|
+
# to the error logger
|
|
113
|
+
def error_level=(level)
|
|
114
|
+
@stderr_logger.level = level
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Overrides the formatter for the error logger. A future call to #formatter= will
|
|
118
|
+
# affect both, so the order of the calls matters.
|
|
119
|
+
#
|
|
120
|
+
# +formatter+:: Proc that handles the formatting, the same as for #formatter=
|
|
121
|
+
def error_formatter=(formatter)
|
|
122
|
+
@stderr_logger.formatter=formatter
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def tty?(device_or_string)
|
|
128
|
+
return device_or_string.tty? if device_or_string.respond_to? :tty?
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Methadone
|
|
2
|
+
# Provides easier access to a shared Methadone::CLILogger instance.
|
|
3
|
+
#
|
|
4
|
+
# Include this module into your class, and #logger provides access to a shared logger.
|
|
5
|
+
# This is handy if you want all of your clases to have access to the same logger, but
|
|
6
|
+
# don't want to (or aren't able to) pass it around to each class.
|
|
7
|
+
#
|
|
8
|
+
# This also provides methods for direct logging without going through the #logger
|
|
9
|
+
#
|
|
10
|
+
# === Example
|
|
11
|
+
#
|
|
12
|
+
# class MyClass
|
|
13
|
+
# include Methadone::CLILogger
|
|
14
|
+
#
|
|
15
|
+
# def doit
|
|
16
|
+
# debug("About to doit!")
|
|
17
|
+
# if results
|
|
18
|
+
# info("We did it!"
|
|
19
|
+
# else
|
|
20
|
+
# error("Something went wrong")
|
|
21
|
+
# end
|
|
22
|
+
# debug("Done doing it")
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# Note that every class that mixes this in shares the *same logger instance*, so if you call #change_logger, this
|
|
27
|
+
# will change the logger for all classes that mix this in. This is likely what you want.
|
|
28
|
+
module CLILogging
|
|
29
|
+
|
|
30
|
+
def self.included(k)
|
|
31
|
+
k.extend(self)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Access the shared logger. All classes that include this module
|
|
35
|
+
# will get the same logger via this method.
|
|
36
|
+
def logger
|
|
37
|
+
@@logger ||= CLILogger.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Change the global logger that includers will use. Useful if you
|
|
41
|
+
# don't want the default configured logger. Note that the +change_logger+
|
|
42
|
+
# version is preferred because Ruby will often parse <tt>logger = Logger.new</tt> as
|
|
43
|
+
# the declaration of, and assignment to, of a local variable. You'd need to
|
|
44
|
+
# do <tt>self.logger=Logger.new</tt> to be sure. This method
|
|
45
|
+
# is a bit easier.
|
|
46
|
+
#
|
|
47
|
+
# +new_logger+:: the new logger. May not be nil and should be a logger of some kind
|
|
48
|
+
def change_logger(new_logger)
|
|
49
|
+
raise ArgumentError,"Logger may not be nil" if new_logger.nil?
|
|
50
|
+
@@logger = new_logger
|
|
51
|
+
@@logger.level = @log_level if @log_level
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
alias logger= change_logger
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# pass-through to <tt>logger.debug(progname,&block)</tt>
|
|
58
|
+
def debug(progname = nil, &block); logger.debug(progname,&block); end
|
|
59
|
+
# pass-through to <tt>logger.info(progname,&block)</tt>
|
|
60
|
+
def info(progname = nil, &block); logger.info(progname,&block); end
|
|
61
|
+
# pass-through to <tt>logger.warn(progname,&block)</tt>
|
|
62
|
+
def warn(progname = nil, &block); logger.warn(progname,&block); end
|
|
63
|
+
# pass-through to <tt>logger.error(progname,&block)</tt>
|
|
64
|
+
def error(progname = nil, &block); logger.error(progname,&block); end
|
|
65
|
+
# pass-through to <tt>logger.fatal(progname,&block)</tt>
|
|
66
|
+
def fatal(progname = nil, &block); logger.fatal(progname,&block); end
|
|
67
|
+
|
|
68
|
+
LOG_LEVELS = {
|
|
69
|
+
'debug' => Logger::DEBUG,
|
|
70
|
+
'info' => Logger::INFO,
|
|
71
|
+
'warn' => Logger::WARN,
|
|
72
|
+
'error' => Logger::ERROR,
|
|
73
|
+
'fatal' => Logger::FATAL,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Call this *if* you've included Methadone::Main to set up a <tt>--log-level</tt> option for your app
|
|
77
|
+
# that will allow the user to configure the logging level. You can pass an optional hash with
|
|
78
|
+
# <tt>:toggle_debug_on_signal => <SIGNAME></tt> to enable runtime toggling of the log level by sending the
|
|
79
|
+
# signal <tt><SIGNAME></tt> to your app
|
|
80
|
+
#
|
|
81
|
+
# +args+:: optional hash
|
|
82
|
+
#
|
|
83
|
+
# Example:
|
|
84
|
+
#
|
|
85
|
+
# main do
|
|
86
|
+
# # your app
|
|
87
|
+
# end
|
|
88
|
+
#
|
|
89
|
+
# use_log_level_option
|
|
90
|
+
#
|
|
91
|
+
# go!
|
|
92
|
+
#
|
|
93
|
+
# Example with runtime toggling:
|
|
94
|
+
#
|
|
95
|
+
#
|
|
96
|
+
# main do
|
|
97
|
+
# # your app
|
|
98
|
+
# end
|
|
99
|
+
#
|
|
100
|
+
# use_log_level_option :toggle_debug_on_signal => 'USR1'
|
|
101
|
+
#
|
|
102
|
+
# go!
|
|
103
|
+
def use_log_level_option(args = {})
|
|
104
|
+
on("--log-level LEVEL",LOG_LEVELS,'Set the logging level',
|
|
105
|
+
'(' + LOG_LEVELS.keys.join('|') + ')',
|
|
106
|
+
'(Default: info)', :global) do |level|
|
|
107
|
+
@log_level = level
|
|
108
|
+
@log_level_original = level
|
|
109
|
+
@log_level_toggled = false
|
|
110
|
+
logger.level = level
|
|
111
|
+
|
|
112
|
+
setup_toggle_trap(args[:toggle_debug_on_signal])
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# Call this to toggle the log level between <tt>debug</tt> and its initial value
|
|
119
|
+
def toggle_log_level
|
|
120
|
+
@log_level_original = logger.level unless @log_level_toggled
|
|
121
|
+
logger.level = if @log_level_toggled
|
|
122
|
+
@log_level_original
|
|
123
|
+
else
|
|
124
|
+
LOG_LEVELS['debug']
|
|
125
|
+
end
|
|
126
|
+
@log_level_toggled = !@log_level_toggled
|
|
127
|
+
@log_level = logger.level
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def setup_toggle_trap(signal)
|
|
131
|
+
if signal
|
|
132
|
+
Signal.trap(signal) do
|
|
133
|
+
toggle_log_level
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
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. By default,
|
|
10
|
+
# the options won't be required to be negatable.
|
|
11
|
+
#
|
|
12
|
+
# Then the following options should be documented:
|
|
13
|
+
# |--force|
|
|
14
|
+
# |-x |
|
|
15
|
+
#
|
|
16
|
+
# Then the following options should be documented:
|
|
17
|
+
# |--force| which is negatable |
|
|
18
|
+
# |-x | which is not negatable |
|
|
19
|
+
#
|
|
20
|
+
# * Check an individual option for documentation:
|
|
21
|
+
#
|
|
22
|
+
# Then the option "--force" should be documented
|
|
23
|
+
# Then the option "--force" should be documented which is negatable
|
|
24
|
+
#
|
|
25
|
+
# * Checks that the help has a proper usage banner
|
|
26
|
+
#
|
|
27
|
+
# Then the banner should be present
|
|
28
|
+
#
|
|
29
|
+
# * Checks that the banner includes the version
|
|
30
|
+
#
|
|
31
|
+
# Then the banner should include the version
|
|
32
|
+
#
|
|
33
|
+
# * Checks that the usage banner indicates it takes options via <tt>[options]</tt>
|
|
34
|
+
#
|
|
35
|
+
# Then the banner should document that this app takes options
|
|
36
|
+
#
|
|
37
|
+
# * Do the opposite; check that you don't indicate options are accepted
|
|
38
|
+
#
|
|
39
|
+
# Then the banner should document that this app takes no options
|
|
40
|
+
#
|
|
41
|
+
# * Checks that the app's usage banner documents that its arguments are <tt>args</tt>
|
|
42
|
+
#
|
|
43
|
+
# Then the banner should document that this app's arguments are
|
|
44
|
+
# |foo|which is optional|
|
|
45
|
+
# |bar|which is required|
|
|
46
|
+
#
|
|
47
|
+
# * Do the opposite; check that your app doesn't take any arguments
|
|
48
|
+
#
|
|
49
|
+
# Then the banner should document that this app takes no arguments
|
|
50
|
+
#
|
|
51
|
+
# * Check for a usage description which occurs after the banner and a blank line
|
|
52
|
+
#
|
|
53
|
+
# Then there should be a one line summary of what the app does
|
|
54
|
+
#
|
|
55
|
+
module Cucumber
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
Given /^PENDING/ do
|
|
60
|
+
pending "test needs to be writen"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
When /^I get help for "([^"]*)"$/ do |app_name|
|
|
64
|
+
@app_name = app_name
|
|
65
|
+
step %(I run `#{app_name} --help`)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
When /^I get help for "([^"]*)" subcommand "([^"]*)"$/ do |app_name,subcommands|
|
|
69
|
+
@app_name = app_name
|
|
70
|
+
@subcommands = subcommands.split(/\s+/)
|
|
71
|
+
step %(I run `#{app_name} #{subcommands} --help`)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Then /^the following (argument|option)s should be documented:$/ do |type,table|
|
|
75
|
+
table.raw.each do |row|
|
|
76
|
+
step %(the #{type} "#{row[0]}" should be documented #{row[1]})
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
Then /^there should be (\d+) options listed$/ do |option_count|
|
|
81
|
+
match = all_output.match(/(?m)Options:\n((?:(?!\n\n).)*)(?:\n\n|\z)/)
|
|
82
|
+
real_option_count = match[1].chomp.split(/\n/).select {|l| l =~ /^ *-/}.length
|
|
83
|
+
real_option_count.should == option_count.to_i
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
Then /^the option "([^"]*)" should be documented(.*)$/ do |options,qualifiers|
|
|
87
|
+
options.split(',').map(&:strip).each do |option|
|
|
88
|
+
if qualifiers.strip == "which is negatable"
|
|
89
|
+
option = option.gsub(/^--/,"--[no-]")
|
|
90
|
+
end
|
|
91
|
+
step %(the output should match /\\s*#{Regexp.escape(option)}[\\s\\W]+\\w[\\s\\w][\\s\\w]+/)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
Then /^the following (commands|global options) should be documented:$/ do |type, table|
|
|
96
|
+
table.raw.each do |row|
|
|
97
|
+
step %(the output should match /(?m)#{type.capitalize}:((?!\\n\\n).)*\\n +#{Regexp.escape(row[0])}/)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
Then /^there should be (\d+) command listed$/ do |command_count|
|
|
102
|
+
match = all_output.match(/(?m)Commands:\n((?: [^\n]*\n)+)(\n|\z)/)
|
|
103
|
+
real_command_count = match[1].chomp.split(/\n/).length
|
|
104
|
+
real_command_count.should == command_count.to_i.should
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
Then /^the banner should be present$/ do
|
|
108
|
+
step %(the output should match /Usage: #{@app_name}/)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
Then /^the banner should document that this app takes options$/ do
|
|
112
|
+
step %(the output should match /\[options\]/)
|
|
113
|
+
step %(the output should contain "Options")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
Then /^the banner should document that this app takes global options$/ do
|
|
117
|
+
step %(the output should match /\[global options\]/)
|
|
118
|
+
step %(the output should contain "Global options")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
Then /^the banner should document that this app takes commands$/ do
|
|
122
|
+
step %(the output should match /command \[command options and args...\]/)
|
|
123
|
+
step %(the output should contain "\\nCommands:")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
Then /^the banner should document that this app's arguments are:$/ do |table|
|
|
127
|
+
expected_arguments = table.raw.map { |row|
|
|
128
|
+
option = row[0]
|
|
129
|
+
option = "#{option}..." if row[1] =~ /(?:which can take )?(many|any)( values)?/
|
|
130
|
+
option = "[#{option}]" if row[1] =~ /(which is optional|which can take any( values)?|optional|any)/
|
|
131
|
+
option
|
|
132
|
+
}.join(' ')
|
|
133
|
+
step %(the output should contain "#{expected_arguments}")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
Then /^there should be (\d+) arguments? listed$/ do |arg_count|
|
|
137
|
+
match = all_output.match(/(?m)\nArguments:\n((?: [^\n]*\n)+)(?:\n|\z)/)
|
|
138
|
+
real_arg_count = match[1].chomp.split(/\n (?=[^ ])/).length
|
|
139
|
+
real_arg_count.should == arg_count.to_i
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
Then /^the argument "([^"]*)" should be documented(.*)$/ do |arg,qualifiers|
|
|
143
|
+
if qualifiers.strip == "which is optional"
|
|
144
|
+
arg += " (optional)"
|
|
145
|
+
end
|
|
146
|
+
step %(the output should match /(?m)Arguments:((?!\\n\\n).)*\\n #{Regexp.escape(arg)}/)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
Then /^the banner should document that this app takes no options$/ do
|
|
150
|
+
step %(the output should not contain "[options]")
|
|
151
|
+
step %(the output should not contain "^Options:")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
Then /^the banner should document that this app takes no arguments$/ do
|
|
155
|
+
step %(the output should match /Usage: #{@app_name}\\s*\(\\[options\\]\)?$/)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
Then /^the banner should document that this app takes no commands$/ do
|
|
159
|
+
step %(the output should not match /command \[command options and args...\]/)
|
|
160
|
+
step %(the output should not contain "\\nCommands:")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
Then /^the banner should include the version$/ do
|
|
164
|
+
step %(the output should match /v\\d+\\.\\d+\\.\\d+/)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
Then /^there should be a one line summary of what the app does$/ do
|
|
168
|
+
output_lines = all_output.split(/\n/)
|
|
169
|
+
output_lines.size.should >= 4
|
|
170
|
+
# [0] is a blank line,
|
|
171
|
+
# [1] is our banner, which we've checked for
|
|
172
|
+
output_lines[2].should match(/^\s*$/)
|
|
173
|
+
output_lines[3].should match(/^\w+\s+\w+/)
|
|
174
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Methadone
|
|
2
|
+
# Standard exception you can throw to exit with a given
|
|
3
|
+
# status code. Generally, you should prefer Methadone::Main#exit_now! over using
|
|
4
|
+
# this directly, however you may wish to create a rich hierarchy of exceptions that extend from
|
|
5
|
+
# this in your app, so this is provided if you wish to do so.
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
attr_reader :exit_code
|
|
8
|
+
# Create an Error with the given status code and message
|
|
9
|
+
def initialize(exit_code,message=nil)
|
|
10
|
+
super(message)
|
|
11
|
+
@exit_code = exit_code
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Thrown by certain methods when an externally-called command exits nonzero
|
|
16
|
+
class FailedCommandError < Error
|
|
17
|
+
|
|
18
|
+
# The command that caused the failure
|
|
19
|
+
attr_reader :command
|
|
20
|
+
|
|
21
|
+
# exit_code:: exit code of the command that caused this
|
|
22
|
+
# command:: the entire command-line that caused this
|
|
23
|
+
# custom_error_message:: an error message to show the user instead of the boilerplate one. Useful
|
|
24
|
+
# for allowing this exception to bubble up and exit the program, but to give
|
|
25
|
+
# the user something actionable.
|
|
26
|
+
def initialize(exit_code,command,custom_error_message = nil)
|
|
27
|
+
error_message = String(custom_error_message).empty? ? "Command '#{command}' exited #{exit_code}" : custom_error_message
|
|
28
|
+
super(exit_code,error_message)
|
|
29
|
+
@command = command
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|