File: code_generation.rb

package info (click to toggle)
ruby-rack-mount 0.8.3-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 424 kB
  • ctags: 569
  • sloc: ruby: 4,100; yacc: 28; makefile: 3
file content (129 lines) | stat: -rw-r--r-- 3,881 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
module Rack::Mount
  module CodeGeneration #:nodoc:
    def _expired_recognize(env) #:nodoc:
      raise 'route set not finalized'
    end

    def rehash
      super
      optimize_recognize!
    end

    private
      def expire!
        if @optimized_recognize_defined
          remove_metaclass_method :recognize

          class << self
            alias_method :recognize, :_expired_recognize
          end

          @optimized_recognize_defined = false
        end

        super
      end

      def optimize_container_iterator(container)
        Utils.debug "optimizing container - size #{container.size}"

        body = []

        container.each_with_index { |route, i|
          body << "route = self[#{i}]"
          body << 'matches = {}'
          body << 'params = route.defaults.dup'

          conditions = []
          route.conditions.each do |(method, condition)|
            b = []
            if condition.is_a?(Regexp) || condition.respond_to?(:match)
              if condition.is_a?(Regexp)
                b << "if m = #{condition.inspect}.match(obj.#{method})"
                b << "matches[:#{method}] = m"
              elsif condition.class.instance_method(:match).arity == 1 ||
                  condition.class.instance_method(:match).arity <= -1
                b << "condition = route.conditions[#{method.inspect}]"
                b << "if m = condition.match(obj.#{method})"
                b << "matches[:#{method}] = m"
              else
                raise 'unexpected arity for condition\'s :match method'
              end
              if (named_captures = route.named_captures[method]) && named_captures.any?
                b << 'captures = m.captures'
                b << 'p = nil'
                b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = captures[#{j}]" }.join('; ')
              end
            else
              b << "if m = obj.#{method} == route.conditions[:#{method}]"
            end
            b << 'true'
            b << 'end'
            conditions << "(#{b.join('; ')})"
          end

          body << <<-RUBY
            if #{conditions.join(' && ')}
              yield route, matches, params
            end
          RUBY
        }

        container.instance_eval(<<-RUBY, __FILE__, __LINE__)
          def optimized_each(obj)
            #{body.join("\n")}
            nil
          end
        RUBY
      end

      def optimize_recognize!
        Utils.debug "optimizing recognize"

        uses_cache = false

        keys = @recognition_keys.map { |key|
          if key.respond_to?(:call_source)
            uses_cache = true
            key.call_source(:cache, :obj)
          else
            "obj.#{key}"
          end
        }.join(', ')

        @optimized_recognize_defined = true

        remove_metaclass_method :recognize

        instance_eval(<<-RUBY, __FILE__, __LINE__)
          def recognize(obj)
            #{"cache = {}" if uses_cache}
            container = @recognition_graph[#{keys}]
            optimize_container_iterator(container) unless container.respond_to?(:optimized_each)

            if block_given?
              container.optimized_each(obj) do |route, matches, params|
                yield route, matches, params
              end
            else
              container.optimized_each(obj) do |route, matches, params|
                return route, matches, params
              end
            end

            nil
          end
        RUBY
      end

      # method_defined? can't distinguish between instance
      # and meta methods. So we have to rescue if the method
      # has not been defined in the metaclass yet.
      def remove_metaclass_method(symbol)
        metaclass = class << self; self; end
        Utils.silence_debug { metaclass.send(:remove_method, symbol) }
      rescue NameError
        nil
      end
  end
end