wouter 0.0.5 → 0.0.6
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 +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
|