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