rack-mount 0.0.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +12 -4
- data/lib/rack/mount/analysis/histogram.rb +55 -6
- data/lib/rack/mount/analysis/splitting.rb +103 -89
- data/lib/rack/mount/code_generation.rb +120 -0
- data/lib/rack/mount/generatable_regexp.rb +95 -48
- data/lib/rack/mount/multimap.rb +84 -41
- data/lib/rack/mount/prefix.rb +13 -8
- data/lib/rack/mount/regexp_with_named_groups.rb +27 -7
- data/lib/rack/mount/route.rb +75 -18
- data/lib/rack/mount/route_set.rb +308 -22
- data/lib/rack/mount/strexp/parser.rb +160 -0
- data/lib/rack/mount/strexp/tokenizer.rb +83 -0
- data/lib/rack/mount/strexp.rb +54 -79
- data/lib/rack/mount/utils.rb +65 -174
- data/lib/rack/mount/vendor/regin/regin/alternation.rb +40 -0
- data/lib/rack/mount/vendor/regin/regin/anchor.rb +4 -0
- data/lib/rack/mount/vendor/regin/regin/atom.rb +54 -0
- data/lib/rack/mount/vendor/regin/regin/character.rb +51 -0
- data/lib/rack/mount/vendor/regin/regin/character_class.rb +50 -0
- data/lib/rack/mount/vendor/regin/regin/collection.rb +77 -0
- data/lib/rack/mount/vendor/regin/regin/expression.rb +126 -0
- data/lib/rack/mount/vendor/regin/regin/group.rb +90 -0
- data/lib/rack/mount/vendor/regin/regin/options.rb +55 -0
- data/lib/rack/mount/vendor/regin/regin/parser.rb +546 -0
- data/lib/rack/mount/vendor/regin/regin/tokenizer.rb +255 -0
- data/lib/rack/mount/vendor/regin/regin/version.rb +3 -0
- data/lib/rack/mount/vendor/regin/regin.rb +75 -0
- data/lib/rack/mount/version.rb +3 -0
- data/lib/rack/mount.rb +13 -17
- metadata +88 -35
- data/lib/rack/mount/analysis/frequency.rb +0 -51
- data/lib/rack/mount/const.rb +0 -45
- data/lib/rack/mount/exceptions.rb +0 -3
- data/lib/rack/mount/generation/route.rb +0 -57
- data/lib/rack/mount/generation/route_set.rb +0 -163
- data/lib/rack/mount/meta_method.rb +0 -104
- data/lib/rack/mount/mixover.rb +0 -47
- data/lib/rack/mount/recognition/code_generation.rb +0 -99
- data/lib/rack/mount/recognition/route.rb +0 -59
- data/lib/rack/mount/recognition/route_set.rb +0 -88
- data/lib/rack/mount/vendor/multimap/multimap.rb +0 -466
- data/lib/rack/mount/vendor/multimap/multiset.rb +0 -153
- data/lib/rack/mount/vendor/multimap/nested_multimap.rb +0 -156
    
        data/lib/rack/mount/strexp.rb
    CHANGED
    
    | @@ -1,93 +1,68 @@ | |
| 1 | 
            -
            require ' | 
| 1 | 
            +
            require 'rack/mount/strexp/parser'
         | 
| 2 2 |  | 
| 3 3 | 
             
            module Rack::Mount
         | 
| 4 | 
            -
              class Strexp | 
| 5 | 
            -
                 | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
                   | 
| 4 | 
            +
              class Strexp
         | 
| 5 | 
            +
                class << self
         | 
| 6 | 
            +
                  # Parses segmented string expression and converts it into a Regexp
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  #   Strexp.compile('foo')
         | 
| 9 | 
            +
                  #     # => %r{\Afoo\Z}
         | 
| 10 | 
            +
                  #
         | 
| 11 | 
            +
                  #   Strexp.compile('foo/:bar', {}, ['/'])
         | 
| 12 | 
            +
                  #     # => %r{\Afoo/(?<bar>[^/]+)\Z}
         | 
| 13 | 
            +
                  #
         | 
| 14 | 
            +
                  #   Strexp.compile(':foo.example.com')
         | 
| 15 | 
            +
                  #     # => %r{\A(?<foo>.+)\.example\.com\Z}
         | 
| 16 | 
            +
                  #
         | 
| 17 | 
            +
                  #   Strexp.compile('foo/:bar', {:bar => /[a-z]+/}, ['/'])
         | 
| 18 | 
            +
                  #     # => %r{\Afoo/(?<bar>[a-z]+)\Z}
         | 
| 19 | 
            +
                  #
         | 
| 20 | 
            +
                  #   Strexp.compile('foo(.:extension)')
         | 
| 21 | 
            +
                  #     # => %r{\Afoo(\.(?<extension>.+))?\Z}
         | 
| 22 | 
            +
                  #
         | 
| 23 | 
            +
                  #   Strexp.compile('src/*files')
         | 
| 24 | 
            +
                  #     # => %r{\Asrc/(?<files>.+)\Z}
         | 
| 25 | 
            +
                  def compile(str, requirements = {}, separators = [], anchor = true)
         | 
| 26 | 
            +
                    return Regexp.compile(str) if str.is_a?(Regexp)
         | 
| 26 27 |  | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 28 | 
            +
                    requirements = requirements ? requirements.dup : {}
         | 
| 29 | 
            +
                    normalize_requirements!(requirements, separators)
         | 
| 29 30 |  | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 31 | 
            +
                    parser = StrexpParser.new
         | 
| 32 | 
            +
                    parser.anchor = anchor
         | 
| 33 | 
            +
                    parser.requirements = requirements
         | 
| 33 34 |  | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
                  def normalize_requirements!(requirements, separators)
         | 
| 39 | 
            -
                    requirements.each do |key, value|
         | 
| 40 | 
            -
                      if value.is_a?(Regexp)
         | 
| 41 | 
            -
                        if regexp_has_modifiers?(value)
         | 
| 42 | 
            -
                          requirements[key] = value
         | 
| 43 | 
            -
                        else
         | 
| 44 | 
            -
                          requirements[key] = value.source
         | 
| 45 | 
            -
                        end
         | 
| 46 | 
            -
                      else
         | 
| 47 | 
            -
                        requirements[key] = Regexp.escape(value)
         | 
| 48 | 
            -
                      end
         | 
| 35 | 
            +
                    begin
         | 
| 36 | 
            +
                      re = parser.scan_str(str)
         | 
| 37 | 
            +
                    rescue Racc::ParseError => e
         | 
| 38 | 
            +
                      raise RegexpError, e.message
         | 
| 49 39 | 
             
                    end
         | 
| 50 | 
            -
                    requirements.default ||= separators.any? ?
         | 
| 51 | 
            -
                      "[^#{separators.join}]+" : '.+'
         | 
| 52 | 
            -
                    requirements
         | 
| 53 | 
            -
                  end
         | 
| 54 40 |  | 
| 55 | 
            -
             | 
| 56 | 
            -
                    re, pos, scanner = '', 0, StringScanner.new(str)
         | 
| 57 | 
            -
                    while scanner.scan_until(/(:|\\\*)([a-zA-Z_]\w*)/)
         | 
| 58 | 
            -
                      pre, pos = scanner.pre_match[pos..-1], scanner.pos
         | 
| 59 | 
            -
                      if pre =~ /(.*)\\\\\Z/
         | 
| 60 | 
            -
                        re << $1 + scanner.matched
         | 
| 61 | 
            -
                      else
         | 
| 62 | 
            -
                        name = scanner[2].to_sym
         | 
| 63 | 
            -
                        requirement = scanner[1] == ':' ?
         | 
| 64 | 
            -
                          requirements[name] : '.+'
         | 
| 65 | 
            -
                        re << pre + Const::REGEXP_NAMED_CAPTURE % [name, requirement]
         | 
| 66 | 
            -
                      end
         | 
| 67 | 
            -
                    end
         | 
| 68 | 
            -
                    re << scanner.rest
         | 
| 69 | 
            -
                    str.replace(re)
         | 
| 41 | 
            +
                    Regexp.compile(re)
         | 
| 70 42 | 
             
                  end
         | 
| 43 | 
            +
                  alias_method :new, :compile
         | 
| 71 44 |  | 
| 72 | 
            -
                   | 
| 73 | 
            -
                     | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 45 | 
            +
                  private
         | 
| 46 | 
            +
                    def normalize_requirements!(requirements, separators)
         | 
| 47 | 
            +
                      requirements.each do |key, value|
         | 
| 48 | 
            +
                        if value.is_a?(Regexp)
         | 
| 49 | 
            +
                          if regexp_has_modifiers?(value)
         | 
| 50 | 
            +
                            requirements[key] = value
         | 
| 51 | 
            +
                          else
         | 
| 52 | 
            +
                            requirements[key] = value.source
         | 
| 53 | 
            +
                          end
         | 
| 54 | 
            +
                        else
         | 
| 55 | 
            +
                          requirements[key] = Regexp.escape(value)
         | 
| 56 | 
            +
                        end
         | 
| 83 57 | 
             
                      end
         | 
| 58 | 
            +
                      requirements.default ||= separators.any? ?
         | 
| 59 | 
            +
                        "[^#{separators.join}]+" : '.+'
         | 
| 60 | 
            +
                      requirements
         | 
| 84 61 | 
             
                    end
         | 
| 85 | 
            -
                    re << scanner.rest
         | 
| 86 | 
            -
                    str.replace(re)
         | 
| 87 | 
            -
                  end
         | 
| 88 62 |  | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 63 | 
            +
                    def regexp_has_modifiers?(regexp)
         | 
| 64 | 
            +
                      regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                end
         | 
| 92 67 | 
             
              end
         | 
| 93 68 | 
             
            end
         | 
    
        data/lib/rack/mount/utils.rb
    CHANGED
    
    | @@ -1,5 +1,10 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            require ' | 
| 1 | 
            +
            begin
         | 
| 2 | 
            +
              require 'regin'
         | 
| 3 | 
            +
            rescue LoadError
         | 
| 4 | 
            +
              $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/regin'))
         | 
| 5 | 
            +
              require 'regin'
         | 
| 6 | 
            +
            end
         | 
| 7 | 
            +
             | 
| 3 8 | 
             
            require 'uri'
         | 
| 4 9 |  | 
| 5 10 | 
             
            module Rack::Mount
         | 
| @@ -9,6 +14,19 @@ module Rack::Mount | |
| 9 14 | 
             
              # more appropriate contexts.
         | 
| 10 15 | 
             
              #++
         | 
| 11 16 | 
             
              module Utils
         | 
| 17 | 
            +
                def silence_debug
         | 
| 18 | 
            +
                  old_debug, $DEBUG = $DEBUG, nil
         | 
| 19 | 
            +
                  yield
         | 
| 20 | 
            +
                ensure
         | 
| 21 | 
            +
                  $DEBUG = old_debug
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
                module_function :silence_debug
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def debug(msg)
         | 
| 26 | 
            +
                  warn "Rack::Mount #{msg}" if $DEBUG
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
                module_function :debug
         | 
| 29 | 
            +
             | 
| 12 30 | 
             
                # Normalizes URI path.
         | 
| 13 31 | 
             
                #
         | 
| 14 32 | 
             
                # Strips off trailing slash and ensures there is a leading slash.
         | 
| @@ -19,25 +37,26 @@ module Rack::Mount | |
| 19 37 | 
             
                #   normalize_path("")      # => "/"
         | 
| 20 38 | 
             
                def normalize_path(path)
         | 
| 21 39 | 
             
                  path = "/#{path}"
         | 
| 22 | 
            -
                  path.squeeze!( | 
| 23 | 
            -
                  path.sub!(%r{/+\Z},  | 
| 24 | 
            -
                  path =  | 
| 40 | 
            +
                  path.squeeze!('/')
         | 
| 41 | 
            +
                  path.sub!(%r{/+\Z}, '')
         | 
| 42 | 
            +
                  path = '/' if path == ''
         | 
| 25 43 | 
             
                  path
         | 
| 26 44 | 
             
                end
         | 
| 27 45 | 
             
                module_function :normalize_path
         | 
| 28 46 |  | 
| 29 47 | 
             
                # Removes trailing nils from array.
         | 
| 30 48 | 
             
                #
         | 
| 31 | 
            -
                #    | 
| 32 | 
            -
                #    | 
| 33 | 
            -
                #    | 
| 34 | 
            -
                 | 
| 35 | 
            -
             | 
| 49 | 
            +
                #   pop_trailing_blanks!([1, 2, 3])           # => [1, 2, 3]
         | 
| 50 | 
            +
                #   pop_trailing_blanks!([1, 2, 3, nil, ""])  # => [1, 2, 3]
         | 
| 51 | 
            +
                #   pop_trailing_blanks!([nil])               # => []
         | 
| 52 | 
            +
                #   pop_trailing_blanks!([""])                # => []
         | 
| 53 | 
            +
                def pop_trailing_blanks!(ary)
         | 
| 54 | 
            +
                  while ary.length > 0 && (ary.last.nil? || ary.last == '')
         | 
| 36 55 | 
             
                    ary.pop
         | 
| 37 56 | 
             
                  end
         | 
| 38 57 | 
             
                  ary
         | 
| 39 58 | 
             
                end
         | 
| 40 | 
            -
                module_function : | 
| 59 | 
            +
                module_function :pop_trailing_blanks!
         | 
| 41 60 |  | 
| 42 61 | 
             
                RESERVED_PCHAR = ':@&=+$,;%'
         | 
| 43 62 | 
             
                SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
         | 
| @@ -47,14 +66,16 @@ module Rack::Mount | |
| 47 66 | 
             
                  UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
         | 
| 48 67 | 
             
                end
         | 
| 49 68 |  | 
| 69 | 
            +
                Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
         | 
| 70 | 
            +
             | 
| 50 71 | 
             
                def escape_uri(uri)
         | 
| 51 | 
            -
                   | 
| 72 | 
            +
                  Parser.escape(uri.to_s, UNSAFE_PCHAR)
         | 
| 52 73 | 
             
                end
         | 
| 53 74 | 
             
                module_function :escape_uri
         | 
| 54 75 |  | 
| 55 76 | 
             
                if ''.respond_to?(:force_encoding)
         | 
| 56 77 | 
             
                  def unescape_uri(uri)
         | 
| 57 | 
            -
                     | 
| 78 | 
            +
                    Parser.unescape(uri).force_encoding('utf-8')
         | 
| 58 79 | 
             
                  end
         | 
| 59 80 | 
             
                else
         | 
| 60 81 | 
             
                  def unescape_uri(uri)
         | 
| @@ -72,33 +93,17 @@ module Rack::Mount | |
| 72 93 | 
             
                    }.join("&")
         | 
| 73 94 | 
             
                  when Hash
         | 
| 74 95 | 
             
                    value.map { |k, v|
         | 
| 75 | 
            -
                      build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
         | 
| 96 | 
            +
                      build_nested_query(v, prefix ? "#{prefix}[#{Rack::Utils.escape(k)}]" : Rack::Utils.escape(k))
         | 
| 76 97 | 
             
                    }.join("&")
         | 
| 77 98 | 
             
                  when String
         | 
| 78 99 | 
             
                    raise ArgumentError, "value must be a Hash" if prefix.nil?
         | 
| 79 | 
            -
                    "#{ | 
| 80 | 
            -
                  when NilClass
         | 
| 81 | 
            -
                    Rack::Utils.escape(prefix)
         | 
| 100 | 
            +
                    "#{prefix}=#{Rack::Utils.escape(value)}"
         | 
| 82 101 | 
             
                  else
         | 
| 83 | 
            -
                     | 
| 84 | 
            -
                      build_nested_query(value.to_param.to_s, prefix)
         | 
| 85 | 
            -
                    else
         | 
| 86 | 
            -
                      Rack::Utils.escape(prefix)
         | 
| 87 | 
            -
                    end
         | 
| 102 | 
            +
                    prefix
         | 
| 88 103 | 
             
                  end
         | 
| 89 104 | 
             
                end
         | 
| 90 105 | 
             
                module_function :build_nested_query
         | 
| 91 106 |  | 
| 92 | 
            -
                def normalize_extended_expression(regexp)
         | 
| 93 | 
            -
                  return regexp unless regexp.options & Regexp::EXTENDED != 0
         | 
| 94 | 
            -
                  source = regexp.source
         | 
| 95 | 
            -
                  source.gsub!(/#.+$/, '')
         | 
| 96 | 
            -
                  source.gsub!(/\s+/, '')
         | 
| 97 | 
            -
                  source.gsub!(/\\\//, '/')
         | 
| 98 | 
            -
                  Regexp.compile(source)
         | 
| 99 | 
            -
                end
         | 
| 100 | 
            -
                module_function :normalize_extended_expression
         | 
| 101 | 
            -
             | 
| 102 107 | 
             
                # Determines whether the regexp must match the entire string.
         | 
| 103 108 | 
             
                #
         | 
| 104 109 | 
             
                #   regexp_anchored?(/^foo$/) # => true
         | 
| @@ -106,166 +111,52 @@ module Rack::Mount | |
| 106 111 | 
             
                #   regexp_anchored?(/^foo/)  # => false
         | 
| 107 112 | 
             
                #   regexp_anchored?(/foo$/)  # => false
         | 
| 108 113 | 
             
                def regexp_anchored?(regexp)
         | 
| 109 | 
            -
             | 
| 114 | 
            +
                 regexp.source =~ /\A(\\A|\^).*(\\Z|\$)\Z/m ? true : false
         | 
| 110 115 | 
             
                end
         | 
| 111 116 | 
             
                module_function :regexp_anchored?
         | 
| 112 117 |  | 
| 113 | 
            -
                 | 
| 114 | 
            -
             | 
| 115 | 
            -
                # returned.
         | 
| 116 | 
            -
                #
         | 
| 117 | 
            -
                #   extract_static_regexp(/^foo$/)      # => "foo"
         | 
| 118 | 
            -
                #   extract_static_regexp(/^foo\.bar$/) # => "foo.bar"
         | 
| 119 | 
            -
                #   extract_static_regexp(/^foo|bar$/)  # => /^foo|bar$/
         | 
| 120 | 
            -
                def extract_static_regexp(regexp, options = nil)
         | 
| 121 | 
            -
                  if regexp.is_a?(String)
         | 
| 122 | 
            -
                    regexp = Regexp.compile("\\A#{regexp}\\Z", options)
         | 
| 123 | 
            -
                  end
         | 
| 124 | 
            -
             | 
| 125 | 
            -
                  # Just return if regexp is case-insensitive
         | 
| 126 | 
            -
                  return regexp if regexp.casefold?
         | 
| 127 | 
            -
             | 
| 118 | 
            +
                def normalize_extended_expression(regexp)
         | 
| 119 | 
            +
                  return regexp if (regexp.options & Regexp::EXTENDED) == 0
         | 
| 128 120 | 
             
                  source = regexp.source
         | 
| 129 | 
            -
                   | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
                        Regexp.compile("\\A(#{source})\\Z") =~ unescaped_source
         | 
| 134 | 
            -
                      return unescaped_source
         | 
| 135 | 
            -
                    end
         | 
| 136 | 
            -
                  end
         | 
| 137 | 
            -
                  regexp
         | 
| 138 | 
            -
                end
         | 
| 139 | 
            -
                module_function :extract_static_regexp
         | 
| 140 | 
            -
             | 
| 141 | 
            -
                if Const::SUPPORTS_NAMED_CAPTURES
         | 
| 142 | 
            -
                  NAMED_CAPTURE_REGEXP = /\?<([^>]+)>/
         | 
| 143 | 
            -
                else
         | 
| 144 | 
            -
                  NAMED_CAPTURE_REGEXP = /\?:<([^>]+)>/
         | 
| 145 | 
            -
                end
         | 
| 146 | 
            -
             | 
| 147 | 
            -
                # Strips shim named capture syntax and returns a clean Regexp and
         | 
| 148 | 
            -
                # an ordered array of the named captures.
         | 
| 149 | 
            -
                #
         | 
| 150 | 
            -
                #   extract_named_captures(/[a-z]+/)          # => /[a-z]+/, []
         | 
| 151 | 
            -
                #   extract_named_captures(/(?:<foo>[a-z]+)/) # => /([a-z]+)/, ['foo']
         | 
| 152 | 
            -
                #   extract_named_captures(/([a-z]+)(?:<foo>[a-z]+)/)
         | 
| 153 | 
            -
                #     # => /([a-z]+)([a-z]+)/, [nil, 'foo']
         | 
| 154 | 
            -
                def extract_named_captures(regexp)
         | 
| 155 | 
            -
                  options = regexp.is_a?(Regexp) ? regexp.options : nil
         | 
| 156 | 
            -
                  source = Regexp.compile(regexp).source
         | 
| 157 | 
            -
                  names, scanner = [], StringScanner.new(source)
         | 
| 158 | 
            -
             | 
| 159 | 
            -
                  while scanner.skip_until(/\(/)
         | 
| 160 | 
            -
                    if scanner.scan(NAMED_CAPTURE_REGEXP)
         | 
| 161 | 
            -
                      names << scanner[1]
         | 
| 162 | 
            -
                    else
         | 
| 163 | 
            -
                      names << nil
         | 
| 164 | 
            -
                    end
         | 
| 165 | 
            -
                  end
         | 
| 166 | 
            -
             | 
| 167 | 
            -
                  names = [] unless names.any?
         | 
| 168 | 
            -
                  source.gsub!(NAMED_CAPTURE_REGEXP, Const::EMPTY_STRING)
         | 
| 169 | 
            -
                  return Regexp.compile(source, options), names
         | 
| 121 | 
            +
                  source.gsub!(/#.+$/, '')
         | 
| 122 | 
            +
                  source.gsub!(/\s+/, '')
         | 
| 123 | 
            +
                  source.gsub!(/\\\//, '/')
         | 
| 124 | 
            +
                  Regexp.compile(source)
         | 
| 170 125 | 
             
                end
         | 
| 171 | 
            -
                module_function : | 
| 172 | 
            -
             | 
| 173 | 
            -
                class Capture < Array #:nodoc:
         | 
| 174 | 
            -
                  attr_reader :name, :optional
         | 
| 175 | 
            -
                  alias_method :optional?, :optional
         | 
| 176 | 
            -
             | 
| 177 | 
            -
                  def initialize(*args)
         | 
| 178 | 
            -
                    options = args.last.is_a?(Hash) ? args.pop : {}
         | 
| 179 | 
            -
             | 
| 180 | 
            -
                    @name = options.delete(:name)
         | 
| 181 | 
            -
                    @name = @name.to_s if @name
         | 
| 182 | 
            -
             | 
| 183 | 
            -
                    @optional = options.delete(:optional) || false
         | 
| 184 | 
            -
             | 
| 185 | 
            -
                    super(args)
         | 
| 186 | 
            -
                  end
         | 
| 187 | 
            -
             | 
| 188 | 
            -
                  def ==(obj)
         | 
| 189 | 
            -
                    obj.is_a?(Capture) && @name == obj.name && @optional == obj.optional && super
         | 
| 190 | 
            -
                  end
         | 
| 191 | 
            -
             | 
| 192 | 
            -
                  def optionalize!
         | 
| 193 | 
            -
                    @optional = true
         | 
| 194 | 
            -
                    self
         | 
| 195 | 
            -
                  end
         | 
| 196 | 
            -
             | 
| 197 | 
            -
                  def named?
         | 
| 198 | 
            -
                    name && name != Const::EMPTY_STRING
         | 
| 199 | 
            -
                  end
         | 
| 200 | 
            -
             | 
| 201 | 
            -
                  def to_s
         | 
| 202 | 
            -
                    source = "(#{join})"
         | 
| 203 | 
            -
                    source << '?' if optional?
         | 
| 204 | 
            -
                    source
         | 
| 205 | 
            -
                  end
         | 
| 126 | 
            +
                module_function :normalize_extended_expression
         | 
| 206 127 |  | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
                  end
         | 
| 128 | 
            +
                def parse_regexp(regexp)
         | 
| 129 | 
            +
                  cache = @@_parse_regexp_cache ||= {}
         | 
| 210 130 |  | 
| 211 | 
            -
                   | 
| 212 | 
            -
                     | 
| 131 | 
            +
                  if expression = cache[regexp]
         | 
| 132 | 
            +
                    return expression
         | 
| 213 133 | 
             
                  end
         | 
| 214 | 
            -
                end
         | 
| 215 134 |  | 
| 216 | 
            -
                def extract_regexp_parts(regexp) #:nodoc:
         | 
| 217 135 | 
             
                  unless regexp.is_a?(RegexpWithNamedGroups)
         | 
| 218 136 | 
             
                    regexp = RegexpWithNamedGroups.new(regexp)
         | 
| 219 137 | 
             
                  end
         | 
| 220 138 |  | 
| 221 | 
            -
                   | 
| 222 | 
            -
                    regexp, names = extract_named_captures(regexp)
         | 
| 223 | 
            -
                  else
         | 
| 224 | 
            -
                    names = regexp.names
         | 
| 225 | 
            -
                  end
         | 
| 226 | 
            -
                  source = regexp.source
         | 
| 227 | 
            -
             | 
| 228 | 
            -
                  source =~ /^(\\A|\^)/ ? source.gsub!(/^(\\A|\^)/, Const::EMPTY_STRING) :
         | 
| 229 | 
            -
                    raise(ArgumentError, "#{source} needs to match the start of the string")
         | 
| 230 | 
            -
             | 
| 231 | 
            -
                  scanner = StringScanner.new(source)
         | 
| 232 | 
            -
                  stack = [[]]
         | 
| 233 | 
            -
             | 
| 234 | 
            -
                  capture_index = 0
         | 
| 235 | 
            -
                  until scanner.eos?
         | 
| 236 | 
            -
                    char = scanner.getch
         | 
| 237 | 
            -
                    cur  = stack.last
         | 
| 238 | 
            -
             | 
| 239 | 
            -
                    escaped = cur.last.is_a?(String) && cur.last[-1, 1] == '\\'
         | 
| 139 | 
            +
                  expression = Regin.parse(regexp)
         | 
| 240 140 |  | 
| 241 | 
            -
             | 
| 242 | 
            -
             | 
| 243 | 
            -
                       | 
| 244 | 
            -
             | 
| 245 | 
            -
             | 
| 246 | 
            -
             | 
| 247 | 
            -
             | 
| 248 | 
            -
                       | 
| 249 | 
            -
             | 
| 250 | 
            -
                      capture_index += 1
         | 
| 251 | 
            -
                      cur.push(capture)
         | 
| 252 | 
            -
                      stack.push(capture)
         | 
| 253 | 
            -
                    elsif char == ')'
         | 
| 254 | 
            -
                      capture = stack.pop
         | 
| 255 | 
            -
                      if scanner.peek(1) == '?'
         | 
| 256 | 
            -
                        scanner.pos += 1
         | 
| 257 | 
            -
                        capture.optionalize!
         | 
| 141 | 
            +
                  unless Regin.regexp_supports_named_captures?
         | 
| 142 | 
            +
                    tag_captures = Proc.new do |group|
         | 
| 143 | 
            +
                      case group
         | 
| 144 | 
            +
                      when Regin::Group
         | 
| 145 | 
            +
                        # TODO: dup instead of mutating
         | 
| 146 | 
            +
                        group.instance_variable_set('@name', regexp.names[group.index]) if group.index
         | 
| 147 | 
            +
                        tag_captures.call(group.expression)
         | 
| 148 | 
            +
                      when Regin::Expression
         | 
| 149 | 
            +
                        group.each { |child| tag_captures.call(child) }
         | 
| 258 150 | 
             
                      end
         | 
| 259 | 
            -
                    elsif char == '$'
         | 
| 260 | 
            -
                      cur.push(Const::NULL)
         | 
| 261 | 
            -
                    else
         | 
| 262 | 
            -
                      cur.push('') unless cur.last.is_a?(String)
         | 
| 263 | 
            -
                      cur.last << char
         | 
| 264 151 | 
             
                    end
         | 
| 152 | 
            +
                    tag_captures.call(expression)
         | 
| 265 153 | 
             
                  end
         | 
| 266 154 |  | 
| 267 | 
            -
                   | 
| 155 | 
            +
                  cache[regexp] = expression.freeze
         | 
| 156 | 
            +
                  expression
         | 
| 157 | 
            +
                rescue Racc::ParseError, Regin::Parser::ScanError
         | 
| 158 | 
            +
                  []
         | 
| 268 159 | 
             
                end
         | 
| 269 | 
            -
                module_function : | 
| 160 | 
            +
                module_function :parse_regexp
         | 
| 270 161 | 
             
              end
         | 
| 271 162 | 
             
            end
         | 
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            module Regin
         | 
| 2 | 
            +
              class Alternation < Collection
         | 
| 3 | 
            +
                def initialize(*args)
         | 
| 4 | 
            +
                  args, options = extract_options(args)
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  if args.length == 1 && args.first.instance_of?(Array)
         | 
| 7 | 
            +
                    super(args.first)
         | 
| 8 | 
            +
                  else
         | 
| 9 | 
            +
                    super(args)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  if options.key?(:ignorecase)
         | 
| 13 | 
            +
                    @array.map! { |e| e.dup(:ignorecase => options[:ignorecase]) }
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                # Returns true if expression could be treated as a literal string.
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # Alternation groups are never literal.
         | 
| 20 | 
            +
                def literal?
         | 
| 21 | 
            +
                  false
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def flags
         | 
| 25 | 
            +
                  0
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def dup(options = {})
         | 
| 29 | 
            +
                  self.class.new(to_a, options)
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def to_s(parent = false)
         | 
| 33 | 
            +
                  map { |e| e.to_s(parent) }.join('|')
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def inspect #:nodoc:
         | 
| 37 | 
            +
                  to_s.inspect
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            module Regin
         | 
| 2 | 
            +
              class Atom
         | 
| 3 | 
            +
                attr_reader :value, :ignorecase
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(value, options = {})
         | 
| 6 | 
            +
                  @value = value
         | 
| 7 | 
            +
                  @ignorecase = options[:ignorecase]
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def option_names
         | 
| 11 | 
            +
                  %w( ignorecase )
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # Returns true if expression could be treated as a literal string.
         | 
| 15 | 
            +
                def literal?
         | 
| 16 | 
            +
                  false
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def casefold?
         | 
| 20 | 
            +
                  ignorecase ? true : false
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def dup(options = {})
         | 
| 24 | 
            +
                  original_options = option_names.inject({}) do |h, m|
         | 
| 25 | 
            +
                    h[m.to_sym] = send(m)
         | 
| 26 | 
            +
                    h
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                  self.class.new(value, original_options.merge(options))
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def to_s(parent = false)
         | 
| 32 | 
            +
                  "#{value}"
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def inspect #:nodoc:
         | 
| 36 | 
            +
                  "#<#{self.class.to_s.sub('Regin::', '')} #{to_s.inspect}>"
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def ==(other) #:nodoc:
         | 
| 40 | 
            +
                  case other
         | 
| 41 | 
            +
                  when String
         | 
| 42 | 
            +
                    other == to_s
         | 
| 43 | 
            +
                  else
         | 
| 44 | 
            +
                    eql?(other)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def eql?(other) #:nodoc:
         | 
| 49 | 
            +
                  other.instance_of?(self.class) &&
         | 
| 50 | 
            +
                    self.value.eql?(other.value) &&
         | 
| 51 | 
            +
                    (!!self.ignorecase).eql?(!!other.ignorecase)
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            module Regin
         | 
| 2 | 
            +
              class Character < Atom
         | 
| 3 | 
            +
                attr_reader :quantifier
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(value, options = {})
         | 
| 6 | 
            +
                  @quantifier = options[:quantifier]
         | 
| 7 | 
            +
                  super
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def option_names
         | 
| 11 | 
            +
                  %w( quantifier ) + super
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # Returns true if expression could be treated as a literal string.
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # A Character is literal is there is no quantifier attached to it.
         | 
| 17 | 
            +
                def literal?
         | 
| 18 | 
            +
                  quantifier.nil? && !ignorecase
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def to_s(parent = false)
         | 
| 22 | 
            +
                  if !parent && ignorecase
         | 
| 23 | 
            +
                    "(?i-mx:#{value})#{quantifier}"
         | 
| 24 | 
            +
                  else
         | 
| 25 | 
            +
                    "#{value}#{quantifier}"
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def to_regexp(anchored = false)
         | 
| 30 | 
            +
                  re = to_s(true)
         | 
| 31 | 
            +
                  re = "\\A#{re}\\Z" if anchored
         | 
| 32 | 
            +
                  Regexp.compile(re, ignorecase)
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def match(char)
         | 
| 36 | 
            +
                  to_regexp(true).match(char)
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def include?(char)
         | 
| 40 | 
            +
                  if ignorecase
         | 
| 41 | 
            +
                    value.downcase == char.downcase
         | 
| 42 | 
            +
                  else
         | 
| 43 | 
            +
                    value == char
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def eql?(other) #:nodoc:
         | 
| 48 | 
            +
                  super && quantifier.eql?(other.quantifier)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            module Regin
         | 
| 2 | 
            +
              class CharacterClass < Character
         | 
| 3 | 
            +
                def initialize(value, options = {})
         | 
| 4 | 
            +
                  @negate = options[:negate]
         | 
| 5 | 
            +
                  super
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def option_names
         | 
| 9 | 
            +
                  %w( negate ) + super
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                attr_reader :negate
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def negated?
         | 
| 15 | 
            +
                  negate ? true : false
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                # Returns true if expression could be treated as a literal string.
         | 
| 19 | 
            +
                #
         | 
| 20 | 
            +
                # A CharacterClass is never literal.
         | 
| 21 | 
            +
                def literal?
         | 
| 22 | 
            +
                  false
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def bracketed?
         | 
| 26 | 
            +
                  value != '.' && value !~ /^\\[dDsSwW]$/
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def to_s(parent = false)
         | 
| 30 | 
            +
                  if bracketed?
         | 
| 31 | 
            +
                    if !parent && ignorecase
         | 
| 32 | 
            +
                      "(?i-mx:[#{negate && '^'}#{value}])#{quantifier}"
         | 
| 33 | 
            +
                    else
         | 
| 34 | 
            +
                      "[#{negate && '^'}#{value}]#{quantifier}"
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                  else
         | 
| 37 | 
            +
                    super
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def include?(char)
         | 
| 42 | 
            +
                  re = quantifier ? to_s.sub(/#{Regexp.escape(quantifier)}$/, '') : to_s
         | 
| 43 | 
            +
                  Regexp.compile("\\A#{re}\\Z").match(char)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def eql?(other) #:nodoc:
         | 
| 47 | 
            +
                  super && negate == other.negate
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         |