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.
@@ -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