rackr 0.0.4

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
+ SHA256:
3
+ metadata.gz: cdefbf566acec64232a4dc955298b3790f174884219515361cc41392191182ba
4
+ data.tar.gz: 06552a5aa5dae83ce342c31df357a1e805150ecd31cbb356f76281cecec73617
5
+ SHA512:
6
+ metadata.gz: 926c42e1321efc1bb16fb1b8a7b40ddb81e59c57f6f79174ed619ed8aed6ca788523cc3ad6f7224ca2c78c99b1e174cd4617929a8836fa1678bde0504445ea0c
7
+ data.tar.gz: 8d10b27258450d640dfd413554dd4dd4cc0e3cc153992929f89c96265df2005b6d7d46ce4a86549e46814499de740011d3d74442ce78fbd9e932ade9361f4e49
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erubi'
4
+ require 'oj'
5
+ require 'rack'
6
+
7
+ class Rackr
8
+ module Action
9
+ def self.included(base)
10
+ base.class_eval do
11
+ attr_reader :route, :config, :db if self != Rackr
12
+
13
+ def initialize(route: nil, config: nil)
14
+ @route = route
15
+ @config = config
16
+ @db = config[:db]
17
+ end
18
+
19
+ def view_response(a_path, a_view_params = {}, status: 200)
20
+ Rackr::Action.view_response(
21
+ a_path,
22
+ a_view_params,
23
+ status: status,
24
+ config: config,
25
+ route: route,
26
+ db: db
27
+ )
28
+ end
29
+
30
+ def view(
31
+ a_path, a_view_params = {}, status: 200, response_instance: false
32
+ )
33
+ Rackr::Action.view(
34
+ a_path,
35
+ a_view_params,
36
+ status: status,
37
+ config: config,
38
+ route: route,
39
+ db: db,
40
+ response_instance: response_instance
41
+ )
42
+ end
43
+ end
44
+ end
45
+
46
+ def layout(layout_path, file_path)
47
+ Rackr::Action.layout(layout_path, file_path)
48
+ end
49
+
50
+ def html(content, status: 200)
51
+ Rackr::Action.html(content, status: status)
52
+ end
53
+
54
+ def html_response(content, status: 200)
55
+ Rackr::Action.html_response(content, status: status)
56
+ end
57
+
58
+ def json(content = {}, status: 200)
59
+ Rackr::Action.json(content, status: status)
60
+ end
61
+
62
+ def json_response(content = {}, status: 200)
63
+ Rackr::Action.json_response(content, status: status)
64
+ end
65
+
66
+ def text(content, status: 200)
67
+ Rackr::Action.text(content, status: status)
68
+ end
69
+
70
+ def text_response(content, status: 200)
71
+ Rackr::Action.text_response(content, status: status)
72
+ end
73
+
74
+ def erb(content, view_params = {})
75
+ Rackr::Action.erb(content, view_params)
76
+ end
77
+
78
+ def redirect_response(url)
79
+ Rackr::Action.redirect_response(url)
80
+ end
81
+
82
+ def redirect_to(url)
83
+ Rackr::Action.redirect_to(url)
84
+ end
85
+
86
+ def response(body = nil, status = 200, headers = {})
87
+ Rack::Response.new(body, status, headers)
88
+ end
89
+
90
+ class << self
91
+ def view_response(
92
+ paths,
93
+ view_params = {},
94
+ status: 200,
95
+ config: {},
96
+ route: nil,
97
+ db: nil
98
+ )
99
+ view(
100
+ paths,
101
+ view_params,
102
+ status: status,
103
+ config: config,
104
+ route: route,
105
+ db: db,
106
+ response_instance: true
107
+ )
108
+ end
109
+
110
+ def view(
111
+ paths,
112
+ view_params = {},
113
+ status: 200,
114
+ config: {},
115
+ route: nil,
116
+ db: nil,
117
+ response_instance: false
118
+ )
119
+ base_path = config.dig(:views, :path) || "views"
120
+
121
+ file_or_nil = lambda do |path|
122
+ ::File.read(path)
123
+ rescue Errno::ENOENT
124
+ nil
125
+ end
126
+
127
+ file_content = if paths.is_a?(Array)
128
+ paths.map { |path| ::File.read("#{base_path}/#{path}.html.erb") }.join
129
+ else
130
+ ::File.read("#{base_path}/#{paths}.html.erb")
131
+ end
132
+
133
+ erb = erb(
134
+ [
135
+ file_or_nil.call("#{base_path}/layout/_header.html.erb"),
136
+ file_content,
137
+ file_or_nil.call("#{base_path}/layout/_footer.html.erb")
138
+ ].join,
139
+ view_params,
140
+ config: config,
141
+ route: route,
142
+ db: db
143
+ )
144
+
145
+ if response_instance
146
+ return Rack::Response.new(
147
+ erb,
148
+ status,
149
+ { 'Content-Type' => 'text/html' }
150
+ )
151
+ end
152
+
153
+ [status, { 'Content-Type' => 'text/html' }, [erb]]
154
+ end
155
+
156
+ def layout(layout_path, file_path)
157
+ [
158
+ "layout/#{layout_path}/_header",
159
+ file_path,
160
+ "layout/#{layout_path}/_footer"
161
+ ]
162
+ end
163
+
164
+ def html(content, status: 200)
165
+ [status, { 'Content-Type' => 'text/html' }, [content]]
166
+ end
167
+
168
+ def html_response(content, status: 200)
169
+ Rack::Response.new(content, status, { 'Content-Type' => 'text/html' })
170
+ end
171
+
172
+ def json(content = {}, status: 200)
173
+ [status, { 'Content-Type' => 'application/json' }, [Oj.dump(content, mode: :compat)]]
174
+ end
175
+
176
+ def json_response(content = {}, status: 200)
177
+ Rack::Response.new(
178
+ Oj.dump(content, mode: :compat),
179
+ status,
180
+ { 'Content-Type' => 'application/json' }
181
+ )
182
+ end
183
+
184
+ def text(content, status: 200)
185
+ [status, { 'Content-Type' => 'text/plain' }, [content]]
186
+ end
187
+
188
+ def text_response(content, status: 200)
189
+ Rack::Response.new(
190
+ content,
191
+ status,
192
+ { 'Content-Type' => 'text/plain' }
193
+ )
194
+ end
195
+
196
+ # rubocop:disable Lint/UnusedMethodArgument
197
+ def erb(content, view_params = {}, config: nil, route: nil, db: nil)
198
+ @view = OpenStruct.new(view_params)
199
+
200
+ eval(Erubi::Engine.new(content).src)
201
+ end
202
+ # rubocop:enable Lint/UnusedMethodArgument
203
+
204
+ def redirect_response(url)
205
+ Rack::Response.new(
206
+ nil,
207
+ 302,
208
+ { 'Location' => url }
209
+ )
210
+ end
211
+
212
+ def redirect_to(url)
213
+ [302, { 'Location' => url }, []]
214
+ end
215
+
216
+ def response(body = nil, status = 200, headers = {})
217
+ Rack::Response.new(body, status, headers)
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,21 @@
1
+ class Rackr
2
+ module Callback
3
+ def self.included(base)
4
+ base.class_eval do
5
+ include Rackr::Action
6
+ end
7
+ end
8
+
9
+ def assign(obj, hash)
10
+ Rackr::Callback.assign(obj, hash)
11
+ end
12
+
13
+ def self.assign(obj, hash)
14
+ hash.each do |k, v|
15
+ obj.define_singleton_method(k) { v }
16
+ end
17
+
18
+ obj
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rackr
4
+ class Router
5
+ class BuildRequest
6
+ def initialize(env)
7
+ @env = env
8
+ end
9
+
10
+ def call(route = nil)
11
+ request = Rack::Request.new(@env)
12
+
13
+ return request if route.nil?
14
+ return request unless route.has_params
15
+
16
+ update_request_params(request, route)
17
+ end
18
+
19
+ private
20
+
21
+ def update_request_params(request, route)
22
+ splitted_request_path = request.path.split('/')
23
+
24
+ i = 0
25
+
26
+ while i < route.splitted_path.size
27
+ route_word = route.splitted_path[i]
28
+ if route_word.start_with?(':')
29
+ param = splitted_request_path[i]
30
+ param = param.to_i if is_a_integer_string?(param)
31
+
32
+ update_request_param(request, route_word, param)
33
+ end
34
+
35
+ i += 1
36
+ end
37
+
38
+ request
39
+ end
40
+
41
+ def is_a_integer_string?(string)
42
+ string =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
43
+ end
44
+
45
+ def update_request_param(request, word, param)
46
+ request.update_param(word.sub(':', '').to_sym, param)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rackr
4
+ class Router
5
+ class Route
6
+ attr_reader :endpoint, :splitted_path, :has_params, :has_befores, :befores
7
+
8
+ def initialize(path, endpoint, befores = [])
9
+ @path = path
10
+ @splitted_path = @path.split('/')
11
+ @endpoint = endpoint
12
+ @params = fetch_params
13
+ @has_params = @params != []
14
+ @befores = befores
15
+ @has_befores = befores != []
16
+ end
17
+
18
+ def match?(env)
19
+ return match_with_params?(env) if @has_params
20
+
21
+ env['REQUEST_PATH'] == @path
22
+ end
23
+
24
+ private
25
+
26
+ def fetch_params
27
+ @splitted_path.select { |value| value.start_with? ':' }
28
+ end
29
+
30
+ def match_with_params?(env)
31
+ splitted_request_path = env['REQUEST_PATH'].split('/')
32
+
33
+ return false if @splitted_path.size != splitted_request_path.size
34
+
35
+ matched_path_pieces =
36
+ @splitted_path
37
+ .map
38
+ .with_index do |segment, i|
39
+ if segment.start_with?(':')
40
+ true
41
+ else
42
+ splitted_request_path[i] == segment
43
+ end
44
+ end
45
+
46
+ !matched_path_pieces.include?(false)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,192 @@
1
+ require_relative 'router/errors'
2
+ require_relative 'router/route'
3
+ require_relative 'router/build_request'
4
+
5
+ class Rackr
6
+ class Router
7
+ attr_writer :not_found
8
+ attr_reader :route, :config
9
+
10
+ def initialize(config = {})
11
+ @routes = {}
12
+ %w[GET POST DELETE PUT TRACE OPTIONS PATCH].each do |method|
13
+ @routes[method] = { __instances: [] }
14
+ end
15
+ @route =
16
+ Hash.new do |_hash, key|
17
+ raise(Errors::UndefinedNamedRouteError, "Undefined named route: '#{key}'")
18
+ end
19
+ @config = config
20
+ @branches = []
21
+ @befores = []
22
+ @branches_befores = {}
23
+ @nameds_as = []
24
+ @branches_named_as = {}
25
+ @error = proc { |_req, e| raise e }
26
+ @not_found = proc { [404, {}, ['Not found']] }
27
+ end
28
+
29
+ def call(env)
30
+ request_builder = BuildRequest.new(env)
31
+ env['REQUEST_METHOD'] = 'GET' if env['REQUEST_METHOD'] == 'HEAD'
32
+
33
+ route_instance = match_route(env)
34
+
35
+ return render_not_found(request_builder.call) if route_instance.nil?
36
+
37
+ rack_request = request_builder.call(route_instance)
38
+
39
+ befores = route_instance.befores
40
+ before_result = nil
41
+ i = 0
42
+ while i < befores.size
43
+ before_result = call_endpoint(befores[i], rack_request)
44
+ return before_result unless before_result.is_a?(Rack::Request)
45
+
46
+ i += 1
47
+ end
48
+
49
+ call_endpoint(route_instance.endpoint, before_result || rack_request)
50
+ rescue Exception => e
51
+ @error.call(request_builder.call, e)
52
+ end
53
+
54
+ def add(method, path, endpoint, as = nil, route_befores = [])
55
+ Errors.check_path(path)
56
+ Errors.check_endpoint(endpoint, path)
57
+ Errors.check_as(as, path)
58
+ Errors.check_callbacks(route_befores, path)
59
+
60
+ method = :get if method == :head
61
+
62
+ path_with_branches = "/#{@branches.join('/')}#{put_path_slash(path)}"
63
+ add_named_route(path_with_branches, as)
64
+
65
+ route_instance =
66
+ Route.new(path_with_branches, endpoint, @befores + ensure_array(route_befores))
67
+
68
+ if @branches.size >= 1
69
+ return push_to_branch(method.to_s.upcase, route_instance)
70
+ end
71
+
72
+ @routes[method.to_s.upcase][:__instances].push(route_instance)
73
+ end
74
+
75
+ def add_not_found(endpoint)
76
+ Errors.check_endpoint(endpoint, 'not_found')
77
+
78
+ @not_found = endpoint
79
+ end
80
+
81
+ def add_error(endpoint)
82
+ Errors.check_endpoint(endpoint, 'error')
83
+
84
+ @error = endpoint
85
+ end
86
+
87
+ def append_branch(name, branch_befores = [], as = nil)
88
+ Errors.check_branch_name(name)
89
+ Errors.check_as(as, @branches.join('/'))
90
+ Errors.check_callbacks(branch_befores, name)
91
+
92
+ @branches.push(name)
93
+
94
+ branch_befores = ensure_array(branch_befores)
95
+ @befores.concat(branch_befores)
96
+ @branches_befores[name] = branch_befores
97
+
98
+ @nameds_as.push(as)
99
+ @branches_named_as[name] = as
100
+ end
101
+
102
+ def clear_last_branch
103
+ @befores -= @branches_befores[@branches.last]
104
+ @nameds_as -= [@branches_named_as[@branches.last]]
105
+ @branches = @branches.first(@branches.size - 1)
106
+ end
107
+
108
+ private
109
+
110
+ def call_endpoint(endpoint, rack_request)
111
+ return endpoint.call(rack_request) if endpoint.respond_to?(:call)
112
+
113
+ if endpoint.include?(Rackr::Action)
114
+ return endpoint.new(route: @route, config: @config).call(rack_request)
115
+ end
116
+
117
+ endpoint.new.call(rack_request)
118
+ end
119
+
120
+ def ensure_array(list)
121
+ return [] if list.nil?
122
+ return list if list.is_a?(Array)
123
+
124
+ [list]
125
+ end
126
+
127
+ def add_named_route(path_with_branches, as)
128
+ nameds_as = [@nameds_as.last].push(as).compact
129
+ return if nameds_as.empty?
130
+
131
+ @route[nameds_as.join('_').to_sym] = path_with_branches
132
+ end
133
+
134
+ def push_to_branch(method, route_instance)
135
+ branches_with_slash = @branches + %i[__instances]
136
+ push_it(@routes[method], *branches_with_slash, route_instance)
137
+ end
138
+
139
+ def push_it(hash, first_key, *rest_keys, val)
140
+ if rest_keys.empty?
141
+ (hash[first_key] ||= []) << val
142
+ else
143
+ hash[first_key] = push_it(hash[first_key] ||= {}, *rest_keys, val)
144
+ end
145
+ hash
146
+ end
147
+
148
+ def put_path_slash(path)
149
+ return '' if ['/', ''].include?(path) && @branches != []
150
+ return "/#{path}" if @branches != []
151
+
152
+ path
153
+ end
154
+
155
+ def render_not_found(env)
156
+ return @not_found.call(env) if @not_found.respond_to?(:call)
157
+
158
+ @not_found.new.call(env)
159
+ end
160
+
161
+ def match_route(env, last_tail = nil, found_branches = [])
162
+ routes =
163
+ if last_tail.nil?
164
+ last_tail = env['REQUEST_PATH'].split('/').drop(1)
165
+
166
+ @routes[env['REQUEST_METHOD']]
167
+ else
168
+ @routes[env['REQUEST_METHOD']].dig(*found_branches)
169
+ end
170
+
171
+ segment, *tail = last_tail
172
+
173
+ routes.each do |branch, _v|
174
+ next if branch == :__instances
175
+
176
+ if segment == branch || branch.start_with?(':')
177
+ found_branches.push(branch)
178
+ break
179
+ end
180
+ end
181
+
182
+ if tail.empty? || found_branches == []
183
+ return @routes[env['REQUEST_METHOD']].dig(
184
+ *(found_branches << :__instances)
185
+ )
186
+ &.detect { |route_instance| route_instance.match?(env) }
187
+ end
188
+
189
+ match_route(env, tail, found_branches)
190
+ end
191
+ end
192
+ end
data/lib/rackr.rb ADDED
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rackr/router'
4
+ require_relative 'rackr/action'
5
+ require_relative 'rackr/callback'
6
+
7
+ class Rackr
8
+ include Action
9
+
10
+ def initialize(config = {})
11
+ @router = Router.new(config)
12
+ end
13
+
14
+ def call(&block)
15
+ instance_eval(&block)
16
+
17
+ @router
18
+ end
19
+
20
+ def route
21
+ @router.route
22
+ end
23
+
24
+ def config
25
+ @router.config
26
+ end
27
+
28
+ def db
29
+ @router.config[:db]
30
+ end
31
+
32
+ def r(name, before: [], as: nil, &block)
33
+ @router.append_branch(name, before, as)
34
+ instance_eval(&block)
35
+
36
+ @router.clear_last_branch
37
+ end
38
+
39
+ def not_found(endpoint = -> {}, &block)
40
+ if block_given?
41
+ @router.add_not_found(block)
42
+ else
43
+ @router.add_not_found(endpoint)
44
+ end
45
+ end
46
+
47
+ def error(endpoint = -> {}, &block)
48
+ if block_given?
49
+ @router.add_error(block)
50
+ else
51
+ @router.add_error(endpoint)
52
+ end
53
+ end
54
+
55
+ %w[GET POST DELETE PUT TRACE OPTIONS PATCH].each do |http_method|
56
+ define_method(http_method.downcase.to_sym) do |path = '', endpoint = -> {}, as: nil, before: nil, &block|
57
+ if block.respond_to?(:call)
58
+ @router.add(http_method, path, block, as, before)
59
+ else
60
+ @router.add(http_method, path, endpoint, as, before)
61
+ end
62
+ end
63
+ end
64
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rackr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Henrique F. Teixeira
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-08-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: erubi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.15'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: '4.0'
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '2.0'
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: '4.0'
61
+ description: A complete http router solution that fit well with pure rack apps.
62
+ email: hriqueft@gmail.com
63
+ executables: []
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - lib/rackr.rb
68
+ - lib/rackr/action.rb
69
+ - lib/rackr/callback.rb
70
+ - lib/rackr/router.rb
71
+ - lib/rackr/router/build_request.rb
72
+ - lib/rackr/router/route.rb
73
+ homepage: https://github.com/henrique-ft/rackr
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubygems_version: 3.4.3
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: A complete http router solution that fit well with pure rack apps.
96
+ test_files: []