methadone-rehab 1.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +11 -0
  6. data/CHANGES.md +66 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE.txt +201 -0
  9. data/README.rdoc +179 -0
  10. data/Rakefile +98 -0
  11. data/TODO.md +3 -0
  12. data/bin/methadone +157 -0
  13. data/features/bootstrap.feature +169 -0
  14. data/features/license.feature +43 -0
  15. data/features/multilevel_commands.feature +125 -0
  16. data/features/readme.feature +26 -0
  17. data/features/rspec_support.feature +27 -0
  18. data/features/step_definitions/bootstrap_steps.rb +47 -0
  19. data/features/step_definitions/license_steps.rb +30 -0
  20. data/features/step_definitions/readme_steps.rb +26 -0
  21. data/features/step_definitions/version_steps.rb +4 -0
  22. data/features/support/env.rb +26 -0
  23. data/features/version.feature +17 -0
  24. data/lib/methadone.rb +15 -0
  25. data/lib/methadone/argv_parser.rb +50 -0
  26. data/lib/methadone/cli.rb +124 -0
  27. data/lib/methadone/cli_logger.rb +133 -0
  28. data/lib/methadone/cli_logging.rb +138 -0
  29. data/lib/methadone/cucumber.rb +174 -0
  30. data/lib/methadone/error.rb +32 -0
  31. data/lib/methadone/execution_strategy/base.rb +34 -0
  32. data/lib/methadone/execution_strategy/jvm.rb +37 -0
  33. data/lib/methadone/execution_strategy/mri.rb +16 -0
  34. data/lib/methadone/execution_strategy/open_3.rb +16 -0
  35. data/lib/methadone/execution_strategy/open_4.rb +22 -0
  36. data/lib/methadone/execution_strategy/rbx_open_4.rb +12 -0
  37. data/lib/methadone/exit_now.rb +40 -0
  38. data/lib/methadone/main.rb +1039 -0
  39. data/lib/methadone/process_status.rb +45 -0
  40. data/lib/methadone/sh.rb +223 -0
  41. data/lib/methadone/version.rb +3 -0
  42. data/methadone-rehab.gemspec +32 -0
  43. data/templates/full/.gitignore.erb +4 -0
  44. data/templates/full/README.rdoc.erb +25 -0
  45. data/templates/full/Rakefile.erb +74 -0
  46. data/templates/full/_license_head.txt.erb +2 -0
  47. data/templates/full/apache_LICENSE.txt.erb +203 -0
  48. data/templates/full/bin/executable.erb +47 -0
  49. data/templates/full/custom_LICENSE.txt.erb +0 -0
  50. data/templates/full/features/executable.feature.erb +13 -0
  51. data/templates/full/features/step_definitions/executable_steps.rb.erb +1 -0
  52. data/templates/full/features/support/env.rb.erb +16 -0
  53. data/templates/full/gplv2_LICENSE.txt.erb +14 -0
  54. data/templates/full/gplv3_LICENSE.txt.erb +15 -0
  55. data/templates/full/mit_LICENSE.txt.erb +7 -0
  56. data/templates/multicommand/bin/executable.erb +52 -0
  57. data/templates/multicommand/lib/command.rb.erb +40 -0
  58. data/templates/multicommand/lib/commands.rb.erb +7 -0
  59. data/templates/rspec/spec/something_spec.rb.erb +5 -0
  60. data/templates/test_unit/test/tc_something.rb.erb +7 -0
  61. data/test/base_test.rb +20 -0
  62. data/test/command_for_tests.sh +7 -0
  63. data/test/execution_strategy/test_base.rb +24 -0
  64. data/test/execution_strategy/test_jvm.rb +77 -0
  65. data/test/execution_strategy/test_mri.rb +32 -0
  66. data/test/execution_strategy/test_open_3.rb +70 -0
  67. data/test/execution_strategy/test_open_4.rb +86 -0
  68. data/test/execution_strategy/test_rbx_open_4.rb +25 -0
  69. data/test/test_cli_logger.rb +219 -0
  70. data/test/test_cli_logging.rb +243 -0
  71. data/test/test_exit_now.rb +37 -0
  72. data/test/test_main.rb +1213 -0
  73. data/test/test_multi.rb +405 -0
  74. data/test/test_sh.rb +404 -0
  75. 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