lennarb 0.4.4 → 0.5.0

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: 888f84e566de65f5c87013ff56ac3fe485f59acba6f1cc39862178894cf101fa
4
+ data.tar.gz: 8703693e316b83f747f927c23b07d56f6dd571f813d8c00901432cf6c941d1f2
5
5
  SHA512:
6
- metadata.gz: ab79db446aab1914aa18146f5b0f8814c68296f04fc7b300af3ac9e0079b11f002b284402335a03acba3a2d63d039c54ce20396e84b1c60b3cc9dfadbeb5f860
7
- data.tar.gz: d16922a8773b300d364c2db69b3925555dfa3d29f91a4018c2d887360dd0cdb36c5b9c3a0821a4b6044bb14586d739ae4ce98168e6bdcb9e9a6d82a494337fe0
6
+ metadata.gz: bc83791eb32ad87b5aa772f375b048829d794d35bdea5c5d1d083dc4a2e8b27560b313c79f51ca7a3a7084d73cc4ddeb9fb17a29a33a211bd53db57090af791a
7
+ data.tar.gz: a1fb09a73e8610d562cb4f4ea5e2ce1a07ffa68f4574363f07700b0e1004823fb5f1003d6cf6276fec2eb0002894ddf4dce7c7e4c4b55773bbb729a96ec74f4c
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,236 @@
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
+ module Application
8
+ class Base
9
+ # @attribute [r] _route
10
+ # @returns [RouteNode]
11
+ #
12
+ # @attribute [r] _middlewares
13
+ # @returns [Array]
14
+ #
15
+ # @attribute [r] _global_after_hooks
16
+ # @returns [Array]
17
+ #
18
+ # @attribute [r] _global_before_hooks
19
+ # @returns [Array]
20
+ #
21
+ # @attribute [r] _after_hooks
22
+ # @returns [RouteNode]
23
+ #
24
+ # @attribute [r] _before_hooks
25
+ # @returns [RouteNode]
26
+ #
27
+ attr_accessor :_route, :_global_after_hooks, :_global_before_hooks, :_after_hooks, :_before_hooks
28
+
29
+ # Initialize the Application
30
+ #
31
+ # @returns [Base]
32
+ #
33
+ def self.inherited(subclass)
34
+ subclass.instance_variable_set(:@_route, Lennarb.new)
35
+ subclass.instance_variable_set(:@_middlewares, [])
36
+ subclass.instance_variable_set(:@_global_after_hooks, [])
37
+ subclass.instance_variable_set(:@_global_before_hooks, [])
38
+ subclass.instance_variable_set(:@_after_hooks, Lennarb::RouteNode.new)
39
+ subclass.instance_variable_set(:@_before_hooks, Lennarb::RouteNode.new)
40
+ end
41
+
42
+ def self.get(...) = @_route.get(...)
43
+ def self.put(...) = @_route.put(...)
44
+ def self.post(...) = @_route.post(...)
45
+ def self.head(...) = @_route.head(...)
46
+ def self.match(...) = @_route.match(...)
47
+ def self.patch(...) = @_route.patch(...)
48
+ def self.delete(...) = @_route.delete(...)
49
+ def self.options(...) = @_route.options(...)
50
+
51
+ # @returns [Array] middlewares
52
+ def self.middlewares = @_middlewares
53
+
54
+ # Use a middleware
55
+ #
56
+ # @parameter [Object] middleware
57
+ # @parameter [Array] args
58
+ # @parameter [Block] block
59
+ #
60
+ # @returns [Array] middlewares
61
+ #
62
+ def self.use(middleware, *args, &block)
63
+ @_middlewares << [middleware, args, block]
64
+ end
65
+
66
+ # Add a before hook
67
+ #
68
+ # @parameter [String] path
69
+ # @parameter [Block] block
70
+ #
71
+ def self.before(path = nil, &block)
72
+ if path
73
+ parts = path.split('/').reject(&:empty?)
74
+ @_before_hooks.add_route(parts, :before, block)
75
+ else
76
+ @_global_before_hooks << block
77
+ end
78
+ end
79
+
80
+ # Add a after hook
81
+ #
82
+ # @parameter [String] path
83
+ # @parameter [Block] block
84
+ #
85
+ def self.after(path = nil, &block)
86
+ if path
87
+ parts = path.split('/').reject(&:empty?)
88
+ @_after_hooks.add_route(parts, :after, block)
89
+ else
90
+ @_global_after_hooks << block
91
+ end
92
+ end
93
+
94
+ # Call the application
95
+ #
96
+ # @parameter [Hash] env
97
+ #
98
+ # @returns [Array] response
99
+ #
100
+ def self.call(env)
101
+ response =
102
+ catch(:halt) do
103
+ execute_hooks(@_before_hooks, env, :before)
104
+
105
+ res = @_route.call(env)
106
+
107
+ execute_hooks(@_after_hooks, env, :after)
108
+ res
109
+ end
110
+
111
+ case response
112
+ in [404 | 404, _, _] if html_request?(env) && File.exist?('public/404.html')
113
+ env['PATH_INFO'] = '/404.html'
114
+ Rack::Static.new(-> { response }, urls: ['/404.html'], root: 'public').call(env)
115
+ in [404 | 404, _, _] then response
116
+ else
117
+ response.is_a?(Array) ? response : response.finish
118
+ end
119
+ end
120
+
121
+ # Run the Application
122
+ #
123
+ # @returns [Base] self
124
+ #
125
+ # When you use this method, the application will be frozen. And you can't add more routes after that.
126
+ # This method is used to run the application in a Rack server so, you can use the `rackup` command to run the application.
127
+ # Ex. rackup -p 3000
128
+ # This command will use the following middleware:
129
+ # - Rack::ShowExceptions
130
+ # - Rack::Lint
131
+ # - Rack::MethodOverride
132
+ # - Rack::Head
133
+ # - Rack::ContentLength
134
+ # - Rack::Static
135
+ #
136
+ def self.run!
137
+ stack = Rack::Builder.new
138
+
139
+ use Rack::ShowExceptions
140
+ use Rack::Lint
141
+ use Rack::MethodOverride
142
+ use Rack::Head
143
+ use Rack::ContentLength
144
+ use Rack::Static,
145
+ root: 'public',
146
+ urls: ['/404.html'],
147
+ header_rules: [
148
+ [200, %w[html], { 'content-type' => 'text/html; charset=utf-8' }]
149
+ ]
150
+
151
+ middlewares.each do |(middleware, args, block)|
152
+ stack.use(middleware, *args, &block)
153
+ end
154
+
155
+ stack.run @_route
156
+
157
+ @_route.freeze!
158
+
159
+ @_route = stack.to_app.freeze
160
+
161
+ self
162
+ end
163
+
164
+ # Redirect to a path
165
+ #
166
+ # @parameter [String] path
167
+ # @parameter [Integer] status default is 302
168
+ # @parameter [Hash] env (optional)
169
+ #
170
+ def self.redirect(path, status = 302, _env = nil)
171
+ response = Rack::Response.new([], status, 'location' => path)
172
+ throw :halt, response.finish
173
+ end
174
+
175
+ # Execute the hooks
176
+ #
177
+ # @parameter [RouteNode] hook_route
178
+ # @parameter [Hash] env
179
+ # @parameter [Symbol] action
180
+ #
181
+ # @returns [void]
182
+ #
183
+ def self.execute_hooks(hook_route, env, action)
184
+ execute_global_hooks(env, action)
185
+
186
+ execute_route_hooks(hook_route, env, action)
187
+ end
188
+
189
+ # Execute the global hooks
190
+ #
191
+ # @parameter [Hash] env
192
+ # @parameter [Symbol] action
193
+ #
194
+ # @returns [void]
195
+ #
196
+ def self.execute_global_hooks(env, action)
197
+ global_hooks = action == :before ? @_global_before_hooks : @_global_after_hooks
198
+ global_hooks.each { |hook| hook.call(env) }
199
+ end
200
+
201
+ # Execute the route hooks
202
+ #
203
+ # @parameter [RouteNode] hook_route
204
+ # @parameter [Hash] env
205
+ # @parameter [Symbol] action
206
+ #
207
+ # @returns [void]
208
+ #
209
+ def self.execute_route_hooks(hook_route, env, action)
210
+ parts = parse_path(env)
211
+ return unless parts
212
+
213
+ block, = hook_route.match_route(parts, action)
214
+ block&.call(env)
215
+ end
216
+
217
+ # Parse the path
218
+ #
219
+ # @parameter [Hash] env
220
+ #
221
+ # @returns [Array] parts
222
+ #
223
+ def self.parse_path(env) = env[Rack::PATH_INFO]&.split('/')&.reject(&:empty?)
224
+
225
+ # Check if the request is a HTML request
226
+ #
227
+ # @parameter [Hash] env
228
+ #
229
+ # @returns [Boolean]
230
+ #
231
+ def self.html_request?(env) = env['HTTP_ACCEPT']&.include?('text/html')
232
+
233
+ private_class_method :execute_hooks, :execute_global_hooks, :execute_route_hooks, :parse_path, :html_request?
234
+ end
235
+ end
236
+ 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.0'
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.0
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-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -44,6 +44,34 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: 3.0.8
47
+ - !ruby/object:Gem::Dependency
48
+ name: rack-protection
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '4.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '4.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rack-session
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.0'
47
75
  - !ruby/object:Gem::Dependency
48
76
  name: bake
49
77
  requirement: !ruby/object:Gem::Requirement
@@ -180,10 +208,10 @@ files:
180
208
  - changelog.md
181
209
  - exe/lenna
182
210
  - lib/lennarb.rb
211
+ - lib/lennarb/application/base.rb
183
212
  - lib/lennarb/request.rb
184
213
  - lib/lennarb/response.rb
185
214
  - lib/lennarb/route_node.rb
186
- - lib/lennarb/router.rb
187
215
  - lib/lennarb/version.rb
188
216
  - license.md
189
217
  - readme.md
@@ -211,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
239
  - !ruby/object:Gem::Version
212
240
  version: '0'
213
241
  requirements: []
214
- rubygems_version: 3.5.3
242
+ rubygems_version: 3.5.9
215
243
  signing_key:
216
244
  specification_version: 4
217
245
  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