367 lines
9.6 KiB
Ruby
367 lines
9.6 KiB
Ruby
# TODO: Fix formatting on member/collection methods
|
|
|
|
module Rails
|
|
module Upgrading
|
|
module FakeRouter
|
|
module ActionController
|
|
module Routing
|
|
class Routes
|
|
def self.setup
|
|
@redrawer = Rails::Upgrading::RouteRedrawer.new
|
|
end
|
|
|
|
def self.redrawer
|
|
@redrawer
|
|
end
|
|
|
|
def self.draw
|
|
yield @redrawer
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class RoutesUpgrader
|
|
def generate_new_routes
|
|
if has_routes_file?
|
|
upgrade_routes
|
|
else
|
|
raise FileNotFoundError, "Can't find your routes file [config/routes.rb]!"
|
|
end
|
|
end
|
|
|
|
def has_routes_file?
|
|
File.exists?("config/routes.rb")
|
|
end
|
|
|
|
def routes_code
|
|
File.read("config/routes.rb")
|
|
end
|
|
|
|
def upgrade_routes
|
|
FakeRouter::ActionController::Routing::Routes.setup
|
|
|
|
# Read and eval the file; our fake route mapper will capture
|
|
# the calls to draw routes and generate new route code
|
|
FakeRouter.module_eval(routes_code)
|
|
|
|
# Give the route set to the code generator and get its output
|
|
generator = RouteGenerator.new(FakeRouter::ActionController::Routing::Routes.redrawer.routes)
|
|
generator.generate
|
|
end
|
|
end
|
|
|
|
class RouteRedrawer
|
|
attr_accessor :routes
|
|
|
|
def self.stack
|
|
@stack
|
|
end
|
|
|
|
def self.stack=(val)
|
|
@stack = val
|
|
end
|
|
|
|
def initialize
|
|
@routes = []
|
|
|
|
# The old default route was actually two routes; we generate the new style
|
|
# one only if we haven't generated it for the first old default route.
|
|
@default_route_generated = false
|
|
|
|
# Setup the stack for parents; used use proper indentation
|
|
self.class.stack = [@routes]
|
|
end
|
|
|
|
def root(options)
|
|
debug "mapping root"
|
|
@routes << FakeRoute.new("/", options)
|
|
end
|
|
|
|
def connect(path, options={})
|
|
debug "connecting #{path}"
|
|
|
|
if (path == ":controller/:action/:id.:format" || path == ":controller/:action/:id")
|
|
if !@default_route_generated
|
|
current_parent << FakeRoute.new("/:controller(/:action(/:id))", {:default_route => true})
|
|
|
|
@default_route_generated = true
|
|
end
|
|
else
|
|
current_parent << FakeRoute.new(path, options)
|
|
end
|
|
end
|
|
|
|
def resources(*args)
|
|
if block_given?
|
|
parent = FakeResourceRoute.new(args.shift)
|
|
debug "mapping resources #{parent.name} with block"
|
|
|
|
parent = stack(parent) do
|
|
yield(self)
|
|
end
|
|
|
|
current_parent << parent
|
|
else
|
|
if args.last.is_a?(Hash)
|
|
current_parent << FakeResourceRoute.new(args.shift, args.pop)
|
|
debug "mapping resources #{current_parent.last.name} w/o block with args"
|
|
else
|
|
args.each do |a|
|
|
current_parent << FakeResourceRoute.new(a)
|
|
debug "mapping resources #{current_parent.last.name}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def resource(*args)
|
|
if block_given?
|
|
parent = FakeSingletonResourceRoute.new(args.shift)
|
|
debug "mapping resource #{parent.name} with block"
|
|
|
|
parent = stack(parent) do
|
|
yield(self)
|
|
end
|
|
|
|
current_parent << parent
|
|
else
|
|
if args.last.is_a?(Hash)
|
|
current_parent << FakeSingletonResourceRoute.new(args.shift, args.pop)
|
|
debug "mapping resources #{current_parent.last.name} w/o block with args"
|
|
else
|
|
args.each do |a|
|
|
current_parent << FakeSingletonResourceRoute.new(a)
|
|
debug "mapping resources #{current_parent.last.name}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def namespace(name, options = {})
|
|
debug "mapping namespace #{name}"
|
|
namespace = FakeNamespace.new(name, options)
|
|
|
|
namespace = stack(namespace) do
|
|
yield(self)
|
|
end
|
|
|
|
current_parent << namespace
|
|
end
|
|
|
|
def method_missing(m, *args)
|
|
debug "named route: #{m}"
|
|
current_parent << FakeRoute.new(args.shift, args.pop, m.to_s)
|
|
end
|
|
|
|
def self.indent
|
|
' ' * ((stack.length) * 2)
|
|
end
|
|
|
|
private
|
|
def debug(txt)
|
|
puts txt if ENV['DEBUG']
|
|
end
|
|
|
|
def stack(obj)
|
|
self.class.stack << obj
|
|
yield
|
|
self.class.stack.pop
|
|
end
|
|
|
|
def current_parent
|
|
self.class.stack.last
|
|
end
|
|
end
|
|
|
|
class RouteObject
|
|
def indent_lines(code_lines)
|
|
if code_lines.length > 1
|
|
code_lines.flatten.map {|l| "#{@indent}#{l.chomp}"}.join("\n") + "\n"
|
|
else
|
|
"#{@indent}#{code_lines.shift}"
|
|
end
|
|
end
|
|
|
|
def opts_to_string(opts)
|
|
opts.is_a?(Hash) ? opts.map {|k, v|
|
|
":#{k} => " + (v.is_a?(Hash) ? ('{ ' + opts_to_string(v) + ' }') : "#{value_to_string(v)}")
|
|
}.join(", ") : opts.to_s
|
|
end
|
|
|
|
def value_to_string(value)
|
|
case value
|
|
when Regexp then value.inspect
|
|
when String then "'" + value.to_s + "'"
|
|
else value.to_s
|
|
end
|
|
end
|
|
end
|
|
|
|
class FakeNamespace < RouteObject
|
|
attr_accessor :routes, :name, :options
|
|
|
|
def initialize(name, options = {})
|
|
@routes = []
|
|
@name, @options = name, options
|
|
@indent = RouteRedrawer.indent
|
|
end
|
|
|
|
def to_route_code
|
|
if !@options.empty?
|
|
options = ', ' + opts_to_string(@options)
|
|
else
|
|
options = ''
|
|
end
|
|
|
|
lines = ["namespace :#{@name}#{options} do", @routes.map {|r| r.to_route_code}, "end"]
|
|
|
|
indent_lines(lines)
|
|
end
|
|
|
|
def <<(val)
|
|
@routes << val
|
|
end
|
|
|
|
def last
|
|
@routes.last
|
|
end
|
|
end
|
|
|
|
class FakeRoute < RouteObject
|
|
attr_accessor :name, :path, :options
|
|
|
|
def initialize(path, options, name = "")
|
|
@path = path
|
|
@options = options || {}
|
|
@name = name
|
|
@indent = RouteRedrawer.indent
|
|
end
|
|
|
|
def to_route_code
|
|
if @options[:default_route]
|
|
indent_lines ["match '#{@path}'"]
|
|
else
|
|
base = "match '%s' => '%s#%s'"
|
|
extra_options = []
|
|
|
|
if not name.empty?
|
|
extra_options << ":as => :#{name}"
|
|
end
|
|
|
|
if @options[:requirements]
|
|
@options[:constraints] = @options.delete(:requirements)
|
|
end
|
|
|
|
if @options[:conditions]
|
|
@options[:via] = @options.delete(:conditions).delete(:method)
|
|
end
|
|
|
|
if @options[:method]
|
|
@options[:via] = @options.delete(:method).to_s
|
|
end
|
|
|
|
@options ||= {}
|
|
base = (base % [@path, @options.delete(:controller), (@options.delete(:action) || "index")])
|
|
opts = opts_to_string(@options)
|
|
|
|
route_pieces = ([base] + extra_options + [opts])
|
|
route_pieces.delete("")
|
|
|
|
indent_lines [route_pieces.join(", ")]
|
|
end
|
|
end
|
|
end
|
|
|
|
class FakeResourceRoute < RouteObject
|
|
attr_accessor :name, :children
|
|
|
|
def initialize(name, options = {})
|
|
@name = name
|
|
@children = []
|
|
@options = options
|
|
@indent = RouteRedrawer.indent
|
|
end
|
|
|
|
def to_route_code
|
|
# preserve :only & :except options
|
|
copied_options = @options.reject { |k,v| ![:only, :except].member?(k) }
|
|
unless copied_options.empty?
|
|
copied_options_str = ", " + copied_options.map { |k, v| "#{k.inspect} => #{v.inspect}" }.join(",")
|
|
end
|
|
|
|
if !@children.empty? || @options.has_key?(:collection) || @options.has_key?(:member)
|
|
prefix = ["#{route_method} :#{@name}#{copied_options_str} do"]
|
|
lines = prefix + custom_methods + [@children.map {|r| r.to_route_code}.join("\n"), "end"]
|
|
|
|
indent_lines(lines)
|
|
else
|
|
base = "#{route_method} :%s#{copied_options_str}"
|
|
indent_lines [base % [@name]]
|
|
end
|
|
end
|
|
|
|
def custom_methods
|
|
collection_code = generate_custom_methods_for(:collection)
|
|
member_code = generate_custom_methods_for(:member)
|
|
[collection_code, member_code]
|
|
end
|
|
|
|
def generate_custom_methods_for(group)
|
|
return "" unless @options[group]
|
|
|
|
method_code = []
|
|
|
|
RouteRedrawer.stack << self
|
|
@options[group].each do |name, methods|
|
|
[*methods].each do |method|
|
|
method_code << "#{method} :#{name}"
|
|
end
|
|
end
|
|
RouteRedrawer.stack.pop
|
|
|
|
indent_lines ["#{group} do", method_code, "end"].flatten
|
|
end
|
|
|
|
def route_method
|
|
"resources"
|
|
end
|
|
|
|
def <<(val)
|
|
@children << val
|
|
end
|
|
|
|
def last
|
|
@children.last
|
|
end
|
|
end
|
|
|
|
class FakeSingletonResourceRoute < FakeResourceRoute
|
|
def route_method
|
|
"resource"
|
|
end
|
|
end
|
|
|
|
class RouteGenerator
|
|
def initialize(routes)
|
|
@routes = routes
|
|
@new_code = ""
|
|
end
|
|
|
|
def generate
|
|
@new_code = @routes.map do |r|
|
|
r.to_route_code
|
|
end.join("\n")
|
|
|
|
"#{app_name.underscore.classify}::Application.routes.draw do\n#{@new_code}\nend\n"
|
|
end
|
|
|
|
private
|
|
def app_name
|
|
File.basename(Dir.pwd)
|
|
end
|
|
end
|
|
end
|
|
end
|