pendragon 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,13 @@
1
+ lang: ruby
2
+ before_install: gem install bundler --pre
3
+ install:
4
+ - gem update --system
5
+ - bundle update
6
+ rvm:
7
+ - 2.0.0
8
+ notifications:
9
+ recipients:
10
+ - namusyaka@gmail.com
11
+ branches:
12
+ only:
13
+ - master
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -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)
@@ -0,0 +1,159 @@
1
+ # Pendragon
2
+
3
+ [![Build Status](https://travis-ci.org/namusyaka/pendragon.png)](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
@@ -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
@@ -0,0 +1,9 @@
1
+ load File.expand_path('../lib/pendragon.rb', __FILE__)
2
+
3
+ pendragon = Pendragon.new
4
+
5
+ pendragon.add(:get, "/") do
6
+ "hello world !"
7
+ end
8
+
9
+ run pendragon
@@ -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