| 27 | | hash.delete k |
|---|
| 28 | | keys_to_delete << k |
|---|
| 29 | | end |
|---|
| 30 | | end |
|---|
| 31 | | hash |
|---|
| 32 | | end |
|---|
| 33 | | |
|---|
| 34 | | def test_condition(expression, condition) |
|---|
| 35 | | case condition |
|---|
| 36 | | when String then "(#{expression} == #{condition.inspect})" |
|---|
| 37 | | when Regexp then |
|---|
| 38 | | condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source |
|---|
| 39 | | "(#{condition.inspect} =~ #{expression})" |
|---|
| 40 | | when Array then |
|---|
| 41 | | conds = condition.collect do |condition| |
|---|
| 42 | | cond = test_condition(expression, condition) |
|---|
| 43 | | (cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})" |
|---|
| 44 | | end |
|---|
| 45 | | "(#{conds.join(' || ')})" |
|---|
| 46 | | when true then expression |
|---|
| 47 | | when nil then "! #{expression}" |
|---|
| 48 | | else |
|---|
| 49 | | raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil" |
|---|
| 50 | | end |
|---|
| 51 | | end |
|---|
| 52 | | end |
|---|
| 53 | | |
|---|
| 54 | | class Component #:nodoc: |
|---|
| 55 | | def dynamic?() false end |
|---|
| 56 | | def optional?() false end |
|---|
| 57 | | |
|---|
| 58 | | def key() nil end |
|---|
| 59 | | |
|---|
| 60 | | def self.new(string, *args) |
|---|
| 61 | | return super(string, *args) unless self == Component |
|---|
| 62 | | case string |
|---|
| 63 | | when /.*;.*/ then SubpathComponent.new(string.split(/;/), *args) |
|---|
| 64 | | when ':controller' then ControllerComponent.new(:controller, *args) |
|---|
| 65 | | when /^:(\w+)$/ then DynamicComponent.new($1, *args) |
|---|
| 66 | | when /^\*(\w+)$/ then PathComponent.new($1, *args) |
|---|
| 67 | | else StaticComponent.new(string, *args) |
|---|
| 68 | | end |
|---|
| 69 | | end |
|---|
| 70 | | end |
|---|
| 71 | | |
|---|
| 72 | | class SubpathComponent < Component #:nodoc: |
|---|
| 73 | | attr_reader :parts |
|---|
| 74 | | |
|---|
| 75 | | def initialize(parts, *args) |
|---|
| 76 | | @parts = parts.map { |part| Component.new(part, *args) } |
|---|
| 77 | | end |
|---|
| 78 | | |
|---|
| 79 | | def write_recognition(g) |
|---|
| 80 | | raise RoutingError, "Subpath components must occur last" unless g.after.empty? |
|---|
| 81 | | g.if("#{g.next_segment(true)} && #{g.next_segment}.include?(';')") do |gp| |
|---|
| 82 | | gp.line "subindex, subpath = 0, #{gp.next_segment}.split(/;/)" |
|---|
| 83 | | tweak_recognizer(gp).go |
|---|
| 84 | | gp.move_forward { |gpp| gpp.continue } |
|---|
| 85 | | end |
|---|
| 86 | | end |
|---|
| 87 | | |
|---|
| 88 | | def write_generation(g) |
|---|
| 89 | | raise RoutingError, "Subpath components must occur last" unless g.after.empty? |
|---|
| 90 | | tweak_generator(g).go |
|---|
| 91 | | end |
|---|
| 92 | | |
|---|
| 93 | | def key |
|---|
| 94 | | parts.map { |p| p.key } |
|---|
| 95 | | end |
|---|
| 96 | | |
|---|
| 97 | | private |
|---|
| 98 | | |
|---|
| 99 | | def tweak_recognizer(g) |
|---|
| 100 | | gg = g.dup |
|---|
| 101 | | |
|---|
| 102 | | gg.path_name = :subpath |
|---|
| 103 | | gg.base_segment_name = :subsegment |
|---|
| 104 | | gg.base_index_name = :subindex |
|---|
| 105 | | gg.depth = 0 |
|---|
| 106 | | |
|---|
| 107 | | gg.before, gg.current, gg.after = [], parts.first, (parts[1..-1] || []) |
|---|
| 108 | | |
|---|
| 109 | | gg |
|---|
| 110 | | end |
|---|
| 111 | | |
|---|
| 112 | | def tweak_generator(g) |
|---|
| 113 | | gg = g.dup |
|---|
| 114 | | gg.before, gg.current, gg.after = [], parts.first, (parts[1..-1] || []) |
|---|
| 115 | | gg.start_subpath! |
|---|
| 116 | | gg |
|---|
| 117 | | end |
|---|
| 118 | | end |
|---|
| 119 | | |
|---|
| 120 | | class StaticComponent < Component #:nodoc: |
|---|
| 121 | | attr_reader :value |
|---|
| 122 | | |
|---|
| 123 | | def initialize(value) |
|---|
| 124 | | @value = value |
|---|
| 125 | | end |
|---|
| 126 | | |
|---|
| 127 | | def write_recognition(g) |
|---|
| 128 | | g.if_next_matches(value) do |gp| |
|---|
| 129 | | gp.move_forward {|gpp| gpp.continue} |
|---|
| 130 | | end |
|---|
| 131 | | end |
|---|
| 132 | | |
|---|
| 133 | | def write_generation(g) |
|---|
| 134 | | g.add_segment(value) {|gp| gp.continue } |
|---|
| 135 | | end |
|---|
| 136 | | end |
|---|
| 137 | | |
|---|
| 138 | | class DynamicComponent < Component #:nodoc: |
|---|
| 139 | | attr_reader :key, :default |
|---|
| 140 | | attr_accessor :condition |
|---|
| 141 | | |
|---|
| 142 | | def dynamic?() true end |
|---|
| 143 | | def optional?() @optional end |
|---|
| 144 | | |
|---|
| 145 | | def default=(default) |
|---|
| 146 | | @optional = true |
|---|
| 147 | | @default = default |
|---|
| 148 | | end |
|---|
| 149 | | |
|---|
| 150 | | def initialize(key, options = {}) |
|---|
| 151 | | @key = key.to_sym |
|---|
| 152 | | @optional = false |
|---|
| 153 | | default, @condition = options[:default], options[:condition] |
|---|
| 154 | | self.default = default if options.key?(:default) |
|---|
| 155 | | end |
|---|
| 156 | | |
|---|
| 157 | | def default_check(g) |
|---|
| 158 | | presence = "#{g.hash_value(key, !! default)}" |
|---|
| 159 | | if default |
|---|
| 160 | | "!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})" |
|---|
| | 148 | "hash[:#{key}] == #{req.inspect}" |
|---|
| | 149 | end |
|---|
| | 150 | end |
|---|
| | 151 | requirement_conditions * ' && ' unless requirement_conditions.empty? |
|---|
| | 152 | end |
|---|
| | 153 | def generation_structure |
|---|
| | 154 | segments.last.string_structure segments[0..-2] |
|---|
| | 155 | end |
|---|
| | 156 | |
|---|
| | 157 | # Write and compile a +recognize+ method for this Route. |
|---|
| | 158 | def write_recognition |
|---|
| | 159 | # Create an if structure to extract the params from a match if it occurs. |
|---|
| | 160 | body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" |
|---|
| | 161 | body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" |
|---|
| | 162 | |
|---|
| | 163 | # Build the method declaration and compile it |
|---|
| | 164 | method_decl = "def recognize(path, env={})\n#{body}\nend" |
|---|
| | 165 | # puts "\n======================" |
|---|
| | 166 | # puts |
|---|
| | 167 | # p self |
|---|
| | 168 | # puts |
|---|
| | 169 | # puts method_decl |
|---|
| | 170 | # puts |
|---|
| | 171 | instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" |
|---|
| | 172 | method_decl |
|---|
| | 173 | end |
|---|
| | 174 | |
|---|
| | 175 | # Plugins may override this method to add other conditions, like checks on |
|---|
| | 176 | # host, subdomain, and so forth. Note that changes here only affect route |
|---|
| | 177 | # recognition, not generation. |
|---|
| | 178 | def recognition_conditions |
|---|
| | 179 | result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"] |
|---|
| | 180 | result << "conditions[:method] === env[:method]" if conditions[:method] |
|---|
| | 181 | result |
|---|
| | 182 | end |
|---|
| | 183 | |
|---|
| | 184 | # Build the regular expression pattern that will match this route. |
|---|
| | 185 | def recognition_pattern(wrap = true) |
|---|
| | 186 | pattern = '' |
|---|
| | 187 | segments.reverse_each do |segment| |
|---|
| | 188 | pattern = segment.build_pattern pattern |
|---|
| | 189 | end |
|---|
| | 190 | wrap ? ("\\A" + pattern + "\\Z") : pattern |
|---|
| | 191 | end |
|---|
| | 192 | |
|---|
| | 193 | # Write the code to extract the parameters from a matched route. |
|---|
| | 194 | def recognition_extraction |
|---|
| | 195 | next_capture = 1 |
|---|
| | 196 | extraction = segments.collect do |segment| |
|---|
| | 197 | x = segment.match_extraction next_capture |
|---|
| | 198 | next_capture += Regexp.new(segment.regexp_chunk).number_of_captures |
|---|
| | 199 | x |
|---|
| | 200 | end |
|---|
| | 201 | extraction.compact |
|---|
| | 202 | end |
|---|
| | 203 | |
|---|
| | 204 | # Write the real generation implementation and then resend the message. |
|---|
| | 205 | def generate(options, hash, expire_on = {}) |
|---|
| | 206 | write_generation |
|---|
| | 207 | generate options, hash, expire_on |
|---|
| | 208 | end |
|---|
| | 209 | |
|---|
| | 210 | def generate_extras(options, hash, expire_on = {}) |
|---|
| | 211 | write_generation |
|---|
| | 212 | generate_extras options, hash, expire_on |
|---|
| | 213 | end |
|---|
| | 214 | |
|---|
| | 215 | # Generate the query string with any extra keys in the hash and append |
|---|
| | 216 | # it to the given path, returning the new path. |
|---|
| | 217 | def append_query_string(path, hash) |
|---|
| | 218 | return nil unless path |
|---|
| | 219 | query = hash.keys.map(&:to_sym) - significant_keys |
|---|
| | 220 | "#{path}#{build_query_string(hash, query)}" |
|---|
| | 221 | end |
|---|
| | 222 | |
|---|
| | 223 | # Build a query string from the keys of the given hash. If +only_keys+ |
|---|
| | 224 | # is given (as an array), only the keys indicated will be used to build |
|---|
| | 225 | # the query string. The query string will correctly build array parameter |
|---|
| | 226 | # values. |
|---|
| | 227 | def build_query_string(hash, only_keys=nil) |
|---|
| | 228 | elements = [] |
|---|
| | 229 | |
|---|
| | 230 | only_keys ||= hash.keys |
|---|
| | 231 | |
|---|
| | 232 | only_keys.each do |key| |
|---|
| | 233 | value = hash[key] |
|---|
| | 234 | key = CGI.escape key.to_s |
|---|
| | 235 | if value.class == Array |
|---|
| | 236 | key << '[]' |
|---|
| | 237 | else |
|---|
| | 238 | value = [ value ] |
|---|
| | 239 | end |
|---|
| | 240 | value.each { |val| elements << "#{key}=#{CGI.escape(val.to_param.to_s)}" } |
|---|
| | 241 | end |
|---|
| | 242 | |
|---|
| | 243 | query_string = "?#{elements.join("&")}" unless elements.empty? |
|---|
| | 244 | query_string || "" |
|---|
| | 245 | end |
|---|
| | 246 | |
|---|
| | 247 | # Write the real recognition implementation and then resend the message. |
|---|
| | 248 | def recognize(path, environment={}) |
|---|
| | 249 | write_recognition |
|---|
| | 250 | recognize path, environment |
|---|
| | 251 | end |
|---|
| | 252 | |
|---|
| | 253 | # A route's parameter shell contains parameter values that are not in the |
|---|
| | 254 | # route's path, but should be placed in the recognized hash. |
|---|
| | 255 | # |
|---|
| | 256 | # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route: |
|---|
| | 257 | # |
|---|
| | 258 | # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ |
|---|
| | 259 | # |
|---|
| | 260 | def parameter_shell |
|---|
| | 261 | @parameter_shell ||= returning({}) do |shell| |
|---|
| | 262 | requirements.each do |key, requirement| |
|---|
| | 263 | shell[key] = requirement unless requirement.is_a? Regexp |
|---|
| | 264 | end |
|---|
| | 265 | end |
|---|
| | 266 | end |
|---|
| | 267 | |
|---|
| | 268 | # Return an array containing all the keys that are used in this route. This |
|---|
| | 269 | # includes keys that appear inside the path, and keys that have requirements |
|---|
| | 270 | # placed upon them. |
|---|
| | 271 | def significant_keys |
|---|
| | 272 | @significant_keys ||= returning [] do |sk| |
|---|
| | 273 | segments.each { |segment| sk << segment.key if segment.respond_to? :key } |
|---|
| | 274 | sk.concat requirements.keys |
|---|
| | 275 | sk.uniq! |
|---|
| | 276 | end |
|---|
| | 277 | end |
|---|
| | 278 | |
|---|
| | 279 | # Return a hash of key/value pairs representing the keys in the route that |
|---|
| | 280 | # have defaults, or which are specified by non-regexp requirements. |
|---|
| | 281 | def defaults |
|---|
| | 282 | @defaults ||= returning({}) do |hash| |
|---|
| | 283 | segments.each do |segment| |
|---|
| | 284 | next unless segment.respond_to? :default |
|---|
| | 285 | hash[segment.key] = segment.default unless segment.default.nil? |
|---|
| | 286 | end |
|---|
| | 287 | requirements.each do |key,req| |
|---|
| | 288 | next if Regexp === req || req.nil? |
|---|
| | 289 | hash[key] = req |
|---|
| | 290 | end |
|---|
| | 291 | end |
|---|
| | 292 | end |
|---|
| | 293 | |
|---|
| | 294 | def matches_controller_and_action?(controller, action) |
|---|
| | 295 | unless @matching_prepared |
|---|
| | 296 | @controller_requirement = requirement_for(:controller) |
|---|
| | 297 | @action_requirement = requirement_for(:action) |
|---|
| | 298 | @matching_prepared = true |
|---|
| | 299 | end |
|---|
| | 300 | |
|---|
| | 301 | (@controller_requirement.nil? || @controller_requirement === controller) && |
|---|
| | 302 | (@action_requirement.nil? || @action_requirement === action) |
|---|
| | 303 | end |
|---|
| | 304 | |
|---|
| | 305 | def to_s |
|---|
| | 306 | @to_s ||= segments.inject("") { |str,s| str << s.to_s } |
|---|
| | 307 | end |
|---|
| | 308 | |
|---|
| | 309 | protected |
|---|
| | 310 | |
|---|
| | 311 | def requirement_for(key) |
|---|
| | 312 | return requirements[key] if requirements.key? key |
|---|
| | 313 | segments.each do |segment| |
|---|
| | 314 | return segment.regexp if segment.respond_to?(:key) && segment.key == key |
|---|
| | 315 | end |
|---|
| | 316 | nil |
|---|
| | 317 | end |
|---|
| | 318 | |
|---|
| | 319 | end |
|---|
| | 320 | |
|---|
| | 321 | class Segment |
|---|
| | 322 | attr_accessor :is_optional |
|---|
| | 323 | alias_method :optional?, :is_optional |
|---|
| | 324 | |
|---|
| | 325 | def initialize |
|---|
| | 326 | self.is_optional = false |
|---|
| | 327 | end |
|---|
| | 328 | |
|---|
| | 329 | def extraction_code |
|---|
| | 330 | nil |
|---|
| | 331 | end |
|---|
| | 332 | |
|---|
| | 333 | # Continue generating string for the prior segments. |
|---|
| | 334 | def continue_string_structure(prior_segments) |
|---|
| | 335 | if prior_segments.empty? |
|---|
| | 336 | interpolation_statement(prior_segments) |
|---|
| 162 | | "! #{presence}" |
|---|
| 163 | | end |
|---|
| 164 | | end |
|---|
| 165 | | |
|---|
| 166 | | def write_generation(g) |
|---|
| 167 | | wrote_dropout = write_dropout_generation(g) |
|---|
| 168 | | write_continue_generation(g, wrote_dropout) |
|---|
| 169 | | end |
|---|
| 170 | | |
|---|
| 171 | | def write_dropout_generation(g) |
|---|
| 172 | | return false unless optional? && g.after.all? {|c| c.optional?} |
|---|
| 173 | | |
|---|
| 174 | | check = [default_check(g)] |
|---|
| 175 | | gp = g.dup # Use another generator to write the conditions after the first && |
|---|
| 176 | | # We do this to ensure that the generator will not assume x_value is set. It will |
|---|
| 177 | | # not be set if it follows a false condition -- for example, false && (x = 2) |
|---|
| 178 | | |
|---|
| 179 | | check += gp.after.map {|c| c.default_check gp} |
|---|
| 180 | | gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here |
|---|
| 181 | | true |
|---|
| 182 | | end |
|---|
| 183 | | |
|---|
| 184 | | def write_continue_generation(g, use_else) |
|---|
| 185 | | test = Routing.test_condition(g.hash_value(key, true, default), condition || true) |
|---|
| 186 | | check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test] |
|---|
| 187 | | |
|---|
| 188 | | g.send(*check) do |gp| |
|---|
| 189 | | gp.expire_for_keys(key) unless gp.after.empty? |
|---|
| 190 | | add_segments_to(gp) {|gpp| gpp.continue} |
|---|
| 191 | | end |
|---|
| 192 | | end |
|---|
| 193 | | |
|---|
| 194 | | def add_segments_to(g) |
|---|
| 195 | | g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp} |
|---|
| 196 | | end |
|---|
| 197 | | |
|---|
| 198 | | def recognition_check(g) |
|---|
| 199 | | test_type = [true, nil].include?(condition) ? :presence : :constraint |
|---|
| 200 | | |
|---|
| 201 | | prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : '' |
|---|
| 202 | | check = prefix + Routing.test_condition(g.next_segment(true), condition || true) |
|---|
| 203 | | |
|---|
| 204 | | g.if(check) {|gp| yield gp, test_type} |
|---|
| 205 | | end |
|---|
| 206 | | |
|---|
| 207 | | def write_recognition(g) |
|---|
| 208 | | test_type = nil |
|---|
| 209 | | recognition_check(g) do |gp, test_type| |
|---|
| 210 | | assign_result(gp) {|gpp| gpp.continue} |
|---|
| 211 | | end |
|---|
| 212 | | |
|---|
| 213 | | if optional? && g.after.all? {|c| c.optional?} |
|---|
| 214 | | call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"] |
|---|
| 215 | | |
|---|
| 216 | | g.send(*call) do |gp| |
|---|
| 217 | | assign_default(gp) |
|---|
| 218 | | gp.after.each {|c| c.assign_default(gp)} |
|---|
| 219 | | gp.finish(false) |
|---|
| 220 | | end |
|---|
| 221 | | end |
|---|
| 222 | | end |
|---|
| 223 | | |
|---|
| 224 | | def assign_result(g, with_default = false) |
|---|
| 225 | | g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})" |
|---|
| 226 | | g.move_forward {|gp| yield gp} |
|---|
| 227 | | end |
|---|
| 228 | | |
|---|
| 229 | | def assign_default(g) |
|---|
| 230 | | g.constant_result key, default unless default.nil? |
|---|
| 231 | | end |
|---|
| 232 | | end |
|---|
| 233 | | |
|---|
| 234 | | class ControllerComponent < DynamicComponent #:nodoc: |
|---|
| 235 | | def key() :controller end |
|---|
| 236 | | |
|---|
| 237 | | def add_segments_to(g) |
|---|
| 238 | | g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp} |
|---|
| 239 | | end |
|---|
| 240 | | |
|---|
| 241 | | def recognition_check(g) |
|---|
| 242 | | g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})" |
|---|
| 243 | | g.if('controller_result') do |gp| |
|---|
| 244 | | gp << 'controller_value, segments_to_controller = controller_result' |
|---|
| 245 | | if condition |
|---|
| 246 | | gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')" |
|---|
| 247 | | gp.if(Routing.test_condition("controller_path", condition)) do |gpp| |
|---|
| 248 | | gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint} |
|---|
| 249 | | end |
|---|
| 250 | | else |
|---|
| 251 | | gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint} |
|---|
| 252 | | end |
|---|
| 253 | | end |
|---|
| 254 | | end |
|---|
| 255 | | |
|---|
| 256 | | def assign_result(g) |
|---|
| 257 | | g.result key, 'controller_value' |
|---|
| 258 | | yield g |
|---|
| 259 | | end |
|---|
| 260 | | |
|---|
| 261 | | def assign_default(g) |
|---|
| 262 | | ControllerComponent.assign_controller(g, default) |
|---|
| 263 | | end |
|---|
| 264 | | |
|---|
| 265 | | class << self |
|---|
| 266 | | def assign_controller(g, controller) |
|---|
| 267 | | expr = "::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller" |
|---|
| 268 | | g.result :controller, expr, true |
|---|
| 269 | | end |
|---|
| 270 | | |
|---|
| 271 | | def traverse_to_controller(segments, start_at = 0) |
|---|
| 272 | | mod = ::Object |
|---|
| 273 | | length = segments.length |
|---|
| 274 | | index = start_at |
|---|
| 275 | | mod_name = controller_name = segment = nil |
|---|
| 276 | | |
|---|
| 277 | | while index < length |
|---|
| 278 | | return nil unless /^[A-Za-z][A-Za-z\d_]*$/ =~ (segment = segments[index]) |
|---|
| 279 | | index += 1 |
|---|
| 280 | | |
|---|
| 281 | | mod_name = segment.camelize |
|---|
| 282 | | controller_name = "#{mod_name}Controller" |
|---|
| 283 | | |
|---|
| 284 | | begin |
|---|
| 285 | | # We use eval instead of const_get to avoid obtaining values from parent modules. |
|---|
| 286 | | controller = eval("mod::#{controller_name}", nil, __FILE__, __LINE__) |
|---|
| 287 | | expected_name = "#{mod.name}::#{controller_name}" |
|---|
| 288 | | |
|---|
| 289 | | # Detect the case when const_get returns an object from a parent namespace. |
|---|
| 290 | | if controller.is_a?(Class) && controller.ancestors.include?(ActionController::Base) && (mod == Object || controller.name == expected_name) |
|---|
| 291 | | return controller, (index - start_at) |
|---|
| 292 | | end |
|---|
| 293 | | rescue NameError => e |
|---|
| 294 | | raise unless /^uninitialized constant .*#{controller_name}$/ =~ e.message |
|---|
| 295 | | end |
|---|
| 296 | | |
|---|
| 297 | | begin |
|---|
| 298 | | next_mod = eval("mod::#{mod_name}", nil, __FILE__, __LINE__) |
|---|
| 299 | | # Check that we didn't get a module from a parent namespace |
|---|
| 300 | | mod = (mod == Object || next_mod.name == "#{mod.name}::#{mod_name}") ? next_mod : nil |
|---|
| 301 | | rescue NameError => e |
|---|
| 302 | | raise unless /^uninitialized constant .*#{mod_name}$/ =~ e.message |
|---|
| 303 | | end |
|---|
| 304 | | |
|---|
| 305 | | return nil unless mod |
|---|
| 306 | | end |
|---|
| 307 | | end |
|---|
| 308 | | end |
|---|
| 309 | | end |
|---|
| 310 | | |
|---|
| 311 | | class PathComponent < DynamicComponent #:nodoc: |
|---|
| 312 | | def optional?() true end |
|---|
| 313 | | def default() [] end |
|---|
| 314 | | def condition() nil end |
|---|
| 315 | | |
|---|
| 316 | | def default=(value) |
|---|
| 317 | | raise RoutingError, "All path components have an implicit default of []" unless value == [] |
|---|
| 318 | | end |
|---|
| 319 | | |
|---|
| 320 | | def write_generation(g) |
|---|
| 321 | | raise RoutingError, 'Path components must occur last' unless g.after.empty? |
|---|
| 322 | | g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do |
|---|
| 323 | | g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)" |
|---|
| 324 | | g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish } |
|---|
| 325 | | end |
|---|
| 326 | | g.else { g.finish } |
|---|
| 327 | | end |
|---|
| 328 | | |
|---|
| 329 | | def write_recognition(g) |
|---|
| 330 | | raise RoutingError, "Path components must occur last" unless g.after.empty? |
|---|
| 331 | | |
|---|
| 332 | | start = g.index_name.to_s |
|---|
| 333 | | start = "(#{start})" unless /^\w+$/ =~ start.to_s |
|---|
| 334 | | |
|---|
| 335 | | value_expr = "#{g.path_name}[#{start}..-1] || []" |
|---|
| 336 | | g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})" |
|---|
| 337 | | g.finish(false) |
|---|
| 338 | | end |
|---|
| 339 | | |
|---|
| | 338 | new_priors = prior_segments[0..-2] |
|---|
| | 339 | prior_segments.last.string_structure(new_priors) |
|---|
| | 340 | end |
|---|
| | 341 | end |
|---|
| | 342 | |
|---|
| | 343 | # Return a string interpolation statement for this segment and those before it. |
|---|
| | 344 | def interpolation_statement(prior_segments) |
|---|
| | 345 | chunks = prior_segments.collect { |s| s.interpolation_chunk } |
|---|
| | 346 | chunks << interpolation_chunk |
|---|
| | 347 | "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}" |
|---|
| | 348 | end |
|---|
| | 349 | |
|---|
| | 350 | def string_structure(prior_segments) |
|---|
| | 351 | optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments) |
|---|
| | 352 | end |
|---|
| | 353 | |
|---|
| | 354 | # Return an if condition that is true if all the prior segments can be generated. |
|---|
| | 355 | # If there are no optional segments before this one, then nil is returned. |
|---|
| | 356 | def all_optionals_available_condition(prior_segments) |
|---|
| | 357 | optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact |
|---|
| | 358 | optional_locals.empty? ? nil : " if #{optional_locals * ' && '}" |
|---|
| | 359 | end |
|---|
| | 360 | |
|---|
| | 361 | # Recognition |
|---|
| | 362 | |
|---|
| | 363 | def match_extraction(next_capture) |
|---|
| | 364 | nil |
|---|
| | 365 | end |
|---|
| | 366 | |
|---|
| | 367 | # Warning |
|---|
| | 368 | |
|---|
| | 369 | # Returns true if this segment is optional? because of a default. If so, then |
|---|
| | 370 | # no warning will be emitted regarding this segment. |
|---|
| | 371 | def optionality_implied? |
|---|
| | 372 | false |
|---|
| | 373 | end |
|---|
| | 374 | |
|---|
| | 375 | end |
|---|
| | 376 | |
|---|
| | 377 | class StaticSegment < Segment |
|---|
| | 378 | attr_accessor :value, :raw |
|---|
| | 379 | alias_method :raw?, :raw |
|---|
| | 380 | |
|---|
| | 381 | def initialize(value = nil) |
|---|
| | 382 | super() |
|---|
| | 383 | self.value = value |
|---|
| | 384 | end |
|---|
| | 385 | |
|---|
| | 386 | def interpolation_chunk |
|---|
| | 387 | raw? ? value : CGI.escape(value) |
|---|
| | 388 | end |
|---|
| | 389 | |
|---|
| | 390 | def regexp_chunk |
|---|
| | 391 | chunk = Regexp.escape value |
|---|
| | 392 | optional? ? Regexp.optionalize(chunk) : chunk |
|---|
| | 393 | end |
|---|
| | 394 | |
|---|
| | 395 | def build_pattern(pattern) |
|---|
| | 396 | escaped = Regexp.escape(value) |
|---|
| | 397 | if optional? && ! pattern.empty? |
|---|
| | 398 | "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})" |
|---|
| | 399 | elsif optional? |
|---|
| | 400 | Regexp.optionalize escaped |
|---|
| | 401 | else |
|---|
| | 402 | escaped + pattern |
|---|
| | 403 | end |
|---|
| | 404 | end |
|---|
| | 405 | |
|---|
| | 406 | def to_s |
|---|
| | 407 | value |
|---|
| | 408 | end |
|---|
| | 409 | |
|---|
| | 410 | end |
|---|
| | 411 | |
|---|
| | 412 | class DividerSegment < StaticSegment |
|---|
| | 413 | |
|---|
| | 414 | def initialize(value = nil) |
|---|
| | 415 | super(value) |
|---|
| | 416 | self.raw = true |
|---|
| | 417 | self.is_optional = true |
|---|
| | 418 | end |
|---|
| | 419 | |
|---|
| | 420 | def optionality_implied? |
|---|
| | 421 | true |
|---|
| | 422 | end |
|---|
| | 423 | |
|---|
| | 424 | end |
|---|
| | 425 | |
|---|
| | 426 | class DynamicSegment < Segment |
|---|
| | 427 | attr_accessor :key, :default, :regexp |
|---|
| | 428 | |
|---|
| | 429 | def initialize(key = nil, options = {}) |
|---|
| | 430 | super() |
|---|
| | 431 | self.key = key |
|---|
| | 432 | self.default = options[:default] if options.key? :default |
|---|
| | 433 | self.is_optional = true if options[:optional] || options.key?(:default) |
|---|
| | 434 | end |
|---|
| | 435 | |
|---|
| | 436 | def to_s |
|---|
| | 437 | ":#{key}" |
|---|
| | 438 | end |
|---|
| | 439 | |
|---|
| | 440 | # The local variable name that the value of this segment will be extracted to. |
|---|
| | 441 | def local_name |
|---|
| | 442 | "#{key}_value" |
|---|
| | 443 | end |
|---|
| | 444 | |
|---|
| | 445 | def extract_value |
|---|
| | 446 | "#{local_name} = hash[:#{key}] #{"|| #{default.inspect}" if default}" |
|---|
| | 447 | end |
|---|
| | 448 | def value_check |
|---|
| | 449 | if default # Then we know it won't be nil |
|---|
| | 450 | "#{value_regexp.inspect} =~ #{local_name}" if regexp |
|---|
| | 451 | elsif optional? |
|---|
| | 452 | # If we have a regexp check that the value is not given, or that it matches. |
|---|
| | 453 | # If we have no regexp, return nil since we do not require a condition. |
|---|
| | 454 | "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp |
|---|
| | 455 | else # Then it must be present, and if we have a regexp, it must match too. |
|---|
| | 456 | "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}" |
|---|
| | 457 | end |
|---|
| | 458 | end |
|---|
| | 459 | def expiry_statement |
|---|
| | 460 | "not_expired, hash = false, options if not_expired && expire_on[:#{key}]" |
|---|
| | 461 | end |
|---|
| | 462 | |
|---|
| | 463 | def extraction_code |
|---|
| | 464 | s = extract_value |
|---|
| | 465 | vc = value_check |
|---|
| | 466 | s << "\nreturn [nil,nil] unless #{vc}" if vc |
|---|
| | 467 | s << "\n#{expiry_statement}" |
|---|
| | 468 | end |
|---|
| | 469 | |
|---|
| | 470 | def interpolation_chunk |
|---|
| | 471 | "\#{CGI.escape(#{local_name}.to_s)}" |
|---|
| | 472 | end |
|---|
| | 473 | |
|---|
| | 474 | def string_structure(prior_segments) |
|---|
| | 475 | if optional? # We have a conditional to do... |
|---|
| | 476 | # If we should not appear in the url, just write the code for the prior |
|---|
| | 477 | # segments. This occurs if our value is the default value, or, if we are |
|---|
| | 478 | # optional, if we have nil as our value. |
|---|
| | 479 | "if #{local_name} == #{default.inspect}\n" + |
|---|
| | 480 | continue_string_structure(prior_segments) + |
|---|
| | 481 | "\nelse\n" + # Otherwise, write the code up to here |
|---|
| | 482 | "#{interpolation_statement(prior_segments)}\nend" |
|---|
| | 483 | else |
|---|
| | 484 | interpolation_statement(prior_segments) |
|---|
| | 485 | end |
|---|
| | 486 | end |
|---|
| | 487 | |
|---|
| | 488 | def value_regexp |
|---|
| | 489 | Regexp.new "\\A#{regexp.source}\\Z" if regexp |
|---|
| | 490 | end |
|---|
| | 491 | def regexp_chunk |
|---|
| | 492 | regexp ? regexp.source : "([^#{Routing::SEPARATORS.join}]+)" |
|---|
| | 493 | end |
|---|
| | 494 | |
|---|
| | 495 | def build_pattern(pattern) |
|---|
| | 496 | chunk = regexp_chunk |
|---|
| | 497 | chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0 |
|---|
| | 498 | pattern = "#{chunk}#{pattern}" |
|---|
| | 499 | optional? ? Regexp.optionalize(pattern) : pattern |
|---|
| | 500 | end |
|---|
| | 501 | def match_extraction(next_capture) |
|---|
| | 502 | hangon = (default ? "|| #{default.inspect}" : "if match[#{next_capture}]") |
|---|
| | 503 | "params[:#{key}] = match[#{next_capture}] #{hangon}" |
|---|
| | 504 | end |
|---|
| | 505 | |
|---|
| | 506 | def optionality_implied? |
|---|
| | 507 | [:action, :id].include? key |
|---|
| | 508 | end |
|---|
| | 509 | |
|---|
| | 510 | end |
|---|
| | 511 | |
|---|
| | 512 | class ControllerSegment < DynamicSegment |
|---|
| | 513 | def regexp_chunk |
|---|
| | 514 | possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name } |
|---|
| | 515 | "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" |
|---|
| | 516 | end |
|---|
| | 517 | |
|---|
| | 518 | # Don't CGI.escape the controller name, since it may have slashes in it, |
|---|
| | 519 | # like admin/foo. |
|---|
| | 520 | def interpolation_chunk |
|---|
| | 521 | "\#{#{local_name}.to_s}" |
|---|
| | 522 | end |
|---|
| | 523 | |
|---|
| | 524 | # Make sure controller names like Admin/Content are correctly normalized to |
|---|
| | 525 | # admin/content |
|---|
| | 526 | def extract_value |
|---|
| | 527 | "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase" |
|---|
| | 528 | end |
|---|
| | 529 | |
|---|
| | 530 | def match_extraction(next_capture) |
|---|
| | 531 | hangon = (default ? "|| #{default.inspect}" : "if match[#{next_capture}]") |
|---|
| | 532 | "params[:#{key}] = match[#{next_capture}].downcase #{hangon}" |
|---|
| | 533 | end |
|---|
| | 534 | end |
|---|
| | 535 | |
|---|
| | 536 | class PathSegment < DynamicSegment |
|---|
| | 537 | EscapedSlash = CGI.escape("/") |
|---|
| | 538 | def interpolation_chunk |
|---|
| | 539 | "\#{CGI.escape(#{local_name}).gsub(#{EscapedSlash.inspect}, '/')}" |
|---|
| | 540 | end |
|---|
| | 541 | |
|---|
| | 542 | def default |
|---|
| | 543 | '' |
|---|
| | 544 | end |
|---|
| | 545 | |
|---|
| | 546 | def default=(path) |
|---|
| | 547 | raise RoutingError, "paths cannot have non-empty default values" unless path.blank? |
|---|
| | 548 | end |
|---|
| | 549 | |
|---|
| | 550 | def match_extraction(next_capture) |
|---|
| | 551 | "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}" |
|---|
| | 552 | end |
|---|
| | 553 | |
|---|
| | 554 | def regexp_chunk |
|---|
| | 555 | regexp || "(.*)" |
|---|
| | 556 | end |
|---|
| | 557 | |
|---|
| 344 | | end |
|---|
| 345 | | end |
|---|
| 346 | | end |
|---|
| 347 | | |
|---|
| 348 | | class Route #:nodoc: |
|---|
| 349 | | attr_accessor :components, :known |
|---|
| 350 | | attr_reader :path, :options, :keys, :defaults |
|---|
| 351 | | |
|---|
| 352 | | def initialize(path, options = {}) |
|---|
| 353 | | @path, @options = path, options |
|---|
| 354 | | |
|---|
| 355 | | initialize_components path |
|---|
| 356 | | defaults, conditions = initialize_hashes options.dup |
|---|
| 357 | | @defaults = defaults.dup |
|---|
| 358 | | @request_method = conditions.delete(:method) |
|---|
| 359 | | configure_components(defaults, conditions) |
|---|
| 360 | | add_default_requirements |
|---|
| 361 | | initialize_keys |
|---|
| 362 | | end |
|---|
| 363 | | |
|---|
| 364 | | def inspect |
|---|
| 365 | | "<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>" |
|---|
| 366 | | end |
|---|
| 367 | | |
|---|
| 368 | | def write_generation(generator = CodeGeneration::GenerationGenerator.new) |
|---|
| 369 | | generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || []) |
|---|
| 370 | | |
|---|
| 371 | | if known.empty? then generator.go |
|---|
| 372 | | else |
|---|
| 373 | | # Alter the conditions to allow :action => 'index' to also catch :action => nil |
|---|
| 374 | | altered_known = known.collect do |k, v| |
|---|
| 375 | | if k == :action && v== 'index' then [k, [nil, 'index']] |
|---|
| 376 | | else [k, v] |
|---|
| | 562 | end |
|---|
| | 563 | end |
|---|
| | 564 | end |
|---|
| | 565 | |
|---|
| | 566 | class RouteBuilder |
|---|
| | 567 | attr_accessor :separators, :optional_separators |
|---|
| | 568 | |
|---|
| | 569 | def initialize |
|---|
| | 570 | self.separators = Routing::SEPARATORS |
|---|
| | 571 | self.optional_separators = %w( / ) |
|---|
| | 572 | end |
|---|
| | 573 | |
|---|
| | 574 | def separator_pattern(inverted = false) |
|---|
| | 575 | "[#{'^' if inverted}#{Regexp.escape(separators.join)}]" |
|---|
| | 576 | end |
|---|
| | 577 | |
|---|
| | 578 | def interval_regexp |
|---|
| | 579 | Regexp.new "(.*?)(#{separators.source}|$)" |
|---|
| | 580 | end |
|---|
| | 581 | |
|---|
| | 582 | # Accepts a "route path" (a string defining a route), and returns the array |
|---|
| | 583 | # of segments that corresponds to it. Note that the segment array is only |
|---|
| | 584 | # partially initialized--the defaults and requirements, for instance, need |
|---|
| | 585 | # to be set separately, via the #assign_route_options method, and the |
|---|
| | 586 | # #optional? method for each segment will not be reliable until after |
|---|
| | 587 | # #assign_route_options is called, as well. |
|---|
| | 588 | def segments_for_route_path(path) |
|---|
| | 589 | rest, segments = path, [] |
|---|
| | 590 | |
|---|
| | 591 | until rest.empty? |
|---|
| | 592 | segment, rest = segment_for rest |
|---|
| | 593 | segments << segment |
|---|
| | 594 | end |
|---|
| | 595 | segments |
|---|
| | 596 | end |
|---|
| | 597 | |
|---|
| | 598 | # A factory method that returns a new segment instance appropriate for the |
|---|
| | 599 | # format of the given string. |
|---|
| | 600 | def segment_for(string) |
|---|
| | 601 | segment = case string |
|---|
| | 602 | when /\A:(\w+)/ |
|---|
| | 603 | key = $1.to_sym |
|---|
| | 604 | case key |
|---|
| | 605 | when :action then DynamicSegment.new(key, :default => 'index') |
|---|
| | 606 | when :id then DynamicSegment.new(key, :optional => true) |
|---|
| | 607 | when :controller then ControllerSegment.new(key) |
|---|
| | 608 | else DynamicSegment.new key |
|---|