#!/usr/bin/env ruby

unless ENV['RAILS_ROOT']
  if File.directory?("config") && File.exists?("config/environment.rb")
    ENV['RAILS_ROOT'] = File.expand_path(".")
  else
    $stderr.puts("\nperf_bench: can't generate benchmarks unless RAILS_ROOT is set")
    $stderr.puts("\nbenchmarking aborted!")
    exit 1
  end
end

require "#{ENV['RAILS_ROOT']}/config/environment"
require 'application'

if ARGV.first == "help"
  puts <<-"endhelp"
  usage: railsbench generate_benchmarks -excluded_actions=<regexp> -excluded_controllers=<controller_list>
  1) loads your application\'s routes and generates a benchmarks.yml file
     containing benchmark definitions for each route found
  2) for named routes, benchmark names are derived from the route name
  3) for routes defined via map.connect, the benchmark name is derived
     from the controller and action
  4) for each controller, a benchmark consisting of all controller actions
     is generated (named after the controller file name)
  5) generates a benchmark named 'all_controllers', consisting of all
     benchmarks generated in step 4
  endhelp
  exit
end

excluded_controllers = ["application"]
excluded_actions = /delete|destroy/
ARGV.each do |arg|
  excluded_controllers += $1.split(/, */).compact if arg =~ /-excluded_controllers=(.*)/
  excluded_actions = Regexp.new($1.gsub(/,/, '|')) if arg =~ /-excluded_actions=(.*)/
end

benchmark_config = File.expand_path "#{RAILS_ROOT}/config/benchmarks.yml"

if File.exist? benchmark_config
  benchmarks = YAML::load(File.open(benchmark_config)) || {}
else
  benchmarks = {}
end

VALID_KEYS = %w(uri action controller new_session query_params)

def dump_entry(name, entry, io = $stdout)
  io.puts "#{name}:"
  if entry.is_a? Hash
    VALID_KEYS.each do |key|
      io.printf "    %-15s %s\n", key + ':', entry[key] if entry.has_key? key
    end
    io.puts
  elsif entry.is_a? String
    io.printf "    %s\n\n", entry
  else
    raise "unsupported YAML entry"
  end
end

unless Rails::VERSION::STRING >= "1.2"
  $stderr.puts "Rails version #{Rails::VERSION::STRING} is not supported. please use a 1.2.x or later variety."
  exit 1
end

rs = ActionController::Routing::Routes

named_routes_map = rs.named_routes.to_a.inject({}){|h,(name,route)| h[route] = name; h}
controller_action_map = {}
has_default_route = false

out = File.open(benchmark_config, "w")

rs.routes.to_a.each do |route|
  uri = route.segments.map(&:to_s).join
  (has_default_route = true; next) if uri == "/:controller/:action/:id/"
  controller = route.requirements[:controller].to_s
  action = route.requirements[:action].to_s
  controller_action = "#{controller}_#{action}"
  file_name = "#{controller}_controller"
  benchmark_name = (named_routes_map[route] || controller_action).to_s
  entry = (benchmarks[benchmark_name] || {}).reverse_merge "uri" => uri, "controller" => controller, "action" => action
  benchmarks.delete benchmark_name
  if action =~ excluded_actions
    $stderr.puts "ignored action: #{action}"
    benchmarks[file_name] = ((benchmarks[file_name]||"").split(/, */) - [benchmark_name]).uniq.join(', ')
    next
  end
  if excluded_controllers.include? controller
    $stderr.puts "ignored controller: #{controller}"
    benchmarks["all_controllers"] = ((benchmarks["all_controllers"]||"").split(/, */) - [file_name]).uniq.join(', ')
    next
  end
  dump_entry benchmark_name, entry, out
  controller_action_map[controller_action] = true
  benchmarks[file_name] = ((benchmarks[file_name]||"").split(/, */) + [benchmark_name]).uniq.join(', ')
  benchmarks["all_controllers"] = ((benchmarks["all_controllers"]||"").split(/, */) + [file_name]).uniq.join(', ')
end

if has_default_route
  $stderr.puts "warning: you are still using the default route"
  Dir["#{RAILS_ROOT}/app/controllers/*.rb"].each do |file|
    file_name = File.basename(file).sub(/\.rb$/,'')
    controller_name = file_name.sub(/_controller$/,'')
    if excluded_controllers.include? controller_name
      $stderr.puts "ignored controller: #{controller_name}"
      benchmarks["all_controllers"] = ((benchmarks["all_controllers"]||"").split(/, */) - [file_name]).uniq.join(', ')
      next
    end
    begin
      controller = file_name.classify.constantize
      controller.action_methods.map(&:to_s).each do |method|
        benchmark_name = "#{controller_name}_#{method}"
        next if controller_action_map[benchmark_name]
        uri = rs.generate({:controller => controller_name ,:action => method},{})
        entry = (benchmarks[benchmark_name] || {}).reverse_merge "uri" => uri, "controller" => controller_name, "action" => method
        benchmarks.delete benchmark_name
        if method =~ excluded_actions
          $stderr.puts "ignored action: #{method}"
          benchmarks[file_name] = ((benchmarks[file_name]||"").split(/, */) - [benchmark_name]).uniq.join(', ')
          next
        end
        dump_entry benchmark_name, entry, out
        benchmarks[file_name] = ((benchmarks[file_name]||"").split(/, */) + [benchmark_name]).uniq.join(', ')
        benchmarks["all_controllers"] = ((benchmarks["all_controllers"]||"").split(/, */) + [file_name]).uniq.join(', ')
      end
    rescue MissingSourceFile, NoMethodError, ActionController::RoutingError
    end
  end
end

# dump remaining benchmarks
all_entry = benchmarks.delete "all_controllers"

benchmarks.delete_if do |k,v|
  v.is_a?(Hash) && (v["action"].to_s =~ excluded_actions || v["controller"].to_s =~ excluded_controllers)
end

generated_controller_benchmarks, others = benchmarks.partition{|k,v| k =~ /_controller$/}

generated_controller_benchmarks.sort_by(&:first).each do |benchmark_name, entry|
  dump_entry benchmark_name, entry, out
end

dump_entry "all_controllers", all_entry, out

others.each do |benchmark_name, entry|
  dump_entry benchmark_name, entry, out
end

out.close unless out.nil?

__END__

#    Copyright (C) 2007, 2008  Stefan Kaes
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
