#!/usr/bin/env ruby
require 'pp'

require 'rubygems'
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
require 'nitpick'

# {Constant => {Method => [Warnings]}}
$warnings = Hash.new {|h,k| h[k] = Hash.new {|_h,_k| _h[_k] = [] }}

require 'trollop'
opts = Trollop.options do
  banner <<-BANNER
Nitpick is a lint-like static code analyzer for Ruby.

Usage: `nitpick [(--only str | --except str)*|-h] file1.rb file2.rb`

By default, nitpick will analyze *all* loaded code. To reduce the noise:

BANNER
  opt :only, "Nitpick only classes/modules that match this string.", :type => :string, :multi => true
  opt :except, "Don't nitpick classes/modules that match this string.", :type => :string, :multi => true
end

# TODO(kevinclark) 2009-03-19: Figure out how to make this print -h
Trollop.die "I need something to nitpick. Gimmie a filename or two. Or ten" if ARGV.size == 0

NitpickOptions = { :only => opts[:only] || [], :except => opts[:except] || []}
Nitpickers = [
  Nitpick::ArgumentNitpicker, Nitpick::BlockNitpicker, Nitpick::BranchNitpicker,
  Nitpick::LocalVariableNitpicker, Nitpick::MethodNitpicker, Nitpick::RescueNitpicker
]

class Module
  # So we don't set off -w
  alias_method :original_method_added, :method_added
  
  def method_added(name)
    original_method_added(name)
    
    # Except means don't match this
    return if NitpickOptions[:except].any? {|namespace| /#{namespace}/ =~ self.to_s }
    # Only means it must match one
    return unless NitpickOptions[:only].find {|namespace|  /#{namespace}/ =~ self.to_s } or
                    NitpickOptions[:only].empty?
    
    warnings = Nitpickers.map do |nitpicker_class|
      nitpicker = nitpicker_class.new(self, name)
      nitpicker.nitpick!
      nitpicker.warnings
    end.flatten
    
    $warnings[self.name][name.to_s] = warnings
  end
end

at_exit do
  # Prune empty classes and methods
  $warnings.each do |klass, methods|
    methods.each do |meth, warnings|
      $warnings[klass].delete(meth) if warnings.empty?
    end
    $warnings.delete(klass) if $warnings[klass].empty?
  end
  
  puts "Nothing to report boss! He's clean!" if $warnings.empty?
  
  class_names_in_order = $warnings.keys.sort
  
  class_names_in_order.each do |klass|
    puts "#{klass}"
    
    methods_in_order = $warnings[klass].keys.sort
    
    methods_in_order.each do |meth|
      puts "  #{meth}"
      
      $warnings[klass][meth].each do |w|
        puts "    - #{w.message}"
      end
    end
    puts
  end
end

ARGV.each do |file|
  begin
    load file
  rescue Exception => e # grab *everything*
    $stderr.puts "*** Nitpick had trouble loading #{file.inspect}:\n\t#{e.class} #{e.message}"
  end
end