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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4731ee9735261f806ded54813c9655fcae91349f6c3b9a5061f0664ac2618b72
4
- data.tar.gz: 0bb29c4009bcc7846e07dfad0dae5c8c0ba4af3ceaa5bf1607ca4f89590e3e59
3
+ metadata.gz: 328983ffb5c818d8e95bfd645fa6a4390bc97efee9d3b195d28cd86421b69315
4
+ data.tar.gz: '01191a528dab4215b98d03545b863c4cd8c3f0b5be7c0f9f91e97a0bbe300e72'
5
5
  SHA512:
6
- metadata.gz: 81a9adf9928ce8af300ede523ed9fad8ac9c657ccee665fae108b4f13d54c3fa77a11c61cefdd3d4bceae992d4f68e8cf2e65e6f103dafd0b2287ca2e310fb1a
7
- data.tar.gz: ad519803a201b62cbb9e9b1d93365329a74d0517859dd60fff1ca8b10088d86788baad5d9d5903aa04df8dbdd423bcf26f88433e3efd1f9e334ece9037ee59c0
6
+ metadata.gz: 5e1b52bc209def3b17235c036102d145c1e43ed31deb318288437956307489636573c037342dd064d541c8f59d6343b9d42f363c4e3638e4d36d83e9d2c2c9ea
7
+ data.tar.gz: a41ead4e722ebccfd50af05e13571cd880f3f580eec097d89cc992f09376bc6352ab96c1560d85589cb9cd20d1c08d4352c12d98f48146199d65ad14d8a11ae4
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler'
4
+ Bundler.setup(:default, :test)
5
+
6
+ desc 'Run tests'
7
+ task :test do
8
+ require_relative './test/helper'
9
+
10
+ Dir['test/**/*_test.rb'].each do |file|
11
+ load file
12
+ end
13
+ end
data/examples/hello.rb ADDED
@@ -0,0 +1,15 @@
1
+ # Usage: rackup hello.rb
2
+
3
+ require 'wouter'
4
+
5
+ class HelloWorld < Wouty::Endpoint
6
+ def respond
7
+ 'Hello, world!'
8
+ end
9
+ end
10
+
11
+ class Routes < Wouty
12
+ get '/', HelloWorld
13
+ end
14
+
15
+ run Routes.build
@@ -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
@@ -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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # `Wouter::Util` provides some common untility methods.
4
+ #
5
+ class Wouter::Util
6
+ # HTTP 404 Not Found
7
+ def self.not_found
8
+ resp = Rack::Response.new
9
+ resp.status = 404
10
+ resp.write 'Not Found'
11
+ resp
12
+ end
13
+ end
@@ -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 main class to define routes from
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
- # @req : Request
14
- # @res : Response
15
- def initialize(req, res)
16
- @req = req
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
- def self.call(env)
21
- endpoint = new(Rack::Request.new(env), Rack::Response.new)
22
- final_resp = endpoint.respond
23
- # Return a `Rack::Response` or write to the body of the response
24
- if final_resp.is_a?(Rack::Response)
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 json(body)
44
- @res.set_header('Content-Type', 'application/json')
45
- @res.write body
46
- @res
22
+ def put(route, klass)
23
+ define_route(:PUT, route, klass)
47
24
  end
48
25
 
49
- def status(code)
50
- @res.status = code
51
- @res
26
+ def post(route, klass)
27
+ define_route(:POST, route, klass)
52
28
  end
53
- end
54
-
55
- ## ========== Internal
56
29
 
57
- def self.app
58
- @app ||= Rack::Builder.new
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
- path_parameters(route, request.path) do |name, value|
109
- request.update_param(name, value)
110
- end
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
- # Does the given route match the request
119
- def matching_route?(route, request)
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
- # Turn the route into a regex: '/hello/:name' => '\/hello\/(\w*)'
164
- def build_path_regex(split_path)
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
- # Return HTTP 404
176
- def not_found
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.5
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-16 00:00:00.000000000 Z
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