methadone 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ html
6
6
  .*.sw?
7
7
  tmp
8
8
  results.html
9
+ coverage
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 error message to standard error and all messages to standard out
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 STDOUT AND STDERR
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}") # => goes to logfile.txt AND STDERR
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
- * Support for running external commands easily, with full error checking
147
- * Support for main method-style implementation
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
- Rake::RDocTask.new do |rd|
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
- def main(basedir,force)
10
- if Dir.exists? basedir
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
- copy_file "Rakefile"
31
- copy_file "test/tc_something.rb"
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
+
@@ -1,12 +1,21 @@
1
1
  require 'aruba/cucumber'
2
2
  require 'methadone/cucumber'
3
3
 
4
- ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
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
- end
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
@@ -1,3 +1,5 @@
1
1
  require 'methadone/version'
2
2
  require 'methadone/cli_logger'
3
3
  require 'methadone/cli_logging'
4
+ require 'methadone/main'
5
+ require 'methadone/error'
@@ -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
@@ -1,10 +1,20 @@
1
1
  require 'logger'
2
2
 
3
3
  module Methadone
4
- # A Logger appropriate for a command-line program in that it logs
5
- # all messages (based on level) to the standard output and logs "error" type
6
- # messages additionally to the standard error. By default, this will pretty
7
- # much do what you want, however it can be customized:
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.error("Something went wrong!") # => both standard error AND standard output get this
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
- proxy_method :add
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
@@ -1,3 +1,3 @@
1
1
  module Methadone
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
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.6.1")
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
@@ -1,3 +1,7 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter "/test"
4
+ end
1
5
  require 'test/unit'
2
6
  require 'rspec/expectations'
3
7
 
@@ -20,7 +20,28 @@ class TestCLILogger < BaseTest
20
20
  $stdout = @real_stdout
21
21
  end
22
22
 
23
- test "logger sends everything to stdout, and warns, errors, and fatals to stderr" do
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")
@@ -27,6 +27,7 @@ class TestCLILogging < BaseTest
27
27
  logger.formatter = proc do |severity,datetime,progname,msg|
28
28
  msg + "\n"
29
29
  end
30
+ logger.level = Logger::DEBUG
30
31
  end
31
32
 
32
33
  def doit
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.1.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-09-25 00:00:00.000000000Z
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: &70245763026000 !ruby/object:Gem::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: '0'
21
+ version: '2.6'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70245763026000
24
+ version_requirements: *70156777550060
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70245763024980 !ruby/object:Gem::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: *70245763024980
35
+ version_requirements: *70156777549580
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rdoc
38
- requirement: &70245763024340 !ruby/object:Gem::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.6.1
43
+ version: '3.9'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70245763024340
46
+ version_requirements: *70156777548800
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: aruba
49
- requirement: &70245763023660 !ruby/object:Gem::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: *70245763023660
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