lennarb 1.4.0 → 1.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.
data/changelog.md CHANGED
@@ -7,6 +7,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - Add `Lennarb::Application` class to be the base class of the "standard" implementation of the Lennarb framework.
13
+ - Add middleware support to Lennarb::App class.
14
+ - Add `middleware` support to the `Lennarb::Application` with default middlewares.
15
+ - Add files to centralize the errors of the project.
16
+ - Add CODE_OF_CONDUCT.md in English and Portuguese
17
+ - Add CONTRIBUTING.md in English and Portuguese
18
+ - Add `Lennarb::Logger` class for structured logging with support for tags and colorization
19
+ - Add logger dependency to enhance logging capabilities in the framework
20
+ - Add `Lennarb::ParameterFilter` for sensitive parameter filtering in logs and exceptions
21
+ - Add `Lennarb::RequestLogger` middleware for detailed HTTP request logging
22
+ - Add `Lennarb::Hooks` module for implementing hooks in the framework
23
+ - Add improved request handling and configuration options
24
+ - Add `RoutesFrozenError` for improved route modification handling
25
+ - Add support for defining helpers with both modules and blocks in `Lennarb::App`.
26
+ - Add `Lennarb::Helpers.define` method to handle modules and blocks for helper definitions.
27
+ - Add tests for `Lennarb::Helpers` to validate module inclusion and block evaluation.
28
+ - Add `Lennarb::Hooks` tests to validate hook initialization, addition, and execution.
29
+
30
+ ### Changed
31
+
32
+ - Lennarb Logo
33
+ - Migrate from utopiaproject to yard for documentation
34
+ - Fix logo SVG display in yard documentation
35
+ - Restructure App class to enhance routing, middleware, and initialization processes
36
+ - Simplify Routes class by removing unnecessary comments and enhancing route definitions
37
+ - Introduce Helpers module for managing application-specific helper methods
38
+ - Update logger tag to use symbol for consistency
39
+ - Enhance logging functionality with improved request logging details
40
+ - Simplify config method by removing block parameter and improving readability
41
+ - Update `Lennarb::App.helpers` to accept a module or block for defining helpers.
42
+
43
+ ### Fixed
44
+
45
+ - Fix typo in require_relative statement for constants file
46
+ - Fix conditional debug dependency based on Ruby engine
47
+
48
+ ## [1.4.1] - 2025-02-23
49
+
50
+ ### Added
51
+
52
+ - Add support to mount routes. Now, you can centralize the routes in a single file and mount them in the main application. Ex.
53
+
54
+ ```rb
55
+ class PostsController
56
+ extend Lennarb::Routes::Mixin
57
+
58
+ get '/posts' do |req, res|
59
+ res.html('Posts')
60
+ end
61
+ end
62
+
63
+ SampleApp = Lennarb.new do |router|
64
+ mount PostsController
65
+ end
66
+ ```
67
+
68
+ The `mount` method will add the routes from the `PostsController` class to the main application. You can use the `mount` method with multiple classes, ex. `mount PostsController, CommentsController`.
69
+
70
+ - Add `Lennarb::Environment` module to manage the environment variables in the project. Now, the `Lennarb` class is the main class of the project.
71
+ - Add `Lennarb::Config` module to manage the configuration in the project. Now, the `Lennarb` class is the main class of the project.
72
+ - Add `Lennarb::App` class.
73
+ - Lint the code with `standard` gem on the CI/CD pipeline.
74
+
75
+ ### Changed
76
+
77
+ - Convert the `Lennarb` class to a module. Now, the `App` class is the main class of the project.
78
+ - Move the request process to `Lennarb::RequestHandler` class.
79
+ - Improve the method `merge!` from `Lennarb::RouterNode` to prevent the duplication of the routes.
80
+
81
+ ### Fixed
82
+
83
+ - Software design issues.
84
+
10
85
  ## [1.4.0] - 2025-02-09
11
86
 
12
87
  ### Changed
@@ -177,6 +252,7 @@ end
177
252
  - Add `console` gem to print the logs in the console.
178
253
 
179
254
  - Add CLI module to:
255
+
180
256
  - Create a new project with `lennarb new` command.
181
257
  - Run the server with `lennarb server` command.
182
258
 
data/gems.rb CHANGED
@@ -4,26 +4,7 @@ source "https://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  group :maintenance, optional: true do
7
- # [https://rubygems.org/gems/bake]
8
- # Bake is a Ruby library that helps you to create a new project.
9
- gem "bake"
10
- # [https://rubygems.org/gems/covered]
11
- # Covered is a Ruby library that helps you to test your project.
12
- gem "covered"
13
- # [https://rubygems.org/gems/bake-gem]
14
- # Bake Gem is a Bake extension that helps you to create a new Ruby
15
- # gem.
16
- gem "bake-gem"
17
- # [https://rubygems.org/gems/bake-modernize]
18
- # Bake Modernize is a Bake extension that helps you to modernize your
19
- # Ruby code.
20
- gem "bake-modernize"
21
- # [https://rubygems.org/gems/bake-github-pages]
22
- # Bake Github Pages is a Bake extension that helps you to publish your
23
- # project documentation to Github Pages.
24
- gem "bake-github-pages"
25
- # [https://rubygems.org/gems/utpia-project]
26
- # Utopia Project is a Bake extension that helps you to create a new
27
- # project.
28
- gem "utopia-project"
7
+ # Yard is a documentation generation tool for the Ruby programming language.
8
+ # [https://rubygems.org/gems/yard]
9
+ gem "yard"
29
10
  end
@@ -27,15 +27,17 @@ Create a new file named `config.ru`:
27
27
  ```ruby
28
28
  require 'lennarb'
29
29
 
30
- app = Lennarb.new do |app|
31
- app.get '/' do |req, res|
32
- res.status = 200
33
- res.html('<h1>Welcome to Lennarb!</h1>')
30
+ MyApp = Lennarb::App.new do
31
+ routes do
32
+ get '/' do |req, res|
33
+ res.status = 200
34
+ res.html('<h1>Welcome to Lennarb!</h1>')
35
+ end
34
36
  end
35
37
  end
36
38
 
37
- app.initializer!
38
- run app
39
+ MyApp.initialize!
40
+ run MyApp
39
41
  ```
40
42
 
41
43
  Start the server:
@@ -69,7 +71,7 @@ app.get '/html' do |req, res|
69
71
  end
70
72
 
71
73
  app.get '/json' do |req, res|
72
- res.json('{"message": "JSON response"}')
74
+ res.json({ message: "JSON response" })
73
75
  end
74
76
  ```
75
77
 
@@ -84,16 +86,16 @@ end
84
86
  Routes are defined using HTTP method helpers:
85
87
 
86
88
  ```ruby
87
- app = Lennarb.new do |l|
88
- # Basic route
89
- l.get '/' do |req, res|
90
- res.html('Home page')
91
- end
92
-
93
- # Route with parameters
94
- l.get '/users/:id' do |req, res|
95
- user_id = req.params[:id]
96
- res.json("{\"id\": #{user_id}}")
89
+ Lennarb::App.new do
90
+ routes do
91
+ get '/' do |req, res|
92
+ res.html('Home page')
93
+ end
94
+
95
+ get '/users/:id' do |req, res|
96
+ user_id = req.params[:id]
97
+ res.json({ id: user_id })
98
+ end
97
99
  end
98
100
  end
99
101
  ```
@@ -103,7 +105,7 @@ end
103
105
  Parameters from dynamic route segments are available in `req.params`:
104
106
 
105
107
  ```ruby
106
- app.get '/hello/:name' do |req, res|
108
+ get '/hello/:name' do |req, res|
107
109
  name = req.params[:name]
108
110
  res.text("Hello, #{name}!")
109
111
  end
@@ -117,20 +119,26 @@ Lennarb is thread-safe by design:
117
119
  - The router tree is frozen after initialization
118
120
  - Response objects are created per-request
119
121
 
120
- ## Application Lifecycle
122
+ ## Application Life-cycle
121
123
 
122
124
  ### Initialization
123
125
 
124
126
  ```ruby
125
- app = Lennarb.new do |l|
126
- # Define routes and configuration
127
+ MyApp = Lennarb::App.new do
128
+ # Define routes
129
+ routes do
130
+ end
131
+
132
+ # Define Configurations
133
+ config do
134
+ end
127
135
  end
128
136
 
129
137
  # Initialize and freeze the application
130
- app.initializer!
138
+ MyApp.initialize!
131
139
  ```
132
140
 
133
- The `initializer!` method:
141
+ The `initialize!` method:
134
142
 
135
143
  - Loads environment-specific dependencies
136
144
  - Freezes the route tree
@@ -140,6 +148,12 @@ The `initializer!` method:
140
148
 
141
149
  Lennarb uses the `LENNA_ENV` environment variable (defaults to "development"):
142
150
 
151
+ It can be set using the following environment variables:
152
+
153
+ - `LENNA_ENV`
154
+ - `APP_ENV`
155
+ - `RACK_ENV`
156
+
143
157
  ```bash
144
158
  LENNA_ENV=production rackup
145
159
  ```
@@ -149,7 +163,7 @@ LENNA_ENV=production rackup
149
163
  Lennarb provides basic error handling:
150
164
 
151
165
  ```ruby
152
- app.get '/api' do |req, res|
166
+ get '/api' do |req, res|
153
167
  # Errors are caught and return 500 with error message
154
168
  raise "Something went wrong"
155
169
  end
@@ -162,20 +176,20 @@ Default error responses:
162
176
 
163
177
  ## Best Practices
164
178
 
165
- 1. **Always call initializer!**
179
+ 1. **Always call initialize!**
166
180
 
167
181
  ```ruby
168
- app = Lennarb.new { |l| ... }
169
- app.initializer!
182
+ app = Lennarb::App.new
183
+ app.initialize!
170
184
  run app
171
185
  ```
172
186
 
173
187
  2. **Set response status**
174
188
 
175
189
  ```ruby
176
- app.get '/api' do |req, res|
190
+ get '/api' do |req, res|
177
191
  res.status = 200
178
- res.json('{"status": "ok"}')
192
+ res.json({ status: "ok" })
179
193
  end
180
194
  ```
181
195
 
@@ -186,7 +200,7 @@ Default error responses:
186
200
  res.html('<h1>Web Page</h1>')
187
201
 
188
202
  # JSON for APIs
189
- res.json('{"data": "value"}')
203
+ res.json({ data: "value" })
190
204
 
191
205
  # Text for simple responses
192
206
  res.text('Hello')
@@ -199,3 +213,7 @@ For help and bug reports, please visit:
199
213
  - GitHub Issues: [lennarb/issues](https://github.com/aristotelesbr/lennarb/issues)
200
214
 
201
215
  Now you can run your app!
216
+
217
+ ```
218
+
219
+ ```
@@ -0,0 +1,38 @@
1
+ ## Mounting Applications
2
+
3
+ You can mount other applications at specific paths using the `mount` method. The component to be mounted must be a subclass of `Lennarb::App`.
4
+
5
+ ```ruby
6
+ class Blog < Lennarb::App
7
+ get "/" do |req, res|
8
+ res.html("<h1>Welcome to my blog</h1>")
9
+ end
10
+ end
11
+
12
+ class Admin < Lennarb::App
13
+ get "/" do |req, res|
14
+ res.html("<h1>Admin Dashboard</h1>")
15
+ end
16
+ end
17
+
18
+ class Application < Lennarb::Base
19
+ mount(Blog, at: "/blog")
20
+ mount(Admin, at: "/admin")
21
+ end
22
+ ```
23
+
24
+ ## Middleware Configuration
25
+
26
+ Middleware can be configured at both the base application level and within individual mounted applications. This allows for flexible and modular application design.
27
+
28
+ ```ruby
29
+ class Blog < Lennarb::App
30
+ middleware do
31
+ use MyCustomMiddleware
32
+ end
33
+ end
34
+ ```
35
+
36
+ ## Conclusion
37
+
38
+ By using the `Base` class and the `mount` method, Lennarb provides a powerful way to structure your applications into modular components, each with its own routes and middleware.
@@ -10,19 +10,19 @@ You can use the `res` object to send a response to the client.
10
10
  ```ruby
11
11
  # app.rb
12
12
 
13
- app.get '/' do |req, res|
13
+ get '/' do |req, res|
14
14
  res.html 'Hello World'
15
15
  end
16
16
  ```
17
17
 
18
18
  ## Content Types
19
19
 
20
- Lenna supports the following content types:
20
+ Lennarb supports the following content types:
21
21
 
22
22
  ```ruby
23
23
  # app.rb
24
24
 
25
- app.get '/' do |req, res|
25
+ get '/' do |req, res|
26
26
  res.html 'Hello World'
27
27
  res.json '{"message": "Hello World"}'
28
28
  res.text 'Hello World'
@@ -43,7 +43,7 @@ You can use the `res.write` method to write to the response body:
43
43
  ```ruby
44
44
  # app.rb
45
45
 
46
- app.get '/' do |req, res|
46
+ get '/' do |req, res|
47
47
  res.write 'Hello World'
48
48
  end
49
49
  ```
@@ -53,7 +53,7 @@ JSON example:
53
53
  ```ruby
54
54
  # app.rb
55
55
 
56
- app.post '/posts' do |req, res|
56
+ post '/posts' do |req, res|
57
57
  req.params # => { name: 'Lenna' }
58
58
  name = req.params[:name]
59
59
 
@@ -76,7 +76,7 @@ You can redirect the client using the `res.redirect` method:
76
76
  ```ruby
77
77
  # app.ruby
78
78
 
79
- app.get '/' do |req, res|
79
+ get '/' do |req, res|
80
80
  # Stuff code here...
81
81
  res.redirect '/hello'
82
82
  end
data/lennarb.gemspec CHANGED
@@ -31,14 +31,17 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency "bigdecimal"
32
32
  spec.add_dependency "colorize", "~> 1.1"
33
33
  spec.add_dependency "rack", "~> 3.1"
34
+ spec.add_dependency "superconfig", "~> 3.0"
35
+ spec.add_dependency "logger", "~> 1.7"
34
36
  spec.add_development_dependency "bundler"
35
- spec.add_development_dependency "covered"
36
37
  spec.add_development_dependency "simplecov"
38
+ spec.add_development_dependency "simplecov-json"
37
39
  spec.add_development_dependency "minitest"
40
+ spec.add_development_dependency "minitest-utils"
38
41
  spec.add_development_dependency "rack-test"
39
42
  spec.add_development_dependency "rake"
40
43
  spec.add_development_dependency "standard"
41
44
  spec.add_development_dependency "standard-custom"
42
45
  spec.add_development_dependency "standard-performance"
43
- spec.add_development_dependency "m"
46
+ spec.add_development_dependency "debug" if RUBY_ENGINE == "ruby"
44
47
  end
@@ -0,0 +1,253 @@
1
+ module Lennarb
2
+ # Main application class with hooks and helpers support
3
+ class App
4
+ class << self
5
+ attr_writer :app
6
+ # Rack environment variable name
7
+ #
8
+ # @return [String] The environment variable name
9
+ def app
10
+ @app ||= self
11
+ end
12
+
13
+ # Define helper methods for the application
14
+ #
15
+ # @yield Block containing helper definitions
16
+ # @return [Module] The helpers module
17
+ def helpers(mod_or_block = nil, &block)
18
+ Helpers.define(self, mod_or_block, &block)
19
+ end
20
+
21
+ # Define a before hook
22
+ #
23
+ # @yield [req, res] Block to execute before route
24
+ # @return [Array] The before hooks array
25
+ def before(&block)
26
+ Hooks.add(self, :before, &block)
27
+ end
28
+
29
+ # Define an after hook
30
+ #
31
+ # @yield [req, res] Block to execute after route
32
+ # @return [Array] The after hooks array
33
+ def after(&block)
34
+ Hooks.add(self, :after, &block)
35
+ end
36
+
37
+ # Get routes for this app class
38
+ #
39
+ # @return [Routes] The routes instance
40
+ def routes
41
+ @routes ||= Routes.new
42
+ end
43
+
44
+ # Set up subclass
45
+ #
46
+ # @param [Class] subclass The new subclass
47
+ # @return [void]
48
+ def inherited(subclass)
49
+ super
50
+ # Each subclass gets its own routes
51
+ subclass.instance_variable_set(:@routes, Routes.new)
52
+ end
53
+
54
+ # Define route methods for all HTTP methods
55
+ HTTP_METHODS.each do |http_method|
56
+ define_method(http_method.downcase) do |path, &block|
57
+ routes.send(http_method.downcase, path, &block)
58
+ end
59
+ end
60
+
61
+ # Define root route (GET /)
62
+ #
63
+ # @yield [req, res, params] Route block
64
+ # @return [void]
65
+ def root(&block)
66
+ get("/", &block)
67
+ end
68
+
69
+ # Define configuration
70
+ #
71
+ # @param [Array<Symbol>] envs Environments
72
+ # @yield Configuration block
73
+ # @return [Config] The config instance
74
+ def config(*envs, &)
75
+ @config ||= Config.new(self)
76
+
77
+ if block_given?
78
+ write = envs.empty? || envs.map(&:to_sym).include?(env.name)
79
+ @config.instance_eval(&) if write
80
+ end
81
+
82
+ @config
83
+ end
84
+ end
85
+
86
+ # Instance methods
87
+
88
+ # Initialize a new app
89
+ #
90
+ # @yield [self] Configuration block
91
+ def initialize(&block)
92
+ @initialized = false
93
+ @root = Pathname.pwd
94
+ @env = Environment.new(compute_env)
95
+
96
+ instance_eval(&block) if block_given?
97
+ end
98
+
99
+ # The current environment
100
+ attr_reader :env
101
+
102
+ # The root directory
103
+ attr_accessor :root
104
+
105
+ # Set environment
106
+ #
107
+ # @param [String, Symbol] value Environment name
108
+ # @raise [AlreadyInitializedError] If already initialized
109
+ # @return [Environment] The new environment
110
+ def env=(value)
111
+ raise AlreadyInitializedError if initialized?
112
+ @env = Environment.new(value)
113
+ end
114
+
115
+ # Get the Rack app with middleware
116
+ #
117
+ # @return [#call] The Rack app
118
+ def app
119
+ @app ||= begin
120
+ handler = build_request_handler
121
+ stack = middleware.to_a
122
+
123
+ Rack::Builder.app do
124
+ stack.each { |middleware, args, block| use(middleware, *args, &block) }
125
+ run handler
126
+ end
127
+ end
128
+ end
129
+
130
+ # Rack interface method
131
+ #
132
+ # @param [Hash] env Rack environment
133
+ # @return [Array] Rack response
134
+ def call(env)
135
+ env[RACK_LENNA_APP] = self
136
+ app.call(env)
137
+ end
138
+
139
+ # Define middleware
140
+ #
141
+ # @yield Block to configure middleware
142
+ # @return [MiddlewareStack] The middleware stack
143
+ def middleware(&block)
144
+ @middleware_stack ||= default_middleware_stack
145
+ @middleware_stack.instance_eval(&block) if block_given?
146
+ @middleware_stack
147
+ end
148
+
149
+ # Define helpers (instance method)
150
+ #
151
+ # @yield Block with helper definitions
152
+ # @return [Module] The helpers module
153
+ def helpers(&block)
154
+ self.class.helpers(&block)
155
+ end
156
+
157
+ # Define before hook (instance method)
158
+ #
159
+ # @yield [req, res] Before hook block
160
+ # @return [Array] The before hooks array
161
+ def before(&block)
162
+ self.class.before(&block)
163
+ end
164
+
165
+ # Define after hook (instance method)
166
+ #
167
+ # @yield [req, res] After hook block
168
+ # @return [Array] The after hooks array
169
+ def after(&block)
170
+ self.class.after(&block)
171
+ end
172
+
173
+ # Get/define routes
174
+ #
175
+ # @yield Block to define routes
176
+ # @return [Routes] The routes instance
177
+ def routes(&block)
178
+ if block_given?
179
+ self.class.instance_exec(&block)
180
+ end
181
+
182
+ self.class.routes
183
+ end
184
+
185
+ # Get/define configuration
186
+ #
187
+ # @param [Array<Symbol>] envs Environments
188
+ # @yield Configuration block
189
+ # @return [Config] The config instance
190
+ def config(*envs, &)
191
+ @config ||= self.class.config
192
+
193
+ if block_given?
194
+ write = envs.empty? || envs.map(&:to_sym).include?(env.name)
195
+ @config.instance_eval(&) if write
196
+ end
197
+
198
+ @config
199
+ end
200
+
201
+ # Initialize the app
202
+ #
203
+ # @return [self] The initialized app
204
+ # @raise [AlreadyInitializedError] If already initialized
205
+ def initialize!
206
+ raise AlreadyInitializedError if @initialized
207
+
208
+ @initialized = true
209
+ routes.freeze
210
+ self
211
+ end
212
+
213
+ # Check if initialized
214
+ #
215
+ # @return [Boolean] true if initialized
216
+ def initialized?
217
+ @initialized
218
+ end
219
+
220
+ # Error for already initialized app
221
+ AlreadyInitializedError = Class.new(StandardError)
222
+
223
+ protected
224
+
225
+ # Build the request handler
226
+ #
227
+ # @return [RequestHandler] The request handler
228
+ def build_request_handler
229
+ RequestHandler.new(self)
230
+ end
231
+
232
+ # Create default middleware stack
233
+ #
234
+ # @return [MiddlewareStack] The middleware stack
235
+ private def default_middleware_stack
236
+ stack = MiddlewareStack.new
237
+ stack.use(Lennarb::Middleware::RequestLogger)
238
+ stack.use(Rack::Runtime)
239
+ stack.use(Rack::Head)
240
+ stack.use(Rack::ETag)
241
+ stack.use(Rack::ShowExceptions) if env.development?
242
+ stack
243
+ end
244
+
245
+ # Compute environment from ENV variables
246
+ #
247
+ # @return [String] Environment name
248
+ private def compute_env
249
+ env = ENV_NAMES.map { |name| ENV[name] }.compact.first.to_s
250
+ env.empty? ? "development" : env
251
+ end
252
+ end
253
+ end