module Newark
class Route
PARAM_MATCHER = /:(?[^\/]*)/.freeze
PARAM_SUB = /:[^\/]*/.freeze
PATH_MATCHER = /\*(?.*)/.freeze
PATH_SUB = /\*.*/.freeze
attr_reader :handler, :params
def initialize(path, constraints, handler)
fail ArgumentError, 'You must define a route handler' if handler.nil?
@constraints = Constraint.load(constraints)
@handler = handler
@path = path_matcher(path)
end
def match?(request)
path_data = path_match?(request)
if path_data && constraints_match?(request)
@params = Hash[ path_data.names.zip( path_data.captures ) ]
end
end
private
def constraints_match?(request)
@constraints.all? { |constraint| constraint.match?(request) }
end
def path_match?(request)
@path.match(request.path_info)
end
def path_matcher(path)
return path if path.is_a? Regexp
/^#{path_params(path.to_s)}$/
end
def path_params(path)
match_path(path)
match_params(path)
path != '/' ? path.sub(/\/$/, '') : path
end
def match_path(path)
if match = PATH_MATCHER.match(path)
path.sub!(PATH_SUB, "(?<#{match[:path]}>.*)")
end
end
def match_params(path)
while match = PARAM_MATCHER.match(path)
path.sub!(PARAM_SUB, "(?<#{match[:param]}>[^\/]*)")
end
end
class Constraint
attr_reader :field, :matchers
# Expects a hash of constraints
def self.load(constraints)
fail ArgumentError unless constraints.is_a?(Hash)
constraints.map { |field, matcher| Constraint.new(field, matcher) }
end
def initialize(field, match_or_matchers)
@field = field
@matchers = make_matchers_regexp(match_or_matchers)
end
def match?(request)
if request.respond_to?(field)
constrained = request.send(field)
if matchers.is_a?(Hash) && constrained.is_a?(Hash)
hash_match?(request, constrained, matchers)
else
constrained =~ matchers
end
end
end
private
def hash_match?(request, constrained, matchers)
matchers.all? { |key, matcher|
constrained[key] =~ matcher
}
end
def make_matchers_regexp(match_or_matchers)
if match_or_matchers.is_a? Hash
{}.tap do |matchers|
match_or_matchers.each do |k, v|
matchers[k] = matcher_to_regex(v)
end
end
else
matcher_to_regex(match_or_matchers)
end
end
def matcher_to_regex(matcher)
return matcher if matcher.is_a? Regexp
/^#{matcher.to_s}$/
end
end
end
end