wouter 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/README.md +27 -0
- data/Rakefile +13 -0
- data/examples/hello.rb +15 -0
- data/lib/wouter/endpoint.rb +48 -0
- data/lib/wouter/route.rb +60 -0
- data/lib/wouter/util.rb +13 -0
- data/lib/wouter/wrapper.rb +33 -0
- data/lib/wouter.rb +26 -152
- data/wouter.gemspec +23 -0
- metadata +31 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 328983ffb5c818d8e95bfd645fa6a4390bc97efee9d3b195d28cd86421b69315
|
4
|
+
data.tar.gz: '01191a528dab4215b98d03545b863c4cd8c3f0b5be7c0f9f91e97a0bbe300e72'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e1b52bc209def3b17235c036102d145c1e43ed31deb318288437956307489636573c037342dd064d541c8f59d6343b9d42f363c4e3638e4d36d83e9d2c2c9ea
|
7
|
+
data.tar.gz: a41ead4e722ebccfd50af05e13571cd880f3f580eec097d89cc992f09376bc6352ab96c1560d85589cb9cd20d1c08d4352c12d98f48146199d65ad14d8a11ae4
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Wouter
|
2
|
+
|
3
|
+
Wouter is a modular web framework built on top of Rack.
|
4
|
+
|
5
|
+
Wouter design goals: explicit, modular, readable.
|
6
|
+
|
7
|
+
## Basic Usage
|
8
|
+
|
9
|
+
Wouter allows you to define routes that connect to endpoints.
|
10
|
+
|
11
|
+
Here is an example `rackup` compatible file:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'wouty'
|
15
|
+
|
16
|
+
class HelloWorld < Wouty::Endpoint
|
17
|
+
def respond
|
18
|
+
'Hello, world!'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Routes < Wouty
|
23
|
+
get '/', HelloWorld
|
24
|
+
end
|
25
|
+
|
26
|
+
run Routes.build
|
27
|
+
```
|
data/Rakefile
ADDED
data/examples/hello.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# `Wouter::Endpoint` is a class to help define Rack compatible endpoints that
|
4
|
+
# can be assigned to a route path. Each `Wouter::Endpoint` instance is a Rack
|
5
|
+
# application of it's own. It provides convience methods for creating endpoints.
|
6
|
+
#
|
7
|
+
class Wouter::Endpoint
|
8
|
+
class NotImplementedError < StandardError; end
|
9
|
+
|
10
|
+
attr_reader :req, :res
|
11
|
+
|
12
|
+
def initialize(req, res)
|
13
|
+
@req = req
|
14
|
+
@res = res
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.call(env)
|
18
|
+
endpoint = new(Rack::Request.new(env), Rack::Response.new)
|
19
|
+
response = endpoint.respond
|
20
|
+
|
21
|
+
return response.finish if response.is_a?(Rack::Response)
|
22
|
+
|
23
|
+
endpoint.res.write response
|
24
|
+
endpoint.res.finish
|
25
|
+
end
|
26
|
+
|
27
|
+
def respond
|
28
|
+
raise NotImplementedError, 'you must implemented #respond in your Wouter::Endpoint class'
|
29
|
+
end
|
30
|
+
|
31
|
+
def params
|
32
|
+
@req.params
|
33
|
+
end
|
34
|
+
|
35
|
+
def headers(hash = nil)
|
36
|
+
@res.headers.merge! hash if hash
|
37
|
+
@res.headers
|
38
|
+
end
|
39
|
+
|
40
|
+
def status(code)
|
41
|
+
@res.status = code
|
42
|
+
@res.status
|
43
|
+
end
|
44
|
+
|
45
|
+
def not_found
|
46
|
+
Wouter::Util.not_found
|
47
|
+
end
|
48
|
+
end
|
data/lib/wouter/route.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# `Wouter::Route` represents a route definition. Example:
|
4
|
+
#
|
5
|
+
# get '/users/:id/comments/:comment_id', RouteKlass
|
6
|
+
#
|
7
|
+
# It contains utility methods for matching a Rack request to a route.
|
8
|
+
#
|
9
|
+
class Wouter::Route
|
10
|
+
REGEX = '([A-Za-z0-9\-\_]+)'
|
11
|
+
|
12
|
+
attr_reader :request_method, :path, :klass
|
13
|
+
|
14
|
+
def initialize(request_method:, path:, klass:)
|
15
|
+
@request_method = request_method
|
16
|
+
@path = path
|
17
|
+
@klass = klass
|
18
|
+
end
|
19
|
+
|
20
|
+
# Expects `request` to be a `Rack::Request`
|
21
|
+
# Check `request.request_method` and `request.path`
|
22
|
+
def match?(request)
|
23
|
+
return false if request.request_method.to_sym != @request_method
|
24
|
+
return true if request.path == path
|
25
|
+
|
26
|
+
path_regex =~ request.path
|
27
|
+
end
|
28
|
+
|
29
|
+
# Expects `request` to be a `Rack::Request`
|
30
|
+
# Parse the path parameters out of `request.path`
|
31
|
+
def params(request)
|
32
|
+
request.path.match(named_path_regex)&.named_captures
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# /user/:id/comments/:comment_id
|
38
|
+
# /\/user\/(?<id>\w+)\/comments\/(?<comment_id>\w+)/
|
39
|
+
def named_path_regex
|
40
|
+
@named_path_regex ||= begin
|
41
|
+
string_regex = path
|
42
|
+
path.scan(/(:\w+)/).flatten.each do |param|
|
43
|
+
string_regex = string_regex.gsub(param, '(?<' + param[1..-1] +'>' + REGEX + ')')
|
44
|
+
end
|
45
|
+
Regexp.new(string_regex)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# /user/:id/comments/:comment_id
|
50
|
+
# /\/user\/(\w+)\/comments\/(\w+)/
|
51
|
+
def path_regex
|
52
|
+
@path_regex ||= begin
|
53
|
+
string_regex = path
|
54
|
+
path.scan(/(:\w+)/).flatten.each do |param|
|
55
|
+
string_regex = string_regex.gsub(param, REGEX)
|
56
|
+
end
|
57
|
+
Regexp.new(string_regex)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/wouter/util.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# `Wouter::Wrapper` is a class that provides a top level Rack application containing
|
4
|
+
# all the routing endpoints. HTTP requests come through `Wouter::Wrapper` and it dispatches
|
5
|
+
# to the appropriate route class that was defined by the user.
|
6
|
+
#
|
7
|
+
class Wouter::Wrapper
|
8
|
+
def initialize(routes:)
|
9
|
+
@routes = routes
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
request = Rack::Request.new(env)
|
14
|
+
route = find_route(request)
|
15
|
+
|
16
|
+
if route
|
17
|
+
route.params(request).each do |name, value|
|
18
|
+
request.update_param(name, value)
|
19
|
+
end
|
20
|
+
route.klass.call(request.env)
|
21
|
+
else
|
22
|
+
Wouter::Util.not_found
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def find_route(request)
|
29
|
+
@routes.find do |route|
|
30
|
+
route.match?(request)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/wouter.rb
CHANGED
@@ -4,180 +4,54 @@ require 'bundler'
|
|
4
4
|
Bundler.setup(:default)
|
5
5
|
require 'rack'
|
6
6
|
|
7
|
-
# Wouter
|
8
|
-
class Wouter
|
9
|
-
# ========== Endpoint class to make life easier
|
10
|
-
class Endpoint
|
11
|
-
attr_accessor :req, :res
|
7
|
+
# Define `Wouter` so we can load stuff
|
8
|
+
class Wouter; end
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@res = res
|
18
|
-
end
|
10
|
+
require_relative 'wouter/util'
|
11
|
+
require_relative 'wouter/route'
|
12
|
+
require_relative 'wouter/endpoint'
|
13
|
+
require_relative 'wouter/wrapper'
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
final_resp
|
26
|
-
else
|
27
|
-
endpoint.res.write final_resp
|
28
|
-
endpoint.res
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Users of `Wouter::Endpoint` should implement their own `#response`
|
33
|
-
def respond
|
34
|
-
@res
|
35
|
-
end
|
36
|
-
|
37
|
-
## ~~~~~ Convience methods
|
38
|
-
|
39
|
-
def params
|
40
|
-
@req.params
|
15
|
+
# Main route definition DSL
|
16
|
+
class Wouter
|
17
|
+
class <<self
|
18
|
+
def get(route, klass)
|
19
|
+
define_route(:GET, route, klass)
|
41
20
|
end
|
42
21
|
|
43
|
-
def
|
44
|
-
|
45
|
-
@res.write body
|
46
|
-
@res
|
22
|
+
def put(route, klass)
|
23
|
+
define_route(:PUT, route, klass)
|
47
24
|
end
|
48
25
|
|
49
|
-
def
|
50
|
-
|
51
|
-
@res
|
26
|
+
def post(route, klass)
|
27
|
+
define_route(:POST, route, klass)
|
52
28
|
end
|
53
|
-
end
|
54
|
-
|
55
|
-
## ========== Internal
|
56
29
|
|
57
|
-
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.routes
|
62
|
-
@routes ||= []
|
63
|
-
end
|
64
|
-
|
65
|
-
## ========== DSL
|
66
|
-
|
67
|
-
class <<self
|
68
|
-
%i[get post put delete].each do |m|
|
69
|
-
define_method(m) do |path, route_class|
|
70
|
-
route = {
|
71
|
-
method: m.to_s.upcase.to_sym,
|
72
|
-
path: path,
|
73
|
-
route_class: route_class
|
74
|
-
}
|
75
|
-
routes.push(route)
|
76
|
-
end
|
30
|
+
def delete(route, klass)
|
31
|
+
define_route(:DELETE, route, klass)
|
77
32
|
end
|
78
33
|
|
79
34
|
def middleware(klass, *args, &block)
|
80
35
|
app.use(klass, *args, &block)
|
81
36
|
end
|
82
|
-
end
|
83
|
-
|
84
|
-
## ========== Build a Rack entry point
|
85
|
-
|
86
|
-
def self.build
|
87
|
-
app.run(Wrapper.new(routes))
|
88
|
-
app.to_app
|
89
|
-
end
|
90
|
-
|
91
|
-
# Wrapper class for creating single `.call` interface for `Rack::Builder`
|
92
|
-
class Wrapper
|
93
|
-
attr_reader :routes
|
94
|
-
|
95
|
-
def initialize(routes)
|
96
|
-
@routes = routes
|
97
|
-
end
|
98
|
-
|
99
|
-
def call(env)
|
100
|
-
request = Rack::Request.new(env)
|
101
|
-
|
102
|
-
route = routes.find do |r|
|
103
|
-
matching_route?(r, request)
|
104
|
-
end
|
105
|
-
|
106
|
-
return not_found unless route
|
107
37
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
resp = route[:route_class].call(request.env)
|
113
|
-
resp.finish
|
38
|
+
def build
|
39
|
+
app.run(Wrapper.new(routes: routes))
|
40
|
+
app.to_app
|
114
41
|
end
|
115
42
|
|
116
43
|
private
|
117
44
|
|
118
|
-
|
119
|
-
|
120
|
-
return false unless route[:method] == request.request_method.to_sym
|
121
|
-
|
122
|
-
if parameterized_path?(route[:path])
|
123
|
-
path_regex = build_path_regex(route[:path].split('/'))
|
124
|
-
path_regex.match?(request.path)
|
125
|
-
else
|
126
|
-
route[:path] == request.path
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
# Yield the parameters parsed from the path
|
131
|
-
# Example definition: "/hello/:name"
|
132
|
-
# Example request: "/hello/robert"
|
133
|
-
# Example yield: name, robert
|
134
|
-
def path_parameters(route, path)
|
135
|
-
return unless parameterized_path?(route[:path])
|
136
|
-
|
137
|
-
split_path = route[:path].split('/')
|
138
|
-
path_parameter_data = build_path_regex(split_path).match(path)
|
139
|
-
path_parameter_names = find_parameter_names_in_path(split_path)
|
140
|
-
path_parameter_names.each_with_index do |parameter_name, i|
|
141
|
-
yield parameter_name, path_parameter_data[i + 1]
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# Does the path have a parameter in it? ex: '/user/:id'
|
146
|
-
def parameterized_path?(path)
|
147
|
-
path.include?(':')
|
148
|
-
end
|
149
|
-
|
150
|
-
# Find all the named parameters in the route
|
151
|
-
# Then drop the ':' so we have the names: ':id' => 'id'
|
152
|
-
def find_parameter_names_in_path(split_path)
|
153
|
-
params = split_path.find_all do |s|
|
154
|
-
if s.size > 1
|
155
|
-
s[0] == ':'
|
156
|
-
else
|
157
|
-
false
|
158
|
-
end
|
159
|
-
end
|
160
|
-
params.map { |s| s[1..-1] }
|
45
|
+
def app
|
46
|
+
@app ||= Rack::Builder.new
|
161
47
|
end
|
162
48
|
|
163
|
-
|
164
|
-
|
165
|
-
regex_string = split_path.map do |s|
|
166
|
-
if s[0] == ':'
|
167
|
-
'(\\w*)'
|
168
|
-
else
|
169
|
-
s
|
170
|
-
end
|
171
|
-
end
|
172
|
-
Regexp.new(regex_string.join('\/'))
|
49
|
+
def routes
|
50
|
+
@routes ||= []
|
173
51
|
end
|
174
52
|
|
175
|
-
|
176
|
-
|
177
|
-
resp = Rack::Response.new
|
178
|
-
resp.status = 404
|
179
|
-
resp.write 'Not Found'
|
180
|
-
resp
|
53
|
+
def define_route(request_method, path, klass)
|
54
|
+
routes << Wouter::Route.new(request_method: request_method, path: path, klass: klass)
|
181
55
|
end
|
182
56
|
end
|
183
57
|
end
|
data/wouter.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'wouter'
|
5
|
+
s.platform = Gem::Platform::RUBY
|
6
|
+
s.version = '0.0.6'
|
7
|
+
s.summary = 'Rack web router.'
|
8
|
+
s.description = 'Wouter is a modular web router built on Rack.'
|
9
|
+
s.authors = ['Robert Peterson']
|
10
|
+
s.email = 'robertpeterson@gmail.com'
|
11
|
+
s.files = Dir['lib/**/*', 'examples/*'] + ['README.md', 'Gemfile', 'Rakefile', 'wouter.gemspec']
|
12
|
+
s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ }
|
13
|
+
s.license = 'MIT'
|
14
|
+
s.homepage = 'https://github.com/rawburt/wouter'
|
15
|
+
|
16
|
+
s.add_dependency 'rack', '~> 2.0', '>= 2.0.6'
|
17
|
+
s.add_development_dependency 'm', '~> 1.5', '>= 1.5.1'
|
18
|
+
s.add_development_dependency 'minitest', '~> 5.11', '>= 5.11.3'
|
19
|
+
s.add_development_dependency 'minitest-rg', '~> 5.2', '>= 5.2.0'
|
20
|
+
s.add_development_dependency 'mocha', '~> 1.7', '>= 1.7.0'
|
21
|
+
s.add_development_dependency 'rack-test', '~> 1.1', '>= 1.1.0'
|
22
|
+
s.add_development_dependency 'rake', '~> 12.3', '>= 12.3.1'
|
23
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wouter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Peterson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -90,6 +90,26 @@ dependencies:
|
|
90
90
|
- - ">="
|
91
91
|
- !ruby/object:Gem::Version
|
92
92
|
version: 5.2.0
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: mocha
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '1.7'
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.7.0
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.7'
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 1.7.0
|
93
113
|
- !ruby/object:Gem::Dependency
|
94
114
|
name: rack-test
|
95
115
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,7 +156,16 @@ executables: []
|
|
136
156
|
extensions: []
|
137
157
|
extra_rdoc_files: []
|
138
158
|
files:
|
159
|
+
- Gemfile
|
160
|
+
- README.md
|
161
|
+
- Rakefile
|
162
|
+
- examples/hello.rb
|
139
163
|
- lib/wouter.rb
|
164
|
+
- lib/wouter/endpoint.rb
|
165
|
+
- lib/wouter/route.rb
|
166
|
+
- lib/wouter/util.rb
|
167
|
+
- lib/wouter/wrapper.rb
|
168
|
+
- wouter.gemspec
|
140
169
|
homepage: https://github.com/rawburt/wouter
|
141
170
|
licenses:
|
142
171
|
- MIT
|