From e883db02bce5d5f06628dc9eae51c8f526a4df97 Mon Sep 17 00:00:00 2001
From: Aaron Patterson <aaron.patterson@gmail.com>
Date: Tue, 20 May 2014 13:12:59 -0700
Subject: translate AST to a formatter before url generation

---
 actionpack/lib/action_dispatch/journey/route.rb    |  3 +-
 actionpack/lib/action_dispatch/journey/visitors.rb | 70 ++++++++++++++++++++++
 2 files changed, 72 insertions(+), 1 deletion(-)

(limited to 'actionpack/lib')

diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 2b399d3ee3..7535a62740 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -76,7 +76,8 @@ module ActionDispatch
           value.to_s == defaults[key].to_s && !required_parts.include?(key)
         end
 
-        Visitors::Formatter.new(path_options).accept(path.spec)
+        format = Visitors::FormatBuilder.new.accept(path.spec)
+        format.evaluate path_options
       end
 
       def optimized_path
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index 307830aa93..cc7e0b644b 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -2,6 +2,52 @@
 
 module ActionDispatch
   module Journey # :nodoc:
+    class Format
+      ESCAPE_PATH    = ->(value) { Router::Utils.escape_path(value) }
+      ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
+
+      class Parameter < Struct.new(:name, :escaper)
+        def escape(value); escaper.call value; end
+      end
+
+      def self.required_path(symbol)
+        Parameter.new symbol, ESCAPE_PATH
+      end
+
+      def self.required_segment(symbol)
+        Parameter.new symbol, ESCAPE_SEGMENT
+      end
+
+      def initialize(parts)
+        @parts      = parts
+        @children   = []
+        @parameters = []
+
+        parts.each_with_index do |object,i|
+          case object
+          when Journey::Format
+            @children << i
+          when Parameter
+            @parameters << i
+          end
+        end
+      end
+
+      def evaluate(hash)
+        parts = @parts.dup
+
+        @parameters.each do |index|
+          param = parts[index]
+          value = hash.fetch(param.name) { return ''.freeze }
+          parts[index] = param.escape value
+        end
+
+        @children.each { |index| parts[index] = parts[index].evaluate(hash) }
+
+        parts.join
+      end
+    end
+
     module Visitors # :nodoc:
       class Visitor # :nodoc:
         DISPATCH_CACHE = {}
@@ -45,6 +91,30 @@ module ActionDispatch
           end
       end
 
+      class FormatBuilder < Visitor # :nodoc:
+        def accept(node); Journey::Format.new(super); end
+        def terminal(node); [node.left]; end
+
+        def binary(node)
+          visit(node.left) + visit(node.right)
+        end
+
+        def visit_GROUP(n); [Journey::Format.new(unary(n))]; end
+
+        def visit_STAR(n)
+          [Journey::Format.required_path(n.left.to_sym)]
+        end
+
+        def visit_SYMBOL(n)
+          symbol = n.to_sym
+          if symbol == :controller
+            [Journey::Format.required_path(symbol)]
+          else
+            [Journey::Format.required_segment(symbol)]
+          end
+        end
+      end
+
       # Loop through the requirements AST
       class Each < Visitor # :nodoc:
         attr_reader :block
-- 
cgit v1.2.3