lennarb 0.1.7 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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