Guest User

Untitled

a guest
Feb 21st, 2018
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.15 KB | None | 0 0
  1. # Put me in an initializer!
  2. module ActionController
  3. module Routing
  4. class RouteSet
  5. attr_accessor :routes
  6.  
  7. def initialize
  8. @routes = []
  9. @route_structure = {}
  10. end
  11.  
  12. def add_named_route(name, path, options = {})
  13. # TODO: Setup named routes hash so we can Merb-style url() calls
  14. add_route(path, options)
  15. end
  16.  
  17. def add_route(route_string, *args)
  18. # TODO: blah* parameters
  19. @route_structure ||= {}
  20. @routes ||= []
  21. params = []
  22. args = (args.pop || {})
  23.  
  24. # Set the request method; default to GET
  25. request_method = args[:conditions] && args[:conditions][:method] ? args[:conditions].delete(:method) : :get
  26.  
  27. # Grab the requirements from the :requirements param or from any arg that's a regex
  28. requirements = args.delete(:requirements) || {}
  29. args.each do |k, v|
  30. requirements[k.to_sym] = args[k] if v.is_a?(Regexp)
  31. end
  32.  
  33. # Create segment collection for local analysis of a route (i.e., parameter interpolation)
  34. local_segments = []
  35.  
  36. # Split the route string into segments
  37. segments = route_string.split("/").map! do |segment|
  38. next if segment.blank?
  39.  
  40. # Escape the segments so we can push them to a regex later on
  41. segment = local_segment = Regexp.escape(segment)
  42.  
  43. # If there's a dynamic symbol...
  44. if segment =~ /:\w+/
  45. # Grab all the symbols
  46. segment_symbols = segment.scan(/:(\w+)/).flatten
  47.  
  48. segment_symbols.each do |segment_symbol|
  49. # Make a note of the parameter name
  50. params << segment_symbol.to_sym
  51.  
  52. # This regex will be used to interpolate the parameters; the captures are finer grained
  53. local_segment = segment.gsub(/:#{segment_symbol}/, "((#{requirements[segment_symbol.to_sym] || '.*'}))")
  54. # This regex will be used to match the route to a path
  55. segment.gsub!(/:#{segment_symbol}/, "#{requirements[segment_symbol.to_sym] || '.*'}")
  56. end
  57. elsif segment =~ /^\*w+/
  58. # Route globbing
  59. params << segment.to_sym
  60. local_segment = segment.gsub(/:#{segment_symbol}/, "((#{requirements[segment_symbol.to_sym] || '.*'}))")
  61. segment.gsub!(/:#{segment_symbol}/, "#{requirements[segment_symbol.to_sym] || '.*'}")
  62. end
  63.  
  64. local_segments << local_segment
  65. segment
  66. end.compact
  67.  
  68. raise "Invalid route: Controller not specified" unless (params.include?(:controller) || args.keys.include?(:controller))
  69.  
  70. # Create the Route instance and add it to the route collection
  71. new_route = MyRoute.new(segments, local_segments, params, request_method, args)
  72. @routes << new_route
  73.  
  74. new_route.arguments[:controller] ||= :controller
  75. new_route.arguments[:action] ||= :action
  76.  
  77. # Create a tree structure for route generation
  78. @route_structure[request_method] ||= {}
  79. @route_structure[request_method][new_route.arguments[:controller]] ||= {}
  80. @route_structure[request_method][new_route.arguments[:controller]][new_route.arguments[:action]] ||= []
  81. @route_structure[request_method][new_route.arguments[:controller]][new_route.arguments[:action]] << new_route
  82.  
  83. new_route
  84. end
  85.  
  86. def recognize(request)
  87. # Normalize path
  88. path = (request.path.slice(0,1) == "/" ? request.path : "/#{request.path}")
  89.  
  90. # Default to GET for request method
  91. request_method = (request.request_method || :get)
  92.  
  93. matched = {}
  94. matches = captures = nil
  95. routeset = []
  96.  
  97. # Populate recognizer sets
  98. @recognizers ||= build_recognizers
  99.  
  100. # Iterate each set of recognizers
  101. @recognizers.each do |recognizer, routes|
  102. # Match path to recognizer
  103. matches = recognizer.match("#{request_method} #{path}")
  104.  
  105. # Grab set of routes + matched path
  106. if matches && !matches.captures.compact.blank?
  107. routeset = routes
  108. break
  109. end
  110. end
  111.  
  112. raise "No route matches that path" if routeset.blank?
  113.  
  114. # Match indexes of matched path and route
  115. if r = routeset[matches.captures.index(matches.captures.compact.first)]
  116. # Get parameter values
  117. params = r.params.clone
  118. param_matches = path.scan(/#{r.local_recognizer}/).flatten
  119.  
  120. param_list = {}
  121. r.params.each_with_index {|p,i| param_list[p] = param_matches[i]}
  122. matched = r.arguments.merge(matched).merge(param_list)
  123.  
  124. # Default action to index
  125. matched[:action] = 'index' if matched[:action] == :action
  126. end
  127.  
  128. # Populate request's parameters with arguments from request + static values
  129. request.path_parameters = matched
  130. "#{matched[:controller].camelize}Controller".constantize
  131. end
  132.  
  133. # TODO: I forgot what epic_fail does. Look that up.
  134. def generate(params, recall = {}, epic_fail = nil)
  135. # Default request method to GET
  136. request_method = params[:method] ? params[:method].to_sym : :get
  137.  
  138. # If we're given a controller...
  139. if params.keys.include?(:controller)
  140. # Grab controller routes
  141. controller_routes = @route_structure[request_method][params[:controller]]
  142.  
  143. unless controller_routes
  144. controller_routes = @route_structure[request_method][:controller]
  145. end
  146.  
  147. # ...then map action
  148. action_routes = controller_routes[(params[:action] || 'index')] || controller_routes[:action]
  149.  
  150. # Find route we're looking for with the right params
  151. action_routes.each do |route|
  152. if (route.params - params.keys).empty?
  153. return generate_url(route, params)
  154. else
  155. raise "No route to match that"
  156. end
  157. end
  158. else
  159. raise "No controller provided"
  160. end
  161. end
  162.  
  163. def generate_url(route, params)
  164. route_string = route.segments.join("/")
  165. return route_string unless route_string.include?("(.*)")
  166.  
  167. index = -1
  168. route_string.gsub!(/\(\.\*\)/) do |match|
  169. index += 1
  170. params[route.params[index]].to_param
  171. end
  172. end
  173.  
  174. def build_recognizers
  175. recognizers = []
  176. current_route_set = []
  177. current_segment = ""
  178.  
  179. @routes.each do |route|
  180. segment = "(^#{route.request_method} #{route.recognizer}$)"
  181.  
  182. # If our recognizer is getting too big, break it up and start a new one
  183. if ("#{current_segment}|#{segment}").length > 7730
  184. recognizers << [/#{current_segment}/, current_route_set]
  185. current_segment = segment
  186. current_route_set = [route]
  187. else
  188. # ...otherwise keep adding to the current recognizer
  189. current_segment = [current_segment, segment].reject{|s| s.blank?}.compact.join("|")
  190. current_route_set << route
  191. end
  192. end
  193.  
  194. # Clean up any left over segments and routes
  195. unless current_segment.blank?
  196. recognizers << [/#{current_segment}/, current_route_set]
  197. end
  198.  
  199. recognizers
  200. end
  201. end
  202.  
  203. class MyRoute
  204. # TODO: Add dynamic attribute so we can skip parameter interpolation if there aren't any
  205. attr_accessor :params, :segments, :arguments
  206. attr_reader :recognizer, :request_method, :local_recognizer
  207.  
  208. def initialize(segment_list, local_segments, param_list, request_method, argument_list = {})
  209. @segments = segment_list
  210. @params = param_list
  211. @arguments = argument_list || {}
  212. @recognizer = "/#{@segments.join("\/")}"
  213. @local_recognizer = "/#{local_segments.join("\/")}"
  214. @request_method = request_method
  215. end
  216. end
  217. end
  218. end
Add Comment
Please, Sign In to add comment