rackr 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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: []