lennarb 1.3.0 → 1.4.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 +4 -4
- data/.devcontainer/Dockerfile +8 -0
- data/.devcontainer/devcontainer.json +20 -0
- data/.editorconfig +9 -0
- data/.github/workflows/coverage.yaml +58 -0
- data/.github/workflows/documentation.yaml +47 -0
- data/.github/workflows/main.yaml +27 -0
- data/.github/workflows/test.yaml +50 -0
- data/.gitignore +12 -0
- data/.standard.yml +23 -0
- data/.tool-versions +1 -0
- data/LICENCE +24 -0
- data/Rakefile +12 -0
- data/benchmark/memory.png +0 -0
- data/benchmark/rps.png +0 -0
- data/benchmark/runtime_with_startup.png +0 -0
- data/bin/console +8 -0
- data/bin/release +15 -0
- data/bin/setup +8 -0
- data/changelog.md +78 -7
- data/exe/lenna +2 -3
- data/gems.rb +29 -0
- data/guides/getting-started/readme.md +215 -0
- data/guides/links.yaml +6 -0
- data/guides/performance/readme.md +120 -0
- data/guides/response/readme.md +83 -0
- data/lennarb.gemspec +47 -0
- data/lib/lennarb/app.rb +172 -0
- data/lib/lennarb/config.rb +34 -0
- data/lib/lennarb/constansts.rb +6 -0
- data/lib/lennarb/environment.rb +76 -0
- data/lib/lennarb/request.rb +202 -31
- data/lib/lennarb/request_handler.rb +31 -0
- data/lib/lennarb/response.rb +16 -19
- data/lib/lennarb/route_node.rb +50 -13
- data/lib/lennarb/routes.rb +71 -0
- data/lib/lennarb/version.rb +2 -9
- data/lib/lennarb.rb +17 -155
- data/logo/lennarb.png +0 -0
- data/readme.md +32 -13
- metadata +147 -60
- data/lib/lennarb/plugin.rb +0 -49
- data/lib/lennarb/plugins/hooks.rb +0 -117
- data/lib/lennarb/plugins/mount.rb +0 -66
@@ -0,0 +1,215 @@
|
|
1
|
+
# Getting Started with Lennarb
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Lennarb is a minimalist, thread-safe Rack-based web framework for Ruby that focuses on simplicity and performance. It provides a clean routing DSL and straightforward request/response handling.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add Lennarb to your project's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'lennarb'
|
13
|
+
```
|
14
|
+
|
15
|
+
Or install it directly via RubyGems:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
gem install lennarb
|
19
|
+
```
|
20
|
+
|
21
|
+
## Quick Start
|
22
|
+
|
23
|
+
### Basic Application
|
24
|
+
|
25
|
+
Create a new file named `config.ru`:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'lennarb'
|
29
|
+
|
30
|
+
MyApp = Lennarb::App.new do
|
31
|
+
routes
|
32
|
+
get '/' do |req, res|
|
33
|
+
res.status = 200
|
34
|
+
res.html('<h1>Welcome to Lennarb!</h1>')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
MyApp.initialize!
|
40
|
+
run App
|
41
|
+
```
|
42
|
+
|
43
|
+
Start the server:
|
44
|
+
|
45
|
+
```bash
|
46
|
+
rackup
|
47
|
+
```
|
48
|
+
|
49
|
+
Your application will be available at `http://localhost:9292`.
|
50
|
+
|
51
|
+
## Core Concepts
|
52
|
+
|
53
|
+
### Request Handling
|
54
|
+
|
55
|
+
Each route handler receives two arguments:
|
56
|
+
|
57
|
+
- `req`: A Request object wrapping the Rack environment
|
58
|
+
- `res`: A Response object for building the HTTP response
|
59
|
+
|
60
|
+
### Response Types
|
61
|
+
|
62
|
+
Lennarb provides three main response helpers:
|
63
|
+
|
64
|
+
```rb
|
65
|
+
app.get '/text' do |req, res|
|
66
|
+
res.text('Plain text response')
|
67
|
+
end
|
68
|
+
|
69
|
+
app.get '/html' do |req, res|
|
70
|
+
res.html('<h1>HTML response</h1>')
|
71
|
+
end
|
72
|
+
|
73
|
+
app.get '/json' do |req, res|
|
74
|
+
res.json('{"message": "JSON response"}')
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### Redirects
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
app.get '/redirect' do |req, res|
|
82
|
+
res.redirect('/new-location', 302) # 302
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
Routes are defined using HTTP method helpers:
|
87
|
+
|
88
|
+
```ruby
|
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
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
### Route Parameters
|
104
|
+
|
105
|
+
Parameters from dynamic route segments are available in `req.params`:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
get '/hello/:name' do |req, res|
|
109
|
+
name = req.params[:name]
|
110
|
+
res.text("Hello, #{name}!")
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
## Thread Safety
|
115
|
+
|
116
|
+
Lennarb is thread-safe by design:
|
117
|
+
|
118
|
+
- All request processing is synchronized using a mutex
|
119
|
+
- The router tree is frozen after initialization
|
120
|
+
- Response objects are created per-request
|
121
|
+
|
122
|
+
## Application Life-cycle
|
123
|
+
|
124
|
+
### Initialization
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
Myapp = Lennarb::App.new do
|
128
|
+
# Define routes
|
129
|
+
routes do
|
130
|
+
end
|
131
|
+
|
132
|
+
# Define Configurations
|
133
|
+
config do
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Initialize and freeze the application
|
138
|
+
MyApp.initialize!
|
139
|
+
```
|
140
|
+
|
141
|
+
The `initialize!` method:
|
142
|
+
|
143
|
+
- Loads environment-specific dependencies
|
144
|
+
- Freezes the route tree
|
145
|
+
- Freezes the Rack application
|
146
|
+
|
147
|
+
### Environment
|
148
|
+
|
149
|
+
Lennarb uses the `LENNA_ENV` environment variable (defaults to "development"):
|
150
|
+
|
151
|
+
It can be set using the following environment variables:
|
152
|
+
|
153
|
+
- `LENNA_ENV`
|
154
|
+
- `APP_ENV`
|
155
|
+
- `RACK_ENV`
|
156
|
+
|
157
|
+
```bash
|
158
|
+
LENNA_ENV=production rackup
|
159
|
+
```
|
160
|
+
|
161
|
+
## Error Handling
|
162
|
+
|
163
|
+
Lennarb provides basic error handling:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
get '/api' do |req, res|
|
167
|
+
# Errors are caught and return 500 with error message
|
168
|
+
raise "Something went wrong"
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
Default error responses:
|
173
|
+
|
174
|
+
- 404 for unmatched routes
|
175
|
+
- 500 for application errors
|
176
|
+
|
177
|
+
## Best Practices
|
178
|
+
|
179
|
+
1. **Always call initialize!**
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
app = Lennarb::App.new
|
183
|
+
app.initialize!
|
184
|
+
run app
|
185
|
+
```
|
186
|
+
|
187
|
+
2. **Set response status**
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
get '/api' do |req, res|
|
191
|
+
res.status = 200
|
192
|
+
res.json('{"status": "ok"}')
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
3. **Use appropriate response types**
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
# HTML for web pages
|
200
|
+
res.html('<h1>Web Page</h1>')
|
201
|
+
|
202
|
+
# JSON for APIs
|
203
|
+
res.json('{"data": "value"}')
|
204
|
+
|
205
|
+
# Text for simple responses
|
206
|
+
res.text('Hello')
|
207
|
+
```
|
208
|
+
|
209
|
+
## Support
|
210
|
+
|
211
|
+
For help and bug reports, please visit:
|
212
|
+
|
213
|
+
- GitHub Issues: [lennarb/issues](https://github.com/aristotelesbr/lennarb/issues)
|
214
|
+
|
215
|
+
Now you can run your app!
|
data/guides/links.yaml
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# Performance
|
2
|
+
|
3
|
+
The **Lennarb** is very fast. The following benchmarks were performed on a MacBook Pro (Retina, 13-inch, Early 2013) with 2,7 GHz Intel Core i7 and 8 GB 1867 MHz DDR3. Based on [jeremyevans/r10k](https://github.com/jeremyevans/r10k) using the following. All tests are performed using the **Ruby 3.3.0**
|
4
|
+
|
5
|
+
## Benchmark results
|
6
|
+
|
7
|
+
This document contains the benchmarks comparing **Lennarb** with other routers based on Rack. Metrics evaluated include Requests per Second, Initial memory usage and Startup time.
|
8
|
+
|
9
|
+
### 1. Requests per Second (RPS)
|
10
|
+
|
11
|
+

|
12
|
+
|
13
|
+
| Position | Application | 10 RPS | 100 RPS | 1.000 RPS | 10.000 RPS |
|
14
|
+
| -------- | ----------- | ---------- | ---------- | --------- | ---------- |
|
15
|
+
| 1 | Lenna | 126.252,36 | 108.086,55 | 87.111,91 | 68.460,64 |
|
16
|
+
| 2 | Roda | 123.360,37 | 88.380,56 | 66.990,77 | 48.108,29 |
|
17
|
+
| 3 | Syro | 114.105,38 | 80.909,39 | 61.415,86 | 46.639,81 |
|
18
|
+
| 4 | Hanami-API | 68.089,18 | 52.851,88 | 40.801,78 | 27.996,00 |
|
19
|
+
|
20
|
+
This table ranks the routers by the number of requests they can process per second. Higher numbers indicate better performance.
|
21
|
+
|
22
|
+
### 2. Initial memory usage (in KB)
|
23
|
+
|
24
|
+

|
25
|
+
|
26
|
+
| Position | Application | 10 KB | 100 KB | 1.000 KB | 10.000 KB |
|
27
|
+
| -------- | ----------- | ------ | ------ | -------- | --------- |
|
28
|
+
| 1 | Syro | 12,160 | 12,544 | 16,460 | 49,692 |
|
29
|
+
| 2 | Lenna | 14,464 | 14,720 | 18,232 | 56,812 |
|
30
|
+
| 3 | Roda | 15,104 | 15,104 | 18,220 | 49,900 |
|
31
|
+
| 4 | Hanami-API | 15,744 | 16,128 | 20,888 | 64,824 |
|
32
|
+
|
33
|
+
This table shows the initial memory usage in KB. Lower values indicate lower memory consumption.
|
34
|
+
|
35
|
+
### 3. Startup time (in seconds)
|
36
|
+
|
37
|
+

|
38
|
+
|
39
|
+
| Position | Application | 10 seg | 100 seg | 1.000 seg | 10.000 seg |
|
40
|
+
| -------- | ----------- | ------ | ------- | --------- | ---------- |
|
41
|
+
| 1 | Syro | 0.274 | 0.347 | 0.455 | 0.997 |
|
42
|
+
| 2 | Lenna | 0.289 | 0.312 | 0.393 | 0.914 |
|
43
|
+
| 3 | Roda | 0.294 | 0.378 | 0.467 | 0.918 |
|
44
|
+
| 4 | Hanami-API | 0.445 | 0.550 | 0.808 | 3.074 |
|
45
|
+
|
46
|
+
This table shows the startup time in seconds. Lower values indicate faster startup times.
|
47
|
+
|
48
|
+
## Graphs
|
49
|
+
|
50
|
+
See the graphs in the `benchmarks` directory of the lennarb project.
|
51
|
+
|
52
|
+
## Steps to run the benchmarks
|
53
|
+
|
54
|
+
### 1. Install the router gem you want to test
|
55
|
+
|
56
|
+
```bash
|
57
|
+
gem install lennarb
|
58
|
+
gem install syro
|
59
|
+
gem install roda
|
60
|
+
```
|
61
|
+
|
62
|
+
### 2. Clone the jeremyevans/r10k repository
|
63
|
+
|
64
|
+
```bash
|
65
|
+
git clone https://github.com/jeremyevans/r10k
|
66
|
+
```
|
67
|
+
|
68
|
+
### 3. Create a new file in the `r10k` directory
|
69
|
+
|
70
|
+
In the `r10k` directory, create a new file called `lennarb.rb` into `builders` directory with the code below:
|
71
|
+
|
72
|
+
```bash
|
73
|
+
touch r10k/builders/lennarb.rb
|
74
|
+
```
|
75
|
+
|
76
|
+
Put the code below into `lennarb.rb` file:
|
77
|
+
|
78
|
+
```rb
|
79
|
+
# frozen_string_literal: true
|
80
|
+
|
81
|
+
# Released under the MIT License.
|
82
|
+
# Copyright, 2024, by Aristóteles Coutinho.
|
83
|
+
|
84
|
+
lennarb_routes =
|
85
|
+
lambda do |f, level, prefix, calc_path, lvars|
|
86
|
+
base = BASE_ROUTE.dup
|
87
|
+
ROUTES_PER_LEVEL.times do
|
88
|
+
route = "#{prefix}#{base}"
|
89
|
+
if level == 1
|
90
|
+
params = lvars.map { |lvar| "\#{req.params[:#{lvar}]}" }
|
91
|
+
.join('-')
|
92
|
+
f.puts " app.get '#{route}/:#{lvars.last}' do |req, res|"
|
93
|
+
f.puts " body = \"#{calc_path[1..]}#{base}-#{params}\""
|
94
|
+
f.puts ' res.html body'
|
95
|
+
f.puts ' end'
|
96
|
+
else
|
97
|
+
lennarb_routes.call(f, level - 1, "#{route}/:#{lvars.last}/", "#{calc_path}#{base}/", lvars + [lvars.last.succ])
|
98
|
+
end
|
99
|
+
base.succ!
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
File.open("#{File.dirname(__FILE__)}/../apps/lennarb_#{LEVELS}_#{ROUTES_PER_LEVEL}.rb", 'wb') do |f|
|
104
|
+
f.puts '# frozen_string_literal: true'
|
105
|
+
f.puts "require 'lennarb'"
|
106
|
+
f.puts 'app = Lennarb.new'
|
107
|
+
lennarb_routes.call(f, LEVELS, '/', '/', ['a'])
|
108
|
+
f.puts 'App = app'
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### 4. Run the benchmarks
|
113
|
+
|
114
|
+
```bash
|
115
|
+
bundle exec rake bench graphs R10K_APPS="lennarb syro roda"
|
116
|
+
```
|
117
|
+
|
118
|
+
## Conclusion
|
119
|
+
|
120
|
+
These numbers are just a small reference, **Lennarb** is not a framework, it is a router. In my opinion, **Roda** is the best router for Ruby because it has many interesting features, such as a middleware manager, and very good development performance.
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Response
|
2
|
+
|
3
|
+
This is the response guide.
|
4
|
+
The `res` object is used to send a response to the client. The Lennarb use a custom response object to send responses to the client. The `res` object is an instance of {Lennarb::Response}.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
You can use the `res` object to send a response to the client.
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
# app.rb
|
12
|
+
|
13
|
+
get '/' do |req, res|
|
14
|
+
res.html 'Hello World'
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
## Content Types
|
19
|
+
|
20
|
+
Lennarb supports the following content types:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
# app.rb
|
24
|
+
|
25
|
+
get '/' do |req, res|
|
26
|
+
res.html 'Hello World'
|
27
|
+
res.json '{"message": "Hello World"}'
|
28
|
+
res.text 'Hello World'
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
But you can also set your own content type:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
res['content-type'] = 'text/markdown'
|
36
|
+
res.write '# Hello World'
|
37
|
+
```
|
38
|
+
|
39
|
+
## The write method
|
40
|
+
|
41
|
+
You can use the `res.write` method to write to the response body:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# app.rb
|
45
|
+
|
46
|
+
get '/' do |req, res|
|
47
|
+
res.write 'Hello World'
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
JSON example:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
# app.rb
|
55
|
+
|
56
|
+
post '/posts' do |req, res|
|
57
|
+
req.params # => { name: 'Lenna' }
|
58
|
+
name = req.params[:name]
|
59
|
+
|
60
|
+
res.write({ data: { name: } }.to_json) # This will write to the response body
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
## Status Codes
|
65
|
+
|
66
|
+
You can set the status code using the `res.status` method:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
res.status 200
|
70
|
+
```
|
71
|
+
|
72
|
+
## Redirects
|
73
|
+
|
74
|
+
You can redirect the client using the `res.redirect` method:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# app.ruby
|
78
|
+
|
79
|
+
get '/' do |req, res|
|
80
|
+
# Stuff code here...
|
81
|
+
res.redirect '/hello'
|
82
|
+
end
|
83
|
+
```
|
data/lennarb.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative "lib/lennarb/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "lennarb"
|
5
|
+
spec.version = Lennarb::VERSION
|
6
|
+
|
7
|
+
spec.summary = <<~DESC
|
8
|
+
Lennarb provides a lightweight yet robust solution for web routing in Ruby, focusing on performance and simplicity.
|
9
|
+
DESC
|
10
|
+
spec.authors = ["Aristóteles Coutinho"]
|
11
|
+
spec.license = "MIT"
|
12
|
+
spec.homepage = "https://aristotelesbr.github.io/lennarb"
|
13
|
+
spec.metadata = {
|
14
|
+
"allowed_push_host" => "https://rubygems.org",
|
15
|
+
"changelog_uri" => "https://github.com/aristotelesbr/lennarb/blob/master/changelog.md",
|
16
|
+
"homepage_uri" => "https://aristotelesbr.github.io/lennarb",
|
17
|
+
"rubygems_mfa_required" => "true",
|
18
|
+
"source_code_uri" => "https://github.com/aristotelesbr/lennarb"
|
19
|
+
}
|
20
|
+
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
+
`git ls-files -z`
|
23
|
+
.split("\x0")
|
24
|
+
.reject { |f| f.match(%r{^(test|features)/}) }
|
25
|
+
end
|
26
|
+
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = ["lenna"]
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_dependency "bigdecimal"
|
32
|
+
spec.add_dependency "colorize", "~> 1.1"
|
33
|
+
spec.add_dependency "rack", "~> 3.1"
|
34
|
+
spec.add_dependency "superconfig"
|
35
|
+
spec.add_development_dependency "bundler"
|
36
|
+
spec.add_development_dependency "covered"
|
37
|
+
spec.add_development_dependency "simplecov"
|
38
|
+
spec.add_development_dependency "minitest"
|
39
|
+
spec.add_development_dependency "minitest-utils"
|
40
|
+
spec.add_development_dependency "rack-test"
|
41
|
+
spec.add_development_dependency "rake"
|
42
|
+
spec.add_development_dependency "standard"
|
43
|
+
spec.add_development_dependency "standard-custom"
|
44
|
+
spec.add_development_dependency "standard-performance"
|
45
|
+
spec.add_development_dependency "m"
|
46
|
+
spec.add_development_dependency "debug"
|
47
|
+
end
|
data/lib/lennarb/app.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
module Lennarb
|
2
|
+
class App
|
3
|
+
# This error is raised whenever the app is initialized more than once.
|
4
|
+
AlreadyInitializedError = Class.new(StandardError)
|
5
|
+
|
6
|
+
# The root app directory of the app.
|
7
|
+
#
|
8
|
+
# @returns [Pathname]
|
9
|
+
#
|
10
|
+
attr_accessor :root
|
11
|
+
|
12
|
+
# The current environment. Defaults to "development".
|
13
|
+
# It can be set using the following environment variables:
|
14
|
+
#
|
15
|
+
# - `LENNA_ENV`
|
16
|
+
# - `APP_ENV`
|
17
|
+
# - `RACK_ENV`
|
18
|
+
#
|
19
|
+
# @returns [Lennarb::Environment]
|
20
|
+
#
|
21
|
+
attr_reader :env
|
22
|
+
|
23
|
+
def initialize(&)
|
24
|
+
@initialized = false
|
25
|
+
self.root = Pathname.pwd
|
26
|
+
self.env = compute_env
|
27
|
+
instance_eval(&) if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set the current environment. See {Lennarb::Environment} for more details.
|
31
|
+
#
|
32
|
+
# @parameter [Hash] env
|
33
|
+
#
|
34
|
+
def env=(env)
|
35
|
+
raise AlreadyInitializedError if initialized?
|
36
|
+
|
37
|
+
@env = Environment.new(env)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Mount an app at a specific path.
|
41
|
+
#
|
42
|
+
# @parameter [Object] The controller|app to mount.
|
43
|
+
#
|
44
|
+
# @returns [void]
|
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)
|
64
|
+
|
65
|
+
self.controllers << controller
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Define the app's configuration. See {Lennarb::Config}.
|
70
|
+
#
|
71
|
+
# @returns [Lennarb::Config]
|
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
|
87
|
+
#
|
88
|
+
def config(*envs, &)
|
89
|
+
@config ||= Config.new
|
90
|
+
|
91
|
+
write = block_given? &&
|
92
|
+
(envs.map(&:to_sym).include?(env.to_sym) || envs.empty?)
|
93
|
+
|
94
|
+
@config.instance_eval(&) if write
|
95
|
+
|
96
|
+
@config
|
97
|
+
end
|
98
|
+
|
99
|
+
# Define the app's route. See {Lennarb::RouteNode} for more details.
|
100
|
+
#
|
101
|
+
# @returns [Lennarb::RouteNode]
|
102
|
+
#
|
103
|
+
def routes(&)
|
104
|
+
@routes ||= Routes.new
|
105
|
+
@routes.instance_eval(&) if block_given?
|
106
|
+
@routes
|
107
|
+
end
|
108
|
+
|
109
|
+
# The Rack app.
|
110
|
+
#
|
111
|
+
def app
|
112
|
+
@app ||= begin
|
113
|
+
request_handler = RequestHandler.new(self)
|
114
|
+
|
115
|
+
Rack::Builder.app do
|
116
|
+
run request_handler
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Store mounted app's
|
122
|
+
#
|
123
|
+
def controllers
|
124
|
+
@controllers ||= []
|
125
|
+
end
|
126
|
+
alias_method :mounted_apps, :controllers
|
127
|
+
|
128
|
+
# Check if the app is initialized.
|
129
|
+
#
|
130
|
+
# @returns [Boolean]
|
131
|
+
#
|
132
|
+
def initialized? = @initialized
|
133
|
+
|
134
|
+
# Initialize the app.
|
135
|
+
#
|
136
|
+
# @returns [void]
|
137
|
+
#
|
138
|
+
def initialize!
|
139
|
+
raise AlreadyInitializedError if initialized?
|
140
|
+
|
141
|
+
controllers.each do
|
142
|
+
routes.store.merge!(it.routes.store)
|
143
|
+
end
|
144
|
+
|
145
|
+
@initialized = true
|
146
|
+
|
147
|
+
app.freeze
|
148
|
+
routes.freeze
|
149
|
+
end
|
150
|
+
|
151
|
+
# Call the app.
|
152
|
+
#
|
153
|
+
# @parameter [Hash] env
|
154
|
+
#
|
155
|
+
def call(env)
|
156
|
+
env[RACK_LENNA_APP] = self
|
157
|
+
Dir.chdir(root) { return app.call(env) }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Compute the current environment.
|
161
|
+
#
|
162
|
+
# @returns [String]
|
163
|
+
#
|
164
|
+
# @private
|
165
|
+
#
|
166
|
+
private def compute_env
|
167
|
+
env = ENV_NAMES.map { ENV[_1] }.compact.first.to_s
|
168
|
+
|
169
|
+
env.empty? ? "development" : env
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Lennarb
|
2
|
+
# The configuration for the application.
|
3
|
+
# It uses {https://rubygems.org/gems/superconfig SuperConfig} to define the
|
4
|
+
# configuration.
|
5
|
+
class Config < SuperConfig::Base
|
6
|
+
MissingEnvironmentVariable = Class.new(StandardError)
|
7
|
+
MissingCallable = Class.new(StandardError)
|
8
|
+
|
9
|
+
undef_method :credential
|
10
|
+
|
11
|
+
def initialize(**)
|
12
|
+
block = proc { true }
|
13
|
+
super(**, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @private
|
17
|
+
def to_s = "#<Lennarb::Config>"
|
18
|
+
|
19
|
+
# @private
|
20
|
+
def mandatory(*, **)
|
21
|
+
super
|
22
|
+
rescue SuperConfig::MissingEnvironmentVariable => error
|
23
|
+
raise MissingEnvironmentVariable, error.message
|
24
|
+
end
|
25
|
+
|
26
|
+
# @private
|
27
|
+
def property(*, **, &)
|
28
|
+
super
|
29
|
+
rescue SuperConfig::MissingCallable
|
30
|
+
raise MissingCallable,
|
31
|
+
"arg[1] must respond to #call or a block must be provided"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|