lennarb 0.4.4 → 0.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00b277c3ca70e4b15e74cb88d343915623d6ae869d4c98b15f46cf6cd286821d
4
- data.tar.gz: c539aa17c39cd2d861e2bfc16b8ae1b76c1d4e37304f95e918ded2235e8c7a82
3
+ metadata.gz: ff30294350acea0efb5cf2c074d256472f99300d46fee6fe0ea9cadfb375e151
4
+ data.tar.gz: d96ea3d9ade277730f83fa0b011d3396d73be3ba8692229c5ad98cfd72f48ea4
5
5
  SHA512:
6
- metadata.gz: ab79db446aab1914aa18146f5b0f8814c68296f04fc7b300af3ac9e0079b11f002b284402335a03acba3a2d63d039c54ce20396e84b1c60b3cc9dfadbeb5f860
7
- data.tar.gz: d16922a8773b300d364c2db69b3925555dfa3d29f91a4018c2d887360dd0cdb36c5b9c3a0821a4b6044bb14586d739ae4ce98168e6bdcb9e9a6d82a494337fe0
6
+ metadata.gz: a5aad4351361de62499992df5f53ac8ec5f8b42f3cd1b207d108ac137404ab6e4e103f394193ae58c847195cc97fe53d475d275e542840360b4d61dfe74474c4
7
+ data.tar.gz: 55661804a4cb178b3365534c5df7a3c7653ecbe07e598b04ae4edfda64f0ada02edcd03ff08cc6f3004a519f5b1df80da4eccc1f4b2a6162d14166e6786f471e
data/changelog.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.4] - 2024-04-02
11
+
12
+ ### Added
13
+
14
+ - Add `Lennarb::Router` module to manage the routes in the project. Now, the `Lennarb` class is the main class of the project.
15
+
10
16
  ## [0.4.3] - 2024-04-01
11
17
 
12
18
  ### Added
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Aristóteles Coutinho.
5
+
6
+ require 'colorize'
7
+
8
+ class Lennarb
9
+ module Application
10
+ class Base
11
+ # @attribute [r] _route
12
+ # @returns [RouteNode]
13
+ #
14
+ # @attribute [r] _middlewares
15
+ # @returns [Array]
16
+ #
17
+ # @attribute [r] _global_after_hooks
18
+ # @returns [Array]
19
+ #
20
+ # @attribute [r] _global_before_hooks
21
+ # @returns [Array]
22
+ #
23
+ # @attribute [r] _after_hooks
24
+ # @returns [RouteNode]
25
+ #
26
+ # @attribute [r] _before_hooks
27
+ # @returns [RouteNode]
28
+ #
29
+ attr_accessor :_route, :_global_after_hooks, :_global_before_hooks, :_after_hooks, :_before_hooks
30
+
31
+ # Initialize the Application
32
+ #
33
+ # @returns [Base]
34
+ #
35
+ def self.inherited(subclass)
36
+ subclass.instance_variable_set(:@_route, Lennarb.new)
37
+ subclass.instance_variable_set(:@_middlewares, [])
38
+ subclass.instance_variable_set(:@_global_after_hooks, [])
39
+ subclass.instance_variable_set(:@_global_before_hooks, [])
40
+ subclass.instance_variable_set(:@_after_hooks, Lennarb::RouteNode.new)
41
+ subclass.instance_variable_set(:@_before_hooks, Lennarb::RouteNode.new)
42
+ end
43
+
44
+ def self.get(...) = @_route.get(...)
45
+ def self.put(...) = @_route.put(...)
46
+ def self.post(...) = @_route.post(...)
47
+ def self.head(...) = @_route.head(...)
48
+ def self.match(...) = @_route.match(...)
49
+ def self.patch(...) = @_route.patch(...)
50
+ def self.delete(...) = @_route.delete(...)
51
+ def self.options(...) = @_route.options(...)
52
+
53
+ # @returns [Array] middlewares
54
+ def self.middlewares = @_middlewares
55
+
56
+ # Use a middleware
57
+ #
58
+ # @parameter [Object] middleware
59
+ # @parameter [Array] args
60
+ # @parameter [Block] block
61
+ #
62
+ # @returns [Array] middlewares
63
+ #
64
+ def self.use(middleware, *args, &block)
65
+ @_middlewares << [middleware, args, block]
66
+ end
67
+
68
+ # Add a before hook
69
+ #
70
+ # @parameter [String] path
71
+ # @parameter [Block] block
72
+ #
73
+ def self.before(path = nil, &block)
74
+ if path
75
+ parts = path.split('/').reject(&:empty?)
76
+ @_before_hooks.add_route(parts, :before, block)
77
+ else
78
+ @_global_before_hooks << block
79
+ end
80
+ end
81
+
82
+ # Add a after hook
83
+ #
84
+ # @parameter [String] path
85
+ # @parameter [Block] block
86
+ #
87
+ def self.after(path = nil, &block)
88
+ if path
89
+ parts = path.split('/').reject(&:empty?)
90
+ @_after_hooks.add_route(parts, :after, block)
91
+ else
92
+ @_global_after_hooks << block
93
+ end
94
+ end
95
+
96
+ # Run the Application
97
+ #
98
+ # @returns [Base] self
99
+ #
100
+ # When you use this method, the application will be frozen. And you can't add more routes after that.
101
+ # This method is used to run the application in a Rack server so, you can use the `rackup` command
102
+ # to run the application.
103
+ # Ex. rackup -p 3000
104
+ # This command will use the following middleware:
105
+ # - Rack::ShowExceptions
106
+ # - Rack::Lint
107
+ # - Rack::MethodOverride
108
+ # - Rack::Head
109
+ # - Rack::ContentLength
110
+ # - Rack::Static
111
+ #
112
+ def self.run!
113
+ stack = Rack::Builder.new
114
+
115
+ use Rack::ShowExceptions
116
+ use Rack::Lint
117
+ use Rack::MethodOverride
118
+ use Rack::Head
119
+ use Rack::ContentLength
120
+ use Rack::Static,
121
+ root: 'public',
122
+ urls: ['/404.html', '/500.html'],
123
+ header_rules: [
124
+ [200, %w[html], { 'content-type' => 'text/html; charset=utf-8' }],
125
+ [400, %w[html], { 'content-type' => 'text/html; charset=utf-8' }],
126
+ [500, %w[html], { 'content-type' => 'text/html; charset=utf-8' }]
127
+ ]
128
+
129
+ middlewares.each do |(middleware, args, block)|
130
+ stack.use(middleware, *args, &block)
131
+ end
132
+
133
+ stack.run ->(env) do
134
+ catch(:halt) do
135
+ begin
136
+ execute_hooks(@_before_hooks, env, :before)
137
+ res = @_route.call(env)
138
+ execute_hooks(@_after_hooks, env, :after)
139
+ res
140
+ rescue StandardError => e
141
+ render_error if production?
142
+
143
+ puts e.message.red
144
+ puts e.backtrace
145
+ raise e
146
+ end.then do |response|
147
+ case response
148
+ in [400..499, _, _] then render_not_found
149
+ else response
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ @_route.freeze!
156
+
157
+ stack.to_app
158
+ end
159
+
160
+ def self.test? = ENV['RACK_ENV'] == 'test' || ENV['LENNARB_ENV'] == 'test'
161
+ def self.production? = ENV['RACK_ENV'] == 'production' || ENV['LENNARB_ENV'] == 'production'
162
+ def self.development? = ENV['RACK_ENV'] == 'development' || ENV['LENNARB_ENV'] == 'development'
163
+
164
+ # Render a not found
165
+ #
166
+ # @returns [void]
167
+ #
168
+ def self.render_not_found(content = nil)
169
+ default = File.exist?('public/404.html')
170
+ body = content || default || 'Not Found'
171
+ throw :halt, [404, { 'content-type' => 'text/html' }, [body]]
172
+ end
173
+
174
+ # Render an error
175
+ #
176
+ # @returns [void]
177
+ #
178
+ def self.render_error(content = nil)
179
+ default = File.exist?('public/500.html')
180
+ body = content || default || 'Internal Server Error'
181
+ throw :halt, [500, { 'content-type' => 'text/html' }, [body]]
182
+ end
183
+
184
+ # Redirect to a path
185
+ #
186
+ # @parameter [String] path
187
+ # @parameter [Integer] status default is 302
188
+ #
189
+ def self.redirect(path, status = 302) = throw :halt, [status, { 'location' => path }, []]
190
+
191
+ # Execute the hooks
192
+ #
193
+ # @parameter [RouteNode] hook_route
194
+ # @parameter [Hash] env
195
+ # @parameter [Symbol] action
196
+ #
197
+ # @returns [void]
198
+ #
199
+ def self.execute_hooks(hook_route, env, action)
200
+ execute_global_hooks(env, action)
201
+
202
+ execute_route_hooks(hook_route, env, action)
203
+ end
204
+
205
+ # Execute the global hooks
206
+ #
207
+ # @parameter [Hash] env
208
+ # @parameter [Symbol] action
209
+ #
210
+ # @returns [void]
211
+ #
212
+ def self.execute_global_hooks(env, action)
213
+ global_hooks = action == :before ? @_global_before_hooks : @_global_after_hooks
214
+ global_hooks.each { |hook| hook.call(env) }
215
+ end
216
+
217
+ # Execute the route hooks
218
+ #
219
+ # @parameter [RouteNode] hook_route
220
+ # @parameter [Hash] env
221
+ # @parameter [Symbol] action
222
+ #
223
+ # @returns [void]
224
+ #
225
+ def self.execute_route_hooks(hook_route, env, action)
226
+ parts = parse_path(env)
227
+ return unless parts
228
+
229
+ block, = hook_route.match_route(parts, action)
230
+ block&.call(env)
231
+ end
232
+
233
+ # Parse the path
234
+ #
235
+ # @parameter [Hash] env
236
+ #
237
+ # @returns [Array] parts
238
+ #
239
+ def self.parse_path(env) = env[Rack::PATH_INFO]&.split('/')&.reject(&:empty?)
240
+
241
+ # Check if the request is a HTML request
242
+ #
243
+ # @parameter [Hash] env
244
+ #
245
+ # @returns [Boolean]
246
+ #
247
+ def self.html_request?(env) = env['HTTP_ACCEPT']&.include?('text/html')
248
+
249
+ private_class_method :execute_hooks, :execute_global_hooks, :execute_route_hooks, :parse_path, :html_request?
250
+ end
251
+ end
252
+ end
@@ -125,6 +125,8 @@ class Lennarb
125
125
  def redirect(path, status = 302)
126
126
  @headers[LOCATION] = path
127
127
  @status = status
128
+
129
+ throw :halt, finish
128
130
  end
129
131
 
130
132
  # Finish the response
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2023-2024, by Aristóteles Coutinho.
5
5
 
6
6
  class Lennarb
7
- VERSION = '0.4.4'
7
+ VERSION = '0.5.1'
8
8
 
9
9
  public_constant :VERSION
10
10
  end
data/lib/lennarb.rb CHANGED
@@ -12,10 +12,10 @@ require 'rack'
12
12
 
13
13
  # Base class for Lennarb
14
14
  #
15
+ require_relative 'lennarb/application/base'
15
16
  require_relative 'lennarb/request'
16
17
  require_relative 'lennarb/response'
17
18
  require_relative 'lennarb/route_node'
18
- require_relative 'lennarb/router'
19
19
  require_relative 'lennarb/version'
20
20
 
21
21
  class Lennarb
@@ -26,7 +26,7 @@ class Lennarb
26
26
  # @attribute [r] root
27
27
  # @returns [RouteNode]
28
28
  #
29
- attr_reader :root
29
+ attr_reader :_root
30
30
 
31
31
  # Initialize the application
32
32
  #
@@ -35,7 +35,7 @@ class Lennarb
35
35
  # @returns [Lennarb]
36
36
  #
37
37
  def initialize
38
- @root = RouteNode.new
38
+ @_root = RouteNode.new
39
39
  yield self if block_given?
40
40
  end
41
41
 
@@ -58,7 +58,7 @@ class Lennarb
58
58
  http_method = env[Rack::REQUEST_METHOD].to_sym
59
59
  parts = SplitPath[env[Rack::PATH_INFO]]
60
60
 
61
- block, params = @root.match_route(parts, http_method)
61
+ block, params = @_root.match_route(parts, http_method)
62
62
  return [404, { 'content-type' => 'text/plain' }, ['Not Found']] unless block
63
63
 
64
64
  @res = Response.new
@@ -66,36 +66,15 @@ class Lennarb
66
66
 
67
67
  catch(:halt) do
68
68
  instance_exec(req, @res, &block)
69
- finish!
69
+ @res.finish
70
70
  end
71
71
  end
72
72
 
73
- # Finish the request
74
- #
75
- # @returns [Array] Response
76
- #
77
- def finish! = halt(@res.finish)
78
-
79
- # Immediately stops the request and returns `response`
80
- # as per Rack's specification.
81
- #
82
- # halt([200, { "Content-Type" => "text/html" }, ["hello"]])
83
- # halt([res.status, res.headers, res.body])
84
- # halt(res.finish)
85
- #
86
- # @parameter [Array] response
87
- #
88
- # @returns [Array] response
89
- #
90
- def halt(response)
91
- throw(:halt, response)
92
- end
93
-
94
73
  # Freeze the routes
95
74
  #
96
75
  # @returns [void]
97
76
  #
98
- def freeze! = @root.freeze
77
+ def freeze! = @_root.freeze
99
78
 
100
79
  # Add a routes
101
80
  #
@@ -124,6 +103,6 @@ class Lennarb
124
103
  #
125
104
  def add_route(path, http_method, block)
126
105
  parts = SplitPath[path]
127
- @root.add_route(parts, http_method, block)
106
+ @_root.add_route(parts, http_method, block)
128
107
  end
129
108
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lennarb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aristóteles Coutinho
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-03 00:00:00.000000000 Z
11
+ date: 2024-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -180,10 +180,10 @@ files:
180
180
  - changelog.md
181
181
  - exe/lenna
182
182
  - lib/lennarb.rb
183
+ - lib/lennarb/application/base.rb
183
184
  - lib/lennarb/request.rb
184
185
  - lib/lennarb/response.rb
185
186
  - lib/lennarb/route_node.rb
186
- - lib/lennarb/router.rb
187
187
  - lib/lennarb/version.rb
188
188
  - license.md
189
189
  - readme.md
@@ -211,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
211
  - !ruby/object:Gem::Version
212
212
  version: '0'
213
213
  requirements: []
214
- rubygems_version: 3.5.3
214
+ rubygems_version: 3.5.9
215
215
  signing_key:
216
216
  specification_version: 4
217
217
  summary: A lightweight and experimental web framework for Ruby.
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2023-2024, by Aristóteles Coutinho.
5
-
6
- class Lennarb
7
- # This module must be included in the class that will use the router.
8
- #
9
- module Router
10
- def self.included(base)
11
- base.extend(ClassMethods)
12
- base.instance_variable_set(:@router, Lennarb.new)
13
- base.define_singleton_method(:router) do
14
- base.instance_variable_get(:@router)
15
- end
16
- end
17
-
18
- module ClassMethods
19
- # Helper method to define a route
20
- #
21
- # returns [Lennarb] instance of the router
22
- #
23
- def route = router
24
-
25
- # Use this in congi.ru to start the application
26
- #
27
- def app
28
- router.freeze!
29
- router
30
- end
31
- end
32
- end
33
- end