inital mustermann support
This commit is contained in:
parent
d2cd9edc9f
commit
7a9ffccdaf
|
@ -4,6 +4,9 @@
|
||||||
require 'rack'
|
require 'rack'
|
||||||
require 'tilt'
|
require 'tilt'
|
||||||
require 'rack/protection'
|
require 'rack/protection'
|
||||||
|
require 'mustermann'
|
||||||
|
require 'mustermann/sinatra'
|
||||||
|
require 'mustermann/regular'
|
||||||
|
|
||||||
# stdlib dependencies
|
# stdlib dependencies
|
||||||
require 'thread'
|
require 'thread'
|
||||||
|
@ -964,9 +967,9 @@ module Sinatra
|
||||||
# Run routes defined on the class and all superclasses.
|
# Run routes defined on the class and all superclasses.
|
||||||
def route!(base = settings, pass_block = nil)
|
def route!(base = settings, pass_block = nil)
|
||||||
if routes = base.routes[@request.request_method]
|
if routes = base.routes[@request.request_method]
|
||||||
routes.each do |pattern, keys, conditions, block|
|
routes.each do |pattern, conditions, block|
|
||||||
returned_pass_block = process_route(pattern, keys, conditions) do |*args|
|
returned_pass_block = process_route(pattern, conditions) do |*args|
|
||||||
env['sinatra.route'] = block.instance_variable_get(:@route_name)
|
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
||||||
route_eval { block[*args] }
|
route_eval { block[*args] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -994,15 +997,20 @@ module Sinatra
|
||||||
# Revert params afterwards.
|
# Revert params afterwards.
|
||||||
#
|
#
|
||||||
# Returns pass block.
|
# Returns pass block.
|
||||||
def process_route(pattern, keys, conditions, block = nil, values = [])
|
def process_route(pattern, conditions, block = nil, values = [])
|
||||||
route = @request.path_info
|
route = @request.path_info
|
||||||
route = '/' if route.empty? and not settings.empty_path_info?
|
route = '/' if route.empty? and not settings.empty_path_info?
|
||||||
return unless match = pattern.match(route)
|
return unless params = pattern.params(route)
|
||||||
values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
|
|
||||||
|
|
||||||
if values.any?
|
params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
|
||||||
original, @params = params, params.merge('splat' => [], 'captures' => values)
|
original, @params = @params, @params.merge(params) if params.any?
|
||||||
keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
|
|
||||||
|
if pattern.is_a? Mustermann::Regular
|
||||||
|
captures = pattern.match(route).captures
|
||||||
|
values += captures
|
||||||
|
@params[:captures] = captures
|
||||||
|
else
|
||||||
|
values += params.values.flatten
|
||||||
end
|
end
|
||||||
|
|
||||||
catch(:pass) do
|
catch(:pass) do
|
||||||
|
@ -1254,7 +1262,7 @@ module Sinatra
|
||||||
# class, or an HTTP status code to specify which errors should be
|
# class, or an HTTP status code to specify which errors should be
|
||||||
# handled.
|
# handled.
|
||||||
def error(*codes, &block)
|
def error(*codes, &block)
|
||||||
args = compile! "ERROR", //, block
|
args = compile! "ERROR", /.*/, block
|
||||||
codes = codes.map { |c| Array(c) }.flatten
|
codes = codes.map { |c| Array(c) }.flatten
|
||||||
codes << Exception if codes.empty?
|
codes << Exception if codes.empty?
|
||||||
codes << Sinatra::NotFound if codes.include?(404)
|
codes << Sinatra::NotFound if codes.include?(404)
|
||||||
|
@ -1330,21 +1338,20 @@ module Sinatra
|
||||||
# Define a before filter; runs before all requests within the same
|
# Define a before filter; runs before all requests within the same
|
||||||
# context as route handlers and may access/modify the request and
|
# context as route handlers and may access/modify the request and
|
||||||
# response.
|
# response.
|
||||||
def before(path = nil, options = {}, &block)
|
def before(path = /.*/, **options, &block)
|
||||||
add_filter(:before, path, options, &block)
|
add_filter(:before, path, options, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Define an after filter; runs after all requests within the same
|
# Define an after filter; runs after all requests within the same
|
||||||
# context as route handlers and may access/modify the request and
|
# context as route handlers and may access/modify the request and
|
||||||
# response.
|
# response.
|
||||||
def after(path = nil, options = {}, &block)
|
def after(path = /.*/, **options, &block)
|
||||||
add_filter(:after, path, options, &block)
|
add_filter(:after, path, options, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
# add a filter
|
# add a filter
|
||||||
def add_filter(type, path = nil, options = {}, &block)
|
def add_filter(type, path = /.*/, **options, &block)
|
||||||
path, options = //, path if path.respond_to?(:each_pair)
|
filters[type] << compile!(type, path, block, options)
|
||||||
filters[type] << compile!(type, path || //, block, options)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add a route condition. The route is considered non-matching when the
|
# Add a route condition. The route is considered non-matching when the
|
||||||
|
@ -1600,108 +1607,22 @@ module Sinatra
|
||||||
method
|
method
|
||||||
end
|
end
|
||||||
|
|
||||||
def compile!(verb, path, block, options = {})
|
def compile!(verb, path, block, **options)
|
||||||
options.each_pair { |option, args| send(option, *args) }
|
options.each_pair { |option, args| send(option, *args) }
|
||||||
|
|
||||||
|
pattern = compile(path)
|
||||||
method_name = "#{verb} #{path}"
|
method_name = "#{verb} #{path}"
|
||||||
unbound_method = generate_method(method_name, &block)
|
unbound_method = generate_method(method_name, &block)
|
||||||
pattern, keys = compile path
|
|
||||||
conditions, @conditions = @conditions, []
|
conditions, @conditions = @conditions, []
|
||||||
|
|
||||||
wrapper = block.arity != 0 ?
|
wrapper = block.arity != 0 ?
|
||||||
proc { |a,p| unbound_method.bind(a).call(*p) } :
|
proc { |a,p| unbound_method.bind(a).call(*p) } :
|
||||||
proc { |a,p| unbound_method.bind(a).call }
|
proc { |a,p| unbound_method.bind(a).call }
|
||||||
wrapper.instance_variable_set(:@route_name, method_name)
|
|
||||||
|
|
||||||
[ pattern, keys, conditions, wrapper ]
|
[ pattern, conditions, wrapper ]
|
||||||
end
|
end
|
||||||
|
|
||||||
def compile(path)
|
def compile(path)
|
||||||
if path.respond_to? :to_str
|
Mustermann.new(path)
|
||||||
keys = []
|
|
||||||
|
|
||||||
# Split the path into pieces in between forward slashes.
|
|
||||||
# A negative number is given as the second argument of path.split
|
|
||||||
# because with this number, the method does not ignore / at the end
|
|
||||||
# and appends an empty string at the end of the return value.
|
|
||||||
#
|
|
||||||
segments = path.split('/', -1).map! do |segment|
|
|
||||||
ignore = []
|
|
||||||
|
|
||||||
# Special character handling.
|
|
||||||
#
|
|
||||||
pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]|:(?!\w)/) do |c|
|
|
||||||
ignore << escaped(c).join if c.match(/[\.@]/)
|
|
||||||
patt = encoded(c)
|
|
||||||
patt.gsub(/%[\da-fA-F]{2}/) do |match|
|
|
||||||
match.split(//).map! { |char| char == char.downcase ? char : "[#{char}#{char.downcase}]" }.join
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ignore = ignore.uniq.join
|
|
||||||
|
|
||||||
# Key handling.
|
|
||||||
#
|
|
||||||
pattern.gsub(/((:\w+)|\*)/) do |match|
|
|
||||||
if match == "*"
|
|
||||||
keys << 'splat'
|
|
||||||
"(.*?)"
|
|
||||||
else
|
|
||||||
keys << $2[1..-1]
|
|
||||||
ignore_pattern = safe_ignore(ignore)
|
|
||||||
|
|
||||||
ignore_pattern
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Special case handling.
|
|
||||||
#
|
|
||||||
if last_segment = segments[-1] and last_segment.match(/\[\^\\\./)
|
|
||||||
parts = last_segment.rpartition(/\[\^\\\./)
|
|
||||||
parts[1] = '[^'
|
|
||||||
segments[-1] = parts.join
|
|
||||||
end
|
|
||||||
[/\A#{segments.join('/')}\z/, keys]
|
|
||||||
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
|
||||||
[path, path.keys]
|
|
||||||
elsif path.respond_to?(:names) && path.respond_to?(:match)
|
|
||||||
[path, path.names]
|
|
||||||
elsif path.respond_to? :match
|
|
||||||
[path, []]
|
|
||||||
else
|
|
||||||
raise TypeError, path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def encoded(char)
|
|
||||||
enc = URI_INSTANCE.escape(char)
|
|
||||||
enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
|
|
||||||
enc = "(?:#{enc}|#{encoded('+')})" if char == " "
|
|
||||||
enc
|
|
||||||
end
|
|
||||||
|
|
||||||
def escaped(char, enc = URI_INSTANCE.escape(char))
|
|
||||||
[Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
|
|
||||||
end
|
|
||||||
|
|
||||||
def safe_ignore(ignore)
|
|
||||||
unsafe_ignore = []
|
|
||||||
ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
|
|
||||||
unsafe_ignore << hex[1..2]
|
|
||||||
''
|
|
||||||
end
|
|
||||||
unsafe_patterns = unsafe_ignore.map! do |unsafe|
|
|
||||||
chars = unsafe.split(//).map! do |char|
|
|
||||||
char == char.downcase ? char : char + char.downcase
|
|
||||||
end
|
|
||||||
|
|
||||||
"|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
|
|
||||||
end
|
|
||||||
if unsafe_patterns.length > 0
|
|
||||||
"((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
|
|
||||||
else
|
|
||||||
"([^#{ignore}/?#]+)"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_default_middleware(builder)
|
def setup_default_middleware(builder)
|
||||||
|
|
|
@ -18,4 +18,5 @@ Gem::Specification.new 'sinatra', Sinatra::VERSION do |s|
|
||||||
s.add_dependency 'rack', '= 2.0.0.alpha'
|
s.add_dependency 'rack', '= 2.0.0.alpha'
|
||||||
s.add_dependency 'tilt', '~> 2.0'
|
s.add_dependency 'tilt', '~> 2.0'
|
||||||
s.add_dependency 'rack-protection', '~> 1.5'
|
s.add_dependency 'rack-protection', '~> 1.5'
|
||||||
|
s.add_dependency 'mustermann', '~> 0.4'
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,151 +2,103 @@
|
||||||
require File.expand_path('../helper', __FILE__)
|
require File.expand_path('../helper', __FILE__)
|
||||||
|
|
||||||
class CompileTest < Minitest::Test
|
class CompileTest < Minitest::Test
|
||||||
|
|
||||||
def self.converts pattern, expected_regexp
|
|
||||||
it "generates #{expected_regexp.source} from #{pattern}" do
|
|
||||||
compiled, _ = compiled pattern
|
|
||||||
assert_equal expected_regexp, compiled, "Pattern #{pattern} is not compiled into #{expected_regexp.source}. Was #{compiled.source}."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
def self.parses pattern, example, expected_params
|
def self.parses pattern, example, expected_params
|
||||||
it "parses #{example} with #{pattern} into params #{expected_params}" do
|
it "parses #{example} with #{pattern} into params #{expected_params}" do
|
||||||
compiled, keys = compiled pattern
|
compiled = mock_app {}.send(:compile, pattern)
|
||||||
match = compiled.match(example)
|
params = compiled.params(example)
|
||||||
fail %Q{"#{example}" does not parse on pattern "#{pattern}" (compiled pattern is #{compiled.source}).} unless match
|
fail %Q{"#{example}" does not parse on pattern "#{pattern}".} unless params
|
||||||
|
|
||||||
# Aggregate e.g. multiple splat values into one array.
|
|
||||||
#
|
|
||||||
params = keys.zip(match.captures).reduce({}) do |hash, mapping|
|
|
||||||
key, value = mapping
|
|
||||||
hash[key] = if existing = hash[key]
|
|
||||||
existing.respond_to?(:to_ary) ? existing << value : [existing, value]
|
|
||||||
else
|
|
||||||
value
|
|
||||||
end
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_equal expected_params, params, "Pattern #{pattern} does not match path #{example}."
|
assert_equal expected_params, params, "Pattern #{pattern} does not match path #{example}."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.fails pattern, example
|
def self.fails pattern, example
|
||||||
it "does not parse #{example} with #{pattern}" do
|
it "does not parse #{example} with #{pattern}" do
|
||||||
compiled, _ = compiled pattern
|
compiled = mock_app {}.send(:compile, pattern)
|
||||||
match = compiled.match(example)
|
match = compiled.match(example)
|
||||||
fail %Q{"#{pattern}" does parse "#{example}" but it should fail} if match
|
fail %Q{"#{pattern}" does parse "#{example}" but it should fail} if match
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
def compiled pattern
|
|
||||||
app ||= mock_app {}
|
|
||||||
compiled, keys = app.send(:compile, pattern)
|
|
||||||
[compiled, keys]
|
|
||||||
end
|
|
||||||
|
|
||||||
converts "/", %r{\A/\z}
|
|
||||||
parses "/", "/", {}
|
parses "/", "/", {}
|
||||||
|
|
||||||
converts "/foo", %r{\A/foo\z}
|
|
||||||
parses "/foo", "/foo", {}
|
parses "/foo", "/foo", {}
|
||||||
|
|
||||||
converts "/:foo", %r{\A/([^/?#]+)\z}
|
|
||||||
parses "/:foo", "/foo", "foo" => "foo"
|
parses "/:foo", "/foo", "foo" => "foo"
|
||||||
parses "/:foo", "/foo.bar", "foo" => "foo.bar"
|
parses "/:foo", "/foo.bar", "foo" => "foo.bar"
|
||||||
parses "/:foo", "/foo%2Fbar", "foo" => "foo%2Fbar"
|
parses "/:foo", "/foo%2Fbar", "foo" => "foo/bar"
|
||||||
parses "/:foo", "/%0Afoo", "foo" => "%0Afoo"
|
parses "/:foo", "/%0Afoo", "foo" => "\nfoo"
|
||||||
fails "/:foo", "/foo?"
|
fails "/:foo", "/foo?"
|
||||||
fails "/:foo", "/foo/bar"
|
fails "/:foo", "/foo/bar"
|
||||||
fails "/:foo", "/"
|
fails "/:foo", "/"
|
||||||
fails "/:foo", "/foo/"
|
fails "/:foo", "/foo/"
|
||||||
|
|
||||||
converts "/föö", %r{\A/f%[Cc]3%[Bb]6%[Cc]3%[Bb]6\z}
|
|
||||||
parses "/föö", "/f%C3%B6%C3%B6", {}
|
parses "/föö", "/f%C3%B6%C3%B6", {}
|
||||||
|
|
||||||
converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z}
|
|
||||||
parses "/:foo/:bar", "/foo/bar", "foo" => "foo", "bar" => "bar"
|
parses "/:foo/:bar", "/foo/bar", "foo" => "foo", "bar" => "bar"
|
||||||
|
|
||||||
converts "/hello/:person", %r{\A/hello/([^/?#]+)\z}
|
|
||||||
parses "/hello/:person", "/hello/Frank", "person" => "Frank"
|
parses "/hello/:person", "/hello/Frank", "person" => "Frank"
|
||||||
|
|
||||||
converts "/?:foo?/?:bar?", %r{\A/?([^/?#]+)?/?([^/?#]+)?\z}
|
|
||||||
parses "/?:foo?/?:bar?", "/hello/world", "foo" => "hello", "bar" => "world"
|
parses "/?:foo?/?:bar?", "/hello/world", "foo" => "hello", "bar" => "world"
|
||||||
parses "/?:foo?/?:bar?", "/hello", "foo" => "hello", "bar" => nil
|
parses "/?:foo?/?:bar?", "/hello", "foo" => "hello", "bar" => nil
|
||||||
parses "/?:foo?/?:bar?", "/", "foo" => nil, "bar" => nil
|
parses "/?:foo?/?:bar?", "/", "foo" => nil, "bar" => nil
|
||||||
parses "/?:foo?/?:bar?", "", "foo" => nil, "bar" => nil
|
parses "/?:foo?/?:bar?", "", "foo" => nil, "bar" => nil
|
||||||
|
|
||||||
converts "/*", %r{\A/(.*?)\z}
|
parses "/*", "/", "splat" => [""]
|
||||||
parses "/*", "/", "splat" => ""
|
parses "/*", "/foo", "splat" => ["foo"]
|
||||||
parses "/*", "/foo", "splat" => "foo"
|
parses "/*", "/foo/bar", "splat" => ["foo/bar"]
|
||||||
parses "/*", "/foo/bar", "splat" => "foo/bar"
|
|
||||||
|
|
||||||
converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z}
|
parses "/:foo/*", "/foo/bar/baz", "foo" => "foo", "splat" => ["bar/baz"]
|
||||||
parses "/:foo/*", "/foo/bar/baz", "foo" => "foo", "splat" => "bar/baz"
|
|
||||||
|
|
||||||
converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z}
|
|
||||||
parses "/:foo/:bar", "/user@example.com/name", "foo" => "user@example.com", "bar" => "name"
|
parses "/:foo/:bar", "/user@example.com/name", "foo" => "user@example.com", "bar" => "name"
|
||||||
|
|
||||||
converts "/test$/", %r{\A/test(?:\$|%24)/\z}
|
|
||||||
parses "/test$/", "/test$/", {}
|
parses "/test$/", "/test$/", {}
|
||||||
|
|
||||||
converts "/te+st/", %r{\A/te(?:\+|%2[Bb])st/\z}
|
|
||||||
parses "/te+st/", "/te+st/", {}
|
parses "/te+st/", "/te+st/", {}
|
||||||
fails "/te+st/", "/test/"
|
fails "/te+st/", "/test/"
|
||||||
fails "/te+st/", "/teeest/"
|
fails "/te+st/", "/teeest/"
|
||||||
|
|
||||||
converts "/test(bar)/", %r{\A/test(?:\(|%28)bar(?:\)|%29)/\z}
|
parses "/test(bar)/", "/testbar/", {}
|
||||||
parses "/test(bar)/", "/test(bar)/", {}
|
|
||||||
|
|
||||||
converts "/path with spaces", %r{\A/path(?:%20|(?:\+|%2[Bb]))with(?:%20|(?:\+|%2[Bb]))spaces\z}
|
|
||||||
parses "/path with spaces", "/path%20with%20spaces", {}
|
parses "/path with spaces", "/path%20with%20spaces", {}
|
||||||
parses "/path with spaces", "/path%2Bwith%2Bspaces", {}
|
parses "/path with spaces", "/path%2Bwith%2Bspaces", {}
|
||||||
parses "/path with spaces", "/path+with+spaces", {}
|
parses "/path with spaces", "/path+with+spaces", {}
|
||||||
|
|
||||||
converts "/foo&bar", %r{\A/foo(?:&|%26)bar\z}
|
|
||||||
parses "/foo&bar", "/foo&bar", {}
|
parses "/foo&bar", "/foo&bar", {}
|
||||||
|
|
||||||
converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z}
|
parses "/:foo/*", "/hello%20world/how%20are%20you", "foo" => "hello world", "splat" => ["how are you"]
|
||||||
parses "/:foo/*", "/hello%20world/how%20are%20you", "foo" => "hello%20world", "splat" => "how%20are%20you"
|
|
||||||
|
|
||||||
converts "/*/foo/*/*", %r{\A/(.*?)/foo/(.*?)/(.*?)\z}
|
|
||||||
parses "/*/foo/*/*", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling", "baz/boom"]
|
parses "/*/foo/*/*", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling", "baz/boom"]
|
||||||
|
parses "/*/foo/*/*rest", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling"], "rest" => "baz/boom"
|
||||||
fails "/*/foo/*/*", "/bar/foo/baz"
|
fails "/*/foo/*/*", "/bar/foo/baz"
|
||||||
|
|
||||||
converts "/test.bar", %r{\A/test(?:\.|%2[Ee])bar\z}
|
|
||||||
parses "/test.bar", "/test.bar", {}
|
parses "/test.bar", "/test.bar", {}
|
||||||
fails "/test.bar", "/test0bar"
|
fails "/test.bar", "/test0bar"
|
||||||
|
|
||||||
converts "/:file.:ext", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z}
|
|
||||||
parses "/:file.:ext", "/pony.jpg", "file" => "pony", "ext" => "jpg"
|
parses "/:file.:ext", "/pony.jpg", "file" => "pony", "ext" => "jpg"
|
||||||
parses "/:file.:ext", "/pony%2Ejpg", "file" => "pony", "ext" => "jpg"
|
parses "/:file.:ext", "/pony%2Ejpg", "file" => "pony", "ext" => "jpg"
|
||||||
fails "/:file.:ext", "/.jpg"
|
fails "/:file.:ext", "/.jpg"
|
||||||
|
|
||||||
converts "/:name.?:format?", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])?((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)?\z}
|
|
||||||
parses "/:name.?:format?", "/foo", "name" => "foo", "format" => nil
|
parses "/:name.?:format?", "/foo", "name" => "foo", "format" => nil
|
||||||
parses "/:name.?:format?", "/foo.bar", "name" => "foo", "format" => "bar"
|
parses "/:name.?:format?", "/foo.bar", "name" => "foo", "format" => "bar"
|
||||||
parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar"
|
parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar"
|
||||||
parses "/:name?.?:format", "/.bar", "name" => nil, "format" => "bar"
|
|
||||||
parses "/:name?.?:format?", "/.bar", "name" => nil, "format" => "bar"
|
|
||||||
parses "/:name?.:format?", "/.bar", "name" => nil, "format" => "bar"
|
|
||||||
fails "/:name.:format", "/.bar"
|
|
||||||
fails "/:name.?:format?", "/.bar"
|
|
||||||
|
|
||||||
converts "/:user@?:host?", %r{\A/((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)(?:@|%40)?((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)?\z}
|
|
||||||
parses "/:user@?:host?", "/foo@bar", "user" => "foo", "host" => "bar"
|
parses "/:user@?:host?", "/foo@bar", "user" => "foo", "host" => "bar"
|
||||||
parses "/:user@?:host?", "/foo.foo@bar", "user" => "foo.foo", "host" => "bar"
|
parses "/:user@?:host?", "/foo.foo@bar", "user" => "foo.foo", "host" => "bar"
|
||||||
parses "/:user@?:host?", "/foo@bar.bar", "user" => "foo", "host" => "bar.bar"
|
parses "/:user@?:host?", "/foo@bar.bar", "user" => "foo", "host" => "bar.bar"
|
||||||
|
|
||||||
# From https://gist.github.com/2154980#gistcomment-169469.
|
# From https://gist.github.com/2154980#gistcomment-169469.
|
||||||
#
|
#
|
||||||
# converts "/:name(.:format)?", %r{\A/([^\.%2E/?#]+)(?:\(|%28)(?:\.|%2E)([^\.%2E/?#]+)(?:\)|%29)?\z}
|
parses "/:name(.:format)?", "/foo", "name" => "foo", "format" => nil
|
||||||
# parses "/:name(.:format)?", "/foo", "name" => "foo", "format" => nil
|
parses "/:name(.:format)?", "/foo.bar", "name" => "foo", "format" => "bar"
|
||||||
# parses "/:name(.:format)?", "/foo.bar", "name" => "foo", "format" => "bar"
|
parses "/:name(.:format)?", "/foo.", "name" => "foo.", "format" => nil
|
||||||
fails "/:name(.:format)?", "/foo."
|
|
||||||
|
|
||||||
parses "/:id/test.bar", "/3/test.bar", {"id" => "3"}
|
parses "/:id/test.bar", "/3/test.bar", {"id" => "3"}
|
||||||
parses "/:id/test.bar", "/2/test.bar", {"id" => "2"}
|
parses "/:id/test.bar", "/2/test.bar", {"id" => "2"}
|
||||||
parses "/:id/test.bar", "/2E/test.bar", {"id" => "2E"}
|
parses "/:id/test.bar", "/2E/test.bar", {"id" => "2E"}
|
||||||
parses "/:id/test.bar", "/2e/test.bar", {"id" => "2e"}
|
parses "/:id/test.bar", "/2e/test.bar", {"id" => "2e"}
|
||||||
parses "/:id/test.bar", "/%2E/test.bar", {"id" => "%2E"}
|
parses "/:id/test.bar", "/%2E/test.bar", {"id" => "."}
|
||||||
|
parses "/{id}/test.bar", "/%2E/test.bar", {"id" => "."}
|
||||||
|
|
||||||
parses '/10/:id', '/10/test', "id" => "test"
|
parses '/10/:id', '/10/test', "id" => "test"
|
||||||
parses '/10/:id', '/10/te.st', "id" => "te.st"
|
parses '/10/:id', '/10/te.st', "id" => "te.st"
|
||||||
|
@ -156,7 +108,6 @@ class CompileTest < Minitest::Test
|
||||||
parses '/:foo/:id', '/10.1/te.st', "foo" => "10.1", "id" => "te.st"
|
parses '/:foo/:id', '/10.1/te.st', "foo" => "10.1", "id" => "te.st"
|
||||||
parses '/:foo/:id', '/10.1.2/te.st', "foo" => "10.1.2", "id" => "te.st"
|
parses '/:foo/:id', '/10.1.2/te.st', "foo" => "10.1.2", "id" => "te.st"
|
||||||
parses '/:foo.:bar/:id', '/10.1/te.st', "foo" => "10", "bar" => "1", "id" => "te.st"
|
parses '/:foo.:bar/:id', '/10.1/te.st', "foo" => "10", "bar" => "1", "id" => "te.st"
|
||||||
fails '/:foo.:bar/:id', '/10.1.2/te.st' # We don't do crazy.
|
|
||||||
|
|
||||||
parses '/:a/:b.?:c?', '/a/b', "a" => "a", "b" => "b", "c" => nil
|
parses '/:a/:b.?:c?', '/a/b', "a" => "a", "b" => "b", "c" => nil
|
||||||
parses '/:a/:b.?:c?', '/a/b.c', "a" => "a", "b" => "b", "c" => "c"
|
parses '/:a/:b.?:c?', '/a/b.c', "a" => "a", "b" => "b", "c" => "c"
|
||||||
|
@ -165,17 +116,15 @@ class CompileTest < Minitest::Test
|
||||||
fails '/:a/:b.?:c?', '/a.b/c.d/e'
|
fails '/:a/:b.?:c?', '/a.b/c.d/e'
|
||||||
|
|
||||||
parses "/:file.:ext", "/pony%2ejpg", "file" => "pony", "ext" => "jpg"
|
parses "/:file.:ext", "/pony%2ejpg", "file" => "pony", "ext" => "jpg"
|
||||||
parses "/:file.:ext", "/pony%E6%AD%A3%2Ejpg", "file" => "pony%E6%AD%A3", "ext" => "jpg"
|
parses "/:file.:ext", "/pony%E6%AD%A3%2Ejpg", "file" => "pony正", "ext" => "jpg"
|
||||||
parses "/:file.:ext", "/pony%e6%ad%a3%2ejpg", "file" => "pony%e6%ad%a3", "ext" => "jpg"
|
parses "/:file.:ext", "/pony%e6%ad%a3%2ejpg", "file" => "pony正", "ext" => "jpg"
|
||||||
parses "/:file.:ext", "/pony正%2Ejpg", "file" => "pony正", "ext" => "jpg"
|
parses "/:file.:ext", "/pony正%2Ejpg", "file" => "pony正", "ext" => "jpg"
|
||||||
parses "/:file.:ext", "/pony正%2ejpg", "file" => "pony正", "ext" => "jpg"
|
parses "/:file.:ext", "/pony正%2ejpg", "file" => "pony正", "ext" => "jpg"
|
||||||
parses "/:file.:ext", "/pony正..jpg", "file" => "pony正", "ext" => ".jpg"
|
parses "/:file.:ext", "/pony正..jpg", "file" => "pony正.", "ext" => "jpg"
|
||||||
fails "/:file.:ext", "/pony正.%2ejpg"
|
|
||||||
|
|
||||||
converts "/:name.:format", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z}
|
parses "/:name.:format", "/file.tar.gz", "name" => "file.tar", "format" => "gz"
|
||||||
parses "/:name.:format", "/file.tar.gz", "name" => "file", "format" => "tar.gz"
|
|
||||||
parses "/:name.:format1.:format2", "/file.tar.gz", "name" => "file", "format1" => "tar", "format2" => "gz"
|
parses "/:name.:format1.:format2", "/file.tar.gz", "name" => "file", "format1" => "tar", "format2" => "gz"
|
||||||
parses "/:name.:format1.:format2", "/file.temp.tar.gz", "name" => "file", "format1" => "temp", "format2" => "tar.gz"
|
parses "/:name.:format1.:format2", "/file.temp.tar.gz", "name" => "file.temp", "format1" => "tar", "format2" => "gz"
|
||||||
|
|
||||||
# From issue #688.
|
# From issue #688.
|
||||||
#
|
#
|
||||||
|
|
|
@ -31,7 +31,7 @@ end
|
||||||
|
|
||||||
class Rack::Builder
|
class Rack::Builder
|
||||||
def include?(middleware)
|
def include?(middleware)
|
||||||
@ins.any? { |m| p m ; middleware === m }
|
@ins.any? { |m| middleware === m }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,19 +6,13 @@ def route_def(pattern)
|
||||||
mock_app { get(pattern) { } }
|
mock_app { get(pattern) { } }
|
||||||
end
|
end
|
||||||
|
|
||||||
class RegexpLookAlike
|
class PatternLookAlike
|
||||||
class MatchData
|
def to_pattern(*)
|
||||||
def captures
|
self
|
||||||
["this", "is", "a", "test"]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def match(string)
|
def params(input)
|
||||||
::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/"
|
{ "one" => "this", "two" => "is", "three" => "a", "four" => "test" }
|
||||||
end
|
|
||||||
|
|
||||||
def keys
|
|
||||||
["one", "two", "three", "four"]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -101,11 +95,11 @@ class RoutingTest < Minitest::Test
|
||||||
|
|
||||||
it "it handles encoded colons correctly" do
|
it "it handles encoded colons correctly" do
|
||||||
mock_app {
|
mock_app {
|
||||||
get("/:") { 'a' }
|
get("/\\:") { 'a' }
|
||||||
get("/a/:") { 'b' }
|
get("/a/\\:") { 'b' }
|
||||||
get("/a/:/b") { 'c' }
|
get("/a/\\:/b") { 'c' }
|
||||||
get("/a/b:") { 'd' }
|
get("/a/b\\:") { 'd' }
|
||||||
get("/a/b: ") { 'e' }
|
get("/a/b\\: ") { 'e' }
|
||||||
}
|
}
|
||||||
get '/:'
|
get '/:'
|
||||||
assert_equal 200, status
|
assert_equal 200, status
|
||||||
|
@ -430,8 +424,8 @@ class RoutingTest < Minitest::Test
|
||||||
assert_equal 'bob+ross', body
|
assert_equal 'bob+ross', body
|
||||||
end
|
end
|
||||||
|
|
||||||
it "literally matches parens in paths" do
|
it "literally matches parens in paths when escaped" do
|
||||||
route_def '/test(bar)/'
|
route_def '/test\(bar\)/'
|
||||||
|
|
||||||
get '/test(bar)/'
|
get '/test(bar)/'
|
||||||
assert ok?
|
assert ok?
|
||||||
|
@ -599,7 +593,7 @@ class RoutingTest < Minitest::Test
|
||||||
|
|
||||||
it 'supports regular expression look-alike routes' do
|
it 'supports regular expression look-alike routes' do
|
||||||
mock_app {
|
mock_app {
|
||||||
get(RegexpLookAlike.new) do
|
get(PatternLookAlike.new) do
|
||||||
assert_equal 'this', params[:one]
|
assert_equal 'this', params[:one]
|
||||||
assert_equal 'is', params[:two]
|
assert_equal 'is', params[:two]
|
||||||
assert_equal 'a', params[:three]
|
assert_equal 'a', params[:three]
|
||||||
|
@ -1435,7 +1429,7 @@ class RoutingTest < Minitest::Test
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_equal Array, signature.class
|
assert_equal Array, signature.class
|
||||||
assert_equal 4, signature.length
|
assert_equal 3, signature.length
|
||||||
assert list.include?(signature)
|
assert list.include?(signature)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue