howl-router 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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
|