aboutsummaryrefslogblamecommitdiffstats
path: root/actionpack/lib/action_dispatch/journey/visitors.rb
blob: 307830aa93c6797a3f696cff74e21780bc9d7d83 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                 
 
                     

                             
                             
                           
 

                        


               
 


                                                 
 




                                          
 



                                               
 






                                           







                                                       
             

         



                                         
                             


                        
                       
                          
               


           
                                      

               
                        


                                                    

                                                     

           
                          


                   

                                 


           




                                             

               


















                                                                              

         
                                          
                                         
                                       
 
                               
                             
                        


               






                                               
 





                                                
               
             
 


                            
 

                                                      
             
 

                                                   

             





                                                




                                   
                                                                             

                  
               
             

         
                                   




                      
                        












                        





                                                              
             
 




                                                              
             
 



                                                                    
 



                                                         
 



                                                          
 



                                                        
 



                                                        
 

                             
 

                                                               



         
# encoding: utf-8

module ActionDispatch
  module Journey # :nodoc:
    module Visitors # :nodoc:
      class Visitor # :nodoc:
        DISPATCH_CACHE = {}

        def accept(node)
          visit(node)
        end

        private

          def visit node
            send(DISPATCH_CACHE[node.type], node)
          end

          def binary(node)
            visit(node.left)
            visit(node.right)
          end
          def visit_CAT(n); binary(n); end

          def nary(node)
            node.children.each { |c| visit(c) }
          end
          def visit_OR(n); nary(n); end

          def unary(node)
            visit(node.left)
          end
          def visit_GROUP(n); unary(n); end
          def visit_STAR(n); unary(n); end

          def terminal(node); end
          def visit_LITERAL(n); terminal(n); end
          def visit_SYMBOL(n);  terminal(n); end
          def visit_SLASH(n);   terminal(n); end
          def visit_DOT(n);     terminal(n); end

          private_instance_methods(false).each do |pim|
            next unless pim =~ /^visit_(.*)$/
            DISPATCH_CACHE[$1.to_sym] = pim
          end
      end

      # Loop through the requirements AST
      class Each < Visitor # :nodoc:
        attr_reader :block

        def initialize(block)
          @block = block
        end

        def visit(node)
          block.call(node)
          super
        end
      end

      class String < Visitor # :nodoc:
        private

        def binary(node)
          [visit(node.left), visit(node.right)].join
        end

        def nary(node)
          node.children.map { |c| visit(c) }.join '|'
        end

        def terminal(node)
          node.left
        end

        def visit_GROUP(node)
          "(#{visit(node.left)})"
        end
      end

      class OptimizedPath < Visitor # :nodoc:
        def accept(node)
          Array(visit(node))
        end

        private

          def visit_CAT(node)
            [visit(node.left), visit(node.right)].flatten
          end

          def visit_SYMBOL(node)
            node.left[1..-1].to_sym
          end

          def visit_STAR(node)
            visit(node.left)
          end

          def visit_GROUP(node)
            []
          end

          %w{ LITERAL SLASH DOT }.each do |t|
            class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__
          end
      end

      # Used for formatting urls (url_for)
      class Formatter < Visitor # :nodoc:
        attr_reader :options, :consumed

        def initialize(options)
          @options  = options
          @consumed = {}
        end

        private
          def escape_path(value)
            Router::Utils.escape_path(value)
          end

          def escape_segment(value)
            Router::Utils.escape_segment(value)
          end

          def visit_GROUP(node)
            if consumed == options
              nil
            else
              route = visit(node.left)
              route.include?("\0") ? nil : route
            end
          end

          def terminal(node)
            node.left
          end

          def binary(node)
            [visit(node.left), visit(node.right)].join
          end

          def nary(node)
            node.children.map { |c| visit(c) }.join
          end

          def visit_STAR(node)
            if value = options[node.left.to_sym]
              escape_path(value)
            end
          end

          def visit_SYMBOL(node)
            key = node.to_sym

            if value = options[key]
              consumed[key] = value
              key == :controller ? escape_path(value) : escape_segment(value)
            else
              "\0"
            end
          end
      end

      class Dot < Visitor # :nodoc:
        def initialize
          @nodes = []
          @edges = []
        end

        def accept(node)
          super
          <<-eodot
  digraph parse_tree {
    size="8,5"
    node [shape = none];
    edge [dir = none];
    #{@nodes.join "\n"}
    #{@edges.join("\n")}
  }
          eodot
        end

        private

          def binary(node)
            node.children.each do |c|
              @edges << "#{node.object_id} -> #{c.object_id};"
            end
            super
          end

          def nary(node)
            node.children.each do |c|
              @edges << "#{node.object_id} -> #{c.object_id};"
            end
            super
          end

          def unary(node)
            @edges << "#{node.object_id} -> #{node.left.object_id};"
            super
          end

          def visit_GROUP(node)
            @nodes << "#{node.object_id} [label=\"()\"];"
            super
          end

          def visit_CAT(node)
            @nodes << "#{node.object_id} [label=\"\"];"
            super
          end

          def visit_STAR(node)
            @nodes << "#{node.object_id} [label=\"*\"];"
            super
          end

          def visit_OR(node)
            @nodes << "#{node.object_id} [label=\"|\"];"
            super
          end

          def terminal(node)
            value = node.left

            @nodes << "#{node.object_id} [label=\"#{value}\"];"
          end
      end
    end
  end
end