howl-router 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +13 -0
- data/Gemfile +3 -0
- data/README.md +132 -0
- data/Rakefile +10 -0
- data/config.ru +9 -0
- data/lib/howl-router.rb +163 -0
- data/lib/howl-router/matcher.rb +46 -0
- data/lib/howl-router/padrino.rb +17 -0
- data/lib/howl-router/padrino/core.rb +41 -0
- data/lib/howl-router/padrino/ext/class_methods.rb +179 -0
- data/lib/howl-router/padrino/ext/instance_methods.rb +60 -0
- data/lib/howl-router/padrino/matcher.rb +8 -0
- data/lib/howl-router/padrino/route.rb +45 -0
- data/lib/howl-router/padrino/router.rb +8 -0
- data/lib/howl-router/request.rb +7 -0
- data/lib/howl-router/route.rb +40 -0
- data/lib/howl-router/router.rb +70 -0
- data/lib/howl-router/version.rb +3 -0
- data/test/helper.rb +83 -0
- data/test/howl_test.rb +101 -0
- data/test/padrino_test.rb +1918 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 81ee1a9fd3fb9b42514e112a63ae5d0da2531553
|
4
|
+
data.tar.gz: 18f0e9a58f174d209feb7857d77918067059b7ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 23a2afffa48fabc92fa977bf01de758575dd5bd8bed88345cf6d9c92e12e43584143330262a5afeff6e9a6709718e17c1502c14c2ce6f2467e03e75699c10f23
|
7
|
+
data.tar.gz: a08e7b11b6a41c65fd66e8635662aac4c62fcf7c78580ea45cf9d5305088f8c7eb7515d8a1203f3a06bd6fd0f36a32e765651e8692a01a5e7dc50f9baacb76cb
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# Howl Router
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/namusyaka/howl-router.png)](https://travis-ci.org/namusyaka/howl-router)
|
4
|
+
|
5
|
+
A http router for Rack and Padrino.
|
6
|
+
|
7
|
+
Howl works only in Ruby2.0.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
`git clone git@github.com:namusyaka/howl-router.git`
|
12
|
+
|
13
|
+
`cd howl-router; bundle install`
|
14
|
+
|
15
|
+
## Example
|
16
|
+
|
17
|
+
Write this code to your config.ru.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'howl-router'
|
21
|
+
|
22
|
+
howl = Howl.new
|
23
|
+
howl.add(:get, "/") do
|
24
|
+
"get"
|
25
|
+
end
|
26
|
+
|
27
|
+
howl.add(:post, "/") do
|
28
|
+
"post"
|
29
|
+
end
|
30
|
+
|
31
|
+
howl.add(:get, "/users/:user_id") do |params|
|
32
|
+
params.inspect
|
33
|
+
end
|
34
|
+
|
35
|
+
run howl
|
36
|
+
```
|
37
|
+
|
38
|
+
## Normal path
|
39
|
+
|
40
|
+
### Base
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
howl = Howl.new
|
44
|
+
|
45
|
+
howl.add(:get, "/") do
|
46
|
+
"hello"
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
### Regexp
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
howl = Howl.new
|
54
|
+
|
55
|
+
howl.add(:get, /(\d+)/) do
|
56
|
+
"hello"
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
### Params
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
howl = Howl.new
|
64
|
+
|
65
|
+
howl.add(:get, "/users/:name") do |params|
|
66
|
+
"hello #{params[:name]}"
|
67
|
+
end
|
68
|
+
|
69
|
+
howl.add(:get, /\/page\/(.+?)/) do |params|
|
70
|
+
"show #{params[:captures]}"
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
### Captures
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
howl = Howl.new
|
78
|
+
|
79
|
+
users = howl.add(:get, "/users/:name") do |params|
|
80
|
+
"hello #{params[:name]}"
|
81
|
+
end
|
82
|
+
users.captures[:name] = /\d+/
|
83
|
+
```
|
84
|
+
|
85
|
+
### Name and Path
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
howl = Howl.new
|
89
|
+
|
90
|
+
users = howl.add(:get, "/users/:name") do |params|
|
91
|
+
"hello #{params[:name]}"
|
92
|
+
end
|
93
|
+
users.name = :users
|
94
|
+
|
95
|
+
howl.path(:users, :name => "howl") #=> "/users/howl"
|
96
|
+
```
|
97
|
+
|
98
|
+
## with Padrino
|
99
|
+
|
100
|
+
If you use Howl, your application does not use http_router.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
require 'howl-router/padrino'
|
104
|
+
|
105
|
+
class App < Padrino::Application
|
106
|
+
register Howl::Padrino
|
107
|
+
|
108
|
+
get :index do
|
109
|
+
"hello howl!"
|
110
|
+
end
|
111
|
+
|
112
|
+
get :users, :map => "/users/:user_id/", :user_id => /\d+/ do |user_id|
|
113
|
+
params.inspect
|
114
|
+
end
|
115
|
+
|
116
|
+
get :user_items, :map => "/users/:user_id/:item_id", :user_id => /\d+/, :item_id => /[1-9]+/ do |user_id, item_id|
|
117
|
+
"Show #{user_id} and #{item_id}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
## Contributing
|
123
|
+
|
124
|
+
1. fork the project.
|
125
|
+
2. create your feature branch. (`git checkout -b my-feature`)
|
126
|
+
3. commit your changes. (`git commit -am 'commit message'`)
|
127
|
+
4. push to the branch. (`git push origin my-feature`)
|
128
|
+
5. send pull request.
|
129
|
+
|
130
|
+
## License
|
131
|
+
|
132
|
+
the MIT License
|
data/Rakefile
ADDED
data/config.ru
ADDED
data/lib/howl-router.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'howl-router/route'
|
3
|
+
require 'howl-router/router'
|
4
|
+
require 'howl-router/matcher'
|
5
|
+
require 'howl-router/request'
|
6
|
+
|
7
|
+
class Howl
|
8
|
+
class InvalidRouteException < ArgumentError
|
9
|
+
end
|
10
|
+
|
11
|
+
HTTP_VERBS = [:get, :post, :delete, :put, :head]
|
12
|
+
RESPONSE_HEADERS = {
|
13
|
+
:bad_request => 400,
|
14
|
+
:not_found => 404,
|
15
|
+
:method_not_allowed => 405,
|
16
|
+
:server_error => 500
|
17
|
+
}
|
18
|
+
|
19
|
+
# Generate a route, and add to routes.
|
20
|
+
#
|
21
|
+
# @param [String, Symbol] verb The verb decide a acceptable request method.
|
22
|
+
# @param [String] path The path associate to route.
|
23
|
+
# @option options [String] :path_for_generation Accept path_for_generation.
|
24
|
+
# @yield The block assosiate to route.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# howl = Howl.new
|
28
|
+
# howl.add(:get, "/") #=> Howl::Route
|
29
|
+
#
|
30
|
+
# @return [Howl::Route] Return a generated Howl::Route instance.
|
31
|
+
#
|
32
|
+
def add(verb, path, options = {}, &block)
|
33
|
+
verb = verb.downcase.to_sym
|
34
|
+
(router.routes_with_verbs[verb] ||= []) << (route = Route.new(path, &block))
|
35
|
+
route.path_for_generation = options[:path_for_generation] if options[:path_for_generation]
|
36
|
+
route.verb = verb
|
37
|
+
route.router = router
|
38
|
+
router.routes << route
|
39
|
+
route
|
40
|
+
end
|
41
|
+
|
42
|
+
# call method for Rack Application.
|
43
|
+
def call(env)
|
44
|
+
request = Request.new(env)
|
45
|
+
return bad_request unless HTTP_VERBS.include?(request.request_method.downcase.to_sym)
|
46
|
+
compile unless compiled?
|
47
|
+
begin
|
48
|
+
matched_routes = recognize(request)
|
49
|
+
route, params = matched_routes.first
|
50
|
+
result = route.arity != 0 ? route.call(params) : route.call
|
51
|
+
[200, {'Content-Type' => 'text/html;charset=utf-8;'}, [result]]
|
52
|
+
rescue => evar
|
53
|
+
case evar
|
54
|
+
when NotFound then not_found
|
55
|
+
when MethodNotAllowed then method_not_allowed
|
56
|
+
else server_error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Determines whether the compiled.
|
62
|
+
#
|
63
|
+
# @return [Bool]
|
64
|
+
def compiled?
|
65
|
+
@compiled
|
66
|
+
end
|
67
|
+
|
68
|
+
# Compile routes.
|
69
|
+
#
|
70
|
+
# @return [Array] Return a compiled routes.
|
71
|
+
def compile
|
72
|
+
@compiled = true
|
73
|
+
router.compile
|
74
|
+
end
|
75
|
+
|
76
|
+
# Recognize a request, and return a matched routes.
|
77
|
+
#
|
78
|
+
# @param [Rack::Request] request The request is a Rack::Request or instance that inherited it.
|
79
|
+
#
|
80
|
+
# @return [Array] Return a routes that match the path_info.
|
81
|
+
def recognize(request)
|
82
|
+
router.recognize(request)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Recognize a path_info, and return a matched first route's name and params.
|
86
|
+
#
|
87
|
+
# @param [String] path_info
|
88
|
+
#
|
89
|
+
# @return [Array] Return a Array that likes [name, params].
|
90
|
+
def recognize_path(path_info)
|
91
|
+
response = router.recognize(Rack::MockRequest.env_for(path_info))
|
92
|
+
route, params = response.first
|
93
|
+
[route.name, params]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Reset a router.
|
97
|
+
def reset!
|
98
|
+
@compiled = nil
|
99
|
+
router.reset!
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return a Router instance.
|
103
|
+
#
|
104
|
+
# @return [Howl::Router]
|
105
|
+
def router
|
106
|
+
@router ||= Router.new
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return a added routes.
|
110
|
+
#
|
111
|
+
# @return [Array]
|
112
|
+
def routes
|
113
|
+
router.routes
|
114
|
+
end
|
115
|
+
|
116
|
+
# Find a route, and return a generated path of route.
|
117
|
+
#
|
118
|
+
# @param [Symbol] name The name is route name.
|
119
|
+
# @param [Array] args The args are route params and queries.
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
#
|
123
|
+
# howl = Howl.new
|
124
|
+
# index = howl.add(:get, "/:id"){}
|
125
|
+
# index.name = :index
|
126
|
+
# howl.path(:index, :id => 1) #=> "/1"
|
127
|
+
# howl.path(:index, :id => 2, :foo => "bar") #=> "/1?foo=bar"
|
128
|
+
#
|
129
|
+
# @return [String] return a generated path.
|
130
|
+
#
|
131
|
+
def path(name, *args)
|
132
|
+
params = args.delete_at(args.last.is_a?(Hash) ? -1 : 0) || {}
|
133
|
+
saved_args = args.dup
|
134
|
+
router.routes.each do |route|
|
135
|
+
next unless route.name == name
|
136
|
+
matcher = route.matcher
|
137
|
+
if !args.empty? and matcher.mustermann?
|
138
|
+
matcher_names = matcher.names
|
139
|
+
params_for_expand = Hash[matcher_names.map{|matcher_name|
|
140
|
+
[matcher_name.to_sym, (params[matcher_name.to_sym] || args.shift)]
|
141
|
+
}]
|
142
|
+
params_for_expand.merge!(Hash[params.select{|k, v| !matcher_names.include?(name.to_sym) }])
|
143
|
+
args = saved_args.dup
|
144
|
+
else
|
145
|
+
params_for_expand = params.dup
|
146
|
+
end
|
147
|
+
return matcher.mustermann? ? matcher.expand(params_for_expand) : route.path_for_generation
|
148
|
+
end
|
149
|
+
raise InvalidRouteException
|
150
|
+
end
|
151
|
+
|
152
|
+
RESPONSE_HEADERS.keys.each do |method_name|
|
153
|
+
define_method(method_name){|headers = {}| generate_response(method_name.to_sym, headers) }
|
154
|
+
Object.const_set(method_name.to_s.split('_').map(&:capitalize).join, Class.new(StandardError))
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def generate_response(key, headers = {})
|
160
|
+
headers['Content-Type'] = 'text/html;charset=utf-8;' if headers.empty?
|
161
|
+
[RESPONSE_HEADERS[key], headers, [key.to_s.split('_').map(&:capitalize).join(" ")]]
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'mustermann'
|
2
|
+
|
3
|
+
class Howl
|
4
|
+
class Matcher
|
5
|
+
def initialize(path, options = {})
|
6
|
+
@path = path.is_a?(String) && path.empty? ? "/" : path
|
7
|
+
@capture = options.delete(:capture)
|
8
|
+
@default_values = options.delete(:default_values)
|
9
|
+
end
|
10
|
+
|
11
|
+
def match(pattern)
|
12
|
+
handler.match(pattern)
|
13
|
+
end
|
14
|
+
|
15
|
+
def expand(params)
|
16
|
+
params = params.dup
|
17
|
+
query = params.keys.inject({}) do |result, key|
|
18
|
+
result[key] = params.delete(key) if !handler.names.include?(key.to_s)
|
19
|
+
result
|
20
|
+
end
|
21
|
+
params.merge!(@default_values) if @default_values.is_a?(Hash)
|
22
|
+
expanded_path = handler.expand(params)
|
23
|
+
expanded_path = expanded_path + "?" + query.map{|k,v| "#{k}=#{v}" }.join("&") unless query.empty?
|
24
|
+
expanded_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def mustermann?
|
28
|
+
handler.class == Mustermann::Rails
|
29
|
+
end
|
30
|
+
|
31
|
+
def handler
|
32
|
+
@handler ||= case @path
|
33
|
+
when String then Mustermann.new(@path, :type => :rails, :capture => @capture)
|
34
|
+
when Regexp then /^(?:#{@path})$/
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
handler.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def names
|
43
|
+
mustermann? ? handler.names.map(&:to_sym) : []
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'howl-router/padrino/core'
|
2
|
+
require 'howl-router/padrino/route'
|
3
|
+
require 'howl-router/padrino/router'
|
4
|
+
require 'howl-router/padrino/matcher'
|
5
|
+
require 'howl-router/padrino/ext/instance_methods'
|
6
|
+
require 'howl-router/padrino/ext/class_methods'
|
7
|
+
|
8
|
+
class Howl
|
9
|
+
module Padrino
|
10
|
+
class << self
|
11
|
+
def registered(app)
|
12
|
+
app.extend(ClassMethods)
|
13
|
+
app.send(:include, InstanceMethods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'howl-router' unless defined?(Howl)
|
2
|
+
|
3
|
+
class Howl
|
4
|
+
module Padrino
|
5
|
+
class Core < ::Howl
|
6
|
+
def add(verb, path, options = {}, &block)
|
7
|
+
verb = verb.downcase.to_sym
|
8
|
+
(router.routes_with_verbs[verb] ||= []) << (route = Route.new(path, &block))
|
9
|
+
route.path_for_generation = options[:path_for_generation] if options[:path_for_generation]
|
10
|
+
route.verb = verb
|
11
|
+
route.router = router
|
12
|
+
router.routes << route
|
13
|
+
route
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
request = Request.new(env)
|
18
|
+
return bad_request unless HTTP_VERBS.include?(request.request_method.downcase.to_sym)
|
19
|
+
|
20
|
+
compile unless compiled?
|
21
|
+
|
22
|
+
begin
|
23
|
+
matched_routes = recognize(request)
|
24
|
+
[200, {}, matched_routes]
|
25
|
+
rescue => evar
|
26
|
+
case evar
|
27
|
+
when NotFound then not_found
|
28
|
+
when MethodNotAllowed then method_not_allowed('Allow' => request.acceptable_methods.sort.join(", "))
|
29
|
+
else server_error
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def generate_response(key, headers = {})
|
37
|
+
[RESPONSE_HEADERS[key], headers, [key.to_s.split('_').map(&:capitalize) * " "]]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|