rack-routes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
data/.gemtest ADDED
File without changes
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ === 0.1.0 / 2012-02-22
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ examples/config.ru
7
+ lib/rack/routes.rb
8
+ test/helper.rb
9
+ test/test_rack_routes.rb
data/README.rdoc ADDED
@@ -0,0 +1,59 @@
1
+ = rack-routes
2
+
3
+ * https://github.com/bhenderson/rack-routes
4
+
5
+ == DESCRIPTION:
6
+
7
+ Provides a routing layer for Rack similar to nginx location directive.
8
+ http://wiki.nginx.org/HttpCoreModule#location
9
+
10
+ support provided by ATT Interactive (http://yp.com)
11
+
12
+ == SYNOPSIS:
13
+
14
+ Adding location directives will route any request which matches the path according to the rules to the given block. The idea is to further extend this to call your own code (controller/action, etc.)
15
+
16
+ see sample controller in examples/config.ru
17
+ see also the docs for Rack::Routes#location
18
+
19
+ == FEATURES/PROBLEMS:
20
+
21
+ * attempting to copy the nginx location directive.
22
+
23
+ == INSTALL:
24
+
25
+ * gem install rack-routes
26
+
27
+ == DEVELOPERS:
28
+
29
+ After checking out the source, run:
30
+
31
+ $ rake newb
32
+
33
+ This task will install any missing dependencies, run the tests/specs,
34
+ and generate the RDoc.
35
+
36
+ == LICENSE:
37
+
38
+ (The MIT License)
39
+
40
+ Copyright (c) 2012 bhenderson
41
+
42
+ Permission is hereby granted, free of charge, to any person obtaining
43
+ a copy of this software and associated documentation files (the
44
+ 'Software'), to deal in the Software without restriction, including
45
+ without limitation the rights to use, copy, modify, merge, publish,
46
+ distribute, sublicense, and/or sell copies of the Software, and to
47
+ permit persons to whom the Software is furnished to do so, subject to
48
+ the following conditions:
49
+
50
+ The above copyright notice and this permission notice shall be
51
+ included in all copies or substantial portions of the Software.
52
+
53
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
54
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
55
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
56
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
57
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
58
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
59
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ # Hoe.plugin :compiler
7
+ # Hoe.plugin :gem_prelude_sucks
8
+ # Hoe.plugin :inline
9
+ # Hoe.plugin :isolate
10
+ # Hoe.plugin :racc
11
+ # Hoe.plugin :rubyforge
12
+
13
+ Hoe.spec 'rack-routes' do
14
+ developer 'Brian Henderson', 'bhenderson@attinteractive.com'
15
+
16
+ # self.rubyforge_name = 'rack-routesx' # if different than 'rack-routes'
17
+ end
18
+
19
+ # vim: syntax=ruby
@@ -0,0 +1,76 @@
1
+ require 'rack/routes'
2
+
3
+ class Server
4
+ class Request < Rack::Request
5
+ end
6
+ class Response < Rack::Response
7
+ end
8
+
9
+ def self.route verb, path, opts = {}, &blk
10
+ app = self
11
+ opts.merge! :method => verb.upcase
12
+
13
+ # change default behavior to use exact
14
+ opts[:exact] = opts.fetch(:exact, true)
15
+ Rack::Routes.location path, opts do |env|
16
+ app.call(env, &blk)
17
+ end
18
+ end
19
+
20
+ def self.get(*a, &b) route 'GET', *a, &b end
21
+ def self.post(*a, &b) route 'POST', *a, &b end
22
+
23
+ def self.call env, &blk
24
+ new.call! env, &blk
25
+ end
26
+
27
+ attr_reader :env, :request, :response
28
+ def call! env, &blk
29
+ @env = env
30
+ @request = Request.new @env
31
+ @response = Response.new
32
+
33
+ status 200
34
+ content_type 'text/plain'
35
+
36
+ response.write instance_exec(&blk)
37
+ response.finish
38
+ end
39
+
40
+ def content_type type
41
+ return headers['Content-Type'] unless type
42
+ headers 'Content-Type' => type
43
+ end
44
+
45
+ def headers opts = {}
46
+ response.headers.merge! opts
47
+ end
48
+
49
+ def status num = nil
50
+ return response.status unless num
51
+ response.status = num.to_i
52
+ end
53
+
54
+ get '/' do
55
+ foo
56
+ end
57
+
58
+ get '/asdf', :exact => false do
59
+ 'hi there'
60
+ end
61
+
62
+ post '/' do
63
+ bar
64
+ end
65
+
66
+ def foo
67
+ '3'
68
+ end
69
+
70
+ def bar
71
+ '4'
72
+ end
73
+ end
74
+
75
+ use Rack::Routes
76
+ run lambda{|env| [500, {'Content-Type' => 'text/plain'}, ['not found']]}
@@ -0,0 +1,181 @@
1
+ require 'uri'
2
+
3
+ module Rack
4
+ class Routes
5
+ VERSION = '0.1.0'
6
+
7
+ class LocDirectiveError < RuntimeError; end
8
+
9
+ class << self
10
+
11
+ # convience rack interface
12
+ # Enables the use of either:
13
+ # use Rack::Routes
14
+ # or
15
+ # run Rack::Routes
16
+
17
+ def call env
18
+ @app ||= new
19
+ @app.call env
20
+ end
21
+
22
+ # location directives should be run in a certain order. This is needed to
23
+ # establish that order.
24
+
25
+ def compile!
26
+ # longest first
27
+ [:exact, :string, :string_break].each do |type|
28
+ locations[type].sort_by!{|path, _| -path.length}
29
+ end
30
+ end
31
+
32
+ ##
33
+ # === Description
34
+ #
35
+ # Main interface method.
36
+ # Reimplementation of nginx location directive.
37
+ #
38
+ # === Args
39
+ # +path+:: The path to match on. Can be String or Regexp.
40
+ # +opts+:: Hash of options. see below.
41
+ #
42
+ # +opts+ keys:
43
+ # +:exact+:: Type of string matching. default false
44
+ # Also can be +:prefix+ (for making '^~' make sense)
45
+ # +:method+:: HTTP Request Method matching. defaults nil (all)
46
+ # +:type+:: Explicetly set the type of match.
47
+ #
48
+ # +exact+ values can be:
49
+ # +false+:: prefix match
50
+ # +true+:: literal match
51
+ # +=+:: literal match
52
+ # +^~+:: skip regex matching
53
+ #
54
+ # yields +env+
55
+ #
56
+ # === Examples
57
+ #
58
+ # # config.ru
59
+ #
60
+ # # matches everything
61
+ # # but longer matches will get applied first
62
+ # Rack::Routes.location '/' do
63
+ # [200, {}, ['hi']]
64
+ # end
65
+ #
66
+ # # matches everything beginning with /asdf
67
+ # Rack::Routes.location '/asdf' do
68
+ # [200, {}, ['hi asdf']]
69
+ # end
70
+ #
71
+ # # matches /foo and not /foobar nor /foo/baz etc.
72
+ # Rack::Routes.location '/foo', :exact => true do
73
+ # [200, {}, ['hi foo']]
74
+ # end
75
+ #
76
+ # # matches anything beginning with /bar
77
+ # # +path+ can be any ruby regex
78
+ # # matchdata is stored in env['routes.location.matchdata']
79
+ # Rack::Routes.location /\/bar(.*)/ do |env|
80
+ # m = env['routes.location.matchdata']
81
+ # [200, {}, ["hi #{m[1]}"]]
82
+ # end
83
+ #
84
+ # # matches beginning of path but stops if match is found. Does not
85
+ # evaluate regexen
86
+ # Rack::Routes.location '/baz', :prefix => '^~' do
87
+ # [200, {}, ['hi baz']]
88
+ # end
89
+ #
90
+ # run Rack::Routes
91
+
92
+ def location path, opts = {}, &blk
93
+ type = opts.fetch(:type, nil)
94
+ type = :regex if Regexp === path
95
+ type ||= case opts.fetch(:exact, opts.fetch(:prefix, false))
96
+ when FalseClass
97
+ :string
98
+ when TrueClass, '='
99
+ :exact
100
+ when '^~'
101
+ :string_break
102
+ end
103
+
104
+ raise LocDirectiveError, "unknown type `#{type}'" unless
105
+ [:regex, :string, :exact, :string_break].include? type
106
+
107
+ app = blk
108
+
109
+ locations[type] << [path, app, opts]
110
+ end
111
+
112
+ # accessor for @locations hash
113
+ def locations
114
+ @locations ||= Hash.new{|h,k| h[k] = []}
115
+ end
116
+ end
117
+
118
+
119
+ # rack app
120
+ def initialize app = nil
121
+ self.class.compile!
122
+ @app = app
123
+ end
124
+
125
+ # rack interface
126
+ def call env
127
+ dup.call! env
128
+ end
129
+
130
+ def call! env
131
+ @env = env
132
+ @path = URI.decode @env['PATH_INFO']
133
+
134
+ (matching_app || @app).call(env)
135
+ end
136
+
137
+ def find_type type
138
+ _, app = locations[type].find do |path, _, opts|
139
+ next if opts[:method] and opts[:method] != @env['REQUEST_METHOD']
140
+ yield path
141
+ end
142
+ app
143
+ end
144
+
145
+ def find_exact
146
+ find_type(:exact){|pth| pth == @path}
147
+ end
148
+
149
+ def find_string
150
+ find_type(:string){|pth| @path[0, pth.length] == pth}
151
+ end
152
+
153
+ def find_string_break
154
+ find_type(:string_break){|pth| @path[0, pth.length] == pth}
155
+ end
156
+
157
+ def find_regex
158
+ find_type(:regex){|pth| pth === @path and
159
+ @env['routes.location.matchdata'] = Regexp.last_match }
160
+ end
161
+
162
+ def locations
163
+ self.class.locations
164
+ end
165
+
166
+ # search exact matches first
167
+ # then ^~ strings (not sure what else to call them)
168
+ # then regex
169
+ # then all the other strings
170
+ #
171
+ # NOTE the docs say to run strings before regex but I don't see why it
172
+ # matters as the return logic is the same.
173
+
174
+ def matching_app
175
+ find_exact ||
176
+ find_string_break ||
177
+ find_regex ||
178
+ find_string
179
+ end
180
+ end
181
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'minitest/autorun'
2
+ require 'rack/routes'
3
+
4
+ class TestRack
5
+ end
6
+ class RoutesTestCase < MiniTest::Unit::TestCase
7
+ def app
8
+ Rack::Routes
9
+ end
10
+ end
@@ -0,0 +1,149 @@
1
+ require 'helper'
2
+
3
+ class TestRack::TestRoutes < RoutesTestCase
4
+ def setup
5
+ @env = {'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/'}
6
+ app.locations.clear
7
+ end
8
+
9
+ def test_location_exact
10
+ app.location '/', :exact => true do
11
+ "1"
12
+ end
13
+
14
+ assert_location_match '1', '/'
15
+ refute_location_match '/asdf'
16
+ end
17
+
18
+ def test_location_string
19
+ app.location '/' do
20
+ "2"
21
+ end
22
+
23
+ assert_location_match '2', '/'
24
+ assert_location_match '2', '/asdf'
25
+ end
26
+
27
+ def test_location_string_longest
28
+ app.location '/' do
29
+ "2"
30
+ end
31
+ app.location '/asd' do
32
+ "3"
33
+ end
34
+ app.compile!
35
+
36
+ assert_location_match '3', '/asdf'
37
+ end
38
+
39
+ def test_location_complex
40
+ app.location '/', :exact => true do
41
+ "1"
42
+ end
43
+ app.location '/' do
44
+ "2"
45
+ end
46
+ app.location '/images/', :prefix => '^~' do
47
+ "3"
48
+ end
49
+ app.location /\.(gif|jpg|jpeg)$/i do
50
+ "4"
51
+ end
52
+
53
+ assert_location_match '1', '/'
54
+ assert_location_match '2', '/docs/docs.html'
55
+ assert_location_match '3', '/images/1.gif'
56
+ assert_location_match '4', '/docs/1.jpg'
57
+ end
58
+
59
+ def test_decoded_uris
60
+ app.location '/images/ /test' do
61
+ '1'
62
+ end
63
+
64
+ assert_location_match '1', '/images/%20/test'
65
+ end
66
+
67
+ def test_captures
68
+ app.location /^\/imag(.*)/ do
69
+ end
70
+
71
+ @env['PATH_INFO'] = '/images/foo'
72
+ app.call @env
73
+
74
+ m = @env['routes.location.matchdata']
75
+ assert_equal 'es/foo', m[1]
76
+ end
77
+
78
+ def test_request_method
79
+ app.location '/', :method => 'GET' do
80
+ '1'
81
+ end
82
+ app.location '/', :method => 'POST' do
83
+ '2'
84
+ end
85
+
86
+ assert_location_match '1', '/'
87
+ assert_location_match '2', '/', 'REQUEST_METHOD' => 'POST'
88
+ end
89
+
90
+ def test_string_break_skips_regex
91
+ app.location '/asd', :prefix => '^~' do
92
+ '1'
93
+ end
94
+
95
+ app.location /\/asdf/ do
96
+ '2'
97
+ end
98
+
99
+ assert_location_match '1', '/asdf'
100
+ end
101
+
102
+ def test_regex_first
103
+ app.location '/asd' do
104
+ '1'
105
+ end
106
+
107
+ assert_location_match '1', '/asdf'
108
+
109
+ app.location /\/asdf/ do
110
+ '2'
111
+ end
112
+
113
+ assert_location_match '2', '/asdf'
114
+ end
115
+
116
+ def test_call_twice
117
+ app.location /\/asdf/ do
118
+ '2'
119
+ end
120
+
121
+ assert_location_match '2', '/asdf'
122
+ assert_location_match '2', '/asdf'
123
+ end
124
+
125
+ def test_invalid_types
126
+ assert_raises app::LocDirectiveError do
127
+ app.location '/', :prefix => nil do
128
+ end
129
+ end
130
+ assert_raises app::LocDirectiveError do
131
+ app.location '/', :prefix => 'unknown' do
132
+ end
133
+ end
134
+ end
135
+
136
+ def assert_location_match expected, path, opts = {}
137
+ @env.merge! opts
138
+ @env['PATH_INFO'] = path
139
+ actual = app.call @env
140
+ assert_equal expected, actual, path
141
+ end
142
+
143
+ def refute_location_match path
144
+ assert_raises NoMethodError do
145
+ app.call 'PATH_INFO' => path
146
+ end
147
+ end
148
+
149
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-routes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brian Henderson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rdoc
16
+ requirement: &20516820 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.10'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *20516820
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ requirement: &20516400 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.13'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *20516400
36
+ description: ''
37
+ email:
38
+ - bhenderson@attinteractive.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files:
42
+ - History.txt
43
+ - Manifest.txt
44
+ files:
45
+ - .autotest
46
+ - History.txt
47
+ - Manifest.txt
48
+ - README.rdoc
49
+ - Rakefile
50
+ - examples/config.ru
51
+ - lib/rack/routes.rb
52
+ - test/helper.rb
53
+ - test/test_rack_routes.rb
54
+ - .gemtest
55
+ homepage:
56
+ licenses: []
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --main
60
+ - README.txt
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project: rack-routes
77
+ rubygems_version: 1.8.11
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: ''
81
+ test_files:
82
+ - test/test_rack_routes.rb