| 1 |
module ActionController |
|---|
| 2 |
module Routing |
|---|
| 3 |
class RouteBuilder |
|---|
| 4 |
attr_accessor :separators, :optional_separators |
|---|
| 5 |
|
|---|
| 6 |
def initialize |
|---|
| 7 |
self.separators = Routing::SEPARATORS |
|---|
| 8 |
self.optional_separators = %w( / ) |
|---|
| 9 |
end |
|---|
| 10 |
|
|---|
| 11 |
def separator_pattern(inverted = false) |
|---|
| 12 |
"[#{'^' if inverted}#{Regexp.escape(separators.join)}]" |
|---|
| 13 |
end |
|---|
| 14 |
|
|---|
| 15 |
def interval_regexp |
|---|
| 16 |
Regexp.new "(.*?)(#{separators.source}|$)" |
|---|
| 17 |
end |
|---|
| 18 |
|
|---|
| 19 |
def multiline_regexp?(expression) |
|---|
| 20 |
expression.options & Regexp::MULTILINE == Regexp::MULTILINE |
|---|
| 21 |
end |
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 |
|
|---|
| 25 |
|
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 |
|
|---|
| 29 |
def segments_for_route_path(path) |
|---|
| 30 |
rest, segments = path, [] |
|---|
| 31 |
|
|---|
| 32 |
until rest.empty? |
|---|
| 33 |
segment, rest = segment_for rest |
|---|
| 34 |
segments << segment |
|---|
| 35 |
end |
|---|
| 36 |
segments |
|---|
| 37 |
end |
|---|
| 38 |
|
|---|
| 39 |
|
|---|
| 40 |
|
|---|
| 41 |
def segment_for(string) |
|---|
| 42 |
segment = case string |
|---|
| 43 |
when /\A:(\w+)/ |
|---|
| 44 |
key = $1.to_sym |
|---|
| 45 |
case key |
|---|
| 46 |
when :controller then ControllerSegment.new(key) |
|---|
| 47 |
else DynamicSegment.new key |
|---|
| 48 |
end |
|---|
| 49 |
when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true) |
|---|
| 50 |
when /\A\?(.*?)\?/ |
|---|
| 51 |
returning segment = StaticSegment.new($1) do |
|---|
| 52 |
segment.is_optional = true |
|---|
| 53 |
end |
|---|
| 54 |
when /\A( |
|---|
| 55 |
when Regexp.new(separator_pattern) then |
|---|
| 56 |
returning segment = DividerSegment.new($&) do |
|---|
| 57 |
segment.is_optional = (optional_separators.include? $&) |
|---|
| 58 |
end |
|---|
| 59 |
end |
|---|
| 60 |
[segment, $~.post_match] |
|---|
| 61 |
end |
|---|
| 62 |
|
|---|
| 63 |
|
|---|
| 64 |
|
|---|
| 65 |
|
|---|
| 66 |
def divide_route_options(segments, options) |
|---|
| 67 |
options = options.dup |
|---|
| 68 |
|
|---|
| 69 |
if options[:namespace] |
|---|
| 70 |
options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}" |
|---|
| 71 |
options.delete(:path_prefix) |
|---|
| 72 |
options.delete(:name_prefix) |
|---|
| 73 |
options.delete(:namespace) |
|---|
| 74 |
end |
|---|
| 75 |
|
|---|
| 76 |
requirements = (options.delete(:requirements) || {}).dup |
|---|
| 77 |
defaults = (options.delete(:defaults) || {}).dup |
|---|
| 78 |
conditions = (options.delete(:conditions) || {}).dup |
|---|
| 79 |
|
|---|
| 80 |
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact |
|---|
| 81 |
options.each do |key, value| |
|---|
| 82 |
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements |
|---|
| 83 |
hash[key] = value |
|---|
| 84 |
end |
|---|
| 85 |
|
|---|
| 86 |
[defaults, requirements, conditions] |
|---|
| 87 |
end |
|---|
| 88 |
|
|---|
| 89 |
|
|---|
| 90 |
|
|---|
| 91 |
|
|---|
| 92 |
def assign_route_options(segments, defaults, requirements) |
|---|
| 93 |
route_requirements = {} |
|---|
| 94 |
|
|---|
| 95 |
segment_named = Proc.new do |key| |
|---|
| 96 |
segments.detect { |segment| segment.key == key if segment.respond_to?(:key) } |
|---|
| 97 |
end |
|---|
| 98 |
|
|---|
| 99 |
requirements.each do |key, requirement| |
|---|
| 100 |
segment = segment_named[key] |
|---|
| 101 |
if segment |
|---|
| 102 |
raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp) |
|---|
| 103 |
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} |
|---|
| 104 |
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" |
|---|
| 105 |
end |
|---|
| 106 |
if multiline_regexp?(requirement) |
|---|
| 107 |
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" |
|---|
| 108 |
end |
|---|
| 109 |
segment.regexp = requirement |
|---|
| 110 |
else |
|---|
| 111 |
route_requirements[key] = requirement |
|---|
| 112 |
end |
|---|
| 113 |
end |
|---|
| 114 |
|
|---|
| 115 |
defaults.each do |key, default| |
|---|
| 116 |
segment = segment_named[key] |
|---|
| 117 |
raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment |
|---|
| 118 |
segment.is_optional = true |
|---|
| 119 |
segment.default = default.to_param if default |
|---|
| 120 |
end |
|---|
| 121 |
|
|---|
| 122 |
assign_default_route_options(segments) |
|---|
| 123 |
ensure_required_segments(segments) |
|---|
| 124 |
route_requirements |
|---|
| 125 |
end |
|---|
| 126 |
|
|---|
| 127 |
|
|---|
| 128 |
|
|---|
| 129 |
|
|---|
| 130 |
def assign_default_route_options(segments) |
|---|
| 131 |
segments.each do |segment| |
|---|
| 132 |
next unless segment.is_a? DynamicSegment |
|---|
| 133 |
case segment.key |
|---|
| 134 |
when :action |
|---|
| 135 |
if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index' |
|---|
| 136 |
segment.default ||= 'index' |
|---|
| 137 |
segment.is_optional = true |
|---|
| 138 |
end |
|---|
| 139 |
when :id |
|---|
| 140 |
if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ '' |
|---|
| 141 |
segment.is_optional = true |
|---|
| 142 |
end |
|---|
| 143 |
end |
|---|
| 144 |
end |
|---|
| 145 |
end |
|---|
| 146 |
|
|---|
| 147 |
|
|---|
| 148 |
|
|---|
| 149 |
|
|---|
| 150 |
def ensure_required_segments(segments) |
|---|
| 151 |
allow_optional = true |
|---|
| 152 |
segments.reverse_each do |segment| |
|---|
| 153 |
allow_optional &&= segment.optional? |
|---|
| 154 |
if !allow_optional && segment.optional? |
|---|
| 155 |
unless segment.optionality_implied? |
|---|
| 156 |
warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required." |
|---|
| 157 |
end |
|---|
| 158 |
segment.is_optional = false |
|---|
| 159 |
elsif allow_optional && segment.respond_to?(:default) && segment.default |
|---|
| 160 |
|
|---|
| 161 |
segment.is_optional = true |
|---|
| 162 |
end |
|---|
| 163 |
end |
|---|
| 164 |
end |
|---|
| 165 |
|
|---|
| 166 |
|
|---|
| 167 |
def build(path, options) |
|---|
| 168 |
|
|---|
| 169 |
path = "/#{path}" unless path[0] == ?/ |
|---|
| 170 |
path = "#{path}/" unless path[-1] == ?/ |
|---|
| 171 |
|
|---|
| 172 |
path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix] |
|---|
| 173 |
|
|---|
| 174 |
segments = segments_for_route_path(path) |
|---|
| 175 |
defaults, requirements, conditions = divide_route_options(segments, options) |
|---|
| 176 |
requirements = assign_route_options(segments, defaults, requirements) |
|---|
| 177 |
|
|---|
| 178 |
route = Route.new |
|---|
| 179 |
|
|---|
| 180 |
route.segments = segments |
|---|
| 181 |
route.requirements = requirements |
|---|
| 182 |
route.conditions = conditions |
|---|
| 183 |
|
|---|
| 184 |
if !route.significant_keys.include?(:action) && !route.requirements[:action] |
|---|
| 185 |
route.requirements[:action] = "index" |
|---|
| 186 |
route.significant_keys << :action |
|---|
| 187 |
end |
|---|
| 188 |
|
|---|
| 189 |
|
|---|
| 190 |
|
|---|
| 191 |
|
|---|
| 192 |
if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION |
|---|
| 193 |
route.optimise = false |
|---|
| 194 |
end |
|---|
| 195 |
|
|---|
| 196 |
if !route.significant_keys.include?(:controller) |
|---|
| 197 |
raise ArgumentError, "Illegal route: the :controller must be specified!" |
|---|
| 198 |
end |
|---|
| 199 |
|
|---|
| 200 |
route |
|---|
| 201 |
end |
|---|
| 202 |
end |
|---|
| 203 |
end |
|---|
| 204 |
end |
|---|