methadone 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/README.rdoc +13 -10
- data/Rakefile +4 -2
- data/bin/methadone +18 -90
- data/features/support/env.rb +11 -2
- data/lib/methadone.rb +2 -0
- data/lib/methadone/cli.rb +100 -0
- data/lib/methadone/cli_logger.rb +52 -7
- data/lib/methadone/error.rb +12 -0
- data/lib/methadone/main.rb +86 -0
- data/lib/methadone/version.rb +1 -1
- data/methadone.gemspec +3 -2
- data/templates/full/bin/executable.erb +20 -0
- data/test/base_test.rb +4 -0
- data/test/test_cli_logger.rb +24 -1
- data/test/test_cli_logging.rb +1 -0
- data/test/test_main.rb +136 -0
- metadata +29 -13
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -10,7 +10,7 @@ Currently, this is under development and has the following to offer:
|
|
10
10
|
|
11
11
|
* Bootstrapping a new CLI app
|
12
12
|
* Utility Classes
|
13
|
-
* Methadone::CLILogger - a logger subclass that sends
|
13
|
+
* Methadone::CLILogger - a logger subclass that sends messages to standard error standard out as appropriate
|
14
14
|
* Methadone::CLILogging - a module that, when included in any class, provides easy access to a shared logger
|
15
15
|
* Cucumber Steps
|
16
16
|
|
@@ -61,26 +61,30 @@ Currently, there are classes the assist in directing output logger-style to the
|
|
61
61
|
include Methadone::CLILogging
|
62
62
|
|
63
63
|
command = "rm -rf /tmp/*"
|
64
|
-
debug("About to run #{command}") # => goes only to STDOUT
|
64
|
+
debug("About to run #{command}") # => goes only to STDOUT, no logging format
|
65
65
|
if system(command)
|
66
|
-
info("Succesfully ran #{command}") # => goes only to STDOUT
|
66
|
+
info("Succesfully ran #{command}") # => goes only to STDOUT, no logging format
|
67
67
|
else
|
68
|
-
error("There was a problem running #{command}") # => goes to
|
68
|
+
error("There was a problem running #{command}") # => goes only to STDERR, no logging format
|
69
69
|
end
|
70
70
|
|
71
71
|
==== Using a log file, but respecting STDERR
|
72
72
|
|
73
|
+
Here, since we have a logfile, that logfile gets ALL messages and they have the default logger format.
|
74
|
+
|
73
75
|
require 'methadone'
|
74
76
|
|
75
77
|
include Methadone::CLILogging
|
76
78
|
|
77
79
|
self.logger = CLILogger.new("logfile.txt")
|
78
80
|
command = "rm -rf /tmp/*"
|
79
|
-
debug("About to run #{command}") # => goes only to logfile.txt
|
81
|
+
debug("About to run #{command}") # => goes only to logfile.txt, in the logger-style format
|
80
82
|
if system(command)
|
81
|
-
info("Succesfully ran #{command}") # => goes only to logfile.txt
|
83
|
+
info("Succesfully ran #{command}") # => goes only to logfile.txt, in the logger-style format
|
82
84
|
else
|
83
|
-
error("There was a problem running #{command}")
|
85
|
+
error("There was a problem running #{command}")
|
86
|
+
# => goes to logfile.txt in the logger-style format, and
|
87
|
+
# to STDERR in a plain format
|
84
88
|
end
|
85
89
|
|
86
90
|
== Cucumber Steps
|
@@ -143,6 +147,5 @@ Here's an example from methadone's own tests:
|
|
143
147
|
|
144
148
|
== What might be
|
145
149
|
|
146
|
-
|
147
|
-
|
148
|
-
* Easy support for filtering the output of a command, e.g. <tt>File.open("ls|")</tt> Perl-style
|
150
|
+
See {the roadmap}[https://github.com/davetron5000/methadone/wiki/Roadmap]
|
151
|
+
|
data/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
require 'rake/clean'
|
3
3
|
require 'rake/testtask'
|
4
|
-
gem 'rdoc'
|
4
|
+
gem 'rdoc' # I need to use the installed RDoc gem, not what comes with the system
|
5
5
|
require 'rdoc/task'
|
6
6
|
require 'cucumber'
|
7
7
|
require 'cucumber/rake/task'
|
@@ -18,7 +18,7 @@ Rake::TestTask.new do |t|
|
|
18
18
|
end
|
19
19
|
|
20
20
|
desc 'build rdoc'
|
21
|
-
|
21
|
+
RDoc::Task.new do |rd|
|
22
22
|
rd.main = "README.rdoc"
|
23
23
|
rd.generator = 'hanna'
|
24
24
|
rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
|
@@ -34,4 +34,6 @@ Cucumber::Rake::Task.new(:features) do |t|
|
|
34
34
|
t.fork = false
|
35
35
|
end
|
36
36
|
|
37
|
+
CLEAN << "coverage"
|
38
|
+
|
37
39
|
task :default => [:test, :features]
|
data/bin/methadone
CHANGED
@@ -1,24 +1,22 @@
|
|
1
1
|
#!/usr/bin/env ruby -w
|
2
2
|
|
3
|
+
$: << File.join(File.dirname(__FILE__),'..','lib')
|
4
|
+
|
3
5
|
require 'fileutils'
|
4
|
-
require 'erb'
|
5
6
|
require 'optparse'
|
7
|
+
require 'methadone'
|
8
|
+
require 'methadone/cli'
|
6
9
|
|
7
10
|
include FileUtils
|
11
|
+
include Methadone::Main
|
12
|
+
include Methadone::CLI
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
if force
|
12
|
-
rm_rf basedir, :verbose => true, :secure => true
|
13
|
-
else
|
14
|
-
STDERR.puts "error: #{basedir} exists, use --force to override"
|
15
|
-
return 1
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
mkdir_p basedir
|
14
|
+
main do |basedir,force|
|
15
|
+
check_and_prepare_basedir!(basedir,force)
|
20
16
|
|
21
17
|
gemname = File.basename(basedir)
|
18
|
+
debug "Creating project for gem #{gemname}"
|
19
|
+
|
22
20
|
chdir File.dirname(basedir)
|
23
21
|
|
24
22
|
%x[bundle gem #{gemname}]
|
@@ -27,91 +25,20 @@ def main(basedir,force)
|
|
27
25
|
|
28
26
|
template_dirs_in(:full).each { |dir| mkdir_p dir }
|
29
27
|
|
30
|
-
|
31
|
-
|
28
|
+
["Rakefile", "test/tc_something.rb", "features/support/env.rb"].each do |file|
|
29
|
+
copy_file file
|
30
|
+
end
|
31
|
+
|
32
32
|
copy_file "features/executable.feature", :as => "#{gemname}.feature", :binding => binding
|
33
|
-
copy_file "features/support/env.rb"
|
34
33
|
copy_file "features/step_definitions/executable_steps.rb", :as => "#{gemname}_steps.rb"
|
35
34
|
copy_file "bin/executable", :as => gemname, :executable => true
|
35
|
+
|
36
36
|
add_to_file "#{gemname}.gemspec", [
|
37
37
|
" s.add_development_dependency('rdoc')",
|
38
38
|
" s.add_development_dependency('aruba')",
|
39
39
|
" s.add_development_dependency('rake','~> 0.9.2')",
|
40
40
|
" s.add_dependency('methadone')",
|
41
41
|
], :before => /^end\s*$/
|
42
|
-
|
43
|
-
return 0
|
44
|
-
end
|
45
|
-
|
46
|
-
# Add content to a file
|
47
|
-
#
|
48
|
-
# +file+:: path to the file
|
49
|
-
# +lines+:: Array of String representing the lines to add
|
50
|
-
# +options+:: Hash of options:
|
51
|
-
# <tt>:before</tt>:: A regexp that will appear right after the new content. i.e.
|
52
|
-
# this is where to insert said content.
|
53
|
-
def add_to_file(file,lines,options = {})
|
54
|
-
new_lines = []
|
55
|
-
found_line = false
|
56
|
-
File.open(file).readlines.each do |line|
|
57
|
-
line.chomp!
|
58
|
-
if options[:before] && options[:before] === line
|
59
|
-
found_line = true
|
60
|
-
new_lines += lines
|
61
|
-
end
|
62
|
-
new_lines << line
|
63
|
-
end
|
64
|
-
|
65
|
-
raise "No line matched #{options[:before]}" if options[:before] && !found_line
|
66
|
-
|
67
|
-
new_lines += lines unless options[:before]
|
68
|
-
File.open(file,'w') do |fp|
|
69
|
-
new_lines.each { |line| fp.puts line }
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# Copies a file, running it through ERB
|
74
|
-
#
|
75
|
-
# +relative_path+:: path to the file, relative to the project root, minus the .erb extension
|
76
|
-
# You should use forward slashes to separate paths; this method
|
77
|
-
# will handle making the ultimate path OS independent.
|
78
|
-
# +options+:: Options to affect how the copy is done:
|
79
|
-
# <tt>:from</tt>:: The name of the profile from which to find the file, "full" by default
|
80
|
-
# <tt>:as</tt>:: The name the file should get if not the one in relative_path
|
81
|
-
# <tt>:executable</tt>:: true if this file should be set executable
|
82
|
-
# <tt>:binding</tt>:: the binding to use for the template
|
83
|
-
def copy_file(relative_path,options = {})
|
84
|
-
options[:from] ||= :full
|
85
|
-
|
86
|
-
relative_path = File.join(relative_path.split(/\//))
|
87
|
-
|
88
|
-
template_path = File.join(template_dir(options[:from]),relative_path + ".erb")
|
89
|
-
template = ERB.new(File.open(template_path).readlines.join(''))
|
90
|
-
|
91
|
-
relative_path_parts = File.split(relative_path)
|
92
|
-
relative_path_parts[-1] = options[:as] if options[:as]
|
93
|
-
|
94
|
-
erb_binding = options[:binding] or binding
|
95
|
-
|
96
|
-
File.open(File.join(relative_path_parts),'w') do |file|
|
97
|
-
file.puts template.result(erb_binding)
|
98
|
-
file.chmod(0755) if options[:executable]
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# Get the location of the templates for profile "from"
|
103
|
-
def template_dir(from)
|
104
|
-
File.join(File.dirname(EXE),'..','templates',from.to_s)
|
105
|
-
end
|
106
|
-
|
107
|
-
def template_dirs_in(profile)
|
108
|
-
template_dir = template_dir(:full)
|
109
|
-
|
110
|
-
Dir["#{template_dir}/**/*"].select { |x|
|
111
|
-
File.directory? x
|
112
|
-
}.map { |dir|
|
113
|
-
dir.gsub(/^#{template_dir}\//,'')
|
114
|
-
}
|
115
42
|
end
|
116
43
|
|
117
44
|
options = {}
|
@@ -132,7 +59,8 @@ EXE = File.expand_path(__FILE__)
|
|
132
59
|
if ARGV.empty?
|
133
60
|
STDERR.puts("error: app_dir required")
|
134
61
|
exit 2
|
135
|
-
else
|
136
|
-
exit main(ARGV[0],options[:force])
|
137
62
|
end
|
138
63
|
|
64
|
+
ARGV << options[:force]
|
65
|
+
go!
|
66
|
+
|
data/features/support/env.rb
CHANGED
@@ -1,12 +1,21 @@
|
|
1
1
|
require 'aruba/cucumber'
|
2
2
|
require 'methadone/cucumber'
|
3
3
|
|
4
|
-
|
4
|
+
PROJECT_ROOT = File.join(File.dirname(__FILE__),'..','..')
|
5
|
+
ENV['PATH'] = "#{File.join(PROJECT_ROOT,'bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
5
6
|
ARUBA_DIR = File.join(%w(tmp aruba))
|
6
7
|
Before do
|
7
8
|
@dirs = [ARUBA_DIR]
|
8
9
|
@puts = true
|
9
10
|
@aruba_timeout_seconds = 60
|
10
|
-
|
11
|
+
@original_rubylib = ENV['RUBYLIB']
|
11
12
|
|
13
|
+
# We want to use, hopefully, the methadone from this codebase and not
|
14
|
+
# the gem, so we put it in the RUBYLIB
|
15
|
+
ENV['RUBYLIB'] = File.join(PROJECT_ROOT,'lib') + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
|
16
|
+
end
|
12
17
|
|
18
|
+
After do
|
19
|
+
# Put back how it was
|
20
|
+
ENV['RUBYLIB'] = @original_rubylib
|
21
|
+
end
|
data/lib/methadone.rb
CHANGED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Methadone
|
4
|
+
# Stuff to implement methadone's CLI app. These
|
5
|
+
# stuff isn't generally for your use and it's not
|
6
|
+
# included when you require 'methadone'
|
7
|
+
module CLI
|
8
|
+
|
9
|
+
# Checks that the basedir can be used, either by
|
10
|
+
# not existing, or by existing and force is true.
|
11
|
+
# In that case, we clean it out entirely
|
12
|
+
#
|
13
|
+
# +basedir+:: base directory where the user wants to create a new project
|
14
|
+
# +force+:: if true, and +basedir+ exists, delete it before proceeding
|
15
|
+
#
|
16
|
+
# This will exit the app if the dir exists and force is false
|
17
|
+
def check_and_prepare_basedir!(basedir,force)
|
18
|
+
if Dir.exists? basedir
|
19
|
+
if force
|
20
|
+
rm_rf basedir, :verbose => true, :secure => true
|
21
|
+
else
|
22
|
+
exit_now! 1,"error: #{basedir} exists, use --force to override"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
mkdir_p basedir
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add content to a file
|
29
|
+
#
|
30
|
+
# +file+:: path to the file
|
31
|
+
# +lines+:: Array of String representing the lines to add
|
32
|
+
# +options+:: Hash of options:
|
33
|
+
# <tt>:before</tt>:: A regexp that will appear right after the new content. i.e.
|
34
|
+
# this is where to insert said content.
|
35
|
+
def add_to_file(file,lines,options = {})
|
36
|
+
new_lines = []
|
37
|
+
found_line = false
|
38
|
+
File.open(file).readlines.each do |line|
|
39
|
+
line.chomp!
|
40
|
+
if options[:before] && options[:before] === line
|
41
|
+
found_line = true
|
42
|
+
new_lines += lines
|
43
|
+
end
|
44
|
+
new_lines << line
|
45
|
+
end
|
46
|
+
|
47
|
+
raise "No line matched #{options[:before]}" if options[:before] && !found_line
|
48
|
+
|
49
|
+
new_lines += lines unless options[:before]
|
50
|
+
File.open(file,'w') do |fp|
|
51
|
+
new_lines.each { |line| fp.puts line }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Copies a file, running it through ERB
|
56
|
+
#
|
57
|
+
# +relative_path+:: path to the file, relative to the project root, minus the .erb extension
|
58
|
+
# You should use forward slashes to separate paths; this method
|
59
|
+
# will handle making the ultimate path OS independent.
|
60
|
+
# +options+:: Options to affect how the copy is done:
|
61
|
+
# <tt>:from</tt>:: The name of the profile from which to find the file, "full" by default
|
62
|
+
# <tt>:as</tt>:: The name the file should get if not the one in relative_path
|
63
|
+
# <tt>:executable</tt>:: true if this file should be set executable
|
64
|
+
# <tt>:binding</tt>:: the binding to use for the template
|
65
|
+
def copy_file(relative_path,options = {})
|
66
|
+
options[:from] ||= :full
|
67
|
+
|
68
|
+
relative_path = File.join(relative_path.split(/\//))
|
69
|
+
|
70
|
+
template_path = File.join(template_dir(options[:from]),relative_path + ".erb")
|
71
|
+
template = ERB.new(File.open(template_path).readlines.join(''))
|
72
|
+
|
73
|
+
relative_path_parts = File.split(relative_path)
|
74
|
+
relative_path_parts[-1] = options[:as] if options[:as]
|
75
|
+
|
76
|
+
erb_binding = options[:binding] or binding
|
77
|
+
|
78
|
+
File.open(File.join(relative_path_parts),'w') do |file|
|
79
|
+
file.puts template.result(erb_binding)
|
80
|
+
file.chmod(0755) if options[:executable]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get the location of the templates for profile "from"
|
85
|
+
def template_dir(from)
|
86
|
+
File.join(File.dirname(EXE),'..','templates',from.to_s)
|
87
|
+
end
|
88
|
+
|
89
|
+
def template_dirs_in(profile)
|
90
|
+
template_dir = template_dir(:full)
|
91
|
+
|
92
|
+
Dir["#{template_dir}/**/*"].select { |x|
|
93
|
+
File.directory? x
|
94
|
+
}.map { |dir|
|
95
|
+
dir.gsub(/^#{template_dir}\//,'')
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
data/lib/methadone/cli_logger.rb
CHANGED
@@ -1,10 +1,20 @@
|
|
1
1
|
require 'logger'
|
2
2
|
|
3
3
|
module Methadone
|
4
|
-
# A Logger
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
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
|
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 #fata messagse appear on the standard error
|
15
|
+
# * The default format of messages is simply the message, no logging cruft
|
16
|
+
#
|
17
|
+
# You can customize this in several ways:
|
8
18
|
#
|
9
19
|
# * You can override the devices used by passing different devices to the constructor
|
10
20
|
# * You can adjust the level of message that goes to the error logger via error_level=
|
@@ -14,8 +24,22 @@ module Methadone
|
|
14
24
|
#
|
15
25
|
# logger = CLILogger.new
|
16
26
|
# logger.debug("Starting up") # => only the standard output gets this
|
17
|
-
# logger.
|
27
|
+
# logger.warn("careful!") # => only the standard error gets this
|
28
|
+
# logger.error("Something went wrong!") # => only the standard error gets this
|
29
|
+
#
|
30
|
+
# logger = CLILogger.new
|
31
|
+
# logger.error_level = Logger::ERROR
|
32
|
+
# logger.debug("Starting up") # => only the standard output gets this
|
33
|
+
# logger.warn("careful!") # => only the standard OUTPUT gets this
|
34
|
+
# logger.error("Something went wrong!") # => only the standard error gets this
|
35
|
+
#
|
36
|
+
# logger = CLILogger.new('logfile.txt')
|
37
|
+
# logger.debug("Starting up") # => logfile.txt gets this
|
38
|
+
# logger.error("Something went wrong!") # => BOTH logfile.txt AND the standard error get this
|
18
39
|
class CLILogger < Logger
|
40
|
+
BLANK_FORMAT = proc { |severity,datetime,progname,msg|
|
41
|
+
msg + "\n"
|
42
|
+
}
|
19
43
|
|
20
44
|
# Helper to proxy methods to the super class AND to the internal error logger
|
21
45
|
#
|
@@ -31,17 +55,38 @@ module Methadone
|
|
31
55
|
|
32
56
|
proxy_method :'formatter='
|
33
57
|
proxy_method :'datetime_format='
|
34
|
-
|
58
|
+
|
59
|
+
def add(severity, message = nil, progname = nil, &block) #:nodoc:
|
60
|
+
if @split_logs
|
61
|
+
unless severity >= @stderr_logger.level
|
62
|
+
super(severity,message,progname,&block)
|
63
|
+
end
|
64
|
+
else
|
65
|
+
super(severity,message,progname,&block)
|
66
|
+
end
|
67
|
+
@stderr_logger.add(severity,message,progname,&block)
|
68
|
+
end
|
69
|
+
|
35
70
|
|
36
71
|
# A logger that logs error-type messages to a second device; useful
|
37
|
-
# for ensuring that error messages go to standard error
|
72
|
+
# for ensuring that error messages go to standard error. This should be
|
73
|
+
# pretty smart about doing the right thing. If both log devices are
|
74
|
+
# ttys, e.g. one is going to standard error and the other to the standard output,
|
75
|
+
# messages only appear once in the overall output stream. In other words,
|
76
|
+
# an ERROR logged will show up *only* in the standard error. If either
|
77
|
+
# log device is NOT a tty, then all messages go to +log_device+ and only
|
78
|
+
# errors go to +error_device+
|
38
79
|
#
|
39
80
|
# +log_device+:: device where all log messages should go, based on level
|
40
81
|
# +error_device+:: device where all error messages should go. By default, this is Logger::Severity::WARN
|
41
82
|
def initialize(log_device=$stdout,error_device=$stderr)
|
42
83
|
super(log_device)
|
84
|
+
@split_logs = log_device.tty? && error_device.tty?
|
85
|
+
self.level = Logger::Severity::INFO
|
43
86
|
@stderr_logger = Logger.new(error_device)
|
44
87
|
@stderr_logger.level = Logger::Severity::WARN
|
88
|
+
self.formatter = BLANK_FORMAT if log_device.tty?
|
89
|
+
@stderr_logger.formatter = BLANK_FORMAT if error_device.tty?
|
45
90
|
end
|
46
91
|
|
47
92
|
# Set the threshold for what messages go to the error device. Note that calling
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Methadone
|
2
|
+
# Standard exception you can throw to exit with a given
|
3
|
+
# status code. Prefer Methadone::Main#exit_now! over this
|
4
|
+
class Error < StandardError
|
5
|
+
attr_reader :exit_code
|
6
|
+
# Create an Error with the given status code and message
|
7
|
+
def initialize(exit_code,message=nil)
|
8
|
+
super(message)
|
9
|
+
@exit_code = exit_code
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Methadone
|
2
|
+
# Include this module to gain access to the "canonical command-line app structure"
|
3
|
+
# DSL. This is a *very* lightweight layer on top of what you might
|
4
|
+
# normally write that gives you just a bit of help to keep your code structured
|
5
|
+
# in a sensible way.
|
6
|
+
#
|
7
|
+
# This also includes Methadone::CLILogging to give you access to simple logging
|
8
|
+
module Main
|
9
|
+
include Methadone::CLILogging
|
10
|
+
# Declare the main method for your app.
|
11
|
+
# This allows you to specify the general logic of your
|
12
|
+
# app at the top of your bin file, but can rely on any methods
|
13
|
+
# or other code that you define later.
|
14
|
+
#
|
15
|
+
# For example, suppose you want to process a set of files, but
|
16
|
+
# wish to determine that list from another method to keep your
|
17
|
+
# code clean.
|
18
|
+
#
|
19
|
+
# #!/usr/bin/env ruby -w
|
20
|
+
#
|
21
|
+
# require 'methadone'
|
22
|
+
#
|
23
|
+
# include Methadone::Main
|
24
|
+
#
|
25
|
+
# main do
|
26
|
+
# files_to_process.each do |file|
|
27
|
+
# # process file
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def files_to_process
|
32
|
+
# # return list of files
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# go!
|
36
|
+
#
|
37
|
+
# The block can accept any parameters, and unparsed arguments
|
38
|
+
# from the command line will be passed.
|
39
|
+
#
|
40
|
+
# To run this method, call #go!
|
41
|
+
def main(&block)
|
42
|
+
@main_block = block
|
43
|
+
end
|
44
|
+
|
45
|
+
# Start your command-line app, exiting appropriately when
|
46
|
+
# complete
|
47
|
+
#
|
48
|
+
# This *will* exit your program when it completes. If your
|
49
|
+
# #main block evaluates to an integer, that value will be sent
|
50
|
+
# to Kernel#exit, otherwise, this will exit with 0
|
51
|
+
def go!
|
52
|
+
result = call_main
|
53
|
+
if result.kind_of? Fixnum
|
54
|
+
exit result
|
55
|
+
else
|
56
|
+
exit 0
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Call this to exit the program immediately
|
61
|
+
# with the given error code and message.
|
62
|
+
#
|
63
|
+
# +exit_code+:: exit status you'd like to exit with
|
64
|
+
# +message+:: message to display to the user explaining the problem
|
65
|
+
def exit_now!(exit_code,message=nil)
|
66
|
+
raise Methadone::Error.new(exit_code,message)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Handle calling main and trapping any exceptions thrown
|
72
|
+
def call_main
|
73
|
+
@main_block.call(*ARGV)
|
74
|
+
rescue Methadone::Error => ex
|
75
|
+
error ex.message unless no_message? ex
|
76
|
+
ex.exit_code
|
77
|
+
rescue => ex
|
78
|
+
error ex.message unless no_message? ex
|
79
|
+
70 # Linux sysexit code for internal software error
|
80
|
+
end
|
81
|
+
|
82
|
+
def no_message?(exception)
|
83
|
+
exception.message.nil? || exception.message.strip.empty?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/methadone/version.rb
CHANGED
data/methadone.gemspec
CHANGED
@@ -18,8 +18,9 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
|
-
s.add_development_dependency("rspec-expectations")
|
21
|
+
s.add_development_dependency("rspec-expectations", "~> 2.6")
|
22
22
|
s.add_development_dependency("rake")
|
23
|
-
s.add_development_dependency("rdoc","~> 3.
|
23
|
+
s.add_development_dependency("rdoc","~> 3.9")
|
24
24
|
s.add_development_dependency("aruba")
|
25
|
+
s.add_development_dependency("simplecov", "~> 0.5")
|
25
26
|
end
|
@@ -1,7 +1,17 @@
|
|
1
1
|
#!/usr/bin/env ruby -w
|
2
2
|
|
3
3
|
require 'optparse'
|
4
|
+
require 'methadone'
|
4
5
|
|
6
|
+
include Methadone::Main
|
7
|
+
|
8
|
+
main do |options|
|
9
|
+
# your program code here
|
10
|
+
end
|
11
|
+
|
12
|
+
# supplemental methods here
|
13
|
+
|
14
|
+
options = {}
|
5
15
|
option_parser = OptionParser.new do |opts|
|
6
16
|
executable_name = File.basename(__FILE__)
|
7
17
|
opts.banner = "Usage: #{executable_name}"
|
@@ -9,7 +19,17 @@ option_parser = OptionParser.new do |opts|
|
|
9
19
|
# opts.banner = "Usage: #{executable_name} [options]\n\n" +
|
10
20
|
# "One-line description of your app\n\n" +
|
11
21
|
# "Options:"
|
22
|
+
|
23
|
+
# opts.on("--flag VAL","Some flag") do |val|
|
24
|
+
# options[:flag] = val
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# opts.on("--[no-]switch","Some switch") do |switch|
|
28
|
+
# options[:switch] = switch
|
29
|
+
# end
|
12
30
|
end
|
13
31
|
|
14
32
|
option_parser.parse!
|
15
33
|
|
34
|
+
ARGV << options
|
35
|
+
go!
|
data/test/base_test.rb
CHANGED
data/test/test_cli_logger.rb
CHANGED
@@ -20,7 +20,28 @@ class TestCLILogger < BaseTest
|
|
20
20
|
$stdout = @real_stdout
|
21
21
|
end
|
22
22
|
|
23
|
-
test "
|
23
|
+
test "when both stderr and stdin are ttys, split the log messages between them and don't format" do
|
24
|
+
class << $stderr
|
25
|
+
def tty?; true; end
|
26
|
+
end
|
27
|
+
class << $stdout
|
28
|
+
def tty?; true; end
|
29
|
+
end
|
30
|
+
|
31
|
+
logger = CLILogger.new
|
32
|
+
logger.level = Logger::DEBUG
|
33
|
+
|
34
|
+
logger.debug("debug")
|
35
|
+
logger.info("info")
|
36
|
+
logger.warn("warn")
|
37
|
+
logger.error("error")
|
38
|
+
logger.fatal("fatal")
|
39
|
+
|
40
|
+
$stdout.string.should == "debug\ninfo\n"
|
41
|
+
$stderr.string.should == "warn\nerror\nfatal\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
test "logger sends debug and info to stdout, and warns, errors, and fatals to stderr" do
|
24
45
|
logger = logger_with_blank_format
|
25
46
|
|
26
47
|
logger.debug("debug")
|
@@ -52,6 +73,7 @@ class TestCLILogger < BaseTest
|
|
52
73
|
err = StringIO.new
|
53
74
|
|
54
75
|
logger = CLILogger.new(out,err)
|
76
|
+
logger.level = Logger::DEBUG
|
55
77
|
logger.formatter = @blank_format
|
56
78
|
|
57
79
|
logger.debug("debug")
|
@@ -80,6 +102,7 @@ class TestCLILogger < BaseTest
|
|
80
102
|
|
81
103
|
test "both loggers use the same date format" do
|
82
104
|
logger = CLILogger.new
|
105
|
+
logger.level = Logger::DEBUG
|
83
106
|
logger.datetime_format = "the time"
|
84
107
|
logger.debug("debug")
|
85
108
|
logger.error("error")
|
data/test/test_cli_logging.rb
CHANGED
data/test/test_main.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'base_test'
|
2
|
+
require 'methadone'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
class TestMain < BaseTest
|
6
|
+
include Methadone::Main
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@logged = []
|
10
|
+
@original_argv = ARGV.clone
|
11
|
+
ARGV.clear
|
12
|
+
end
|
13
|
+
|
14
|
+
# Override error so we can capture what's being logged at this level
|
15
|
+
def error(string)
|
16
|
+
@logged << string
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
set_argv @original_argv
|
21
|
+
end
|
22
|
+
|
23
|
+
test "my main block gets called by run and has access to CLILogging" do
|
24
|
+
called = false
|
25
|
+
main do
|
26
|
+
begin
|
27
|
+
debug "debug"
|
28
|
+
info "info"
|
29
|
+
warn "warn"
|
30
|
+
error "error"
|
31
|
+
fatal "fatal"
|
32
|
+
called = true
|
33
|
+
rescue => ex
|
34
|
+
puts ex.message
|
35
|
+
end
|
36
|
+
end
|
37
|
+
safe_go!
|
38
|
+
assert called,"main block wasn't called"
|
39
|
+
end
|
40
|
+
|
41
|
+
test "my main block gets the command-line parameters" do
|
42
|
+
params = []
|
43
|
+
main do |param1,param2,param3|
|
44
|
+
params << param1
|
45
|
+
params << param2
|
46
|
+
params << param3
|
47
|
+
end
|
48
|
+
set_argv %w(one two three)
|
49
|
+
safe_go!
|
50
|
+
assert_equal %w(one two three),params
|
51
|
+
end
|
52
|
+
|
53
|
+
test "my main block can freely ignore arguments given" do
|
54
|
+
called = false
|
55
|
+
main do
|
56
|
+
called = true
|
57
|
+
end
|
58
|
+
set_argv %w(one two three)
|
59
|
+
safe_go!
|
60
|
+
assert called,"Main block wasn't called?!"
|
61
|
+
end
|
62
|
+
|
63
|
+
test "my main block can ask for arguments that it might not receive" do
|
64
|
+
params = []
|
65
|
+
main do |param1,param2,param3|
|
66
|
+
params << param1
|
67
|
+
params << param2
|
68
|
+
params << param3
|
69
|
+
end
|
70
|
+
set_argv %w(one two)
|
71
|
+
safe_go!
|
72
|
+
assert_equal ['one','two',nil],params
|
73
|
+
end
|
74
|
+
|
75
|
+
test "go exits zero when main evaluates to nil or some other non number" do
|
76
|
+
[nil,'some string',Object.new,[],4.5].each do |non_number|
|
77
|
+
main { non_number }
|
78
|
+
assert_exits(0,"for value #{non_number}") { go! }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
test "go exits with the numeric value that main evaluated to" do
|
83
|
+
[0,1,2,3].each do |exit_status|
|
84
|
+
main { exit_status }
|
85
|
+
assert_exits(exit_status) { go! }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
test "go exits with 70, which is the Linux sysexits.h code for this sort of thing, if there's an exception" do
|
90
|
+
main do
|
91
|
+
raise "oh noes"
|
92
|
+
end
|
93
|
+
assert_exits(70) { go! }
|
94
|
+
assert_logged_at_error "oh noes"
|
95
|
+
end
|
96
|
+
|
97
|
+
test "go exits with the exit status included in the special-purpose excepiton" do
|
98
|
+
main do
|
99
|
+
raise Methadone::Error.new(4,"oh noes")
|
100
|
+
end
|
101
|
+
assert_exits(4) { go! }
|
102
|
+
assert_logged_at_error "oh noes"
|
103
|
+
end
|
104
|
+
|
105
|
+
test "can exit with a specific status by using the helper method instead of making a new exception" do
|
106
|
+
main do
|
107
|
+
exit_now!(4,"oh noes")
|
108
|
+
end
|
109
|
+
assert_exits(4) { go! }
|
110
|
+
assert_logged_at_error "oh noes"
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Calls go!, but traps the exit
|
116
|
+
def safe_go!
|
117
|
+
go!
|
118
|
+
rescue SystemExit
|
119
|
+
end
|
120
|
+
|
121
|
+
def assert_logged_at_error(expected_message)
|
122
|
+
assert @logged.include?(expected_message),"#{@logged} didn't include '#{expected_message}'"
|
123
|
+
end
|
124
|
+
|
125
|
+
def assert_exits(exit_code,message='',&block)
|
126
|
+
block.call
|
127
|
+
fail "Expected an exit of #{exit_code}, but we didn't even exit!"
|
128
|
+
rescue SystemExit => ex
|
129
|
+
assert_equal exit_code,ex.status,message
|
130
|
+
end
|
131
|
+
|
132
|
+
def set_argv(args)
|
133
|
+
ARGV.clear
|
134
|
+
args.each { |arg| ARGV << arg }
|
135
|
+
end
|
136
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: methadone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,22 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-10-01 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec-expectations
|
16
|
-
requirement: &
|
16
|
+
requirement: &70156777550060 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
21
|
+
version: '2.6'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70156777550060
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70156777549580 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,21 +32,21 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70156777549580
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rdoc
|
38
|
-
requirement: &
|
38
|
+
requirement: &70156777548800 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 3.
|
43
|
+
version: '3.9'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70156777548800
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: aruba
|
49
|
-
requirement: &
|
49
|
+
requirement: &70156777548320 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,18 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70156777548320
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: simplecov
|
60
|
+
requirement: &70156777547600 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0.5'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70156777547600
|
58
69
|
description: Methadone provides a lot of small but useful features for developing
|
59
70
|
a command-line app, including an opinionated bootstrapping process, some helpful
|
60
71
|
cucumber steps, and some classes to bridge logging and output into a simple, unified,
|
@@ -76,9 +87,12 @@ files:
|
|
76
87
|
- features/step_definitions/bootstrap_steps.rb
|
77
88
|
- features/support/env.rb
|
78
89
|
- lib/methadone.rb
|
90
|
+
- lib/methadone/cli.rb
|
79
91
|
- lib/methadone/cli_logger.rb
|
80
92
|
- lib/methadone/cli_logging.rb
|
81
93
|
- lib/methadone/cucumber.rb
|
94
|
+
- lib/methadone/error.rb
|
95
|
+
- lib/methadone/main.rb
|
82
96
|
- lib/methadone/version.rb
|
83
97
|
- methadone.gemspec
|
84
98
|
- templates/full/Rakefile.erb
|
@@ -90,6 +104,7 @@ files:
|
|
90
104
|
- test/base_test.rb
|
91
105
|
- test/test_cli_logger.rb
|
92
106
|
- test/test_cli_logging.rb
|
107
|
+
- test/test_main.rb
|
93
108
|
homepage: http://github.com/davetron5000/methadone
|
94
109
|
licenses: []
|
95
110
|
post_install_message:
|
@@ -121,3 +136,4 @@ test_files:
|
|
121
136
|
- test/base_test.rb
|
122
137
|
- test/test_cli_logger.rb
|
123
138
|
- test/test_cli_logging.rb
|
139
|
+
- test/test_main.rb
|