howl-router 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
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
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'test'
6
+ test.test_files = Dir['test/**/*_test.rb']
7
+ test.verbose = true
8
+ end
9
+
10
+ task :default => :test
data/config.ru ADDED
@@ -0,0 +1,9 @@
1
+ load File.expand_path('../lib/howl.rb', __FILE__)
2
+
3
+ howl = Howl.new
4
+
5
+ howl.add(:get, "/") do
6
+ "hello world !"
7
+ end
8
+
9
+ run howl
@@ -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