aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGibheer <gibheer@gmail.com>2012-10-11 06:38:55 +0200
committerGibheer <gibheer@gmail.com>2012-10-13 11:48:11 +0200
commit79537632ac62957f1de6f189878d6757b0dd6abb (patch)
tree2ad80848505c04943639ebc838d89704b4402133
parent1c1bda6b66a13b41d53e8ea0bb46debe23ddfd6e (diff)
added new router to the tools
This small router is intended to work like URLMap, but with the feature that it can extract variables from routes.
-rw-r--r--config.ru16
-rw-r--r--lib/zero.rb6
-rw-r--r--lib/zero/rack_request.rb44
-rw-r--r--lib/zero/router.rb31
-rw-r--r--spec/integration/router_spec.rb49
-rw-r--r--spec/spec_helper.rb14
6 files changed, 158 insertions, 2 deletions
diff --git a/config.ru b/config.ru
new file mode 100644
index 0000000..c2c7c62
--- /dev/null
+++ b/config.ru
@@ -0,0 +1,16 @@
+require File.expand_path('../lib/zero.rb', __FILE__)
+require 'json'
+
+class Foo
+ def call(env)
+ req = Rack::Request.new(env)
+ [200, {'Content-Type' => 'text/html'}, ["this works #{req.params.inspect}"]]
+ end
+end
+
+routes = Zero::Router.new(
+ '/foo/:id' => Foo.new,
+ '/' => Foo.new
+)
+
+run routes
diff --git a/lib/zero.rb b/lib/zero.rb
index 7dca8e1..e9824f9 100644
--- a/lib/zero.rb
+++ b/lib/zero.rb
@@ -1,5 +1,7 @@
+require 'rack'
+
module Zero
require_relative 'zero/controller'
- require_relative 'zero/request'
- require_relative 'zero/response'
+ require_relative 'zero/rack_request'
+ require_relative 'zero/router'
end
diff --git a/lib/zero/rack_request.rb b/lib/zero/rack_request.rb
new file mode 100644
index 0000000..0a93fc5
--- /dev/null
+++ b/lib/zero/rack_request.rb
@@ -0,0 +1,44 @@
+# as we need #update_param we patch it it into the request
+# this is directly from thr request.rb of the rack repository, so this
+# can be deleted, when rack was released
+r = Rack::Request.new(Rack::MockRequest.env_for('foo'))
+if not r.respond_to?('update_param') then
+ class Rack::Request
+ # Destructively update a parameter, whether it's in GET and/or POST.
+ # Returns nil.
+ #
+ # The parameter is updated wherever it was previous defined, so
+ # GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
+ #
+ # env['rack.input'] is not touched.
+ def update_param(k, v)
+ found = false
+ if self.GET.has_key?(k)
+ found = true
+ self.GET[k] = v
+ end
+ if self.POST.has_key?(k)
+ found = true
+ self.POST[k] = v
+ end
+ unless found
+ self.GET[k] = v
+ end
+ @params = nil
+ nil
+ end
+
+ # Destructively delete a parameter, whether it's in GET or POST.
+ # Returns the value of the deleted parameter.
+ #
+ # If the parameter is in both GET and POST, the POST value takes
+ # precedence since that's how #params works.
+ #
+ # env['rack.input'] is not touched.
+ def delete_param(k)
+ v = [ self.POST.delete(k), self.GET.delete(k) ].compact.first
+ @params = nil
+ v
+ end
+ end
+end
diff --git a/lib/zero/router.rb b/lib/zero/router.rb
new file mode 100644
index 0000000..c890cb7
--- /dev/null
+++ b/lib/zero/router.rb
@@ -0,0 +1,31 @@
+module Zero
+ class Router
+ # match for variables in routes
+ VARIABLE_MATCH = %r{:(\w+)[^/]?}
+ # the replacement string to make it an regex
+ VARIABLE_REGEX = '(?<\1>.+?)'
+
+ def initialize(routes)
+ @routes = {}
+ routes.each do |route, target|
+ @routes[
+ Regexp.new(
+ route.gsub(VARIABLE_MATCH, VARIABLE_REGEX) + '$')] = target
+ end
+ end
+
+ def call(env)
+ request = Rack::Request.new(env)
+ @routes.each do |route, target|
+ match = route.match(request.path)
+ if match
+ match.names.each_index do |i|
+ request.update_param(match.names[i], match.captures[i])
+ end
+ return target.call(request.env)
+ end
+ end
+ [404, {'Content-Type' => 'text/html'}, ['Not found!']]
+ end
+ end
+end
diff --git a/spec/integration/router_spec.rb b/spec/integration/router_spec.rb
new file mode 100644
index 0000000..3be7b3a
--- /dev/null
+++ b/spec/integration/router_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Zero::Router do
+ let(:router) { Zero::Router.new(routes) }
+ subject { router.call(env) }
+# let(:app) do
+# lambda {|env| [200, {'Content-Type' => 'text/html'}, 'correct'] }
+# end
+ let(:app) { double }
+ let(:wrong_app) do
+ lambda {|env| [200, {'Content-Type' => 'text/html'}, 'Wrong'] }
+ end
+
+ before :each do
+ app.should_receive(:call)
+ end
+
+ context 'a working route' do
+ let(:routes) { { '/app' => app } }
+ let(:env) { generate_env('/app') }
+ it('takes a route') { subject }
+ end
+
+ context 'select the right route' do
+ let(:routes) do
+ { '/wrong' => wrong_app,
+ '/correct' => app }
+ end
+ let(:env) { generate_env('/correct') }
+ it("selects the correct from multiple routes") { subject }
+ end
+
+ context 'uses the deepest path' do
+ let(:routes) { { '/wrong' => wrong_app,
+ '/deep' => wrong_app,
+ '/deep/wrong' => wrong_app,
+ '/deep/correct' => app }}
+ let(:env) { generate_env('/deep/correct') }
+ it("finds uses the deepest path first") { subject }
+ end
+
+ context 'converts parts of the url to parameters' do
+ let(:routes) { { '/foo/:id' => app } }
+ let(:env) { generate_env('/foo/42') }
+ it "should extract variables from the url" do
+ subject
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..b31fe56
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,14 @@
+require 'zero'
+
+class SpecApp
+ attr_reader :env
+
+ def call(env)
+ @env = env
+ return [200, {'Content-Type' => 'text/html'}, ['success']]
+ end
+end
+
+def generate_env(path, options = {})
+ Rack::MockRequest.env_for(path, options = {})
+end