lennarb 0.1.7 → 0.4.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: b3b1fe759bb5de90ef757201a4023c441a9c40146f31a318aecc5c5c08cd04bb
4
- data.tar.gz: 8137ee838ca5636d7013cbfa5aa5f7e83d66e3b28d3387255b702d82becd884a
3
+ metadata.gz: 9dfa4cf1e25284df8669a9fdd9faf45806575137b0b2a2cd131e4af205426176
4
+ data.tar.gz: 879b617cb51177961a055e2dfd069437e9cae0c7e781fb50aad410c14b271dd8
5
5
  SHA512:
6
- metadata.gz: 3216bc56c89485d3292b621dcdfb26082957bea1ecfc622107b2f631f86508d7eb5318ce5bc43889ccf0db59775dcefd34e7e4ac00f5b023f34c4f371bab83b9
7
- data.tar.gz: 944b432173eeeee78c2fc8d546ee74f6768ab3581a61babfdad27f8e54969afd96581259d8866126a3e6e26c2418f92355af8cc07d3aa215b0f9fb9a6bceccc0
6
+ metadata.gz: '087d53fc3627a7c61f028826f0af5ea8fc26e6fb02a2a461c54154852bbc9b71d9d65becf7faf5451e81612eeb9129b307002861aac49b3e85ecc1dab4e6a6de'
7
+ data.tar.gz: 74d0156c558d8619e66b2daa5d5449216d064b38c511efb01cea802397efc2323b149e71cb1a3756af591c58b10510aef48dcc77ec40e20cbbae6f2a350a38a7
data/changelog.md CHANGED
@@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.0] - 2024-07-02
9
+
10
+ ### Added
11
+
12
+ - Add `Lennarb::ApplicationBase` class to be the base class of the `Lennarb` class. Now, the `Lennarb` class is a subclass of `Lennarb::ApplicationBase` class.
13
+
14
+ That permits to create a new application with the `Lennarb::ApplicationBase` class and use http methods to create the routes. Ex.
15
+
16
+ ```rb
17
+ # app.rb
18
+
19
+ require 'lennarb'
20
+
21
+ class MyApp
22
+ include Lennarb::ApplicationBase
23
+
24
+ get '/hello' do |req, res|
25
+ res.html('Hello World')
26
+ end
27
+ end
28
+ ```
29
+
30
+ ### Change
31
+
32
+ - Change the test/test_lenna.rb to test/test_lennarb.rb
33
+ - Change `add_route` method from `Lennarb` class to `__add_route` and remove from private section.
34
+
35
+ ## [0.2.0] - 2024-08-01
36
+
37
+ ### Removed
38
+
39
+ - Remove `zeitwerk` gem to load the files in the project.
40
+ - Remove `console` gem to print the logs in the console.
41
+ - Remove `Lenna` module. Now, the `Lennarb` class is the main class of the project.
42
+ - Remove `Middleware` module.
43
+ - Remove `CLI` module.
44
+ - Remove `Cache` module
45
+
46
+ ### Changed
47
+
48
+ - Change `Lennarb::Application` class to `Lennarb` class.
49
+ - Request class and Response class now are in `Lennarb` class
50
+ - Change `Lennarb::Router` class to `Lennarb` class
51
+
52
+ ### Fixed
53
+
54
+ - Improve performance of the RPS (Requests per second), memory and CPU usage. Now the performance is similar to the [Roda](https://github.com/jeremyevans/roda/tree/master).
55
+
8
56
  ## [0.1.7] - 2023-23-12
9
57
 
10
58
  ### Added
data/exe/lenna CHANGED
@@ -10,8 +10,6 @@
10
10
  #
11
11
  require 'lennarb'
12
12
 
13
- # Call the CLI to start the server
13
+ # Show the version
14
14
  #
15
- # @private `Since v0.1`
16
- #
17
- Lenna::Cli::App.run!(ARGV)
15
+ puts Lennarb::VERSION
@@ -0,0 +1,37 @@
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
+ class Request < Rack::Request
8
+ # Initialize the request object
9
+ #
10
+ # @parameter [Hash] env
11
+ # @parameter [Hash] route_params
12
+ #
13
+ # @returns [Request]
14
+ #
15
+ def initialize(env, route_params = {})
16
+ super(env)
17
+ @route_params = route_params
18
+ end
19
+
20
+ # Get the request body
21
+ #
22
+ # @returns [String]
23
+ def params
24
+ @params ||= super.merge(@route_params)
25
+ end
26
+
27
+ private
28
+
29
+ # Get the query string
30
+ #
31
+ # @returns [String]
32
+ #
33
+ def query_params
34
+ @query_params ||= Rack::Utils.parse_nested_query(query_string)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,138 @@
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
+ class Response
8
+ # @!attribute [rw] status
9
+ # @returns [Integer]
10
+ #
11
+ attr_accessor :status
12
+
13
+ # @!attribute [r] body
14
+ # @returns [Array]
15
+ #
16
+ attr_reader :body
17
+
18
+ # @!attribute [r] headers
19
+ # @returns [Hash]
20
+ #
21
+ attr_reader :headers
22
+
23
+ # @!attribute [r] length
24
+ # @returns [Integer]
25
+ #
26
+ attr_reader :length
27
+
28
+ # Constants
29
+ #
30
+ LOCATION = 'location'
31
+ private_constant :LOCATION
32
+
33
+ CONTENT_TYPE = 'content-type'
34
+ private_constant :CONTENT_TYPE
35
+
36
+ CONTENT_LENGTH = 'content-length'
37
+ private_constant :CONTENT_LENGTH
38
+
39
+ ContentType = { HTML: 'text/html', TEXT: 'text/plain', JSON: 'application/json' }.freeze
40
+ private_constant :ContentType
41
+
42
+ # Initialize the response object
43
+ #
44
+ # @returns [Response]
45
+ #
46
+ def initialize
47
+ @status = 404
48
+ @headers = {}
49
+ @body = []
50
+ @length = 0
51
+ end
52
+
53
+ # Set the response header
54
+ #
55
+ # @parameter [String] key
56
+ #
57
+ # @returns [String] value
58
+ #
59
+ def [](key)
60
+ @headers[key]
61
+ end
62
+
63
+ # Get the response header
64
+ #
65
+ # @parameter [String] key
66
+ # @parameter [String] value
67
+ #
68
+ # @returns [String] value
69
+ #
70
+ def []=(key, value)
71
+ @headers[key] = value
72
+ end
73
+
74
+ # Write to the response body
75
+ #
76
+ # @parameter [String] str
77
+ #
78
+ # @returns [String] str
79
+ #
80
+ def write(str)
81
+ str = str.to_s
82
+ @length += str.bytesize
83
+ @headers[CONTENT_LENGTH] ||= @length.to_s
84
+ @body << str
85
+ end
86
+
87
+ # Set the response type to text
88
+ #
89
+ # @parameter [String] str
90
+ #
91
+ # @returns [String] str
92
+ #
93
+ def text(str)
94
+ @headers[CONTENT_TYPE] = ContentType[:TEXT]
95
+ write(str)
96
+ end
97
+
98
+ # Set the response type to html
99
+ #
100
+ # @parameter [String] str
101
+ #
102
+ # @returns [String] str
103
+ #
104
+ def html(str)
105
+ @headers[CONTENT_TYPE] = ContentType[:HTML]
106
+ write(str)
107
+ end
108
+
109
+ # Set the response type to json
110
+ #
111
+ # @parameter [String] str
112
+ #
113
+ # @returns [String] str
114
+ #
115
+ def json(str)
116
+ @headers[CONTENT_TYPE] = ContentType[:JSON]
117
+ write(str)
118
+ end
119
+
120
+ # Redirect the response
121
+ #
122
+ # @parameter [String] path
123
+ # @parameter [Integer] status, default: 302
124
+ #
125
+ def redirect(path, status = 302)
126
+ @headers[LOCATION] = path
127
+ @status = status
128
+ end
129
+
130
+ # Finish the response
131
+ #
132
+ # @returns [Array] response
133
+ #
134
+ def finish
135
+ [@status, @headers, @body]
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,73 @@
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
+ class RouteNode
8
+ attr_accessor :children, :blocks, :param_key
9
+
10
+ # Initialize the route node
11
+ #
12
+ # @return [RouteNode]
13
+ #
14
+ def initialize
15
+ @children = {}
16
+ @blocks = {}
17
+ @param_key = nil
18
+ end
19
+
20
+ # Add a route to the route node
21
+ #
22
+ # @parameter [Array] parts
23
+ # @parameter [String] http_method
24
+ # @parameter [Proc] block
25
+ #
26
+ # @return [void]
27
+ #
28
+ def add_route(parts, http_method, block)
29
+ current_node = self
30
+
31
+ parts.each do |part|
32
+ if part.start_with?(':')
33
+ key = :param
34
+ current_node.children[key] ||= RouteNode.new
35
+ current_node = current_node.children[key]
36
+ current_node.param_key = part[1..].to_sym
37
+ else
38
+ key = part
39
+ current_node.children[key] ||= RouteNode.new
40
+ current_node = current_node.children[key]
41
+ end
42
+ end
43
+
44
+ current_node.blocks[http_method] = block
45
+ end
46
+
47
+ # Match a route to the route node
48
+ #
49
+ # @parameter [Array] parts
50
+ # @parameter [String] http_method
51
+ #
52
+ # @return [Array]
53
+ #
54
+ def match_route(parts, http_method)
55
+ current_node = self
56
+ params = {}
57
+
58
+ parts.each do |part|
59
+ return [nil, nil] unless current_node.children.key?(part) || current_node.children[:param]
60
+
61
+ if current_node.children.key?(part)
62
+ current_node = current_node.children[part]
63
+ else
64
+ param_node = current_node.children[:param]
65
+ params[param_node.param_key] = part
66
+ current_node = param_node
67
+ end
68
+ end
69
+
70
+ [current_node.blocks[http_method], params]
71
+ end
72
+ end
73
+ end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Aristóteles Coutinho.
4
+ # Copyright, 2023-2024, by Aristóteles Coutinho.
5
5
 
6
- module Lennarb
7
- VERSION = '0.1.7'
6
+ class Lennarb
7
+ VERSION = '0.4.0'
8
8
 
9
9
  public_constant :VERSION
10
10
  end
data/lib/lennarb.rb CHANGED
@@ -1,46 +1,139 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Aristóteles Coutinho.
4
+ # Copyright, 2023-2024, by Aristóteles Coutinho.
5
5
 
6
6
  ENV['RACK_ENV'] ||= 'development'
7
7
 
8
- # Extension for Array class
8
+ # Core extensions
9
9
  #
10
- require 'lennarb/array_extensions'
10
+ require 'pathname'
11
+ require 'rack'
11
12
 
12
13
  # Base class for Lennarb
13
14
  #
14
- require 'lenna/application'
15
- require 'lennarb/version'
15
+ require_relative 'lennarb/request'
16
+ require_relative 'lennarb/response'
17
+ require_relative 'lennarb/route_node'
18
+ require_relative 'lennarb/version'
16
19
 
17
- # Core extensions
18
- #
19
- require 'pathname'
20
+ class Lennarb
21
+ # Error class
22
+ #
23
+ class LennarbError < StandardError; end
20
24
 
21
- # Zeitwerk
22
- #
23
- require 'zeitwerk'
25
+ # @attribute [r] root
26
+ # @returns [RouteNode]
27
+ #
28
+ attr_reader :root
24
29
 
25
- # Zeitwerk loader
26
- #
27
- Zeitwerk::Loader.new.tap do |loader|
28
- loader.inflector.inflect('Version' => 'VERSION')
29
- loader.push_dir(__dir__)
30
- loader.setup
31
- end
30
+ # Initialize the application
31
+ #
32
+ # @yield { ... } The application
33
+ #
34
+ # @returns [Lennarb]
35
+ #
36
+ def initialize
37
+ @root = RouteNode.new
38
+ yield self if block_given?
39
+ end
32
40
 
33
- # Lennarb module
34
- #
35
- module Lennarb
36
- module_function
41
+ # Split a path into parts
42
+ #
43
+ # @parameter [String] path
44
+ #
45
+ # @returns [Array] parts. Ex. ['users', ':id']
46
+ #
47
+ SplitPath = ->(path) { path.split('/').reject(&:empty?) }
48
+ private_constant :SplitPath
37
49
 
38
- # Lennarb root path
50
+ # Call the application
51
+ #
52
+ # @parameter [Hash] env
39
53
  #
40
- # @return [Pathname] the root path
54
+ # @returns [Array] response
41
55
  #
42
- def root
43
- File.expand_path('..', __dir__)
44
- Pathname.new(File.expand_path('..', __dir__))
56
+ def call(env)
57
+ http_method = env.fetch('REQUEST_METHOD').to_sym
58
+ parts = SplitPath[env.fetch('PATH_INFO')]
59
+
60
+ block, params = @root.match_route(parts, http_method)
61
+ return [404, { 'content-type' => 'text/plain' }, ['Not Found']] unless block
62
+
63
+ res = Response.new
64
+ req = Request.new(env, params)
65
+ instance_exec(req, res, &block)
66
+
67
+ res.finish
68
+ end
69
+
70
+ # Add a routes
71
+ #
72
+ # @parameter [String] path
73
+ # @parameter [Proc] block
74
+ #
75
+ # @returns [void]
76
+ #
77
+ def get(path, &block) = __add_route__(path, :GET, block)
78
+ def post(path, &block) = __add_route__(path, :POST, block)
79
+ def put(path, &block) = __add_route__(path, :PUT, block)
80
+ def patch(path, &block) = __add_route__(path, :PATCH, block)
81
+ def delete(path, &block) = __add_route__(path, :DELETE, block)
82
+
83
+ # Add a route
84
+ #
85
+ # @parameter [String] path
86
+ # @parameter [String] http_method
87
+ # @parameter [Proc] block
88
+ #
89
+ # @returns [void]
90
+ #
91
+ def __add_route__(path, http_method, block)
92
+ parts = SplitPath[path]
93
+ @root.add_route(parts, http_method, block)
94
+ end
95
+
96
+ # Base module for the application. The main purpose is to include the class methods
97
+ # and call the Lennarb instance.
98
+ #
99
+ module ApplicationBase
100
+ # Include the class methods
101
+ #
102
+ # @parameter [Class] base
103
+ #
104
+ # @returns [void]
105
+ #
106
+ def self.included(base) = base.extend(ClassMethods)
107
+
108
+ # Call the Lennarb instance
109
+ #
110
+ # @parameter [Hash] env
111
+ #
112
+ # @returns [Array]
113
+ #
114
+ def call(env) = self.class.lennarb_instance.call(env)
115
+
116
+ # Class methods
117
+ #
118
+ module ClassMethods
119
+ # Get the Lennarb instance
120
+ #
121
+ # @returns [Lennarb]
122
+ #
123
+ def lennarb_instance = @lennarb_instance ||= Lennarb.new
124
+
125
+ # Add a route
126
+ #
127
+ # @parameter [String] path
128
+ # @parameter [Proc] block
129
+ #
130
+ # @returns [void]
131
+ #
132
+ def get(path, &block) = lennarb_instance.__add_route__(path, :GET, block)
133
+ def put(path, &block) = lennarb_instance.__add_route__(path, :PUT, block)
134
+ def post(path, &block) = lennarb_instance.__add_route__(path, :POST, block)
135
+ def patch(path, &block) = lennarb_instance.__add_route__(path, :PATCH, block)
136
+ def delete(path, &block) = lennarb_instance.__add_route__(path, :DELETE, block)
137
+ end
45
138
  end
46
139
  end
data/license.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2023, by Aristóteles Coutinho.
3
+ Copyright, 2023-2024, by Aristóteles Coutinho.
4
+ Copyright, 2023, by aristotelesbr.
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -1,26 +1,47 @@
1
1
  # Lennarb
2
2
 
3
- Lennarb is a experimental lightweight, fast, and modular web framework for Ruby based on Rack.
3
+ Lennarb is a lightweight, fast, and modular web framework for Ruby based on Rack. The **Lennarb** supports Ruby (MRI) 3.0+
4
4
 
5
- ## Usage
5
+ **Basic Usage**
6
+
7
+ ```ruby
8
+ require "lennarb"
9
+
10
+ Lennarb.new do |router|
11
+ router.get("/hello/:name") do |req, res|
12
+ name = req.params[:name]
13
+ res.html("Hello, #{name}!")
14
+ end
15
+ end
16
+ ```
17
+
18
+ ## Performance
6
19
 
7
- Please see the [project documentation](https://aristotelesbr.github.io/lennarb) for more details.
20
+ ### 1. Requests per Second (RPS)
8
21
 
9
- - [Getting Started](https://aristotelesbr.github.io/lennarbguides/getting-started/index) - This guide show you how to use the `lennarb`
22
+ ![RPS](https://raw.githubusercontent.com/aristotelesbr/lennarb/main/benchmark/rps.png)
10
23
 
11
- - [Middlewares](https://aristotelesbr.github.io/lennarbguides/middlewares/index) - This guide shows how to use middlewares in Lennarb.
24
+ See all [graphs](https://github.com/aristotelesbr/lennarb/blob/main/benchmark)
12
25
 
13
- - [Namespace routes](https://aristotelesbr.github.io/lennarbguides/namespace-routes/index) - This guide show you how to use namespace routes.
26
+ | Position | Application | 10 RPS | 100 RPS | 1.000 RPS | 10.000 RPS |
27
+ | -------- | ----------- | ---------- | ---------- | --------- | ---------- |
28
+ | 1 | Lenna | 126.252,36 | 108.086,55 | 87.111,91 | 68.460,64 |
29
+ | 2 | Roda | 123.360,37 | 88.380,56 | 66.990,77 | 48.108,29 |
30
+ | 3 | Syro | 114.105,38 | 80.909,39 | 61.415,86 | 46.639,81 |
31
+ | 4 | Hanami-API | 68.089,18 | 52.851,88 | 40.801,78 | 27.996,00 |
32
+
33
+ This table ranks the routers by the number of requests they can process per second. Higher numbers indicate better performance.
34
+
35
+ Plese see [Performance](https://aristotelesbr.github.io/lennarb/guides/performance/index.html) for more information.
36
+
37
+ ## Usage
14
38
 
15
- ## Contributing
39
+ - [Getting Started](https://aristotelesbr.github.io/lennarb/guides/getting-started/index) - This guide covers getting up and running with **Lennarb**.
16
40
 
17
- We welcome contributions to this project.
41
+ - [Performance](https://aristotelesbr.github.io/lennarb/guides/performance/index.html) - 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 [template build](static/r10k/build/lennarb.rb).
18
42
 
19
- 1. Fork it.
20
- 2. Create your feature branch (`git checkout -b my-new-feature`).
21
- 3. Commit your changes (`git commit -am 'Add some feature'`).
22
- 4. Push to the branch (`git push origin my-new-feature`).
23
- 5. Create new Pull Request.
43
+ - [Response](https://aristotelesbr.github.io/lennarb/guides/response/index.html) - This is the response guide.
44
+ 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`.
24
45
 
25
46
  ### Developer Certificate of Origin
26
47