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 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