rack-way 0.0.1 → 0.0.3
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/lib/rack-way/action.rb +122 -26
- data/lib/rack-way/router/{request_builder.rb → build_request.rb} +5 -3
- data/lib/rack-way/router/route.rb +16 -21
- data/lib/rack-way/router.rb +93 -33
- data/lib/rack-way.rb +33 -29
- metadata +12 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a49ebbc6df20d07f91d19e38ee194c6a2958d397c357b8ab6b262ba7ed256902
|
4
|
+
data.tar.gz: ef3a7824645ef0bea5b2f06a4ea3ca89c5cd5eb5daeea2e1f0028dcbee60676a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48802c9548cbdeb967d28dc8a0d543841bcbc0fbb4fc6bdbe9976260df76795060abcc8dce9de3a500aea2dbba0bcdbd5ff55c5c30bc9df2afa65ea28e62049c
|
7
|
+
data.tar.gz: 50e8fb5a03bfdefaa88b5d3b0fabb73bb587b4c2e6c47fe01d787bd4e0898e80bb84e078a61938b0dd169e8ec650c4bbfa99c5e416724dc24f174440eb4b8a21
|
data/lib/rack-way/action.rb
CHANGED
@@ -1,60 +1,156 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erubi'
|
2
4
|
require 'json'
|
3
5
|
require 'rack'
|
4
6
|
|
5
7
|
module Rack
|
6
8
|
class Way
|
7
9
|
module Action
|
8
|
-
def
|
9
|
-
|
10
|
+
def self.included(base)
|
11
|
+
base.class_eval do
|
12
|
+
attr_reader :route if self != Rack::Way
|
13
|
+
|
14
|
+
def initialize(route)
|
15
|
+
@route = route
|
16
|
+
end
|
17
|
+
|
18
|
+
def view_response(a_path, a_view_params = {}, status: 200)
|
19
|
+
Rack::Way::Action.view_response(
|
20
|
+
a_path,
|
21
|
+
a_view_params,
|
22
|
+
status: status, route: route
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def view(
|
27
|
+
a_path, a_view_params = {}, status: 200, response_instance: false
|
28
|
+
)
|
29
|
+
Rack::Way::Action.view(
|
30
|
+
a_path,
|
31
|
+
a_view_params,
|
32
|
+
status: status, response_instance: response_instance, route: route
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def html(content, status: 200)
|
39
|
+
Rack::Way::Action.html(content, status: status)
|
40
|
+
end
|
41
|
+
|
42
|
+
def html_response(content, status: 200)
|
43
|
+
Rack::Way::Action.html_response(content, status: status)
|
44
|
+
end
|
45
|
+
|
46
|
+
def json(content = {}, status: 200)
|
47
|
+
Rack::Way::Action.json(content, status: status)
|
48
|
+
end
|
49
|
+
|
50
|
+
def json_response(content = {}, status: 200)
|
51
|
+
Rack::Way::Action.json_response(content, status: status)
|
10
52
|
end
|
11
53
|
|
12
|
-
def
|
13
|
-
Rack::Way::Action.
|
54
|
+
def text(content, status: 200)
|
55
|
+
Rack::Way::Action.text(content, status: status)
|
14
56
|
end
|
15
57
|
|
16
|
-
def
|
17
|
-
Rack::Way::Action.
|
58
|
+
def text_response(content, status: 200)
|
59
|
+
Rack::Way::Action.text_response(content, status: status)
|
18
60
|
end
|
19
61
|
|
20
|
-
def erb(path,
|
21
|
-
Rack::Way::Action.erb(path,
|
62
|
+
def erb(path, view_params = {})
|
63
|
+
Rack::Way::Action.erb(path, view_params)
|
22
64
|
end
|
23
65
|
|
24
66
|
def redirect_to(url)
|
25
67
|
Rack::Way::Action.redirect_to(url)
|
26
68
|
end
|
27
69
|
|
70
|
+
def response(body = nil, status = 200, headers = {})
|
71
|
+
Rack::Response.new(body, status, headers)
|
72
|
+
end
|
73
|
+
|
28
74
|
class << self
|
29
|
-
def
|
30
|
-
[status,
|
75
|
+
def html(content, status: 200)
|
76
|
+
[status, { 'Content-Type' => 'text/html' }, [content]]
|
77
|
+
end
|
78
|
+
|
79
|
+
def html_response(content, status: 200)
|
80
|
+
Rack::Response.new(content, status, { 'Content-Type' => 'text/html' })
|
31
81
|
end
|
32
82
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
83
|
+
def view_response(paths, view_params = {}, status: 200, route: nil)
|
84
|
+
view(
|
85
|
+
paths,
|
86
|
+
view_params,
|
87
|
+
status: status, response_instance: true,
|
88
|
+
route: route
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def view(
|
93
|
+
paths,
|
94
|
+
view_params = {},
|
95
|
+
status: 200,
|
96
|
+
response_instance: false,
|
97
|
+
route: nil
|
98
|
+
)
|
99
|
+
erb = if paths.is_a?(Array)
|
100
|
+
paths.map { |path| erb("views/#{path}", route, view_params) }.join
|
101
|
+
else
|
102
|
+
erb("views/#{paths}", route, view_params)
|
103
|
+
end
|
104
|
+
|
105
|
+
if response_instance
|
106
|
+
return Rack::Response.new(
|
107
|
+
erb,
|
108
|
+
status,
|
109
|
+
{ 'Content-Type' => 'text/html' }
|
110
|
+
)
|
39
111
|
end
|
40
112
|
|
41
|
-
[status, {
|
113
|
+
[status, { 'Content-Type' => 'text/html' }, [erb]]
|
114
|
+
end
|
115
|
+
|
116
|
+
def json(content = {}, status: 200)
|
117
|
+
[status, { 'Content-Type' => 'application/json' }, [content.to_json]]
|
118
|
+
end
|
119
|
+
|
120
|
+
def json_response(content = {}, status: 200)
|
121
|
+
Rack::Response.new(
|
122
|
+
content.to_json,
|
123
|
+
status,
|
124
|
+
{ 'Content-Type' => 'application/json' }
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
def text(content, status: 200)
|
129
|
+
[status, { 'Content-Type' => 'text/plain' }, [content]]
|
42
130
|
end
|
43
131
|
|
44
|
-
def
|
45
|
-
|
132
|
+
def text_response(content, status: 200)
|
133
|
+
Rack::Response.new(
|
134
|
+
content,
|
135
|
+
status,
|
136
|
+
{ 'Content-Type' => 'text/plain' }
|
137
|
+
)
|
46
138
|
end
|
47
139
|
|
48
|
-
def erb(path,
|
49
|
-
|
50
|
-
|
51
|
-
|
140
|
+
def erb(path, _route, view_params = {})
|
141
|
+
@view = OpenStruct.new(view_params)
|
142
|
+
|
143
|
+
eval(Erubi::Engine.new(::File.read("#{path}.html.erb")).src)
|
52
144
|
end
|
53
145
|
|
54
146
|
def redirect_to(url)
|
55
|
-
[302, {'Location' => url}, []]
|
147
|
+
[302, { 'Location' => url }, []]
|
148
|
+
end
|
149
|
+
|
150
|
+
def response(body = nil, status = 200, headers = {})
|
151
|
+
Rack::Response.new(body, status, headers)
|
56
152
|
end
|
57
153
|
end
|
58
154
|
end
|
59
155
|
end
|
60
|
-
end
|
156
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class Way
|
3
5
|
class Router
|
4
|
-
class
|
6
|
+
class BuildRequest
|
5
7
|
def initialize(env)
|
6
8
|
@env = env
|
7
9
|
end
|
@@ -10,7 +12,7 @@ module Rack
|
|
10
12
|
request = Rack::Request.new(@env)
|
11
13
|
|
12
14
|
return request if route.nil?
|
13
|
-
return request unless route.has_params
|
15
|
+
return request unless route.has_params
|
14
16
|
|
15
17
|
update_request_params(request, route)
|
16
18
|
end
|
@@ -45,4 +47,4 @@ module Rack
|
|
45
47
|
end
|
46
48
|
end
|
47
49
|
end
|
48
|
-
end
|
50
|
+
end
|
@@ -1,28 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class Way
|
3
5
|
class Router
|
4
6
|
class Route
|
5
|
-
attr_reader :endpoint, :splitted_path
|
7
|
+
attr_reader :endpoint, :splitted_path, :has_params
|
6
8
|
|
7
9
|
def initialize(path, endpoint)
|
8
10
|
@path = path
|
9
11
|
@splitted_path = @path.split('/')
|
10
12
|
@endpoint = endpoint
|
11
13
|
@params = fetch_params
|
14
|
+
@has_params = @params != []
|
12
15
|
end
|
13
16
|
|
14
17
|
def match?(env)
|
15
|
-
if has_params
|
16
|
-
return match_with_params?(env)
|
17
|
-
end
|
18
|
+
return match_with_params?(env) if @has_params
|
18
19
|
|
19
20
|
env['REQUEST_PATH'] == @path
|
20
21
|
end
|
21
22
|
|
22
|
-
def has_params?
|
23
|
-
@params != []
|
24
|
-
end
|
25
|
-
|
26
23
|
private
|
27
24
|
|
28
25
|
def fetch_params
|
@@ -32,24 +29,22 @@ module Rack
|
|
32
29
|
def match_with_params?(env)
|
33
30
|
splitted_request_path = env['REQUEST_PATH'].split('/')
|
34
31
|
|
35
|
-
if @splitted_path.size != splitted_request_path.size
|
36
|
-
return false
|
37
|
-
end
|
32
|
+
return false if @splitted_path.size != splitted_request_path.size
|
38
33
|
|
39
|
-
|
34
|
+
matched_path_pieces =
|
40
35
|
@splitted_path
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
36
|
+
.map
|
37
|
+
.with_index do |segment, i|
|
38
|
+
if segment.start_with?(':')
|
39
|
+
true
|
40
|
+
else
|
41
|
+
splitted_request_path[i] == segment
|
48
42
|
end
|
43
|
+
end
|
49
44
|
|
50
|
-
!
|
45
|
+
!matched_path_pieces.include?(false)
|
51
46
|
end
|
52
47
|
end
|
53
48
|
end
|
54
49
|
end
|
55
|
-
end
|
50
|
+
end
|
data/lib/rack-way/router.rb
CHANGED
@@ -1,64 +1,99 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'router/route'
|
4
|
+
require_relative 'router/build_request'
|
3
5
|
|
4
6
|
module Rack
|
5
7
|
class Way
|
6
8
|
class Router
|
9
|
+
class UndefinedNamedRoute < StandardError; end
|
10
|
+
|
7
11
|
attr_writer :not_found
|
12
|
+
attr_reader :route
|
8
13
|
|
9
14
|
def initialize
|
10
|
-
@routes =
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@namespaces = []
|
20
|
-
|
15
|
+
@routes = {}
|
16
|
+
%w[GET POST DELETE PUT TRACE OPTIONS PATCH].each do |method|
|
17
|
+
@routes[method] = { __instances: [] }
|
18
|
+
end
|
19
|
+
@route = Hash.new do |_hash, key|
|
20
|
+
raise(UndefinedNamedRoute, "Undefined named route: '#{key}'")
|
21
|
+
end
|
22
|
+
@scopes = []
|
23
|
+
@error = proc { |_req, e| raise e }
|
21
24
|
@not_found = proc { [404, {}, ['Not found']] }
|
22
25
|
end
|
23
26
|
|
24
27
|
def call(env)
|
25
|
-
|
26
|
-
|
28
|
+
request_builder = BuildRequest.new(env)
|
29
|
+
env['REQUEST_METHOD'] = 'GET' if env['REQUEST_METHOD'] == 'HEAD'
|
30
|
+
|
31
|
+
route_instance = match_route(env)
|
27
32
|
|
28
|
-
return render_not_found(request_builder.call) if
|
33
|
+
return render_not_found(request_builder.call) if route_instance.nil?
|
29
34
|
|
30
|
-
if
|
31
|
-
return
|
35
|
+
if route_instance.endpoint.respond_to?(:call)
|
36
|
+
return route_instance.endpoint.call(request_builder.call(route_instance))
|
32
37
|
end
|
33
38
|
|
34
|
-
|
39
|
+
if route_instance.endpoint.include?(Rack::Way::Action)
|
40
|
+
return route_instance.endpoint.new(@route).call(request_builder.call(route_instance))
|
41
|
+
end
|
42
|
+
|
43
|
+
route_instance.endpoint.new.call(request_builder.call(route_instance))
|
44
|
+
rescue Exception => e
|
45
|
+
@error.call(request_builder.call, e)
|
35
46
|
end
|
36
47
|
|
37
|
-
def add(method, path, endpoint)
|
38
|
-
|
39
|
-
|
48
|
+
def add(method, path, endpoint, as = nil)
|
49
|
+
method = :get if method == :head
|
50
|
+
|
51
|
+
path_with_scopes = "/#{@scopes.join('/')}#{put_path_slash(path)}"
|
52
|
+
@route[as] = path_with_scopes if as
|
53
|
+
|
54
|
+
route_instance = Route.new(path_with_scopes, endpoint)
|
40
55
|
|
41
|
-
|
56
|
+
return push_to_scope(method.to_s.upcase, route_instance) if @scopes.size >= 1
|
57
|
+
|
58
|
+
@routes[method.to_s.upcase][:__instances].push(route_instance)
|
42
59
|
end
|
43
60
|
|
44
61
|
def add_not_found(endpoint)
|
45
62
|
@not_found = endpoint
|
46
63
|
end
|
47
64
|
|
48
|
-
def
|
49
|
-
@
|
65
|
+
def add_error(endpoint)
|
66
|
+
@error = endpoint
|
67
|
+
end
|
68
|
+
|
69
|
+
def append_scope(name)
|
70
|
+
@scopes.push(name)
|
50
71
|
end
|
51
72
|
|
52
|
-
def
|
53
|
-
@
|
54
|
-
@namespaces.first(@namespaces.size - 1)
|
73
|
+
def clear_last_scope
|
74
|
+
@scopes = @scopes.first(@scopes.size - 1)
|
55
75
|
end
|
56
76
|
|
57
77
|
private
|
58
78
|
|
79
|
+
def push_to_scope(method, route_instance)
|
80
|
+
scopes_with_slash = @scopes + [:__instances]
|
81
|
+
push_it(@routes[method], *scopes_with_slash, route_instance)
|
82
|
+
end
|
83
|
+
|
84
|
+
def push_it(h, first_key, *rest_keys, val)
|
85
|
+
if rest_keys.empty?
|
86
|
+
(h[first_key] ||= []) << val
|
87
|
+
else
|
88
|
+
h[first_key] = push_it(h[first_key] ||= {}, *rest_keys, val)
|
89
|
+
end
|
90
|
+
h
|
91
|
+
end
|
92
|
+
|
59
93
|
def put_path_slash(path)
|
60
|
-
return '' if
|
61
|
-
return
|
94
|
+
return '' if ['/', ''].include?(path) && @scopes != []
|
95
|
+
return "/#{path}" if @scopes != []
|
96
|
+
|
62
97
|
path
|
63
98
|
end
|
64
99
|
|
@@ -68,9 +103,34 @@ module Rack
|
|
68
103
|
@not_found.new.call(env)
|
69
104
|
end
|
70
105
|
|
71
|
-
def match_route(env)
|
72
|
-
|
73
|
-
|
106
|
+
def match_route(env, last_tail = nil, found_scopes = [])
|
107
|
+
routes =
|
108
|
+
if last_tail.nil?
|
109
|
+
last_tail = env['REQUEST_PATH'].split('/').drop(1)
|
110
|
+
|
111
|
+
@routes[env['REQUEST_METHOD']]
|
112
|
+
else
|
113
|
+
@routes[env['REQUEST_METHOD']].dig(*found_scopes)
|
114
|
+
end
|
115
|
+
|
116
|
+
segment, *tail = last_tail
|
117
|
+
|
118
|
+
routes.each do |scope, _v|
|
119
|
+
next if scope == :__instances
|
120
|
+
|
121
|
+
if segment == scope || scope.start_with?(':')
|
122
|
+
found_scopes.push(scope)
|
123
|
+
break
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
if tail.empty? || found_scopes == []
|
128
|
+
return @routes[env['REQUEST_METHOD']].dig(*(found_scopes << :__instances)).detect do |route_instance|
|
129
|
+
route_instance.match?(env)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
match_route(env, tail, found_scopes)
|
74
134
|
end
|
75
135
|
end
|
76
136
|
end
|
data/lib/rack-way.rb
CHANGED
@@ -1,53 +1,57 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'rack-way/router'
|
4
|
+
require_relative 'rack-way/action'
|
3
5
|
|
4
6
|
module Rack
|
5
7
|
class Way
|
6
8
|
include Action
|
7
9
|
|
8
|
-
def initialize
|
9
|
-
@router =
|
10
|
+
def initialize(router: Router.new)
|
11
|
+
@router = router
|
10
12
|
end
|
11
13
|
|
12
|
-
def
|
14
|
+
def http_router(&block)
|
13
15
|
instance_eval(&block)
|
14
16
|
|
15
17
|
@router
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
@router.
|
20
|
-
instance_eval(&block)
|
21
|
-
|
22
|
-
@router.clear_last_namespace
|
23
|
-
end
|
24
|
-
|
25
|
-
def root(endpoint)
|
26
|
-
@router.add('GET', '', endpoint)
|
20
|
+
def route
|
21
|
+
@router.route
|
27
22
|
end
|
28
23
|
|
29
|
-
def
|
30
|
-
@router.
|
31
|
-
|
32
|
-
|
33
|
-
def get(path, endpoint)
|
34
|
-
@router.add('GET', path, endpoint)
|
35
|
-
end
|
24
|
+
def scope(name, &block)
|
25
|
+
@router.append_scope(name)
|
26
|
+
instance_eval(&block)
|
36
27
|
|
37
|
-
|
38
|
-
@router.add('POST', path, endpoint)
|
28
|
+
@router.clear_last_scope
|
39
29
|
end
|
40
30
|
|
41
|
-
def
|
42
|
-
|
31
|
+
def not_found(endpoint = -> {}, &block)
|
32
|
+
if block_given?
|
33
|
+
@router.add_not_found(block)
|
34
|
+
else
|
35
|
+
@router.add_not_found(endpoint)
|
36
|
+
end
|
43
37
|
end
|
44
38
|
|
45
|
-
def
|
46
|
-
|
39
|
+
def error(endpoint = -> {}, &block)
|
40
|
+
if block_given?
|
41
|
+
@router.add_error(block)
|
42
|
+
else
|
43
|
+
@router.add_error(endpoint)
|
44
|
+
end
|
47
45
|
end
|
48
46
|
|
49
|
-
|
50
|
-
|
47
|
+
%w[GET POST DELETE PUT TRACE OPTIONS PATCH].each do |http_method|
|
48
|
+
define_method(http_method.downcase.to_sym) do |path = '', endpoint = -> {}, as: nil, &block|
|
49
|
+
if block.respond_to?(:call)
|
50
|
+
@router.add(http_method, path, block, as)
|
51
|
+
else
|
52
|
+
@router.add(http_method, path, endpoint, as)
|
53
|
+
end
|
54
|
+
end
|
51
55
|
end
|
52
56
|
end
|
53
57
|
end
|
metadata
CHANGED
@@ -1,45 +1,44 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-way
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Henrique
|
7
|
+
- Henrique F. Teixeira
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-07-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: erubi
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '1.12'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '1.12'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rack
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '3.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
-
description: A
|
42
|
-
Rack' applications when working in projects that need high performance.
|
40
|
+
version: '3.0'
|
41
|
+
description: A router and helper functions to build pure Rack projects.
|
43
42
|
email: hriqueft@gmail.com
|
44
43
|
executables: []
|
45
44
|
extensions: []
|
@@ -48,7 +47,7 @@ files:
|
|
48
47
|
- lib/rack-way.rb
|
49
48
|
- lib/rack-way/action.rb
|
50
49
|
- lib/rack-way/router.rb
|
51
|
-
- lib/rack-way/router/
|
50
|
+
- lib/rack-way/router/build_request.rb
|
52
51
|
- lib/rack-way/router/route.rb
|
53
52
|
homepage: https://github.com/henriquefernandez/rack-way
|
54
53
|
licenses:
|
@@ -69,8 +68,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
68
|
- !ruby/object:Gem::Version
|
70
69
|
version: '0'
|
71
70
|
requirements: []
|
72
|
-
rubygems_version: 3.
|
71
|
+
rubygems_version: 3.4.3
|
73
72
|
signing_key:
|
74
73
|
specification_version: 4
|
75
|
-
summary: '"rack-way" come with a router and helper functions to build pure
|
74
|
+
summary: '"rack-way" come with a router and helper functions to build pure Rack projects.'
|
76
75
|
test_files: []
|