pendragon 0.3.0
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/Gemfile.lock +64 -0
- data/README.md +159 -0
- data/Rakefile +19 -0
- data/config.ru +9 -0
- data/lib/pendragon.rb +36 -0
- data/lib/pendragon/compile_helpers.rb +41 -0
- data/lib/pendragon/configuration.rb +25 -0
- data/lib/pendragon/error_handler.rb +43 -0
- data/lib/pendragon/matcher.rb +78 -0
- data/lib/pendragon/padrino.rb +15 -0
- data/lib/pendragon/padrino/ext/class_methods.rb +306 -0
- data/lib/pendragon/padrino/ext/instance_methods.rb +63 -0
- data/lib/pendragon/padrino/route.rb +50 -0
- data/lib/pendragon/padrino/router.rb +45 -0
- data/lib/pendragon/route.rb +60 -0
- data/lib/pendragon/router.rb +188 -0
- data/lib/pendragon/version.rb +4 -0
- data/pendragon.gemspec +20 -0
- data/test/compile_helper.rb +5 -0
- data/test/helper.rb +87 -0
- data/test/padrino_test.rb +1942 -0
- data/test/pendragon_test.rb +139 -0
- metadata +168 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 47c14505bf8ecd94fbdfff0f4bd88ef93ae869d6
|
4
|
+
data.tar.gz: db2b7552746a423ce69adf38a727d1fe3a963c0c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c65a69814973dec2ff98523b6a090ad7e552403e349789ce5af6e28014b4d98d3d45c36498f9836887e6e143884e6539f6f45dc71b6be5021776f06aba3b9337
|
7
|
+
data.tar.gz: 62145cf3f8f5e16c26cbc56412be3bb29da00562abc3e10413da142365e7fcfb3c3c463c4677acd0fb0c4d1e65f9caa15c4a3820c60990f57dde24219f61695f
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
pendragon (0.3.0)
|
5
|
+
mustermann (= 0.2.0)
|
6
|
+
rack (>= 1.3.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (4.0.2)
|
12
|
+
i18n (~> 0.6, >= 0.6.4)
|
13
|
+
minitest (~> 4.2)
|
14
|
+
multi_json (~> 1.3)
|
15
|
+
thread_safe (~> 0.1)
|
16
|
+
tzinfo (~> 0.3.37)
|
17
|
+
atomic (1.1.14)
|
18
|
+
haml (4.0.5)
|
19
|
+
tilt
|
20
|
+
http_router (0.11.0)
|
21
|
+
rack (>= 1.0.0)
|
22
|
+
url_mount (~> 0.2.1)
|
23
|
+
i18n (0.6.9)
|
24
|
+
metaclass (0.0.2)
|
25
|
+
minitest (4.7.5)
|
26
|
+
mocha (1.0.0)
|
27
|
+
metaclass (~> 0.0.1)
|
28
|
+
multi_json (1.8.4)
|
29
|
+
mustermann (0.2.0)
|
30
|
+
padrino-core (0.12.0.rc3)
|
31
|
+
activesupport (>= 3.1)
|
32
|
+
http_router (~> 0.11.0)
|
33
|
+
rack-protection (>= 1.5.0)
|
34
|
+
sinatra (~> 1.4.2)
|
35
|
+
thor (~> 0.17.0)
|
36
|
+
tilt (~> 1.4.1)
|
37
|
+
rack (1.5.2)
|
38
|
+
rack-protection (1.5.2)
|
39
|
+
rack
|
40
|
+
rack-test (0.6.2)
|
41
|
+
rack (>= 1.0)
|
42
|
+
rake (10.1.1)
|
43
|
+
sinatra (1.4.4)
|
44
|
+
rack (~> 1.4)
|
45
|
+
rack-protection (~> 1.4)
|
46
|
+
tilt (~> 1.3, >= 1.3.4)
|
47
|
+
thor (0.17.0)
|
48
|
+
thread_safe (0.1.3)
|
49
|
+
atomic
|
50
|
+
tilt (1.4.1)
|
51
|
+
tzinfo (0.3.38)
|
52
|
+
url_mount (0.2.1)
|
53
|
+
rack
|
54
|
+
|
55
|
+
PLATFORMS
|
56
|
+
ruby
|
57
|
+
|
58
|
+
DEPENDENCIES
|
59
|
+
haml
|
60
|
+
mocha (>= 0.10.0)
|
61
|
+
padrino-core (= 0.12.0.rc3)
|
62
|
+
pendragon!
|
63
|
+
rack-test (>= 0.5.0)
|
64
|
+
rake (>= 0.8.7)
|
data/README.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# Pendragon
|
2
|
+
|
3
|
+
[](https://travis-ci.org/namusyaka/pendragon)
|
4
|
+
|
5
|
+
Provides an HTTP router for use in Rack and Padrino.
|
6
|
+
|
7
|
+
Pendragon works only in Ruby2.0.
|
8
|
+
|
9
|
+
If you want to use in Ruby1.9, you can do it by using [mustermann/1.9-support branch](https://github.com/rkh/mustermann/tree/1.9-support).
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
add this line to your Gemfile.
|
14
|
+
|
15
|
+
`gem 'pendragon'`
|
16
|
+
|
17
|
+
or
|
18
|
+
|
19
|
+
`$ gem install pendragon`
|
20
|
+
|
21
|
+
## Configuration
|
22
|
+
|
23
|
+
If you enable compiler, performance will be improved at the expense of some features as below.
|
24
|
+
|
25
|
+
* Route priority will not work (Might support in the future).
|
26
|
+
* Duplicated routes will not work correctly.
|
27
|
+
* MethodNotAllowed will not work.
|
28
|
+
|
29
|
+
This implementation was inspired by [rack-multiplexer](https://github.com/r7kamura/rack-multiplexer).
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
Pendragon.configure do |config|
|
33
|
+
config.enable_compiler = true # default value is false
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Example
|
38
|
+
|
39
|
+
Write this code to your config.ru.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'pendragon'
|
43
|
+
|
44
|
+
pendragon = Pendragon.new
|
45
|
+
pendragon.add(:get, "/") do
|
46
|
+
"get"
|
47
|
+
end
|
48
|
+
|
49
|
+
pendragon.get("/hey") do
|
50
|
+
"hey"
|
51
|
+
end
|
52
|
+
|
53
|
+
pendragon.post("/hey") do
|
54
|
+
"hey, postman!"
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
pendragon.get("/users/:user_id") do |params|
|
59
|
+
params.inspect
|
60
|
+
end
|
61
|
+
|
62
|
+
run pendragon
|
63
|
+
```
|
64
|
+
|
65
|
+
## Normal path
|
66
|
+
|
67
|
+
### Base
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
pendragon = Pendragon.new
|
71
|
+
|
72
|
+
pendragon.add(:get, "/") do
|
73
|
+
"hello"
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
### Regexp
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
pendragon = Pendragon.new
|
81
|
+
|
82
|
+
pendragon.add(:get, /(\d+)/) do
|
83
|
+
"hello"
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
### Params
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
pendragon = Pendragon.new
|
91
|
+
|
92
|
+
pendragon.add(:get, "/users/:name") do |params|
|
93
|
+
"hello #{params[:name]}"
|
94
|
+
end
|
95
|
+
|
96
|
+
pendragon.add(:get, /\/page\/(.+?)/) do |params|
|
97
|
+
"show #{params[:captures]}"
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
### Captures
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
pendragon = Pendragon.new
|
105
|
+
|
106
|
+
users = pendragon.add(:get, "/users/:name") do |params|
|
107
|
+
"hello #{params[:name]}"
|
108
|
+
end
|
109
|
+
users.captures[:name] = /\d+/
|
110
|
+
```
|
111
|
+
|
112
|
+
### Name and Path
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
pendragon = Pendragon.new
|
116
|
+
|
117
|
+
users = pendragon.add(:get, "/users/:name") do |params|
|
118
|
+
"hello #{params[:name]}"
|
119
|
+
end
|
120
|
+
users.name = :users
|
121
|
+
|
122
|
+
pendragon.path(:users, :name => "howl") #=> "/users/howl"
|
123
|
+
```
|
124
|
+
|
125
|
+
## with Padrino
|
126
|
+
|
127
|
+
If you use Pendragon, your application does not use http_router.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
require 'pendragon/padrino'
|
131
|
+
|
132
|
+
class App < Padrino::Application
|
133
|
+
register Pendragon::Padrino
|
134
|
+
|
135
|
+
get :index do
|
136
|
+
"hello pendragon!"
|
137
|
+
end
|
138
|
+
|
139
|
+
get :users, :map => "/users/:user_id/", :user_id => /\d+/ do |user_id|
|
140
|
+
params.inspect
|
141
|
+
end
|
142
|
+
|
143
|
+
get :user_items, :map => "/users/:user_id/:item_id", :user_id => /\d+/, :item_id => /[1-9]+/ do |user_id, item_id|
|
144
|
+
"Show #{user_id} and #{item_id}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
## Contributing
|
150
|
+
|
151
|
+
1. fork the project.
|
152
|
+
2. create your feature branch. (`git checkout -b my-feature`)
|
153
|
+
3. commit your changes. (`git commit -am 'commit message'`)
|
154
|
+
4. push to the branch. (`git push origin my-feature`)
|
155
|
+
5. send pull request.
|
156
|
+
|
157
|
+
## License
|
158
|
+
|
159
|
+
the MIT License
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'pendragon'
|
4
|
+
|
5
|
+
Rake::TestTask.new(:test_without_compiler) do |test|
|
6
|
+
test.libs << 'test'
|
7
|
+
test.test_files = Dir['test/**/*_test.rb']
|
8
|
+
test.verbose = true
|
9
|
+
end
|
10
|
+
|
11
|
+
Rake::TestTask.new(:test_with_compiler) do |test|
|
12
|
+
test.libs << 'test'
|
13
|
+
test.ruby_opts = ["-r compile_helper.rb"]
|
14
|
+
test.test_files = Dir['test/**/*_test.rb']
|
15
|
+
test.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
task :test => [:test_without_compiler, :test_with_compiler]
|
19
|
+
task :default => :test
|
data/config.ru
ADDED
data/lib/pendragon.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'pendragon/router'
|
2
|
+
|
3
|
+
module Pendragon
|
4
|
+
|
5
|
+
# Allow the verbs of these.
|
6
|
+
HTTP_VERBS = [:get, :post, :delete, :put, :head]
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# A new instance of Pendragon::Router
|
10
|
+
# @see Pendragon::Router#initialize
|
11
|
+
def new(&block)
|
12
|
+
Router.new(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Yields Pendragon configuration block
|
16
|
+
# @example
|
17
|
+
# Pendragon.configure do |config|
|
18
|
+
# config.enable_compiler = true
|
19
|
+
# end
|
20
|
+
# @see Pendragon::Configuration
|
21
|
+
def configure(&block)
|
22
|
+
block.call(configuration) if block_given?
|
23
|
+
configuration
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns Pendragon configuration
|
27
|
+
def configuration
|
28
|
+
@configuration ||= Configuration.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Resets Pendragon configuration
|
32
|
+
def reset_configuration!
|
33
|
+
@configuration = nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
module Pendragon
|
3
|
+
module CompileHelpers
|
4
|
+
def compile!
|
5
|
+
@compiled_regexps = Pendragon::HTTP_VERBS.inject({}){|all, verb| all[verb] = []; all }
|
6
|
+
@routes.each_with_index do |route, index|
|
7
|
+
regexp = route.matcher.handler
|
8
|
+
regexp = regexp.to_regexp if route.matcher.mustermann?
|
9
|
+
@compiled_regexps[route.verb] << /(?<_#{index}>#{regexp})/
|
10
|
+
route.index = "_#{index}"
|
11
|
+
end
|
12
|
+
@compiled_regexps.each_pair{|verb, regexps| @compiled_regexps[verb] = /\A#{Regexp.union(regexps)}\Z/ }
|
13
|
+
end
|
14
|
+
|
15
|
+
def compiled?
|
16
|
+
!!@compiled_regexps
|
17
|
+
end
|
18
|
+
|
19
|
+
def recognize_by_compiling_regexp(request)
|
20
|
+
path_info, verb, request_params = parse_request(request)
|
21
|
+
|
22
|
+
unless @compiled_regexps[verb] === path_info
|
23
|
+
old_path_info = path_info
|
24
|
+
path_info = path_info[0..-2] if path_info != "/" and path_info[-1] == "/"
|
25
|
+
raise NotFound if old_path_info == path_info || !(@compiled_regexps[verb] === path_info)
|
26
|
+
end
|
27
|
+
|
28
|
+
route = @routes.select{|route| route.verb == verb }.detect{|route| Regexp.last_match(route.index) }
|
29
|
+
params, match_data = {}, route.match(path_info)
|
30
|
+
if match_data.names.empty?
|
31
|
+
params[:captures] = match_data.captures
|
32
|
+
else
|
33
|
+
params.merge!(match_data.names.inject({}){|result, name|
|
34
|
+
result[name.to_sym] = match_data[name] ? Rack::Utils.unescape(match_data[name]) : nil
|
35
|
+
result
|
36
|
+
}).merge!(request_params){|key, self_val, new_val| self_val || new_val }
|
37
|
+
end
|
38
|
+
[[route, params]]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Pendragon
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
# Enables to compile the routes
|
5
|
+
# Improve the performance by using this option,
|
6
|
+
# but some features will not work correctly.
|
7
|
+
# @see Pendragon::Router#compile
|
8
|
+
attr_accessor :enable_compiler
|
9
|
+
|
10
|
+
# Constructs an instance of Pendragon::Configuration
|
11
|
+
def initialize
|
12
|
+
@enable_compiler = false
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns an instance variable
|
16
|
+
def [](variable_name)
|
17
|
+
instance_variable_get("@#{variable_name}")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a boolean of @enable_compiler
|
21
|
+
def enable_compiler?
|
22
|
+
!!@enable_compiler
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Pendragon
|
2
|
+
class ErrorHandler < StandardError
|
3
|
+
def call
|
4
|
+
response = []
|
5
|
+
response << (settings[:status] || default_response[0])
|
6
|
+
response << (settings[:headers] || default_response[1])
|
7
|
+
response << Array(settings[:body] || default_response[2])
|
8
|
+
end
|
9
|
+
|
10
|
+
def settings
|
11
|
+
self.class.settings
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def set(key, value)
|
16
|
+
settings[key] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def settings
|
20
|
+
@settings ||= {}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def default_response
|
27
|
+
@default_response ||= [404, {'Content-Type' => 'text/html'}, ["Not Found"]]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
NotFound = Class.new(ErrorHandler)
|
32
|
+
InvalidRouteException = Class.new(ArgumentError)
|
33
|
+
|
34
|
+
class MethodNotAllowed < ErrorHandler
|
35
|
+
set :status, 405
|
36
|
+
set :body, "Method Not Allowed"
|
37
|
+
end
|
38
|
+
|
39
|
+
class BadRequest < ErrorHandler
|
40
|
+
set :status, 400
|
41
|
+
set :body, "Bad Request"
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'mustermann'
|
2
|
+
|
3
|
+
module Pendragon
|
4
|
+
class Matcher
|
5
|
+
# @param [String] path The path is string or regexp.
|
6
|
+
# @option options [Hash] :capture Set capture for path pattern.
|
7
|
+
# @option options [Hash] :default_values Set default_values for path pattern.
|
8
|
+
#
|
9
|
+
# @return [Pendragon::Matcher]
|
10
|
+
#
|
11
|
+
def initialize(path, options = {})
|
12
|
+
@path = path.is_a?(String) && path.empty? ? "/" : path
|
13
|
+
@capture = options.delete(:capture)
|
14
|
+
@default_values = options.delete(:default_values)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Do the matching.
|
18
|
+
#
|
19
|
+
# @param [String] pattern The pattern is actual path (path_info etc).
|
20
|
+
#
|
21
|
+
# @return [MatchData] If the pattern matched this route, return a MatchData.
|
22
|
+
# @return [Nil] If the pattern doesn't matched this route, return a nil.
|
23
|
+
#
|
24
|
+
def match(pattern)
|
25
|
+
pattern = pattern[0..-2] if mustermann? and pattern != "/" and pattern[-1] == "/"
|
26
|
+
handler.match(pattern)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Expands the path with params.
|
30
|
+
#
|
31
|
+
# @param [Hash] params The params for path pattern.
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# matcher = Pendragon::Matcher.new("/foo/:bar")
|
35
|
+
# matcher.expand(:bar => 123) #=> "/foo/123"
|
36
|
+
# matcher.expand(:bar => "bar", :baz => "test") #=> "/foo/bar?baz=test"
|
37
|
+
#
|
38
|
+
# @return [String] A expaneded path.
|
39
|
+
def expand(params)
|
40
|
+
params = params.dup
|
41
|
+
query = params.keys.inject({}) do |result, key|
|
42
|
+
result[key] = params.delete(key) if !handler.names.include?(key.to_s)
|
43
|
+
result
|
44
|
+
end
|
45
|
+
params.merge!(@default_values) if @default_values.is_a?(Hash)
|
46
|
+
expanded_path = handler.expand(params)
|
47
|
+
expanded_path = expanded_path + "?" + query.map{|k,v| "#{k}=#{v}" }.join("&") unless query.empty?
|
48
|
+
expanded_path
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Boolean] This matcher's handler is mustermann ?
|
52
|
+
def mustermann?
|
53
|
+
handler.instance_of?(Mustermann::Sinatra)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Mustermann::Sinatra] Returns a Mustermann::Sinatra when @path is string.
|
57
|
+
# @return [Regexp] Returns a regexp when @path is regexp.
|
58
|
+
def handler
|
59
|
+
@handler ||=
|
60
|
+
case @path
|
61
|
+
when String
|
62
|
+
Mustermann.new(@path, :capture => @capture)
|
63
|
+
when Regexp
|
64
|
+
/^(?:#{@path})$/
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [String] Returns a converted handler.
|
69
|
+
def to_s
|
70
|
+
handler.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Array] Returns a named captures.
|
74
|
+
def names
|
75
|
+
handler.names.map(&:to_sym)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|