lennarb 1.4.1 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/coverage.yaml +11 -33
- data/.github/workflows/documentation.yaml +32 -13
- data/.gitignore +9 -0
- data/.yardopts +8 -0
- data/CODE_OF_CONDUCT.md +118 -0
- data/CONTRIBUTING.md +155 -0
- data/README.pt-BR.md +147 -0
- data/Rakefile +32 -1
- data/changelog.md +38 -0
- data/gems.rb +3 -22
- data/guides/getting-started/readme.md +11 -7
- data/guides/mounting-applications/readme.md +38 -0
- data/lennarb.gemspec +4 -4
- data/lib/lennarb/app.rb +199 -118
- data/lib/lennarb/base.rb +314 -0
- data/lib/lennarb/config.rb +23 -5
- data/lib/lennarb/constants.rb +12 -0
- data/lib/lennarb/environment.rb +11 -7
- data/lib/lennarb/errors.rb +17 -0
- data/lib/lennarb/helpers.rb +40 -0
- data/lib/lennarb/hooks.rb +71 -0
- data/lib/lennarb/logger.rb +100 -0
- data/lib/lennarb/middleware/request_logger.rb +117 -0
- data/lib/lennarb/middleware_stack.rb +56 -0
- data/lib/lennarb/parameter_filter.rb +76 -0
- data/lib/lennarb/request.rb +51 -41
- data/lib/lennarb/request_handler.rb +39 -9
- data/lib/lennarb/response.rb +23 -21
- data/lib/lennarb/route_node.rb +17 -5
- data/lib/lennarb/routes.rb +43 -47
- data/lib/lennarb/version.rb +2 -2
- data/lib/lennarb.rb +22 -2
- data/logo/lennarb.svg +11 -0
- data/readme.md +176 -35
- metadata +36 -23
- data/lib/lennarb/constansts.rb +0 -6
- data/logo/lennarb.png +0 -0
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
|
-
#
|
8
|
-
#
|
9
|
-
gem "
|
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
|
@@ -28,7 +28,7 @@ Create a new file named `config.ru`:
|
|
28
28
|
require 'lennarb'
|
29
29
|
|
30
30
|
MyApp = Lennarb::App.new do
|
31
|
-
routes
|
31
|
+
routes do
|
32
32
|
get '/' do |req, res|
|
33
33
|
res.status = 200
|
34
34
|
res.html('<h1>Welcome to Lennarb!</h1>')
|
@@ -37,7 +37,7 @@ MyApp = Lennarb::App.new do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
MyApp.initialize!
|
40
|
-
run
|
40
|
+
run MyApp
|
41
41
|
```
|
42
42
|
|
43
43
|
Start the server:
|
@@ -71,7 +71,7 @@ app.get '/html' do |req, res|
|
|
71
71
|
end
|
72
72
|
|
73
73
|
app.get '/json' do |req, res|
|
74
|
-
res.json(
|
74
|
+
res.json({ message: "JSON response" })
|
75
75
|
end
|
76
76
|
```
|
77
77
|
|
@@ -94,7 +94,7 @@ Lennarb::App.new do
|
|
94
94
|
|
95
95
|
get '/users/:id' do |req, res|
|
96
96
|
user_id = req.params[:id]
|
97
|
-
res.json(
|
97
|
+
res.json({ id: user_id })
|
98
98
|
end
|
99
99
|
end
|
100
100
|
end
|
@@ -124,7 +124,7 @@ Lennarb is thread-safe by design:
|
|
124
124
|
### Initialization
|
125
125
|
|
126
126
|
```ruby
|
127
|
-
|
127
|
+
MyApp = Lennarb::App.new do
|
128
128
|
# Define routes
|
129
129
|
routes do
|
130
130
|
end
|
@@ -189,7 +189,7 @@ Default error responses:
|
|
189
189
|
```ruby
|
190
190
|
get '/api' do |req, res|
|
191
191
|
res.status = 200
|
192
|
-
res.json(
|
192
|
+
res.json({ status: "ok" })
|
193
193
|
end
|
194
194
|
```
|
195
195
|
|
@@ -200,7 +200,7 @@ Default error responses:
|
|
200
200
|
res.html('<h1>Web Page</h1>')
|
201
201
|
|
202
202
|
# JSON for APIs
|
203
|
-
res.json(
|
203
|
+
res.json({ data: "value" })
|
204
204
|
|
205
205
|
# Text for simple responses
|
206
206
|
res.text('Hello')
|
@@ -213,3 +213,7 @@ For help and bug reports, please visit:
|
|
213
213
|
- GitHub Issues: [lennarb/issues](https://github.com/aristotelesbr/lennarb/issues)
|
214
214
|
|
215
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.
|
data/lennarb.gemspec
CHANGED
@@ -31,10 +31,11 @@ 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"
|
34
|
+
spec.add_dependency "superconfig", "~> 3.0"
|
35
|
+
spec.add_dependency "logger", "~> 1.7"
|
35
36
|
spec.add_development_dependency "bundler"
|
36
|
-
spec.add_development_dependency "covered"
|
37
37
|
spec.add_development_dependency "simplecov"
|
38
|
+
spec.add_development_dependency "simplecov-json"
|
38
39
|
spec.add_development_dependency "minitest"
|
39
40
|
spec.add_development_dependency "minitest-utils"
|
40
41
|
spec.add_development_dependency "rack-test"
|
@@ -42,6 +43,5 @@ Gem::Specification.new do |spec|
|
|
42
43
|
spec.add_development_dependency "standard"
|
43
44
|
spec.add_development_dependency "standard-custom"
|
44
45
|
spec.add_development_dependency "standard-performance"
|
45
|
-
spec.add_development_dependency "
|
46
|
-
spec.add_development_dependency "debug"
|
46
|
+
spec.add_development_dependency "debug" if RUBY_ENGINE == "ruby"
|
47
47
|
end
|
data/lib/lennarb/app.rb
CHANGED
@@ -1,171 +1,252 @@
|
|
1
1
|
module Lennarb
|
2
|
+
# Main application class with hooks and helpers support
|
2
3
|
class App
|
3
|
-
|
4
|
-
|
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
|
5
12
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
11
20
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# @returns [Lennarb::Environment]
|
20
|
-
#
|
21
|
-
attr_reader :env
|
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
|
22
28
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
29
36
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
37
|
+
# Get routes for this app class
|
38
|
+
#
|
39
|
+
# @return [Routes] The routes instance
|
40
|
+
def routes
|
41
|
+
@routes ||= Routes.new
|
42
|
+
end
|
36
43
|
|
37
|
-
|
38
|
-
|
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
|
39
53
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
# @example
|
47
|
-
#
|
48
|
-
# class PostController
|
49
|
-
# extend Lennarb::Routes::Mixin
|
50
|
-
#
|
51
|
-
# get "/post/:id" do |req, res|
|
52
|
-
# res.text("Post ##{req.params[:id]}")
|
53
|
-
# end
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
# MyApp = Lennarb::App.new do
|
57
|
-
# routes do
|
58
|
-
# mount PostController
|
59
|
-
# end
|
60
|
-
#
|
61
|
-
def mount(*controllers)
|
62
|
-
controllers.each do |controller|
|
63
|
-
raise ArgumentError, "Controller must respond to :routes" unless controller.respond_to?(:routes)
|
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
|
64
60
|
|
65
|
-
|
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
|
66
83
|
end
|
67
84
|
end
|
68
85
|
|
69
|
-
#
|
70
|
-
|
71
|
-
#
|
72
|
-
#
|
73
|
-
# @example Run config on every environment
|
74
|
-
# app.config do
|
75
|
-
# mandatory :database_url, string
|
76
|
-
# end
|
77
|
-
#
|
78
|
-
# @example Run config on every a specific environment
|
79
|
-
# app.config :development do
|
80
|
-
# set :domain, "example.dev"
|
81
|
-
# end
|
82
|
-
#
|
83
|
-
# @example Run config on every a specific environment
|
84
|
-
# app.config :development, :test do
|
85
|
-
# set :domain, "example.dev"
|
86
|
-
# end
|
86
|
+
# Instance methods
|
87
|
+
|
88
|
+
# Initialize a new app
|
87
89
|
#
|
88
|
-
|
89
|
-
|
90
|
+
# @yield [self] Configuration block
|
91
|
+
def initialize(&block)
|
92
|
+
@initialized = false
|
93
|
+
@root = Pathname.pwd
|
94
|
+
@env = Environment.new(compute_env)
|
90
95
|
|
91
|
-
|
92
|
-
|
96
|
+
instance_eval(&block) if block_given?
|
97
|
+
end
|
93
98
|
|
94
|
-
|
99
|
+
# The current environment
|
100
|
+
attr_reader :env
|
95
101
|
|
96
|
-
|
97
|
-
|
102
|
+
# The root directory
|
103
|
+
attr_accessor :root
|
98
104
|
|
99
|
-
#
|
105
|
+
# Set environment
|
100
106
|
#
|
101
|
-
# @
|
102
|
-
#
|
103
|
-
|
104
|
-
|
105
|
-
|
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)
|
107
113
|
end
|
108
114
|
|
109
|
-
#
|
115
|
+
# Get the Rack app with middleware
|
110
116
|
#
|
117
|
+
# @return [#call] The Rack app
|
111
118
|
def app
|
112
119
|
@app ||= begin
|
113
|
-
|
120
|
+
handler = build_request_handler
|
121
|
+
stack = middleware.to_a
|
114
122
|
|
115
123
|
Rack::Builder.app do
|
116
|
-
|
124
|
+
stack.each { |middleware, args, block| use(middleware, *args, &block) }
|
125
|
+
run handler
|
117
126
|
end
|
118
127
|
end
|
119
128
|
end
|
120
129
|
|
121
|
-
#
|
130
|
+
# Rack interface method
|
122
131
|
#
|
123
|
-
|
124
|
-
|
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)
|
125
137
|
end
|
126
|
-
alias_method :mounted_apps, :controllers
|
127
138
|
|
128
|
-
#
|
139
|
+
# Define middleware
|
129
140
|
#
|
130
|
-
# @
|
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)
|
131
150
|
#
|
132
|
-
|
151
|
+
# @yield Block with helper definitions
|
152
|
+
# @return [Module] The helpers module
|
153
|
+
def helpers(&block)
|
154
|
+
self.class.helpers(&block)
|
155
|
+
end
|
133
156
|
|
134
|
-
#
|
157
|
+
# Define before hook (instance method)
|
135
158
|
#
|
136
|
-
# @
|
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)
|
137
166
|
#
|
138
|
-
|
139
|
-
|
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
|
140
172
|
|
141
|
-
|
142
|
-
|
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)
|
143
180
|
end
|
144
181
|
|
145
|
-
|
146
|
-
|
147
|
-
app.freeze
|
148
|
-
routes.freeze
|
182
|
+
self.class.routes
|
149
183
|
end
|
150
184
|
|
151
|
-
#
|
185
|
+
# Get/define configuration
|
152
186
|
#
|
153
|
-
# @
|
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
|
154
202
|
#
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
158
211
|
end
|
159
212
|
|
160
|
-
#
|
213
|
+
# Check if initialized
|
161
214
|
#
|
162
|
-
# @
|
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
|
163
226
|
#
|
164
|
-
# @
|
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
|
165
246
|
#
|
247
|
+
# @return [String] Environment name
|
166
248
|
private def compute_env
|
167
|
-
env = ENV_NAMES.map { ENV[
|
168
|
-
|
249
|
+
env = ENV_NAMES.map { |name| ENV[name] }.compact.first.to_s
|
169
250
|
env.empty? ? "development" : env
|
170
251
|
end
|
171
252
|
end
|