Skip to content
This repository
tree: dd4822ce16
Fetching contributors…

Cannot retrieve contributors at this time

file 195 lines (156 sloc) 4.683 kb
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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
module ActionDispatch
  module Journey # :nodoc:
    module Path # :nodoc:
      class Pattern # :nodoc:
        attr_reader :spec, :requirements, :anchored

        def initialize strexp
          parser = Journey::Parser.new

          @anchored = true

          case strexp
          when String
            @spec = parser.parse strexp
            @requirements = {}
            @separators = "/.?"
          when Router::Strexp
            @spec = parser.parse strexp.path
            @requirements = strexp.requirements
            @separators = strexp.separators.join
            @anchored = strexp.anchor
          else
            raise "wtf bro: #{strexp}"
          end

          @names = nil
          @optional_names = nil
          @required_names = nil
          @re = nil
          @offsets = nil
        end

        def ast
          @spec.grep(Nodes::Symbol).each do |node|
            re = @requirements[node.to_sym]
            node.regexp = re if re
          end

          @spec.grep(Nodes::Star).each do |node|
            node = node.left
            node.regexp = @requirements[node.to_sym] || /(.+)/
          end

          @spec
        end

        def names
          @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
        end

        def required_names
          @required_names ||= names - optional_names
        end

        def optional_names
          @optional_names ||= spec.grep(Nodes::Group).map { |group|
            group.grep(Nodes::Symbol)
          }.flatten.map { |n| n.name }.uniq
        end

        class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
          attr_reader :offsets

          def initialize matchers
            @matchers = matchers
            @capture_count = [0]
          end

          def visit node
            super
            @capture_count
          end

          def visit_SYMBOL node
            node = node.to_sym

            if @matchers.key? node
              re = /#{@matchers[node]}|/
              @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
            else
              @capture_count << (@capture_count.last || 0)
            end
          end
        end

        class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
          def initialize separator, matchers
            @separator = separator
            @matchers = matchers
            @separator_re = "([^#{separator}]+)"
            super()
          end

          def accept node
            %r{\A#{visit node}\Z}
          end

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

          def visit_SYMBOL node
            node = node.to_sym

            return @separator_re unless @matchers.key? node

            re = @matchers[node]
            "(#{re})"
          end

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

          def visit_LITERAL node
            Regexp.escape node.left
          end
          alias :visit_DOT :visit_LITERAL

          def visit_SLASH node
            node.left
          end

          def visit_STAR node
            re = @matchers[node.left.to_sym] || '.+'
            "(#{re})"
          end
        end

        class UnanchoredRegexp < AnchoredRegexp # :nodoc:
          def accept node
            %r{\A#{visit node}}
          end
        end

        class MatchData # :nodoc:
          attr_reader :names

          def initialize names, offsets, match
            @names = names
            @offsets = offsets
            @match = match
          end

          def captures
            (length - 1).times.map { |i| self[i + 1] }
          end

          def [] x
            idx = @offsets[x - 1] + x
            @match[idx]
          end

          def length
            @offsets.length
          end

          def post_match
            @match.post_match
          end

          def to_s
            @match.to_s
          end
        end

        def match other
          return unless match = to_regexp.match(other)
          MatchData.new names, offsets, match
        end
        alias :=~ :match

        def source
          to_regexp.source
        end

        def to_regexp
          @re ||= regexp_visitor.new(@separators, @requirements).accept spec
        end

        private
        def regexp_visitor
          @anchored ? AnchoredRegexp : UnanchoredRegexp
        end

        def offsets
          return @offsets if @offsets

          viz = RegexpOffsets.new @requirements
          @offsets = viz.accept spec
        end
      end
    end
  end
end
Something went wrong with that request. Please try again.