Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

removed unused interfaces. better docing.

  • Loading branch information...
commit 38d27139e1301980460145231dfa9ceaff0c054c 1 parent 73db91b
@joshbuddy authored
View
2  README.markdown
@@ -30,7 +30,7 @@ Any probably more!
From the rdoc:
-Creates a route from +path+ and +options+
+Creates a route from `path` and `options`
### `path`
A path consists a mix of dynamic and static parts delimited by `/`
View
2  benchmarks/recognition_bm.rb
@@ -8,7 +8,7 @@
u.add_route('/simple/again/and/again')
u.add_route('/dynamic/:variable')
u.add_route('/rails/:controller/:action/:id')
-u.add_route('/greedy/{!:greed,.*}')
+u.add_route('/greedy/{!greed,.*}')
TIMES = 50_000
View
2  lib/usher/delimiters.rb
@@ -5,7 +5,7 @@ class Delimiters < Array
attr_reader :unescaped
# Creates a list of delimiters
- # @param arr [Array<String>] delimters to use
+ # @param ary [Array<String>] delimters to use
def initialize(ary)
super ary
@unescaped = self.map do |delimiter|
View
73 lib/usher/grapher.rb
@@ -2,23 +2,26 @@ class Usher
# Find nearest matching routes based on parameter keys.
class Grapher
- attr_reader :routes, :router, :orders, :key_count, :cache
-
+ attr_reader :router, :orders, :key_count, :cache, :significant_keys
+
+ # @param router An Usher instance you wish to create a grapher for.
def initialize(router)
@router = router
reset!
end
# Add route for matching
+ # @param route [Route] Add route for matching against
def add_route(route)
- reset! if @processed
- routes << route
+ @cache.clear
+ process_route(route)
end
# Finds a matching path based on params hash
+ # @param params [Hash<Symbol, String>] A hash of parameters you wish to use in matching.
+ # @return [nil, Route] Returns the matching {Usher::Route::Path} or nil if no path matches.
def find_matching_path(params)
unless params.empty?
- process_routes
set = params.keys & significant_keys
if cached = cache[set]
return cached
@@ -40,55 +43,49 @@ def find_matching_path(params)
end
private
- def process_routes
- return if @processed
- routes.each do |route|
- route.paths.each do |path|
- if path.dynamic?
- path.dynamic_keys.each do |k|
- orders[path.dynamic_keys.size][k] << path
- key_count[k] += 1
- end
-
- dynamic_parts_with_defaults = path.dynamic_parts.select{|part| part.default_value }.map{|dp| dp.name}
- dynamic_parts_without_defaults = path.dynamic_parts.select{|part| !part.default_value }.map{|dp| dp.name}
+ # Processes route.
+ # @param route [Route] Processes the route for use in the grapher.
+ def process_route(route)
+ route.paths.each do |path|
+ if path.dynamic?
+ path.dynamic_keys.each do |k|
+ orders[path.dynamic_keys.size][k] << path
+ key_count[k] += 1
+ end
- (1...(2 ** (dynamic_parts_with_defaults.size))).each do |i|
- current_set = dynamic_parts_without_defaults.dup
- dynamic_parts_with_defaults.each_with_index do |dp, index|
- current_set << dp unless (index & i) == 0
- end
+ dynamic_parts_with_defaults = path.dynamic_parts.select{|part| part.default_value }.map{|dp| dp.name}
+ dynamic_parts_without_defaults = path.dynamic_parts.select{|part| !part.default_value }.map{|dp| dp.name}
- current_set.each do |k|
- orders[current_set.size][k] << path
- key_count[k] += 1
- end
+ (1...(2 ** (dynamic_parts_with_defaults.size))).each do |i|
+ current_set = dynamic_parts_without_defaults.dup
+ dynamic_parts_with_defaults.each_with_index do |dp, index|
+ current_set << dp unless (index & i) == 0
end
- end
-
- if router.consider_destination_keys?
- path.route.destination_keys.each do |k|
- orders[path.route.destination_keys.size][k] << path
+ current_set.each do |k|
+ orders[current_set.size][k] << path
key_count[k] += 1
end
end
+
+ end
+
+ if router.consider_destination_keys?
+ path.route.destination_keys.each do |k|
+ orders[path.route.destination_keys.size][k] << path
+ key_count[k] += 1
+ end
end
end
- @processed = true
+ @significant_keys = key_count.keys.uniq
end
- def significant_keys
- @significant_keys ||= key_count.keys.uniq
- end
-
+ # Resets the router to its initial state.
def reset!
@significant_keys = nil
@orders = Hash.new{|h,k| h[k] = Hash.new{|h2, k2| h2[k2] = []}}
@key_count = Hash.new(0)
@cache = {}
- @routes = []
- @processed = false
end
end
View
12 lib/usher/interface.rb
@@ -2,22 +2,24 @@ class Usher
# Various interfaces for Usher.
module Interface
- autoload(:Email, File.join(File.dirname(__FILE__), 'interface', 'email'))
- autoload(:Merb, File.join(File.dirname(__FILE__), 'interface', 'merb'))
autoload(:Rails20, File.join(File.dirname(__FILE__), 'interface', 'rails20'))
autoload(:Rails22, File.join(File.dirname(__FILE__), 'interface', 'rails22'))
autoload(:Rails23, File.join(File.dirname(__FILE__), 'interface', 'rails23'))
autoload(:Rack, File.join(File.dirname(__FILE__), 'interface', 'rack'))
autoload(:Rails3, File.join(File.dirname(__FILE__), 'interface', 'rails3'))
- autoload(:Text, File.join(File.dirname(__FILE__), 'interface', 'text'))
autoload(:Sinatra, File.join(File.dirname(__FILE__), 'interface', 'sinatra'))
-
+
+ # Returns the appropriate interface class for a given name.
+ # @param name [Symbol, String] The interface you wish to load. This can be `:rails20`, `:rails22`, `:rails23`, `:rack`, `:rails3` or `:sinatra`
def self.class_for(name)
Usher::Interface.const_get(name.to_s.split(/_/).map{|e| e.capitalize}.join) or
raise ArgumentError, "Interface #{name.inspect} doesn't exist."
end
- # Usher::Interface.for(:rack, &block)
+ # Returns the appropriate interface class for a given name.
+ # @param name [Symbol, String] The interface you wish to load. This can be `:rails20`, `:rails22`, `:rails23`, `:rack`, `:rails3` or `:sinatra`
+ # @param args [Object] Any additional parameters the interface wishes to recieve
+ # @return [Object] An intatiated interface
def self.for(name, *args, &block)
class_for(name).new(*args, &block)
end
View
27 lib/usher/interface/email.rb
@@ -1,27 +0,0 @@
-class Usher
- module Interface
- class Email
-
- def initialize(&blk)
- @routes = Usher.new(:delimiters => ['@', '-', '.'], :valid_regex => '[\+a-zA-Z0-9]+')
- instance_eval(&blk) if blk
- end
-
- def for(path, &block)
- @routes.add_route(path).to(block)
- end
-
- def reset!
- @routes.reset!
- end
-
- def act(email)
- response = @routes.recognize(email, email)
- if response.path
- response.path.route.destination.call(response.params_as_hash)
- end
- end
-
- end
- end
-end
View
61 lib/usher/interface/merb.rb
@@ -1,61 +0,0 @@
-require 'merb-core'
-require 'merb-core/dispatch/router/behavior'
-
-class Usher
- module Interface
- class Merb
-
- # merb does everything with class methods.
-
- @root_behavior = ::Merb::Router::Behavior.new.defaults(:action => "index")
-
- class << self
- attr_accessor :root_behavior
-
- UsherRoutes = Usher.new
-
- def prepare(first = [], last = [], &block)
- @routes = []
- root_behavior._with_proxy(&block)
- @routes = first + @routes + last
- compile
- self
- end
-
- def compile
- routes.each do |r|
- r.segments
- end
-
- #puts r.inspect; UsherRoutes.add_route(r) }
- #routes.each {|r| }
- end
-
- def named_routes
- UsherRoutes.named_routes
- end
-
- def routes
- UsherRoutes.routes
- end
-
- def route_for(request)
- p request
- p UsherRoutes.tree
- UsherRoutes.recognize(request)
- end
-
- end
-
- #class BootLoader < ::Merb::BootLoader
- #end
-
- def load_into_merb!
- ::Merb.send(:remove_const, "Router")
- ::Merb.const_set("Router", Usher::Interface::MerbInterface)
- #::Merb::BootLoader.const_set("Router", Usher::Interface::Merb::BootLoader)
- end
-
- end
- end
-end
View
2  lib/usher/interface/rack.rb
@@ -120,7 +120,7 @@ def call(env)
env[router_key] = self
request = ::Rack::Request.new(env)
response = @router.recognize(request, request.path_info)
- if redirect_on_trailing_delimiters and response.only_trailing_delimiters and request.get?
+ if redirect_on_trailing_delimiters and response.only_trailing_delimiters and (request.get? || request.head?)
response = ::Rack::Response.new
response.redirect(request.path_info[0, request.path_info.size - 1], 302)
response.finish
View
27 lib/usher/interface/rack/builder.rb
@@ -1,35 +1,52 @@
class Usher
module Interface
class Rack
- # Replacement for <tt>Rack::Builder</tt> which using Usher to map requests instead of a simple Hash.
+ # Replacement for {Rack::Builder} which using Usher to map requests instead of a simple Hash.
# As well, add convenience methods for the request methods.
- #
class Builder < ::Rack::Builder
def initialize(&block)
@usher = Usher::Interface::Rack.new
super
end
-
+
+ # Maps a path to a block.
+ # @param path [String] Path to map to.
+ # @param options [Hash] Options for added path.
+ # @see Usher#add_route
def map(path, options = nil, &block)
@usher.add(path, options).to(&block)
@ins << @usher unless @ins.last == @usher
end
- # it returns route, and because you may want to work with the route,
- # for example give it a name, we returns the route with GET request
+ # Maps a path with request methods `HEAD` and `GET` to a block.
+ # @param path [String] Path to map to.
+ # @param options [Hash] Options for added path.
+ # @see Usher#add_route
def get(path, options = nil, &block)
self.map(path, options.merge!(:conditions => {:request_method => "HEAD"}), &block)
self.map(path, options.merge!(:conditions => {:request_method => "GET"}), &block)
end
+ # Maps a path with request methods `POST` to a block.
+ # @param path [String] Path to map to.
+ # @param options [Hash] Options for added path.
+ # @see Usher#add_route
def post(path, options = nil, &block)
self.map(path, options.merge!(:conditions => {:request_method => "POST"}), &block)
end
+ # Maps a path with request methods `PUT` to a block.
+ # @param path [String] Path to map to.
+ # @param options [Hash] Options for added path.
+ # @see Usher#add_route
def put(path, options = nil, &block)
self.map(path, options.merge!(:conditions => {:request_method => "PUT"}), &block)
end
+ # Maps a path with request methods `DELETE` to a block.
+ # @param path [String] Path to map to.
+ # @param options [Hash] Options for added path.
+ # @see Usher#add_route
def delete(path, options = nil, &block)
self.map(path, options.merge!(:conditions => {:request_method => "DELETE"}), &block)
end
View
10 lib/usher/interface/rack/middleware.rb
@@ -2,15 +2,19 @@ class Usher
module Interface
class Rack
# Middleware for using Usher's rack interface to recognize the request, then, pass on to the next application.
- # Values are stored in <tt>env</tt> normally.
- #
+ # Values are stored in `env` normally. The details of that storage is in the Rack interface itself.
+ # @see Usher::Interface::Rack
class Middleware
-
+
+ # @param app [#call] Application to call next
+ # @param router [Usher::Interface::Rack] The router call first before calling the next application
def initialize(app, router)
@app = app
@router = router
end
+ # @param env [Hash] The environment hash
+ # @return [#each] The application's return
def call(env)
@router.call(env)
@app.call(env)
View
7 lib/usher/interface/rack/route.rb
@@ -1,17 +1,18 @@
class Usher
module Interface
class Rack
+ # Route specific for Rack with redirection support built in.
class Route < Usher::Route
- attr_accessor :redirect_on_trailing_slash
-
+ # Redirect route to some other path.
def redirect(path, status = 302)
unless (300..399).include?(status)
raise ArgumentError, "Status has to be an integer between 300 and 399"
end
@destination = lambda do |env|
+ params = env[Usher::Interface::Rack::ENV_KEY_PARAMS]
response = ::Rack::Response.new
- response.redirect(path, status)
+ response.redirect(eval(%|"#{path}"|), status)
response.finish
end
self
View
44 lib/usher/node.rb
@@ -3,6 +3,19 @@
require File.join('usher', 'node', 'response')
class Usher
+
+ # The node class used to walk the tree looking for a matching route. The node has three different things that it looks for.
+ # ## Normal
+ # The normal hash is used to normally find matching parts. As well, the reserved key, `nil` is used to denote a variable match.
+ # ## Greedy
+ # The greedy hash is used when you want to match on the entire path. This match can trancend delimiters (unlike the normal match)
+ # and match as much of the path as needed.
+ # ## Request
+ # The request hash is used to find request method restrictions after the entire path has been consumed.
+ #
+ # Once the node finishes looking for matches, it looks for a `terminates` on the node that is usable. If it finds one, it wraps it into a {Node::Response}
+ # and returns that. All actual matching though should normally be done off of {Node::Root#lookup}
+ # @see Root
class Node
attr_reader :normal, :greedy, :request
@@ -12,6 +25,21 @@ def initialize(parent, value)
@parent, @value = parent, value
end
+ def inspect
+ out = ''
+ out << " " * depth
+ out << "#{terminates? ? '* ' : ''}#{depth}: #{value.inspect}\n"
+ [:normal, :greedy, :request].each do |node_type|
+ send(node_type).each do |k,v|
+ out << (" " * (depth + 1)) << "#{node_type.to_s[0].chr} #{k.inspect} ==> \n" << v.inspect
+ end if send(node_type)
+ end
+ out
+ end
+
+ protected
+
+
def depth
@depth ||= parent.is_a?(Node) ? parent.depth + 1 : 0
end
@@ -40,20 +68,6 @@ def route_set
@route_set ||= root.route_set
end
- def inspect
- out = ''
- out << " " * depth
- out << "#{terminates? ? '* ' : ''}#{depth}: #{value.inspect}\n"
- [:normal, :greedy, :request].each do |node_type|
- send(node_type).each do |k,v|
- out << (" " * (depth + 1)) << "#{node_type.to_s[0].chr} #{k.inspect} ==> \n" << v.inspect
- end if send(node_type)
- end
- out
- end
-
- protected
-
def find(request_object, original_path, path, params = [])
# terminates or is partial
@@ -72,7 +86,7 @@ def find(request_object, original_path, path, params = [])
case child_node.value
when String
when Route::Variable::Single
- variable = child_node.value # get the variable
+ variable = child_node.value # get the variable
variable.valid!(part) # do a validity check
until path.empty? || (variable.look_ahead === path.first) # variables have a look ahead notion,
next_path_part = path.shift # and until they are satified,
View
11 lib/usher/node/response.rb
@@ -1,21 +1,26 @@
class Usher
class Node
- class Response < Struct.new(:path, :params_as_array, :remaining_path, :matched_path)
-
- attr_accessor :only_trailing_delimiters
+ # The response from {Usher::Node::Root#lookup}. Adds some convenience methods for common parameter manipulation.
+ class Response < Struct.new(:path, :params_as_array, :remaining_path, :matched_path, :only_trailing_delimiters)
+ # The params from recognition
+ # @return [Array<Symbol, String>] The parameters detected from recognition returned as an array of arrays.
def params
@params ||= path.convert_params_array(params_as_array)
end
+ # @return [Boolean] The state of partial matching
def partial_match?
!remaining_path.nil?
end
+ # The params from recognition
+ # @return [Hash<Symbol, String>] The parameters detected from recognition returned as a hash.
def params_as_hash
@params_as_hash ||= params_as_array.inject({}){|hash, val| hash[path.dynamic_keys[hash.size]] = val; hash}
end
+ # @return [Object] The destination assigned to the matching enclosed path.
def destination
path && path.route.destination
end
View
2  lib/usher/node/root.rb
@@ -1,7 +1,7 @@
class Usher
class Node
class Root < Node
-
+
def initialize(route_set, request_methods)
super(route_set, nil)
self.request_methods = request_methods
View
37 spec/private/email/recognize_spec.rb
@@ -1,37 +0,0 @@
-require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
-require "usher"
-
-def build_email_mock(email)
- request = mock "Request"
- request.should_receive(:email).any_number_of_times.and_return(email)
- request
-end
-
-describe "Usher (for email) route recognition" do
-
- before(:each) do
- @route_set = Usher::Interface.for(:email)
- end
-
- it "should recognize a simple request" do
- receiver = mock('receiver')
- receiver.should_receive(:action).with({}).exactly(1)
- @route_set.for('joshbuddy@gmail.com') { |params| receiver.action(params) }
- @route_set.act('joshbuddy@gmail.com')
- end
-
- it "should recognize a wildcard domain" do
- receiver = mock('receiver')
- receiver.should_receive(:action).with({:domain => 'gmail.com'}).exactly(1)
- @route_set.for('joshbuddy@{!domain,.*}') { |params| receiver.action(params) }
- @route_set.act('joshbuddy@gmail.com')
- end
-
- it "should recognize a complex email" do
- receiver = mock('receiver')
- receiver.should_receive(:action).with({:subject => 'sub+ect', :id => '123', :sid => '456', :tok => 'sdqwe123ae', :domain => 'mydomain.org'}).exactly(1)
- @route_set.for(':subject.{!id,\d+}-{!sid,\d+}-{!tok,\w+}@{!domain,.*}') { |params| receiver.action(params) }
- @route_set.act('sub+ect.123-456-sdqwe123ae@mydomain.org')
- end
-
-end
View
6 spec/private/path_spec.rb
@@ -45,12 +45,6 @@
route_set.named_routes.size == 0
end
- it "should calculate depths for nodes" do
- route_set.add_named_route(:route, '/bad/route/three/four')
- route_set.root.depth.should == 0
- route_set.root.normal['/'].depth.should == 1
- end
-
describe "merging paths" do
before do
@r1 = route_set.add_route("/foo/bar")
View
7 spec/private/rack/route_spec.rb
@@ -21,6 +21,13 @@
status, headers, body = @route_set.call(@env)
headers["Location"].should eql("/")
end
+
+ it "should redirect '/:id.html' to '/:id'" do
+ @route_set.get("/:id.html").redirect('/#{params[:id]}')
+ @env = Rack::MockRequest.env_for("/123.html")
+ status, headers, body = @route_set.call(@env)
+ headers["Location"].should eql("/123")
+ end
end
describe "chaining" do
Please sign in to comment.
Something went wrong with that request. Please try again.